@feng0531/dsh-execution-bus 0.5.0 → 0.6.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/dist/plugin.js CHANGED
@@ -1,14 +1,13 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { ADMISSION_DEFAULTS, REQUEST_DESCRIPTION, REQUEST_PARAMETERS, STATE_PARAMETERS, } from "./product.js";
2
3
  import { ExecutionOwner } from "./execution.js";
3
4
  import { ExecutionBus, parse } from "./bus.js";
5
+ import { ERRORS } from "./error-codes.js";
4
6
  import { testHandler, readHandler } from "./handlers.js";
5
7
  export const name = "execution-bus";
6
8
  export const inject = ["tools", "jobs"];
7
9
  export function apply(ctx) {
8
- const bus = new ExecutionBus(undefined, {
9
- sessionQueueLimit: 16,
10
- maxQueueWaitMs: 120000,
11
- });
10
+ const bus = new ExecutionBus(undefined, ADMISSION_DEFAULTS);
12
11
  bus.register("test", testHandler);
13
12
  bus.register("host.read", readHandler);
14
13
  const executions = new ExecutionOwner();
@@ -16,21 +15,21 @@ export function apply(ctx) {
16
15
  ctx.tools.register({
17
16
  name: "execution_bus_state",
18
17
  description: "Read a bounded metadata-only snapshot of this agent session’s bus tasks. No inputs/results, no cross-session access. Do not poll during an awaited request.",
19
- parameters: { type: "object", properties: {}, additionalProperties: false },
18
+ parameters: STATE_PARAMETERS,
20
19
  output: {
21
20
  schema: {},
22
21
  render: (_a, v) => [{ type: "text", text: JSON.stringify(v) }],
23
22
  },
24
23
  async execute(raw, exec) {
25
24
  if (executions.phase !== "open")
26
- throw Error("CLOSED");
25
+ throw Error(ERRORS.CLOSED);
27
26
  if (!exec.agent)
28
- throw Error("OWNING_SESSION_REQUIRED");
27
+ throw Error(ERRORS.OWNING_SESSION_REQUIRED);
29
28
  if (!raw ||
30
29
  typeof raw !== "object" ||
31
30
  Array.isArray(raw) ||
32
31
  Reflect.ownKeys(raw).length)
33
- throw Error("INVALID_STATE_QUERY");
32
+ throw Error(ERRORS.INVALID_STATE_QUERY);
34
33
  return bus.inspect({
35
34
  sessionId: String(exec.agent.id),
36
35
  callId: String(exec.callId),
@@ -39,49 +38,33 @@ export function apply(ctx) {
39
38
  });
40
39
  ctx.tools.register({
41
40
  name: "execution_bus",
42
- 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. Handlers: test {delayMs:0..30000,fail:boolean}, host.read {file_path,offset?,limit?<=100}. Control types status/result/cancel use empty payload. Acceptance is volatile, not execution success. No polling needed; no restart resume.",
43
- parameters: {
44
- type: "object",
45
- additionalProperties: false,
46
- required: [
47
- "protocolVersion",
48
- "messageId",
49
- "requestId",
50
- "type",
51
- "payload",
52
- ],
53
- properties: {
54
- protocolVersion: { type: "integer", const: 1 },
55
- messageId: { type: "string" },
56
- requestId: { type: "string" },
57
- type: {
58
- type: "string",
59
- enum: ["request", "status", "result", "cancel"],
60
- },
61
- payload: { type: "object" },
62
- },
63
- },
41
+ description: REQUEST_DESCRIPTION,
42
+ parameters: REQUEST_PARAMETERS,
64
43
  output: {
65
44
  schema: {},
66
45
  render: (_a, v) => [{ type: "text", text: JSON.stringify(v) }],
67
46
  },
68
47
  execute(raw, exec) {
69
48
  if (executions.phase !== "open")
70
- return Promise.reject(Error("CLOSED"));
49
+ return Promise.reject(Error(ERRORS.CLOSED));
71
50
  return executions.track(dispatch(raw, exec));
72
51
  },
73
52
  });
74
53
  async function dispatch(raw, exec) {
75
54
  if (!exec.agent)
76
- throw Error("OWNING_SESSION_REQUIRED");
55
+ throw Error(ERRORS.OWNING_SESSION_REQUIRED);
56
+ const agent = exec.agent;
77
57
  const owner = {
78
- sessionId: String(exec.agent.id),
58
+ sessionId: String(agent.id),
79
59
  callId: String(exec.callId),
80
- }, m = parse(raw);
60
+ };
61
+ const m = parse(raw);
81
62
  if (m.type !== "request")
82
63
  return bus.command(owner, m);
83
64
  let projection;
84
- const attached = bus.submit(owner, m, (event) => projection?.progress(event), async (input, signal) => {
65
+ const attached = bus.submit(owner, m, (event) => projection?.progress(event),
66
+ // 宿主 read 能力:出队时重新进入宿主工具管线,沿用宿主权限与工作目录。
67
+ async (input, signal) => {
85
68
  const result = await ctx.tools.execute({
86
69
  name: "read",
87
70
  callId: (String(exec.callId) +
@@ -89,43 +72,62 @@ export function apply(ctx) {
89
72
  randomUUID()),
90
73
  rootCallId: exec.rootCallId,
91
74
  parent: exec.token,
92
- agent: exec.agent,
75
+ agent,
93
76
  arguments: input,
94
77
  signal,
95
78
  });
96
79
  if (result.isError)
97
- throw Error("HOST_READ_FAILED");
80
+ throw Error(ERRORS.HOST_READ_FAILED);
98
81
  return result.value;
99
82
  });
100
- // Terminal reuse is a read of an existing result, not admission of new work.
101
- if (attached.accepted.payload.state !== "queued" &&
102
- attached.accepted.payload.state !== "running") {
103
- try {
104
- const value = await attached.done;
105
- attached.returned();
106
- return value;
107
- }
108
- finally {
109
- attached.detach();
110
- }
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);
91
+ }
92
+ // —— 三个职责单一的拆分函数(闭包内共享 ctx/bus/executions) ——
93
+ /** accepted 已是终态时直接返回现有结果;否则返回 undefined 走新执行路径。 */
94
+ async function readTerminal(attached) {
95
+ const state = attached.accepted.payload.state;
96
+ if (state === "queued" || state === "running")
97
+ return undefined;
98
+ try {
99
+ const value = await attached.done;
100
+ attached.returned();
101
+ return value;
111
102
  }
112
- const abort = () => attached.cancel();
103
+ finally {
104
+ attached.detach();
105
+ }
106
+ }
107
+ /** 命中已存在的共享投影则复用,否则创建投影;创建失败按 HOST_JOB_UNAVAILABLE 结算。 */
108
+ async function projectExecution(owner, m, attached, ownerId, onProjected) {
113
109
  const jobKey = JSON.stringify([owner.sessionId, m.requestId]);
114
- let execution = executions.get(jobKey);
115
- if (execution)
116
- attached.detach(); // only the first attachment projects progress into the shared Job
117
- if (!execution) {
118
- try {
119
- execution = executions.create(jobKey, ctx.jobs, exec.agent.id, attached);
120
- projection = execution;
121
- }
122
- catch {
123
- abort();
124
- await attached.done;
125
- attached.detach();
126
- throw Error("HOST_JOB_UNAVAILABLE");
127
- }
110
+ const existing = executions.get(jobKey);
111
+ if (existing) {
112
+ // 只让第一个附着把进度投影到共享 Job;后续附着立即解除。
113
+ attached.detach();
114
+ return existing;
115
+ }
116
+ try {
117
+ const created = executions.create(jobKey, ctx.jobs, ownerId, attached);
118
+ onProjected(created);
119
+ return created;
128
120
  }
121
+ catch {
122
+ attached.cancel();
123
+ await attached.done;
124
+ attached.detach();
125
+ throw Error(ERRORS.HOST_JOB_UNAVAILABLE);
126
+ }
127
+ }
128
+ /** 把宿主工具调用的取消映射到执行,并等待投影结算后返回快照。 */
129
+ async function awaitProjection(exec, attached, execution) {
130
+ const abort = () => attached.cancel();
129
131
  exec.signal.addEventListener("abort", abort, { once: true });
130
132
  if (exec.signal.aborted)
131
133
  abort();
package/dist/preset.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Context, Service } from "@deepseek-ai/cordis";
2
2
  import type { PresetDefinition } from "@deepseek-ai/dsh-agent-preset-registry";
3
- export declare const PRESET_ID = "execution-bus-test";
3
+ export declare const PRESET_ID: "execution-bus-test";
4
4
  /** Capture the host-declared composition without evaluating !!js or changing disabled rows. */
5
5
  export declare function derivePreset(content: string): PresetDefinition;
6
6
  /** Host-plane registrar. readDocument is a public serialized declaration, not a private registry read. */
package/dist/preset.js CHANGED
@@ -3,41 +3,44 @@ import { entryListProblem } from "@deepseek-ai/dsh-agent-preset-registry";
3
3
  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
- export const PRESET_ID = "execution-bus-test";
6
+ import { PRODUCT } from "./product.js";
7
+ import { ERRORS } from "./error-codes.js";
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
+ }
7
24
  /** Capture the host-declared composition without evaluating !!js or changing disabled rows. */
8
25
  export function derivePreset(content) {
9
26
  if (Buffer.byteLength(content) > 1048576)
10
- throw Error("PTC_PRESET_TOO_LARGE");
27
+ throw Error(ERRORS.PTC_PRESET_TOO_LARGE);
11
28
  const plugins = load(content, { schema: entryListSchema });
12
29
  const problem = entryListProblem(plugins);
13
30
  if (problem)
14
31
  throw Error("Invalid PTC preset: " + problem);
15
32
  const rows = plugins;
16
- const inspect = (entries) => {
17
- for (const row of entries) {
18
- if (row.id === "xiao-he-execution-bus" ||
19
- row.name?.startsWith("@feng0531/dsh-execution-bus"))
20
- throw Error("PTC_PRESET_ALREADY_CONTAINS_BUS");
21
- // readDocument does not publish the declaring baseUrl. Do not silently
22
- // rebase relative modules/includes into the registrar's different context.
23
- if (row.name?.startsWith(".") ||
24
- row.name === "@deepseek-ai/cordis-plugin-include")
25
- throw Error("PTC_PRESET_RELATIVE_BASE_UNSUPPORTED");
26
- if (row.group && Array.isArray(row.config))
27
- inspect(row.config);
28
- }
29
- };
30
- inspect(rows);
33
+ validateDerivation(rows);
31
34
  return {
32
35
  id: PRESET_ID,
33
- name: "萧何 / Xiao He",
36
+ name: PRODUCT.name,
34
37
  order: 20,
35
38
  description: "继承宿主 PTC 工具组合,叠加执行请求排队、等待与结果回传。普通任务使用原工具,授权和沙箱由宿主管理;不支持重启恢复。",
36
39
  plugins: [
37
40
  ...rows,
38
41
  {
39
42
  id: "xiao-he-execution-bus",
40
- name: "@feng0531/dsh-execution-bus/test-mode",
43
+ name: PRODUCT.modeEntry,
41
44
  },
42
45
  ],
43
46
  };
@@ -67,7 +70,7 @@ export default class XiaoHePreset {
67
70
  }
68
71
  }
69
72
  if (!document)
70
- throw Error("PTC_PRESET_UNAVAILABLE");
73
+ throw Error(ERRORS.PTC_PRESET_UNAVAILABLE);
71
74
  yield await this.ctx.agentPresets.register(derivePreset(document.content));
72
75
  }
73
76
  }
@@ -0,0 +1,56 @@
1
+ /** Stable product identities and policy. Historical IDs are compatibility contracts, not display names. */
2
+ export declare const PRODUCT: Readonly<{
3
+ name: "萧何 / Xiao He";
4
+ packageName: "@feng0531/dsh-execution-bus";
5
+ presetId: "execution-bus-test";
6
+ presetRowId: "preset-execution-bus-test";
7
+ modeEntry: "@feng0531/dsh-execution-bus/mode";
8
+ modePluginName: "execution-bus-mode";
9
+ }>;
10
+ export declare const CORE_DEFAULTS: Readonly<{
11
+ concurrency: 2;
12
+ queueLimit: 32;
13
+ recordLimit: 256;
14
+ resultBytes: 65536;
15
+ retentionMs: 3600000;
16
+ }>;
17
+ export declare const ADMISSION_DEFAULTS: Readonly<{
18
+ sessionQueueLimit: 16;
19
+ maxQueueWaitMs: 120000;
20
+ }>;
21
+ export declare const HANDLER_LIMITS: Readonly<{
22
+ readLines: 100;
23
+ readPathChars: 2048;
24
+ diagnosticDelayMs: 30000;
25
+ }>;
26
+ export declare const REQUEST_PARAMETERS: {
27
+ readonly type: "object";
28
+ readonly additionalProperties: false;
29
+ readonly required: readonly ["protocolVersion", "messageId", "requestId", "type", "payload"];
30
+ readonly properties: {
31
+ readonly protocolVersion: {
32
+ readonly type: "integer";
33
+ readonly const: 1;
34
+ };
35
+ readonly messageId: {
36
+ readonly type: "string";
37
+ };
38
+ readonly requestId: {
39
+ readonly type: "string";
40
+ };
41
+ readonly type: {
42
+ readonly type: "string";
43
+ readonly enum: readonly ["request", "status", "result", "cancel"];
44
+ };
45
+ readonly payload: {
46
+ readonly type: "object";
47
+ };
48
+ };
49
+ };
50
+ export declare const STATE_PARAMETERS: {
51
+ readonly type: "object";
52
+ readonly properties: {};
53
+ readonly additionalProperties: false;
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.";
56
+ export declare const MODE_INSTRUCTIONS: string;
@@ -0,0 +1,48 @@
1
+ /** Stable product identities and policy. Historical IDs are compatibility contracts, not display names. */
2
+ export const PRODUCT = Object.freeze({
3
+ name: "萧何 / Xiao He",
4
+ packageName: "@feng0531/dsh-execution-bus",
5
+ presetId: "execution-bus-test",
6
+ presetRowId: "preset-execution-bus-test",
7
+ modeEntry: "@feng0531/dsh-execution-bus/mode",
8
+ modePluginName: "execution-bus-mode",
9
+ });
10
+ export const CORE_DEFAULTS = Object.freeze({
11
+ concurrency: 2,
12
+ queueLimit: 32,
13
+ recordLimit: 256,
14
+ resultBytes: 65536,
15
+ retentionMs: 3600000,
16
+ });
17
+ export const ADMISSION_DEFAULTS = Object.freeze({
18
+ sessionQueueLimit: 16,
19
+ maxQueueWaitMs: 120000,
20
+ });
21
+ export const HANDLER_LIMITS = Object.freeze({
22
+ readLines: 100,
23
+ readPathChars: 2048,
24
+ diagnosticDelayMs: 30000,
25
+ });
26
+ export const REQUEST_PARAMETERS = {
27
+ type: "object",
28
+ additionalProperties: false,
29
+ required: ["protocolVersion", "messageId", "requestId", "type", "payload"],
30
+ properties: {
31
+ protocolVersion: { type: "integer", const: 1 },
32
+ messageId: { type: "string" },
33
+ requestId: { type: "string" },
34
+ type: { type: "string", enum: ["request", "status", "result", "cancel"] },
35
+ payload: { type: "object" },
36
+ },
37
+ };
38
+ export const STATE_PARAMETERS = {
39
+ type: "object",
40
+ properties: {},
41
+ additionalProperties: false,
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(...),受原有时间预算约束。每会话排队上限 " +
45
+ ADMISSION_DEFAULTS.sessionQueueLimit +
46
+ ",排队超过 " +
47
+ ADMISSION_DEFAULTS.maxQueueWaitMs / 1000 +
48
+ " 秒且未执行会返回 QUEUE_WAIT_TIMEOUT,不自动重试。execution_bus_state 只用于用户明确诊断本会话元数据,不循环查询。请求只提交一次并等待结果,同一逻辑操作重发沿用 requestId。cancelRequested 不等于已取消,unknown 不代表执行失败,不承诺跨重启恢复。后台命令返回 Job ID 不代表已经完成。";
@@ -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;
@@ -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
+ }
@@ -1,5 +1,2 @@
1
- import type { Context } from "@deepseek-ai/cordis";
2
- export declare const name = "execution-bus-test-mode";
3
- export declare const inject: string[];
4
- /** An additive preset row: presentation, tool inventory and authority belong to the host PTC composition. */
5
- export declare function apply(ctx: Context): void;
1
+ /** @deprecated Compatibility-only forwarding entry. Use the package /mode export. */
2
+ export { name, inject, apply } from "./mode.js";
package/dist/test-mode.js CHANGED
@@ -1,15 +1,2 @@
1
- import { scopeOf } from "@deepseek-ai/dsh-scope";
2
- import { apply as applyBus } from "./plugin.js";
3
- export const name = "execution-bus-test-mode"; // legacy export identity, intentionally stable
4
- export const inject = ["tools", "jobs", "systemPrompt"];
5
- /** An additive preset row: presentation, tool inventory and authority belong to the host PTC composition. */
6
- export function apply(ctx) {
7
- if (scopeOf(ctx) === undefined)
8
- throw Error("Xiao He mode requires an Agent preset scope");
9
- ctx.effect(() => ctx.systemPrompt.section({
10
- name: "XIAO_HE_EXECUTION_BUS",
11
- order: 1700,
12
- text: "你正在萧何模式中。这不是角色扮演。保留宿主 PTC 的日常工具能力与权限规则。普通编辑、搜索、命令、安装和其他工作使用原工具,不要声称此模式只读。execution_bus 是可选协调入口,目前只支持 test 与 host.read,不支持任意命令,不要强制所有任务走总线。通过 run_code 调用 await tools.execution_bus(...),受原有时间预算约束。总线默认每会话最多排队16项,排队超过120秒且尚未开始执行会返回QUEUE_WAIT_TIMEOUT,不自动重试。execution_bus_state只用于用户明确诊断本会话的元数据状态,不在等待期间循环查询。总线请求只提交一次并等待结果,不循环查询;同一逻辑操作重发沿用 requestId。cancelRequested 不等于已取消,unknown 不代表执行失败,不承诺跨重启恢复。后台命令仅返回 Job ID 不代表命令已经完成。",
13
- }));
14
- applyBus(ctx);
15
- }
1
+ /** @deprecated Compatibility-only forwarding entry. Use the package /mode export. */
2
+ export { name, inject, apply } from "./mode.js";
@@ -0,0 +1,46 @@
1
+ # 产品结构与维护契约
2
+
3
+ ## 产品边界
4
+
5
+ 萧何是 DSH 内的可选执行协调插件,提供独立可选择的 PTC 派生预设,不是 PTC 替代传输层,也不全局接管终端、编辑或其他工具。实际业务 Handler 为 host.read;test 是兼容保留的诊断 Handler,只有明确诊断需求时使用。测试能力存在不代表其他命令已接入总线。
6
+
7
+ 0.6.0 对齐 DSH 0.2.1-rc.2 并在 core 层为扩展 Handler 增加幂等声明,不以更名宣称完成生产环境认证。当前真实模型、桌面 UI 和实际沙箱尚未完整验收;同进程内存状态不支持跨重启恢复。
8
+
9
+ ## 入口及配置层次
10
+
11
+ | 入口 | 谁使用 | 责任 |
12
+ |---|---|---|
13
+ | 包级 bundle /preset | 目标 DSH 配置加载器 | 读取 ptc 声明快照,注册独立萧何预设 |
14
+ | Agent 内 /mode | 萧何预设末尾插件行 | 保留原 PTC 组合,追加总线工具和说明 |
15
+ | /test-mode | 旧配置兼容 | 直接映射同一个正式 mode 模块,不维护第二份实现 |
16
+ | 包根入口 | 明确自行组装的宿主 | 注册 execution_bus、execution_bus_state,不派生 PTC |
17
+ | /core | 可信嵌入方 | 自行注册 Handler 的内存核心,不提供外部身份认证 |
18
+
19
+ 正式代码在 src/mode.ts,src/test-mode.ts 只重导出。package exports 的新旧入口均映射 dist/mode.js,避免 Cordis 因插件模块身份不同而重复激活;旧物理构建文件仍兼容重导出。不要同时配置两行别名。预设 ID execution-bus-test 和配置行 ID preset-execution-bus-test 是历史兼容身份,不继续用于推荐显示名称。
20
+
21
+ ## 模块职责
22
+
23
+ | 模块 | 负责 | 不负责 |
24
+ |---|---|---|
25
+ | product | 固定身份、容量/准入默认值、工具参数与提示契约 | 不提供用户可覆盖的运行时配置入口 |
26
+ | preset | 声明派生、兼容检查和宿主注册 | 不偷偷重写源 ptc 或持久配置 |
27
+ | mode / test-mode 兼容层 | 作用域说明与工具安装 | 不改宿主权限/沙箱/呈现,不加载第二套 PTC |
28
+ | plugin | 工具身份校验、可信执行上下文和宿主 read 派发 | 不自行文件读取或创建通用命令执行端 |
29
+ | bus / json | 请求校验、幂等冲突、调度、取消、终态与有界状态 | 不以超时推定副作用失败、不自动重跑 |
30
+ | execution | Job 投影、活跃工具交付与关闭所有权 | 不作为第二个执行状态机 |
31
+ | handlers | 实际读取适配和明确诊断 Handler | 不把测试当成通用业务执行 |
32
+ | diagnostics | 有界数值轨迹 | 不保留请求内容、错误原文或充当授权审计 |
33
+
34
+ ## 兼容和错误规则
35
+
36
+ 稳定工具名、请求协议和历史预设身份保留。协议校验/背压错误与执行快照中的固定错误码分层;宿主服务错误继续保持真实失败,不统一吞掉,也不把 error 文本写入数值诊断。完整错误词汇见协议说明,本轮不重命名既有错误码。
37
+
38
+ 单一策略定义不表示开放 YAML 调参:宿主插件目前使用固定默认值;core 构造参数仍按原契约使用。新增配置界面需单独定义校验、升级与误配置回归,不能只在文档放不存在的 config 键。
39
+
40
+ ## 交付要求
41
+
42
+ - 修改身份、默认策略或工具提示时,验证共享 product 契约与实际宿主状态工具一致。
43
+ - 新旧 mode 必须指向同一个模块,派生预设必须使用正式入口。
44
+ - 严格包白名单覆盖正式模块、兼容转发与类型文件;生产隔离安装通过包导出导入两入口核对身份。
45
+ - 继续全量 core、真实宿主生命周期及 Node PTC 回归,不用文档测试替代桌面验收。
46
+ - 完整验证后统一标签 CI 和正式发布;发布不自动安装/重启日常 DSH。
@@ -0,0 +1,18 @@
1
+ # 萧何 / Xiao He 0.5.1
2
+
3
+ ## 正式产品结构整理
4
+
5
+ 本版修正 test-mode 误导命名并梳理内部职责,保留执行能力和兼容规则,不通过更名宣称生产环境已全部验收。
6
+
7
+ - 正式实现移至 mode 模块;新增 /mode 导出,新派生预设默认使用它。/test-mode 是同模块兼容别名,旧源/构建文件只保留重导出,不维护两套插件。
8
+ - 产品身份、core/准入默认策略、读取/诊断限制、工具参数和模式提示集中到 product,减少重复定义漂移。
9
+ - 真实业务 host.read 与诊断 test 明确区分:兼容保留 test,但提示模型只在用户明确诊断时使用,不将其描述成真实业务处理能力。
10
+ - 增加正式/旧入口模块身份、默认策略与提示契约测试;真实 Loader 和生产隔离安装检查正式入口。
11
+
12
+ ## 升级说明
13
+
14
+ 先安装 0.5.1 或以上,再将 Agent 预设末尾叠加行改为 @feng0531/dsh-execution-bus/mode;旧版本没有该导出。旧配置不会自动重写且继续可用,不要同时放新旧两行。包级注册入口仍是 /preset,不能与 Agent 内入口混填。
15
+
16
+ 历史预设 execution-bus-test、配置行 preset-execution-bus-test、工具名和协议不改,避免已有会话关联失效。普通 PTC、权限/沙箱、每会话16项和排队120秒等策略不变,不新增运行依赖。
17
+
18
+ 不自动安装、重写日常配置或重启 DSH。真实模型、桌面 UI 与实际沙箱仍未完整验收;不支持跨重启恢复。
@@ -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 与实际沙箱仍未完整验收;状态仍为进程内存,不支持跨重启恢复。