@feng0531/dsh-execution-bus 0.10.0 → 0.11.2

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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  “萧何”是产品与模式显示名,执行总线是其技术功能;不新增人物 Agent,不进行角色扮演。仓库 dsh-execution-bus、npm 包名、execution_bus 工具名及 execution-bus-test 预设 ID 保持不变。历史版本发布说明保留当时名称。
6
6
 
7
- DSH 独立插件:`@feng0531/dsh-execution-bus` · 当前版本 **0.10.0**
7
+ DSH 独立插件:`@feng0531/dsh-execution-bus` · 当前版本 **0.11.2**
8
8
 
9
9
  > 萧何模式从宿主已声明的 PTC 组合派生,保留日常工具,再叠加可选执行总线。已验证隔离的真实 Node PTC → 原 read + 总线调用,以及脚本模型内存会话;真实模型、桌面界面与实际沙箱仍未验收。**不支持重启后恢复任务。**
10
10
 
@@ -130,7 +130,7 @@ flowchart TD
130
130
  2. 在你选定的隔离 DSH 配置依赖目录安装固定版本,不要在 DSH 程序安装目录执行:
131
131
 
132
132
  ```powershell
133
- npm install --ignore-scripts @feng0531/dsh-execution-bus@0.10.0
133
+ npm install --ignore-scripts @feng0531/dsh-execution-bus@0.11.2
134
134
  ```
135
135
 
136
136
  这是 npm 目录示例。若目标配置使用 pnpm,应沿用其包管理器,不混用锁文件。遇到宿主 peer 版本冲突就停止,不用强制安装绕过。
@@ -201,7 +201,9 @@ flowchart TD
201
201
 
202
202
  PTC 是 DSH 的程序化工具调用入口。在 `run_code` 内使用 `await tools.execution_bus(请求对象)`,而不是直接调用折叠后的工具名。执行总线不会解除 `run_code` 自身的时间预算。已有真实 Node PTC 隔离进程读文件和调用总线的验证,但尚未进行实际模型/桌面沙箱全链路验收。
203
203
 
204
- **0.10.0 起,模式说明与工具声明给出明确触发条件。** 仅当预计单次宿主操作超过约 30 秒、同一逻辑操作可能重复提交(沿用 requestId)、任务需要在 `run_code` 预算结束后继续、或多个步骤需要等待同一执行时,才使用 `execution_bus`;其余任务继续用原工具,避免为简单调用增加包装。工具声明改为按 Handler 的判别联合(`host.read`/`host.glob`/`host.grep`/`host.edit`/`host.write`/`host.pwsh`/`test`),PTC SDK 与原生声明里可直接看到每个 Handler 的必填字段,而不是泛型 `payload`。
204
+ **0.11.0 起,总线改为收益导向触发并支持简化调用。** 只在能获得协调收益时使用:同一逻辑操作可能重复提交或需要重试去重(沿用 requestId)、任务需要在 `run_code` 预算结束后继续/跨步骤取回、或多个步骤与等待者需要同一执行结果;**“操作耗时长”本身不是理由**,一次性长命令请用原工具或宿主后台 Job。工具入口支持简化调用 `{requestId, handler, input}`(messageId 可省略、自动生成)和控制 `{requestId, type:"status"|"result"|"cancel"}`,完整 v1 消息仍被接受;返回快照在复用时带 `reused: true`。工具声明仍是按 Handler 的判别联合,PTC SDK 与原生声明里可直接看到每个 Handler 的必填字段。
205
+
206
+ **跨步骤取回的正确姿势(0.11.1 补充):** 同一 `run_code` 程序内,未完成的 request 会阻塞后发工具调用的结果提交(宿主按提交顺序结算);不要在同一个程序里“启动后立刻 status 轮询”。启动步骤只发请求并尽早结束程序,下一步再用同一 `requestId` 的 `status`/`result`(或重发同一 request)附着取回——pilot 实测单次执行、返回 `reused:true`。
205
207
 
206
208
  **0.9.0 起,预算到期不再连带取消已受理执行。** `run_code` 因时间预算到期或程序结束时,宿主会中断其中的工具子调用;总线把这种中断视为**只停止等待**:已受理执行与宿主 Job 继续,程序返回预算错误后,可在后续 `run_code` 中用同一 `requestId` 的 `status`/`result` 取回结果,或用 `cancel` 取消。用户主动中断、会话取消、沙箱模式变化等其他原因仍会请求取消整个执行。该识别依赖 DSH 0.2.1-rc.2 的中断原因字符串(见[宿主兼容性](<docs/host-compatibility.md>)),不是宿主公开稳定契约;进程退出仍会丢失全部内存状态,保留期一小时。
207
209
 
@@ -264,7 +266,7 @@ npm run verify:package # 检查公开包内容
264
266
  npm run test:install # 临时目录安装,不操作日常 DSH
265
267
  ```
266
268
 
267
- 这些测试不等于“当前聊天会话直接调用插件”。真实 AgentLoop 测试使用脚本模型适配器;工具组件测试使用测试 Agent。具体边界见[流程测试说明](<docs/FLOW-TEST.md>)。构建候选安装包可运行 `npm run pack:candidate`。
269
+ 这些测试不等于“当前聊天会话直接调用插件”。真实 AgentLoop 测试使用脚本模型适配器;工具组件测试使用测试 Agent。具体边界见[流程测试说明](<docs/FLOW-TEST.md>)。构建候选安装包可运行 `npm run pack:candidate`。观察本机真实会话的总线采用率可运行 `npm run metrics:sessions`(只读扫描会话日志,统计各会话的请求类型、Handler、复用标记与错误)。
268
270
 
269
271
  ## 卸载与回滚
270
272
 
@@ -278,7 +280,7 @@ npm run test:install # 临时目录安装,不操作日常 DSH
278
280
 
279
281
  - [协议和投递语义](<docs/protocol.md>):字段、状态、重复处理与取消规则。
280
282
  - [宿主兼容性与源码调用链](<docs/host-compatibility.md>):为什么可以等待,为什么不能跨重启恢复。
281
- - [验证记录](<docs/validation.md>)、[0.10.0 发布说明](<docs/releases/0.10.0.md>):已验证与未验证范围。
283
+ - [验证记录](<docs/validation.md>)、[0.11.2 发布说明](<docs/releases/0.11.2.md>):已验证与未验证范围。
282
284
  - [同类项目源码整合](<docs/INTEGRATION.md>):固定来源、采用范围、许可与验证边界。
283
285
  - [维护者发布流程](<docs/RELEASING.md>):标签 CI、安装包校验与正式发布门禁。
284
286
 
package/dist/bus.d.ts CHANGED
@@ -65,6 +65,8 @@ export declare class ExecutionBus {
65
65
  submit(owner: Owner, raw: unknown, onEvent?: (e: Event) => void, invoke?: Capability): {
66
66
  accepted: Event;
67
67
  done: Promise<RecordView>;
68
+ /** 本次附着是否复用已有逻辑执行(结果标记可见性用)。 */
69
+ reused: boolean;
68
70
  /** 当前快照:供仅在停止等待时使用,不改变交付标记。 */
69
71
  snapshot: () => RecordView;
70
72
  cancel: () => void;
package/dist/bus.js CHANGED
@@ -187,7 +187,7 @@ export class ExecutionBus {
187
187
  if (!previous && e.messages.size >= LIMITS.messageAccountLimit)
188
188
  throw Error(ERRORS.MESSAGE_LIMIT);
189
189
  // 先取得附着,再提交消息身份:附着被拒(WAITER_LIMIT)时无副作用。
190
- const attached = this.attachment(e, onEvent, m.messageId);
190
+ const attached = this.attachment(e, onEvent, m.messageId, true);
191
191
  if (!previous)
192
192
  e.messages.set(m.messageId, canonical(m));
193
193
  this.telemetry.record(e.trace, "reused");
@@ -262,7 +262,7 @@ export class ExecutionBus {
262
262
  };
263
263
  e.queueTimer = setTimeout(expire, this.admission.maxQueueWaitMs);
264
264
  }
265
- attachment(e, onEvent, id) {
265
+ attachment(e, onEvent, id, reused = false) {
266
266
  // 每次附着独立占配额,即使调用方复用同一个回调。
267
267
  const listener = onEvent ? (event) => onEvent(event) : undefined;
268
268
  if (listener && !isTerminal(e.view.state)) {
@@ -282,6 +282,8 @@ export class ExecutionBus {
282
282
  return {
283
283
  accepted,
284
284
  done: e.done,
285
+ /** 本次附着是否复用已有逻辑执行(结果标记可见性用)。 */
286
+ reused,
285
287
  /** 当前快照:供仅在停止等待时使用,不改变交付标记。 */
286
288
  snapshot: () => this.view(e),
287
289
  cancel: () => this.cancel(e),
package/dist/plugin.js CHANGED
@@ -10,6 +10,43 @@ const PTC_PROGRAM_SETTLED = "run_code settled";
10
10
  function isPtcProgramEnd(signal) {
11
11
  return signal.aborted && signal.reason === PTC_PROGRAM_SETTLED;
12
12
  }
13
+ const REQUEST_KEYS = new Set(["requestId", "messageId", "handler", "input"]);
14
+ const CONTROL_KEYS = new Set(["requestId", "messageId", "type"]);
15
+ /** 工具入口兼容两种形状:0.11.0 简化调用 {requestId,handler,input,messageId?}
16
+ * 或 {requestId,type,messageId?},以及完整 v1 消息。简化形状在进入总线前补齐协议字段。 */
17
+ function normalizeToolInput(raw) {
18
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
19
+ return raw;
20
+ const input = raw;
21
+ if (Object.hasOwn(input, "protocolVersion"))
22
+ return raw;
23
+ if (Object.keys(input).some((key) => !REQUEST_KEYS.has(key) && !CONTROL_KEYS.has(key)) ||
24
+ typeof input.requestId !== "string")
25
+ throw Error(ERRORS.INVALID_REQUEST);
26
+ const messageId = typeof input.messageId === "string" ? input.messageId : randomUUID();
27
+ if (Object.hasOwn(input, "handler")) {
28
+ if (Object.hasOwn(input, "type") ||
29
+ typeof input.handler !== "string")
30
+ throw Error(ERRORS.INVALID_REQUEST);
31
+ return {
32
+ protocolVersion: 1,
33
+ messageId,
34
+ requestId: input.requestId,
35
+ type: "request",
36
+ payload: { handler: input.handler, input: input.input },
37
+ };
38
+ }
39
+ if (typeof input.type !== "string" ||
40
+ !["status", "result", "cancel"].includes(input.type))
41
+ throw Error(ERRORS.INVALID_REQUEST);
42
+ return {
43
+ protocolVersion: 1,
44
+ messageId,
45
+ requestId: input.requestId,
46
+ type: input.type,
47
+ payload: {},
48
+ };
49
+ }
13
50
  export const name = "execution-bus";
14
51
  export const inject = ["tools", "jobs"];
15
52
  export function apply(ctx) {
@@ -69,7 +106,7 @@ export function apply(ctx) {
69
106
  sessionId: String(agent.id),
70
107
  callId: String(exec.callId),
71
108
  };
72
- const m = parse(raw);
109
+ const m = parse(normalizeToolInput(raw));
73
110
  if (m.type !== "request")
74
111
  return bus.command(owner, m);
75
112
  // 宿主工具转发:仅 host.* handler 注入对应工具的 capability。
@@ -127,7 +164,7 @@ export function apply(ctx) {
127
164
  try {
128
165
  const value = await attached.done;
129
166
  attached.returned();
130
- return value;
167
+ return markReused(value, attached);
131
168
  }
132
169
  finally {
133
170
  attached.detach();
@@ -154,6 +191,10 @@ export function apply(ctx) {
154
191
  throw Error(ERRORS.HOST_JOB_UNAVAILABLE);
155
192
  }
156
193
  }
194
+ /** 本次调用是否复用了已有执行;仅在真复用时追加标记,保持首次快照形状不变。 */
195
+ function markReused(view, attached) {
196
+ return attached.reused ? { ...view, reused: true } : view;
197
+ }
157
198
  /** 外层取消映射:PTC 程序结束(含预算到期)只停止本次等待,已受理执行与
158
199
  * 宿主 Job 保留,同会话可用同 requestId 的 status/result 取回;其他取消原因
159
200
  * (用户/会话中断)仍请求取消整个执行。 */
@@ -178,12 +219,12 @@ export function apply(ctx) {
178
219
  detachedView.then((view) => ({ kind: "detached", view })),
179
220
  ]);
180
221
  if (outcome.kind === "detached")
181
- return outcome.view;
222
+ return markReused(outcome.view, attached);
182
223
  if (outcome.kind === "error")
183
224
  throw outcome.error;
184
225
  const value = await attached.done;
185
226
  attached.returned();
186
- return value;
227
+ return markReused(value, attached);
187
228
  }
188
229
  finally {
189
230
  exec.signal.removeEventListener("abort", abort);
package/dist/product.d.ts CHANGED
@@ -23,14 +23,18 @@ export declare const HANDLER_LIMITS: Readonly<{
23
23
  readPathChars: 2048;
24
24
  diagnosticDelayMs: 30000;
25
25
  }>;
26
- /** request 的 {handler,input} 判别联合;控制消息为空对象。 */
27
- export declare const PAYLOAD_SCHEMA: {
28
- readonly description: "request 必须匹配某个 Handler 的 {handler,input};status/result/cancel 的 payload 必须为空对象 {}。";
26
+ export declare const REQUEST_PARAMETERS: {
29
27
  readonly oneOf: readonly [...{
30
28
  type: "object";
31
29
  additionalProperties: false;
32
30
  required: string[];
33
31
  properties: {
32
+ requestId: {
33
+ readonly type: "string";
34
+ };
35
+ messageId: {
36
+ readonly type: "string";
37
+ };
34
38
  handler: {
35
39
  type: "string";
36
40
  const: "host.read" | "host.glob" | "host.grep" | "host.edit" | "host.write" | "host.pwsh" | "test";
@@ -175,191 +179,26 @@ export declare const PAYLOAD_SCHEMA: {
175
179
  }[], {
176
180
  readonly type: "object";
177
181
  readonly additionalProperties: false;
178
- readonly properties: {};
179
- }];
180
- };
181
- export declare const REQUEST_PARAMETERS: {
182
- readonly type: "object";
183
- readonly additionalProperties: false;
184
- readonly required: readonly ["protocolVersion", "messageId", "requestId", "type", "payload"];
185
- readonly properties: {
186
- readonly protocolVersion: {
187
- readonly type: "integer";
188
- readonly const: 1;
189
- };
190
- readonly messageId: {
191
- readonly type: "string";
192
- readonly description: "Unique id of this message; reuse only when redelivering the same message.";
193
- };
194
- readonly requestId: {
195
- readonly type: "string";
196
- readonly description: "Stable id of one logical operation; reuse it when the same operation is submitted again.";
197
- };
198
- readonly type: {
199
- readonly type: "string";
200
- readonly enum: readonly ["request", "status", "result", "cancel"];
201
- readonly description: "request submits work; status/result read the saved snapshot; cancel requests cancellation.";
202
- };
203
- readonly payload: {
204
- readonly description: "request 必须匹配某个 Handler 的 {handler,input};status/result/cancel 的 payload 必须为空对象 {}。";
205
- readonly oneOf: readonly [...{
206
- type: "object";
207
- additionalProperties: false;
208
- required: string[];
209
- properties: {
210
- handler: {
211
- type: "string";
212
- const: "host.read" | "host.glob" | "host.grep" | "host.edit" | "host.write" | "host.pwsh" | "test";
213
- };
214
- input: {
215
- readonly type: "object";
216
- readonly additionalProperties: false;
217
- readonly required: readonly ["delayMs", "fail"];
218
- readonly properties: {
219
- readonly delayMs: {
220
- readonly type: "integer";
221
- readonly description: "Diagnostic delay in milliseconds (0-30000).";
222
- };
223
- readonly fail: {
224
- readonly type: "boolean";
225
- readonly description: "Settle as a controlled failure when true.";
226
- };
227
- };
228
- } | {
229
- readonly type: "object";
230
- readonly additionalProperties: false;
231
- readonly required: readonly ["file_path"];
232
- readonly properties: {
233
- readonly file_path: {
234
- readonly type: "string";
235
- readonly description: "Path resolved against the session working directory.";
236
- };
237
- readonly offset: {
238
- readonly type: "integer";
239
- readonly description: "1-based first line to read.";
240
- };
241
- readonly limit: {
242
- readonly type: "integer";
243
- readonly description: "Maximum lines to read (<=100).";
244
- };
245
- };
246
- } | {
247
- readonly type: "object";
248
- readonly additionalProperties: false;
249
- readonly required: readonly ["pattern"];
250
- readonly properties: {
251
- readonly pattern: {
252
- readonly type: "string";
253
- readonly description: "Glob pattern; a bare pattern matches basenames at any depth.";
254
- };
255
- readonly path: {
256
- readonly type: "string";
257
- readonly description: "Directory to search; defaults to the session working directory.";
258
- };
259
- };
260
- } | {
261
- readonly type: "object";
262
- readonly additionalProperties: false;
263
- readonly required: readonly ["pattern"];
264
- readonly properties: {
265
- readonly pattern: {
266
- readonly type: "string";
267
- readonly description: "Ripgrep regular expression.";
268
- };
269
- readonly path: {
270
- readonly type: "string";
271
- readonly description: "File or directory to search; defaults to the session working directory.";
272
- };
273
- readonly include: {
274
- readonly type: "string";
275
- readonly description: "One positive glob filter; negation and comma lists are rejected.";
276
- };
277
- };
278
- } | {
279
- readonly type: "object";
280
- readonly additionalProperties: false;
281
- readonly required: readonly ["file_path", "old_string", "new_string"];
282
- readonly properties: {
283
- readonly file_path: {
284
- readonly type: "string";
285
- readonly description: "File to edit.";
286
- };
287
- readonly old_string: {
288
- readonly type: "string";
289
- readonly description: "Literal text to replace.";
290
- };
291
- readonly new_string: {
292
- readonly type: "string";
293
- readonly description: "Replacement text.";
294
- };
295
- readonly replace_all: {
296
- readonly type: "boolean";
297
- readonly description: "Replace every occurrence.";
298
- };
299
- };
300
- } | {
301
- readonly type: "object";
302
- readonly additionalProperties: false;
303
- readonly required: readonly ["file_path", "content"];
304
- readonly properties: {
305
- readonly file_path: {
306
- readonly type: "string";
307
- readonly description: "File to write.";
308
- };
309
- readonly content: {
310
- readonly type: "string";
311
- readonly description: "Full file content.";
312
- };
313
- };
314
- } | {
315
- readonly type: "object";
316
- readonly additionalProperties: false;
317
- readonly required: readonly ["description", "command"];
318
- readonly properties: {
319
- readonly description: {
320
- readonly type: "string";
321
- readonly description: "Clear, concise description of what the command does (shown in the UI).";
322
- };
323
- readonly command: {
324
- readonly type: "string";
325
- readonly description: "PowerShell command to execute.";
326
- };
327
- readonly timeoutMs: {
328
- readonly type: "number";
329
- readonly description: "Positive timeout in milliseconds.";
330
- };
331
- readonly workdir: {
332
- readonly type: "string";
333
- readonly description: "Working directory; defaults to the session working directory.";
334
- };
335
- readonly run_in_background: {
336
- readonly type: "boolean";
337
- readonly description: "Return a host Job id immediately instead of waiting.";
338
- };
339
- readonly sandbox_permissions: {
340
- readonly type: "string";
341
- readonly enum: readonly ["workspace-write", "danger-full-access"];
342
- readonly description: "Narrowest wider sandbox mode; must be paired with justification.";
343
- };
344
- readonly justification: {
345
- readonly type: "string";
346
- readonly description: "Required with sandbox_permissions; one sentence of reason.";
347
- };
348
- };
349
- };
350
- };
351
- }[], {
352
- readonly type: "object";
353
- readonly additionalProperties: false;
354
- readonly properties: {};
355
- }];
182
+ readonly required: readonly ["requestId", "type"];
183
+ readonly properties: {
184
+ readonly requestId: {
185
+ readonly type: "string";
186
+ };
187
+ readonly messageId: {
188
+ readonly type: "string";
189
+ };
190
+ readonly type: {
191
+ readonly type: "string";
192
+ readonly enum: readonly ["status", "result", "cancel"];
193
+ readonly description: "Read the saved snapshot or request cancellation for this requestId.";
194
+ };
356
195
  };
357
- };
196
+ }];
358
197
  };
359
198
  export declare const STATE_PARAMETERS: {
360
199
  readonly type: "object";
361
200
  readonly properties: {};
362
201
  readonly additionalProperties: false;
363
202
  };
364
- export declare const REQUEST_DESCRIPTION = "\u8427\u4F55 / Xiao He. Submit a version 1 request and WAIT for its terminal result in this tool call. Use only when a host operation is expected to exceed ~30s, may be retried as the same logical operation (reuse requestId), must outlive a run_code budget end, or several steps must await one execution; otherwise call the original tool directly. messageId is per message; requestId is per logical operation. Control status/result/cancel use an empty payload; their snapshot is read-only. A PTC program end (including run_code budget expiry) only stops waiting: accepted work continues and the same requestId can be retrieved with status/result or cancelled. Diagnostic handler only: test {delayMs:0..30000,fail:boolean}; never use it to perform real work. Acceptance is volatile, not success. No polling or restart resume.";
203
+ export declare const REQUEST_DESCRIPTION = "\u8427\u4F55 / Xiao He. Submit a request and WAIT for its terminal snapshot; the simplified call takes {requestId, handler, input} (messageId optional) and control calls take {requestId, type:\"status\"|\"result\"|\"cancel\"}. Use only when coordination helps: the same logical operation may be retried or deduplicated (reuse requestId), work must outlive a run_code budget end, or multiple steps must await one execution. Duration alone is not a reason \u2014 for a one-off long command use the original tool or its background Job. A PTC program end only stops waiting: accepted work continues and the same requestId can be retrieved with type:\"result\" or cancelled. Do not poll status inside the same run_code program as an unfinished request: end that program and retrieve in a later step. The snapshot may contain reused:true when this call attached to an existing execution. The full v1 envelope is still accepted. Diagnostic handler only: test {delayMs:0..30000,fail:boolean}; never use it for real work. Acceptance is volatile, not success. No polling or restart resume.";
365
204
  export declare const MODE_INSTRUCTIONS: string;
package/dist/product.js CHANGED
@@ -108,51 +108,46 @@ const HANDLER_NAMES = [
108
108
  "host.pwsh",
109
109
  "test",
110
110
  ];
111
- /** request 的 {handler,input} 判别联合;控制消息为空对象。 */
112
- export const PAYLOAD_SCHEMA = {
113
- description: "request 必须匹配某个 Handler 的 {handler,input};status/result/cancel 的 payload 必须为空对象 {}。",
111
+ /** 简化调用(0.11.0):{requestId, handler, input} 或 {requestId, type};
112
+ * 完整 v1 消息仍被运行时接受,但不再进入模型可见 schema。 */
113
+ const REQUEST_ID_PROP = { type: "string" };
114
+ const MESSAGE_ID_PROP = { type: "string" };
115
+ export const REQUEST_PARAMETERS = {
114
116
  oneOf: [
115
117
  ...HANDLER_NAMES.map((handler) => ({
116
118
  type: "object",
117
119
  additionalProperties: false,
118
- required: ["handler", "input"],
120
+ required: ["requestId", "handler", "input"],
119
121
  properties: {
122
+ requestId: REQUEST_ID_PROP,
123
+ messageId: MESSAGE_ID_PROP,
120
124
  handler: { type: "string", const: handler },
121
125
  input: INPUT_SCHEMAS[handler],
122
126
  },
123
127
  })),
124
- { type: "object", additionalProperties: false, properties: {} },
125
- ],
126
- };
127
- export const REQUEST_PARAMETERS = {
128
- type: "object",
129
- additionalProperties: false,
130
- required: ["protocolVersion", "messageId", "requestId", "type", "payload"],
131
- properties: {
132
- protocolVersion: { type: "integer", const: 1 },
133
- messageId: {
134
- type: "string",
135
- description: "Unique id of this message; reuse only when redelivering the same message.",
136
- },
137
- requestId: {
138
- type: "string",
139
- description: "Stable id of one logical operation; reuse it when the same operation is submitted again.",
140
- },
141
- type: {
142
- type: "string",
143
- enum: ["request", "status", "result", "cancel"],
144
- description: "request submits work; status/result read the saved snapshot; cancel requests cancellation.",
128
+ {
129
+ type: "object",
130
+ additionalProperties: false,
131
+ required: ["requestId", "type"],
132
+ properties: {
133
+ requestId: REQUEST_ID_PROP,
134
+ messageId: MESSAGE_ID_PROP,
135
+ type: {
136
+ type: "string",
137
+ enum: ["status", "result", "cancel"],
138
+ description: "Read the saved snapshot or request cancellation for this requestId.",
139
+ },
140
+ },
145
141
  },
146
- payload: PAYLOAD_SCHEMA,
147
- },
142
+ ],
148
143
  };
149
144
  export const STATE_PARAMETERS = {
150
145
  type: "object",
151
146
  properties: {},
152
147
  additionalProperties: false,
153
148
  };
154
- export const REQUEST_DESCRIPTION = "萧何 / Xiao He. Submit a version 1 request and WAIT for its terminal result in this tool call. Use only when a host operation is expected to exceed ~30s, may be retried as the same logical operation (reuse requestId), must outlive a run_code budget end, or several steps must await one execution; otherwise call the original tool directly. messageId is per message; requestId is per logical operation. Control status/result/cancel use an empty payload; their snapshot is read-only. A PTC program end (including run_code budget expiry) only stops waiting: accepted work continues and the same requestId can be retrieved with status/result or cancelled. Diagnostic handler only: test {delayMs:0..30000,fail:boolean}; never use it to perform real work. Acceptance is volatile, not success. No polling or restart resume.";
155
- export const MODE_INSTRUCTIONS = "你正在萧何模式中。这不是角色扮演。保留宿主 PTC 的日常工具能力与权限规则。普通编辑、搜索、命令、安装和其他工作使用原工具,不要声称此模式只读。execution_bus 是可选协调入口,只在以下情况使用:预计单次宿主操作超过约 30 秒;同一逻辑操作可能重复提交或需要重试核对(沿用 requestId);任务需要在 run_code 时间预算结束后继续或取回结果;多个步骤需要等待同一执行。其余情况直接用原工具,不要为简单调用增加总线包装。实际业务 Handler 为 host.read、host.glob、host.grep(只读,可复用终态结果)与 host.edit、host.write、host.pwsh(有副作用,终态结果不可复用);test 仅用于用户明确要求的诊断,不做真实工作。示例:await tools.execution_bus({protocolVersion:1,messageId:\"<消息编号>\",requestId:\"<逻辑操作编号>\",type:\"request\",payload:{handler:\"host.read\",input:{file_path:\"README.md\"}}});messageId 每条消息唯一,同一逻辑操作重发沿用 requestId。通过 run_code 调用 await tools.execution_bus(...),受原有时间预算约束。每会话排队上限 " +
149
+ export const REQUEST_DESCRIPTION = "萧何 / Xiao He. Submit a request and WAIT for its terminal snapshot; the simplified call takes {requestId, handler, input} (messageId optional) and control calls take {requestId, type:\"status\"|\"result\"|\"cancel\"}. Use only when coordination helps: the same logical operation may be retried or deduplicated (reuse requestId), work must outlive a run_code budget end, or multiple steps must await one execution. Duration alone is not a reason — for a one-off long command use the original tool or its background Job. A PTC program end only stops waiting: accepted work continues and the same requestId can be retrieved with type:\"result\" or cancelled. Do not poll status inside the same run_code program as an unfinished request: end that program and retrieve in a later step. The snapshot may contain reused:true when this call attached to an existing execution. The full v1 envelope is still accepted. Diagnostic handler only: test {delayMs:0..30000,fail:boolean}; never use it for real work. Acceptance is volatile, not success. No polling or restart resume.";
150
+ export const MODE_INSTRUCTIONS = "你正在萧何模式中。这不是角色扮演。保留宿主 PTC 的日常工具能力与权限规则。普通编辑、搜索、命令、安装和其他工作使用原工具,不要声称此模式只读。execution_bus 只在能获得协调收益时使用:① 同一逻辑操作可能重复提交或需要重试核对(沿用同一 requestId,只执行一次);② 任务需要在 run_code 时间预算结束后继续或跨步骤取回;③ 多个步骤/等待者需要同一执行结果。仅\"操作耗时长\"不是理由:一次性长命令用原工具或宿主后台 Job。其余普通任务直接用原工具,不要为简单调用增加包装。实际业务 Handler 为 host.read、host.glob、host.grep(只读,可复用终态结果)与 host.edit、host.write、host.pwsh(有副作用,终态结果不可复用);test 仅用于用户明确要求的诊断,不做真实工作。简化调用示例:await tools.execution_bus({requestId:\"<逻辑操作编号>\", handler:\"host.pwsh\", input:{description:\"运行测试\", command:\"pnpm test\", timeoutMs:120000}});预算中断后用 await tools.execution_bus({requestId:\"<同一编号>\", type:\"result\"}) 取回;同一逻辑操作重发沿用 requestId,messageId 可省略(自动生成)。返回快照出现 reused:true 表示本次调用复用了已有执行。注意:同一 run_code 程序内,未完成的 request 会阻塞后发工具调用的结果提交;跨步骤取回请让启动步骤尽早结束程序,下一步再用同一 requestId 的 status/result 或重发 request;不要在同一程序里对未完成请求轮询状态。通过 run_code 调用,受原有时间预算约束。每会话排队上限 " +
156
151
  ADMISSION_DEFAULTS.sessionQueueLimit +
157
152
  ",排队超过 " +
158
153
  ADMISSION_DEFAULTS.maxQueueWaitMs / 1000 +
@@ -41,6 +41,8 @@ export interface RecordView {
41
41
  result?: unknown;
42
42
  error?: string;
43
43
  progress?: number;
44
+ /** true 表示本次调用附着到已存在的逻辑执行,而不是新建执行(0.11.0 新增,可选)。 */
45
+ reused?: true;
44
46
  }
45
47
  export declare function object(v: unknown): Record<string, unknown>;
46
48
  /** 规范排序后的稳定摘要,只用于同 requestId 的冲突比较,不用于跨请求去重。 */
package/docs/FLOW-TEST.md CHANGED
@@ -22,9 +22,12 @@ npm run test:install
22
22
  | `check` | 编译并运行全部测试;包括后续 Job 故障、卸载、预设隔离等回归 |
23
23
  | `verify:package` | 检查候选打包清单及导出,不安装日常宿主 |
24
24
  | `test:install` | 在临时目录安装生产依赖并验证根入口/简化注册,结束后清理 |
25
+ | `metrics:sessions` | 只读扫描本机会话日志,汇总总线请求/Handler/复用标记/错误,用于采用率观察 |
25
26
 
26
27
  `test:flow` 不是全部集成测试的别名;发布前必须运行 `check`。临时安装不是桌面插件管理器安装。完整验证层次见[验证范围](<validation.md>)。
27
28
 
29
+ `metrics:sessions` 只读取本地会话日志,不发送网络请求,也不代表模型效果对比;它是采用率与异常观察工具,不是 A/B 基准。
30
+
28
31
  ## 已覆盖的代表场景
29
32
 
30
33
  - 正常受理与异步返回,两个会话不串线,同键冲突拒绝。
package/docs/PRODUCT.md CHANGED
@@ -8,7 +8,9 @@
8
8
 
9
9
  0.9.0 增加 PTC 预算到期后的分离等待:宿主中断 PTC 子调用时,已受理执行只解除等待、继续运行,可用同 requestId 的 status/result 取回或用 cancel 取消;其他中断原因仍按取消处理。该区分依据 DSH 0.2.1-rc.2 的中断原因字符串,不是公开稳定契约,宿主升级需复核。
10
10
 
11
- 0.10.0 改善模型侧的采用条件:模式说明写明仅在预计 >30 秒、可能重复提交、预算结束后继续或跨步骤等待同一执行时使用总线,并给出调用示例;工具声明把 payload 改为按 Handler 的判别联合,PTC SDK 与原生声明直接显示每个 Handler 的必填/可选字段。wire 协议、字段、错误码与 Handler 行为不变,只改变模型可见的提示与 schema。
11
+ 0.10.0 改善模型侧的采用条件:模式说明写明使用场景并给出调用示例;工具声明把 payload 改为按 Handler 的判别联合,PTC SDK 与原生声明直接显示每个 Handler 的必填/可选字段。wire 协议、字段、错误码与 Handler 行为不变,只改变模型可见的提示与 schema。
12
+
13
+ 0.11.0 根据 pilot 收紧为收益导向触发:重复提交/重试去重、预算结束后继续/跨步骤取回、多等待者同一执行才用总线,时长本身不是理由;新增简化调用(requestId+handler+input,messageId 自动生成)与控制(requestId+type),完整 v1 消息仍兼容;快照在复用时带 reused:true,并新增只读的会话采用率统计脚本。
12
14
 
13
15
  ## 入口及配置层次
14
16
 
package/docs/protocol.md CHANGED
@@ -38,7 +38,7 @@
38
38
 
39
39
  **工具 request 不提前返回 accepted 或仅 taskId。** 它异步等待后返回执行快照;校验、容量、宿主服务等也可能令外层工具调用返回错误,而非执行快照。
40
40
 
41
- 快照字段:`requestId`、`state`、`sequence`、`cancelRequested`、`delivery`,以及可选 `result`、`error`、`progress`。`status`、`result`、`cancel` 均返回当前快照;`result` 在运行中也只返回当前状态,不额外等待。
41
+ 快照字段:`requestId`、`state`、`sequence`、`cancelRequested`、`delivery`,以及可选 `result`、`error`、`progress`;0.11.0 起,附着到已有执行的返回快照可带可选 `reused:true`。`status`、`result`、`cancel` 均返回当前快照;`result` 在运行中也只返回当前状态,不额外等待。
42
42
 
43
43
  core 的 `submit()` 返回 `accepted`、`done` Promise、只读 `snapshot()` 及取消/解除通知/返回标记句柄。accepted 只是内存受理或已有记录的快照,`durable:false`,不代表执行成功;终态重取也不代表重新执行。0.9.0 的 `snapshot()` 只供停止等待时读取当前视图,不改变交付标记。
44
44
 
@@ -74,6 +74,8 @@ PTC 程序结束(含 `run_code` 时间预算到期)时,宿主以固定原
74
74
 
75
75
  这条区分依据 DSH 0.2.1-rc.2 的内部中断原因字符串(`run_code settled`),不是宿主公开稳定契约;未知原因按取消处理。分离等待不改变内存与保留期边界:进程退出仍丢失全部状态,`returned` 仍不证明模型已取回结果。
76
76
 
77
+ 同一 PTC 程序内,未完成的 request 会阻塞后发工具调用的结果提交(宿主按提交顺序结算)。跨步骤取回应让启动步骤尽早结束程序,在后续步骤用同一 requestId 的 `status`/`result` 或重发同一 request 取回;不要在同一程序里对未完成请求轮询状态。
78
+
77
79
  错误也须分层:Handler 异常原文不进入保存结果,使用 HANDLER_FAILED 等固定码;不合法、过大或快照不可保存结果为 unknown / RESULT_NOT_RETAINED。宿主 wait/remove 等服务错误可能作为外层工具错误传出,不能笼统声称所有错误都已脱敏或都是固定码。不要把宿主服务错误文本当成业务执行失败凭据。
78
80
 
79
81
  ## 4. 容量、保存与关闭
@@ -124,4 +126,6 @@ core 实例的 `diagnostics()` 返回只读用途的独立快照:阶段计数
124
126
 
125
127
  0.10.0 起,模型可见的工具体声明把 `payload` 改为按 Handler 的判别联合(每个 `host.*`/test 的 `input` 字段与必填项);这是提示与 SDK 类型层的变化,wire 结构、校验、错误码与保存语义不变。
126
128
 
129
+ 0.11.0 起,模型可传简化输入:请求 `{requestId, handler, input, messageId?}`、控制 `{requestId, type, messageId?}`;插件在进入总线前补齐完整 v1 消息,完整 v1 消息仍被接受。返回快照在附着已有执行时追加可选 `reused:true`,只表示复用了逻辑执行,不代表外部副作用对账。
130
+
127
131
  内置 test、host.read、host.glob 与 host.grep 没有副作用;host.edit、host.write 与 host.pwsh 是非幂等写/命令 Handler,通过 `Handler.idempotent=false` 显式声明,其终态结果复用被 `MUTATING_REUSE_DENIED` 拒绝,运行中的重复附着仍只等待同一执行。0.8.0 新增宿主工具输入错误码 INVALID_GLOB_INPUT、INVALID_GREP_INPUT 与 INVALID_PWSH_INPUT;协议既有错误码不改名。host.pwsh 的后台结果只是宿主 Job ID,不是完成回执。当前没有 ctx.executionBus 服务、远端传输、跨插件动态注册或外部副作用对账契约。扩展有副作用 Handler 前必须显式声明幂等并另行设计授权与崩溃对账,不能仅依赖本版请求键。
@@ -0,0 +1,19 @@
1
+ # 萧何 / Xiao He 0.11.0
2
+
3
+ ## 收益导向触发、简化调用与复用可见
4
+
5
+ 根据 0.10.0 的采用率 pilot(4 个全新队友会话)结果,本版收紧触发条件并降低调用摩擦。
6
+
7
+ - **触发收紧**:只在能获得协调收益时使用总线——同一逻辑操作可能重复提交或需要重试去重(沿用 requestId);任务需要在 `run_code` 预算结束后继续或跨步骤取回;多个步骤/等待者需要同一执行结果。**仅“操作耗时长”不再作为理由**:一次性长命令使用原工具或宿主后台 Job(pilot 中一次性 40 秒命令被包装后没有协调收益)。
8
+ - **简化调用**:请求可传 `{requestId, handler, input, messageId?}`,控制可传 `{requestId, type:"status"|"result"|"cancel", messageId?}`;`messageId` 省略时自动生成。完整 v1 消息仍被接受(向后兼容)。工具 schema 改为简化调用的 Handler 判别联合,PTC SDK 不再要求构造完整消息。
9
+ - **复用可见**:附着到已有执行(终态复用或运行中重发)时,返回快照追加 `reused:true`;首次执行不带该字段。该标记只表示复用了逻辑执行,不代表外部副作用对账。
10
+ - **采用率观察**:新增只读命令 `npm run metrics:sessions`,扫描本地会话日志并汇总总线请求/Handler/复用/错误,供后续两臂 A/B 比较。
11
+ - 新增/重写 9 项回归(简化请求与控制、完整 v1 兼容、复用标记、非法形状拒绝、schema/SDK 渲染、收益导向提示),全量 **110** 项测试通过。
12
+
13
+ ## 升级说明
14
+
15
+ 需要 DSH 0.2.1-rc.2。wire 协议结构、预设、工具名、错误码与 Handler 行为不变;工具入口新增简化形状并保留完整 v1 消息。不自动安装、重写日常配置或重启 DSH。
16
+
17
+ ## 验证与限制
18
+
19
+ 本地全量 110 项测试通过。0.10.0 pilot 样本小且任务由本仓库构造:符合条件任务 2/3 使用总线,不是 A/B 结论;0.11.0 收紧后的采用率与轮次/耗时收益仍需两臂复测。真实模型、桌面 UI 与实际沙箱仍未完整验收。
@@ -0,0 +1,18 @@
1
+ # 萧何 / Xiao He 0.11.1
2
+
3
+ ## 修正跨步骤取回说明(pilot 发现)
4
+
5
+ 本版只修正提示与文档,不改变执行机制。
6
+
7
+ - pilot 发现:同一 PTC 程序内,未完成的 request 会阻塞后发工具调用的结果提交(宿主按提交顺序结算);在同一程序里“启动后立刻 `status` 轮询”会实际阻塞到执行结束,而不是立即看到 running。
8
+ - 模式说明与工具体描述补充正确姿势:启动步骤只发请求并尽早结束程序;下一步再用同一 requestId 的 `status`/`result` 或重发同一 request 附着取回;不要在同一程序里对未完成请求轮询状态。
9
+ - `README` 与 `protocol.md` 同步该行为说明;无代码、协议、错误码或 Handler 行为变化。
10
+ - 验证:全量 110 项测试通过(提示断言新增该模式的覆盖)。
11
+
12
+ ## 升级说明
13
+
14
+ 需要 DSH 0.2.1-rc.2。只影响模型可见提示与文档,不改变请求结构、状态机或结果语义。不自动安装或重启 DSH。
15
+
16
+ ## 验证与限制
17
+
18
+ 本地全量 110 项测试通过。该行为来自真实 PTC pilot 的组件观察(未做更大规模统计);真实模型、桌面 UI 与实际沙箱仍未完整验收。
@@ -0,0 +1,16 @@
1
+ # 萧何 / Xiao He 0.11.2
2
+
3
+ ## 修正 PTC 预算测试在 CI 的时序抖动
4
+
5
+ - 0.11.1 的标签 CI 在 **windows-latest + Node 22** 上失败:真实 PTC 预算用例只给 `run_code` 2 秒预算,不足以覆盖 CI 冷启动 PTC 子进程的时间,导致“总线请求尚未受理就已超时”(测试夹具时序问题,不是产品缺陷);因此 0.11.1 未发布。
6
+ - 调整测试夹具:PTC 预算 2s→8s、被测命令 5s→12s、等待上限同步放宽,保证“已受理后预算到期”路径在慢速 CI 上稳定复现。本地连续 3 次通过。
7
+ - 0.11.1 的提示与文档修正(同程序内不要对未完成请求轮询 status;启动步骤尽早结束程序、后续步骤用同 requestId 取回)一并包含在本版。
8
+ - 无产品运行时代码、协议或 Handler 行为变化;全量 110 项测试通过。
9
+
10
+ ## 升级说明
11
+
12
+ 需要 DSH 0.2.1-rc.2。行为与 0.11.0 一致,仅修正提示/文档与测试时序。不自动安装或重启 DSH。
13
+
14
+ ## 验证与限制
15
+
16
+ 本地全量 110 项测试通过;PTC 预算/中断用例连跑 3 次通过。真实模型、桌面 UI 与实际沙箱仍未完整验收。
@@ -1,6 +1,6 @@
1
1
  # 验证范围与使用限制
2
2
 
3
- 本文说明当前版本的证据边界,不是生产环境认证。当前能力基线为 **v0.10.0**,在 0.5.0 的会话准入/排队、0.7.0 的宿主工具转发、0.8.0 的搜索/命令 Handler 与 0.9.0 的预算分离等待上,改善模型侧采用条件(触发说明、类型化工具声明);预设沿用 v0.3.0 的宿主 PTC 声明派生方式。验证环境为 Windows、Node.js 24.21.0,宿主组件锁定 DSH 0.2.1-rc.2、Cordis 4.0.4。
3
+ 本文说明当前版本的证据边界,不是生产环境认证。当前能力基线为 **v0.11.2**,在 0.5.0 的会话准入/排队、0.7.0 的宿主工具转发、0.8.0 的搜索/命令 Handler、0.9.0 的预算分离等待与 0.10.0 的采用面改造上,收紧为收益导向触发并加入简化调用、复用标记与本地采用率统计;0.11.1 仅补充跨步骤取回的正确姿势说明;预设沿用 v0.3.0 的宿主 PTC 声明派生方式。验证环境为 Windows、Node.js 24.21.0,宿主组件锁定 DSH 0.2.1-rc.2、Cordis 4.0.4。
4
4
 
5
5
  ## 已验证什么?
6
6
 
@@ -10,10 +10,13 @@
10
10
  | 真实宿主组件 | Cordis、ToolRuntime、LocalJobs、LocalFS、文件工具;host.glob/host.grep/host.pwsh 的转发测试使用宿主工具桩 | 请求经过真实工具管线;权限拒绝、Job 进度、共享任务、参数校验、幂等分流与清理逻辑有效;Agent 身份与工具实现为夹具,桩不证明真实搜索/命令工具实现 |
11
11
  | 真实会话循环 | AgentLoop、AgentRegistry、SessionStore;脚本模型适配器 | 内存会话中原工具结果只提交一次,完成后再进入下一次适配器调用 |
12
12
  | PTC 预算分离等待 | 真实 Node PTC `run_code` 以 2 秒预算等待 5 秒总线执行:预算到期后执行继续、宿主 Job 保留、同 requestId 取回结果;外部中断仍取消执行 | 在 DSH 0.2.1-rc.2 实际中断原因下成立;固定字符串不是跨版本契约 |
13
+ | 简化调用与复用标记 | 简化请求/控制、完整 v1 消息兼容、终态与运行中复用返回 `reused:true`、非法形状拒绝 | 组件级测试确认;不改变 wire 语义与副作用边界 |
13
14
  | 预设隔离 | AgentPresetRegistry 与 Loader | 派生预设可激活;源 ptc 声明不变、普通 Agent 不可见总线;原 write/edit/Job 工具保留,宿主前置策略拒绝仍有效 |
14
15
  | 故障注入 | 克隆失败、观察者拒绝、Job 等待/清理异常、先行移除与卸载交错 | 指定故障下的回归约束;不证明所有故障组合都已覆盖 |
15
16
  | 候选包 | 严格文件白名单、临时目录生产依赖安装、入口注册 | 包可安装和加载;安装验证使用简化宿主夹具,不是桌面插件管理器验收 |
16
17
 
18
+ 2026-10-18 采用率 pilot(0.10.0,4 个全新队友会话):符合条件的长命令/跨预算任务 **2/3** 真实调用了总线;1 个一次性 40 秒命令属于无收益包装,1 个跨步骤任务改用宿主后台 Job。样本小、非 A/B,0.11.0 收紧触发后需复测。
19
+
17
20
  此前 v0.3.0 本地回归为 **51/51 通过**;本轮新增生命周期与多会话压力回归,最新数量以命令输出为准。测试项数量是用例数量,不是全部产品验收条数。当前结果请重新运行下述命令;文档测试数不应代替实际输出。
18
21
 
19
22
  ```powershell
@@ -45,7 +48,7 @@ eb95b4e51675f5f1821275e0812ef4dd4984b1aba45a52fbc6bab3518bbf6f38
45
48
  | 从真实 tgz 经宿主插件管理器加载预设、卸载与回滚 | 未完成完整桌面流程;源码工作区的 Loader 测试不能替代 |
46
49
  | 真实 glob/grep/pwsh 工具实现 | 未在测试环境启用:真实搜索工具依赖打包 ripgrep 与 subprocess 服务,真实 pwsh 依赖 shell/sandbox/审批组合;本版只验证按 handler 名转发、输入校验、幂等分流与失败固定码 |
47
50
  | 跨版本 PTC 中断原因 | 分离等待只识别 DSH 0.2.1-rc.2 实际使用的固定中断原因字符串;宿主升级后未知原因保守按取消处理,需复核 |
48
- | 模式采用率与效果 | 未验证。0.10.0 只改善触发说明与工具声明可读性;真实模型是否更常使用总线、是否带来更少轮次/更短耗时,需要 pilot 与同任务 A/B 对照,不能由 schema 或测试推断 |
51
+ | 模式采用率与效果 | 未完成 A/B。0.10.0 pilot 显示采用面改造能让总线被真实使用(2/3 符合条件任务),但样本小、任务由本仓库构造;0.11.0 收益导向收紧后的采用率与轮次/耗时收益仍未知,需两臂复测,不能由 schema 或单元测试推断 |
49
52
  | 磁盘会话结果提交及重启恢复原调用 | 未验收磁盘提交;本插件不支持跨重启恢复 |
50
53
  | 减少真实模型轮次、状态查询或总耗时的对照 | 未验证,不给提升比例 |
51
54
  | 长期运行、所有卸载/服务故障组合 | 未穷尽;持续故障仍有清理限制 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feng0531/dsh-execution-bus",
3
- "version": "0.10.0",
3
+ "version": "0.11.2",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -47,7 +47,8 @@
47
47
  "verify:package": "node scripts/verify-package.mjs",
48
48
  "test:install": "node scripts/test-install.mjs",
49
49
  "pack:candidate": "node scripts/pack.mjs",
50
- "verify:release": "node scripts/release-preflight.mjs"
50
+ "verify:release": "node scripts/release-preflight.mjs",
51
+ "metrics:sessions": "node scripts/session-metrics.mjs"
51
52
  },
52
53
  "peerDependencies": {
53
54
  "@deepseek-ai/cordis": "~4.0.4",