mirage

3.5kStar259Fork107Issue9Watching

把 DeepSeek Harness 的文件与 shell 后端替换为 Mirage 工作区,让 dsh 的文件工具和 bash 直接操作 S3、Slack、Redis 等挂载资源。

语言
TypeScript
License
Apache-2.0
分支
main
agent-sandboxagent-toolsai-agentsbashclaude-codedshdsh-pluginfuse

安装

$ dsh plugin --profile web add github:strukto-ai/mirage

在终端中运行以上命令,通过 dsh CLI 安装此插件。可在右上角切换 Profile。 第一次用 dsh?看这篇新手教程

一句话定位

把 DeepSeek Harness 的文件与 shell 后端替换为 Mirage 工作区,让 dsh 的文件工具和 bash 工具直接读写通过挂载接入的 S3、Slack、Redis、Gmail 等数据源,而不再是宿主机的本地磁盘。

核心能力

  • 接管 dsh 的文件系统接口(ctx.fs):read、write、edit、stat、listDir 等都走 Mirage 工作区,所有操作都先经过挂载点模式和策略层
  • 接管 dsh 的 shell 接口(ctx.shell):bash 工具改为执行 Mirage 的 shell,POSIX 工具(grep、ls、cat、cp 等)跨多个挂载源统一可用
  • 提供声明式挂载:在 profile 的 cordis.patch.yml 里列出资源(resource 注册名、mode、config),token 等秘密通过 !!js process.env.X 在挂载时解析
  • 支持沙箱 Python(monty)和约 50 个内置后端(RAM、Disk、S3、Redis、Slack、Gmail、GDrive、Notion、Postgres、SSH 等),脚本可以跑在任意挂载路径下
  • 默认挂载一个 /tmp(RAM 内存盘,可执行模式),可用于临时文件和 Python 脚本输出
  • 支持持久 shell:给 shell 插件传 sessionId 后,cd、export、函数定义在多次调用之间保留
  • 当工作区里所有运行时都限定在 vfs 内时,自动向 dsh 报告 workspace-write 沙箱模式,让 dsh 的权限预设与之组合

技术实现

  • 语言: TypeScript(DSH 集成包);底层的 Mirage 另有 Python 实现,本插件只与 TypeScript 镜像交互
  • 关键依赖: @struktoai/mirage-node(提供 Workspace、资源注册表、shell 执行器)、@deepseek-ai/cordis(插件编排)、@deepseek-ai/dsh-fs@deepseek-ai/dsh-shell(DSH 的文件与 shell 能力缝隙,0.1.0-rc.6)
  • 架构模式: 通过 dsh 的 bundle 机制(package.json 里 dsh.bundle.patch 指向 cordis.patch.yml)加载;patch 关闭 dsh 自带的 fs-sandboxbash-sandboxpwsh-sandboxtool-pwshtool-fs-search 五个插件行,插入 mirage 的三件套(MirageService 持有工作区,MirageFileSystemMirageShellExecutor 各声明 inject = ['mirage'] 拿到它),最终让 ctx.fs 和 ctx.shell 共享同一个工作区
  • 入口文件: typescript/packages/dsh/cordis.patch.yml(DSH 加载入口)、typescript/packages/dsh/src/plugin/{service,fs,shell}.ts(每个插件的默认导出)

适用场景

你希望 dsh 中的 Agent 操作的不是宿主机原本的文件系统,而是 S3、Slack、Notion、Redis 等外部数据源里现成的对象;或者你希望给一次 Agent 会话一个干净的、跟宿主机隔离的"虚拟工作区",让 Agent 在里面读写、跑 Python 脚本、同步产出文件。也适合把已有的 Python+TypeScript Mirage 工作区直接嵌入到 DSH 体系里来使用。

前置依赖与兼容性

依赖最低版本说明
Node.js>=20.10.0由包的 engines 字段声明
DSH0.1.0-rc.6peerDependencies 固定该版本,未声明更宽范围
平台macOS / Linux / Windows包本身未声明 os/cpu 限制;底层如要走 FUSE 真挂载则需要平台支持
原生模块DSH 集成包本身不含原生依赖,运行时按需引入 mirage-node 的依赖

安装方式

dsh plugin --profile web add github:strukto-ai/mirage

配置项

配置类型说明默认值
MirageService.mounts对象声明式挂载:每项是一段 resource(注册名)/ mode(read/write/exec)/ config(资源配置);不传则必须传一个 live workspace,二选一不设
MirageService.workspaceWorkspace由调用方拥有生命周期的 Mirage 工作区;传入后插件不再自行构造不设
MirageService.runtimes字符串或对象数组运行时清单(monty、pyodide、quickjs 等),可与 workspaceOptions.runtimes 二选一不设
MirageShellExecutor.workdir字符串命令的默认工作目录;在绑定 sessionId 时用作该会话的初始目录/
MirageShellExecutor.sessionId字符串绑定到指定的工作区会话,所有命令在该会话内执行,cd/export/函数定义跨调用保留不绑定(每次调用独立子 shell)
MirageShellExecutor.defaultTimeoutMs毫秒单条命令的默认超时120000
MirageShellExecutor.maxTimeoutMs毫秒调用方请求超时的上限600000
MirageShellExecutor.stdoutMaxBytes字节stdout 捕获上限200000
MirageShellExecutor.stderrMaxBytes字节stderr 捕获上限64000
MirageShellExecutor.spillDir字符串后台命令超缓冲后的"溢出"文件目录(应为工作区路径,如 /tmp),Agent 后续可通过该路径读完整输出不设(超出会标记 truncated 而不落盘)
MirageFileSystem.cwd字符串相对路径解析的虚拟基目录/
MirageFileSystem.diffBasisMaxBytes字节写入回执中 before/after 字段的"前文本"上限10485760(10 MiB)

常见问题

Q: 装上之后 dsh 默认能看到什么内容?

A: 自动挂载了一个 /tmp 是 RAM 临时盘,权限是 exec(可执行脚本)。除此之外没有其它真实数据源;所有读写都发生在这次会话的内存里,关掉 dsh 就消失。需要真实数据源时,在 profile 里覆盖 mirage 那一行即可。

Q: 怎么把 Slack、Redis、S3 等真实数据源接进来?

A: 在 profile 自己的 cordis.patch.yml 里把 mirage 这条配置改成 mounts 块,写出 resource(注册名,如 slack/redis/s3)、moderead/write/exec)和 config(资源所需字段)。token 这种秘密用 !!js process.env.SLACK_BOT_TOKEN 在加载时解析,避免明文落盘。

Q: 启用之后 dsh 自带的 PowerShell 和 ripgrep 搜索怎么不见了?

A: 这两个工具会拉起宿主进程,工作区里没有它们的位置,所以这个 bundle 显式禁用了 pwsh-sandboxtool-pwshtool-fs-search。搜索能力由 bash 工具里的 grep 覆盖,覆盖所有挂载源。

Q: 怎么让 bash 工具在多次调用之间保持目录和变量?

A: 给 MirageShellExecutorsessionId 字段(如 'agent-1'),cd、export、函数定义就会跨调用保留;不传则每次调用都是独立子 shell,状态不持久。sessionId 相同表示接到同一个工作区会话,不同则完全隔离。

Q: 装上之后的工作区是只读还是可写?

A: 取决于每个 mount 的 moderead < write < exec 三档依次放开。默认的 /tmp 是 exec 档,可执行 bash 脚本。要让 dsh 的文件工具能写某个挂载(如把结果写回 Redis),就把它声明成 mode: writemode: exec

Q: 大输出量的命令会怎样处理?

A: 默认 stdout 缓冲 200KB、stderr 缓冲 64KB,超出会被截掉并标记 truncated。要保留完整输出就把 MirageShellExecutor.spillDir 指向一个工作区路径(如 /tmp),溢出部分会落盘成文件,路径在 readOutput() 里返回,Agent 可通过同一 VFS 读回。

Q: 它是 DSH 官方插件吗?

A: 不是 DSH 仓库自带的。它通过 dsh 的 bundle 机制(cordis.patch.yml)把自己的三个 Cordis 插件插入到 dsh 体系里,替换 dsh 的 ctx.fsctx.shell 两个能力缝隙。底层使用的是 DSH 0.1.0-rc.6 暴露的 fs/shell 插件接口。

Q: 怎么挂载自己写的后端?

A: 在调用方一侧 registerResourceFactory('my-store', (config) => new MyStore(config)),然后在 mount 块里写 resource: my-store 即可。注册逻辑必须在工作区构造之前完成,因此放在另一个由 profile 加载的插件里(或在代码里 MirageService 启动前直接导入)。内置名字不能被覆盖。

上手难度

进阶 — 装上即可用默认 /tmp 跑命令,但要让 Agent 真正拿到 Slack/Redis 等数据,就得在 profile 的 cordis.patch.yml 里写 mount 块、搞清楚 mode 三档和 !!js 表达式的语义;启用持久 shell、溢出目录、Python 沙箱等细节也都需要一段配置。

已知问题与限制

  • bundle 默认禁用 dsh 自带的 PowerShell 工具和基于 ripgrep 的全文搜索工具,因为它们跑宿主进程而工作区里没有它们的位置;要 search 改用 bash 工具里的 grep
  • 同一个目标路径的写操作会被串行化(按路径加锁),并发写时只有一个赢家,其它请求会观测到新版本并被旧版本守卫视为过期拒绝
  • 当工作区里包含了一个"绕过 VFS"的运行时(如宿主本地 Python 运行时),vfsOnly 会返回 false,shell 插件会向 dsh 报告 sandboxMode = undefined(即"没有沙箱"),而不是声明成 workspace-write;这种情形下 dsh 的权限预设不会和这个 shell 组合
  • spillDir 不设时,后台命令输出超出 stdout 缓冲只会标记 truncated,不会把完整输出写入文件;想"完整可读"必须显式给一个工作区路径
  • bash 工具默认在每次调用之间是隔离的(state 不持久),想保留 cwd/export/函数必须传 sessionId;同一个 sessionId 上不同 shell 实例共享同一个工作区会话
  • mount 模式强度按 read < write < exec 排列,模式弱的 mount 上脚本无法执行;让 Agent 跑 Python 脚本需要把 /tmp(或对应路径)显式声明为 mode: exec
  • 写操作前置的"陈旧版本"守卫基于后端的指纹或修改时间+大小,没法同时表征二者的后端会报告为 unversioned,写时不做版本比较