@zhushanwen/subagent-engine-sdk 0.2.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.
Files changed (139) hide show
  1. package/dist/best-effort.cjs +88 -0
  2. package/dist/best-effort.d.cts +14 -0
  3. package/dist/best-effort.d.ts +14 -0
  4. package/dist/best-effort.js +8 -0
  5. package/dist/chunk-2DIMPZCQ.js +0 -0
  6. package/dist/chunk-365AUV6N.js +67 -0
  7. package/dist/chunk-3K2P2CM2.js +127 -0
  8. package/dist/chunk-75QEMUGV.js +58 -0
  9. package/dist/chunk-7I4XGL5J.js +95 -0
  10. package/dist/chunk-A75XDJIC.js +227 -0
  11. package/dist/chunk-DSQ7JQKM.js +39 -0
  12. package/dist/chunk-EJMF63R5.js +19 -0
  13. package/dist/chunk-GT6YLLN4.js +46 -0
  14. package/dist/chunk-HYES77BR.js +109 -0
  15. package/dist/chunk-JSBRDJBE.js +30 -0
  16. package/dist/chunk-LEOBWKRM.js +128 -0
  17. package/dist/chunk-N3RL6OVM.js +38 -0
  18. package/dist/chunk-OPMY4G4M.js +27 -0
  19. package/dist/chunk-PPEPBVCC.js +120 -0
  20. package/dist/chunk-PYO3YR7W.js +16 -0
  21. package/dist/chunk-RDH3ZOV6.js +46 -0
  22. package/dist/chunk-RULLX6C6.js +11 -0
  23. package/dist/chunk-X24SFZYW.js +6646 -0
  24. package/dist/chunk-YFSN3D5N.js +216 -0
  25. package/dist/chunk-ZOFFJNJD.js +136 -0
  26. package/dist/chunk-ZXEAW25V.js +132 -0
  27. package/dist/cli-entry.cjs +119 -0
  28. package/dist/cli-entry.d.cts +24 -0
  29. package/dist/cli-entry.d.ts +24 -0
  30. package/dist/cli-entry.js +8 -0
  31. package/dist/contract-types-sSlgppBC.d.cts +352 -0
  32. package/dist/contract-types-sSlgppBC.d.ts +352 -0
  33. package/dist/data-dir.cjs +117 -0
  34. package/dist/data-dir.d.cts +20 -0
  35. package/dist/data-dir.d.ts +20 -0
  36. package/dist/data-dir.js +12 -0
  37. package/dist/env.cjs +205 -0
  38. package/dist/env.d.cts +73 -0
  39. package/dist/env.d.ts +73 -0
  40. package/dist/env.js +16 -0
  41. package/dist/error-codes-DHco5-i_.d.cts +118 -0
  42. package/dist/error-codes-Dhss2Kmk.d.ts +118 -0
  43. package/dist/error-message.cjs +40 -0
  44. package/dist/error-message.d.cts +3 -0
  45. package/dist/error-message.d.ts +3 -0
  46. package/dist/error-message.js +7 -0
  47. package/dist/index.cjs +8375 -0
  48. package/dist/index.d.cts +23 -0
  49. package/dist/index.d.ts +23 -0
  50. package/dist/index.js +268 -0
  51. package/dist/journal-io.cjs +70 -0
  52. package/dist/journal-io.d.cts +12 -0
  53. package/dist/journal-io.d.ts +12 -0
  54. package/dist/journal-io.js +7 -0
  55. package/dist/journal-replay.cjs +256 -0
  56. package/dist/journal-replay.d.cts +45 -0
  57. package/dist/journal-replay.d.ts +45 -0
  58. package/dist/journal-replay.js +17 -0
  59. package/dist/kill-chain.cjs +221 -0
  60. package/dist/kill-chain.d.cts +74 -0
  61. package/dist/kill-chain.d.ts +74 -0
  62. package/dist/kill-chain.js +23 -0
  63. package/dist/logger.cjs +84 -0
  64. package/dist/logger.d.cts +24 -0
  65. package/dist/logger.d.ts +24 -0
  66. package/dist/logger.js +11 -0
  67. package/dist/logs/stderr-rotation.cjs +151 -0
  68. package/dist/logs/stderr-rotation.d.cts +37 -0
  69. package/dist/logs/stderr-rotation.d.ts +37 -0
  70. package/dist/logs/stderr-rotation.js +23 -0
  71. package/dist/nesting-guard.cjs +95 -0
  72. package/dist/nesting-guard.d.cts +65 -0
  73. package/dist/nesting-guard.d.ts +65 -0
  74. package/dist/nesting-guard.js +15 -0
  75. package/dist/node-executor.cjs +180 -0
  76. package/dist/node-executor.d.cts +63 -0
  77. package/dist/node-executor.d.ts +63 -0
  78. package/dist/node-executor.js +16 -0
  79. package/dist/paths.cjs +55 -0
  80. package/dist/paths.d.cts +8 -0
  81. package/dist/paths.d.ts +8 -0
  82. package/dist/paths.js +15 -0
  83. package/dist/port-contract.cjs +35 -0
  84. package/dist/port-contract.d.cts +88 -0
  85. package/dist/port-contract.d.ts +88 -0
  86. package/dist/port-contract.js +7 -0
  87. package/dist/protocol/index.cjs +400 -0
  88. package/dist/protocol/index.d.cts +685 -0
  89. package/dist/protocol/index.d.ts +685 -0
  90. package/dist/protocol/index.js +89 -0
  91. package/dist/relay-env.cjs +71 -0
  92. package/dist/relay-env.d.cts +37 -0
  93. package/dist/relay-env.d.ts +37 -0
  94. package/dist/relay-env.js +23 -0
  95. package/dist/schema-emulation.cjs +6680 -0
  96. package/dist/schema-emulation.d.cts +39 -0
  97. package/dist/schema-emulation.d.ts +39 -0
  98. package/dist/schema-emulation.js +11 -0
  99. package/dist/spawn.cjs +200 -0
  100. package/dist/spawn.d.cts +80 -0
  101. package/dist/spawn.d.ts +80 -0
  102. package/dist/spawn.js +16 -0
  103. package/dist/ui-channels.cjs +120 -0
  104. package/dist/ui-channels.d.cts +60 -0
  105. package/dist/ui-channels.d.ts +60 -0
  106. package/dist/ui-channels.js +9 -0
  107. package/dist/ui-types.cjs +18 -0
  108. package/dist/ui-types.d.cts +62 -0
  109. package/dist/ui-types.d.ts +62 -0
  110. package/dist/ui-types.js +1 -0
  111. package/package.json +58 -0
  112. package/src/best-effort.ts +37 -0
  113. package/src/cli-entry.ts +77 -0
  114. package/src/data-dir.ts +88 -0
  115. package/src/env.ts +265 -0
  116. package/src/error-message.ts +22 -0
  117. package/src/index.ts +63 -0
  118. package/src/journal-io.ts +82 -0
  119. package/src/journal-replay.ts +432 -0
  120. package/src/kill-chain.ts +265 -0
  121. package/src/logger.ts +105 -0
  122. package/src/logs/stderr-rotation.ts +166 -0
  123. package/src/nesting-guard.ts +140 -0
  124. package/src/node-executor.ts +272 -0
  125. package/src/paths.ts +48 -0
  126. package/src/port-contract.ts +117 -0
  127. package/src/protocol/contract-types.ts +378 -0
  128. package/src/protocol/engine-protocol.ts +81 -0
  129. package/src/protocol/error-codes.ts +179 -0
  130. package/src/protocol/frames.ts +145 -0
  131. package/src/protocol/index.ts +12 -0
  132. package/src/protocol/methods.ts +229 -0
  133. package/src/protocol/reverse-channels.ts +274 -0
  134. package/src/protocol/schema.ts +154 -0
  135. package/src/relay-env.ts +60 -0
  136. package/src/schema-emulation.ts +192 -0
  137. package/src/spawn.ts +246 -0
  138. package/src/ui-channels.ts +219 -0
  139. package/src/ui-types.ts +84 -0
@@ -0,0 +1,39 @@
1
+ /** 原始输出尾部回显长度(结果对象的 tail 字段,供错误展示与重试 prompt 回灌)。 */
2
+ declare const SCHEMA_EMULATION_TAIL_CHARS = 500;
3
+ /**
4
+ * 构造 prompt 注入段:schema 声明 + 输出格式约定(emulated 引擎的 launcher 把它拼进
5
+ * 最终 prompt,替代引擎不存在的 schema 强制通道)。
6
+ *
7
+ * 文案风格对齐 structured-output 的 promptGuidelines("Do not output JSON in text":emulated 引擎没有工具可调,JSON 必须出现在文本里)。
8
+ */
9
+ declare function buildSchemaEmulationSegment(schema: object): string;
10
+ /** 提取结果联合:ok=true 携带通过校验的 parsed;ok=false 携带 error 简述 + 原始输出尾部。 */
11
+ type StructuredOutputResult = {
12
+ ok: true;
13
+ parsed: unknown;
14
+ } | {
15
+ ok: false;
16
+ error: string;
17
+ tail: string;
18
+ };
19
+ /**
20
+ * 从模型文本输出提取并校验结构化 JSON(三级容错 → ajv)。
21
+ *
22
+ * 三级容错(逐级降级,任一级提取成功即进 ajv):
23
+ * 1. 直接 JSON.parse(trim 后整体是合法 JSON);
24
+ * 2. 剥 markdown code fence(```json ... ``` / ``` ... ```)取围栏内内容;
25
+ * 3. 首尾括号扫描(首个 '{'/'[' 到末个 '}'/']' 的子串——容纳前后杂文本)。
26
+ *
27
+ * ajv 校验失败/三级提取失败返回 ok:false(不 throw)——重试与否是宿主编排层的
28
+ * 决策(见文件头重试语义),本函数每次调用独立无状态。
29
+ */
30
+ declare function extractAndValidateStructuredOutput(text: string, schema: object): {
31
+ ok: true;
32
+ parsed: unknown;
33
+ } | {
34
+ ok: false;
35
+ error: string;
36
+ tail: string;
37
+ };
38
+
39
+ export { SCHEMA_EMULATION_TAIL_CHARS, type StructuredOutputResult, buildSchemaEmulationSegment, extractAndValidateStructuredOutput };
@@ -0,0 +1,39 @@
1
+ /** 原始输出尾部回显长度(结果对象的 tail 字段,供错误展示与重试 prompt 回灌)。 */
2
+ declare const SCHEMA_EMULATION_TAIL_CHARS = 500;
3
+ /**
4
+ * 构造 prompt 注入段:schema 声明 + 输出格式约定(emulated 引擎的 launcher 把它拼进
5
+ * 最终 prompt,替代引擎不存在的 schema 强制通道)。
6
+ *
7
+ * 文案风格对齐 structured-output 的 promptGuidelines("Do not output JSON in text":emulated 引擎没有工具可调,JSON 必须出现在文本里)。
8
+ */
9
+ declare function buildSchemaEmulationSegment(schema: object): string;
10
+ /** 提取结果联合:ok=true 携带通过校验的 parsed;ok=false 携带 error 简述 + 原始输出尾部。 */
11
+ type StructuredOutputResult = {
12
+ ok: true;
13
+ parsed: unknown;
14
+ } | {
15
+ ok: false;
16
+ error: string;
17
+ tail: string;
18
+ };
19
+ /**
20
+ * 从模型文本输出提取并校验结构化 JSON(三级容错 → ajv)。
21
+ *
22
+ * 三级容错(逐级降级,任一级提取成功即进 ajv):
23
+ * 1. 直接 JSON.parse(trim 后整体是合法 JSON);
24
+ * 2. 剥 markdown code fence(```json ... ``` / ``` ... ```)取围栏内内容;
25
+ * 3. 首尾括号扫描(首个 '{'/'[' 到末个 '}'/']' 的子串——容纳前后杂文本)。
26
+ *
27
+ * ajv 校验失败/三级提取失败返回 ok:false(不 throw)——重试与否是宿主编排层的
28
+ * 决策(见文件头重试语义),本函数每次调用独立无状态。
29
+ */
30
+ declare function extractAndValidateStructuredOutput(text: string, schema: object): {
31
+ ok: true;
32
+ parsed: unknown;
33
+ } | {
34
+ ok: false;
35
+ error: string;
36
+ tail: string;
37
+ };
38
+
39
+ export { SCHEMA_EMULATION_TAIL_CHARS, type StructuredOutputResult, buildSchemaEmulationSegment, extractAndValidateStructuredOutput };
@@ -0,0 +1,11 @@
1
+ import {
2
+ SCHEMA_EMULATION_TAIL_CHARS,
3
+ buildSchemaEmulationSegment,
4
+ extractAndValidateStructuredOutput
5
+ } from "./chunk-X24SFZYW.js";
6
+ import "./chunk-JSBRDJBE.js";
7
+ export {
8
+ SCHEMA_EMULATION_TAIL_CHARS,
9
+ buildSchemaEmulationSegment,
10
+ extractAndValidateStructuredOutput
11
+ };
package/dist/spawn.cjs ADDED
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/spawn.ts
21
+ var spawn_exports = {};
22
+ __export(spawn_exports, {
23
+ DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS: () => DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS,
24
+ ENGINE_HOST_REQUEST_TIMEOUT_ENV: () => ENGINE_HOST_REQUEST_TIMEOUT_ENV,
25
+ armEngineSelfDestruct: () => armEngineSelfDestruct,
26
+ pumpNdjsonLines: () => pumpNdjsonLines,
27
+ spawnEngineChild: () => spawnEngineChild
28
+ });
29
+ module.exports = __toCommonJS(spawn_exports);
30
+ var import_node_child_process = require("child_process");
31
+
32
+ // src/logger.ts
33
+ var SINK_SLOT_KEY = /* @__PURE__ */ Symbol.for("@zhushanwen/subagent-engine-sdk.logger-sink");
34
+ function getSinkSlot() {
35
+ let slot = Reflect.get(globalThis, SINK_SLOT_KEY);
36
+ if (!slot) {
37
+ slot = { current: void 0 };
38
+ Reflect.set(globalThis, SINK_SLOT_KEY, slot);
39
+ }
40
+ return slot;
41
+ }
42
+ var CONSOLE_SINK = {
43
+ log(level, component, message, data) {
44
+ const line = `[${component}] ${message}`;
45
+ if (level === "error") {
46
+ if (data === void 0) console.error(line);
47
+ else console.error(line, data);
48
+ return;
49
+ }
50
+ if (level === "warn") {
51
+ if (data === void 0) console.warn(line);
52
+ else console.warn(line, data);
53
+ return;
54
+ }
55
+ }
56
+ };
57
+ function currentSink() {
58
+ return getSinkSlot().current ?? CONSOLE_SINK;
59
+ }
60
+ var facadeCache = /* @__PURE__ */ new Map();
61
+ function getLogger(component) {
62
+ const existing = facadeCache.get(component);
63
+ if (existing) return existing;
64
+ const facade = {
65
+ debug(msg, data) {
66
+ currentSink().log("debug", component, msg, data);
67
+ },
68
+ warn(msg, data) {
69
+ currentSink().log("warn", component, msg, data);
70
+ },
71
+ error(msg, data) {
72
+ currentSink().log("error", component, msg, data);
73
+ }
74
+ };
75
+ facadeCache.set(component, facade);
76
+ return facade;
77
+ }
78
+
79
+ // src/spawn.ts
80
+ var logger = getLogger("engine-sdk/spawn");
81
+ var FORBIDDEN_OPTION_KEYS = ["detached", "stdio", "stdin", "windowsHide"];
82
+ function spawnEngineChild(opts) {
83
+ for (const key of FORBIDDEN_OPTION_KEYS) {
84
+ if (key in opts) {
85
+ throw new Error(
86
+ `spawnEngineChild: option "${key}" is hardcoded by the engine spawn contract (detached:false / windowsHide:true / child stdin is always its own pipe); remove it from the call site`
87
+ );
88
+ }
89
+ }
90
+ const child = (0, import_node_child_process.spawn)(opts.command, [...opts.args], {
91
+ cwd: opts.cwd,
92
+ env: opts.env,
93
+ detached: false,
94
+ windowsHide: true,
95
+ stdio: ["pipe", opts.stdout ?? "pipe", opts.stderr ?? "pipe"]
96
+ });
97
+ if (opts.signal !== void 0) {
98
+ opts.signal.addEventListener(
99
+ "abort",
100
+ () => {
101
+ child.kill("SIGTERM");
102
+ },
103
+ { once: true }
104
+ );
105
+ }
106
+ return child;
107
+ }
108
+ var DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS = 3e4;
109
+ var ENGINE_HOST_REQUEST_TIMEOUT_ENV = "XYZ_ENGINE_HOST_REQUEST_TIMEOUT_MS";
110
+ var SWEEP_INTERVAL_MS = 1e3;
111
+ function resolveHostRequestTimeoutMs(opts) {
112
+ if (opts.hostRequestTimeoutMs !== void 0) return opts.hostRequestTimeoutMs;
113
+ const raw = (opts.timeoutEnv ?? process.env)[ENGINE_HOST_REQUEST_TIMEOUT_ENV];
114
+ if (raw === void 0 || raw === "") return DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS;
115
+ const parsed = Number(raw);
116
+ if (!Number.isInteger(parsed) || parsed <= 0) {
117
+ logger.warn(
118
+ `invalid ${ENGINE_HOST_REQUEST_TIMEOUT_ENV}=${JSON.stringify(raw)}, falling back to ${DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS}ms`
119
+ );
120
+ return DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS;
121
+ }
122
+ return parsed;
123
+ }
124
+ function killOwnProcessGroup() {
125
+ const pid = process.pid;
126
+ try {
127
+ process.kill(-pid, "SIGKILL");
128
+ } catch (err) {
129
+ logger.warn(`process-group kill failed (${String(err)}), falling back to self kill`);
130
+ process.kill(pid, "SIGKILL");
131
+ }
132
+ }
133
+ function armEngineSelfDestruct(opts) {
134
+ const timeoutMs = resolveHostRequestTimeoutMs(opts);
135
+ const killSelf = opts.killSelf ?? killOwnProcessGroup;
136
+ const pending = /* @__PURE__ */ new Map();
137
+ let disposed = false;
138
+ const destroy = (reason) => {
139
+ if (disposed) return;
140
+ disposed = true;
141
+ clearInterval(timer);
142
+ opts.stdin.removeAllListeners?.("end");
143
+ opts.stdin.removeAllListeners?.("close");
144
+ logger.warn(`engine self-destruct: ${reason}`);
145
+ killSelf();
146
+ };
147
+ opts.stdin.once("end", () => destroy("host stdin EOF (end)"));
148
+ opts.stdin.once("close", () => destroy("host stdin closed"));
149
+ const timer = setInterval(() => {
150
+ const now = Date.now();
151
+ for (const [id, startedAt] of pending) {
152
+ if (now - startedAt > timeoutMs) {
153
+ destroy(`unacked reverse request ${id} exceeded ${timeoutMs}ms without host ack`);
154
+ return;
155
+ }
156
+ }
157
+ }, SWEEP_INTERVAL_MS);
158
+ timer.unref?.();
159
+ return {
160
+ started(id) {
161
+ if (!disposed) pending.set(id, Date.now());
162
+ },
163
+ acked(id) {
164
+ pending.delete(id);
165
+ },
166
+ settled(id) {
167
+ pending.delete(id);
168
+ },
169
+ dispose() {
170
+ if (disposed) return;
171
+ disposed = true;
172
+ clearInterval(timer);
173
+ opts.stdin.removeAllListeners?.("end");
174
+ opts.stdin.removeAllListeners?.("close");
175
+ }
176
+ };
177
+ }
178
+ function pumpNdjsonLines(stream, onLine) {
179
+ if (stream === null || stream === void 0) return;
180
+ stream.setEncoding("utf8");
181
+ let buffer = "";
182
+ stream.on("data", (chunk) => {
183
+ buffer += chunk;
184
+ let nl = buffer.indexOf("\n");
185
+ while (nl >= 0) {
186
+ const line = buffer.slice(0, nl);
187
+ buffer = buffer.slice(nl + 1);
188
+ onLine(line);
189
+ nl = buffer.indexOf("\n");
190
+ }
191
+ });
192
+ }
193
+ // Annotate the CommonJS export names for ESM import in node:
194
+ 0 && (module.exports = {
195
+ DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS,
196
+ ENGINE_HOST_REQUEST_TIMEOUT_ENV,
197
+ armEngineSelfDestruct,
198
+ pumpNdjsonLines,
199
+ spawnEngineChild
200
+ });
@@ -0,0 +1,80 @@
1
+ import { ChildProcess } from 'node:child_process';
2
+ import { buildEngineChildEnv } from './env.cjs';
3
+
4
+ /** spawnEngineChild 的 env 必须来自 buildEngineChildEnv(三层契约终态)。 */
5
+ type EngineChildEnv = ReturnType<typeof buildEngineChildEnv>;
6
+ /** 引擎子进程 spawn 选项(刻意不暴露 detached / stdio / stdin——形态硬编码,见文件头)。 */
7
+ interface SpawnEngineChildOptions {
8
+ command: string;
9
+ args: readonly string[];
10
+ /** 须由 buildEngineChildEnv 构建(类型即契约:三层 env 终态) */
11
+ env: EngineChildEnv;
12
+ cwd?: string;
13
+ /** 子进程 stdout/stderr 形态;缺省 'pipe'(协议帧走 stdout,stderr 走日志/轮转) */
14
+ stdout?: "pipe" | "ignore" | "inherit";
15
+ stderr?: "pipe" | "ignore" | "inherit";
16
+ /** AbortSignal 触发即 SIGTERM 杀子(缺省无) */
17
+ signal?: AbortSignal;
18
+ }
19
+ /**
20
+ * 引擎任务子进程唯一 spawn 入口。
21
+ *
22
+ * 形态硬编码:detached:false、windowsHide:true、子进程 stdin 恒 'pipe'(自有 pipe,
23
+ * 绝不继承引擎自身 stdin fd)。传入被禁止的形态键(detached/stdio/stdin/windowsHide)
24
+ * 直接抛错——契约破坏应在配置期出声,不静默降级。
25
+ */
26
+ declare function spawnEngineChild(opts: SpawnEngineChildOptions): ChildProcess;
27
+ /** in-flight 未 ack 反向请求的计时超时缺省值(辅助判据,30s)。 */
28
+ declare const DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS = 30000;
29
+ /** 计时超时覆盖 env 名(引擎 CLI 启动时读;非法值 warn + 回落缺省)。 */
30
+ declare const ENGINE_HOST_REQUEST_TIMEOUT_ENV = "XYZ_ENGINE_HOST_REQUEST_TIMEOUT_MS";
31
+ /** 反向请求计时面操作接口(消费方 = 引擎 CLI 的反向通道分发层)。 */
32
+ interface ReverseRequestClock {
33
+ /** 反向请求已发出(未 ack)——进入计时面。 */
34
+ started(id: string): void;
35
+ /**
36
+ * 收到 ack(两阶段第一阶段:已 ack 的 host/askUser、HostBridge.executeAndAwait
37
+ * 的 ack)——移出计时面但不终结(R9-2:已 ack 的等待不计入 in-flight 超时,
38
+ * ADR-0047 静默 ≠ 卡死)。
39
+ */
40
+ acked(id: string): void;
41
+ /** 请求终结(应答完成/失败/取消)——彻底移出。 */
42
+ settled(id: string): void;
43
+ /** 卸载守卫(引擎正常 shutdown 路径;自灭后幂等)。 */
44
+ dispose(): void;
45
+ }
46
+ interface EngineSelfDestructOptions {
47
+ /** 主判据数据源:宿主→引擎的协议 stdin(EOF = 宿主死亡 → 立即自灭)。 */
48
+ stdin: NodeJS.ReadableStream;
49
+ /** 辅助判据超时(缺省 30s;显式传值优先于 env XYZ_ENGINE_HOST_REQUEST_TIMEOUT_MS)。 */
50
+ hostRequestTimeoutMs?: number;
51
+ /** 计时超时的 env 覆盖源(缺省读 process.env;测试注入用)。 */
52
+ timeoutEnv?: Record<string, string | undefined>;
53
+ /** 自灭执行器(缺省 = 杀自己进程组再杀自己;测试注入用)。 */
54
+ killSelf?: () => void;
55
+ }
56
+ /**
57
+ * 武装引擎侧自灭守卫。返回 ReverseRequestClock 给反向通道分发层登记请求生命周期。
58
+ *
59
+ * 判据:
60
+ * - 主判据 stdio EOF:stdin 'end'(数据面读尽)或 'close'(fd 关闭)→ 立即自灭
61
+ * (宿主死后无人再写协议帧,引擎继续跑只烧 token);
62
+ * - 辅助判据 in-flight 反向请求计时:未 ack 的反向请求超过 hostRequestTimeoutMs
63
+ * 仍无 ack → 宿主大概率已死(EOF 未达或正在关闭)→ 自灭。**排除面**:已 ack 的
64
+ * host/askUser 与两阶段 ack 的长运行 HostBridge.executeAndAwait(ack 后等待任意
65
+ * 时长合法,漏此项即「>30s pi 任务被自灭」turnTimeoutMs 同型事故)。
66
+ *
67
+ * 自灭执行 = 引擎自杀并杀自己进程组(spawnEngineChild 硬编码 detached:false,
68
+ * 任务子进程同组连带收割)。
69
+ */
70
+ declare function armEngineSelfDestruct(opts: EngineSelfDestructOptions): ReverseRequestClock;
71
+ /**
72
+ * 流式 NDJSON 行泵:utf8 解码 + 缓冲累积 + "\n" 拆行循环(S4 簇 5b 收编:pi
73
+ * spawn-run-pump 与 zcode connection 的逐字同构段单源化,实现以 pi 版为基线)。
74
+ * 末行无换行符时滞留缓冲(NDJSON 语义:行以 \n 定界);不监听 end/close/error——
75
+ * 生命周期接线(close 收尾/error 兜底)由调用方自行挂接。stream 为 null/undefined
76
+ * (无 stdio)时无操作,与原调用点 `child.stdout?.` 可选链语义等价。
77
+ */
78
+ declare function pumpNdjsonLines(stream: NodeJS.ReadableStream | null | undefined, onLine: (line: string) => void): void;
79
+
80
+ export { DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS, ENGINE_HOST_REQUEST_TIMEOUT_ENV, type EngineChildEnv, type EngineSelfDestructOptions, type ReverseRequestClock, type SpawnEngineChildOptions, armEngineSelfDestruct, pumpNdjsonLines, spawnEngineChild };
@@ -0,0 +1,80 @@
1
+ import { ChildProcess } from 'node:child_process';
2
+ import { buildEngineChildEnv } from './env.js';
3
+
4
+ /** spawnEngineChild 的 env 必须来自 buildEngineChildEnv(三层契约终态)。 */
5
+ type EngineChildEnv = ReturnType<typeof buildEngineChildEnv>;
6
+ /** 引擎子进程 spawn 选项(刻意不暴露 detached / stdio / stdin——形态硬编码,见文件头)。 */
7
+ interface SpawnEngineChildOptions {
8
+ command: string;
9
+ args: readonly string[];
10
+ /** 须由 buildEngineChildEnv 构建(类型即契约:三层 env 终态) */
11
+ env: EngineChildEnv;
12
+ cwd?: string;
13
+ /** 子进程 stdout/stderr 形态;缺省 'pipe'(协议帧走 stdout,stderr 走日志/轮转) */
14
+ stdout?: "pipe" | "ignore" | "inherit";
15
+ stderr?: "pipe" | "ignore" | "inherit";
16
+ /** AbortSignal 触发即 SIGTERM 杀子(缺省无) */
17
+ signal?: AbortSignal;
18
+ }
19
+ /**
20
+ * 引擎任务子进程唯一 spawn 入口。
21
+ *
22
+ * 形态硬编码:detached:false、windowsHide:true、子进程 stdin 恒 'pipe'(自有 pipe,
23
+ * 绝不继承引擎自身 stdin fd)。传入被禁止的形态键(detached/stdio/stdin/windowsHide)
24
+ * 直接抛错——契约破坏应在配置期出声,不静默降级。
25
+ */
26
+ declare function spawnEngineChild(opts: SpawnEngineChildOptions): ChildProcess;
27
+ /** in-flight 未 ack 反向请求的计时超时缺省值(辅助判据,30s)。 */
28
+ declare const DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS = 30000;
29
+ /** 计时超时覆盖 env 名(引擎 CLI 启动时读;非法值 warn + 回落缺省)。 */
30
+ declare const ENGINE_HOST_REQUEST_TIMEOUT_ENV = "XYZ_ENGINE_HOST_REQUEST_TIMEOUT_MS";
31
+ /** 反向请求计时面操作接口(消费方 = 引擎 CLI 的反向通道分发层)。 */
32
+ interface ReverseRequestClock {
33
+ /** 反向请求已发出(未 ack)——进入计时面。 */
34
+ started(id: string): void;
35
+ /**
36
+ * 收到 ack(两阶段第一阶段:已 ack 的 host/askUser、HostBridge.executeAndAwait
37
+ * 的 ack)——移出计时面但不终结(R9-2:已 ack 的等待不计入 in-flight 超时,
38
+ * ADR-0047 静默 ≠ 卡死)。
39
+ */
40
+ acked(id: string): void;
41
+ /** 请求终结(应答完成/失败/取消)——彻底移出。 */
42
+ settled(id: string): void;
43
+ /** 卸载守卫(引擎正常 shutdown 路径;自灭后幂等)。 */
44
+ dispose(): void;
45
+ }
46
+ interface EngineSelfDestructOptions {
47
+ /** 主判据数据源:宿主→引擎的协议 stdin(EOF = 宿主死亡 → 立即自灭)。 */
48
+ stdin: NodeJS.ReadableStream;
49
+ /** 辅助判据超时(缺省 30s;显式传值优先于 env XYZ_ENGINE_HOST_REQUEST_TIMEOUT_MS)。 */
50
+ hostRequestTimeoutMs?: number;
51
+ /** 计时超时的 env 覆盖源(缺省读 process.env;测试注入用)。 */
52
+ timeoutEnv?: Record<string, string | undefined>;
53
+ /** 自灭执行器(缺省 = 杀自己进程组再杀自己;测试注入用)。 */
54
+ killSelf?: () => void;
55
+ }
56
+ /**
57
+ * 武装引擎侧自灭守卫。返回 ReverseRequestClock 给反向通道分发层登记请求生命周期。
58
+ *
59
+ * 判据:
60
+ * - 主判据 stdio EOF:stdin 'end'(数据面读尽)或 'close'(fd 关闭)→ 立即自灭
61
+ * (宿主死后无人再写协议帧,引擎继续跑只烧 token);
62
+ * - 辅助判据 in-flight 反向请求计时:未 ack 的反向请求超过 hostRequestTimeoutMs
63
+ * 仍无 ack → 宿主大概率已死(EOF 未达或正在关闭)→ 自灭。**排除面**:已 ack 的
64
+ * host/askUser 与两阶段 ack 的长运行 HostBridge.executeAndAwait(ack 后等待任意
65
+ * 时长合法,漏此项即「>30s pi 任务被自灭」turnTimeoutMs 同型事故)。
66
+ *
67
+ * 自灭执行 = 引擎自杀并杀自己进程组(spawnEngineChild 硬编码 detached:false,
68
+ * 任务子进程同组连带收割)。
69
+ */
70
+ declare function armEngineSelfDestruct(opts: EngineSelfDestructOptions): ReverseRequestClock;
71
+ /**
72
+ * 流式 NDJSON 行泵:utf8 解码 + 缓冲累积 + "\n" 拆行循环(S4 簇 5b 收编:pi
73
+ * spawn-run-pump 与 zcode connection 的逐字同构段单源化,实现以 pi 版为基线)。
74
+ * 末行无换行符时滞留缓冲(NDJSON 语义:行以 \n 定界);不监听 end/close/error——
75
+ * 生命周期接线(close 收尾/error 兜底)由调用方自行挂接。stream 为 null/undefined
76
+ * (无 stdio)时无操作,与原调用点 `child.stdout?.` 可选链语义等价。
77
+ */
78
+ declare function pumpNdjsonLines(stream: NodeJS.ReadableStream | null | undefined, onLine: (line: string) => void): void;
79
+
80
+ export { DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS, ENGINE_HOST_REQUEST_TIMEOUT_ENV, type EngineChildEnv, type EngineSelfDestructOptions, type ReverseRequestClock, type SpawnEngineChildOptions, armEngineSelfDestruct, pumpNdjsonLines, spawnEngineChild };
package/dist/spawn.js ADDED
@@ -0,0 +1,16 @@
1
+ import {
2
+ DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS,
3
+ ENGINE_HOST_REQUEST_TIMEOUT_ENV,
4
+ armEngineSelfDestruct,
5
+ pumpNdjsonLines,
6
+ spawnEngineChild
7
+ } from "./chunk-3K2P2CM2.js";
8
+ import "./chunk-75QEMUGV.js";
9
+ import "./chunk-JSBRDJBE.js";
10
+ export {
11
+ DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS,
12
+ ENGINE_HOST_REQUEST_TIMEOUT_ENV,
13
+ armEngineSelfDestruct,
14
+ pumpNdjsonLines,
15
+ spawnEngineChild
16
+ };
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/ui-channels.ts
21
+ var ui_channels_exports = {};
22
+ __export(ui_channels_exports, {
23
+ createUiChannelRegistry: () => createUiChannelRegistry,
24
+ parseChannel: () => parseChannel
25
+ });
26
+ module.exports = __toCommonJS(ui_channels_exports);
27
+ var NUL = "\0";
28
+ function normalizeChannelName(markerLiteral) {
29
+ let name = markerLiteral;
30
+ if (name.startsWith("XYZ_")) {
31
+ name = name.slice("XYZ_".length);
32
+ }
33
+ if (name.endsWith(":")) {
34
+ name = name.slice(0, -1);
35
+ }
36
+ return name.toLowerCase();
37
+ }
38
+ function parseMarkerFromField(str) {
39
+ if (!str.startsWith(NUL)) return void 0;
40
+ const literal = str.slice(NUL.length);
41
+ if (literal === "") return void 0;
42
+ return normalizeChannelName(literal);
43
+ }
44
+ function parseInlineMarkerFromField(str) {
45
+ if (!str.startsWith(NUL)) return void 0;
46
+ const rest = str.slice(NUL.length);
47
+ const colonIdx = rest.indexOf(":");
48
+ let literal;
49
+ if (colonIdx >= 0) {
50
+ literal = rest.slice(0, colonIdx + 1);
51
+ } else {
52
+ literal = rest;
53
+ }
54
+ if (literal === "") return void 0;
55
+ return { channel: normalizeChannelName(literal) };
56
+ }
57
+ function parseFromMarkerString(title, options) {
58
+ if (title === void 0) return {};
59
+ const channel = parseMarkerFromField(title);
60
+ if (channel === void 0) return {};
61
+ let payload;
62
+ if (options !== void 0 && options.length > 0) {
63
+ try {
64
+ payload = JSON.parse(options[0]);
65
+ } catch {
66
+ payload = void 0;
67
+ }
68
+ }
69
+ return { channel, channelPayload: payload };
70
+ }
71
+ function parseFromMarkerArray(widgetLines) {
72
+ if (widgetLines === void 0 || widgetLines.length === 0) return {};
73
+ const firstLine = widgetLines[0];
74
+ if (typeof firstLine !== "string") return {};
75
+ const parsed = parseInlineMarkerFromField(firstLine);
76
+ if (parsed === void 0) return {};
77
+ let payload;
78
+ const rest = firstLine.slice(NUL.length);
79
+ const colonIdx = rest.indexOf(":");
80
+ if (colonIdx >= 0) {
81
+ const jsonStr = rest.slice(colonIdx + 1);
82
+ if (jsonStr !== "") {
83
+ try {
84
+ payload = JSON.parse(jsonStr);
85
+ } catch {
86
+ payload = void 0;
87
+ }
88
+ }
89
+ }
90
+ return { channel: parsed.channel, channelPayload: payload };
91
+ }
92
+ function parseChannel(req) {
93
+ switch (req.method) {
94
+ case "select":
95
+ return parseFromMarkerString(req.title, req.options);
96
+ case "setWidget":
97
+ return parseFromMarkerArray(req.widgetLines);
98
+ default:
99
+ return {};
100
+ }
101
+ }
102
+ function createUiChannelRegistry() {
103
+ const handlers = /* @__PURE__ */ new Map();
104
+ return {
105
+ register(channel, handler) {
106
+ handlers.set(channel, handler);
107
+ },
108
+ resolve(channel) {
109
+ return handlers.get(channel);
110
+ },
111
+ list() {
112
+ return Array.from(handlers.keys());
113
+ }
114
+ };
115
+ }
116
+ // Annotate the CommonJS export names for ESM import in node:
117
+ 0 && (module.exports = {
118
+ createUiChannelRegistry,
119
+ parseChannel
120
+ });
@@ -0,0 +1,60 @@
1
+ /** channel 提取结果。channel 无 NUL 前缀、字段缺失、JSON parse 失败时
2
+ * channel 与 channelPayload 均为 undefined(返回 {})。 */
3
+ interface ParsedChannel {
4
+ /** 规范化后的 channel 名(如 "ask_user"、"gui_widget")。
5
+ * 无 marker 或解析失败时为 undefined。 */
6
+ channel?: string;
7
+ /** marker 标记的结构化 payload(已 JSON.parse)。
8
+ * ask_user: {questions, allowCancel};gui_widget: {component}。
9
+ * payload 来源缺失或 JSON parse 失败时为 undefined(channel 仍可解析)。 */
10
+ channelPayload?: unknown;
11
+ }
12
+ /** parseChannel 入参的最小形状。
13
+ * method 是判别字段;按 method 不同,对应字段(select 的 title/options、
14
+ * setWidget 的 widgetLines)可选出现。其他 method 的字段统称 [key:string]。 */
15
+ interface ExtensionUiRequestLike {
16
+ method: string;
17
+ /** select method:title 字段(可能含 ASK_USER_MARKER NUL 前缀)。 */
18
+ title?: string;
19
+ /** select method:options 数组(options[0] 可能是 channel payload 的 JSON)。 */
20
+ options?: string[];
21
+ /** setWidget method:widgetKey 字段。 */
22
+ widgetKey?: string;
23
+ /** setWidget method:widgetLines 数组(widgetLines[0] 可能含 GUI_WIDGET_MARKER)。 */
24
+ widgetLines?: string[] | undefined;
25
+ /** 其他 method 的任意字段(容错:允许测试和未来扩展传入额外字段)。 */
26
+ [key: string]: unknown;
27
+ }
28
+ /** channel handler 签名:接收 UiRequest,返回 UiResponse。
29
+ * 具体类型定义在 session-runner.ts(W2 工作),此处用最小形状避免循环依赖。
30
+ * handler 实现方按 channel 注册,由 session-runner 按 req.channel 分派。 */
31
+ type ChannelHandler = (req: unknown) => Promise<unknown>;
32
+ /** channel 注册表接口。职责单一:只管业务路由,不管排队、不管透传判定。
33
+ * - register(channel, handler):注册 channel 对应的 handler(同名覆盖)
34
+ * - resolve(channel):取 channel 对应的 handler,未注册返回 undefined
35
+ * - list():列举所有已注册 channel 名 */
36
+ interface UiChannelRegistry {
37
+ register(channel: string, handler: ChannelHandler): void;
38
+ resolve(channel: string): ChannelHandler | undefined;
39
+ list(): string[];
40
+ }
41
+ /** 按 method 分派解析 channel。
42
+ *
43
+ * - select → 从 title 解析 NUL 前缀(payload 从 options[0])
44
+ * - setWidget → 从 widgetLines[0] 解析 NUL 前缀(payload 从同行 marker 后)
45
+ * - 其他 method → {}(无 channel 提取位置)
46
+ *
47
+ * 边界(均不抛错):
48
+ * - title/widgetLines 字段缺失 → {}
49
+ * - 无 NUL 前缀 → {}
50
+ * - JSON parse 失败 → channel 仍解析,channelPayload 为 undefined
51
+ *
52
+ * @param req ExtensionUiRequestLike(method + 对应字段)
53
+ * @returns ParsedChannel(channel/channelPayload 可选) */
54
+ declare function parseChannel(req: ExtensionUiRequestLike): ParsedChannel;
55
+ /** 创建 channel 注册表实例。
56
+ * 进程级单例(通常由 SubagentService 持有一个实例,跨所有子进程共享)。
57
+ * register 同名 channel 会覆盖旧 handler。 */
58
+ declare function createUiChannelRegistry(): UiChannelRegistry;
59
+
60
+ export { type ChannelHandler, type ExtensionUiRequestLike, type ParsedChannel, type UiChannelRegistry, createUiChannelRegistry, parseChannel };