桌面壳(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 唤起。

一句话之后发生什么

  1. 你输入类似 清理下载文件夹。
  2. 壳在本地把它跟已装技能匹配——不接模型、不联网。分数接近的会列成候选;完全不像的会直接说没有,而不是硬猜:

`` 没有匹配的技能。 当前可用:dedupe-folders、downloads-cleanup、first-skill、… ``

  1. 面板展示能力申请单、步骤列表和汇总——全部来自 kernel.preview,只读。
  2. 执行 调 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 日志。

这一版故意没有的东西

不开界面也能用同一套逻辑

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 界面就是解析它。