@feng0531/dsh-execution-bus 0.5.1 → 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 +7 -5
- package/dist/bus.d.ts +14 -43
- package/dist/bus.js +135 -148
- package/dist/diagnostics.js +6 -4
- package/dist/error-codes.d.ts +57 -0
- package/dist/error-codes.js +63 -0
- package/dist/execution.d.ts +8 -0
- package/dist/execution.js +67 -48
- package/dist/handlers.d.ts +12 -0
- package/dist/handlers.js +71 -24
- package/dist/json.d.ts +1 -1
- package/dist/json.js +13 -11
- package/dist/limits.d.ts +20 -0
- package/dist/limits.js +23 -0
- package/dist/plugin.js +83 -39
- package/dist/preset.js +19 -17
- package/dist/product.d.ts +2 -2
- package/dist/product.js +3 -3
- package/dist/protocol.d.ts +49 -0
- package/dist/protocol.js +54 -0
- package/docs/PRODUCT.md +2 -2
- package/docs/protocol.md +1 -1
- package/docs/releases/0.6.0.md +20 -0
- package/docs/releases/0.7.0.md +18 -0
- package/package.json +18 -18
package/dist/plugin.js
CHANGED
|
@@ -2,13 +2,16 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { ADMISSION_DEFAULTS, REQUEST_DESCRIPTION, REQUEST_PARAMETERS, STATE_PARAMETERS, } from "./product.js";
|
|
3
3
|
import { ExecutionOwner } from "./execution.js";
|
|
4
4
|
import { ExecutionBus, parse } from "./bus.js";
|
|
5
|
-
import {
|
|
5
|
+
import { ERRORS } from "./error-codes.js";
|
|
6
|
+
import { testHandler, readHandler, editHandler, writeHandler, } from "./handlers.js";
|
|
6
7
|
export const name = "execution-bus";
|
|
7
8
|
export const inject = ["tools", "jobs"];
|
|
8
9
|
export function apply(ctx) {
|
|
9
10
|
const bus = new ExecutionBus(undefined, ADMISSION_DEFAULTS);
|
|
10
11
|
bus.register("test", testHandler);
|
|
11
12
|
bus.register("host.read", readHandler);
|
|
13
|
+
bus.register("host.edit", editHandler);
|
|
14
|
+
bus.register("host.write", writeHandler);
|
|
12
15
|
const executions = new ExecutionOwner();
|
|
13
16
|
ctx.effect(() => () => executions.close(() => bus.close()));
|
|
14
17
|
ctx.tools.register({
|
|
@@ -21,14 +24,14 @@ export function apply(ctx) {
|
|
|
21
24
|
},
|
|
22
25
|
async execute(raw, exec) {
|
|
23
26
|
if (executions.phase !== "open")
|
|
24
|
-
throw Error(
|
|
27
|
+
throw Error(ERRORS.CLOSED);
|
|
25
28
|
if (!exec.agent)
|
|
26
|
-
throw Error(
|
|
29
|
+
throw Error(ERRORS.OWNING_SESSION_REQUIRED);
|
|
27
30
|
if (!raw ||
|
|
28
31
|
typeof raw !== "object" ||
|
|
29
32
|
Array.isArray(raw) ||
|
|
30
33
|
Reflect.ownKeys(raw).length)
|
|
31
|
-
throw Error(
|
|
34
|
+
throw Error(ERRORS.INVALID_STATE_QUERY);
|
|
32
35
|
return bus.inspect({
|
|
33
36
|
sessionId: String(exec.agent.id),
|
|
34
37
|
callId: String(exec.callId),
|
|
@@ -45,65 +48,106 @@ export function apply(ctx) {
|
|
|
45
48
|
},
|
|
46
49
|
execute(raw, exec) {
|
|
47
50
|
if (executions.phase !== "open")
|
|
48
|
-
return Promise.reject(Error(
|
|
51
|
+
return Promise.reject(Error(ERRORS.CLOSED));
|
|
49
52
|
return executions.track(dispatch(raw, exec));
|
|
50
53
|
},
|
|
51
54
|
});
|
|
52
55
|
async function dispatch(raw, exec) {
|
|
53
56
|
if (!exec.agent)
|
|
54
|
-
throw Error(
|
|
57
|
+
throw Error(ERRORS.OWNING_SESSION_REQUIRED);
|
|
58
|
+
const agent = exec.agent;
|
|
55
59
|
const owner = {
|
|
56
|
-
sessionId: String(
|
|
60
|
+
sessionId: String(agent.id),
|
|
57
61
|
callId: String(exec.callId),
|
|
58
|
-
}
|
|
62
|
+
};
|
|
63
|
+
const m = parse(raw);
|
|
59
64
|
if (m.type !== "request")
|
|
60
65
|
return bus.command(owner, m);
|
|
66
|
+
// 宿主工具转发:仅 host.* handler 注入对应工具的 capability。
|
|
67
|
+
const hostTool = hostToolFor(m.payload.handler);
|
|
61
68
|
let projection;
|
|
62
|
-
const attached = bus.submit(owner, m, (event) => projection?.progress(event),
|
|
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) => {
|
|
63
96
|
const result = await ctx.tools.execute({
|
|
64
|
-
name:
|
|
97
|
+
name: toolName,
|
|
65
98
|
callId: (String(exec.callId) +
|
|
66
99
|
":bus:" +
|
|
67
100
|
randomUUID()),
|
|
68
101
|
rootCallId: exec.rootCallId,
|
|
69
102
|
parent: exec.token,
|
|
70
|
-
agent
|
|
103
|
+
agent,
|
|
71
104
|
arguments: input,
|
|
72
105
|
signal,
|
|
73
106
|
});
|
|
74
107
|
if (result.isError)
|
|
75
|
-
throw Error(
|
|
108
|
+
throw Error(errorCode);
|
|
76
109
|
return result.value;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// —— 三个职责单一的拆分函数(闭包内共享 ctx/bus/executions) ——
|
|
113
|
+
/** accepted 已是终态时直接返回现有结果;否则返回 undefined 走新执行路径。 */
|
|
114
|
+
async function readTerminal(attached) {
|
|
115
|
+
const state = attached.accepted.payload.state;
|
|
116
|
+
if (state === "queued" || state === "running")
|
|
117
|
+
return undefined;
|
|
118
|
+
try {
|
|
119
|
+
const value = await attached.done;
|
|
120
|
+
attached.returned();
|
|
121
|
+
return value;
|
|
89
122
|
}
|
|
90
|
-
|
|
123
|
+
finally {
|
|
124
|
+
attached.detach();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** 命中已存在的共享投影则复用,否则创建投影;创建失败按 HOST_JOB_UNAVAILABLE 结算。 */
|
|
128
|
+
async function projectExecution(owner, m, attached, ownerId, onProjected) {
|
|
91
129
|
const jobKey = JSON.stringify([owner.sessionId, m.requestId]);
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
await attached.done;
|
|
103
|
-
attached.detach();
|
|
104
|
-
throw Error("HOST_JOB_UNAVAILABLE");
|
|
105
|
-
}
|
|
130
|
+
const existing = executions.get(jobKey);
|
|
131
|
+
if (existing) {
|
|
132
|
+
// 只让第一个附着把进度投影到共享 Job;后续附着立即解除。
|
|
133
|
+
attached.detach();
|
|
134
|
+
return existing;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const created = executions.create(jobKey, ctx.jobs, ownerId, attached);
|
|
138
|
+
onProjected(created);
|
|
139
|
+
return created;
|
|
106
140
|
}
|
|
141
|
+
catch {
|
|
142
|
+
attached.cancel();
|
|
143
|
+
await attached.done;
|
|
144
|
+
attached.detach();
|
|
145
|
+
throw Error(ERRORS.HOST_JOB_UNAVAILABLE);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** 把宿主工具调用的取消映射到执行,并等待投影结算后返回快照。 */
|
|
149
|
+
async function awaitProjection(exec, attached, execution) {
|
|
150
|
+
const abort = () => attached.cancel();
|
|
107
151
|
exec.signal.addEventListener("abort", abort, { once: true });
|
|
108
152
|
if (exec.signal.aborted)
|
|
109
153
|
abort();
|
package/dist/preset.js
CHANGED
|
@@ -4,31 +4,33 @@ import { entryListSchema } from "@deepseek-ai/cordis-plugin-include";
|
|
|
4
4
|
import { load } from "js-yaml";
|
|
5
5
|
import { setTimeout as pause } from "node:timers/promises";
|
|
6
6
|
import { PRODUCT } from "./product.js";
|
|
7
|
+
import { ERRORS } from "./error-codes.js";
|
|
7
8
|
export const PRESET_ID = PRODUCT.presetId;
|
|
9
|
+
/** 校验派生源组合:拒绝已含总线,以及缺少公开基准地址的相对模块/include 子文件。 */
|
|
10
|
+
function validateDerivation(rows) {
|
|
11
|
+
for (const row of rows) {
|
|
12
|
+
if (row.id === "xiao-he-execution-bus" ||
|
|
13
|
+
row.name?.startsWith("@feng0531/dsh-execution-bus"))
|
|
14
|
+
throw Error(ERRORS.PTC_PRESET_ALREADY_CONTAINS_BUS);
|
|
15
|
+
// readDocument 不发布声明方的 baseUrl,不能把相对模块/include
|
|
16
|
+
// 静默重定位到 registrar 的不同上下文。
|
|
17
|
+
if (row.name?.startsWith(".") ||
|
|
18
|
+
row.name === "@deepseek-ai/cordis-plugin-include")
|
|
19
|
+
throw Error(ERRORS.PTC_PRESET_RELATIVE_BASE_UNSUPPORTED);
|
|
20
|
+
if (row.group && Array.isArray(row.config))
|
|
21
|
+
validateDerivation(row.config);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
8
24
|
/** Capture the host-declared composition without evaluating !!js or changing disabled rows. */
|
|
9
25
|
export function derivePreset(content) {
|
|
10
26
|
if (Buffer.byteLength(content) > 1048576)
|
|
11
|
-
throw Error(
|
|
27
|
+
throw Error(ERRORS.PTC_PRESET_TOO_LARGE);
|
|
12
28
|
const plugins = load(content, { schema: entryListSchema });
|
|
13
29
|
const problem = entryListProblem(plugins);
|
|
14
30
|
if (problem)
|
|
15
31
|
throw Error("Invalid PTC preset: " + problem);
|
|
16
32
|
const rows = plugins;
|
|
17
|
-
|
|
18
|
-
for (const row of entries) {
|
|
19
|
-
if (row.id === "xiao-he-execution-bus" ||
|
|
20
|
-
row.name?.startsWith("@feng0531/dsh-execution-bus"))
|
|
21
|
-
throw Error("PTC_PRESET_ALREADY_CONTAINS_BUS");
|
|
22
|
-
// readDocument does not publish the declaring baseUrl. Do not silently
|
|
23
|
-
// rebase relative modules/includes into the registrar's different context.
|
|
24
|
-
if (row.name?.startsWith(".") ||
|
|
25
|
-
row.name === "@deepseek-ai/cordis-plugin-include")
|
|
26
|
-
throw Error("PTC_PRESET_RELATIVE_BASE_UNSUPPORTED");
|
|
27
|
-
if (row.group && Array.isArray(row.config))
|
|
28
|
-
inspect(row.config);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
inspect(rows);
|
|
33
|
+
validateDerivation(rows);
|
|
32
34
|
return {
|
|
33
35
|
id: PRESET_ID,
|
|
34
36
|
name: PRODUCT.name,
|
|
@@ -68,7 +70,7 @@ export default class XiaoHePreset {
|
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
if (!document)
|
|
71
|
-
throw Error(
|
|
73
|
+
throw Error(ERRORS.PTC_PRESET_UNAVAILABLE);
|
|
72
74
|
yield await this.ctx.agentPresets.register(derivePreset(document.content));
|
|
73
75
|
}
|
|
74
76
|
}
|
package/dist/product.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export declare const ADMISSION_DEFAULTS: Readonly<{
|
|
|
18
18
|
sessionQueueLimit: 16;
|
|
19
19
|
maxQueueWaitMs: 120000;
|
|
20
20
|
}>;
|
|
21
|
-
export declare const
|
|
21
|
+
export declare const HANDLER_LIMITS: Readonly<{
|
|
22
22
|
readLines: 100;
|
|
23
23
|
readPathChars: 2048;
|
|
24
24
|
diagnosticDelayMs: 30000;
|
|
@@ -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
|
|
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
|
@@ -18,7 +18,7 @@ export const ADMISSION_DEFAULTS = Object.freeze({
|
|
|
18
18
|
sessionQueueLimit: 16,
|
|
19
19
|
maxQueueWaitMs: 120000,
|
|
20
20
|
});
|
|
21
|
-
export const
|
|
21
|
+
export const HANDLER_LIMITS = Object.freeze({
|
|
22
22
|
readLines: 100,
|
|
23
23
|
readPathChars: 2048,
|
|
24
24
|
diagnosticDelayMs: 30000,
|
|
@@ -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
|
|
44
|
-
export const MODE_INSTRUCTIONS = "你正在萧何模式中。这不是角色扮演。保留宿主 PTC 的日常工具能力与权限规则。普通编辑、搜索、命令、安装和其他工作使用原工具,不要声称此模式只读。execution_bus 是可选协调入口,实际业务 Handler
|
|
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 +
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type State = "queued" | "running" | "succeeded" | "failed" | "cancelled" | "unknown";
|
|
2
|
+
export type Owner = {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
callId: string;
|
|
5
|
+
};
|
|
6
|
+
export type Message = {
|
|
7
|
+
protocolVersion: 1;
|
|
8
|
+
messageId: string;
|
|
9
|
+
requestId: string;
|
|
10
|
+
type: "request" | "status" | "result" | "cancel";
|
|
11
|
+
payload: Record<string, unknown>;
|
|
12
|
+
};
|
|
13
|
+
export type Event = {
|
|
14
|
+
protocolVersion: 1;
|
|
15
|
+
messageId: string;
|
|
16
|
+
requestId: string;
|
|
17
|
+
correlationId: string;
|
|
18
|
+
sequence: number;
|
|
19
|
+
type: string;
|
|
20
|
+
payload: Record<string, unknown>;
|
|
21
|
+
};
|
|
22
|
+
export type Capability = (input: Record<string, unknown>, signal: AbortSignal) => Promise<unknown>;
|
|
23
|
+
export interface Handler {
|
|
24
|
+
/** 是否可安全复用终态结果。省略默认 true(可复用,向后兼容);
|
|
25
|
+
* 显式 false = 有副作用,终态结果复用将被拒绝(运行中等待仍放行)。 */
|
|
26
|
+
readonly idempotent?: boolean;
|
|
27
|
+
validate(input: Record<string, unknown>): void;
|
|
28
|
+
run(input: Record<string, unknown>, ctx: {
|
|
29
|
+
signal: AbortSignal;
|
|
30
|
+
progress: (sequence: number, percent: number) => void;
|
|
31
|
+
requestId: string;
|
|
32
|
+
invoke?: Capability;
|
|
33
|
+
}): Promise<unknown>;
|
|
34
|
+
}
|
|
35
|
+
export interface RecordView {
|
|
36
|
+
requestId: string;
|
|
37
|
+
state: State;
|
|
38
|
+
sequence: number;
|
|
39
|
+
cancelRequested: boolean;
|
|
40
|
+
delivery: "pending" | "returned";
|
|
41
|
+
result?: unknown;
|
|
42
|
+
error?: string;
|
|
43
|
+
progress?: number;
|
|
44
|
+
}
|
|
45
|
+
export declare function object(v: unknown): Record<string, unknown>;
|
|
46
|
+
/** 规范排序后的稳定摘要,只用于同 requestId 的冲突比较,不用于跨请求去重。 */
|
|
47
|
+
export declare function canonical(v: unknown): string;
|
|
48
|
+
export declare function parse(raw: unknown): Message;
|
|
49
|
+
export declare function freezeJson<T>(value: T): T;
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { assertJson } from "./json.js";
|
|
2
|
+
import { ERRORS } from "./error-codes.js";
|
|
3
|
+
import { LIMITS } from "./limits.js";
|
|
4
|
+
export function object(v) {
|
|
5
|
+
if (!v || typeof v !== "object" || Array.isArray(v))
|
|
6
|
+
throw Error(ERRORS.INVALID_OBJECT);
|
|
7
|
+
return v;
|
|
8
|
+
}
|
|
9
|
+
/** 规范排序后的稳定摘要,只用于同 requestId 的冲突比较,不用于跨请求去重。 */
|
|
10
|
+
export function canonical(v) {
|
|
11
|
+
if (v === null || typeof v !== "object")
|
|
12
|
+
return JSON.stringify(v);
|
|
13
|
+
if (Array.isArray(v))
|
|
14
|
+
return "[" + v.map(canonical).join(",") + "]";
|
|
15
|
+
return ("{" +
|
|
16
|
+
Object.keys(v)
|
|
17
|
+
.sort()
|
|
18
|
+
.map((k) => JSON.stringify(k) +
|
|
19
|
+
":" +
|
|
20
|
+
canonical(v[k]))
|
|
21
|
+
.join(",") +
|
|
22
|
+
"}");
|
|
23
|
+
}
|
|
24
|
+
export function parse(raw) {
|
|
25
|
+
assertJson(raw);
|
|
26
|
+
const m = object(raw);
|
|
27
|
+
if (Object.keys(m).some((k) => ![
|
|
28
|
+
"protocolVersion",
|
|
29
|
+
"messageId",
|
|
30
|
+
"requestId",
|
|
31
|
+
"type",
|
|
32
|
+
"payload",
|
|
33
|
+
].includes(k)))
|
|
34
|
+
throw Error(ERRORS.UNKNOWN_FIELD);
|
|
35
|
+
if (m.protocolVersion !== 1)
|
|
36
|
+
throw Error(ERRORS.UNSUPPORTED_VERSION);
|
|
37
|
+
for (const k of ["messageId", "requestId"])
|
|
38
|
+
if (typeof m[k] !== "string" || !LIMITS.idPattern.test(m[k]))
|
|
39
|
+
throw Error(ERRORS.INVALID_ID);
|
|
40
|
+
if (!["request", "status", "result", "cancel"].includes(m.type))
|
|
41
|
+
throw Error(ERRORS.UNSUPPORTED_TYPE);
|
|
42
|
+
object(m.payload);
|
|
43
|
+
if (Buffer.byteLength(JSON.stringify(m)) > LIMITS.messageMaxBytes)
|
|
44
|
+
throw Error(ERRORS.MESSAGE_TOO_LARGE);
|
|
45
|
+
return structuredClone(m);
|
|
46
|
+
}
|
|
47
|
+
export function freezeJson(value) {
|
|
48
|
+
if (value && typeof value === "object") {
|
|
49
|
+
for (const child of Object.values(value))
|
|
50
|
+
freezeJson(child);
|
|
51
|
+
Object.freeze(value);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
package/docs/PRODUCT.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## 产品边界
|
|
4
4
|
|
|
5
|
-
萧何是 DSH 内的可选执行协调插件,提供独立可选择的 PTC 派生预设,不是 PTC 替代传输层,也不全局接管终端、编辑或其他工具。实际业务 Handler 为 host.read
|
|
5
|
+
萧何是 DSH 内的可选执行协调插件,提供独立可选择的 PTC 派生预设,不是 PTC 替代传输层,也不全局接管终端、编辑或其他工具。实际业务 Handler 为 host.read、host.edit 与 host.write(host.edit/host.write 有副作用、非幂等);test 是兼容保留的诊断 Handler,只有明确诊断需求时使用。测试能力存在不代表其他命令已接入总线。
|
|
6
6
|
|
|
7
|
-
0.
|
|
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
|
|
119
|
+
内置 test 与 host.read 没有副作用;host.edit 与 host.write 是非幂等写 Handler,通过 `Handler.idempotent=false` 显式声明,其终态结果复用被 `MUTATING_REUSE_DENIED` 拒绝,运行中的重复附着仍只等待同一执行。当前没有 ctx.executionBus 服务、远端传输、跨插件动态注册或外部副作用对账契约。扩展有副作用 Handler 前必须显式声明幂等并另行设计授权与崩溃对账,不能仅依赖本版请求键。
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 萧何 / Xiao He 0.6.0
|
|
2
|
+
|
|
3
|
+
## DSH 0.2.1-rc.2 兼容与 Handler 幂等约束
|
|
4
|
+
|
|
5
|
+
本版把兼容目标对齐到 DSH 0.2.1-rc.2,并在 core 层为扩展 Handler 增加可声明的幂等约束。
|
|
6
|
+
|
|
7
|
+
- 兼容目标升级为 DSH 0.2.1-rc.2:五个宿主 peer 依赖由 0.2.0-rc.2 升至 0.2.1-rc.2,锁文件重建。0.2.0-rc.2 宿主应继续使用 0.5.x。
|
|
8
|
+
- 新增 Handler.idempotent 声明。显式 idempotent:false 时,终态结果复用被拒绝并返回 MUTATING_REUSE_DENIED;运行中的重复 attach 仍只等待同一个执行,不会二次执行。省略该字段默认幂等,保持既有 handler 与调用方行为。
|
|
9
|
+
- 内置 test 与 host.read 显式声明 idempotent:true。
|
|
10
|
+
- 适配 0.2.1-rc.2 新增的 workingDirectory 服务:真实宿主组件测试为 read/run_code 路径注入最小 stub,不改变插件运行逻辑。
|
|
11
|
+
|
|
12
|
+
## 升级说明
|
|
13
|
+
|
|
14
|
+
需要 DSH 0.2.1-rc.2。升级前先停止新请求,等待已有任务完成或请求合作取消。本版不自动安装、重写日常配置或重启 DSH。
|
|
15
|
+
|
|
16
|
+
历史预设 execution-bus-test、配置行 preset-execution-bus-test、工具名、协议字段和既有错误码均不变;错误词汇仅新增 MUTATING_REUSE_DENIED。
|
|
17
|
+
|
|
18
|
+
## 验证与限制
|
|
19
|
+
|
|
20
|
+
本地全量 76 项测试通过(含新增幂等回归、真实宿主组件与 Node PTC 组件测试),包白名单校验通过。真实模型、桌面 UI 与实际沙箱仍未完整验收;状态仍为进程内存,不支持跨重启恢复。
|
|
@@ -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.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -52,25 +52,25 @@
|
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@deepseek-ai/cordis": "~4.0.4",
|
|
54
54
|
"@deepseek-ai/cordis-plugin-include": "~1.0.9",
|
|
55
|
-
"@deepseek-ai/dsh-agent-preset-registry": "0.2.
|
|
56
|
-
"@deepseek-ai/dsh-jobs": "0.2.
|
|
57
|
-
"@deepseek-ai/dsh-scope": "0.2.
|
|
58
|
-
"@deepseek-ai/dsh-system-prompt": "0.2.
|
|
59
|
-
"@deepseek-ai/dsh-tools": "0.2.
|
|
55
|
+
"@deepseek-ai/dsh-agent-preset-registry": "0.2.1-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-jobs": "0.2.1-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-scope": "0.2.1-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.1-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-tools": "0.2.1-rc.2"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@deepseek-ai/dsh-agent-loop": "0.2.
|
|
63
|
-
"@deepseek-ai/dsh-agent-preset": "0.2.
|
|
64
|
-
"@deepseek-ai/dsh-agent-preset-registry": "0.2.
|
|
65
|
-
"@deepseek-ai/dsh-agent-tool-presentation": "0.2.
|
|
66
|
-
"@deepseek-ai/dsh-fs-local": "0.2.
|
|
67
|
-
"@deepseek-ai/dsh-jobs": "0.2.
|
|
68
|
-
"@deepseek-ai/dsh-jobs-local": "0.2.
|
|
69
|
-
"@deepseek-ai/dsh-persona": "0.2.
|
|
70
|
-
"@deepseek-ai/dsh-ptc-runtime-node": "0.2.
|
|
71
|
-
"@deepseek-ai/dsh-subprocess-local": "0.2.
|
|
72
|
-
"@deepseek-ai/dsh-tool-fs": "0.2.
|
|
73
|
-
"@deepseek-ai/dsh-tool-jobs": "0.2.
|
|
62
|
+
"@deepseek-ai/dsh-agent-loop": "0.2.1-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-agent-preset": "0.2.1-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-agent-preset-registry": "0.2.1-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-agent-tool-presentation": "0.2.1-rc.2",
|
|
66
|
+
"@deepseek-ai/dsh-fs-local": "0.2.1-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-jobs": "0.2.1-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-jobs-local": "0.2.1-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-persona": "0.2.1-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-ptc-runtime-node": "0.2.1-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-subprocess-local": "0.2.1-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-tool-fs": "0.2.1-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-tool-jobs": "0.2.1-rc.2",
|
|
74
74
|
"@types/js-yaml": "4.0.9",
|
|
75
75
|
"@types/node": "^22",
|
|
76
76
|
"prettier": "3.6.2",
|