Shell 与 Sandbox
Workspace 持久 Sandbox、Shell Session、宿主执行与审批
Downcity 将四种职责分开:
City → Workspace
Workspace → Shell
Shell → SandboxProvider + Workspace 持久 Sandbox + Shell Sessions
Chat Session → 只负责调用每个 Workspace 拥有一个独立、持久的 Sandbox。同一 Workspace 下的多个 Chat Session 共享 Sandbox HOME、安装工具和缓存;不同 Workspace 不共享可写 Sandbox 状态。Shell Session 使用 shell_id 标识,是独立进程资源,不是 Chat Session。
安装与初始化
pnpm add @downcity/agent @downcity/city @downcity/sandbox-microsandbox
npx microsandbox setup默认 Provider 使用 microsandbox microVM 与 OCI 镜像,支持相应的 macOS、Linux 和 Windows 宿主。
import { Agent } from "@downcity/agent";
import { City, Shell, Workspace } from "@downcity/city";
import { MicrosandboxProvider } from "@downcity/sandbox-microsandbox";
const workspace = new Workspace({
id: "project",
path: process.cwd(),
shell: new Shell({
sandbox_provider: new MicrosandboxProvider(),
}),
});
const city = new City({
workspaces: [workspace],
});
const agent = new Agent({ id: "repo-helper", model });
city.agents.add(agent);不使用 City 的嵌入式宿主,需要向 Shell 提供 sandbox_provider,向 Workspace 提供 runtime_path。City 只自动分配运行目录,不选择或持有 Provider。
执行目标
Shell 只保留两个目标:
sandbox:默认值。命令在 Workspace microVM 中执行,项目固定挂载到/workspace。host:使用完整宿主权限,必须提供非空原因并经过宿主审批。
shell_exec({
cmd: "open ./report.pdf",
target: "host",
reason: "在宿主桌面应用中打开生成的报告。",
});系统不提供部分宿主路径白名单、中间权限模式或静默降级。Sandbox 不可用时直接失败,不会切换到宿主执行。
路径与 Workspace 挂载
文件与搜索工具在宿主进程中执行,Shell 命令在 Sandbox 内执行。两者都接受沙箱内路径,例如 /workspace/src/main.ts:沙箱路径会在路径策略之前映射回项目根,因此 /workspace/src/main.ts 与 src/main.ts 指向同一个文件,返回结果始终是宿主侧路径。挂载之外的路径(例如 /etc/hosts)仍会被拒绝。
工具与生命周期
Shell 提供适合短命令的 shell_exec,以及适合长任务和交互进程的 shell_session。后者支持 start、send、read、wait、list 和 stop。
关闭 Workspace 会释放其 Shell;Shell 先关闭全部 Shell Sessions,再停止 Sandbox 计算资源,同时保留 Sandbox 文件系统。使用相同 City storage 恢复同一 Workspace 时会重新连接该环境。只有显式调用 shell.reset_sandbox() 才会删除 HOME 和已安装工具。
Workspace 挂载在 Sandbox 创建时固定。如果稳定 Workspace ID 后续指向不同宿主路径或 City runtime path,Downcity 会拒绝连接并要求显式 reset,避免静默挂载意外文件。
环境变量
Sandbox 命令只获得显式 Workspace env 与 Downcity 执行标识,不继承完整宿主 process.env。Host 命令从宿主环境开始,再叠加 Workspace env。两种执行都会移除 Downcity 鉴权密钥。
workspace.set_env() 与 workspace.patch_env() 只影响后续命令;已启动进程继续使用创建时的环境快照。
失败提示
命令以非零退出码结束或 Shell 状态为 failed 时,工具返回 success: false 和明确的 error,包含退出码(如有)以及最多 2000 个字符的输出尾部。完整的返回输出仍保留在 output 中;交互会话的当前输出为空时,错误提示使用最近的输出预览。
审批未通过时返回 Host execution was not approved.。这也可能是审批网关或调用身份缺失,不能仅凭这条提示判断用户主动拒绝了请求。
本地 Agent 的 Session ID 不区分大小写,创建和查询入口统一为小写。新建和 fork 使用相同的 12 位随机 ID,例如 k7m2x9p4w6rt;字符排除容易混淆的 0/o、1/i/l。自动生成的 ID 直接作为目录名,存储层仍处理自定义标识中的特殊路径字符和 Windows 保留名称。
旧 Session 需要停机迁移后才能使用新规则。迁移脚本默认只预览:
node public/packages/agent/scripts/migrate-session-ids.mjs /path/to/agent-root
# 停止 Downcity,并检查预览与外部引用后执行:
node public/packages/agent/scripts/migrate-session-ids.mjs /path/to/agent-root --apply --offline工具检查同一来源分区内的大小写冲突,备份活动和归档目录,再更新数据库身份、消息来源与结构化附件路径并重命名目录。旧 ID 只转小写,不改成短 ID。备份目录中的 manifest.json 提供 ID 和路径映射;Task、Messenger、Power 或自定义 Composer 的外部引用由其维护者依据映射同步更新,完成后再启动 Downcity。迁移失败时保持停机,从备份恢复两棵 Session 目录。
审批
每次 host 启动,以及向 host Shell Session 写 stdin,都会经过当前 Chat Session 注入的审批网关。审批属于当前工具调用,但创建出的 Shell Session 仍是 Workspace Shell 资源。
const pending = await session.interactions();
await session.respond({
interaction_id: pending[0].request.interaction_id,
response: {
type: "approval",
outcome: "resolved",
payload: { decision: "approved" },
},
});ask 是默认审批模式。always-allow 只影响当前 Chat Session 后续的新 host 审批请求,不改变 Sandbox 隔离边界。