@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.
- package/dist/best-effort.cjs +88 -0
- package/dist/best-effort.d.cts +14 -0
- package/dist/best-effort.d.ts +14 -0
- package/dist/best-effort.js +8 -0
- package/dist/chunk-2DIMPZCQ.js +0 -0
- package/dist/chunk-365AUV6N.js +67 -0
- package/dist/chunk-3K2P2CM2.js +127 -0
- package/dist/chunk-75QEMUGV.js +58 -0
- package/dist/chunk-7I4XGL5J.js +95 -0
- package/dist/chunk-A75XDJIC.js +227 -0
- package/dist/chunk-DSQ7JQKM.js +39 -0
- package/dist/chunk-EJMF63R5.js +19 -0
- package/dist/chunk-GT6YLLN4.js +46 -0
- package/dist/chunk-HYES77BR.js +109 -0
- package/dist/chunk-JSBRDJBE.js +30 -0
- package/dist/chunk-LEOBWKRM.js +128 -0
- package/dist/chunk-N3RL6OVM.js +38 -0
- package/dist/chunk-OPMY4G4M.js +27 -0
- package/dist/chunk-PPEPBVCC.js +120 -0
- package/dist/chunk-PYO3YR7W.js +16 -0
- package/dist/chunk-RDH3ZOV6.js +46 -0
- package/dist/chunk-RULLX6C6.js +11 -0
- package/dist/chunk-X24SFZYW.js +6646 -0
- package/dist/chunk-YFSN3D5N.js +216 -0
- package/dist/chunk-ZOFFJNJD.js +136 -0
- package/dist/chunk-ZXEAW25V.js +132 -0
- package/dist/cli-entry.cjs +119 -0
- package/dist/cli-entry.d.cts +24 -0
- package/dist/cli-entry.d.ts +24 -0
- package/dist/cli-entry.js +8 -0
- package/dist/contract-types-sSlgppBC.d.cts +352 -0
- package/dist/contract-types-sSlgppBC.d.ts +352 -0
- package/dist/data-dir.cjs +117 -0
- package/dist/data-dir.d.cts +20 -0
- package/dist/data-dir.d.ts +20 -0
- package/dist/data-dir.js +12 -0
- package/dist/env.cjs +205 -0
- package/dist/env.d.cts +73 -0
- package/dist/env.d.ts +73 -0
- package/dist/env.js +16 -0
- package/dist/error-codes-DHco5-i_.d.cts +118 -0
- package/dist/error-codes-Dhss2Kmk.d.ts +118 -0
- package/dist/error-message.cjs +40 -0
- package/dist/error-message.d.cts +3 -0
- package/dist/error-message.d.ts +3 -0
- package/dist/error-message.js +7 -0
- package/dist/index.cjs +8375 -0
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +268 -0
- package/dist/journal-io.cjs +70 -0
- package/dist/journal-io.d.cts +12 -0
- package/dist/journal-io.d.ts +12 -0
- package/dist/journal-io.js +7 -0
- package/dist/journal-replay.cjs +256 -0
- package/dist/journal-replay.d.cts +45 -0
- package/dist/journal-replay.d.ts +45 -0
- package/dist/journal-replay.js +17 -0
- package/dist/kill-chain.cjs +221 -0
- package/dist/kill-chain.d.cts +74 -0
- package/dist/kill-chain.d.ts +74 -0
- package/dist/kill-chain.js +23 -0
- package/dist/logger.cjs +84 -0
- package/dist/logger.d.cts +24 -0
- package/dist/logger.d.ts +24 -0
- package/dist/logger.js +11 -0
- package/dist/logs/stderr-rotation.cjs +151 -0
- package/dist/logs/stderr-rotation.d.cts +37 -0
- package/dist/logs/stderr-rotation.d.ts +37 -0
- package/dist/logs/stderr-rotation.js +23 -0
- package/dist/nesting-guard.cjs +95 -0
- package/dist/nesting-guard.d.cts +65 -0
- package/dist/nesting-guard.d.ts +65 -0
- package/dist/nesting-guard.js +15 -0
- package/dist/node-executor.cjs +180 -0
- package/dist/node-executor.d.cts +63 -0
- package/dist/node-executor.d.ts +63 -0
- package/dist/node-executor.js +16 -0
- package/dist/paths.cjs +55 -0
- package/dist/paths.d.cts +8 -0
- package/dist/paths.d.ts +8 -0
- package/dist/paths.js +15 -0
- package/dist/port-contract.cjs +35 -0
- package/dist/port-contract.d.cts +88 -0
- package/dist/port-contract.d.ts +88 -0
- package/dist/port-contract.js +7 -0
- package/dist/protocol/index.cjs +400 -0
- package/dist/protocol/index.d.cts +685 -0
- package/dist/protocol/index.d.ts +685 -0
- package/dist/protocol/index.js +89 -0
- package/dist/relay-env.cjs +71 -0
- package/dist/relay-env.d.cts +37 -0
- package/dist/relay-env.d.ts +37 -0
- package/dist/relay-env.js +23 -0
- package/dist/schema-emulation.cjs +6680 -0
- package/dist/schema-emulation.d.cts +39 -0
- package/dist/schema-emulation.d.ts +39 -0
- package/dist/schema-emulation.js +11 -0
- package/dist/spawn.cjs +200 -0
- package/dist/spawn.d.cts +80 -0
- package/dist/spawn.d.ts +80 -0
- package/dist/spawn.js +16 -0
- package/dist/ui-channels.cjs +120 -0
- package/dist/ui-channels.d.cts +60 -0
- package/dist/ui-channels.d.ts +60 -0
- package/dist/ui-channels.js +9 -0
- package/dist/ui-types.cjs +18 -0
- package/dist/ui-types.d.cts +62 -0
- package/dist/ui-types.d.ts +62 -0
- package/dist/ui-types.js +1 -0
- package/package.json +58 -0
- package/src/best-effort.ts +37 -0
- package/src/cli-entry.ts +77 -0
- package/src/data-dir.ts +88 -0
- package/src/env.ts +265 -0
- package/src/error-message.ts +22 -0
- package/src/index.ts +63 -0
- package/src/journal-io.ts +82 -0
- package/src/journal-replay.ts +432 -0
- package/src/kill-chain.ts +265 -0
- package/src/logger.ts +105 -0
- package/src/logs/stderr-rotation.ts +166 -0
- package/src/nesting-guard.ts +140 -0
- package/src/node-executor.ts +272 -0
- package/src/paths.ts +48 -0
- package/src/port-contract.ts +117 -0
- package/src/protocol/contract-types.ts +378 -0
- package/src/protocol/engine-protocol.ts +81 -0
- package/src/protocol/error-codes.ts +179 -0
- package/src/protocol/frames.ts +145 -0
- package/src/protocol/index.ts +12 -0
- package/src/protocol/methods.ts +229 -0
- package/src/protocol/reverse-channels.ts +274 -0
- package/src/protocol/schema.ts +154 -0
- package/src/relay-env.ts +60 -0
- package/src/schema-emulation.ts +192 -0
- package/src/spawn.ts +246 -0
- package/src/ui-channels.ts +219 -0
- package/src/ui-types.ts +84 -0
|
@@ -0,0 +1,88 @@
|
|
|
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/best-effort.ts
|
|
21
|
+
var best_effort_exports = {};
|
|
22
|
+
__export(best_effort_exports, {
|
|
23
|
+
bestEffort: () => bestEffort
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(best_effort_exports);
|
|
26
|
+
|
|
27
|
+
// src/logger.ts
|
|
28
|
+
var SINK_SLOT_KEY = /* @__PURE__ */ Symbol.for("@zhushanwen/subagent-engine-sdk.logger-sink");
|
|
29
|
+
function getSinkSlot() {
|
|
30
|
+
let slot = Reflect.get(globalThis, SINK_SLOT_KEY);
|
|
31
|
+
if (!slot) {
|
|
32
|
+
slot = { current: void 0 };
|
|
33
|
+
Reflect.set(globalThis, SINK_SLOT_KEY, slot);
|
|
34
|
+
}
|
|
35
|
+
return slot;
|
|
36
|
+
}
|
|
37
|
+
var CONSOLE_SINK = {
|
|
38
|
+
log(level, component, message, data) {
|
|
39
|
+
const line = `[${component}] ${message}`;
|
|
40
|
+
if (level === "error") {
|
|
41
|
+
if (data === void 0) console.error(line);
|
|
42
|
+
else console.error(line, data);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (level === "warn") {
|
|
46
|
+
if (data === void 0) console.warn(line);
|
|
47
|
+
else console.warn(line, data);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
function currentSink() {
|
|
53
|
+
return getSinkSlot().current ?? CONSOLE_SINK;
|
|
54
|
+
}
|
|
55
|
+
var facadeCache = /* @__PURE__ */ new Map();
|
|
56
|
+
function getLogger(component) {
|
|
57
|
+
const existing = facadeCache.get(component);
|
|
58
|
+
if (existing) return existing;
|
|
59
|
+
const facade = {
|
|
60
|
+
debug(msg, data) {
|
|
61
|
+
currentSink().log("debug", component, msg, data);
|
|
62
|
+
},
|
|
63
|
+
warn(msg, data) {
|
|
64
|
+
currentSink().log("warn", component, msg, data);
|
|
65
|
+
},
|
|
66
|
+
error(msg, data) {
|
|
67
|
+
currentSink().log("error", component, msg, data);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
facadeCache.set(component, facade);
|
|
71
|
+
return facade;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/best-effort.ts
|
|
75
|
+
var logger = getLogger("subagents");
|
|
76
|
+
function bestEffort(err, context, level = "debug") {
|
|
77
|
+
const detail = err instanceof Error ? err.message : err;
|
|
78
|
+
const msg = `[subagents] best-effort ${context} failed`;
|
|
79
|
+
if (level === "error") {
|
|
80
|
+
logger.error(msg, { detail });
|
|
81
|
+
} else {
|
|
82
|
+
logger.debug(msg, { detail });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
86
|
+
0 && (module.exports = {
|
|
87
|
+
bestEffort
|
|
88
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** 错误日志级别。debug = 次要清理(默认);error = 关键步骤但需继续后续清理。 */
|
|
2
|
+
type BestEffortLevel = "debug" | "error";
|
|
3
|
+
/**
|
|
4
|
+
* 吞咽 best-effort IO 的错误,按 level 经共享 logger 记录。
|
|
5
|
+
*
|
|
6
|
+
* - debug(默认):次要清理(sidecar/worktree/alive marker),失败属预期路径
|
|
7
|
+
* - error:关键步骤抛错但需继续后续清理(如 finalizeRecord 的 B9 链:completeRecord
|
|
8
|
+
* 抛错后仍要执行 finalized/cleanup,错误需可见但不阻断)
|
|
9
|
+
*
|
|
10
|
+
* 错误对象优先取 message(避免打印巨大堆栈/对象),其他类型原样传入。
|
|
11
|
+
*/
|
|
12
|
+
declare function bestEffort(err: unknown, context: string, level?: BestEffortLevel): void;
|
|
13
|
+
|
|
14
|
+
export { type BestEffortLevel, bestEffort };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** 错误日志级别。debug = 次要清理(默认);error = 关键步骤但需继续后续清理。 */
|
|
2
|
+
type BestEffortLevel = "debug" | "error";
|
|
3
|
+
/**
|
|
4
|
+
* 吞咽 best-effort IO 的错误,按 level 经共享 logger 记录。
|
|
5
|
+
*
|
|
6
|
+
* - debug(默认):次要清理(sidecar/worktree/alive marker),失败属预期路径
|
|
7
|
+
* - error:关键步骤抛错但需继续后续清理(如 finalizeRecord 的 B9 链:completeRecord
|
|
8
|
+
* 抛错后仍要执行 finalized/cleanup,错误需可见但不阻断)
|
|
9
|
+
*
|
|
10
|
+
* 错误对象优先取 message(避免打印巨大堆栈/对象),其他类型原样传入。
|
|
11
|
+
*/
|
|
12
|
+
declare function bestEffort(err: unknown, context: string, level?: BestEffortLevel): void;
|
|
13
|
+
|
|
14
|
+
export { type BestEffortLevel, bestEffort };
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/nesting-guard.ts
|
|
2
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3
|
+
var NESTED_SPAWN_ENV = "XYZ_AGENT_SUBAGENT";
|
|
4
|
+
var NATIVE_NESTED_KEYS = ["CLAUDECODE", "ZSW_NESTED"];
|
|
5
|
+
var NATIVE_NESTED_PREFIXES = ["PI_SUBAGENT_"];
|
|
6
|
+
var NestedSpawnRejectedError = class extends Error {
|
|
7
|
+
code = "nested_spawn_rejected";
|
|
8
|
+
/** 恢复指引:指向具体下一步,非安慰性文案。 */
|
|
9
|
+
recovery;
|
|
10
|
+
constructor() {
|
|
11
|
+
super(
|
|
12
|
+
"nested_spawn_rejected: this process is already a subagent (XYZ_AGENT_SUBAGENT=1)"
|
|
13
|
+
);
|
|
14
|
+
this.name = "NestedSpawnRejectedError";
|
|
15
|
+
this.recovery = "Subagents must not spawn further subagents (unbounded recursion guard). Do the work directly inside the current task instead of delegating.";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function buildNestedSpawnEnv(baseEnv) {
|
|
19
|
+
const env = {};
|
|
20
|
+
for (const [key, value] of Object.entries(baseEnv)) {
|
|
21
|
+
if (NATIVE_NESTED_KEYS.includes(key)) continue;
|
|
22
|
+
if (NATIVE_NESTED_PREFIXES.some((prefix) => key.startsWith(prefix))) continue;
|
|
23
|
+
env[key] = value;
|
|
24
|
+
}
|
|
25
|
+
env[NESTED_SPAWN_ENV] = "1";
|
|
26
|
+
return env;
|
|
27
|
+
}
|
|
28
|
+
function assertNotNestedSpawn(env) {
|
|
29
|
+
if (env[NESTED_SPAWN_ENV] === "1") {
|
|
30
|
+
throw new NestedSpawnRejectedError();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
var ExecutionNestingContext = class {
|
|
34
|
+
als = new AsyncLocalStorage();
|
|
35
|
+
baselineState = null;
|
|
36
|
+
/** 建立进程级基线(initSession:有 env 自我标记 → env 身份;根进程 → null 顶层)。 */
|
|
37
|
+
setBaseline(state) {
|
|
38
|
+
this.baselineState = state;
|
|
39
|
+
}
|
|
40
|
+
/** 读当前嵌套状态:ALS store 优先,断裂时基线兜底(顶层 = null)。 */
|
|
41
|
+
current() {
|
|
42
|
+
return this.als.getStore() ?? this.baselineState;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 读进程级基线(与 current() 的差异:不看 ALS store——直接父归属校验等场景要的是
|
|
46
|
+
* 「本进程自己的身份」而非「当前异步链正在跑的 record 身份」)。
|
|
47
|
+
*/
|
|
48
|
+
baseline() {
|
|
49
|
+
return this.baselineState;
|
|
50
|
+
}
|
|
51
|
+
/** 包裹执行(B run() 期间挂 B 身份——内层创建 C 时 current() 读到 B)。 */
|
|
52
|
+
run(state, fn) {
|
|
53
|
+
return this.als.run(state, fn);
|
|
54
|
+
}
|
|
55
|
+
/** 顶层 enterWith(initSession 建立基线身份后挂入当前异步链)。 */
|
|
56
|
+
enterWith(state) {
|
|
57
|
+
this.als.enterWith(state);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
NESTED_SPAWN_ENV,
|
|
63
|
+
NestedSpawnRejectedError,
|
|
64
|
+
buildNestedSpawnEnv,
|
|
65
|
+
assertNotNestedSpawn,
|
|
66
|
+
ExecutionNestingContext
|
|
67
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLogger
|
|
3
|
+
} from "./chunk-75QEMUGV.js";
|
|
4
|
+
|
|
5
|
+
// src/spawn.ts
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
var logger = getLogger("engine-sdk/spawn");
|
|
8
|
+
var FORBIDDEN_OPTION_KEYS = ["detached", "stdio", "stdin", "windowsHide"];
|
|
9
|
+
function spawnEngineChild(opts) {
|
|
10
|
+
for (const key of FORBIDDEN_OPTION_KEYS) {
|
|
11
|
+
if (key in opts) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`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`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const child = spawn(opts.command, [...opts.args], {
|
|
18
|
+
cwd: opts.cwd,
|
|
19
|
+
env: opts.env,
|
|
20
|
+
detached: false,
|
|
21
|
+
windowsHide: true,
|
|
22
|
+
stdio: ["pipe", opts.stdout ?? "pipe", opts.stderr ?? "pipe"]
|
|
23
|
+
});
|
|
24
|
+
if (opts.signal !== void 0) {
|
|
25
|
+
opts.signal.addEventListener(
|
|
26
|
+
"abort",
|
|
27
|
+
() => {
|
|
28
|
+
child.kill("SIGTERM");
|
|
29
|
+
},
|
|
30
|
+
{ once: true }
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return child;
|
|
34
|
+
}
|
|
35
|
+
var DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS = 3e4;
|
|
36
|
+
var ENGINE_HOST_REQUEST_TIMEOUT_ENV = "XYZ_ENGINE_HOST_REQUEST_TIMEOUT_MS";
|
|
37
|
+
var SWEEP_INTERVAL_MS = 1e3;
|
|
38
|
+
function resolveHostRequestTimeoutMs(opts) {
|
|
39
|
+
if (opts.hostRequestTimeoutMs !== void 0) return opts.hostRequestTimeoutMs;
|
|
40
|
+
const raw = (opts.timeoutEnv ?? process.env)[ENGINE_HOST_REQUEST_TIMEOUT_ENV];
|
|
41
|
+
if (raw === void 0 || raw === "") return DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS;
|
|
42
|
+
const parsed = Number(raw);
|
|
43
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
44
|
+
logger.warn(
|
|
45
|
+
`invalid ${ENGINE_HOST_REQUEST_TIMEOUT_ENV}=${JSON.stringify(raw)}, falling back to ${DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS}ms`
|
|
46
|
+
);
|
|
47
|
+
return DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS;
|
|
48
|
+
}
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
function killOwnProcessGroup() {
|
|
52
|
+
const pid = process.pid;
|
|
53
|
+
try {
|
|
54
|
+
process.kill(-pid, "SIGKILL");
|
|
55
|
+
} catch (err) {
|
|
56
|
+
logger.warn(`process-group kill failed (${String(err)}), falling back to self kill`);
|
|
57
|
+
process.kill(pid, "SIGKILL");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function armEngineSelfDestruct(opts) {
|
|
61
|
+
const timeoutMs = resolveHostRequestTimeoutMs(opts);
|
|
62
|
+
const killSelf = opts.killSelf ?? killOwnProcessGroup;
|
|
63
|
+
const pending = /* @__PURE__ */ new Map();
|
|
64
|
+
let disposed = false;
|
|
65
|
+
const destroy = (reason) => {
|
|
66
|
+
if (disposed) return;
|
|
67
|
+
disposed = true;
|
|
68
|
+
clearInterval(timer);
|
|
69
|
+
opts.stdin.removeAllListeners?.("end");
|
|
70
|
+
opts.stdin.removeAllListeners?.("close");
|
|
71
|
+
logger.warn(`engine self-destruct: ${reason}`);
|
|
72
|
+
killSelf();
|
|
73
|
+
};
|
|
74
|
+
opts.stdin.once("end", () => destroy("host stdin EOF (end)"));
|
|
75
|
+
opts.stdin.once("close", () => destroy("host stdin closed"));
|
|
76
|
+
const timer = setInterval(() => {
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
for (const [id, startedAt] of pending) {
|
|
79
|
+
if (now - startedAt > timeoutMs) {
|
|
80
|
+
destroy(`unacked reverse request ${id} exceeded ${timeoutMs}ms without host ack`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}, SWEEP_INTERVAL_MS);
|
|
85
|
+
timer.unref?.();
|
|
86
|
+
return {
|
|
87
|
+
started(id) {
|
|
88
|
+
if (!disposed) pending.set(id, Date.now());
|
|
89
|
+
},
|
|
90
|
+
acked(id) {
|
|
91
|
+
pending.delete(id);
|
|
92
|
+
},
|
|
93
|
+
settled(id) {
|
|
94
|
+
pending.delete(id);
|
|
95
|
+
},
|
|
96
|
+
dispose() {
|
|
97
|
+
if (disposed) return;
|
|
98
|
+
disposed = true;
|
|
99
|
+
clearInterval(timer);
|
|
100
|
+
opts.stdin.removeAllListeners?.("end");
|
|
101
|
+
opts.stdin.removeAllListeners?.("close");
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function pumpNdjsonLines(stream, onLine) {
|
|
106
|
+
if (stream === null || stream === void 0) return;
|
|
107
|
+
stream.setEncoding("utf8");
|
|
108
|
+
let buffer = "";
|
|
109
|
+
stream.on("data", (chunk) => {
|
|
110
|
+
buffer += chunk;
|
|
111
|
+
let nl = buffer.indexOf("\n");
|
|
112
|
+
while (nl >= 0) {
|
|
113
|
+
const line = buffer.slice(0, nl);
|
|
114
|
+
buffer = buffer.slice(nl + 1);
|
|
115
|
+
onLine(line);
|
|
116
|
+
nl = buffer.indexOf("\n");
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
spawnEngineChild,
|
|
123
|
+
DEFAULT_ENGINE_HOST_REQUEST_TIMEOUT_MS,
|
|
124
|
+
ENGINE_HOST_REQUEST_TIMEOUT_ENV,
|
|
125
|
+
armEngineSelfDestruct,
|
|
126
|
+
pumpNdjsonLines
|
|
127
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
var SINK_SLOT_KEY = /* @__PURE__ */ Symbol.for("@zhushanwen/subagent-engine-sdk.logger-sink");
|
|
3
|
+
function getSinkSlot() {
|
|
4
|
+
let slot = Reflect.get(globalThis, SINK_SLOT_KEY);
|
|
5
|
+
if (!slot) {
|
|
6
|
+
slot = { current: void 0 };
|
|
7
|
+
Reflect.set(globalThis, SINK_SLOT_KEY, slot);
|
|
8
|
+
}
|
|
9
|
+
return slot;
|
|
10
|
+
}
|
|
11
|
+
var CONSOLE_SINK = {
|
|
12
|
+
log(level, component, message, data) {
|
|
13
|
+
const line = `[${component}] ${message}`;
|
|
14
|
+
if (level === "error") {
|
|
15
|
+
if (data === void 0) console.error(line);
|
|
16
|
+
else console.error(line, data);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (level === "warn") {
|
|
20
|
+
if (data === void 0) console.warn(line);
|
|
21
|
+
else console.warn(line, data);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
function currentSink() {
|
|
27
|
+
return getSinkSlot().current ?? CONSOLE_SINK;
|
|
28
|
+
}
|
|
29
|
+
function configureLoggerSink(sink) {
|
|
30
|
+
getSinkSlot().current = sink;
|
|
31
|
+
}
|
|
32
|
+
function resetLoggerSinkForTests() {
|
|
33
|
+
getSinkSlot().current = void 0;
|
|
34
|
+
}
|
|
35
|
+
var facadeCache = /* @__PURE__ */ new Map();
|
|
36
|
+
function getLogger(component) {
|
|
37
|
+
const existing = facadeCache.get(component);
|
|
38
|
+
if (existing) return existing;
|
|
39
|
+
const facade = {
|
|
40
|
+
debug(msg, data) {
|
|
41
|
+
currentSink().log("debug", component, msg, data);
|
|
42
|
+
},
|
|
43
|
+
warn(msg, data) {
|
|
44
|
+
currentSink().log("warn", component, msg, data);
|
|
45
|
+
},
|
|
46
|
+
error(msg, data) {
|
|
47
|
+
currentSink().log("error", component, msg, data);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
facadeCache.set(component, facade);
|
|
51
|
+
return facade;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
configureLoggerSink,
|
|
56
|
+
resetLoggerSinkForTests,
|
|
57
|
+
getLogger
|
|
58
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/ui-channels.ts
|
|
2
|
+
var NUL = "\0";
|
|
3
|
+
function normalizeChannelName(markerLiteral) {
|
|
4
|
+
let name = markerLiteral;
|
|
5
|
+
if (name.startsWith("XYZ_")) {
|
|
6
|
+
name = name.slice("XYZ_".length);
|
|
7
|
+
}
|
|
8
|
+
if (name.endsWith(":")) {
|
|
9
|
+
name = name.slice(0, -1);
|
|
10
|
+
}
|
|
11
|
+
return name.toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
function parseMarkerFromField(str) {
|
|
14
|
+
if (!str.startsWith(NUL)) return void 0;
|
|
15
|
+
const literal = str.slice(NUL.length);
|
|
16
|
+
if (literal === "") return void 0;
|
|
17
|
+
return normalizeChannelName(literal);
|
|
18
|
+
}
|
|
19
|
+
function parseInlineMarkerFromField(str) {
|
|
20
|
+
if (!str.startsWith(NUL)) return void 0;
|
|
21
|
+
const rest = str.slice(NUL.length);
|
|
22
|
+
const colonIdx = rest.indexOf(":");
|
|
23
|
+
let literal;
|
|
24
|
+
if (colonIdx >= 0) {
|
|
25
|
+
literal = rest.slice(0, colonIdx + 1);
|
|
26
|
+
} else {
|
|
27
|
+
literal = rest;
|
|
28
|
+
}
|
|
29
|
+
if (literal === "") return void 0;
|
|
30
|
+
return { channel: normalizeChannelName(literal) };
|
|
31
|
+
}
|
|
32
|
+
function parseFromMarkerString(title, options) {
|
|
33
|
+
if (title === void 0) return {};
|
|
34
|
+
const channel = parseMarkerFromField(title);
|
|
35
|
+
if (channel === void 0) return {};
|
|
36
|
+
let payload;
|
|
37
|
+
if (options !== void 0 && options.length > 0) {
|
|
38
|
+
try {
|
|
39
|
+
payload = JSON.parse(options[0]);
|
|
40
|
+
} catch {
|
|
41
|
+
payload = void 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { channel, channelPayload: payload };
|
|
45
|
+
}
|
|
46
|
+
function parseFromMarkerArray(widgetLines) {
|
|
47
|
+
if (widgetLines === void 0 || widgetLines.length === 0) return {};
|
|
48
|
+
const firstLine = widgetLines[0];
|
|
49
|
+
if (typeof firstLine !== "string") return {};
|
|
50
|
+
const parsed = parseInlineMarkerFromField(firstLine);
|
|
51
|
+
if (parsed === void 0) return {};
|
|
52
|
+
let payload;
|
|
53
|
+
const rest = firstLine.slice(NUL.length);
|
|
54
|
+
const colonIdx = rest.indexOf(":");
|
|
55
|
+
if (colonIdx >= 0) {
|
|
56
|
+
const jsonStr = rest.slice(colonIdx + 1);
|
|
57
|
+
if (jsonStr !== "") {
|
|
58
|
+
try {
|
|
59
|
+
payload = JSON.parse(jsonStr);
|
|
60
|
+
} catch {
|
|
61
|
+
payload = void 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { channel: parsed.channel, channelPayload: payload };
|
|
66
|
+
}
|
|
67
|
+
function parseChannel(req) {
|
|
68
|
+
switch (req.method) {
|
|
69
|
+
case "select":
|
|
70
|
+
return parseFromMarkerString(req.title, req.options);
|
|
71
|
+
case "setWidget":
|
|
72
|
+
return parseFromMarkerArray(req.widgetLines);
|
|
73
|
+
default:
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function createUiChannelRegistry() {
|
|
78
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
79
|
+
return {
|
|
80
|
+
register(channel, handler) {
|
|
81
|
+
handlers.set(channel, handler);
|
|
82
|
+
},
|
|
83
|
+
resolve(channel) {
|
|
84
|
+
return handlers.get(channel);
|
|
85
|
+
},
|
|
86
|
+
list() {
|
|
87
|
+
return Array.from(handlers.keys());
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
parseChannel,
|
|
94
|
+
createUiChannelRegistry
|
|
95
|
+
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// src/journal-replay.ts
|
|
2
|
+
function sumUsageField(a, b) {
|
|
3
|
+
return (a ?? 0) + (b ?? 0);
|
|
4
|
+
}
|
|
5
|
+
function usageFromNext(next) {
|
|
6
|
+
return {
|
|
7
|
+
input: next.input ?? 0,
|
|
8
|
+
output: next.output ?? 0,
|
|
9
|
+
cacheRead: next.cacheRead ?? 0,
|
|
10
|
+
cacheWrite: next.cacheWrite ?? 0,
|
|
11
|
+
cost: next.cost
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function addUsage(prev, next) {
|
|
15
|
+
if (prev === void 0) return usageFromNext(next);
|
|
16
|
+
return {
|
|
17
|
+
input: sumUsageField(prev.input, next.input),
|
|
18
|
+
output: sumUsageField(prev.output, next.output),
|
|
19
|
+
cacheRead: sumUsageField(prev.cacheRead, next.cacheRead),
|
|
20
|
+
cacheWrite: sumUsageField(prev.cacheWrite, next.cacheWrite),
|
|
21
|
+
cost: sumUsageField(prev.cost, next.cost)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function emptyTurn() {
|
|
25
|
+
return { text: "", thinking: "", toolCalls: [], usageDelta: void 0, closed: false };
|
|
26
|
+
}
|
|
27
|
+
function createReplayRecord() {
|
|
28
|
+
return {
|
|
29
|
+
// turns[] 初始化为 [空 turn]——第一个 turn 从创建即存在,
|
|
30
|
+
// updateFromEvent 直接往 turns[last] 累积,无需「无 turn」分支判断。
|
|
31
|
+
turns: [emptyTurn()],
|
|
32
|
+
turnCount: 0,
|
|
33
|
+
totalTokens: 0,
|
|
34
|
+
lastError: void 0
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function currentTurn(record) {
|
|
38
|
+
const last = record.turns[record.turns.length - 1];
|
|
39
|
+
if (last !== void 0 && !last.closed) return last;
|
|
40
|
+
const fresh = emptyTurn();
|
|
41
|
+
record.turns.push(fresh);
|
|
42
|
+
return fresh;
|
|
43
|
+
}
|
|
44
|
+
function findRunningToolCall(record, toolName) {
|
|
45
|
+
for (let t = record.turns.length - 1; t >= 0; t--) {
|
|
46
|
+
const turn = record.turns[t];
|
|
47
|
+
if (turn === void 0) continue;
|
|
48
|
+
for (let i = turn.toolCalls.length - 1; i >= 0; i--) {
|
|
49
|
+
const tc = turn.toolCalls[i];
|
|
50
|
+
if (tc?._status === "running" && tc.toolName === toolName) {
|
|
51
|
+
return [turn, i];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return void 0;
|
|
56
|
+
}
|
|
57
|
+
var runningToolIndex = /* @__PURE__ */ new WeakMap();
|
|
58
|
+
function indexToolStart(record, turn, toolName) {
|
|
59
|
+
let byName = runningToolIndex.get(record);
|
|
60
|
+
if (byName === void 0) {
|
|
61
|
+
byName = /* @__PURE__ */ new Map();
|
|
62
|
+
runningToolIndex.set(record, byName);
|
|
63
|
+
}
|
|
64
|
+
const arr = byName.get(toolName);
|
|
65
|
+
if (arr === void 0) {
|
|
66
|
+
byName.set(toolName, [{ turn, idx: turn.toolCalls.length - 1 }]);
|
|
67
|
+
} else {
|
|
68
|
+
arr.push({ turn, idx: turn.toolCalls.length - 1 });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function applyTextDelta(record, event) {
|
|
72
|
+
currentTurn(record).text += event.delta;
|
|
73
|
+
}
|
|
74
|
+
function applyThinkingDelta(record, event) {
|
|
75
|
+
currentTurn(record).thinking += event.delta;
|
|
76
|
+
}
|
|
77
|
+
function applyToolStart(record, event) {
|
|
78
|
+
const tc = {
|
|
79
|
+
toolName: event.toolName,
|
|
80
|
+
args: event.args,
|
|
81
|
+
result: void 0,
|
|
82
|
+
isError: false,
|
|
83
|
+
_status: "running",
|
|
84
|
+
startedTs: Date.now()
|
|
85
|
+
};
|
|
86
|
+
const turn = currentTurn(record);
|
|
87
|
+
turn.toolCalls.push(tc);
|
|
88
|
+
indexToolStart(record, turn, event.toolName);
|
|
89
|
+
}
|
|
90
|
+
function matchRunningToolCall(record, toolName) {
|
|
91
|
+
const byName = runningToolIndex.get(record);
|
|
92
|
+
const arr = byName?.get(toolName);
|
|
93
|
+
if (arr !== void 0 && arr.length > 0) {
|
|
94
|
+
const item = arr[arr.length - 1];
|
|
95
|
+
arr.pop();
|
|
96
|
+
const tc = item.turn.toolCalls[item.idx];
|
|
97
|
+
if (tc !== void 0 && tc._status === "running") {
|
|
98
|
+
return [item.turn, item.idx];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return findRunningToolCall(record, toolName);
|
|
102
|
+
}
|
|
103
|
+
function applyToolEnd(record, event) {
|
|
104
|
+
const matched = matchRunningToolCall(record, event.toolName);
|
|
105
|
+
if (matched !== void 0) {
|
|
106
|
+
const [turn, i] = matched;
|
|
107
|
+
const tc = turn.toolCalls[i];
|
|
108
|
+
tc.args = event.args ?? tc.args;
|
|
109
|
+
tc.result = event.result;
|
|
110
|
+
tc.isError = event.isError ?? false;
|
|
111
|
+
tc._status = event.isError ? "failed" : "done";
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
currentTurn(record).toolCalls.push({
|
|
115
|
+
toolName: event.toolName,
|
|
116
|
+
args: event.args,
|
|
117
|
+
result: event.result,
|
|
118
|
+
isError: event.isError ?? false,
|
|
119
|
+
_status: event.isError ? "failed" : "done",
|
|
120
|
+
startedTs: Date.now()
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function applyTurnEnd(record) {
|
|
124
|
+
const turn = currentTurn(record);
|
|
125
|
+
turn.closed = true;
|
|
126
|
+
turn.closedTs = Date.now();
|
|
127
|
+
record.turnCount += 1;
|
|
128
|
+
record.lastError = void 0;
|
|
129
|
+
}
|
|
130
|
+
function applyMessageEnd(record, event) {
|
|
131
|
+
if (event.usage) {
|
|
132
|
+
const turn = currentTurn(record);
|
|
133
|
+
turn.usageDelta = addUsage(turn.usageDelta, event.usage);
|
|
134
|
+
record.totalTokens += (event.usage.input ?? 0) + (event.usage.output ?? 0) + (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
|
|
135
|
+
}
|
|
136
|
+
if (event.error) {
|
|
137
|
+
record.lastError = event.error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function applyErrorEvent(record, event) {
|
|
141
|
+
record.lastError = event.message;
|
|
142
|
+
}
|
|
143
|
+
function updateFromEvent(record, event) {
|
|
144
|
+
switch (event.type) {
|
|
145
|
+
// ── text / thinking:流式累积进当前 turn ──
|
|
146
|
+
case "text_delta":
|
|
147
|
+
return applyTextDelta(record, event);
|
|
148
|
+
case "thinking_delta":
|
|
149
|
+
return applyThinkingDelta(record, event);
|
|
150
|
+
// ── tool_start/end:push 进 currentTurn().toolCalls(含完整 result)──
|
|
151
|
+
case "tool_start":
|
|
152
|
+
return applyToolStart(record, event);
|
|
153
|
+
case "tool_end":
|
|
154
|
+
return applyToolEnd(record, event);
|
|
155
|
+
// ── turn_end:闭合当前 turn ──
|
|
156
|
+
case "turn_end":
|
|
157
|
+
return applyTurnEnd(record);
|
|
158
|
+
// ── message_end:usage 增量累加 + totalTokens 累加 ──
|
|
159
|
+
case "message_end":
|
|
160
|
+
return applyMessageEnd(record, event);
|
|
161
|
+
// ── error:存 record.lastError ──
|
|
162
|
+
case "error":
|
|
163
|
+
return applyErrorEvent(record, event);
|
|
164
|
+
// ── compaction:不产生数据(不变)──
|
|
165
|
+
case "compaction":
|
|
166
|
+
return;
|
|
167
|
+
default: {
|
|
168
|
+
const _exhaustive = event;
|
|
169
|
+
return _exhaustive;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function toExportedToolCall(tc) {
|
|
174
|
+
return {
|
|
175
|
+
toolName: tc.toolName,
|
|
176
|
+
...tc.args !== void 0 ? { args: tc.args } : {},
|
|
177
|
+
...tc.result !== void 0 ? { result: tc.result } : {},
|
|
178
|
+
...tc.isError !== void 0 ? { isError: tc.isError } : {}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function toReplayedTurn(turn) {
|
|
182
|
+
return {
|
|
183
|
+
text: turn.text,
|
|
184
|
+
thinking: turn.thinking,
|
|
185
|
+
toolCalls: turn.toolCalls.map(toExportedToolCall),
|
|
186
|
+
closed: true
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function aggregateUsage(turns) {
|
|
190
|
+
let acc;
|
|
191
|
+
for (const turn of turns) {
|
|
192
|
+
const d = turn.usageDelta;
|
|
193
|
+
if (!d) continue;
|
|
194
|
+
if (!acc) acc = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, total: 0 };
|
|
195
|
+
acc.input += d.input;
|
|
196
|
+
acc.output += d.output;
|
|
197
|
+
acc.cacheRead += d.cacheRead;
|
|
198
|
+
acc.cacheWrite += d.cacheWrite;
|
|
199
|
+
acc.cost += d.cost ?? 0;
|
|
200
|
+
}
|
|
201
|
+
if (acc) acc.total = acc.input + acc.output + acc.cacheRead + acc.cacheWrite;
|
|
202
|
+
return acc;
|
|
203
|
+
}
|
|
204
|
+
function eventsToSessionView(events, engineId, sessionId) {
|
|
205
|
+
const record = createReplayRecord();
|
|
206
|
+
for (const ev of events) updateFromEvent(record, ev);
|
|
207
|
+
return {
|
|
208
|
+
engineId,
|
|
209
|
+
...sessionId !== void 0 ? { sessionId } : {},
|
|
210
|
+
turns: record.turns.map(toReplayedTurn),
|
|
211
|
+
usage: aggregateUsage(record.turns),
|
|
212
|
+
source: "journal"
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function sessionIdFromHandle(handle) {
|
|
216
|
+
const v = handle.sessionRef["sessionId"];
|
|
217
|
+
return typeof v === "string" ? v : void 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export {
|
|
221
|
+
createReplayRecord,
|
|
222
|
+
updateFromEvent,
|
|
223
|
+
toReplayedTurn,
|
|
224
|
+
aggregateUsage,
|
|
225
|
+
eventsToSessionView,
|
|
226
|
+
sessionIdFromHandle
|
|
227
|
+
};
|