@dsh-workbench/core 0.3.4 → 0.3.6
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.
- package/client.js +1362 -85
- package/dist/client/client.body.js +1362 -85
- package/dist/client/index.js +1362 -85
- package/dist/host/index.js +1019 -869
- package/dist/types/app/client.d.ts +234 -60
- package/dist/types/modules/panels/client.d.ts +1 -1
- package/dist/types/modules/panels/model/approval.d.ts +8 -0
- package/dist/types/modules/panels/model/diy-context.d.ts +4 -5
- package/dist/types/modules/panels/model/diy-observers.d.ts +2 -2
- package/dist/types/modules/panels/model/diy-page.d.ts +1 -1
- package/dist/types/modules/panels/model/long-task-tools.d.ts +15 -0
- package/dist/types/modules/panels/model/long-task-tools.test.d.ts +1 -0
- package/dist/types/modules/panels/unit.d.ts +6 -5
- package/dist/types/shared/api/context-chips.d.ts +20 -0
- package/dist/types/shared/api/host/approval-call.d.ts +16 -0
- package/dist/types/shared/api/host/diy-components.d.ts +17 -6
- package/dist/types/shared/api/host/diy-workshop.d.ts +0 -3
- package/dist/types/shared/api/host/long-task-notice.d.ts +30 -0
- package/dist/types/shared/api/host/mount-units.d.ts +3 -1
- package/dist/types/shared/api/host/preset-scope.d.ts +16 -0
- package/dist/types/shared/api/host/tool-scope.d.ts +6 -2
- package/dist/types/shared/api/host/workshop-root.d.ts +7 -0
- package/dist/types/shared/components/BlockerState.d.ts +12 -0
- package/dist/types/shared/components/PanelFoot.d.ts +9 -0
- package/dist/types/shared/components/Tip.d.ts +8 -0
- package/dist/types/shared/hooks/bundle-queries.d.ts +12 -0
- package/dist/types/shared/hooks/fact-transports.d.ts +4 -0
- package/dist/types/shared/hooks/panel-state-report.d.ts +12 -0
- package/dist/types/shared/hooks/use-api-resource.d.ts +22 -0
- package/dist/types/shared/hooks/use-blocker-actions.d.ts +8 -0
- package/dist/types/shared/hooks/use-capabilities.d.ts +55 -0
- package/dist/types/shared/hooks/use-capability-gate.d.ts +16 -0
- package/dist/types/shared/hooks/use-card-summary.d.ts +7 -0
- package/dist/types/shared/hooks/use-diy-debug.d.ts +13 -0
- package/dist/types/shared/hooks/use-diy-record.d.ts +13 -0
- package/dist/types/shared/hooks/use-facts.d.ts +10 -0
- package/dist/types/shared/hooks/use-host-write.d.ts +12 -0
- package/dist/types/shared/hooks/use-long-task.d.ts +15 -1
- package/dist/types/shared/hooks/use-unit-board.d.ts +17 -0
- package/dist/types/shared/hooks/use-visible-busy.d.ts +17 -0
- package/package.json +2 -2
- package/skills/workbench-module-diy/DATASOURCE.md +2 -2
- package/skills/workbench-module-diy/FAMILY.md +5 -4
- package/skills/workbench-module-diy/KIT.md +105 -100
- package/skills/workbench-module-diy/PANEL.md +19 -11
- package/skills/workbench-module-diy/REVIEW.md +4 -4
- package/skills/workbench-module-diy/SKILL.md +5 -4
- package/skills/workbench-module-diy/TOOLING.md +7 -3
- package/skills/workbench-module-diy/TOOLS.md +20 -2
- package/skills/workbench-module-diy/WIRING.md +2 -1
- package/skills/workbench-module-diy/HOOKS.md +0 -95
|
@@ -90,12 +90,11 @@ return {
|
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
**浏览器半侧**:写走 `prefs` 通道,读还是模块自己的只读口(别为配置另开一条)。
|
|
93
|
-
`
|
|
93
|
+
排版与内边距都是外壳给的默认(`Field` / `TextField`),**模块不需要为"能看"写 CSS**:
|
|
94
94
|
|
|
95
95
|
```tsx
|
|
96
96
|
const write = useHostWrite({ channel: 'prefs', resource: 'your-module' });
|
|
97
|
-
// Field + TextField / ChoiceGroup
|
|
98
|
-
// (输入框的内边距和"标签 + 提示"的排布由外壳给默认,模块不用自己写 CSS)
|
|
97
|
+
// 控件用 kit 的(Field + TextField / ChoiceGroup),外面套 SettingsSection,保存用 SaveBar;失败留在原地
|
|
99
98
|
write.send({ ignored, timeoutSeconds, workspace });
|
|
100
99
|
```
|
|
101
100
|
|
|
@@ -120,19 +119,28 @@ agent 那一侧也要能通过模块自己的能力读到同一份偏好。
|
|
|
120
119
|
- **默认收起**:历史、明细折叠,露出最近一条;只有一条时不给折叠三角(没有可展开的东西)。
|
|
121
120
|
- **屏幕上的字越少越好**:他能看懂的(红点绿点、数字)就不用再写一段规则解释;删掉的东西不要留一行说明。
|
|
122
121
|
- **默认状态点**只有四种意思:在做 / 做完了 / 出错 / 没在跑。
|
|
123
|
-
- **外链一律用 kit 的 `Link`**(`h(Link, { href: … }, 文案)
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
- **外链一律用 kit 的 `Link`**(`h(Link, { href: … }, 文案)`):新窗口 + `rel` + 自带外链箭头,
|
|
123
|
+
**它自带的样子就是唯一一份,别去掉箭头、别换箭头、别自己调**(没有 `icon` 这个口子)——
|
|
124
|
+
全仓的链接必须长得一样。
|
|
126
125
|
模块源码里手写 `<a>` / `h('a', …)` 会被「样式归属」判据**直接判红**。
|
|
127
126
|
**文案里也不许自带箭头**(`'去网关看账单 →'` 这种)—— 箭头由组件给,自带的话页面上就是两颗。
|
|
128
127
|
它内部已经把 dsh 的图标原语包好了,所以模块**不用**为它声明 `client.external`。
|
|
129
|
-
-
|
|
130
|
-
|
|
131
|
-
`aria-label`
|
|
128
|
+
- **哪几种手写 `<a>` 不算 `Link`(判据不该咬、也别硬迁)**:判据钉的是**"手写 `<a>` 这件事本身"**
|
|
129
|
+
(`Link` 是唯一出处),下面三类**有意**留着,因为它们**不是"正文链接"**——
|
|
130
|
+
- **图标链**(icon-only 的 `<a>`,靠 `title` / `aria-label` 顶着):`Link` 一定会多画一颗 ↗,
|
|
131
|
+
套进去就是两颗,尺寸与槽位它也表达不了 ⇒ 留 `<a>`。
|
|
132
|
+
- **按钮皮的链**(`.anchorButton` 那类 28 高 / 描边的"看起来像按钮、实际是链接"):**必须是链接的动作
|
|
133
|
+
留真 `<a>`** —— `href` 带来的是中键新开、右键复制链接、悬停看 URL;塞成
|
|
134
|
+
`onClick: () => window.open(…)` 的按钮,这三件一起丢。
|
|
135
|
+
- **整行链**(一行整体可点,`flex: 1` / `color: inherit`):它是**那条行的布局槽**,不是正文里的字。
|
|
136
|
+
**要放宽这一条就走棘轮**:那张已知豁免表(`module-tools` 的 `style` 判据里,`ANCHOR_EXEMPT`)
|
|
137
|
+
**只许减不许加**(迁一处删一行);真要加一格,那是**改判据口径**,得写清"为什么 `Link` 表达不了它"。
|
|
138
|
+
口径的权威那份是 `style.mjs` 里 `BARE_ANCHOR` 那一段注释(`ANCHOR_EXEMPT` 上面)
|
|
139
|
+
与 `module-tools/README.md` 的 `style` 那一行。
|
|
140
|
+
- **"去看某处"用哪个**:跳**整张卡对应的系统** → 卡头那颗图标按钮(`CardHead.platformHref`,给这一个就够);
|
|
132
141
|
跳**一个具体页面 / 一条具体记录** → `Link`(正文里的文字链接);**触发一个动作**(刷新、重建、去登录)→
|
|
133
142
|
dsh 的 `Button`,或阻断屏里的 `action`。区别在"跳去哪"和"它是不是一个动作",不在好看不好看。
|
|
134
|
-
- **标签 +
|
|
135
|
-
别为"能看"写模块自己的 CSS(见 §5.1)。
|
|
143
|
+
- **标签 + 输入框用 `Field` + `TextField`**(排布与内边距是外壳的默认,别自己写 CSS,见 §5.1)。
|
|
136
144
|
- **要做什么用哪件,照 [KIT.md](KIT.md) 抄**("要做什么 → 用哪件 → 最小写法"的表)。
|
|
137
145
|
下面几条是口径,KIT.md 是写法。
|
|
138
146
|
- **字号与重点**:走那把刻度(`--dsw-font-*`:11 / 12 / 13 / 14 / 16 / 20 / 24),
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
| `standalone` | "只有工作台源码在场才编得过" ⇒ 发出去就散架 | 别引用工作台源码;`tsconfig` 的 `paths` 用**相对路径**(`"./src/*"`) |
|
|
15
15
|
| `markers` | 内网域名 / 企业内部名泄进要公开发布的包 | 别写进包里;要配就落到模块自己的配置面 |
|
|
16
16
|
| `alias` | `@/` 伸进工作台源码 ⇒ 装的人手上没那份源码 | `@/` 只许指自己 `src/`;跨包走包名 |
|
|
17
|
-
| `style` | 模块定义 `.wb-*`
|
|
17
|
+
| `style` | 模块定义 `.wb-*` 覆盖外壳类;手写行 / 操作区 / **整张表单**(F1–F4) | 样式走 `*.module.css`(不带 `wb-` 前缀);借外壳类写 `:global(.wb-…)`;行与操作区用 kit 的 `FormRow(s)` / `FormActions`,整张表单用 `Form`([KIT.md](KIT.md) §5)。**F4**:源码里出现裸 `<form`、或手写行槽类(`wb-formRow` / `wb-formRows` / `wb-field`)⇒ 红(口径见 ADR-0068) |
|
|
18
18
|
| `arch` | 结构退化(巨文件 / 反向依赖 / 跨运行时 / 表外目录 / 模型层没测试) | 见「第十二条 `arch`」 |
|
|
19
19
|
| `lint` | 单文件语法卫生(`as any` / 残留 `console`、`debugger` / 内联颜色、`!important` / react-hooks) | 见「第十三条 `lint`」 |
|
|
20
20
|
|
|
@@ -120,9 +120,9 @@ export default [
|
|
|
120
120
|
他说"一片空白 / 一直转圈 / 数字不对"时,**先取证再改代码**;前一步有结论就停在那儿修:
|
|
121
121
|
|
|
122
122
|
1. **`workbench_diy_panel`** —— 这个工作区面板现在什么样:哪几张卡、每张最后一次取数是读到了还是读不到(带宿主回的码与原文)、崩过没(原因 + 次数)。有答案就别往下走。
|
|
123
|
-
1.5 **看这一张卡**(`workbench_diy_screenshot` / `
|
|
123
|
+
1.5 **看这一张卡**(`workbench_diy_screenshot` / `_html` / `_find` / `_eval`)—— 打的是**用户当前那一页**上工坊那块板里这张卡(`unit=…`;四把的入参与拿回见 [FAMILY.md](FAMILY.md) §2),配合上一步把"看起来不对"说成"哪一处不对"。**这一族(浏览器那 14 把 + `logs_*`)要先 `workbench_diy_debug_start`**(`unit=…`;会请用户点头一次)—— 没有这场约定一律当场拒;不看屏先 `_pause`,收工 `_stop`。(`_panel` / `_status` 不碰他那一屏,不用开;`_find` / `_eval` 带 `read=` 时也不碰。)
|
|
124
124
|
2. **`workbench_diy_status`(`unit=…`)** —— 看"起得来"那一行:模块起不来时原因就在那里(装配抛的错 / 声明被拒的原文)。
|
|
125
|
-
3. **自己把那条读跑一遍**:卡片画的就是模块那条宿主路由的返回值 —— `curl '<工作台地址>/workbench/api/<unit>/<route>'`(默认 `http://127.0.0.1:3081`)。接口对而卡片不对 ⇒ 渲染侧;接口就错 ⇒ 宿主侧(照 [DATASOURCE.md](DATASOURCE.md) 重探)。要**浏览器里那一次**的原文,用 `
|
|
126
|
-
4. **最后才看用户那一页**:要控制台走三拍 —— `workbench_diy_logs_watch` → 做那件你怀疑会报错的事 → `workbench_diy_logs_read`(读完 `
|
|
125
|
+
3. **自己把那条读跑一遍**:卡片画的就是模块那条宿主路由的返回值 —— `curl '<工作台地址>/workbench/api/<unit>/<route>'`(默认 `http://127.0.0.1:3081`)。接口对而卡片不对 ⇒ 渲染侧;接口就错 ⇒ 宿主侧(照 [DATASOURCE.md](DATASOURCE.md) 重探)。要**浏览器里那一次**的原文,用 `workbench_diy_eval`(`read=<站点地址>`,脚本里 `fetch` 自己那条读)。
|
|
126
|
+
4. **最后才看用户那一页**:要控制台走三拍 —— `workbench_diy_logs_watch` → 做那件你怀疑会报错的事 → `workbench_diy_logs_read`(读完 `_logs_stop` 卸掉;上限:每页 200 条 / 单条 400 字、随刷新消失)。**别用 `opencli browser <会话> console`** —— 那是个 stub(恒回空数组、还 exit 0)。要在页面里跑自己的代码用 `workbench_diy_eval` 的 `read=` 档;看**用户那一页**有什么可点的用 `workbench_diy_state`。宿主侧热换 / 装载的报错在**起工作台那个终端**里。
|
|
127
127
|
|
|
128
128
|
两条纪律:**别让用户截图或替你转述报错**;**验收就是你自己拿这一族走** —— 逐条对着三张表走「打开哪 → 看什么 → 点什么 → 应该看到什么」,`screenshot`/`html`/`find`/`eval` 的读数、动作族的返回、`workbench_diy_network` 的收发包、`logs_*` 的报错都算证据;走通再 `design accept`,走不通的如实说没验过(用户不必在场,也不用请他逐条点头)。工具参数与返回体都是 opencli 的原样(见 [FAMILY.md](FAMILY.md))。面板运行态是**用户那一页自己报上来的**,第 1 步没有内容时,先让他把工作台切到工坊那个工作区、刷新一次页面再看。
|
|
@@ -14,7 +14,7 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
|
|
|
14
14
|
这一步的产出是 ASCII 画面 + 三张表,不是文字。三张表当场落进模块里的 `design.json`
|
|
15
15
|
(命令与形状:[DESIGN.md](DESIGN.md) §5)—— 只留在对话里等于没定。
|
|
16
16
|
3. **先定数据从哪来** —— [DATASOURCE.md](DATASOURCE.md):干净机器上没有现成 skill 可抄,
|
|
17
|
-
用 `
|
|
17
|
+
用 `workbench_diy_eval` / `_find` 带 `read=<站点地址>`(跑在 `workbench-read` 上)复用他的登录去探 —— 没在站点上我自己开一次,`not-logged-in` / `not-on-site` 两条码按读线约定归一
|
|
18
18
|
都在那把工具里;探明后**固化成一条读**(`@dsh-workbench/shared/host` 的 `browserRead`)。顺手把三态写掉。
|
|
19
19
|
要 agent **真去改东西**(提交 / 发布 / 建单 / 改配置)的,读完 [WRITES.md](WRITES.md) 再动手:
|
|
20
20
|
四步(现成写命令 → 入参 / 出参来源 → 没有预览档的不许试写 → 写完后回读确认)。
|
|
@@ -39,7 +39,7 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
|
|
|
39
39
|
要看 / 动**他那一屏**(浏览器那 14 把 + `logs_*`)得先开约定:
|
|
40
40
|
`workbench_diy_debug_start`(`unit=…`;会请他点头一次,他会被告知「调试中请留在那一屏」)——
|
|
41
41
|
**没有这场约定,上面那一族一律当场拒**;不看屏了先 `workbench_diy_debug_pause`,收工 `…_stop`。
|
|
42
|
-
(`workbench_diy_panel` / `_status` / `
|
|
42
|
+
(`workbench_diy_panel` / `_status` 不碰他那一屏,不用开;`_find` / `_eval` 带 `read=` 时也**不碰** —— 那走只读线。)
|
|
43
43
|
走法(全表与参数在 [FAMILY.md](FAMILY.md)):
|
|
44
44
|
- **这一张卡长什么样**:`workbench_diy_screenshot`(要卡那一块就先 `eval` 量盒子、填 `clip`)+
|
|
45
45
|
`workbench_diy_html`(`--as json` 给结构树);
|
|
@@ -78,7 +78,7 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
|
|
|
78
78
|
| 面板上该有什么、状态矩阵怎么写、界面习惯 | [PANEL.md](PANEL.md) |
|
|
79
79
|
| **要做什么 → 用哪件 kit → 最小写法**(照抄面) | [KIT.md](KIT.md) |
|
|
80
80
|
| **kit / dsh 原语里到底有什么、某一件怎么用**(DIY 会话里直接调这把工具) | `workbench_diy_components`(`name=Button` 回它的类型文件与声明;工程域用 `check --only kit` · `--only primitives`)· [KIT.md](KIT.md) §8 |
|
|
81
|
-
| **kit 那些 hook
|
|
81
|
+
| **kit 那些 hook 怎么搭**(四个场合、一格卡片怎么拼) | [KIT.md](KIT.md) §4′(逐件的 API 看 `.d.ts` 的 JSDoc 或 `workbench_diy_components`) |
|
|
82
82
|
| 能力怎么划、传什么拿回什么、哪几件要点头 | [TOOLS.md](TOOLS.md) |
|
|
83
83
|
| **工坊会话里那 33 把的全表**(枚举 opencli 的 14 把 + 看/装/探 + 调试约定 + 录制交接、参数与如实边界) | [FAMILY.md](FAMILY.md) |
|
|
84
84
|
| agent 动完面板哪一格变、他正在干的时候看哪儿 | [LINKAGE.md](LINKAGE.md) |
|
|
@@ -109,4 +109,5 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
|
|
|
109
109
|
- **dsh 已经有的,先从 `kit` 拿**(`@dsh-workbench/core/client`),不许从 DOM + CSS 重画;
|
|
110
110
|
也不直接 import dsh 的包 —— 直接引会被门禁拦,而且那是第二份实例。
|
|
111
111
|
- **工坊里的模块一律 `private: true`,不发到任何源。**
|
|
112
|
-
-
|
|
112
|
+
- **源码里只许 JSDoc,且贴着导出**(用法住在声明里,tsc 把它搬进 `.d.ts`);`//`、实现里的解释、
|
|
113
|
+
以及 `.css` 里的注释照旧不许有。要说的"为什么"写进模块自己的 `README.md` 或 spec。
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
| 落点、分层文件形状、六个脚本([BUILD.md](BUILD.md) §1–§2) | `dsh-wb-module init --template diy`(骨架带分层目录 + 一行 `arch.config.mjs` + 两行 `eslint.config.mjs`(`moduleLint({ rootDir: import.meta.dirname })`)+ 一条真读盘的数据源)+ `scripts/smoke.mjs` |
|
|
17
17
|
| 语法卫生红了、要自动修([REVIEW.md](REVIEW.md) 第十三条) | `dsh-wb-module lint [--fix]`(**`check` 永不动手改文件**;`--fix` 才写回) |
|
|
18
18
|
| 装、上板、不重装([WIRING.md](WIRING.md) §3) | `workbench_diy_install` / `workbench_diy_show` / `workbench_diy_status`(工坊那一跳自己上板) |
|
|
19
|
-
| 想知道 dsh 原语 / 这份外壳的 kit 里有什么、某一件怎么用([KIT.md](KIT.md) §8) | `workbench_diy_components`(不填给清单 + 类型面根;`name=Button` 回它的类型文件与那份声明——JSDoc
|
|
19
|
+
| 想知道 dsh 原语 / 这份外壳的 kit 里有什么、某一件怎么用([KIT.md](KIT.md) §8) | `workbench_diy_components`(不填给清单 + 类型面根;`name=Button` 回它的类型文件与那份声明——JSDoc 里写着默认值与语义;`bucket=icons` 把那一桶列全——`all=true` 的全表在真工坊超 8192、中间会被修剪器裁掉,按桶取才装得下;工程域用 `dsh-wb-module check --only primitives --file <名字>`) |
|
|
20
20
|
| 它到底起得来没(装配抛的错 / 声明被拒的原文) | `workbench_diy_status`("起得来"那一行,见 [REVIEW.md](REVIEW.md) 四步取证的第二步) |
|
|
21
|
-
| 看 / 动**用户那一屏**之前要先取得约定(ADR-0055 §7「调试是一次约定」) | `workbench_diy_debug_start`(`unit=…`,**要用户点头一次**)—— 没有它,浏览器那 14 把 / `logs_*` **一律当场拒**(拒的时候一条命令都不发);不看屏了 `_pause`、收工 `_stop`(**收工顺手把网络捕获与日志钩子也收了**)。`_panel` / `_status` / `
|
|
21
|
+
| 看 / 动**用户那一屏**之前要先取得约定(ADR-0055 §7「调试是一次约定」) | `workbench_diy_debug_start`(`unit=…`,**要用户点头一次**)—— 没有它,浏览器那 14 把 / `logs_*` **一律当场拒**(拒的时候一条命令都不发);不看屏了 `_pause`、收工 `_stop`(**收工顺手把网络捕获与日志钩子也收了**)。`_panel` / `_status` 不碰他那一屏,不用开(`_find` / `_eval` 的 `read=` 档也不碰 —— 那是只读线)。**全表与参数见 [FAMILY.md](FAMILY.md)** |
|
|
22
22
|
| 抓那一次**在外站页面上点出来的写**(他的写只发生一次,也不在我们那条会话里) | `_record_prepare`(`unit` / `url` / `doing`,**要用户点头一次**)→ 请他按「开始录制」→ 做**一次**那动作 → 按「结束录制」→ `_record_read` 取那一段 → `_record_stop` 收工(**先 read 再 stop**)。它不走调试约定,只碰自己的 `workbench-rec` 会话;三颗键、边界与实话见 [FAMILY.md](FAMILY.md) §5 · [WRITES.md](WRITES.md) 步骤 3 |
|
|
23
23
|
| 看 / 动那一屏的浏览器动作怎么发(枚举 opencli,argv 与结果原样转发) | 14 把:`workbench_diy_state` / `_html` / `_find` / `_click` / `_hover` / `_type` / `_fill` / `_keys` / `_scroll` / `_wait` / `_eval` / `_screenshot` / `_network` / `_dialog` —— 公共参数只有 `unit?` / `args?` / `timeoutMs?`(另有 `scope` / `in` / `clip` 只长在该长的那几把上;`$card` 在任何 `args` 里都替换)。**不知道参数就问 `--help`**(也照样转发);返回体是 opencli 的原样,事实在 `stdout` 里 |
|
|
24
24
|
| 这一页的控制台 / 网络怎么取证([REVIEW.md](REVIEW.md) 四步取证) | `workbench_diy_logs_watch` / `_read` / `_stop`(钩子有上限:每页 200 条、单条 400 字)—— **opencli 的 `console` 是空桩**(恒回空数组、还 exit 0),控制台只能我们自己做;网络是 `workbench_diy_network` 一把(`--follow` 臂上、`--failed` / `--since` 这些原样转发;收工 `_debug_stop` 顺手收),但**`--detail` 取不到 body** —— 缓存落在会话可写根之外,只能列请求 key;要 body 走 `_eval` 里的 `fetch`。网络那一头工作台走的是**隔离 opencli**(自己的 `HOME`),它的缓存与你自己 shell 里 `~/.opencli` 那份不是同一个 —— 核验要用工具,别看错文件 |
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
| 发货前的清单([WIRING.md](WIRING.md) §5) | `dsh-wb-module publish`(门禁 + 打包清单 `files` 漏项,只 dry run) |
|
|
28
28
|
| 干净机器上的第一脚([SKILL.md](SKILL.md) 第 4 步) | 启动器自愈骨架 + PATH 上的 `dsh-wb-module` |
|
|
29
29
|
| 会写的工具要点几次头([TOOLS.md](TOOLS.md) §6) | `writes: true` + `needsApproval` + `approvalDetail`,`check --only contracts` 查 |
|
|
30
|
-
| 跑一会儿的事怎么让用户看见([TOOLS.md](TOOLS.md) §9) | `longTasks`
|
|
30
|
+
| 跑一会儿的事怎么让用户看见([TOOLS.md](TOOLS.md) §9) | `longTasks`(注册表含 `stop`)+ kit 的 `useLongTask` / `LongTaskRow`,`check --only contracts` 查**形状** |
|
|
31
|
+
| agent 要能看 / 停长任务([TOOLS.md](TOOLS.md) §9) | **外壳自带**的 `workbench_long_tasks_list` / `workbench_long_tasks_stop`(自己起的免审批、别人的 / 用户按的要审批、外地工作区直接拒绝)—— 模块不用自己写;"谁起的"靠 `runs.start({ …, session: sessionIdOf(exec) ?? undefined })` 记(**不记 = 用户按的**) |
|
|
31
32
|
| 这个能力要靠什么才跑得起来([TOOLS.md](TOOLS.md) §8) | `manifest.env` + `libInstallCommand`,`check --only contracts` 查 |
|
|
32
33
|
| 配置存不存([PANEL.md](PANEL.md) §5.1) | `config[].persistence`(`prefs` / `view`)+ `check --only contracts` |
|
|
33
34
|
| agent 动完面板哪一格变([LINKAGE.md](LINKAGE.md) §8) | `factsOf` + `factOfRoute` / `context` / `warmReads` / `values`,`check --only contracts` 只查形状 |
|
|
@@ -47,5 +48,8 @@
|
|
|
47
48
|
- **安全取舍**:"说不清这次会发生什么就不许问" —— [TOOLS.md](TOOLS.md) §6
|
|
48
49
|
- **联动颗粒度**:动的是整卡、某一行、还是某一列 —— [LINKAGE.md](LINKAGE.md) §1
|
|
49
50
|
(工具只能保证"形状对","该多窄"是判断)
|
|
51
|
+
- **停不停得了**:那颗停止钮画不画(`LongTaskRow` 的 `stop`)、`run` 收到的 `signal` 认不认
|
|
52
|
+
([TOOLS.md](TOOLS.md) §9)—— 判据只看形状;"外壳把这一趟标成 `stopped`、你的活儿还在跑"这件事
|
|
53
|
+
机器看不出来,是模块自己的取舍
|
|
50
54
|
|
|
51
55
|
工具只负责把**事实**摆出来(状态、报错原文、预览结果),判断仍然是人 / agent 的事。
|
|
@@ -87,9 +87,27 @@
|
|
|
87
87
|
|
|
88
88
|
## 9 长任务:跑一会儿的事
|
|
89
89
|
|
|
90
|
-
跑几十秒的事(构建、发布、批量删)做成**长任务**。宿主半侧用 `mount` deps 里的 `longTasks` 建注册表并挂回返回值:`longTasks.create({ key, run, step, outcome, view })` —— `run({ payload, onStep })` 里用 `onStep({ kind: 'start', text })` 报步骤、`return` 结果;`step`/`outcome`/`view` 各映射一段给界面;返回里带上 `longTasks: runs`。
|
|
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
91
|
|
|
92
|
-
|
|
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`)、`create` 的 `key`/`run`/`view` 不能缺、产物里必须有 `useLongTask`/`LongTaskRow`/`longTaskDot` 之一。**它只查形状与"画得出来"** —— "你真认了 signal 吗、真收得住手吗"不在它的射程里(那只有你自己知道,见上面第一条)。
|
|
109
|
+
|
|
110
|
+
**要用 `stop` 这个新 prop / 新返回字段,就得装带这次改动的那份外壳**:把自己 `peerDependencies['@dsh-workbench/core']` 的下限抬到那一版 —— `check --only kit` 盯着这条(产物实际取的 kit 成员 ⊆ 你 peer 下限那份外壳的 kit)。**同理**:`start` 收 `session`、外壳那两把 `workbench_long_tasks_*` 工具,也都是这一版才有的。
|
|
93
111
|
|
|
94
112
|
## 10 用户自己按的那个入口 / 范例
|
|
95
113
|
|
|
@@ -44,7 +44,8 @@ import 运行值会被**打进你的包**(第二份实例),而且浏览器
|
|
|
44
44
|
另外两条不是"被拦",是纪律:**样式走 `*.module.css`,类名别带 `wb-` 前缀**(`wb-*` 是外壳的**全局**
|
|
45
45
|
类名:模块定义它、或借契约里没有的那一个,`check --only style` 都判红;模块自己的类名由构建**按文件路径
|
|
46
46
|
哈希**,天然不会跟谁撞。要借外壳契约里的类,写 `:global(.wb-…)`,且那个类必须在契约里);
|
|
47
|
-
**`src/**`
|
|
47
|
+
**`src/**` 里只许 JSDoc、且贴着导出**(用法归声明;`//`、实现里的解释与 `.css` 注释都不许)——
|
|
48
|
+
为什么写进 `README.md` 或 spec。
|
|
48
49
|
|
|
49
50
|
## 3 装上、上板
|
|
50
51
|
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# HOOKS:kit 里那些 hook 怎么用
|
|
2
|
-
|
|
3
|
-
**为什么有这份**:外壳与共享包的源码按"零注释"规矩写,emit 出来的 `<名字>.d.ts` 里**没有说明文字** ——
|
|
4
|
-
只有参数名与类型。组件的 prop 一般看得出来,hook 的语义看不出来(什么时候调、取数什么时候重来、
|
|
5
|
-
"在忙"为什么还要挂一会儿)。这份就是那些语义的出处。
|
|
6
|
-
**原语那 5 个 hook 不在这里**(`useAnchoredPosition` / `useAnchoredMaxHeight` / `useCodeHighlighter` /
|
|
7
|
-
`useDismissOnOutsidePointer` / `useModalLayer`):它们的 `.d.ts` 自带 JSDoc。
|
|
8
|
-
|
|
9
|
-
问清单、或要某一件的类型文件:`workbench_diy_components`(`name=useVisibleBusy`)。
|
|
10
|
-
|
|
11
|
-
## 0 两条路,别绕开
|
|
12
|
-
|
|
13
|
-
- **取数**:卡片里所有读都走 `useApiResource`。它自带三件事 —— 同 key 合流、事实通道订阅("这条变了"自动重取)、
|
|
14
|
-
把读数上报面板(`workbench_diy_panel` 看的就是它)。
|
|
15
|
-
- **状态**:`useVisibleBusy`(在忙 / 安定)+ `useCapabilityGate`(被能力挡住没)+ `useUnitBoard`(把两者与
|
|
16
|
-
卡片读数叠成一组)。**别自己 `useState` + `setTimeout` 造一套**:面板要的是同一套口径。
|
|
17
|
-
|
|
18
|
-
## 1 取数与事实
|
|
19
|
-
|
|
20
|
-
### `useApiResource(route, query, refreshToken, options?)` → `{ phase, value?, error?, at? }`
|
|
21
|
-
|
|
22
|
-
详见 [LINKAGE.md](LINKAGE.md) §8。`phase` 四态 `idle` / `loading` / `ready` / `error`;`value` 是
|
|
23
|
-
`ApiResponse<T>`(`ok` / `data`);`options`:`enabled`(前置条件不满足就别发)、`key`(自定义身份)、
|
|
24
|
-
`fact`(订哪条事实,默认 `factOfRoute(route)`)、`report: false`(不上报面板 —— 夹具 / 后台探针用)。
|
|
25
|
-
|
|
26
|
-
### `useFactRevision(fact)` → 版本号 | `useFactInvalidate()` → `(fact?) => void`
|
|
27
|
-
|
|
28
|
-
详见 [LINKAGE.md](LINKAGE.md) §8。前者是"这条事实的版本",喂给 `useApiResource`(默认订阅已经接好);
|
|
29
|
-
后者是**写完之后**把这条标脏 —— 不标,面板就停在旧值。
|
|
30
|
-
|
|
31
|
-
### `subscribeFactRevision(fact, listener)` → 退订函数
|
|
32
|
-
|
|
33
|
-
非 hook:给"不在渲染里"的地方用(宿主事件、定时器里看到的旧值)。返回值就是退订函数,**记得退订**。
|
|
34
|
-
渲染里要版本号用 `useFactRevision`,别自己 `useEffect` + `subscribe` 绕一圈。
|
|
35
|
-
|
|
36
|
-
### `resourceIdentity(route, query, override?)` | `readKeyOf(identity, refreshToken, factRev)`
|
|
37
|
-
|
|
38
|
-
都不是 hook:前者是"这次读的身份"(`route` + 排序后的 query;给了 `override` 就用它);后者把身份与两个
|
|
39
|
-
版本号拼成**合流的 key**。要自己管缓存时才用得上。
|
|
40
|
-
|
|
41
|
-
### `requestResource(route, query, key)` → `{ task, release }`
|
|
42
|
-
|
|
43
|
-
非 hook 的取数本体:**同 key 只发一次**(并发调用共享同一个 `task`),最后一个 `release()` 才 abort。
|
|
44
|
-
`useApiResource` 内部就是它 + 事实订阅 + 上报。用它就**一定要 `release`**(放 `finally`),否则那条请求永不取消。
|
|
45
|
-
|
|
46
|
-
## 2 忙与安定
|
|
47
|
-
|
|
48
|
-
### `useVisibleBusy(active, floorMs)` → `{ busy, settling }`
|
|
49
|
-
|
|
50
|
-
**最短可见时长**:`active` 变真立刻 `busy`;变假时若这次忙还没到 `floorMs`,`busy` 先挂着,这期间
|
|
51
|
-
`settling: true`("读数在回来的路上")。`floorMs` 传共享包的 `MIN_BUSY_MS`,别自己编。
|
|
52
|
-
为什么:取数几毫秒就回来时不挂一会儿,用户看到的是"闪一下",比不转还难读(ADR-0022)。
|
|
53
|
-
|
|
54
|
-
### `busyView(active, visible)` | `heldPhase(phase, refreshing)`
|
|
55
|
-
|
|
56
|
-
两个纯函数(不是 hook,好测):`busyView` 是"真忙还是在收尾"的投影,`useVisibleBusy` 用的就是它;
|
|
57
|
-
`heldPhase` 把"正在刷新"折进 `phase`(`refreshing && phase === 'ready'` ⇒ `'loading'`),
|
|
58
|
-
让"旧值还显示着、新的在路上"这种态有名字。
|
|
59
|
-
|
|
60
|
-
## 3 卡片那一格与能力门
|
|
61
|
-
|
|
62
|
-
### `useUnitBoard({ unit, gate, refreshToken, onRefresh, onSummary, read, t, enabled?, platformBlocker? })`
|
|
63
|
-
|
|
64
|
-
**卡片起步就用它**:把"gate 挡着就不读"、忙 / 安定、探针(被能力挡住或读数报错时自动 `forceProbe`)、
|
|
65
|
-
`useCardSummary`(把 `{ refreshing, at }` 交给调用点上报面板)、以及 blocker 的拆分
|
|
66
|
-
(`capabilityBlocker` / `errorBlocker`)一次叠好。`read(gated)` 是你自己的读数函数(里面调 `useApiResource`);
|
|
67
|
-
返回 `feed`(含 `state`)、`capability`、`gated`、`refresh`、`forceProbe`、`summary` 与那几个 blocker 字段。
|
|
68
|
-
|
|
69
|
-
### `useCapabilityGate(unit, refreshToken, options?)` → `{ capability, gated, forceProbe }`
|
|
70
|
-
|
|
71
|
-
这一格的**能力**(`design.json` 里声明的那份)与"挡住了没"。`options.mode` 换门的口径(默认 `'capability'`),
|
|
72
|
-
`options.fixture` 给夹具。`forceProbe()` 是"现在就去探一次"。
|
|
73
|
-
|
|
74
|
-
### `useCapabilityMap(refreshToken)` → `{ capabilities, checkedAt, checking, forceProbe, reload }`
|
|
75
|
-
|
|
76
|
-
**全部**单元的能力表(不是一格):设置页 / 体检那一类界面用。`checking` 是探针在跑,`reload()` 重取整张表。
|
|
77
|
-
|
|
78
|
-
### `useCardSummary(onSummary, summary)`
|
|
79
|
-
|
|
80
|
-
把 `{ refreshing, at }` 在变化时交给调用点(`onSummary` 要**稳定引用**,一般 `useCallback`)——
|
|
81
|
-
面板那行"最近一次读是什么时候"就是这么上去的。`useUnitBoard` 已经接好;自己搭卡片才直接用。
|
|
82
|
-
|
|
83
|
-
## 4 写与长任务
|
|
84
|
-
|
|
85
|
-
### `useHostWrite({ channel, resource?, networkText })` → `{ busy, failed, detail, send, fail, clear }`
|
|
86
|
-
|
|
87
|
-
详见 [PANEL.md](PANEL.md) §5(写通道与配置持久化)。`send(body, { silent })` 回 `WriteOutcome`
|
|
88
|
-
(`ok` / 人话 message / detail),失败自动落到 `failed` + `detail`(界面直接显示,别自己拼);`silent` 是不打扰;
|
|
89
|
-
`fail` 是"本地就知道不行"时自己塞一条。**写只走这条**:别在组件里直接 `fetch`。
|
|
90
|
-
|
|
91
|
-
### `useLongTask({ unit, workspace, mocked?, enabled?, pollMs? })` → `{ runs }`
|
|
92
|
-
|
|
93
|
-
详见 [TOOLS.md](TOOLS.md) §9。轮询这一单元的长任务:有在跑的(`running` / `needs-login`)按
|
|
94
|
-
`longTaskPollMs` 快档轮,空闲慢档;`workspace` 为 null 或 `mocked` 时不轮。画用 `<LongTaskRow …/>`,
|
|
95
|
-
点用 `longTaskDot(status)`。
|