桌面壳(MVP)
壳是"一个框、一句话"的入口。它不新增能力——跑的是 CLI 跑的那个 kernel,只是把动手前的账单摆在你面前。
启动
# 在仓库根目录执行;需要桌面会话(Windows)
$env:DESKSHELL_NODE = "C:\path\to\node.exe" # 可选,默认用 PATH 里的 node
powershell -ExecutionPolicy Bypass -File apps/shell/ui.ps1 -Root "C:\Users\me\Downloads"
任何地方按 Ctrl+Alt+Space 唤起。
一句话之后发生什么
- 你输入类似
清理下载文件夹。 - 壳在本地把它跟已装技能匹配——不接模型、不联网。分数接近的会列成候选;完全不像的会直接说没有,而不是硬猜:
`` 没有匹配的技能。 当前可用:dedupe-folders、downloads-cleanup、first-skill、… ``
- 面板展示能力申请单、步骤列表和汇总——全部来自
kernel.preview,只读。 - 执行 调
kernel.apply;取消 调cancel(),别的什么都不做。
一句话 → 带参数的计划
匹配到技能只是一半,句子里还带着参数。skill.json 可以声明 intents:
"intents": {
"triggers": ["日志", "归档日志", "旧日志"],
"args": { "maxAgeDays": { "type": "days", "hint": "多少天之前的日志算旧日志?" } }
}
apps/shell/intent.ts 离线把这些槽填上——扩展名(jpg、图片)、天数(30 天、一周)、路径(带引号、下载文件夹、或 从 src 到 backup)、前缀(前缀 trip)。它返回 confidence、missing 和一句 question,所以面板是问你而不是猜:
批量重命名 → rename-batch,缺 ["prefix"],置信度 0.5
给我订一张机票 → null(不猜)
参数不是装饰:把 30 天前的日志归档 和 把 90 天前的日志归档 会得到不同的 plan,测试既比对步数不同,也验证执行后那份 2 天的日志真的没被动。
以后接模型只需要实现一个接口:
interface ModelBackend { name: string; resolve(query: string, skills: ShellSkill[]): Promise<IntentResult | null>; }
默认是 RuleBackend;FixtureBackend 是同一契约下的固定问答实现,测试用它证明这个接缝是通的。
"取消零副作用"是被测出来的,不是承诺
scripts/shell-test.ts:
PASS 取消返回明确信号
PASS 取消后文件系统零变化
PASS 取消后没有产生日志
PASS 预览不写日志
UI 自检(scripts/shell-ui-selftest.ts)更进一步:真的把窗口开起来、真的注册热键、跑一次预览、取消,然后检查夹具目录里仍然只有原来那两个文件,且没有 .deskshell 日志。
这一版故意没有的东西
- 没有模型。 匹配是对技能名和描述的关键词打分。说"把上周的截图按项目分一下"它不会变成计划——那是 M08 的事。
- 没有常驻服务。 面板就是个普通进程,进程退出热键就没了。
- 只有 Windows。
ui.ps1是 WinForms。核心(apps/shell/core.ts)与平台无关且已有测试,GTK/Qt 的壳可以直接复用。
不开界面也能用同一套逻辑
node apps/shell/cli.ts skills
node apps/shell/cli.ts match "清理下载"
node apps/shell/cli.ts preview downloads-cleanup --root .
node apps/shell/cli.ts apply downloads-cleanup --root .
node apps/shell/cli.ts cancel
每条命令输出一行 JSON——PowerShell 界面就是解析它。