@feng0531/dsh-execution-bus 0.6.0 → 0.7.0

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.6.0**
7
+ DSH 独立插件:`@feng0531/dsh-execution-bus` · 当前版本 **0.7.0**
8
8
 
9
9
  > 萧何模式从宿主已声明的 PTC 组合派生,保留日常工具,再叠加可选执行总线。已验证隔离的真实 Node PTC → 原 read + 总线调用,以及脚本模型内存会话;真实模型、桌面界面与实际沙箱仍未验收。**不支持重启后恢复任务。**
10
10
 
@@ -85,8 +85,10 @@ flowchart TD
85
85
  |---|---|---|
86
86
  | `test`(诊断,非业务) | 仅在明确诊断时检查延迟、进度、成功、失败和取消 | `delayMs`:0–30000;`fail`:布尔值 |
87
87
  | `host.read` | 经由 DSH 现有 `read` 工具读取文本 | `file_path`;可选 `offset` 和 `limit`,最多 100 行 |
88
+ | `host.edit` | 经由 DSH 现有 `edit` 工具替换文本(有副作用) | `file_path`、`old_string`、`new_string`;可选 `replace_all` |
89
+ | `host.write` | 经由 DSH 现有 `write` 工具写入文本(有副作用) | `file_path`、`content` |
88
90
 
89
- `host.read` 在出队时重新进入宿主工具管线,沿用宿主当前权限、会话工作目录及输出限制。插件不自行启动命令、不提供任意远程执行服务。它也不会强制将现有工具改成总线调用。
91
+ `host.read`、`host.edit` 与 `host.write` 在出队时重新进入宿主工具管线,沿用宿主当前权限、会话工作目录及输出限制。`host.edit`/`host.write` 有副作用,显式声明非幂等:同一 requestId 的终态结果不可复用,重复提交返回 `MUTATING_REUSE_DENIED`。插件不自行启动命令、不提供任意远程执行服务,也不会强制将现有工具改成总线调用。
90
92
 
91
93
  ## 选择“萧何”预设
92
94
 
@@ -103,7 +105,7 @@ flowchart TD
103
105
 
104
106
  **0.3.0 起不再是只读验证模式。** 加载时读取宿主 `ptc` 预设的组合声明快照,保留原工具、平台表达式、禁用项和配置,只追加萧何总线与说明。不再强制 native 呈现或添加只读白名单;终端、写入、搜索、技能等能力是否可用,取决于原 PTC 配置和宿主服务。其他预设和默认模式不被修改,原 PTC 会话看不到新增总线。
105
107
 
106
- 普通任务仍通过 `run_code` 调用原工具;**总线目前只支持 test 和 host.read,不接管任意命令、安装或全部工具**。例如更新插件应使用原终端能力并遵守宿主授权;专用插件管理工具若在源 PTC 中禁用,萧何不会擅自启用。命令受理、返回 Job ID 不等于执行完成。
108
+ 普通任务仍通过 `run_code` 调用原工具;**总线目前只支持 test、host.read、host.edit 和 host.write,不接管任意命令、安装或全部工具**。例如更新插件应使用原终端能力并遵守宿主授权;专用插件管理工具若在源 PTC 中禁用,萧何不会擅自启用。命令受理、返回 Job ID 不等于执行完成。
107
109
 
108
110
  升级提示:预设 ID 保持兼容,但从旧版只读模式升级后能力范围扩大,使用者须审阅后加载;本次不自动升级日常配置。它不是进程沙箱,同一预设会话共享总线容量,结果按会话隔离。派生是加载时快照,不自动跟随源预设热编辑;源 PTC 更新后应按宿主正常流程重新加载派生行,旧会话遵循宿主版本保留规则。
109
111
 
@@ -124,7 +126,7 @@ flowchart TD
124
126
  2. 在你选定的隔离 DSH 配置依赖目录安装固定版本,不要在 DSH 程序安装目录执行:
125
127
 
126
128
  ```powershell
127
- npm install --ignore-scripts @feng0531/dsh-execution-bus@0.6.0
129
+ npm install --ignore-scripts @feng0531/dsh-execution-bus@0.7.0
128
130
  ```
129
131
 
130
132
  这是 npm 目录示例。若目标配置使用 pnpm,应沿用其包管理器,不混用锁文件。遇到宿主 peer 版本冲突就停止,不用强制安装绕过。
@@ -267,7 +269,7 @@ npm run test:install # 临时目录安装,不操作日常 DSH
267
269
 
268
270
  - [协议和投递语义](<docs/protocol.md>):字段、状态、重复处理与取消规则。
269
271
  - [宿主兼容性与源码调用链](<docs/host-compatibility.md>):为什么可以等待,为什么不能跨重启恢复。
270
- - [验证记录](<docs/validation.md>)、[0.6.0 发布说明](<docs/releases/0.6.0.md>):已验证与未验证范围。
272
+ - [验证记录](<docs/validation.md>)、[0.7.0 发布说明](<docs/releases/0.7.0.md>):已验证与未验证范围。
271
273
  - [同类项目源码整合](<docs/INTEGRATION.md>):固定来源、采用范围、许可与验证边界。
272
274
  - [维护者发布流程](<docs/RELEASING.md>):标签 CI、安装包校验与正式发布门禁。
273
275
 
@@ -41,6 +41,7 @@ export declare const ERRORS: Readonly<{
41
41
  DUPLICATE_EXECUTION_HANDLE: "DUPLICATE_EXECUTION_HANDLE";
42
42
  INVALID_STATE_QUERY: "INVALID_STATE_QUERY";
43
43
  HOST_READ_FAILED: "HOST_READ_FAILED";
44
+ HOST_TOOL_FAILED: "HOST_TOOL_FAILED";
44
45
  HOST_JOB_UNAVAILABLE: "HOST_JOB_UNAVAILABLE";
45
46
  PTC_PRESET_TOO_LARGE: "PTC_PRESET_TOO_LARGE";
46
47
  PTC_PRESET_ALREADY_CONTAINS_BUS: "PTC_PRESET_ALREADY_CONTAINS_BUS";
@@ -48,6 +49,8 @@ export declare const ERRORS: Readonly<{
48
49
  PTC_PRESET_UNAVAILABLE: "PTC_PRESET_UNAVAILABLE";
49
50
  INVALID_TEST_INPUT: "INVALID_TEST_INPUT";
50
51
  INVALID_READ_INPUT: "INVALID_READ_INPUT";
52
+ INVALID_EDIT_INPUT: "INVALID_EDIT_INPUT";
53
+ INVALID_WRITE_INPUT: "INVALID_WRITE_INPUT";
51
54
  READ_LIMIT: "READ_LIMIT";
52
55
  CONTROLLED_FAILURE: "CONTROLLED_FAILURE";
53
56
  HOST_CAPABILITY_REQUIRED: "HOST_CAPABILITY_REQUIRED";
@@ -45,6 +45,7 @@ export const ERRORS = Object.freeze({
45
45
  DUPLICATE_EXECUTION_HANDLE: "DUPLICATE_EXECUTION_HANDLE",
46
46
  INVALID_STATE_QUERY: "INVALID_STATE_QUERY",
47
47
  HOST_READ_FAILED: "HOST_READ_FAILED",
48
+ HOST_TOOL_FAILED: "HOST_TOOL_FAILED",
48
49
  HOST_JOB_UNAVAILABLE: "HOST_JOB_UNAVAILABLE",
49
50
  // —— 预设派生(preset) ——
50
51
  PTC_PRESET_TOO_LARGE: "PTC_PRESET_TOO_LARGE",
@@ -54,6 +55,8 @@ export const ERRORS = Object.freeze({
54
55
  // —— 内置 Handler(handlers) ——
55
56
  INVALID_TEST_INPUT: "INVALID_TEST_INPUT",
56
57
  INVALID_READ_INPUT: "INVALID_READ_INPUT",
58
+ INVALID_EDIT_INPUT: "INVALID_EDIT_INPUT",
59
+ INVALID_WRITE_INPUT: "INVALID_WRITE_INPUT",
57
60
  READ_LIMIT: "READ_LIMIT",
58
61
  CONTROLLED_FAILURE: "CONTROLLED_FAILURE",
59
62
  HOST_CAPABILITY_REQUIRED: "HOST_CAPABILITY_REQUIRED",
@@ -1,3 +1,15 @@
1
1
  import type { Handler } from "./bus.js";
2
+ /** 宿主工具转发 Handler 工厂:校验后把调用交回宿主工具管线执行。
3
+ * idempotent 由调用方按工具副作用显式声明;prepare 可补默认参数。 */
4
+ export declare function hostToolHandler(toolName: string, opts: {
5
+ idempotent: boolean;
6
+ validate(input: Record<string, unknown>): void;
7
+ prepare?(input: Record<string, unknown>): Record<string, unknown>;
8
+ }): Handler;
2
9
  export declare const testHandler: Handler;
10
+ /** 读取:无副作用,幂等,终态结果可复用。 */
3
11
  export declare const readHandler: Handler;
12
+ /** 写入:有副作用,非幂等,终态结果不可复用。 */
13
+ export declare const writeHandler: Handler;
14
+ /** 编辑:有副作用,非幂等,终态结果不可复用。 */
15
+ export declare const editHandler: Handler;
package/dist/handlers.js CHANGED
@@ -1,6 +1,21 @@
1
1
  import { setTimeout } from "node:timers/promises";
2
2
  import { ERRORS } from "./error-codes.js";
3
3
  import { HANDLER_LIMITS } from "./product.js";
4
+ /** 宿主工具转发 Handler 工厂:校验后把调用交回宿主工具管线执行。
5
+ * idempotent 由调用方按工具副作用显式声明;prepare 可补默认参数。 */
6
+ export function hostToolHandler(toolName, opts) {
7
+ // toolName 只表达意图;实际宿主工具已由宿主层按 handler 名绑定到 capability。
8
+ void toolName;
9
+ return {
10
+ idempotent: opts.idempotent,
11
+ validate: opts.validate,
12
+ async run(input, ctx) {
13
+ if (!ctx.invoke)
14
+ throw Error(ERRORS.HOST_CAPABILITY_REQUIRED);
15
+ return ctx.invoke(opts.prepare ? opts.prepare(input) : input, ctx.signal);
16
+ },
17
+ };
18
+ }
4
19
  export const testHandler = {
5
20
  idempotent: true,
6
21
  validate(input) {
@@ -23,24 +38,53 @@ export const testHandler = {
23
38
  return { ok: true };
24
39
  },
25
40
  };
26
- export const readHandler = {
27
- idempotent: true,
28
- validate(input) {
29
- if (Object.keys(input).some((k) => !["file_path", "offset", "limit"].includes(k)) ||
30
- typeof input.file_path !== "string" ||
31
- !input.file_path ||
32
- input.file_path.length > HANDLER_LIMITS.readPathChars)
41
+ function validateRead(input) {
42
+ if (Object.keys(input).some((k) => !["file_path", "offset", "limit"].includes(k)) ||
43
+ typeof input.file_path !== "string" ||
44
+ !input.file_path ||
45
+ input.file_path.length > HANDLER_LIMITS.readPathChars)
46
+ throw Error(ERRORS.INVALID_READ_INPUT);
47
+ for (const k of ["offset", "limit"])
48
+ if (input[k] !== undefined &&
49
+ (!Number.isSafeInteger(input[k]) || Number(input[k]) < 1))
33
50
  throw Error(ERRORS.INVALID_READ_INPUT);
34
- for (const k of ["offset", "limit"])
35
- if (input[k] !== undefined &&
36
- (!Number.isSafeInteger(input[k]) || Number(input[k]) < 1))
37
- throw Error(ERRORS.INVALID_READ_INPUT);
38
- if (Number(input.limit ?? HANDLER_LIMITS.readLines) > HANDLER_LIMITS.readLines)
39
- throw Error(ERRORS.READ_LIMIT);
40
- },
41
- async run(input, ctx) {
42
- if (!ctx.invoke)
43
- throw Error(ERRORS.HOST_CAPABILITY_REQUIRED);
44
- return ctx.invoke({ ...input, limit: input.limit ?? HANDLER_LIMITS.readLines }, ctx.signal);
45
- },
46
- };
51
+ if (Number(input.limit ?? HANDLER_LIMITS.readLines) > HANDLER_LIMITS.readLines)
52
+ throw Error(ERRORS.READ_LIMIT);
53
+ }
54
+ function validateEdit(input) {
55
+ if (Object.keys(input).some((k) => !["file_path", "old_string", "new_string", "replace_all"].includes(k)) ||
56
+ typeof input.file_path !== "string" ||
57
+ !input.file_path ||
58
+ input.file_path.length > HANDLER_LIMITS.readPathChars ||
59
+ typeof input.old_string !== "string" ||
60
+ typeof input.new_string !== "string" ||
61
+ (input.replace_all !== undefined && typeof input.replace_all !== "boolean"))
62
+ throw Error(ERRORS.INVALID_EDIT_INPUT);
63
+ }
64
+ /** 读取:无副作用,幂等,终态结果可复用。 */
65
+ export const readHandler = hostToolHandler("read", {
66
+ idempotent: true,
67
+ validate: validateRead,
68
+ prepare: (input) => ({
69
+ ...input,
70
+ limit: input.limit ?? HANDLER_LIMITS.readLines,
71
+ }),
72
+ });
73
+ function validateWrite(input) {
74
+ if (Object.keys(input).some((k) => !["file_path", "content"].includes(k)) ||
75
+ typeof input.file_path !== "string" ||
76
+ !input.file_path ||
77
+ input.file_path.length > HANDLER_LIMITS.readPathChars ||
78
+ typeof input.content !== "string")
79
+ throw Error(ERRORS.INVALID_WRITE_INPUT);
80
+ }
81
+ /** 写入:有副作用,非幂等,终态结果不可复用。 */
82
+ export const writeHandler = hostToolHandler("write", {
83
+ idempotent: false,
84
+ validate: validateWrite,
85
+ });
86
+ /** 编辑:有副作用,非幂等,终态结果不可复用。 */
87
+ export const editHandler = hostToolHandler("edit", {
88
+ idempotent: false,
89
+ validate: validateEdit,
90
+ });
package/dist/plugin.js CHANGED
@@ -3,13 +3,15 @@ import { ADMISSION_DEFAULTS, REQUEST_DESCRIPTION, REQUEST_PARAMETERS, STATE_PARA
3
3
  import { ExecutionOwner } from "./execution.js";
4
4
  import { ExecutionBus, parse } from "./bus.js";
5
5
  import { ERRORS } from "./error-codes.js";
6
- import { testHandler, readHandler } from "./handlers.js";
6
+ import { testHandler, readHandler, editHandler, writeHandler, } from "./handlers.js";
7
7
  export const name = "execution-bus";
8
8
  export const inject = ["tools", "jobs"];
9
9
  export function apply(ctx) {
10
10
  const bus = new ExecutionBus(undefined, ADMISSION_DEFAULTS);
11
11
  bus.register("test", testHandler);
12
12
  bus.register("host.read", readHandler);
13
+ bus.register("host.edit", editHandler);
14
+ bus.register("host.write", writeHandler);
13
15
  const executions = new ExecutionOwner();
14
16
  ctx.effect(() => () => executions.close(() => bus.close()));
15
17
  ctx.tools.register({
@@ -61,12 +63,38 @@ export function apply(ctx) {
61
63
  const m = parse(raw);
62
64
  if (m.type !== "request")
63
65
  return bus.command(owner, m);
66
+ // 宿主工具转发:仅 host.* handler 注入对应工具的 capability。
67
+ const hostTool = hostToolFor(m.payload.handler);
64
68
  let projection;
65
- const attached = bus.submit(owner, m, (event) => projection?.progress(event),
66
- // 宿主 read 能力:出队时重新进入宿主工具管线,沿用宿主权限与工作目录。
67
- async (input, signal) => {
69
+ const attached = bus.submit(owner, m, (event) => projection?.progress(event), hostTool
70
+ ? makeHostInvoke(exec, agent, hostTool.name, hostTool.errorCode)
71
+ : undefined);
72
+ // 1) 终态复用:已有执行直接读结果,不是新工作受理、不再申请 Job。
73
+ const terminal = await readTerminal(attached);
74
+ if (terminal)
75
+ return terminal;
76
+ // 2) 新执行:取得或创建宿主 Job 投影。
77
+ const execution = await projectExecution(owner, m, attached, agent.id, (handle) => (projection = handle));
78
+ // 3) 挂接取消生命周期,等待投影结算后返回执行快照。
79
+ return await awaitProjection(exec, attached, execution);
80
+ }
81
+ /** 把 host.<tool> handler 名映射到宿主工具名与失败错误码。 */
82
+ function hostToolFor(handler) {
83
+ if (typeof handler !== "string" || !handler.startsWith("host."))
84
+ return undefined;
85
+ const name = handler.slice("host.".length);
86
+ if (!name)
87
+ return undefined;
88
+ return {
89
+ name,
90
+ errorCode: name === "read" ? ERRORS.HOST_READ_FAILED : ERRORS.HOST_TOOL_FAILED,
91
+ };
92
+ }
93
+ /** 为一类宿主工具构造转发能力:出队时重新进入宿主工具管线,沿用宿主权限与工作目录。 */
94
+ function makeHostInvoke(exec, agent, toolName, errorCode) {
95
+ return async (input, signal) => {
68
96
  const result = await ctx.tools.execute({
69
- name: "read",
97
+ name: toolName,
70
98
  callId: (String(exec.callId) +
71
99
  ":bus:" +
72
100
  randomUUID()),
@@ -77,17 +105,9 @@ export function apply(ctx) {
77
105
  signal,
78
106
  });
79
107
  if (result.isError)
80
- throw Error(ERRORS.HOST_READ_FAILED);
108
+ throw Error(errorCode);
81
109
  return result.value;
82
- });
83
- // 1) 终态复用:已有执行直接读结果,不是新工作受理、不再申请 Job。
84
- const terminal = await readTerminal(attached);
85
- if (terminal)
86
- return terminal;
87
- // 2) 新执行:取得或创建宿主 Job 投影。
88
- const execution = await projectExecution(owner, m, attached, agent.id, (handle) => (projection = handle));
89
- // 3) 挂接取消生命周期,等待投影结算后返回执行快照。
90
- return await awaitProjection(exec, attached, execution);
110
+ };
91
111
  }
92
112
  // —— 三个职责单一的拆分函数(闭包内共享 ctx/bus/executions) ——
93
113
  /** accepted 已是终态时直接返回现有结果;否则返回 undefined 走新执行路径。 */
package/dist/product.d.ts CHANGED
@@ -52,5 +52,5 @@ export declare const STATE_PARAMETERS: {
52
52
  readonly properties: {};
53
53
  readonly additionalProperties: false;
54
54
  };
55
- export declare const REQUEST_DESCRIPTION = "\u8427\u4F55 / Xiao He. Submit a version 1 request and WAIT for its terminal result in this tool call. Reuse requestId only for the same logical operation. Business handler: host.read {file_path,offset?,limit?<=100}. Diagnostic handler only: test {delayMs:0..30000,fail:boolean}; never use it to perform real work. Control status/result/cancel use empty payload. Acceptance is volatile, not success. No polling or restart resume.";
55
+ export declare const REQUEST_DESCRIPTION = "\u8427\u4F55 / Xiao He. Submit a version 1 request and WAIT for its terminal result in this tool call. Reuse requestId only for the same logical operation. Business handlers: host.read {file_path,offset?,limit?<=100}; host.edit {file_path,old_string,new_string,replace_all?}; host.write {file_path,content} (mutating, terminal reuse denied). Diagnostic handler only: test {delayMs:0..30000,fail:boolean}; never use it to perform real work. Control status/result/cancel use empty payload. Acceptance is volatile, not success. No polling or restart resume.";
56
56
  export declare const MODE_INSTRUCTIONS: string;
package/dist/product.js CHANGED
@@ -40,8 +40,8 @@ export const STATE_PARAMETERS = {
40
40
  properties: {},
41
41
  additionalProperties: false,
42
42
  };
43
- export const REQUEST_DESCRIPTION = "萧何 / Xiao He. Submit a version 1 request and WAIT for its terminal result in this tool call. Reuse requestId only for the same logical operation. Business handler: host.read {file_path,offset?,limit?<=100}. Diagnostic handler only: test {delayMs:0..30000,fail:boolean}; never use it to perform real work. Control status/result/cancel use empty payload. Acceptance is volatile, not success. No polling or restart resume.";
44
- export const MODE_INSTRUCTIONS = "你正在萧何模式中。这不是角色扮演。保留宿主 PTC 的日常工具能力与权限规则。普通编辑、搜索、命令、安装和其他工作使用原工具,不要声称此模式只读。execution_bus 是可选协调入口,实际业务 Handler 只有 host.read;test 仅用于用户明确要求的诊断,不做真实工作,不要强制所有任务走总线。通过 run_code 调用 await tools.execution_bus(...),受原有时间预算约束。每会话排队上限 " +
43
+ export const REQUEST_DESCRIPTION = "萧何 / Xiao He. Submit a version 1 request and WAIT for its terminal result in this tool call. Reuse requestId only for the same logical operation. Business handlers: host.read {file_path,offset?,limit?<=100}; host.edit {file_path,old_string,new_string,replace_all?}; host.write {file_path,content} (mutating, terminal reuse denied). Diagnostic handler only: test {delayMs:0..30000,fail:boolean}; never use it to perform real work. Control status/result/cancel use empty payload. Acceptance is volatile, not success. No polling or restart resume.";
44
+ export const MODE_INSTRUCTIONS = "你正在萧何模式中。这不是角色扮演。保留宿主 PTC 的日常工具能力与权限规则。普通编辑、搜索、命令、安装和其他工作使用原工具,不要声称此模式只读。execution_bus 是可选协调入口,实际业务 Handler 为 host.read、host.edit 与 host.write(host.edit/host.write 有副作用,终态结果不可复用);test 仅用于用户明确要求的诊断,不做真实工作,不要强制所有任务走总线。通过 run_code 调用 await tools.execution_bus(...),受原有时间预算约束。每会话排队上限 " +
45
45
  ADMISSION_DEFAULTS.sessionQueueLimit +
46
46
  ",排队超过 " +
47
47
  ADMISSION_DEFAULTS.maxQueueWaitMs / 1000 +
package/docs/PRODUCT.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  ## 产品边界
4
4
 
5
- 萧何是 DSH 内的可选执行协调插件,提供独立可选择的 PTC 派生预设,不是 PTC 替代传输层,也不全局接管终端、编辑或其他工具。实际业务 Handler 为 host.read;test 是兼容保留的诊断 Handler,只有明确诊断需求时使用。测试能力存在不代表其他命令已接入总线。
5
+ 萧何是 DSH 内的可选执行协调插件,提供独立可选择的 PTC 派生预设,不是 PTC 替代传输层,也不全局接管终端、编辑或其他工具。实际业务 Handler 为 host.read、host.edit 与 host.write(host.edit/host.write 有副作用、非幂等);test 是兼容保留的诊断 Handler,只有明确诊断需求时使用。测试能力存在不代表其他命令已接入总线。
6
6
 
7
- 0.6.0 对齐 DSH 0.2.1-rc.2 并在 core 层为扩展 Handler 增加幂等声明,不以更名宣称完成生产环境认证。当前真实模型、桌面 UI 和实际沙箱尚未完整验收;同进程内存状态不支持跨重启恢复。
7
+ 0.7.0 在 DSH 0.2.1-rc.2 基线上接入 host.edit 与 host.write,并以幂等声明约束有副作用 Handler 的结果复用,不以更名宣称完成生产环境认证。当前真实模型、桌面 UI 和实际沙箱尚未完整验收;同进程内存状态不支持跨重启恢复。
8
8
 
9
9
  ## 入口及配置层次
10
10
 
package/docs/protocol.md CHANGED
@@ -116,4 +116,4 @@ core 实例的 `diagnostics()` 返回只读用途的独立快照:阶段计数
116
116
 
117
117
  包的 `./core` 导出 ExecutionBus、Handler、Capability 类型。Handler 只接收输入、requestId、signal、progress 和可选最小 capability,不接收聊天历史。register 返回绑定唯一注册令牌的幂等注销函数,不会误删后来同名注册;已受理请求继续持有原 Handler。
118
118
 
119
- 内置 test 和 host.read 没有非幂等写能力。当前没有 ctx.executionBus 服务、远端传输、跨插件动态注册或外部副作用对账契约。扩展有副作用 Handler 前必须另行设计授权、幂等与崩溃对账,不能仅依赖本版请求键。
119
+ 内置 test 与 host.read 没有副作用;host.edit 与 host.write 是非幂等写 Handler,通过 `Handler.idempotent=false` 显式声明,其终态结果复用被 `MUTATING_REUSE_DENIED` 拒绝,运行中的重复附着仍只等待同一执行。当前没有 ctx.executionBus 服务、远端传输、跨插件动态注册或外部副作用对账契约。扩展有副作用 Handler 前必须显式声明幂等并另行设计授权与崩溃对账,不能仅依赖本版请求键。
@@ -0,0 +1,18 @@
1
+ # 萧何 / Xiao He 0.7.0
2
+
3
+ ## 宿主工具转发扩展:host.edit 与 host.write
4
+
5
+ 本版在既有 host.read 之外接入两个有副作用的宿主工具,并把幂等声明机制用于真实写操作。
6
+
7
+ - 新增 host.edit(file_path / old_string / new_string / replace_all?)与 host.write(file_path / content),均经宿主工具管线执行,沿用宿主权限、会话工作目录与沙箱策略。
8
+ - 两者显式声明 Handler.idempotent=false:同一 requestId 的终态结果不可复用,重复提交返回 MUTATING_REUSE_DENIED;运行中的重复附着仍只等待同一执行,不会二次执行。
9
+ - 抽取出通用 hostToolHandler 工厂与 makeHostInvoke 能力抽象,按 host.<tool> handler 名绑定宿主工具与失败错误码;后续接入只读或写工具无需改动核心代码。
10
+ - 新增错误码 HOST_TOOL_FAILED、INVALID_EDIT_INPUT、INVALID_WRITE_INPUT。host.read 行为不变(仍幂等,失败码 HOST_READ_FAILED)。
11
+
12
+ ## 升级说明
13
+
14
+ 需要 DSH 0.2.1-rc.2。本版不强制把原工具改成总线调用,普通任务仍使用原工具;host.edit / host.write 只在你显式提交总线请求时执行。不自动安装、重写日常配置或重启 DSH。历史预设、配置行、工具名、协议字段与既有错误码均不变。
15
+
16
+ ## 验证与限制
17
+
18
+ 本地全量 83 项测试通过(含新增 host.edit / host.write 回归与真实宿主组件测试),包白名单校验通过。真实模型、桌面 UI 与实际沙箱仍未完整验收;状态为进程内存,不支持跨重启恢复。有副作用 Handler 的外部副作用对账仍不在本版范围。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feng0531/dsh-execution-bus",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "license": "SEE LICENSE IN LICENSE",