@zhushanwen/pi-subagent-workflow 8.4.0 → 8.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/package.json +22 -7
- package/relay/relay.mjs +390 -0
- package/skills/subagent-ext-config/SKILL.md +80 -0
- package/src/execution/__tests__/agent-registry.test.ts +110 -0
- package/src/execution/__tests__/bg-notify-render.test.ts +73 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +601 -0
- package/src/execution/__tests__/delivery-methods.test.ts +38 -1
- package/src/execution/__tests__/execute-options-mapper.test.ts +11 -0
- package/src/execution/__tests__/execution-record.test.ts +237 -1
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +171 -0
- package/src/execution/__tests__/format-schema-instruction.test.ts +63 -32
- package/src/execution/__tests__/helpers/spawn-mock.ts +4 -0
- package/src/execution/__tests__/index-session-start.test.ts +86 -7
- package/src/execution/__tests__/lifecycle-manager.test.ts +46 -0
- package/src/execution/__tests__/list-fields.test.ts +45 -14
- package/src/execution/__tests__/model-resolver.test.ts +57 -5
- package/src/execution/__tests__/notifier-flush.test.ts +64 -26
- package/src/execution/__tests__/notify-ledger.test.ts +826 -0
- package/src/execution/__tests__/output-collector.test.ts +299 -2
- package/src/execution/__tests__/pi-invocation.test.ts +62 -1
- package/src/execution/__tests__/relay-agent.test.ts +448 -0
- package/src/execution/__tests__/relay-env.test.ts +42 -0
- package/src/execution/__tests__/rpc-mode.test.ts +1 -1
- package/src/execution/__tests__/run-spawn-edges.test.ts +44 -1
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +199 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +39 -0
- package/src/execution/__tests__/spawn-args.test.ts +37 -26
- package/src/execution/__tests__/start-sync-model-guard.test.ts +150 -0
- package/src/execution/__tests__/startup-config-declaration.test.ts +35 -0
- package/src/execution/__tests__/stream-sink-retirement.test.ts +261 -0
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +310 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +147 -6
- package/src/execution/__tests__/timeout-integration.test.ts +220 -2
- package/src/execution/__tests__/tool-action.test.ts +92 -1
- package/src/execution/agent-registry.ts +16 -0
- package/src/execution/argv-mirror.ts +5 -1
- package/src/execution/concurrency-pool.ts +1 -1
- package/src/execution/config.ts +25 -2
- package/src/execution/engine/__tests__/common/data-dir.test.ts +53 -0
- package/src/execution/engine/__tests__/common/errors.test.ts +132 -0
- package/src/execution/engine/__tests__/common/event-journal.test.ts +177 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +192 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +81 -0
- package/src/execution/engine/__tests__/common/persona-router.test.ts +123 -0
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +154 -0
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +128 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +28 -0
- package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +141 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +109 -0
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +101 -0
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +77 -0
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +104 -0
- package/src/execution/engine/__tests__/conformance/contract.relay.test.ts +342 -0
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +201 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +76 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +79 -0
- package/src/execution/engine/__tests__/engine-discovery.test.ts +87 -0
- package/src/execution/engine/__tests__/engines-declaration.test.ts +36 -0
- package/src/execution/engine/__tests__/model-prompt.test.ts +85 -0
- package/src/execution/engine/__tests__/paths.test.ts +39 -0
- package/src/execution/engine/__tests__/registry.test.ts +120 -0
- package/src/execution/engine/__tests__/routing.test.ts +231 -0
- package/src/execution/engine/common/data-dir.ts +62 -0
- package/src/execution/engine/common/errors.ts +183 -0
- package/src/execution/engine/common/event-journal.ts +254 -0
- package/src/execution/engine/common/journal-replay.ts +62 -0
- package/src/execution/engine/common/kill-chain.ts +221 -0
- package/src/execution/engine/common/nesting-guard.ts +50 -0
- package/src/execution/engine/common/persona-router.ts +108 -0
- package/src/execution/engine/common/pool-manager.ts +226 -0
- package/src/execution/engine/common/schema-emulation.ts +189 -0
- package/src/execution/engine/common/session-view-projection.ts +51 -0
- package/src/execution/engine/engine-discovery.ts +65 -0
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +469 -0
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +155 -0
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +164 -0
- package/src/execution/engine/engines/pi/pi-engine.ts +415 -0
- package/src/execution/engine/engines/pi/reader.ts +48 -0
- package/src/execution/engine/engines/pi/registration.ts +35 -0
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +100 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +39 -0
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +150 -0
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +246 -0
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +228 -0
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +64 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +127 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +580 -0
- package/src/execution/engine/engines/zcode/constants.ts +43 -0
- package/src/execution/engine/engines/zcode/golden-sample.ts +39 -0
- package/src/execution/engine/engines/zcode/launcher.ts +161 -0
- package/src/execution/engine/engines/zcode/parser.ts +436 -0
- package/src/execution/engine/engines/zcode/preparer.ts +363 -0
- package/src/execution/engine/engines/zcode/reader.ts +381 -0
- package/src/execution/engine/engines/zcode/registration.ts +37 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +658 -0
- package/src/execution/engine/host-task-spec.ts +47 -0
- package/src/execution/engine/model-prompt.ts +59 -0
- package/src/execution/engine/paths.ts +42 -0
- package/src/execution/engine/port.ts +153 -0
- package/src/execution/engine/registry.ts +123 -0
- package/src/execution/engine/routing.ts +218 -0
- package/src/execution/engine/types.ts +309 -0
- package/src/execution/execute-options-mapper.ts +13 -8
- package/src/execution/execution-record.ts +66 -1
- package/src/execution/lifecycle-manager.ts +23 -1
- package/src/execution/model-config-service.ts +16 -1
- package/src/execution/model-resolver.ts +37 -59
- package/src/execution/notifier.ts +105 -35
- package/src/execution/notify-ledger.ts +580 -0
- package/src/execution/output-collector.ts +143 -3
- package/src/execution/pi-invocation.ts +32 -2
- package/src/execution/record-entry.ts +14 -0
- package/src/execution/record-store.ts +34 -0
- package/src/execution/relay-env.ts +37 -0
- package/src/execution/session-runner.ts +328 -71
- package/src/execution/stream-sink.ts +26 -0
- package/src/execution/subagent-service.ts +273 -13
- package/src/execution/subprocess-agent-runner.ts +210 -14
- package/src/execution/types.ts +124 -5
- package/src/execution/ui-request-queue.ts +14 -4
- package/src/index.ts +99 -1
- package/src/interface/__tests__/subagent-tool-path-guard.test.ts +157 -0
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +12 -0
- package/src/interface/bg-notify-render.ts +33 -12
- package/src/interface/helpers.ts +2 -2
- package/src/interface/subagent-actions.ts +29 -9
- package/src/interface/subagent-tool-schema.ts +156 -0
- package/src/interface/subagent-tool.ts +56 -119
- package/src/interface/subagents.ts +2 -2
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +19 -3
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -6
- package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -5
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +89 -4
- package/src/orchestration/__tests__/execute-agent-call.test.ts +137 -0
- package/src/orchestration/__tests__/jsonl-run-store-corrupt-entry.test.ts +150 -0
- package/src/orchestration/__tests__/jsonl-run-store-retention.test.ts +202 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +326 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +41 -7
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +95 -0
- package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +57 -3
- package/src/orchestration/__tests__/skill-discovery.test.ts +44 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +368 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +43 -0
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +22 -3
- package/src/orchestration/agent-opts-resolver.ts +104 -23
- package/src/orchestration/error-recovery.ts +189 -33
- package/src/orchestration/execute-agent-call.ts +39 -0
- package/src/orchestration/jsonl-run-store.ts +121 -7
- package/src/orchestration/launcher.ts +60 -15
- package/src/orchestration/lifecycle.ts +10 -7
- package/src/orchestration/models/__tests__/budget.test.ts +1 -61
- package/src/orchestration/models/budget.ts +5 -35
- package/src/orchestration/models/run-runtime.ts +24 -9
- package/src/orchestration/models/types.ts +16 -0
- package/src/orchestration/script-lint.ts +1 -1
- package/src/orchestration/skill-discovery.ts +31 -8
- package/src/orchestration/worker-script-builder.ts +19 -3
- package/src/shared/__tests__/model-ref.test.ts +306 -0
- package/src/shared/__tests__/schema-jsonify.test.ts +1 -1
- package/src/shared/__tests__/timer-delay.test.ts +61 -0
- package/src/shared/meta-parser.ts +5 -1
- package/src/shared/model-ref.ts +286 -0
- package/src/shared/resource-meta.ts +5 -0
- package/src/shared/schema-env.ts +44 -0
- package/src/shared/schema-jsonify.ts +6 -4
- package/src/shared/timer-delay.ts +54 -0
- package/workflows/review-fix-loop-utils.cjs +9 -7
- package/workflows/review-fix-loop.js +20 -12
- package/src/orchestration/__tests__/concurrency-gate.test.ts +0 -125
- package/src/orchestration/concurrency-gate.ts +0 -69
|
@@ -20,6 +20,15 @@ import {
|
|
|
20
20
|
getFullText,
|
|
21
21
|
getTotalUsage,
|
|
22
22
|
} from "./execution-record.ts";
|
|
23
|
+
// [F-R1] 值引用 stale-context 分诊 pattern 表(SSOT 在 execute-agent-call,消费点
|
|
24
|
+
// isStaleContextErrorMsg 同源)。execution → orchestration 值引用有先例
|
|
25
|
+
// (agent-registry.ts → script-lint.ts),且 execute-agent-call 对 execution 仅剩
|
|
26
|
+
// type-only import(已擦除),无运行时循环。
|
|
27
|
+
// [MF-1] 同向引用确定性失败标记前缀(SSOT 同在 execute-agent-call,同一布局理由)。
|
|
28
|
+
import {
|
|
29
|
+
DETERMINISTIC_SCHEMA_FAILURE_PREFIX,
|
|
30
|
+
STALE_CONTEXT_PATTERNS,
|
|
31
|
+
} from "../orchestration/execute-agent-call.ts";
|
|
23
32
|
|
|
24
33
|
// ============================================================
|
|
25
34
|
// Result 收集
|
|
@@ -32,28 +41,149 @@ export interface CollectResultArgs {
|
|
|
32
41
|
error: string | undefined;
|
|
33
42
|
sessionId: string;
|
|
34
43
|
sessionFile: string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* [F-1] 本次执行是否要求结构化产出(opts.schema 或 opts.schemaEnv 任一存在)。
|
|
46
|
+
* true 且 run 结束仍无有效 parsedOutput 时:结果不得静默 success——置 success=false
|
|
47
|
+
* 并把三态失败归因写进 error(上层 executeAgentCall 只看 error 判 completed/failed)。
|
|
48
|
+
* 缺省 false(非 schema 模式 parsedOutput===undefined 是正常态,不动成败)。
|
|
49
|
+
*/
|
|
50
|
+
schemaExpected?: boolean;
|
|
35
51
|
}
|
|
36
52
|
|
|
37
53
|
/** structured-output tool 名(与 structured-output 扩展 TOOL_NAME 一致,见 session-runner.ts)。 */
|
|
38
54
|
const STRUCTURED_OUTPUT_TOOL = "structured-output";
|
|
39
55
|
|
|
56
|
+
/** F-1 失败摘要截断长度(字符):错误详情拼进 run 结果 error 字段,防大 payload 撑爆。 */
|
|
57
|
+
const FAILED_SO_SUMMARY_MAX_CHARS = 300;
|
|
58
|
+
|
|
40
59
|
/**
|
|
41
60
|
* 从 toolCalls 提取 structured-output 的 result.details(schema 模式产出)。
|
|
42
61
|
* schema enforcement 保证 agent 调过该 tool(漏调会 steer 重试);这里只做逆向提取。
|
|
43
62
|
* 未调或无 details 返回 undefined。
|
|
44
63
|
*
|
|
64
|
+
* [F-1 失败吞没修复] isError:true 的调用一律跳过——失败调用的 details(pi 失败
|
|
65
|
+
* 路径默认 {})不是通过 schema 校验的产出,把它当 parsedOutput 会让 gate 终止/
|
|
66
|
+
* 模型自弃的 run 以 parsedOutput={} 静默 completed。isError 字段缺失(旧 record/
|
|
67
|
+
* 进行中调用)不误伤——仅显式 true 跳过。
|
|
68
|
+
*
|
|
45
69
|
* 导出以便直接单测(纯函数契约)。
|
|
46
70
|
*/
|
|
47
71
|
export function extractParsedOutput(toolCalls: ToolCall[]): unknown {
|
|
48
72
|
for (let i = toolCalls.length - 1; i >= 0; i--) {
|
|
49
73
|
const tc = toolCalls[i]!;
|
|
50
|
-
if (tc.toolName === STRUCTURED_OUTPUT_TOOL && tc.result?.details !== undefined) {
|
|
74
|
+
if (tc.toolName === STRUCTURED_OUTPUT_TOOL && tc.isError !== true && tc.result?.details !== undefined) {
|
|
51
75
|
return tc.result.details;
|
|
52
76
|
}
|
|
53
77
|
}
|
|
54
78
|
return undefined;
|
|
55
79
|
}
|
|
56
80
|
|
|
81
|
+
/**
|
|
82
|
+
* [F-R1] 中和动态错误摘要中的 stale-context 命中词:对每个 STALE_CONTEXT_PATTERNS
|
|
83
|
+
* 做大小写不敏感的子串替换为 "[redacted]"(分诊 isStaleContextErrorMsg 是
|
|
84
|
+
* toLowerCase 后的子串匹配,故中和也必须大小写不敏感)。
|
|
85
|
+
*
|
|
86
|
+
* 为何只中和动态段(summarizeToolContent 产物)、不中和固定前缀:前缀是本模块
|
|
87
|
+
* 静态文案,已逐字核对无任何 pattern 命中,且由 output-collector.test.ts 的
|
|
88
|
+
* "messages avoid STALE_CONTEXT_PATTERNS substrings" 用例锁定;动态段是
|
|
89
|
+
* 模型/provider 的原始错误文本(可含 "aborted"/"ctx is stale" 等),不受本模块
|
|
90
|
+
* 控制。不中和的话,拼接结果经 collectResult → result.error 流到
|
|
91
|
+
* execute-agent-call 的 isStaleContextErrorMsg 分诊,SO 失败归因被误诊为
|
|
92
|
+
* stale-context → 跳过重试。
|
|
93
|
+
*
|
|
94
|
+
* 导出以便直接单测(纯函数契约)。
|
|
95
|
+
*/
|
|
96
|
+
export function neutralizeStalePatterns(text: string): string {
|
|
97
|
+
let out = text;
|
|
98
|
+
for (const pattern of STALE_CONTEXT_PATTERNS) {
|
|
99
|
+
// pattern 当前均为纯小写词/短语(无 regex 元字符),escape 仅为防未来 pattern
|
|
100
|
+
// 演化引入元字符时静默改变语义。
|
|
101
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
102
|
+
out = out.replace(new RegExp(escaped, "gi"), "[redacted]");
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* schema 模式下无有效 parsedOutput 时的三态失败归因(F-1)。
|
|
109
|
+
*
|
|
110
|
+
* 返回 undefined 表示 toolCalls 里有可用的 parsedOutput(无需归因)。
|
|
111
|
+
* 三态判定优先级:校验失败(有 isError 调用)> 从未调用 SO tool > 调用过但无 details。
|
|
112
|
+
*
|
|
113
|
+
* [MF-1] 三态可重试性矩阵(标记 SSOT 与消费端判定见 execute-agent-call 的
|
|
114
|
+
* DETERMINISTIC_SCHEMA_FAILURE_PREFIX / isDeterministicSchemaFailureMsg):
|
|
115
|
+
* | 归因态 | 带标记 | 可重试性 | 理由 |
|
|
116
|
+
* |------------------------------|-------|---------|------|
|
|
117
|
+
* | ① 从未调用 SO tool | 是 | 不可重试 | 缺 extension 是环境确定性(C1 安装盲区),同环境重试必同结果 |
|
|
118
|
+
* | ② SO 调用 isError(gate 终止/不可满足 schema) | 是 | 不可重试 | 同 schema 重试必同结果(第五轮实测 retry 放大回归:3 attempts/4 子进程/235s) |
|
|
119
|
+
* | ③ 调用过但无 details | 否 | 可重试 | 可能瞬态(details 提取/序列化异常),保留既有重试语义 |
|
|
120
|
+
*
|
|
121
|
+
* 文案约束:不得命中 execute-agent-call 的 STALE_CONTEXT_PATTERNS("aborted" 等
|
|
122
|
+
* 子串)——命中会被误诊为 stale-context 跳过重试。固定前缀(含 MF-1 标记词)静态
|
|
123
|
+
* 无命中(测试锁定);动态段经 neutralizeStalePatterns 中和(F-R1)。
|
|
124
|
+
*
|
|
125
|
+
* 导出以便直接单测(纯函数契约)。
|
|
126
|
+
*/
|
|
127
|
+
export function describeMissingParsedOutput(toolCalls: ToolCall[]): string | undefined {
|
|
128
|
+
if (extractParsedOutput(toolCalls) !== undefined) return undefined;
|
|
129
|
+
const soCalls = toolCalls.filter((tc) => tc.toolName === STRUCTURED_OUTPUT_TOOL);
|
|
130
|
+
if (soCalls.length === 0) {
|
|
131
|
+
// 覆盖独立安装盲区(C1):schema enforcement 依赖 SO extension 注册 tool,
|
|
132
|
+
// 未安装时 agent 无工具可调、steer 无法生效——只能在这里事后暴露。
|
|
133
|
+
// [MF-1] 态① 带确定性标记:缺 extension 是环境确定性,不重试。
|
|
134
|
+
return (
|
|
135
|
+
`${DETERMINISTIC_SCHEMA_FAILURE_PREFIX} ` +
|
|
136
|
+
"Agent finished without producing a structured output: the structured-output tool was never called. " +
|
|
137
|
+
"Recovery: verify the structured-output extension is installed and enabled for this agent, " +
|
|
138
|
+
"and that the agent's final answer conforms to the requested schema."
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const failed = soCalls.filter((tc) => tc.isError === true);
|
|
142
|
+
if (failed.length > 0) {
|
|
143
|
+
const last = failed[failed.length - 1]!;
|
|
144
|
+
// [F-R1] 先截断后中和:截断边界把 pattern 切断时不产生新命中(半截词本就
|
|
145
|
+
// 匹配不到分诊子串),完整落入截断窗口的 pattern 一定被中和。
|
|
146
|
+
const lastErrorSummary = neutralizeStalePatterns(summarizeToolContent(last.result?.content));
|
|
147
|
+
// [F-R4] 失败原因分类:isError 只说明「调用失败」,原因未必是 schema 校验
|
|
148
|
+
// (provider 错误/pi abort/扩展内部错误同样走 isError),硬编码
|
|
149
|
+
// "(schema validation)" 会误导排障。按最后错误的实际内容分类——权威锚点是
|
|
150
|
+
// structured-output 扩展校验失败的固定文案前缀 "Schema validation failed: ..."
|
|
151
|
+
//(structured-output/src/execute.ts),大小写不敏感匹配;其余归为 execution
|
|
152
|
+
// failure。在已中和的摘要上分类:中和仅替换 stale 词,与校验词无交集。
|
|
153
|
+
const failureKind = lastErrorSummary.toLowerCase().includes("validation failed")
|
|
154
|
+
? "schema validation"
|
|
155
|
+
: "execution failure";
|
|
156
|
+
// [MF-1] 态② 带确定性标记:gate 终止/不可满足 schema 同 schema 重试必同结果。
|
|
157
|
+
// 注:failureKind=execution failure(provider 瞬态错误)同样不重试——isError 态
|
|
158
|
+
// 整体从重试面摘除(实测回归即此态循环烧钱),瞬态恢复交由上层 workflow 编排。
|
|
159
|
+
return (
|
|
160
|
+
`${DETERMINISTIC_SCHEMA_FAILURE_PREFIX} ` +
|
|
161
|
+
`Agent finished without a valid structured output: ${failed.length} structured-output call(s) failed ` +
|
|
162
|
+
`(${failureKind}). Last error: ${lastErrorSummary}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
return (
|
|
166
|
+
"Agent finished without a valid structured output: structured-output was called but none of the " +
|
|
167
|
+
"successful calls carried result details."
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** 从 tool result.content 提取可读错误文本([{type:"text",text}] 形态,防御未知形状)。 */
|
|
172
|
+
function summarizeToolContent(content: unknown): string {
|
|
173
|
+
let summary = "(no detail)";
|
|
174
|
+
if (Array.isArray(content)) {
|
|
175
|
+
const texts = content
|
|
176
|
+
.map((item) => (item && typeof item === "object" && "text" in item ? String((item as { text: unknown }).text) : ""))
|
|
177
|
+
.filter((t) => t !== "");
|
|
178
|
+
if (texts.length > 0) summary = texts.join(" ");
|
|
179
|
+
} else if (typeof content === "string" && content !== "") {
|
|
180
|
+
summary = content;
|
|
181
|
+
}
|
|
182
|
+
return summary.length > FAILED_SO_SUMMARY_MAX_CHARS
|
|
183
|
+
? `${summary.slice(0, FAILED_SO_SUMMARY_MAX_CHARS)}...`
|
|
184
|
+
: summary;
|
|
185
|
+
}
|
|
186
|
+
|
|
57
187
|
/**
|
|
58
188
|
* 从 record + args 组装 AgentResult。每个字段来源单一且收口于 record:
|
|
59
189
|
* text ← getFullText(record)(聚合 turns[].text,单一数据源)
|
|
@@ -73,7 +203,8 @@ export function collectResult(
|
|
|
73
203
|
args: CollectResultArgs,
|
|
74
204
|
): AgentResult {
|
|
75
205
|
const toolCalls = getAllToolCalls(record);
|
|
76
|
-
|
|
206
|
+
const parsedOutput = extractParsedOutput(toolCalls);
|
|
207
|
+
const result: AgentResult = {
|
|
77
208
|
text: getFullText(record),
|
|
78
209
|
turns: record.turnCount,
|
|
79
210
|
durationMs: Date.now() - args.startTime,
|
|
@@ -83,6 +214,15 @@ export function collectResult(
|
|
|
83
214
|
toolCalls,
|
|
84
215
|
usage: getTotalUsage(record),
|
|
85
216
|
sessionFile: args.sessionFile,
|
|
86
|
-
parsedOutput
|
|
217
|
+
parsedOutput,
|
|
87
218
|
};
|
|
219
|
+
// [F-1 失败吞没修复] schema 模式下 run 结束仍无有效 parsedOutput 时不得静默
|
|
220
|
+
// success:只在「本将成功」的路径上标注(success=false 的路径已有真实 error/
|
|
221
|
+
// abort 语义,不覆写)。error 经 mapToWorkflowAgentResult(仅 !success 透传)
|
|
222
|
+
// → executeAgentCall(error!==undefined 即 failed)逐层可见。
|
|
223
|
+
if (args.schemaExpected === true && result.success && parsedOutput === undefined) {
|
|
224
|
+
result.success = false;
|
|
225
|
+
result.error = describeMissingParsedOutput(toolCalls) ?? result.error;
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
88
228
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/execution/pi-invocation.ts
|
|
2
2
|
//
|
|
3
3
|
// 定位 pi 二进制并组装 spawn 调用。Core 叶子原语(仅依赖 node 内置)。
|
|
4
4
|
//
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
17
|
import * as path from "node:path";
|
|
18
18
|
|
|
19
|
+
import { isRelayActive, RELAY_ENV_NODE, RELAY_ENV_SCRIPT } from "./relay-env.ts";
|
|
20
|
+
|
|
19
21
|
/** spawn 调用描述符:command + args(透传给 child_process.spawn)。 */
|
|
20
22
|
export interface PiInvocation {
|
|
21
23
|
/** 可执行文件路径(node/bun/pi 二进制)。 */
|
|
@@ -24,6 +26,16 @@ export interface PiInvocation {
|
|
|
24
26
|
args: string[];
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
/** getPiInvocation 可选项。 */
|
|
30
|
+
export interface PiInvocationOptions {
|
|
31
|
+
/**
|
|
32
|
+
* false = 强制直连 spawn 真实 pi(不经 relay 代理)。唯一现役消费点是 PiEngine.probe
|
|
33
|
+
* ——探针意图是 pi 本体可解析性,经 relay 探到的是 runtime 健康,语义错位(E 方案 §5.1)。
|
|
34
|
+
* 缺省(undefined / true)时按 relay env 激活判定走代理。
|
|
35
|
+
*/
|
|
36
|
+
relay?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
/**
|
|
28
40
|
* bun 虚拟文件系统前缀。bun bundle 模式下 process.argv[1] 形如
|
|
29
41
|
* /$bunfs/root/pi——这不是磁盘上的真实文件,不能直接 spawn。
|
|
@@ -43,9 +55,12 @@ function isGenericRuntime(execPath: string): boolean {
|
|
|
43
55
|
* 组装 pi 子进程的 spawn 调用。
|
|
44
56
|
*
|
|
45
57
|
* @param userArgs pi CLI 参数(如 ["--mode", "rpc", "--session-dir", "..."])
|
|
58
|
+
* @param opts 可选项(relay:false 强制直连——probe 用,见 PiInvocationOptions)
|
|
46
59
|
* @returns spawn 描述符(command + 完整 args)
|
|
47
60
|
*
|
|
48
61
|
* 决策链(按优先级):
|
|
62
|
+
* 0. relay 激活(三 env 齐备且未显式禁用)→ <RELAY_NODE> <RELAY_SCRIPT> <userArgs>
|
|
63
|
+
* (E 方案 §5.1:xyz-agent runtime 存在时经代理 spawn,改的是进程拓扑不是 pi 语义)
|
|
49
64
|
* 1. process.argv[1] 是真实磁盘文件且非 bun 虚拟路径 → <execPath> <argv[1]> <userArgs>
|
|
50
65
|
* (复现当前 pi 进程的启动方式,确保扩展/配置/版本一致)
|
|
51
66
|
* 2. execPath 非通用运行时(pi standalone binary)→ <execPath> <userArgs>
|
|
@@ -71,10 +86,25 @@ function currentScriptExists(): boolean {
|
|
|
71
86
|
return scriptExistsCache.exists;
|
|
72
87
|
}
|
|
73
88
|
|
|
74
|
-
export function getPiInvocation(userArgs: string[]): PiInvocation {
|
|
89
|
+
export function getPiInvocation(userArgs: string[], opts?: PiInvocationOptions): PiInvocation {
|
|
75
90
|
const currentScript = process.argv[1];
|
|
76
91
|
const isBunVirtualScript = currentScript?.startsWith(BUN_VIRTUAL_PREFIX);
|
|
77
92
|
|
|
93
|
+
// 分支 0(前置):relay 激活(E 方案 §5.1)——runtime 注入三 env 齐备且未显式禁用时,
|
|
94
|
+
// spawn 目标切换为 <RELAY_NODE> <RELAY_SCRIPT> <原 pi spawnArgs>,形态与分支 1「node +
|
|
95
|
+
// script」同构(command 是执行器、args[0] 是脚本)。ELECTRON_RUN_AS_NODE 不在此补:
|
|
96
|
+
// 打包态执行器为 Electron 时由 runtime 与三 env 同点注入主 pi 进程 env(E-2
|
|
97
|
+
// getRelaySpawnEnv),本进程 {...process.env} 继承即达代理进程,extension 不重复判定。
|
|
98
|
+
// isRelayActive 已保证三 env 非空,下方取局部变量仅为 TS 收窄(isRelayActive 的
|
|
99
|
+
// 收窄不跨函数边界传导)。
|
|
100
|
+
if (opts?.relay !== false && isRelayActive(process.env)) {
|
|
101
|
+
const relayNode = process.env[RELAY_ENV_NODE];
|
|
102
|
+
const relayScript = process.env[RELAY_ENV_SCRIPT];
|
|
103
|
+
if (relayNode !== undefined && relayScript !== undefined) {
|
|
104
|
+
return { command: relayNode, args: [relayScript, ...userArgs] };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
78
108
|
// 分支 1:有真实脚本路径 → 复现启动方式(node <pi-script> <args>)
|
|
79
109
|
if (currentScript && !isBunVirtualScript && currentScriptExists()) {
|
|
80
110
|
return { command: process.execPath, args: [currentScript, ...userArgs] };
|
|
@@ -82,6 +82,17 @@ export interface SubagentRecordEntryData {
|
|
|
82
82
|
chatMode?: boolean;
|
|
83
83
|
/** 执行态信号(residual-fixes):true = 无活进程驱动的 running(轮终/孤儿兜底)。 */
|
|
84
84
|
resumable?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* 实际执行引擎 id(P4 路由留痕,D9①)。缺省(存量 entry)= pi 投影,消费方零迁移。
|
|
87
|
+
*/
|
|
88
|
+
engine?: string;
|
|
89
|
+
/** 引擎 fallback 留痕(probe 失败路由回默认引擎)。GUI 警告条数据源。 */
|
|
90
|
+
engineFallback?: { from: string; reason: string };
|
|
91
|
+
/**
|
|
92
|
+
* 引擎自描述定位符(U1:read 降级链①②级数据源)。引擎无关——sessionRef 整体
|
|
93
|
+
* 透传不枚举内部键(zcode = { sessionId, dbPath });缺省 = pi(存量 entry 零迁移)。
|
|
94
|
+
*/
|
|
95
|
+
engineHandle?: { sessionRef: Record<string, string>; journalPath?: string; poolKey: string };
|
|
85
96
|
}
|
|
86
97
|
|
|
87
98
|
/** SubagentRecord → 自描述 entry data(快照投影,不 mutate 源)。 */
|
|
@@ -114,5 +125,8 @@ export function toSubagentRecordEntry(record: SubagentRecord): SubagentRecordEnt
|
|
|
114
125
|
round: record.round,
|
|
115
126
|
chatMode: record.chatMode,
|
|
116
127
|
resumable: record.resumable,
|
|
128
|
+
engine: record.engine,
|
|
129
|
+
engineFallback: record.engineFallback,
|
|
130
|
+
engineHandle: record.engineHandle,
|
|
117
131
|
};
|
|
118
132
|
}
|
|
@@ -267,9 +267,39 @@ function rebuildEntryRecord(id: string, d: Record<string, unknown>): SubagentRec
|
|
|
267
267
|
sessionFile: undefined,
|
|
268
268
|
chatMode: d.chatMode === true,
|
|
269
269
|
round: num("round"),
|
|
270
|
+
engine: str("engine"),
|
|
271
|
+
engineFallback:
|
|
272
|
+
isEngineFallbackShape(d.engineFallback) ? d.engineFallback : undefined,
|
|
273
|
+
engineHandle: isEngineHandleShape(d.engineHandle) ? d.engineHandle : undefined,
|
|
270
274
|
};
|
|
271
275
|
}
|
|
272
276
|
|
|
277
|
+
/** engineFallback entry 值的运行时 guard(未知 JSON 不裸收)。 */
|
|
278
|
+
function isEngineFallbackShape(v: unknown): v is { from: string; reason: string } {
|
|
279
|
+
if (typeof v !== "object" || v === null) return false;
|
|
280
|
+
const r = v as Record<string, unknown>;
|
|
281
|
+
return typeof r.from === "string" && typeof r.reason === "string";
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** engineHandle entry 值的运行时 guard(未知 JSON 不裸收;形状与 runtime 读侧
|
|
285
|
+
* subagent-engine-history 的 extractRecordEngineHandle 守卫语义对齐:poolKey
|
|
286
|
+
* 必有非空 string + sessionRef 值全 string 才收,journalPath 可选 string)。 */
|
|
287
|
+
function isEngineHandleShape(
|
|
288
|
+
v: unknown,
|
|
289
|
+
): v is { sessionRef: Record<string, string>; journalPath?: string; poolKey: string } {
|
|
290
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
291
|
+
const h = v as Record<string, unknown>;
|
|
292
|
+
if (typeof h.poolKey !== "string" || h.poolKey.length === 0) return false;
|
|
293
|
+
if (typeof h.sessionRef !== "object" || h.sessionRef === null || Array.isArray(h.sessionRef)) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
for (const value of Object.values(h.sessionRef as Record<string, unknown>)) {
|
|
297
|
+
if (typeof value !== "string") return false;
|
|
298
|
+
}
|
|
299
|
+
if (h.journalPath !== undefined && typeof h.journalPath !== "string") return false;
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
|
|
273
303
|
/** stat 戳(不存在 → null)。 */
|
|
274
304
|
function statStamp(p: string): Stamp | null {
|
|
275
305
|
try {
|
|
@@ -1195,6 +1225,10 @@ export class RecordStore {
|
|
|
1195
1225
|
resumable: r.resumable,
|
|
1196
1226
|
// [review round2] worktree 隔离标志:内存源有 handle 或跨重启重建带 hadWorktree 均为 true。
|
|
1197
1227
|
worktree: r.worktreeHandle !== undefined || r.hadWorktree === true,
|
|
1228
|
+
engine: r.engine,
|
|
1229
|
+
engineFallback: r.engineFallback,
|
|
1230
|
+
// U2:engineHandle 经 entry 持久化(register/archive 双写点均经本投影),无则 undefined 自然省略
|
|
1231
|
+
engineHandle: r.engineHandle,
|
|
1198
1232
|
};
|
|
1199
1233
|
}
|
|
1200
1234
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* subagent relay 通道 env 名与协议常量 SSOT(docs/architecture/subagent-realtime-channel.md §5.1/§5.2/§3.1)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么独立成模块:三方消费同一份常量——extension 侧(pi-invocation 激活判定 /
|
|
5
|
+
* buildChildEnv 归属写入)、runtime 侧(env 注入与镜像校验)、代理脚本 relay.mjs
|
|
6
|
+
* (零依赖脚本不能 import workspace 包,只能内嵌镜像,镜像一致性由 conformance
|
|
7
|
+
* relay 变体断言锁定)。禁各处手写字符串。
|
|
8
|
+
*
|
|
9
|
+
* env 语义:SOCKET/NODE/SCRIPT 三者同时非空 = relay 激活(全有或全无,无中间态);
|
|
10
|
+
* SESSION_ID/RECORD_ID 是 tee 帧归属键(缺失由代理握手前自检拒绝,退出码 13)。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const RELAY_ENV_SOCKET = 'XYZ_SUBAGENT_RELAY_SOCKET';
|
|
14
|
+
export const RELAY_ENV_NODE = 'XYZ_SUBAGENT_RELAY_NODE';
|
|
15
|
+
export const RELAY_ENV_SCRIPT = 'XYZ_SUBAGENT_RELAY_SCRIPT';
|
|
16
|
+
export const RELAY_ENV_SESSION_ID = 'XYZ_SUBAGENT_RELAY_SESSION_ID';
|
|
17
|
+
export const RELAY_ENV_RECORD_ID = 'XYZ_SUBAGENT_RELAY_RECORD_ID';
|
|
18
|
+
|
|
19
|
+
/** relay 协议版本(握手帧 v 字段;runtime 与代理同包分发,不匹配=安装损坏)。 */
|
|
20
|
+
export const RELAY_PROTOCOL_VERSION = 1;
|
|
21
|
+
|
|
22
|
+
/** 代理专用退出码(extension 侧表现为「子进程非零退出」→ engine_run_failed 语义)。 */
|
|
23
|
+
export const RELAY_EXIT_CODES = {
|
|
24
|
+
/** 握手被拒:协议版本不匹配(安装损坏,重装应用)。 */
|
|
25
|
+
VERSION_MISMATCH: 10,
|
|
26
|
+
/** relay socket 不可达(runtime 未运行或已重启)。 */
|
|
27
|
+
SOCKET_UNREACHABLE: 11,
|
|
28
|
+
/** socket 中途断开(runtime 崩溃等)——代理生命线断即退。 */
|
|
29
|
+
SOCKET_CLOSED: 12,
|
|
30
|
+
/** 归属 env(SESSION_ID/RECORD_ID)缺失——防无归属帧污染广播。 */
|
|
31
|
+
MISSING_IDENTITY: 13,
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
/** 激活判定:三 env 同时非空才走 relay,任一缺失回落直连 spawn 真实 pi(TUI/独立 pi 零回归)。 */
|
|
35
|
+
export function isRelayActive(env: NodeJS.ProcessEnv | Record<string, string | undefined>): boolean {
|
|
36
|
+
return Boolean(env[RELAY_ENV_SOCKET] && env[RELAY_ENV_NODE] && env[RELAY_ENV_SCRIPT]);
|
|
37
|
+
}
|