@dsh-workbench/core 0.3.9 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +19 -7
  2. package/bin/dsh-workbench +2 -2
  3. package/client.js +1687 -1022
  4. package/cordis.patch.yml +60 -35
  5. package/dist/client/client.body.js +1687 -1022
  6. package/dist/client/index.js +1372 -707
  7. package/dist/host/index.js +2897 -2335
  8. package/dist/types/app/client.d.ts +2 -1
  9. package/dist/types/app/layouts/env-gate/boot-gate.d.ts +1 -1
  10. package/dist/types/app/layouts/market/use-module-versions.d.ts +13 -2
  11. package/dist/types/app/layouts/rightbar/MarketView.d.ts +6 -3
  12. package/dist/types/app/layouts/rightbar/market-act.d.ts +6 -3
  13. package/dist/types/app/layouts/rightbar/market-rows.d.ts +1 -1
  14. package/dist/types/app/layouts/rightbar/market-settle-hold.d.ts +2 -0
  15. package/dist/types/app/layouts/rightbar/market-settle.d.ts +20 -0
  16. package/dist/types/app/layouts/rightbar/market-shared-rows.d.ts +0 -1
  17. package/dist/types/app/layouts/rightbar/market-update-verdict.d.ts +41 -0
  18. package/dist/types/app/layouts/rightbar/market-view-text.d.ts +4 -1
  19. package/dist/types/app/layouts/rightbar/store.d.ts +14 -0
  20. package/dist/types/app/layouts/sidebar/LongTaskDot.d.ts +12 -0
  21. package/dist/types/app/layouts/sidebar/LongTaskHover.d.ts +12 -0
  22. package/dist/types/app/layouts/sidebar/edition-boot.d.ts +24 -0
  23. package/dist/types/app/layouts/sidebar/edition.d.ts +22 -0
  24. package/dist/types/app/layouts/workbench-settings/ConfigSyncItem.d.ts +5 -0
  25. package/dist/types/app/layouts/workbench-settings/SettingsItem.d.ts +17 -0
  26. package/dist/types/app/layouts/workbench-settings/SidebarEditionItem.d.ts +11 -0
  27. package/dist/types/app/layouts/workbench-settings/WorkbenchSettingsSection.d.ts +11 -0
  28. package/dist/types/modules/panels/model/approval.d.ts +12 -4
  29. package/dist/types/modules/panels/model/diy-browser-tools.d.ts +1 -1
  30. package/dist/types/modules/panels/model/diy-context.d.ts +31 -14
  31. package/dist/types/modules/panels/model/diy-page.d.ts +3 -2
  32. package/dist/types/modules/panels/model/diy-record.d.ts +3 -3
  33. package/dist/types/modules/panels/model/diy-report.d.ts +6 -7
  34. package/dist/types/modules/panels/model/market-dependency-update.d.ts +21 -0
  35. package/dist/types/modules/panels/model/market-dispatch.d.ts +1 -0
  36. package/dist/types/modules/panels/model/market-types.d.ts +1 -0
  37. package/dist/types/modules/panels/model/market-update-bundle.d.ts +25 -0
  38. package/dist/types/modules/panels/model/market-verify.d.ts +31 -0
  39. package/dist/types/modules/panels/unit.d.ts +3 -4
  40. package/dist/types/shared/api/bundle-install.d.ts +13 -0
  41. package/dist/types/shared/api/host/approval-gate.d.ts +39 -0
  42. package/dist/types/shared/api/host/bundle-routes.d.ts +2 -1
  43. package/dist/types/shared/api/host/diy-artifacts.d.ts +14 -0
  44. package/dist/types/shared/api/host/diy-modules.d.ts +41 -0
  45. package/dist/types/shared/api/host/host-routes.d.ts +13 -0
  46. package/dist/types/shared/api/host/permission-mode.d.ts +9 -0
  47. package/dist/types/shared/api/host/runtime-seed.d.ts +2 -0
  48. package/dist/types/shared/api/host/self-update.d.ts +9 -2
  49. package/dist/types/shared/api/host/sidebar-edition.d.ts +46 -0
  50. package/dist/types/shared/api/host/workspace-root.d.ts +13 -0
  51. package/dist/types/shared/api/sidebar-edition.d.ts +14 -0
  52. package/dist/types/shared/api/unit-board.d.ts +20 -0
  53. package/dist/types/shared/api/write-outcome.d.ts +15 -0
  54. package/dist/types/shared/components/IconModules.d.ts +8 -0
  55. package/dist/types/shared/components/blocker-action.d.ts +16 -0
  56. package/dist/types/shared/hooks/sidebar-edition.d.ts +7 -0
  57. package/dist/types/shared/hooks/use-capabilities.d.ts +5 -0
  58. package/dist/types/shared/hooks/use-capability-gate.d.ts +1 -2
  59. package/dist/types/shared/hooks/use-host-write.d.ts +1 -1
  60. package/dist/types/shared/hooks/use-unit-board.d.ts +1 -1
  61. package/docs/HANDOFF.md +3 -3
  62. package/docs/OPERATIONS.md +16 -5
  63. package/package.json +3 -3
  64. package/preset/diy.patch.yml +2 -2
  65. package/profile/isolation.patch.yml +11 -0
  66. package/skills/workbench-module-diy/BUILD.md +62 -131
  67. package/skills/workbench-module-diy/COPY.md +89 -0
  68. package/skills/workbench-module-diy/DATASOURCE.md +84 -112
  69. package/skills/workbench-module-diy/DESIGN.md +75 -109
  70. package/skills/workbench-module-diy/FAMILY.md +70 -54
  71. package/skills/workbench-module-diy/KIT.md +77 -133
  72. package/skills/workbench-module-diy/LINKAGE.md +66 -105
  73. package/skills/workbench-module-diy/PANEL.md +79 -171
  74. package/skills/workbench-module-diy/REVIEW.md +67 -81
  75. package/skills/workbench-module-diy/SKILL.md +57 -96
  76. package/skills/workbench-module-diy/STEPS.md +66 -69
  77. package/skills/workbench-module-diy/TOOLING.md +34 -51
  78. package/skills/workbench-module-diy/TOOLS.md +110 -103
  79. package/skills/workbench-module-diy/WIRING.md +51 -83
  80. package/skills/workbench-module-diy/WRITES.md +89 -142
  81. package/dist/types/app/layouts/config-sync/ConfigSyncSection.d.ts +0 -5
  82. package/dist/types/shared/api/host/diy-workshop.d.ts +0 -23
  83. package/dist/types/shared/api/host/workshop-root.d.ts +0 -16
  84. /package/dist/types/shared/{api/host/diy-workshop.test.d.ts → components/blocker-action.test.d.ts} +0 -0
@@ -1,131 +1,138 @@
1
- # agent 能力:他能替用户做什么,哪几件要用户点头
1
+ # agent 能力:划分、入参返回、审批
2
2
 
3
- 步骤 2 的参考:agent 传什么拿回什么、用户看到什么(不是怎么实现)。
3
+ 步骤 2 的参考。
4
4
 
5
- ## 1 能力怎么划
5
+ ## 1 能力划分
6
6
 
7
- | 情况 | 结论 |
7
+ | 情况 | 处理 |
8
8
  |---|---|
9
- | 同一件事、同一份数据,只是"要不要展开明细" | **一件事**,加一个"展开"的选项 |
10
- | 不同的动作、不同的后果(看 vs 改) | **分开** |
11
- | 用户要反复做、每次都得他点头 | 别硬拆成一堆小事;封成一个动作(一个按钮 + 一件事) |
9
+ | 同一件事、同一份数据,只是要不要展开明细 | 一件事 + 一个"展开"选项 |
10
+ | 动作或后果不同(看 vs 改) | 拆开 |
11
+ | 用户反复做、每次都要点头 | 封成一个动作(一个按钮 + 一件事),不拆成多个小工具 |
12
12
 
13
- 每件事都要能回答:"用户会怎么跟 agent 说这件事?"
13
+ 每件事都要能回答:"用户会怎么对 agent 说这件事?"
14
14
 
15
- ## 2 传什么,怎么写清
15
+ ## 2 入参
16
16
 
17
- **贴真实的那一段**:能力 = 真实调用(参数 JSON)+ 真实返回正文(**照抄,不转述**);schema 也照抄一份(模型每轮都收到它)。能打到平台就**真跑一遍**(连时间戳);打不到就用真实代码的输出、标出样例;实现后换真机记录。
17
+ 描述、返回、报错的语言口径见 [COPY.md](COPY.md) §4–§5。
18
18
 
19
- - **字段名**用人和 agent 都懂的话(别用平台内部字段名)。**默认值**:不给他什么,等于什么。**范围**:只认哪几个值,不对就拒绝并说清怎么改。
20
- - **上限**:要多少给多少,**不悄悄截**;超了说清上限;截断要说清截了什么、完整那份在哪。
21
- - **该先问谁**:要先拿到别的东西,就在描述里指给 agent 看("先调 X")。
22
- - **会改变行为的话要留着**:默认只预览、要点头、别连刷、同一个做了一半的接着问。
19
+ 能力 = 真实调用(参数 JSON)+ 真实返回正文(照抄)+ schema。能连平台就真跑一遍。
23
20
 
24
- ## 3 拿回什么
21
+ - 字段名用人和 agent 都懂的话,不用平台内部名。
22
+ - 默认值、取值范围、给错时的修法都写明。
23
+ - 上限写清;要多少给多少,不悄悄截;截断写清截了什么、完整版在哪。
24
+ - 有前置依赖就在描述里指路("先调 X")。
25
+ - 保留会改变行为的话:默认只预览、需点头、不可连刷、半途可续。
25
26
 
26
- - 只放 **agent 推不出来的事实**:口径 + 数字 / 每一行 + 平台链接 + "要更多明细该走哪一步"。
27
- - 口径复述、"这是最后一批"这类全删;**总数读不到就说读不到**,不拿这一页冒充实测总数;每页都能往下走(能接着问的标识 + 还有多少 + 下一批怎么要)。
28
- - 长编号原样保留(别让它被当成数字)。
27
+ ## 3 返回
29
28
 
30
- ## 4 每件事的全场景(一格不许空)
29
+ - 只放 agent 推不出的事实:口径 + 数字 / 每行 + 平台链接 + "要明细该调哪个能力"。
30
+ - 删掉口径复述、"这是最后一批";总数读不到就说读不到,不用当前页冒充。
31
+ - 每页可续:续取标识 + 剩余量 + 下一批怎么取。
32
+ - 长编号按字符串保留。
31
33
 
32
- 每一格写**真实的返回或真实的报错原文**(照抄),不写转述。
34
+ ## 4 全场景(每格必填,写真实返回或真实报错原文)
33
35
 
34
- | 情况 | 必须给什么 |
36
+ | 情况 | 内容 |
35
37
  |---|---|
36
38
  | 正常 | 口径 + 数字 / 行 + 链接 |
37
39
  | 没有 | 说"没有"并给依据(不是报错)+ 下一步 |
38
- | 分不清是哪一份 | 举出候选让他选;**不许猜、不许悄悄用默认那份** |
39
- | 参数不对 / 超出上限 | 说清哪个字段该给什么 / 拒绝并说清上限 |
40
- | 能力还没装好 | 缺的是哪一件 + 该去哪更新它 |
41
- | 平台要登录 / 超时 | 分开说 + 一条重试或去登录的路 |
42
- | 还有下一批 / 太长截断 | 这一页给了什么 + 下一批怎么要 / 截了什么、多少、完整那份在哪 |
43
- | 没有正在做的项目 | 说清这件事按哪个项目取数 |
40
+ | 分不清是哪一份 | 列出候选让用户选;不猜、不退回默认 |
41
+ | 参数错 / 超上限 | 指名字段与合法值 / 拒绝并给上限 |
42
+ | 能力未装好 | 缺哪件 + 去哪更新 |
43
+ | 要登录 / 超时 | 分开报 + 重试或去登录 |
44
+ | 下一批 / 截断 | 本页内容 + 续取方式 / 截了什么、多少、完整版在哪 |
45
+ | 没有当前项目 | 说明按哪个项目取数 |
44
46
 
45
- ## 5 动手的事:两拍
47
+ ## 5 写操作:两拍
46
48
 
47
- 1. **预览**:说清**将要做什么**(哪个对象、什么值、到哪去)再请他确认 —— 让他能判断"要不要做",不是"要不要允许"。
48
- 2. **真的做**:他确认后才动平台,并回话说做完了。
49
+ 1. 预览:说清对象、值、去向,等确认。用户判断"要不要做"。
50
+ 2. 执行:确认后动平台,回报结果。
49
51
 
50
- 一件事只做一条链路:他按按钮走的和 agent 走的是**同一件事**;**重复发起要安全**:先看平台上是不是已经有一条,同一对象正在做就接着看那一次。
52
+ 一个动作一条链路:用户按按钮与 agent 调用是同一件事;重复发起要幂等(先查是否已有,进行中则续看)。
53
+ 用户自按入口不点头:最小确认条(将要做什么 + 取消 / 执行),按下变"正在做…",失败留在条内说明。
51
54
 
52
- ## 6 哪几件要用户点头
55
+ ## 6 审批范围
53
56
 
54
- 命中任一就点头:① 改**平台上**的东西;② 改**这台机器**(装 / 卸 / 更新);③ 改**他自己的面板**(加 / 收 / 排序 / 清空)。
57
+ 命中任一就审批:① 改平台;② 改这台机器(装 / 卸 / 更新);③ 改用户面板(加 / 收 / 排序 / 清空)。
55
58
 
56
- 点头时用户要看到**这次真的会发生什么**(哪个对象、什么值 / 版本、到什么环境、影响几台;一句"这一步会真的做什么")—— **不是**"这个操作会写数据"这种空话。**说不清这次会发生什么,就不许问。**
59
+ 审批内容必须是这次的具体后果:对象、值 / 版本、环境、影响数量。不写"这个操作会写数据"。
60
+ 说不出后果就不发起审批:停止,把原因交给 agent 去查。
57
61
 
58
- ### 6.1 写法(清单 + 两个函数;缺一个门禁就红)
62
+ ### 6.1 声明写法(缺一个 `contracts` 判据红)
59
63
 
60
- 写法:清单 `tools[]` 一条(`writes: true` + `key` 短名),加 `needsApproval(call)` 与 `approvalDetail(call)` 两个函数(形状照 `template-diy/` 的实例;`check --only contracts` 逐条点名缺什么)。
64
+ 清单 `tools[]` 一条(`writes: true` + `key`),加 `needsApproval(call)` 与 `approvalDetail(call)`。
61
65
 
62
- - `key`(lowerCamelCase):给了就跑一次 `dsh-wb-module sync` 生成 `src/declared-tools.ts`,代码写 `DECLARED_TOOLS.rebuild.name` —— 工具名字面量只剩清单这一处(键写错编译错;生成物过期 / 半写 key / 又写字面量都红)。
63
- - `needsApproval` 回 `{ kind: 'read' }` = 不问,`{ kind: 'ask', question, fallback }` = 要问;**`writes: true` 却不认领 = 当场 deny**。`check --only contracts` 先替你看:缺 `needsApproval`、zh/en 不齐、`approvalDetail` 没给 → 红。
64
- - `question` 是问句、`fallback` 是拼不出细节时的兜底,都要 zh/en;`approvalDetail` 是 §7 那段明细(一行一个事实),拿不到料回 `{ blocked: '…' }` = **不弹**。缺东西先自己走一遍预览摆事实,补不齐就停下把原因交给 agent 查。
65
- - **那屏的样子是定死的**([DESIGN.md](DESIGN.md) §3 末节):标题**只有一句**(`approvalDetail` 那块明细 ≤20 字一行,且**只给没有详情表的能力**);**事实与长内容走客户端面的 `approval.render`**,用外壳的 `ApprovalPlan` 画成两列表 —— 详情座那边等宽、能换行,路径 / diff / 多字段都放得下。宿主算的料到不了浏览器侧,事实要从**工具参数**重建。
66
+ - `key` 用 lowerCamelCase;跑 `dsh-wb-module sync` 生成 `src/declared-tools.ts`,代码写
67
+ `DECLARED_TOOLS.rebuild.name`。工具名字面量只留清单一处;键错编译错,生成物过期 / 半写 / 又写字面量都红。
68
+ - `needsApproval` 返回:
69
+ `{ kind: 'read' }` 不问;`{ kind: 'ask', question, fallback }` 要问;
70
+ `{ kind: 'deny', reason }` 拒绝(reason 交给 agent;只放策略同步可判的事实)。`writes: true` 未认领 = deny。
71
+ `question` / `fallback` 需 zh / en。
72
+ - `approvalDetail` 拿不到材料时返回 `{ blocked: '…' }` = 不弹。标题与详情形状见 [DESIGN.md](DESIGN.md) §3 末节:
73
+ 标题一句;`approvalDetail` → 仅无详情表的族,≤20 字一行;长内容走客户端 `approval.render` 的 `ApprovalPlan`。
74
+ 宿主数据到不了浏览器,事实从工具参数重建。
66
75
 
67
- ## 7 点头的全场景
76
+ ## 7 审批全场景
68
77
 
69
- | 情况 | 用户看到什么 | agent 得到什么 |
78
+ | 情况 | 用户看到 | agent 得到 |
70
79
  |---|---|---|
71
- | 预览 | 什么都不弹 | 预览的话:将要做什么 |
72
- | 真的做,且料齐 | 问话:**这次会发生什么** + "允许吗" | 等他答 |
73
- | 料补不齐 / 项目对不上 / 这件事没开 | **不弹**(他不该被问) | 停下的原因 / 说清是哪个项目 |
74
- | 他拒绝 / 他点头之后中途变了 | 弹窗关掉 / 料是当时那一份 | 被拒的原因 / 失败原因要能照做 |
75
- | 他这一轮没回答就中断 / 后台不许问 | 弹窗消失 / 不弹 | 这一轮结束 / 自动不许做 |
76
- | 一口气连着要好几件 | 一次一条,逐条问 | 逐条等 |
77
-
78
- 按钮、快捷键、弹窗都是现成的;模块只决定**里面那块明细画什么**:**一行一个事实**。
79
-
80
- ## 8 外部供给:这个能力要靠什么才跑得起来
81
-
82
- 模块要的东西机器上不一定有(opencli 插件、内网源 npm 库)。**清单的 `env` 段就是说这件事的**:外壳的隔离运行时按它装、探,状态在 `workbench_modules_list` 里逐条看得到。形状照抄:`env: { command, libs: [{ name, registryEnv, registryDefault, purpose }], plugins: [{ name, commands, installSource }] }`。
83
-
84
- - `libs[]` 四个字段都要:`registryEnv` 让**用户的源**覆盖 `registryDefault`(换源不用改代码),`purpose` 是给用户看的那半句"装它干嘛"。`installSource` 三种:`{kind:'npm', package, registryEnv}` / `{kind:'git', url}` / `{kind:'local', env}`。
85
- - **没装好时那句话由你写**(外壳不知道你这个能力叫什么):`libInstallCommand({ need })`(`@dsh-workbench/shared/host`)连源与缓存目录一起拼好,**原样给用户、别自己拼 npm**;卡片上放进失败态([PANEL.md](PANEL.md) 三态里那条"照做")。
86
- - 判据(`check --only contracts`):`env` 不能空;`libs` 缺 `purpose`/`registryEnv`、`registryDefault` 不是 `http(s)://` 或 `file://`、`installSource` 写错都红;**声明了 `libs` 就必须用 `libInstallCommand`**。
87
-
88
- ## 9 长任务:跑一会儿的事
89
-
90
- 跑几十秒的事(构建、发布、批量删)做成**长任务**。宿主半侧用 `mount` deps 里的 `longTasks` 建注册表并挂回返回值:`longTasks.create({ key, run, step, outcome, view, stopped })` —— `run({ payload, onStep, signal })` 里用 `onStep({ kind: 'start', text })` 报步骤、`return` 结果;`step`/`outcome`/`view` 各映射一段给界面;返回里带上 `longTasks: runs`。
91
-
92
- **来源要记**(起这一趟的是谁):在起它的那把工具里用 `execute(args, exec)` 给的现场,`runs.start({ …, session: sessionIdOf(exec) ?? undefined })`(`sessionIdOf` 从 `@dsh-workbench/shared/host` 取)。**不记 = 用户在界面上按的**(或这一版之前起的)—— 保守的那一侧:停它要走审批。
93
-
94
- 浏览器半侧用 kit 现成的 `useLongTask`(→ `feed.runs` / `feed.stop(id)` / `feed.stopping` / `feed.stopFailed`),画用 `LongTaskRow` 或 `longTaskDot`(每条的字段与**五种**状态 —— `running` 进行中 / `needs-login` 要登录 / `stopped` 停下的(灰点)/ `ok` 完成 / `failed` 出错 —— 见 `.d.ts`)。`stopped` 是后加的第五态:自己穷举过四态的地方(`switch` 没 `default`)会 tsc 报错,那是提醒你把它补上。
95
-
96
- **要给用户那颗「停止」钮,两半都得做**:
97
-
98
- - **认信号**:`run` 收到的 `signal` 被 abort = **有人按了停止**(用户在卡片上按的,或 agent 用 `workbench_long_tasks_stop` 停的)。在你能收手的地方(每轮循环、每条子命令前后)看一眼 `signal.aborted` 就收工。**不认就别画那颗钮** —— 画了就是骗人:外壳只会把这一趟标成 `stopped`,你的活儿还在跑(内部是 opencli 子进程那类编排,今天最多做到"我们不再等它",平台侧那趟照旧跑完)。
99
- - **画钮**:给那一行的 `LongTaskRow` 传 `stop`(`onStop: () => feed.stop(run.id)`、`busy: feed.stopping === run.id`、`failed: feed.stopFailed`、`label` 用 dsh 那句 `停止任务 {label}`(`{label}` = 这一趟的名字)、`askLabel: '确认停止'`)。**词与取值照 dsh 任务列表那行来**:没上膛的 aria `停止任务 {label}` → 上膛后可见字 `确认停止`、aria `再次点击确认停止`,3 秒自己落膛。**失败那半是我们自己的口径**:不往钮里写"停止失败"四个字(那是 dsh 词表里的一项),而是把这次请求失败的**原话**画在那一行下面 —— `failed: feed.stopFailed` 给什么就画什么(例如 `网络不通,停止请求没发出去`),不给就不画。两按确认(第一次只上膛、再按才真发、请求在飞时按不动)归组件,调用点只管上面这几样;**没传 `stop` 的行一个像素都不变**。
100
- - **多行同时在跑要留意**:`feed.stopping` 与 `feed.stopFailed` 都只有**一格**("最后一次"),不分行 —— 停 A 再停 B,`stopping` 变成 B,A 那行**提前解禁**(再按一下只是再发一发,外壳回 `{ ok: true, stopped: false }`:无害但白费);某一次停止失败,那句原文会画在**每一个**你传了 `failed: feed.stopFailed` 的行下面(`LongTaskRow` 只认 `failed` 这个字符串,不认 id)。**要分行报成败、要精确禁用就自己按 id 记**(比如 `useState` 记"在飞的那趟 id"与"那趟失败的话");单行卡片用现成这一格就够,嫌它糊就别把 `stopFailed` 传给所有行。
101
-
102
- **外壳自带两把通用工具,你不用自己写**:`workbench_long_tasks_list`(读)看**当前工作区**跑着什么、**谁起的**(`我起的` / `用户按的` / `别的会话起的`),`workbench_long_tasks_stop`(写)停一趟。审批口径:**自己起的免审批**(这一趟记的 `session` 就是调用方那个会话);**别人的 / 用户按的 / 没记来源的要审批**(那一屏外壳自己认领,画"要停哪一趟"+ 一句"停了只是让外壳发出中断信号");**不在你当前工作区的直接拒绝**(工作区是边界,仓内 ADR-0018)。
103
-
104
- **被停时会通知发起那次运行的会话**:用户(或别的会话)停下你起的这一趟,外壳给**起它的那个会话**发一条 notice —— 那会话正在跑就 inject 一条,闲着就跳过;**跑完 / 失败不发**(那是你自己那把工具的活,它起的那趟它自己报)。你不用在模块里自己发。
105
-
106
- **为什么不走 `ctx.jobs`**:dsh 的 job 归**一次 agent 会话**(teardown 主动取消、读口按会话围栏),我们的长跑归**工作区的一个目标**(`dispose` 刻意不取消)—— 归属轴不同;换过去要改归属口径,而它的 `JobStatus` 里没有 `needs-login`、`JobView` 里也没有工作区字段。理由、代价与"只借形状"那四件见仓内 ADR-0069(这里不链仓内路径)。
107
-
108
- 判据(`check --only contracts`):注册表**七个**方法要全(含 `stop` / `live`)、`create` 的 `key`/`run`/`view` 不能缺、产物里必须有 `useLongTask`/`LongTaskRow`/`longTaskDot` 之一。**它只查形状与"画得出来"** —— "你真认了 signal 吗、真收得住手吗"不在它的射程里(那只有你自己知道,见上面第一条)。
109
-
110
- **左栏那颗 loading 点不用你做**:有长任务在跑时,起它的那条会话行、以及项目下拉里有它的那个工作区那一行,各自那颗状态点会自己转(外壳按 `live()` 读,不看是谁起的;用户在面板上按的那趟也会点亮它所在工作区那一行)。
111
-
112
- **要用 `stop` 这个新 prop / 新返回字段,就得装带这次改动的那份外壳**:把自己 `peerDependencies['@dsh-workbench/core']` 的下限抬到那一版 —— `check --only kit` 盯着这条(产物实际取的 kit 成员 ⊆ 你 peer 下限那份外壳的 kit)。**同理**:`start` 收 `session`、外壳那两把 `workbench_long_tasks_*` 工具,也都是这一版才有的。
113
-
114
- ## 10 用户自己按的那个入口 / 范例
115
-
116
- 他自己按按钮发起的事**不问他**:最小确认条(将要做什么 + 取消 / 执行),按下去按钮变"正在做…",失败留在条里说原因;和 agent 走同一件事、同一进度、同一结果。范例(同形状、能跑):[SKILL.md](SKILL.md) 的「找不到一件事的时候」—— 模板与装机模块源码都能直接读。
117
-
118
- ## 12 同一把工具,在有些项目里不该出现
119
-
120
- 工具是**这台机器上的**,但项目不止一个:在 A 有意义的到 B 就是噪音。**别让工具自己判断再回"这个项目用不了"**,当场从清单里拿掉 —— `gate: { deniedTools(workspace), subscribe(listener) }`(挂进 `mount` 返回值):
121
-
122
- - `deniedTools(workspace)` 回"这个项目里我的哪几个工具不存在"(名字跟 `registerTools()` 注册的一致;`workspace` 也可能是 `null`)。`subscribe(listener)`:你状态变了(用户把能力关了)时通知外壳重算 —— 不接要等下次重启。
123
- - 外壳两道都做:清单里不出现 + 真调到时当场拒。它自己就这么用:工坊那一族 **33 把**(14 枚举 + 12 看 / 装 / 探 + 4 调试 + 3 录制交接,见 [FAMILY.md](FAMILY.md))只在工坊会话里出现。
124
-
125
- **"哪些工作区出现"不用你声明**(「模块开发者不能定义 scope」):他用 `workbench_panels_show` / `_hide` 放上 / 收起,卡片与**全部**工具同判。`deniedTools` 可选。
126
-
127
- ## 13 工坊会话里那一族:33 把
128
-
129
- 那一族(枚举 opencli 的 14 把 + 看 / 装 / 探 12 把 + 调试约定 4 把 + 录制交接 3 把)与它的前置、
130
- 参数、如实边界,全表在 [FAMILY.md](FAMILY.md) —— 这一份讲**你写的模块**的能力怎么写;
131
- 那一份讲**工坊会话里 agent 手上真实有什么**。为什么按 opencli 子命令枚举见仓内 ADR-0062(这里不链仓内路径)。
80
+ | 预览 | 不弹 | 预览内容 |
81
+ | 执行且材料齐 | 这次会发生什么 + 允许吗 | 等待回答 |
82
+ | 材料不齐 / 项目不符 / 能力关闭 | 不弹 | 停止原因 / 项目说明 |
83
+ | 拒绝 / 点头后中途变化 | 关闭 / 当时那份材料 | 拒绝或失败原因,可照做 |
84
+ | 本轮中断 / 后台 | 消失 / 不弹 | 本轮结束 / 自动禁止 |
85
+ | 连发多件 | 逐条询问 | 逐条等待 |
86
+
87
+ 按钮与弹窗由外壳提供;模块只决定明细画什么:一行一个事实。
88
+
89
+ ## 8 外部供给
90
+
91
+ `env` 声明能力依赖,外壳隔离运行时按它装、探,状态见 `workbench_modules_list`。
92
+ 形状:`env: { command, libs: [{ name, registryEnv, registryDefault, purpose }], plugins: [{ name, commands, installSource }] }`。
93
+
94
+ - `libs[]` 四项必填:`registryEnv` 让用户源覆盖 `registryDefault`;`purpose` 是给用户的一句用途。
95
+ `installSource`:`{kind:'npm', package, registryEnv}` / `{kind:'git', url}` / `{kind:'local', env}`。
96
+ - 未装好文案由模块写:`libInstallCommand({ need })`(`@dsh-workbench/shared/host`)拼好源与缓存目录,原样输出;
97
+ 放进失败态。
98
+ - `contracts` 判据:`env` 非空;`libs` 缺 `purpose` / `registryEnv`、`registryDefault` 非 `http(s)://` 或 `file://`、
99
+ `installSource` 错误都红;声明 `libs` 必须用 `libInstallCommand`。
100
+
101
+ ## 9 长任务
102
+
103
+ 耗时几十秒以上的操作做成长任务。宿主侧用 `mount` deps 的 `longTasks`,并挂回返回值:
104
+
105
+ ```ts
106
+ longTasks.create({ key, run, step, outcome, view, stopped })
107
+ // run({ payload, onStep, signal }):onStep({ kind: 'start', text }) 报步骤;return 结果
108
+ ```
109
+
110
+ - 来源:在工具里 `runs.start({ …, session: sessionIdOf(exec) ?? undefined })`(`sessionIdOf` 来自
111
+ `@dsh-workbench/shared/host`)。不记 = 用户按的 → 停止需审批。
112
+ - 浏览器侧:`useLongTask` → `feed.runs` / `feed.stop(id)` / `feed.stopping` / `feed.stopFailed`;
113
+ 画 `LongTaskRow`(五态:`running` / `needs-login` / `stopped` / `ok` / `failed`)。
114
+ - 停止:`run` 收到 `signal` 被 abort = 有人按停止;在循环点与每条子命令前后查 `signal.aborted`。
115
+ 不认 signal 就不要画停止钮。画钮传 `stop`(`onStop: () => feed.stop(run.id)`、`busy: feed.stopping === run.id`、
116
+ `failed: feed.stopFailed`、`label: 停止任务 {label}`、`askLabel: '确认停止'`);失败文案画在行下,
117
+ 不写"停止失败"。两按确认归组件;未传 `stop` 的行不变。
118
+ - `feed.stopping` / `feed.stopFailed` 只有一格(最后一次)。多行并行时要分行状态就按 id 自记。
119
+ - 外壳自带 `workbench_long_tasks_list`(读)与 `workbench_long_tasks_stop`(写)。自己起的免审批;
120
+ 别人的 / 用户按的 / 没记来源的要审批;跨工作区直接拒。被停时外壳向起它的会话发 notice(完成 / 失败不发)。
121
+ - `contracts` 查形状:注册表七个方法(含 `stop` / `live`)、`create` 的 `key` / `run` / `view`、
122
+ 产物含 `useLongTask` / `LongTaskRow` 之一。是否真响应 signal 不在射程内。
123
+ - 左栏 loading 点由外壳按 `live()` 驱动,模块不做。
124
+ - 用 `stop` / `session` / 外壳长任务工具需把 `@dsh-workbench/core` peer 下限抬到含它们的版本(`check --only kit` 查)。
125
+
126
+ ## 12 按工作区 / 模式收窄工具
127
+
128
+ 工具是机器级的;不同项目需要不同工具。不要运行时返回"此项目不可用"—— 从清单移除。
129
+ `gate: { deniedTools(context), subscribe(listener) }` 挂进 `mount` 返回值:
130
+
131
+ - `deniedTools({ workspace, preset })` 返回该会话不存在的工具名(与 `registerTools()` 一致;二者可为 `null`)。
132
+ `subscribe` 在状态变化时通知外壳重算;不接则等重启。
133
+ - 两个轴:`workspace` = 用户在看哪个项目;`preset` = 该会话被授权做什么。外壳两者都用。
134
+ 模块工具按工作区是否有这张卡收窄;DIY 族按模式收窄。
135
+ - 外壳双重执行:清单不出现 + 调用时拒。
136
+
137
+ "哪些工作区出现"不由模块声明:用户用 `workbench_panels_show` / `_hide` 控制,卡片与全部工具同判。
138
+ `deniedTools` 可选。
@@ -1,98 +1,66 @@
1
- # 接上外壳并交付:依赖、禁区、装、热换、发布
1
+ # 接外壳:依赖、禁区、安装、热换、发布
2
2
 
3
- [BUILD.md](BUILD.md) 的下一半。模块造出来了 —— 这儿说它从哪拿依赖、哪些写法会被门禁拦、
4
- 怎么装上、改一行怎么看到它变、怎么发出去。
3
+ [BUILD.md](BUILD.md) 的后半。设计见 [DESIGN.md](DESIGN.md) / [STEPS.md](STEPS.md);门禁见 [REVIEW.md](REVIEW.md)。
5
4
 
6
- 设计口径看 [DESIGN.md](DESIGN.md) 与 [STEPS.md](STEPS.md);门禁红了看 [REVIEW.md](REVIEW.md)。
5
+ ## 1 依赖
7
6
 
8
- ## 1 依赖从哪拿
9
-
10
- | 要什么 | 从哪拿 |
7
+ | 需要 | 来源 |
11
8
  |---|---|
12
- | 宿主侧要用的纯函数与常量(`param`、平台解析、工作区存储…) | `@dsh-workbench/shared/host`(写进 `dependencies`) |
13
- | 浏览器侧要的一切(hooks、组件、图标、文案工具) | `@dsh-workbench/core/client` 的 `kit` |
14
- | kit 里确实没有的 dsh 控件 / 图标(如 `Button`、整套 `Icon*Regular`) | `@deepseek-ai/dsh-client-ui-primitives`(声明 external + devDependency 精确版;清单见 `KIT.md` §8) |
15
- | 类型 | `import type` 随便用(编译时擦除,不会进产物) |
16
- | 外壳本体 | `@dsh-workbench/core`(peer 依赖,装的时候要比版本) |
9
+ | 宿主侧纯函数 / 常量 | `@dsh-workbench/shared/host`(`dependencies`) |
10
+ | 浏览器侧 hooks / 组件 / 图标 / 文案工具 | `@dsh-workbench/core/client` 的 `kit` |
11
+ | kit 没有的 dsh 控件 / 图标 | `@deepseek-ai/dsh-client-ui-primitives`(`dsh.client.external` + 精确版 devDependency;[KIT.md](KIT.md) §8) |
12
+ | 类型 | `import type` |
13
+ | 外壳 | `@dsh-workbench/core`(peer,安装时比版本) |
17
14
 
18
- **`kit` 里到底有什么,不要猜也不要背**:`dsh-wb-module check --only kit` 会把**这份外壳**的
19
- kit 成员(`components` / `hooks` / `api` / `lib` 各有哪些)列出来 —— 它取的是外壳真实产物
20
- `client.js` 里那一份,不是另抄的清单,所以永远和用户手上那版一致。
15
+ 成员清单用命令取,不手抄:`check --only kit`(当前安装的外壳真实产物)、`check --only primitives`
16
+ (dsh 原语导出、与 kit 求交、需写入 `package.json` 的两行)。
21
17
 
22
- **kit 里没有的 dsh 原语有哪些,同样问命令**:`dsh-wb-module check --only primitives`
23
- (`--all` 全表 / `--json` 机器档)列装机那份的导出、与 kit 求交、并给出该写进 `package.json`
24
- 的两行。两边都是"取真产物",没有手抄清单(口径见 ADR-0063 · `.scratch/dsh-primitives-face/spec.md`)。
18
+ ⚠️ 共享包入口按运行时分离(`source` 判据):
25
19
 
26
- ⚠️ **浏览器侧不许 `import` `@dsh-workbench/shared` 的运行值**(两个入口都不行):
27
- 它**不在**客户端构建的 external 默认表里(工坊里那份是
28
- `node_modules/@dsh-workbench/module-tools/src/build.mjs`),
29
- import 运行值会被**打进你的包**(第二份实例),而且浏览器运行时也解析不到它。
30
- 宿主侧正相反:`@dsh-workbench/shared` 与 `/host` 都在 external 里,直接 import 才对。
20
+ - 根入口只许 `import type`。浏览器侧取运行值会打包进产物(第二份实例);宿主侧取运行值会使 `dist/host.js`
21
+ 无法 import(根 barrel 拉到 dsh 浏览器件)。浏览器运行值走 `kit`。
22
+ - `@dsh-workbench/shared/host` 仅宿主侧。含 `node:*`;浏览器侧纯函数走 `kit`。
23
+ - 契约类型(`Translate` / `ApiError`…)从根入口取,允许。
31
24
 
32
- **单例只能有一份**:kit 里的 hooks 与容器组件**不许被复制进模块包**
33
- (各打一份的症状不是报错,而是"agent 改完面板不自己刷新")。它们只能从 `kit` 拿。
25
+ 单例只能一份:kit 的 hooks 与容器组件不得复制进模块,否则表现为"agent 改完面板不自动刷新"。
34
26
 
35
- ## 2 四条会被门禁拦住的写法
27
+ ## 2 会被门禁拦的写法
36
28
 
37
- | 别这么写 | 为什么 |
29
+ | 写法 | 结果 |
38
30
  |---|---|
39
- | 从 DOM + CSS 手搓一个 dsh 已经有的东西(按钮、弹窗、输入框、图标) | 重画就是第二份材质与交互(悬停、禁用、焦点环、色板),迟早跟 dsh 漂。**先从 `kit` 拿**;kit 里没有再自己写,并在 spec 里记理由 |
40
- | `import … from '@deepseek-ai/dsh-client-ui-primitives'` | **可以**,但要三处齐:只在浏览器侧(宿主侧碰它 `source` 判红)、在 `dsh.client.external` 里声明、**用了就必须在产物里是 `require`**(漏声明 = 被内联成第二份实例,`primitives` 判红)。kit 里有的先拿 kit(`KIT.md` §8);模板默认已经把这三处备好了 |
41
- | `import … from '@/shared/…'`、`../../workbench/src/…` | 装你的人手上没有工作台的源码。`@/` 只许指**你自己**的 `src/` |
42
- | 在公开包里写内网地址 / 内网域名 / 企业内部名 | 门禁会扫出来并指到行 |
43
-
44
- 另外两条不是"被拦",是纪律:**样式走 `*.module.css`,类名别带 `wb-` 前缀**(`wb-*` 是外壳的**全局**
45
- 类名:模块定义它、或借契约里没有的那一个,`check --only style` 都判红;模块自己的类名由构建**按文件路径
46
- 哈希**,天然不会跟谁撞。要借外壳契约里的类,写 `:global(.wb-…)`,且那个类必须在契约里);
47
- **`src/**` 里只许 JSDoc、且贴着导出**(用法归声明;`//`、实现里的解释与 `.css` 注释都不许)——
48
- 为什么写进 `README.md` 或 spec。
49
-
50
- ## 3 装上、上板
51
-
52
- 模块造好、门禁绿了,**装这一步你自己做** —— 用 `workbench_diy_install`(`unit=<单元 id>`):
53
-
54
- - 它只认**工坊里造好的**那个模块(按 unit 在工坊里查,不接任何路径)。
55
- - 装完顺手在**当前会话这个工作区**的面板上把它打开。
56
- - 装是这台机器上的持久变更,**但它不问审批**(工坊整族都是这个口径)—— 不会弹审批屏、也不用等他点头:
57
- 别对他说"请你点一下确认"。
58
- - 会话的审批被关成 `never` 时**拒的是"真回了 `ask`"的那些调用**(比如开始调试那次 `workbench_diy_debug_start`)——
59
- 装 / 上板是 `allow`,`never` 下照样过,不用让用户去把审批开回来。判据与构建不受影响。
60
-
61
- 两个必须记住的后果:
62
-
63
- - **那一跳一辈子只该走一次。** 包名一旦在 profile 里,**再调它不会再装**(会告诉你"已经装过了"),
64
- 改源码 + `dsh-wb-module build` 就行;同名重装本来就只会得到"要重启",而那一支连重载都不做。
65
- - **装 ≠ 上板**:面板默认是空的,得在面板配置里把它打开 —— 那一跳顺手做掉了。
66
- 只有在"要重启才生效"或它还没登记进外壳时才会没打开,那时返回里会说清:
67
- 等它生效后调 `workbench_diy_show`(`unit=<单元 id>`)—— 它只上板、不装,也不会多要一次点头。
68
- - **拿不准它现在走到哪一步就问 `workbench_diy_status`**:装了没 / 面板认得没 / 这个项目上开着没 / 产物齐不齐 /
69
- 源码是否比产物新,外加一句"下一步做什么"。这五件事别自己去 panels、profile、工坊目录三处拼 ——
70
- 实测会拼错(把"要重启才注册"读成"没装上",或者源码改了没 build 却去重装一遍)。
71
- - **已经装过、只是没上板**:`workbench_diy_show`(只上板,不装)。
72
-
73
- 工坊那一族 `workbench_diy_*`(33 把:14 把枚举的 opencli + 12 把看/装/探 + 4 把调试约定 + 3 把录制交接,
74
- 全表见 [FAMILY.md](FAMILY.md))**只在工坊那个会话里**(工具清单按工作区收窄,别处连它都不在),
75
- 落在别的项目里调用会被当场拒 —— 这就是"DIY 模式"在工具面上的样子。
76
- 普通项目里装别的东西是另一套工具:`workbench_modules_install` / `workbench_modules_uninstall` 等(见 TOOLS.md)。
77
-
78
- ## 4 改一行,看到它变
79
-
80
- 装完之后**不要再装一遍**。改源码 → `dsh-wb-module build` → 用户面板上那张卡自己变。
81
-
82
- - **宿主侧**(`src/host.ts` / `balance.ts` … → `dist/host.js`):**秒级热换**,不重启、不刷新
83
- (加一行探针 → build → 3 秒内接口就返回新内容)。`cordis.patch.yml` 里 hmr 那两件都不能去掉:
84
- `followSymlinks: false`(跟着软链会把文件描述符用光),以及 `ignored` 里那两条 `*(../)`
85
- extglob(工坊那个根在外壳包目录之外,相对路径以 `..` 打头,而 picomatch 的 `**` 不吃 `..` ——
86
- 少了它工坊 `node_modules` 的实目录会整棵进监听表)。为什么、实测数字与边界见仓内 ADR-0034 §3。
87
- - **客户端侧**(`src/client.tsx` / CSS → `client.js`):浏览器**不会自己**换,**刷新一下页面**即可
88
- (实测:重建后 60 秒页面没有任何反应)。`*.module.css` 的改动是就地改写那一份 `<style>`,刷新即生效。
89
- - **改 `cordis.patch.yml`、改 `package.json` 里的 `dsh.client` / `exports["./client"]` 需要重启。**
90
- 这些是"模块怎么被装上"的声明,不是模块的内容。
91
- - 重启操作只有在极少数情况下才需要用户做。真需要时,说清**为什么**、**要重启哪个**。
31
+ | DOM + CSS 重画 dsh 已有控件(按钮、弹窗、输入框、图标) | 第二份材质与交互;先从 `kit` 取 |
32
+ | `import '@deepseek-ai/dsh-client-ui-primitives'` | 允许,需三处齐:仅浏览器侧、`dsh.client.external` 声明、产物中为 `require`(否则被内联,`primitives` 红) |
33
+ | `import '@/shared/…'`、`../../workbench/src/…` | `@/` 只指自己 `src/`;跨包用包名 |
34
+ | 公开包写内网地址 / 域名 / 企业名 | `markers` 红 |
35
+
36
+ 纪律:样式走 `*.module.css`,类名不带 `wb-` 前缀(外壳全局类;借契约类写 `:global(.wb-…)`);
37
+ `src/**` 只许贴导出的 JSDoc。
38
+
39
+ ## 3 安装、上板
40
+
41
+ `workbench_diy_install`(`unit=<id>`):只认当前工作区造好的模块;装后在当前工作区面板打开;不弹审批。
42
+
43
+ - 只装一次。包名已在 profile 后再次调用会返回"已装过";迭代用 `dsh-wb-module build`。
44
+ - 装 ≠ 上板。返回里说未打开时,待生效后调 `workbench_diy_show`(只上板)。
45
+ - 状态不明问 `workbench_diy_status`(`unit=…`):装没装 / 面板认没认 / 项目内开没开 / 产物齐不齐 /
46
+ 源码是否新于产物 / 下一步。不要自行拼 panels、profile、目录三处。
47
+ - 用户选「完全权限」时,需审批的调用直接过;未认领的写与 `{blocked}` 仍拒。
48
+
49
+ DIY 族仅在选了 DIY 模式的会话出现(按模式,不按工作区);全表 [FAMILY.md](FAMILY.md)。
50
+ 普通项目安装用 `workbench_modules_install` / `workbench_modules_uninstall`。
51
+
52
+ ## 4 热换
53
+
54
+ 装后不再安装。改源码 → `dsh-wb-module build`:
55
+
56
+ - 宿主侧 → `dist/host.js`:秒级热换,不重启、不刷新。
57
+ - 客户端侧 → `client.js`:刷新页面;`*.module.css` 就地改写 `<style>`,刷新生效。
58
+ - 改 `cordis.patch.yml`、`package.json` 的 `dsh.client` / `exports["./client"]`:需重启(安装声明变更)。
59
+ - 必须重启时说明原因与重启对象。
92
60
 
93
61
  ## 5 发布
94
62
 
95
- **工坊里的模块一律 `private: true`,不发到任何源** —— 模型写的代码在他自己的机器上跑,摘掉它是另一个决定。
63
+ DIY 模块 `private: true`,不发任何源。
96
64
 
97
- 真要给别人用:先 `pnpm exec dsh-wb-module publish`(门禁 + 三张表 + 打包清单的 `files` 漏项,**只 dry run**),
98
- 过了再摘 `private` 并**显式**写发布目标(`publishConfig.access` 或内网源 `publishConfig.registry`)。
65
+ 需要发布时:`pnpm exec dsh-wb-module publish`(门禁 + 三张表 + `files` 漏项,仅 dry run);
66
+ 通过后移除 `private`,显式写 `publishConfig.access` 或内网 `publishConfig.registry`。