内部实现
各部分到底怎么咬合,哪条规则在哪一层执行。写给要扩展它的人,不只是要跑它的人。
完整流程
checkCapabilities(caps, granted, root) 声明的能力必须全部被授予
↓
skill.plan(input, ctx) → Action[] 由受信技能代码生成计划
↓
checkAction(action, root, ids, caps) 逐动作:路径逃逸、窗口作用域、可撤销性
↓
preview ─ 打印后停止
apply ─ 每个动作:前置断言 → 执行 → 后置断言 → 写日志
undo ─ 读日志 → 逆序回放撤销 → 标记 rolled-back
以上全是同步的文件系统操作加驱动调用。没有调度器、没有重试循环、没有模型。
规划约定为只读,但技能当前运行在宿主进程中,可以直接访问文件系统和 driver;该约定不是沙箱。预览和执行还会分别调用一次 plan()。固定计划与可信授权边界见 RFC 0001 草案。
策略层到底查什么
| 检查 | 位置 | 挡住什么 | | --- | --- | --- | | 能力是否被授予 | checkCapabilities | 拿用户没给过的权限干活 | | fs.* 作用域在 root 内 | checkCapabilities | 声明 C:\ 当作用域 | | 动作被声明的能力覆盖 | checkAction | 声明 ui.read 却去点鼠标 | | 路径词法在 root 内 | checkAction → isInside | .. 穿越、绝对路径 | | 路径真实位置在 root 内 | checkAction → isInsideReal | root 内指向外部的 junction / 软链接 | | 窗口落在声明作用域内 | checkAction | 声明"记事本"却操作"计算器" | | 动作可撤销 | checkAction | fs.delete 配 undo: null |
真实路径检查会沿最近的存在祖先解析,所以目标还不存在也能查;对 fs.move 的源只查父目录——搬走一个快捷方式本身是合法操作。
失败语义
动作失败时,内核依次做四件事:
- 把失败写进日志(
status: "failed"+detail); - 撤掉失败动作自身的副作用——但只有真的进入过执行阶段、且效果确实存在时才撤(
selfUndoSafe),避免"前置断言失败却把别人的文件搬走"这类事故; - 逆序回滚所有已完成的步骤;
- 只把确实撤销成功的步骤标成
rolled-back。
如果撤销本身失败(比如文件被改过),那一步在日志里保持未撤销状态。日志不会谎报回滚。
日志是唯一事实来源
<root>/.deskshell/journal.jsonl,一行一个 JSON 对象:
{"ts":1793281142815,"taskId":"task-1793281142815","skill":"photo-sort","actionId":"move:IMG_0001.jpg",
"kind":"fs.move","summary":"move IMG_0001.jpg → …/2026-03/IMG_0001.jpg","status":"ok",
"undo":{"kind":"fs.move","from":"…/2026-03/IMG_0001.jpg","to":"…/IMG_0001.jpg"}}
taskId把一次run()归组;skill让deskshell history可读;hash出现在fs.write步骤上:写入内容的 sha256。撤销时若文件当前哈希不同就拒绝覆盖——人工改过的内容不会被悄悄冲掉;status只有ok/failed/rolled-back。
deskshell history 汇总任务,deskshell show <taskId> 打印单次任务时间线。
并行执行
deskshell run a b c --parallel 在同一个进程里并发跑多个 skill:各自保留自己的计划和上下文,共用一份日志。日志写入是同步的,所以追加不会交错丢行;markRolledBack 的读-改-写也在同一个 tick 内完成。
一个 skill 失败会让 CLI 以非零退出,但不会中断其它 skill——有测试专门把"必然失败"的 skill 和 photo-sort 放在一起跑。
skill 放在哪
packages/skills/—— 内置,随包发布;~/.deskshell/skills/(或$DESKSHELL_SKILLS_DIR)—— 通过deskshell add安装。
同名时用户目录优先;因为它在包外,升级 deskhell 不会把它冲掉。
扩展点
- 新 skill —— 一个目录,
skill.json+index.ts,只写plan()。 - 新驱动 —— 实现
UiDriver、通过 13 项契约测试;内核不动。 - 模型 —— 坐在
plan()之上,产出的动作照样过同一套策略检查;内核里没有任何模型引用。
尚待建立的能力
上文描述的 v0 内核本身不提供持久任务队列或常驻服务。新 agent 已在其外建立常驻宿主、SQLite 固定计划/批准/认领、逐步效果与有限恢复,并接入独立记事本任务和固定授权多轮;旧 CLI/Shell/MCP 尚未迁移。数据库仍不能将系统效果与日志提交变成同一原子事务,未知结果不自动重放;第三方插件沙箱尚未实现。当前实现与边界见任务运行时及路线图。