@dsh-workbench/module-tools 0.3.5 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/module-tools",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "模块包的构建与门禁工具:build(单文件 ESM 宿主侧 + 套 loader 壳的浏览器侧)、check(契约/产物边界/源码边界/卡片渲染)。模块包不再各带一份构建脚本。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  "eslint-plugin-react": "^7.37.5",
30
30
  "eslint-plugin-react-hooks": "^7.1.1",
31
31
  "typescript-eslint": "^8.71.0",
32
- "@dsh-workbench/shared": "0.3.5"
32
+ "@dsh-workbench/shared": "0.3.6"
33
33
  },
34
34
  "scripts": {
35
35
  "selfcheck": "node scripts/selfcheck/index.mjs"
@@ -251,11 +251,14 @@ function fakeLongTasks(created) {
251
251
  return {
252
252
  create(options) {
253
253
  created.push(options);
254
+ // 形状跟着真外壳那份注册表走(`createLongTaskRegistry`):假的那份少一个方法,
255
+ // 把注册表原样返回的模块就会被这条判据冤枉成"缺方法"——所以 `stop` 也在这里。
254
256
  return {
255
257
  start: () => ({}),
256
258
  snapshot: () => null,
257
259
  active: () => null,
258
260
  current: () => [],
261
+ stop: () => false,
259
262
  onChange: () => () => {},
260
263
  };
261
264
  },
@@ -347,7 +350,7 @@ function mountOf(face, created, manifest, tools) {
347
350
  }
348
351
  }
349
352
 
350
- const LONG_TASK_METHODS = ['start', 'snapshot', 'active', 'current', 'onChange'];
353
+ const LONG_TASK_METHODS = ['start', 'snapshot', 'active', 'current', 'stop', 'onChange'];
351
354
 
352
355
  const LONG_TASK_RENDERERS = ['useLongTask', 'LongTaskRow', 'longTaskDot'];
353
356
 
@@ -788,12 +791,19 @@ export async function run() {
788
791
  }
789
792
  }
790
793
 
791
- // 长任务:跑一会儿的事,注册了就得让用户看得见那四盏灯。
794
+ // 长任务:跑一会儿的事,注册了就得让用户看得见那一行。
792
795
  //
793
796
  // 外壳按 mount 结果里的 longTasks 投影出 `${unit}/runs`(kit 的 useLongTask 读的就是它)。
794
- // 契约里的状态只有四种:running / needs-login / ok / failed(`longTaskDot` 把它们映成
795
- // 进行中 / 要登录 / 完成 / 出错)——**没有"停止"**,所以这里不查停止,只查"能不能画出来":
796
- // · 注册表形状要全(少了方法,外壳那条 runs 路由读不动);
797
+ // 契约里的状态是**五种**:running / needs-login / stopped / ok / failed(`longTaskDot` 把它们映成
798
+ // 进行中 / 要登录 / 灰点 / 完成 / 出错 —— `stopped` 落进默认分支,是"用户按停的",不是成功也不是出错)。
799
+ // 契约里也**有"停止"**:注册表多一个 `stop(id)`,`run` 多收一个 `signal`。
800
+ // `start` 还可以带一个**可选**的 `session`(起这一趟的那个会话 id,模块从 `sessionIdOf(exec)` 拿):
801
+ // 不记 = 用户在界面上按的 —— 外壳那两把 `workbench_long_tasks_*` 按它判"谁的"(自己起的免审批)。
802
+ // 判据**不查**这一条(它只查形状;"起的时候记没记来源"是形状之外的事)。
803
+ // 但"停止"在注册表这一侧的射程只有两件:外壳把 `signal` abort 掉、把这一趟标成 `stopped`;
804
+ // **真停不停得了看生产方认不认这个 signal** —— 那是模块的知识,判据查不了这一条
805
+ // (内部是 opencli 子进程那类编排,今天最多做到"我们不再等它")。判据能查的是形状与"画得出来":
806
+ // · 注册表形状要全(少了方法,外壳那条 runs 路由 / 那颗停止钮就按不动);
797
807
  // · create 的 key 非空、run 与 view 都是函数(没有 view,卡片上那行就没有可渲染的快照);
798
808
  // · 真的注册了长任务,客户端产物里就得出现 kit 那套(useLongTask / LongTaskRow / longTaskDot),
799
809
  // 否则用户点完只看到"没反应"。
@@ -803,7 +813,7 @@ export async function run() {
803
813
  const missing = LONG_TASK_METHODS.filter((method) => typeof registry[method] !== 'function');
804
814
  if (missing.length > 0) {
805
815
  problems.push(
806
- `${name}: 长任务注册表缺方法 ${missing.join(' / ')} —— 外壳按它给这个单元投影出 /runs 路由,` +
816
+ `${name}: 长任务注册表缺方法 ${missing.join(' / ')} —— 外壳按它给这个单元投影出 /runs 路由(停止那颗钮也按它发),` +
807
817
  `kit 的 useLongTask 读的就是那条(注册表用 @dsh-workbench/shared/host 的 createLongTaskRegistry 建)`,
808
818
  );
809
819
  }
@@ -827,7 +837,7 @@ export async function run() {
827
837
  if (!LONG_TASK_RENDERERS.some((one) => clientText.includes(one))) {
828
838
  problems.push(
829
839
  `${name}: 注册了长任务,但客户端产物里没有 ${LONG_TASK_RENDERERS.join(' / ')}(kit 里那套)—— ` +
830
- `用户点完只看得到"没反应",看不到进行中 / 要登录 / 完成 / 出错`,
840
+ `用户点完只看得到"没反应",看不到进行中 / 要登录 / 停下的 / 完成 / 出错`,
831
841
  );
832
842
  }
833
843
  }
@@ -194,7 +194,12 @@ approvalDetail: async (call) => ({ zh: '会覆盖 3 个模块的产物,不碰
194
194
  mount({ longTasks, ... }) {
195
195
  const runs = longTasks?.create({
196
196
  key: 'rebuild',
197
- run: async ({ payload, onStep }) => { onStep({ kind: 'start', text: '开始…' }); /* … */ return {}; },
197
+ run: async ({ payload, onStep, signal }) => {
198
+ onStep({ kind: 'start', text: '开始…' });
199
+ // signal 被 abort = 有人按了停止(用户 / agent):每一轮都看一眼 `signal.aborted`,能收手就收手
200
+ /* … */
201
+ return {};
202
+ },
198
203
  outcome: (result) => ({ why: '…' }),
199
204
  view: (snapshot) => ({ steps: String(snapshot.steps.length) }),
200
205
  });
@@ -202,9 +207,27 @@ mount({ longTasks, ... }) {
202
207
  }
203
208
  ```
204
209
 
205
- 浏览器侧用 kit 那套画(`kit.hooks.useLongTask` + `<LongTaskRow …/>`):状态只有
206
- `running` 进行中 / `needs-login` 要登录 / `ok` 完成 / `failed` 出错 —— **契约里没有"停止"**,
207
- 别在界面上摆一个不存在的按钮。
210
+ 起这一趟的那把工具里(`execute(args, exec)` 拿得到现场)记下来源:
211
+
212
+ ```ts
213
+ runs.start({ workspace, target, payload, session: sessionIdOf(exec) ?? undefined });
214
+ ```
215
+
216
+ `sessionIdOf`(`@dsh-workbench/shared/host`)说这次调用是哪个会话发的。**不记来源 = 用户按的**
217
+ (或这一版之前起的)—— 停它要走审批;外壳自带的那两把 `workbench_long_tasks_list` /
218
+ `workbench_long_tasks_stop` 就是这么判"谁的"(自己起的免审批,别人的 / 用户按的要审批,
219
+ 不在当前工作区的直接拒绝)。
220
+
221
+ 浏览器侧用 kit 那套画(`kit.hooks.useLongTask` + `<LongTaskRow …/>`):状态有五种 ——
222
+ `running` 进行中 / `needs-login` 要登录 / `stopped` 停下的(用户按停)/ `ok` 完成 / `failed` 出错。
223
+
224
+ 要给用户那颗停止钮,就在卡片上给那一行的 `LongTaskRow` 传 `stop`
225
+ (词与取值照 dsh 任务列表那行:aria `停止任务 {label}` → 上膛 → 可见字 `确认停止`、
226
+ aria `再次点击确认停止`,3 秒落膛;**失败那半是我们自己的** —— 不往钮里写「停止失败」,
227
+ 而是把这次请求失败的原话画在那一行下面,`failed` 给什么画什么);**要停就认 `signal`,不认就别画** ——
228
+ 画了就是骗人:外壳只会把这一趟标成 `stopped`,你的活儿还在跑(内部是 opencli 子进程那类编排,
229
+ 今天最多做到"我们不再等它")。要用这个新 prop 就得装带这次改动的那份外壳 ——
230
+ 把自己 `peerDependencies['@dsh-workbench/core']` 的下限抬到那一版。
208
231
 
209
232
  ### 存起来:他能自己配什么(`prefs`)
210
233