@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,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 };
@@ -0,0 +1,9 @@
1
+ import {
2
+ createUiChannelRegistry,
3
+ parseChannel
4
+ } from "./chunk-7I4XGL5J.js";
5
+ import "./chunk-JSBRDJBE.js";
6
+ export {
7
+ createUiChannelRegistry,
8
+ parseChannel
9
+ };
@@ -0,0 +1,18 @@
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 __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/ui-types.ts
17
+ var ui_types_exports = {};
18
+ module.exports = __toCommonJS(ui_types_exports);
@@ -0,0 +1,62 @@
1
+ /** Pi extension_ui_request 的方法枚举(dialog + fire-and-forget 两类)。
2
+ * dialog 类:select/confirm/input/editor(占输入焦点,等响应)。
3
+ * fire-and-forget 类:notify/setStatus/setWidget/setTitle/set_editor_text(纯展示/写入)。
4
+ * (string & {}) 兜底:Pi 未来新增 method 或未知 method 走字符串字面量类型。 */
5
+ type UiMethod = "select" | "confirm" | "input" | "editor" | "notify" | "setStatus" | "setWidget" | "setTitle" | "set_editor_text" | (string & {});
6
+ /** UI 请求(session-runner 构造后传给 handler)。
7
+ *
8
+ * method 是判别字段,决定排队策略(dialog 排队)和业务路由(channel 分发)。
9
+ * method 特定字段按 method 可选出现(与 ExtensionUiRequest 1:1,由 session-runner 从
10
+ * ExtensionUiRequest 平铺构造)。channel/channelPayload 由 parseChannel 填充。
11
+ *
12
+ * 契约来源:.fix-plans/00-master-summary.md §二 2.2。 */
13
+ interface UiRequest {
14
+ /** Pi rpc-types.ts 的 method(select/confirm/input/editor 为 dialog 类)。 */
15
+ method: UiMethod;
16
+ /** 请求 id(从 extension_ui_request envelope 顶层提取,用于 response 关联)。 */
17
+ id: string;
18
+ title?: string;
19
+ options?: string[];
20
+ message?: string;
21
+ placeholder?: string;
22
+ prefill?: string;
23
+ notifyType?: string;
24
+ statusKey?: string;
25
+ statusText?: string | undefined;
26
+ widgetKey?: string;
27
+ widgetLines?: string[] | undefined;
28
+ widgetPlacement?: "aboveEditor" | "belowEditor";
29
+ text?: string;
30
+ timeout?: number;
31
+ /** channel 名(从 method 对应字段的 NUL 前缀解析)。
32
+ * select → 从 title 解析;setWidget → 从 widgetLines[0] 解析;其他 → undefined。
33
+ * 已知值:"ask_user"(select)、"gui_widget"(setWidget)。handler 按 channel 分发。 */
34
+ channel?: string;
35
+ /** channel 解析后的结构化 payload(已 JSON.parse)。
36
+ * ask_user: {questions, allowCancel};gui_widget: {component};无 channel: undefined。 */
37
+ channelPayload?: unknown;
38
+ /** 内部元数据字段:发起该 UI 请求的子进程 pid(由 session-runner.handleUiRequest 从
39
+ * child.pid 填入)。L2 队列据此关联 rejectChildDialogs(child close 时批量 reject)。
40
+ * 下划线前缀表示内部字段,非 Pi 协议字段,不参与 stdin 回写。 */
41
+ _childPid?: number;
42
+ }
43
+ /** UI 响应(handler 返回,session-runner 按 shape 回写 stdin)。
44
+ * - {value}: select/input/editor 的答案
45
+ * - {confirmed}: confirm 的答案
46
+ * - {cancelled}: 取消(child close / handler 抛错 / 用户取消)
47
+ * - {ack}: fire-and-forget(当前不透传到 TUI,留作协议完整) */
48
+ type UiResponse = {
49
+ value: string;
50
+ } | {
51
+ confirmed: boolean;
52
+ } | {
53
+ cancelled: true;
54
+ } | {
55
+ ack: true;
56
+ };
57
+ /** UI 请求 handler 签名(单函数,按 req.method 内部路由)。
58
+ * 实现方负责:channel 业务路由(ask_user → AskUserComponent)+ 默认转发(ctx.ui.*)。
59
+ * 抛错由调用方(DialogGlobalQueue / session-runner)兜底为 {cancelled:true}。 */
60
+ type UiRequestHandler = (req: UiRequest) => Promise<UiResponse>;
61
+
62
+ export type { UiMethod, UiRequest, UiRequestHandler, UiResponse };
@@ -0,0 +1,62 @@
1
+ /** Pi extension_ui_request 的方法枚举(dialog + fire-and-forget 两类)。
2
+ * dialog 类:select/confirm/input/editor(占输入焦点,等响应)。
3
+ * fire-and-forget 类:notify/setStatus/setWidget/setTitle/set_editor_text(纯展示/写入)。
4
+ * (string & {}) 兜底:Pi 未来新增 method 或未知 method 走字符串字面量类型。 */
5
+ type UiMethod = "select" | "confirm" | "input" | "editor" | "notify" | "setStatus" | "setWidget" | "setTitle" | "set_editor_text" | (string & {});
6
+ /** UI 请求(session-runner 构造后传给 handler)。
7
+ *
8
+ * method 是判别字段,决定排队策略(dialog 排队)和业务路由(channel 分发)。
9
+ * method 特定字段按 method 可选出现(与 ExtensionUiRequest 1:1,由 session-runner 从
10
+ * ExtensionUiRequest 平铺构造)。channel/channelPayload 由 parseChannel 填充。
11
+ *
12
+ * 契约来源:.fix-plans/00-master-summary.md §二 2.2。 */
13
+ interface UiRequest {
14
+ /** Pi rpc-types.ts 的 method(select/confirm/input/editor 为 dialog 类)。 */
15
+ method: UiMethod;
16
+ /** 请求 id(从 extension_ui_request envelope 顶层提取,用于 response 关联)。 */
17
+ id: string;
18
+ title?: string;
19
+ options?: string[];
20
+ message?: string;
21
+ placeholder?: string;
22
+ prefill?: string;
23
+ notifyType?: string;
24
+ statusKey?: string;
25
+ statusText?: string | undefined;
26
+ widgetKey?: string;
27
+ widgetLines?: string[] | undefined;
28
+ widgetPlacement?: "aboveEditor" | "belowEditor";
29
+ text?: string;
30
+ timeout?: number;
31
+ /** channel 名(从 method 对应字段的 NUL 前缀解析)。
32
+ * select → 从 title 解析;setWidget → 从 widgetLines[0] 解析;其他 → undefined。
33
+ * 已知值:"ask_user"(select)、"gui_widget"(setWidget)。handler 按 channel 分发。 */
34
+ channel?: string;
35
+ /** channel 解析后的结构化 payload(已 JSON.parse)。
36
+ * ask_user: {questions, allowCancel};gui_widget: {component};无 channel: undefined。 */
37
+ channelPayload?: unknown;
38
+ /** 内部元数据字段:发起该 UI 请求的子进程 pid(由 session-runner.handleUiRequest 从
39
+ * child.pid 填入)。L2 队列据此关联 rejectChildDialogs(child close 时批量 reject)。
40
+ * 下划线前缀表示内部字段,非 Pi 协议字段,不参与 stdin 回写。 */
41
+ _childPid?: number;
42
+ }
43
+ /** UI 响应(handler 返回,session-runner 按 shape 回写 stdin)。
44
+ * - {value}: select/input/editor 的答案
45
+ * - {confirmed}: confirm 的答案
46
+ * - {cancelled}: 取消(child close / handler 抛错 / 用户取消)
47
+ * - {ack}: fire-and-forget(当前不透传到 TUI,留作协议完整) */
48
+ type UiResponse = {
49
+ value: string;
50
+ } | {
51
+ confirmed: boolean;
52
+ } | {
53
+ cancelled: true;
54
+ } | {
55
+ ack: true;
56
+ };
57
+ /** UI 请求 handler 签名(单函数,按 req.method 内部路由)。
58
+ * 实现方负责:channel 业务路由(ask_user → AskUserComponent)+ 默认转发(ctx.ui.*)。
59
+ * 抛错由调用方(DialogGlobalQueue / session-runner)兜底为 {cancelled:true}。 */
60
+ type UiRequestHandler = (req: UiRequest) => Promise<UiResponse>;
61
+
62
+ export type { UiMethod, UiRequest, UiRequestHandler, UiResponse };
@@ -0,0 +1 @@
1
+ import "./chunk-2DIMPZCQ.js";
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@zhushanwen/subagent-engine-sdk",
3
+ "version": "0.2.0",
4
+ "description": "Engine-side SDK for the subagent engine protocol: NDJSON stdio contract types, engine primitives (schema emulation, nesting guard, kill chain, journal replay projection, data-dir/paths resolution) — the shared contract root between subagent-core and engine CLI packages (engines import SDK only, never core)",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
10
+ "main": "dist/index.cjs",
11
+ "module": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "require": {
20
+ "types": "./dist/index.d.cts",
21
+ "default": "./dist/index.cjs"
22
+ },
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./protocol": {
26
+ "import": {
27
+ "types": "./dist/protocol/index.d.ts",
28
+ "default": "./dist/protocol/index.js"
29
+ },
30
+ "require": {
31
+ "types": "./dist/protocol/index.d.cts",
32
+ "default": "./dist/protocol/index.cjs"
33
+ },
34
+ "default": "./dist/protocol/index.js"
35
+ }
36
+ },
37
+ "$comment": "W1 骨架(docs/design/subagent-engine-protocolization.impl-plan.md §2.1)。双形态同 subagent-core 先例(D4):workspace 消费走 import 条件 -> src;npm 消费走 publishConfig -> dist。exports 面先收窄到 ./ 与 ./protocol(exports = semver 契约,收窄不放宽;引擎包 W5/W7 消费经主 barrel)。协议版本独立于包版本:ENGINE_PROTOCOL_VERSION = 1(src/protocol/engine-protocol.ts)。不变量:本包禁止 import @zhushanwen/subagent-core(守卫 .githooks/check-engine-sdk-boundary.mjs)。src/env.ts 与 src/spawn.ts 是 W12 领地(impl-plan §2.12),本单元仅空骨架占位。tsup entry 逐模块具名登记(§2.1 末行),新增模块必须同步登记。",
38
+ "files": [
39
+ "src/",
40
+ "!src/__tests__",
41
+ "dist/"
42
+ ],
43
+ "dependencies": {
44
+ "ajv": "^8.20.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^24.0.0",
48
+ "@vitest/coverage-v8": "^4.1.9",
49
+ "tsup": "^8.5.1",
50
+ "typescript": "^5.7.0",
51
+ "vitest": "^4.1.8"
52
+ },
53
+ "scripts": {
54
+ "build": "tsup",
55
+ "test": "vitest run",
56
+ "typecheck": "tsc --noEmit"
57
+ }
58
+ }
@@ -0,0 +1,37 @@
1
+ // src/best-effort.ts
2
+ //
3
+ // best-effort IO 清理的错误吞咽 helper 单源(round1-reuse R11 微副本收编,实现体
4
+ // 自 core src/execution/best-effort.ts 迁移;pi 引擎包 best-effort.ts 副本自本模块
5
+ // re-export 收编。core 版保留自持:其 logger 经 configureCore HostServices 通道解析,
6
+ // 与 SDK LoggerSink 通道是真差异,非可消副本)。
7
+ //
8
+ // 用途:sidecar 写入 / worktree remove / alive marker 删除等次要 IO,失败不影响
9
+ // 主流程(session 已完成或正在收尾)。这类 catch 故意吞错——但 taste/no-silent-catch
10
+ // 规则禁止空 catch 或仅 console 的 catch。本 helper 提供一条「实质调用语句」让
11
+ // catch 合规,同时把错误记录到 debug/error 便于排查(经共享 logger 路由,不裸 console)。
12
+
13
+ import { getLogger } from "./logger.ts";
14
+
15
+ const logger = getLogger("subagents");
16
+
17
+ /** 错误日志级别。debug = 次要清理(默认);error = 关键步骤但需继续后续清理。 */
18
+ export type BestEffortLevel = "debug" | "error";
19
+
20
+ /**
21
+ * 吞咽 best-effort IO 的错误,按 level 经共享 logger 记录。
22
+ *
23
+ * - debug(默认):次要清理(sidecar/worktree/alive marker),失败属预期路径
24
+ * - error:关键步骤抛错但需继续后续清理(如 finalizeRecord 的 B9 链:completeRecord
25
+ * 抛错后仍要执行 finalized/cleanup,错误需可见但不阻断)
26
+ *
27
+ * 错误对象优先取 message(避免打印巨大堆栈/对象),其他类型原样传入。
28
+ */
29
+ export function bestEffort(err: unknown, context: string, level: BestEffortLevel = "debug"): void {
30
+ const detail = err instanceof Error ? err.message : err;
31
+ const msg = `[subagents] best-effort ${context} failed`;
32
+ if (level === "error") {
33
+ logger.error(msg, { detail });
34
+ } else {
35
+ logger.debug(msg, { detail });
36
+ }
37
+ }
@@ -0,0 +1,77 @@
1
+ // src/cli-entry.ts
2
+ //
3
+ // 引擎 CLI 进程入口样板单源(S4 簇 2 收编:pi/zcode 两包 main.ts 的逐字同构段
4
+ // 迁移——差异仅 component 字符串与各包自己的 EngineProtocolServer;实现以 pi
5
+ // main.ts 为基线逐字迁移,坏行截断长度 200、日志格式、启动序逐字节保持)。
6
+ //
7
+ // 职界分割:armEngineSelfDestruct 与 EngineProtocolServer 构造留调用方薄层——
8
+ // server 构造需要 arm 产出的 ReverseRequestClock,实例传入后无法回补。本入口承接
9
+ // 其余启动序:configureLoggerSink(stderr 兜底 + host/log 反向请求)→ readline
10
+ // NDJSON 行循环 → close 留痕。
11
+ //
12
+ // stdout/stdin 用 process 全局(与 spawn.ts 同款先例——入口函数只在进程入口调用)。
13
+ // 进程退出面:宿主 stdin EOF → 自灭守卫(薄层 arm)杀进程组;正常 dispose 不退进程。
14
+
15
+ import * as readline from "node:readline";
16
+
17
+ import { configureLoggerSink, getLogger } from "./logger.ts";
18
+
19
+ /**
20
+ * 引擎协议服务器的结构化最小接口(调用方薄层注入本包 EngineProtocolServer 实例;
21
+ * 结构子集避免 SDK 反向依赖各包 server 类型)。
22
+ */
23
+ export interface CliProtocolServerSink {
24
+ /** 入站协议帧处理(readline 已拆行 + JSON.parse 后的帧)。 */
25
+ handleFrame(frame: unknown): void;
26
+ /** 数据面反向请求(host/log 等;应答分类由各包 server 实现承载)。 */
27
+ reverseRequest(method: string, params: unknown): Promise<unknown>;
28
+ }
29
+
30
+ /** runEngineCliEntry 参数。 */
31
+ export interface RunEngineCliEntryOptions {
32
+ /** logger component 标签(如 "pi-engine-cli" / "zcode-engine-cli")。 */
33
+ component: string;
34
+ /** 调用方构造的协议服务器实例(构造参数含 armEngineSelfDestruct 产出的 reverseClock)。 */
35
+ server: CliProtocolServerSink;
36
+ }
37
+
38
+ /** 坏行进日志的截断长度(够诊断、不刷屏)。 */
39
+ const INBOUND_LOG_CHARS = 200;
40
+
41
+ /**
42
+ * 引擎 CLI 进程入口样板(行为与收编前两包 main.ts 逐字节等价):
43
+ * configureLoggerSink → stdin 行解析(NDJSON)→ server.handleFrame → close 留痕。
44
+ */
45
+ export function runEngineCliEntry(opts: RunEngineCliEntryOptions): void {
46
+ const logger = getLogger(opts.component);
47
+ const server = opts.server;
48
+
49
+ // stdout 是协议独占通道:应用日志一律走 host/log 反向请求 + stderr 兜底,禁写 stdout。
50
+ configureLoggerSink({
51
+ log(level, component, message, data) {
52
+ // stderr 兜底先落(反向通道未就绪/失败时日志不丢);host/log 为数据面反向请求
53
+ // (10s 应答分类),失败吞掉——日志面不能拖垮主链路。
54
+ process.stderr.write(`[${level}] [${component}] ${message}${data !== undefined ? ` ${JSON.stringify(data)}` : ""}\n`);
55
+ void server.reverseRequest("host/log", { level, component, message }).catch(() => undefined);
56
+ },
57
+ });
58
+
59
+ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
60
+ rl.on("line", (line) => {
61
+ const text = line.trim();
62
+ if (text === "") return;
63
+ try {
64
+ server.handleFrame(JSON.parse(text));
65
+ } catch (err) {
66
+ // 坏行跳过不断流(对端 EngineClient 同款纪律);stdout 协议通道不回显坏行。
67
+ logger.warn(`unparseable inbound protocol line (ignored): ${text.slice(0, INBOUND_LOG_CHARS)}`, {
68
+ reason: err instanceof Error ? err.message : String(err),
69
+ });
70
+ }
71
+ });
72
+ rl.on("close", () => {
73
+ // stdin EOF:自灭守卫已挂(armEngineSelfDestruct 的 once('close') 杀进程组);
74
+ // 此处仅留痕(守卫先注册,杀链不依赖本 handler)。
75
+ logger.debug("stdin closed (host exit path)");
76
+ });
77
+ }
@@ -0,0 +1,88 @@
1
+ // src/data-dir.ts
2
+ //
3
+ // 引擎数据目录解析(引擎侧原语,自 core execution/engine/common/data-dir.ts 迁入
4
+ // @zhushanwen/subagent-engine-sdk)。迁移处置(impl-plan §2.1 data-dir 行):**拆
5
+ // seam**——今天 core 版缺 env 时回退 getHostServices().dataRoot()(引擎进程无
6
+ // configureCore 通道,不可复制);SDK 版 = **参数化/env 优先**,core 侧保留
7
+ // getHostServices() 绑定(core 文件不动,引用切换归 W2+)。
8
+ //
9
+ // SDK 版缺省语义(impl-plan §2.9 数据根注入矩阵,写死):env 优先;缺 env 且调用方
10
+ // 未显式注入 fallback → **显式报错**(错误信息可操作:env 名 + 期望注入动作),不留
11
+ // 静默漂目录。带 fallback 注入(pi 扩展宿主 = core 侧解析后显式传入)时保留
12
+ // warn-once 语义(回退非权威通道必须可观测),与 core 版行为逐字对齐。
13
+ //
14
+ // 通道调查结论(2026-08-25,P2 实测证据链,自 core 版头部保留):
15
+ // - runtime spawn pi 主会话时 RpcClient.buildSafeEnv 从 runtime process.env 继承
16
+ // XYZ_ 前缀白名单变量(rpc-client.ts:14-30),且显式设置 PI_CODING_AGENT_DIR;
17
+ // - dev 模式 Electron main 设置 XYZ_AGENT_DATA_DIR(apps/electron/main/main.ts:122),
18
+ // 经 process-control.ts:262 透传给 runtime → 透传链成立;
19
+ // - prod 默认路径(~/.xyz-agent 缺省)下 runtime 进程 env 中**没有**该变量
20
+ // → runtime 侧 process-manager.ts spawn env 补注入 XYZ_AGENT_DATA_DIR 修复。
21
+
22
+ import { getLogger } from "./logger.ts";
23
+
24
+ const logger = getLogger("subagents");
25
+
26
+ /** xyz-agent 数据目录 env 名(与 packages/shared/src/paths.ts 的 SSOT 变量同名)。 */
27
+ export const XYZ_DATA_DIR_ENV = "XYZ_AGENT_DATA_DIR";
28
+
29
+ // warn-once 配置态:globalThis[Symbol.for] slot(core host-services 同款范式)——
30
+ // CJS 多 entry 内联副本下模块级 `let` 会分裂成多份(每份各 warn 一次),slot 跨副本一致。
31
+ const WARNED_SLOT_KEY = Symbol.for("@zhushanwen/subagent-engine-sdk.data-dir-warned");
32
+
33
+ function getWarnedSlot(): { current: boolean } {
34
+ let slot = Reflect.get(globalThis, WARNED_SLOT_KEY) as { current: boolean } | undefined;
35
+ if (!slot) {
36
+ slot = { current: false };
37
+ Reflect.set(globalThis, WARNED_SLOT_KEY, slot);
38
+ }
39
+ return slot;
40
+ }
41
+
42
+ /** 测试隔离专用:重置 warn-once 标记(生产禁用)。 */
43
+ export function resetDataDirWarnForTests(): void {
44
+ getWarnedSlot().current = false;
45
+ }
46
+
47
+ /**
48
+ * 解析引擎数据目录(journal 落盘 / 隔离池的根):
49
+ * 1. env[XYZ_AGENT_DATA_DIR](宿主注入,权威通道);
50
+ * 2. 缺失 + opts.fallbackDataRoot 显式传入(宿主解析过的数据根)→ 用之 + warn 一次
51
+ * (非权威通道回退必须可观测——journal 落点变化不留静默漂移);
52
+ * 3. 两者皆缺 → 抛错(engine_not_found 语义:错误含 env 名、期望值示例与恢复动作;
53
+ * impl-plan §2.9「缺 env 且无注入 → 显式报错(附期望路径)」)。
54
+ *
55
+ * 每次调用重新解析(env 读取是零成本操作,不缓存路径防测试/宿主切换时读到旧值)。
56
+ */
57
+ export function resolveEngineDataDir(
58
+ env: NodeJS.ProcessEnv,
59
+ opts: { fallbackDataRoot?: string; warn?: (msg: string) => void } = {},
60
+ ): string {
61
+ const fromEnv = env[XYZ_DATA_DIR_ENV]?.trim();
62
+ if (fromEnv !== undefined && fromEnv !== "") return fromEnv;
63
+
64
+ const fallback = opts.fallbackDataRoot?.trim();
65
+ if (fallback === undefined || fallback === "") {
66
+ throw new Error(
67
+ `[subagent-engine-sdk] engine_not_found: ${XYZ_DATA_DIR_ENV} is not set and no fallback data root was injected. ` +
68
+ `Recovery: the host must inject ${XYZ_DATA_DIR_ENV} into the engine process env ` +
69
+ `(xyz-agent runtime / pi extension host do this at spawn), or pass ` +
70
+ `{ fallbackDataRoot } when calling resolveEngineDataDir from a host that resolves the data root itself.`,
71
+ );
72
+ }
73
+
74
+ if (!getWarnedSlot().current) {
75
+ getWarnedSlot().current = true;
76
+ const warn = opts.warn ?? defaultWarn;
77
+ warn(
78
+ `[engine-data-dir] ${XYZ_DATA_DIR_ENV} is not set; engine journal/pool fall back to the injected data root ` +
79
+ `(${fallback}). The xyz-agent host normally injects this env — if you are running inside xyz-agent, ` +
80
+ `check the runtime spawn env; standalone installs intentionally use the injected root.`,
81
+ );
82
+ }
83
+ return fallback;
84
+ }
85
+
86
+ function defaultWarn(msg: string): void {
87
+ logger.warn(msg);
88
+ }