@feng0531/dsh-execution-bus 0.2.0 → 0.2.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  **让模型发出一次执行请求,由程序负责排队、等待和收取结果。**
4
4
 
5
- DSH 独立插件:`@feng0531/dsh-execution-bus` · 当前版本 **0.2.0**
5
+ DSH 独立插件:`@feng0531/dsh-execution-bus` · 当前版本 **0.2.2**
6
6
 
7
7
  > 当前是早期可运行版本,不是完整生产验收版。已验证真实 DSH 组件和脚本模型驱动的内存会话;真实模型、桌面界面和 PTC 完整链路尚未验收。**不支持重启后恢复任务。**
8
8
 
@@ -81,7 +81,7 @@ flowchart TD
81
81
 
82
82
  ## 在 Agent 预设中选择测试模式
83
83
 
84
- 从 0.2.0 起,包内提供标准 DSH bundle patch。通过宿主正常加载这个 bundle 后,设置 → Agent 预设 → **自定义**区域会收到“执行总线测试 / Execution Bus Test”预设定义;选择它创建一个新测试会话。不要把它设成默认,也不要修改正在使用的普通会话。宿主一般只允许空白会话切换预设。
84
+ 从 0.2.0 起,包内提供标准 DSH bundle patch。通过宿主正常加载这个 bundle 后,设置 → Agent 预设 → **自定义**区域会收到“执行总线 / Execution Bus”预设定义;选择它创建一个新测试会话。不要把它设成默认,也不要修改正在使用的普通会话。宿主一般只允许空白会话切换预设。
85
85
 
86
86
  该预设只在自己的作用域注册 execution_bus,使用原生工具调用,不改其他会话的 PTC 模式。它没有 shell 行;宿主文件工具套件会声明 read/write/edit 等工具,但测试模式的执行 guard 只允许总线、read 和 Job 控制,写入/编辑即使可见也会拒绝。它不是进程级沙箱,读取边界仍由宿主权限决定;同一测试预设的会话共享有界队列容量,但请求结果按会话隔离。
87
87
 
@@ -100,7 +100,7 @@ flowchart TD
100
100
  2. 在你选定的隔离 DSH 配置依赖目录安装固定版本,不要在 DSH 程序安装目录执行:
101
101
 
102
102
  ```powershell
103
- npm install --ignore-scripts @feng0531/dsh-execution-bus@0.2.0
103
+ npm install --ignore-scripts @feng0531/dsh-execution-bus@0.2.2
104
104
  ```
105
105
 
106
106
  这是 npm 目录示例。若目标配置使用 pnpm,应沿用其包管理器,不混用锁文件。遇到宿主 peer 版本冲突就停止,不用强制安装绕过。
@@ -229,7 +229,7 @@ npm run test:install # 临时目录安装,不操作日常 DSH
229
229
 
230
230
  - [协议和投递语义](<docs/protocol.md>):字段、状态、重复处理与取消规则。
231
231
  - [宿主兼容性与源码调用链](<docs/host-compatibility.md>):为什么可以等待,为什么不能跨重启恢复。
232
- - [验证记录](<docs/validation.md>)、[0.2.0 发布说明](<docs/releases/0.2.0.md>):已验证与未验证范围。
232
+ - [验证记录](<docs/validation.md>)、[0.2.2 发布说明](<docs/releases/0.2.2.md>):已验证与未验证范围。
233
233
  - [维护者发布流程](<docs/RELEASING.md>):标签 CI、安装包校验与正式发布门禁。
234
234
 
235
235
  ## 许可
package/cordis.patch.yml CHANGED
@@ -4,15 +4,15 @@
4
4
  name: '@deepseek-ai/dsh-agent-preset'
5
5
  config:
6
6
  id: execution-bus-test
7
- name: 执行总线测试 / Execution Bus Test
8
- description: 独立可选测试模式:只读文件、延迟与失败演示、进度和取消。不修改默认模式,禁止命令和写入执行;不支持重启恢复。
7
+ name: 执行总线 / Execution Bus
8
+ description: 独立执行模式:请求排队、异步等待、进度和取消;当前支持受控验证与只读文件。不修改默认模式,禁止命令和写入执行;不支持重启恢复。
9
9
  order: 20
10
10
  plugins:
11
11
  - id: execution-bus-persona
12
12
  name: '@deepseek-ai/dsh-persona'
13
13
  config:
14
14
  prefix: >-
15
- 你正在执行总线测试模式中。只使用 execution_bus、只读文件和任务控制工具。
15
+ 你正在执行总线模式中。只使用 execution_bus、只读文件和任务控制工具。
16
16
  请求只提交一次并等待结果,不循环查询进度;同一逻辑操作重发沿用 requestId。
17
17
  默认先用 test Handler 做延迟成功、失败和取消测试,读取真实文件须有用户要求。
18
18
  cancelRequested 不代表已取消,unknown 不代表执行失败。禁止声称跨重启恢复或完整模型性能提升。
package/dist/bus.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { randomUUID, createHash } from "node:crypto";
2
+ import { assertJson } from "./json.js";
2
3
  export function object(v) {
3
4
  if (!v || typeof v !== "object" || Array.isArray(v))
4
5
  throw Error("INVALID_OBJECT");
@@ -19,6 +20,7 @@ function canonical(v) {
19
20
  "}");
20
21
  }
21
22
  export function parse(raw) {
23
+ assertJson(raw);
22
24
  const m = object(raw);
23
25
  if (Object.keys(m).some((k) => ![
24
26
  "protocolVersion",
@@ -220,8 +222,13 @@ export class ExecutionBus {
220
222
  if (previous && previous !== fingerprint)
221
223
  throw Error("MESSAGE_CONFLICT");
222
224
  if (!previous) {
223
- if (e.messages.size >= 128)
224
- throw Error("MESSAGE_LIMIT");
225
+ // Keep one emergency control slot: read traffic must never prevent cancellation.
226
+ if (e.messages.size >= 128) {
227
+ if (m.type !== "cancel")
228
+ throw Error("MESSAGE_LIMIT");
229
+ if (e.view.cancelRequested || terminal(e.view.state))
230
+ return this.view(e);
231
+ }
225
232
  e.messages.set(m.messageId, fingerprint);
226
233
  if (m.type === "cancel")
227
234
  this.cancel(e);
@@ -242,14 +249,32 @@ export class ExecutionBus {
242
249
  finish(e, state, result, error) {
243
250
  if (terminal(e.view.state))
244
251
  return;
245
- e.created = Date.now();
246
- e.view.state = state;
252
+ // Prepare the delivery snapshot BEFORE committing an irreversible terminal state.
253
+ let next = { ...e.view, state, sequence: e.view.sequence + 1 };
247
254
  if (result !== undefined)
248
- e.view.result = result;
255
+ next.result = result;
249
256
  if (error)
250
- e.view.error = error;
257
+ next.error = error;
258
+ let snapshot;
259
+ try {
260
+ snapshot = freezeJson(structuredClone(next));
261
+ }
262
+ catch {
263
+ state = "unknown";
264
+ next = {
265
+ requestId: e.view.requestId,
266
+ state,
267
+ sequence: e.view.sequence + 1,
268
+ cancelRequested: e.view.cancelRequested,
269
+ delivery: e.view.delivery,
270
+ error: "RESULT_NOT_RETAINED",
271
+ };
272
+ snapshot = freezeJson({ ...next });
273
+ }
274
+ e.created = Date.now();
275
+ e.view = { ...next, sequence: next.sequence - 1 };
251
276
  this.emit(e, state, { state });
252
- e.resolve(freezeJson(this.view(e)));
277
+ e.resolve(snapshot);
253
278
  e.listeners.clear();
254
279
  }
255
280
  pump() {
@@ -289,6 +314,7 @@ export class ExecutionBus {
289
314
  });
290
315
  let json;
291
316
  try {
317
+ assertJson(result);
292
318
  json = JSON.stringify(result);
293
319
  }
294
320
  catch {
package/dist/json.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /** Strict JSON tree validation before serialization, cloning or recursive freezing. */
2
+ export declare function assertJson(value: unknown, depth?: number, active?: Set<object>, budget?: {
3
+ left: number;
4
+ }): void;
package/dist/json.js ADDED
@@ -0,0 +1,42 @@
1
+ /** Strict JSON tree validation before serialization, cloning or recursive freezing. */
2
+ export function assertJson(value, depth = 0, active = new Set(), budget = { left: 65536 }) {
3
+ if (--budget.left < 0 || depth > 64)
4
+ throw Error("JSON_LIMIT");
5
+ if (value === null || typeof value === "string" || typeof value === "boolean")
6
+ return;
7
+ if (typeof value === "number" &&
8
+ Number.isFinite(value) &&
9
+ !Object.is(value, -0))
10
+ return;
11
+ if (typeof value !== "object" || value === null)
12
+ throw Error("INVALID_JSON");
13
+ if (active.has(value))
14
+ throw Error("CYCLIC_JSON");
15
+ const array = Array.isArray(value);
16
+ if (!array &&
17
+ Object.getPrototypeOf(value) !== Object.prototype &&
18
+ Object.getPrototypeOf(value) !== null)
19
+ throw Error("INVALID_JSON_OBJECT");
20
+ active.add(value);
21
+ try {
22
+ const keys = Reflect.ownKeys(value);
23
+ if (array && keys.length !== value.length + 1)
24
+ throw Error("INVALID_JSON_ARRAY");
25
+ for (const key of keys) {
26
+ if (array && key === "length")
27
+ continue;
28
+ if (typeof key !== "string")
29
+ throw Error("INVALID_JSON_KEY");
30
+ if (array &&
31
+ (!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= value.length))
32
+ throw Error("INVALID_JSON_ARRAY");
33
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
34
+ if (!descriptor.enumerable || !("value" in descriptor))
35
+ throw Error("INVALID_JSON_PROPERTY");
36
+ assertJson(descriptor.value, depth + 1, active, budget);
37
+ }
38
+ }
39
+ finally {
40
+ active.delete(value);
41
+ }
42
+ }
package/docs/protocol.md CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  ## 输入
4
4
 
5
- 严格 JSON,最大 16 KiB,不接受额外顶层字段。messageId / requestId 为 1–128 位字母、数字、下划线、点、冒号或短横线。
5
+ 严格无损 JSON,最大 16 KiB,不接受额外顶层字段。JSON 值最大深度 64、访问节点最多 65536;拒绝非有限数字、负零、undefined、函数、符号、稀疏数组、访问器属性、特殊对象和循环引用。messageId / requestId 为 1–128 位字母、数字、下划线、点、冒号或短横线。
6
6
 
7
7
 
8
8
  type 为 request 时 payload 为 {handler,input};type 为 status / result / cancel 时 payload 必须为 {}。不支持其他类型或版本。sessionId、权限、工作目录不能出现在输入中,可信归属只能由宿主工具上下文绑定。
9
9
 
10
- - messageId 标识一条消息;同请求内同 messageId 不同内容拒绝。消息记账最多 128 条,超过拒绝新消息,不自动遗忘取消记账。
10
+ - messageId 标识一条消息;同请求内同 messageId 不同内容拒绝。普通消息记账最多 128 条,超过拒绝普通新消息。首次有效取消可使用额外一个保留槽,最多 129 条;之后的新取消不再增加记账,已记录消息 ID 的冲突检查仍生效,不会因查询耗尽额度而丧失取消路径。
11
11
  - requestId 是一次逻辑操作的稳定幂等键;作用域为当前插件实例 + 宿主 session。相同 requestId 不同 handler/input 拒绝冲突,相同输入使用新 requestId 仍是新操作。
12
12
  - 输入规范排序后的摘要仅用于同 requestId 冲突比较,不作永久参数去重。保存期后旧键会失效,调用者不得因此盲目重跑有副作用操作。
13
13
  - callId / rootCallId / Agent / parent token 来自宿主;通过工具原本的 Promise 关联结果,不依赖模型提供的身份。
@@ -27,13 +27,13 @@ queued → cancelled。终态不可变;取消终态任务只返回原状态。
27
27
 
28
28
  运行中取消先置 cancelRequested 并触发 AbortSignal,不立即声称 cancelled。Handler 用 AbortError 确认取消后才 cancelled;收到取消仍成功返回则 success 获胜;取消后其他异常为 unknown。Handler 必须合作取消并等待自身资源停止,不能把部分副作用当作已撤销。初版内置 Handler 仅为受控测试和只读宿主 read,没有非幂等写操作。
29
29
 
30
- 异常是固定错误码,不将异常堆栈/原文写日志。过大或不可保存的结果进入 unknown / RESULT_NOT_RETAINED,不因结果传输失败自动执行一次。执行结束不等于交付,delivery 单独记录 pending / returned;returned 仅代表插件已走到工具返回路径,**不是会话持久化提交确认**。done 快照保留当时的 pending,后续查询可见 returned。宿主可能因取消/后置策略改变最终工具结果,插件不声称已确认模型收到。
30
+ 异常是固定错误码,不将异常堆栈/原文写日志。结果遵守同样的严格 JSON 深度/节点与值约束;过大、会丢失信息或不可保存的结果进入 unknown / RESULT_NOT_RETAINED,不因结果传输失败自动执行一次。执行结束不等于交付,delivery 单独记录 pending / returned;returned 仅代表插件已走到工具返回路径,**不是会话持久化提交确认**。done 快照保留当时的 pending,后续查询可见 returned。宿主可能因取消/后置策略改变最终工具结果,插件不声称已确认模型收到。
31
31
 
32
32
  ## 容量和清理
33
33
 
34
34
  默认并发 2,待分发队列 32,记录 256,单结果 JSON 64 KiB;所有请求先经上限检查,满则 BACKPRESSURE,不自动重试。Job 仍受宿主自己的每 owner 容量限制。队列不提供公平性 SLA,单会话可占满实例。
35
35
 
36
- 终态自完成后保存一小时;新受理时惰性清理,可通过 core.sweep 主动清理。无定时器常驻、无磁盘结果存储、无日志输出。插件关闭先停止受理,取消队列与运行任务,等待合作 Handler,清理内存;不合作的 Handler 会阻碍关闭,插件无法安全硬杀同进程代码。
36
+ 终态自完成后保存一小时;新受理及控制消息查询时惰性清理,可通过 core.sweep 主动清理。无定时器常驻、无磁盘结果存储、无日志输出。插件关闭先停止受理,取消队列与运行任务,等待合作 Handler,清理内存;不合作的 Handler 会阻碍关闭,插件无法安全硬杀同进程代码。
37
37
 
38
38
  敏感文件结果仍可能进入宿主原有工具会话日志,本插件不具备任意文本秘密识别能力。只请求确实需要的内容;不将敏感内容放入请求标识。结果长度检查发生在 Handler 返回后,扩展 Handler 自身也必须限制分配,不可把此检查当成防止超大对象构造的内存沙箱。
39
39
 
@@ -0,0 +1,15 @@
1
+ # 执行总线 0.2.1
2
+
3
+ ## 本版变化
4
+
5
+ 规范正式产品命名:Agent 预设显示名统一为“执行总线 / Execution Bus”,不再带“测试 / Test”。用途、只读限制及验证边界保留在描述中,不用显示名称暗示完整生产验收。
6
+
7
+ 兼容标识不变:预设 ID execution-bus-test、配置行 ID preset-execution-bus-test、工具名 execution_bus 和 /test-mode 导出继续保留,避免已有配置或会话失联。这些是历史内部标识,不是显示名称。
8
+
9
+ ## 使用与限制
10
+
11
+ 在宿主正常加载 bundle 后,从设置的 Agent 预设自定义区域选择“执行总线 / Execution Bus”。不改默认预设,不向普通 Agent 注册全局总线。当前用户选择只发布暂不安装,所以实际桌面卡片仍未验收。预设不是进程隔离沙箱,仍不支持跨重启恢复。
12
+
13
+ ## 验证与许可
14
+
15
+ 本版仅显示命名/说明修正;真实预设注册表、会话作用域和执行门禁继续回归。正式发布按标签四矩阵、同包安装与公开包一致性门禁执行。非商业源码公开许可,不自动安装或重启 DSH。
@@ -0,0 +1,15 @@
1
+ # 执行总线 0.2.2
2
+
3
+ ## BUG 修复
4
+
5
+ - 终态提交先准备返回快照再更新状态;深层结果或快照构造失败会明确结算为 unknown / RESULT_NOT_RETAINED,避免原工具调用和关闭流程永久等待。
6
+ - 普通消息额度耗尽后为首次取消保留一个额外槽;最多 129 条记账,重复取消不增加内存,不绕过已记录消息 ID 的冲突检查。
7
+ - 输入和结果采用严格无损 JSON 校验,拒绝 NaN、Infinity、负零、undefined、函数、稀疏数组、循环和特殊对象,避免静默改写成成功结果。JSON 深度最多 64,访问节点最多 65536;不自动重跑保存失败任务。
8
+
9
+ ## 验证
10
+
11
+ 新增 5 项回归,覆盖 1800 层结果、注入快照克隆故障、消息额度耗尽取消、无损返回值和不执行 getter 的输入拒绝。本地全量 31/31 测试通过;仍须标签 Windows/Ubuntu × Node 22/24 四矩阵 CI、候选一致性、隔离安装及公开包核验通过才能发布。
12
+
13
+ ## 安装与边界
14
+
15
+ 固定版本 @feng0531/dsh-execution-bus@0.2.2,DSH 0.2.0-rc.2。只修复审核确认问题,本版不混入待实施的“萧何”显示名称调整。非商业源码公开许可证。真实模型/GUI/PTC 完整链路未验收,不支持跨重启恢复;发布不自动安装或重启 DSH。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feng0531/dsh-execution-bus",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "license": "SEE LICENSE IN LICENSE",