@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
|
@@ -8,6 +8,21 @@
|
|
|
8
8
|
// 设计:默认与主 agent 同模型(零配置)。只有「有人显式指定 model」时才查
|
|
9
9
|
// registry 做解析 + 鉴权校验。thinkingLevel 同链路,无指定时 undefined。
|
|
10
10
|
|
|
11
|
+
// [U1 ModelRef 全等裁决] THINKING_ORDER / ThinkingLevel / strip / 裁决入口收拢到
|
|
12
|
+
// shared/model-ref.ts(单一权威),此处 re-export 保持既有 import 路径不变
|
|
13
|
+
//(subagent-tool / tool-workflow 的 schema 枚举从本模块派生)。
|
|
14
|
+
import {
|
|
15
|
+
THINKING_ORDER,
|
|
16
|
+
assertCanonicalModelRef,
|
|
17
|
+
modelRefFromVerified,
|
|
18
|
+
} from "../shared/model-ref";
|
|
19
|
+
|
|
20
|
+
export { THINKING_ORDER };
|
|
21
|
+
export type { ThinkingLevel } from "../shared/model-ref";
|
|
22
|
+
|
|
23
|
+
/** 解析失败时错误信息列出的可用模型上限(防超长错误信息)。 */
|
|
24
|
+
const MODEL_LIST_LIMIT = 20;
|
|
25
|
+
|
|
11
26
|
/**
|
|
12
27
|
* ModelRegistry 的最小接口(duck-typed,测试可 mock)。
|
|
13
28
|
* 字段结构与 Pi SDK 的 ctx.modelRegistry 对齐。
|
|
@@ -48,6 +63,12 @@ export interface AgentConfig {
|
|
|
48
63
|
thinkingLevel?: string;
|
|
49
64
|
/** 默认 background 模式(true 时无显式 wait 走 background)。 */
|
|
50
65
|
defaultBackground?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* 执行引擎 id(agent .md frontmatter engine 字段,D9 per-agent 主通道)。
|
|
68
|
+
* 解析期已对注册表校验(未注册 id 在 agent-registry 抛 EngineNotFoundError);
|
|
69
|
+
* 执行侧(P4 路由层)按 调用参数 > 本字段 > 全局默认 三层取值。
|
|
70
|
+
*/
|
|
71
|
+
engine?: string;
|
|
51
72
|
}
|
|
52
73
|
|
|
53
74
|
/** 解析结果(model 实例 + 生效的 thinkingLevel)。 */
|
|
@@ -60,13 +81,8 @@ export interface ResolvedModel {
|
|
|
60
81
|
// 常量
|
|
61
82
|
// ============================================================
|
|
62
83
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* 从本常量派生,避免两处硬编码不同步。 */
|
|
66
|
-
export const THINKING_ORDER = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
67
|
-
|
|
68
|
-
/** 解析失败时错误信息列出的可用模型上限(防超长错误信息)。 */
|
|
69
|
-
const MODEL_LIST_LIMIT = 20;
|
|
84
|
+
// MODEL_LIST_LIMIT 与 THINKING_ORDER 随 suggestSimilarModels/lookupModel 一并迁入
|
|
85
|
+
// shared/model-ref.ts(U1 裁决单一入口)。
|
|
70
86
|
|
|
71
87
|
// ============================================================
|
|
72
88
|
// 解析
|
|
@@ -123,7 +139,10 @@ export function resolveModel(
|
|
|
123
139
|
}
|
|
124
140
|
|
|
125
141
|
// 3. 主 agent model(直接透传)。无显式 thinking → 兜底最高可用档。
|
|
142
|
+
// [U1 D2 豁免口径] ctxModel 是运行时已验证的 ModelInfo 对象,豁免 registry 存在性
|
|
143
|
+
// 复查与 auth 校验;但孪生守卫同等适用(registry 含大小写孪生时拒绝放行,modelRefFromVerified)。
|
|
126
144
|
if (ctxModel) {
|
|
145
|
+
modelRefFromVerified(ctxModel, modelRegistry);
|
|
127
146
|
return {
|
|
128
147
|
model: ctxModel,
|
|
129
148
|
thinkingLevel: paramOverride?.thinkingLevel ?? agentConfig?.thinkingLevel
|
|
@@ -144,9 +163,12 @@ export function resolveModel(
|
|
|
144
163
|
/**
|
|
145
164
|
* lookup + auth 校验 + thinkingLevel clamp。显式指定但失败 → 抛错(不降级)。
|
|
146
165
|
*
|
|
166
|
+
* [U1 D1] 模型串裁决经 assertCanonicalModelRef 单一入口(strip 后缀 → provider 精确 →
|
|
167
|
+
* 全等匹配 → 孪生守卫;未命中同步抛错并附问句式纠错候选)。放行即与 registry 条目全等。
|
|
168
|
+
*
|
|
147
169
|
* 错误信息区分两种失败(避免误导排查方向):
|
|
148
|
-
* - model
|
|
149
|
-
* - model
|
|
170
|
+
* - model 非全等(不存在/大小写不符/孪生歧义)→ assertCanonicalModelRef 的问句式报错
|
|
171
|
+
* - model 全等命中但 auth 未配置 → 提示在 models.json 配置鉴权
|
|
150
172
|
*/
|
|
151
173
|
function lookupAndResolve(
|
|
152
174
|
modelStr: string,
|
|
@@ -154,11 +176,14 @@ function lookupAndResolve(
|
|
|
154
176
|
registry: ModelRegistryLike,
|
|
155
177
|
source: "paramOverride" | "agentConfig",
|
|
156
178
|
): ResolvedModel {
|
|
157
|
-
const
|
|
179
|
+
const ref = assertCanonicalModelRef(modelStr, registry, { source });
|
|
180
|
+
const model = registry.find(ref.provider, ref.id);
|
|
158
181
|
if (!model) {
|
|
182
|
+
// 裁决已按 getAvailable 全等命中;find 独立实现(duck-typed mock 可能不同源)时的
|
|
183
|
+
// 类型收窄兜底,非预期路径。
|
|
159
184
|
throw new Error(
|
|
160
|
-
`Model "${modelStr}" (${source})
|
|
161
|
-
|
|
185
|
+
`Model "${modelStr}" (${source}) passed the canonical ref check but registry.find missed it ` +
|
|
186
|
+
`(registry snapshot inconsistent). Retry with an exact entry from the available models list.`,
|
|
162
187
|
);
|
|
163
188
|
}
|
|
164
189
|
if (!registry.hasConfiguredAuth(model)) {
|
|
@@ -191,53 +216,6 @@ function maxThinkingForModel(
|
|
|
191
216
|
return model.reasoning ? "xhigh" : undefined;
|
|
192
217
|
}
|
|
193
218
|
|
|
194
|
-
/**
|
|
195
|
-
* 解析 "provider/modelId" 并查 registry。
|
|
196
|
-
*
|
|
197
|
-
* 容错:剥离尾部 ":thinkingLevel" 后缀(off/minimal/low/medium/high/xhigh)。
|
|
198
|
-
* 原因:LLM 常把平台复合标识 "provider/modelId:thinkingLevel"(如
|
|
199
|
-
* "deepseek-router/ds-pro:xhigh")整体当 model 参数传入。registry 仅存
|
|
200
|
-
* "provider/modelId"(无后缀),不剥离则 modelId="ds-pro:xhigh" 查不到。
|
|
201
|
-
* 剥离后 thinkingLevel 仍由独立的 thinkingLevel 参数/resolveThinkingLevel 处理。
|
|
202
|
-
*
|
|
203
|
-
* modelId 可含 /,按第一个 / 分割 provider 与 modelId。
|
|
204
|
-
*/
|
|
205
|
-
function lookupModel(modelStr: string, registry: ModelRegistryLike): ModelInfo | undefined {
|
|
206
|
-
const cleanStr = stripThinkingSuffix(modelStr);
|
|
207
|
-
const idx = cleanStr.indexOf("/");
|
|
208
|
-
if (idx <= 0) return undefined;
|
|
209
|
-
return registry.find(cleanStr.slice(0, idx), cleanStr.slice(idx + 1));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* 剥离模型字符串尾部 ":thinkingLevel" 后缀(如 "ds-pro:xhigh" → "ds-pro")。
|
|
214
|
-
* 仅匹配合法 thinking level,避免误剥 "foo:bar" 这类无关冒号。
|
|
215
|
-
* 返回去除后缀的字符串;无后缀则原样返回。
|
|
216
|
-
*/
|
|
217
|
-
function stripThinkingSuffix(modelStr: string): string {
|
|
218
|
-
// THINKING_ORDER 含 off/minimal/low/medium/high/xhigh,按长度降序拼正则避免短串误匹配
|
|
219
|
-
const alt = THINKING_ORDER.slice().sort((a, b) => b.length - a.length).join("|");
|
|
220
|
-
return modelStr.replace(new RegExp(`:(${alt})$`), "");
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* 为 not-found 错误生成「相近可用 model」建议,辅助定位拼写错误。
|
|
225
|
-
* 策略:取 provider/modelId 的末段,与每个可用 model 的末段做小写包含匹配,
|
|
226
|
-
* 命中则列出。无命中则列出前 N 个全部可用 model(兜底)。空 registry 不列。
|
|
227
|
-
*/
|
|
228
|
-
function suggestSimilarModels(modelStr: string, registry: ModelRegistryLike): string {
|
|
229
|
-
const available = registry.getAvailable();
|
|
230
|
-
if (available.length === 0) return "Registry has no available models.";
|
|
231
|
-
const target = modelStr.split("/").pop()?.toLowerCase() ?? "";
|
|
232
|
-
// ponytail: 末段子串包含足够定位拼写错误,无需编辑距离/相似度库
|
|
233
|
-
const similar = available
|
|
234
|
-
.filter((m) => m.id.toLowerCase().includes(target) || target.includes(m.id.toLowerCase()))
|
|
235
|
-
.map((m) => `${m.provider}/${m.id}`)
|
|
236
|
-
.slice(0, MODEL_LIST_LIMIT);
|
|
237
|
-
const list = similar.length > 0 ? similar : available.slice(0, MODEL_LIST_LIMIT).map((m) => `${m.provider}/${m.id}`);
|
|
238
|
-
return `Check the model string (maybe a typo, or a ":thinkingLevel" suffix that should be passed via the thinkingLevel param instead). Similar available models:\n ${list.join("\n ")}`;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
219
|
/**
|
|
242
220
|
* 从 model.thinkingLevelMap 提取可用级别,clamp 到最高可用。
|
|
243
221
|
* model.reasoning === false → undefined(不支持 thinking)
|
|
@@ -2,16 +2,30 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Background 完成回注主对话。sync 不用(调用方还在 await,结果直接返回)。
|
|
4
4
|
//
|
|
5
|
-
//
|
|
5
|
+
// 职责(U2 后):
|
|
6
6
|
// - buildLlmContent:格式化通知文案(本文件唯一逻辑职责)
|
|
7
|
-
// - createNotifier
|
|
8
|
-
//
|
|
7
|
+
// - createNotifier:薄工厂——ledger 装配时(bindNotifyLedgerHost 已注入)走
|
|
8
|
+
// 四步生命周期(写账 → courier 边沿投递 → 回执销账 → notifyId 幂等重放,
|
|
9
|
+
// notify-ledger.ts);未装配时退回 @xyz-agent/session-delivery 内核路径
|
|
10
|
+
// (合并窗口 / 去重 / 退避 / flush 委托内核,旧装配 / 无 ledger 测试兼容)
|
|
9
11
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
+
// 投递通道(D5 单通道化):ledger 路径经 courier 在 settled 边沿直达
|
|
13
|
+
// pi.sendMessage({triggerTurn:true});内核路径的 port.send 同样只传
|
|
14
|
+
// {triggerTurn:true}——steer / followUp / nextTurn 通道已全部删除(nextTurn 的
|
|
15
|
+
// 唯一 drain 点在 session.prompt() 内,主 agent 长 streaming 场景下无限期滞留,
|
|
16
|
+
// 设计 D5 实测证伪);busy 场景由 ledger(settled 边沿 + isIdle 二次复查)或内核
|
|
17
|
+
// settled 订阅驱动在空闲边沿投递。
|
|
12
18
|
|
|
19
|
+
import { getLogger } from "@zhushanwen/pi-extension-logger";
|
|
13
20
|
import { createDelivery, type DeliveryHandle, type DeliveryPort } from "@xyz-agent/session-delivery";
|
|
14
21
|
|
|
22
|
+
import { deriveOutcome } from "./execution-record.ts";
|
|
23
|
+
import { getBoundNotifyLedger, NOTIFY_CUSTOM_TYPE } from "./notify-ledger.ts";
|
|
24
|
+
import type { ClosedReason, ExecutionOutcome } from "./types.ts";
|
|
25
|
+
|
|
26
|
+
/** U4:delivery warn 出口注入用——与 index.ts 共享同一具名 logger 单例。 */
|
|
27
|
+
const notifyLogger = getLogger("subagents");
|
|
28
|
+
|
|
15
29
|
/**
|
|
16
30
|
* 一条待发送的完成通知记录。
|
|
17
31
|
* SP-1: done/failed/crashed 合并为 closed + closedReason L2 子枚举。
|
|
@@ -24,8 +38,15 @@ export interface BgNotifyRecord {
|
|
|
24
38
|
* toNotifyRecord 守卫放行后经此联合穷尽。
|
|
25
39
|
*/
|
|
26
40
|
status: "running" | "closed";
|
|
27
|
-
/** L2 关闭原因子枚举(仅 status="closed"
|
|
28
|
-
closedReason?:
|
|
41
|
+
/** L2 关闭原因子枚举(仅 status="closed" 时有意义)。内部诊断 + outcome 兑底派生输入。 */
|
|
42
|
+
closedReason?: ClosedReason;
|
|
43
|
+
/**
|
|
44
|
+
* 终态三态对外语义(U3 C-outcome)。notify() 投影边界物化:closed 入参缺省时按
|
|
45
|
+
* deriveOutcome(closedReason, error) 兑底填充(所有可达流程下与 completeRecord
|
|
46
|
+
* 冻结的 record.outcome 等价——toNotifyRecord 构造点在 completeRecord 之后;该
|
|
47
|
+
* 构造点属 U3 领地外,不透传本字段)。buildLlmContent 与 bg-notify-render 只读本字段。
|
|
48
|
+
*/
|
|
49
|
+
outcome?: ExecutionOutcome;
|
|
29
50
|
agent: string;
|
|
30
51
|
/** 执行所用 model(RecordSnapshot.model),用于完成通知显示。 */
|
|
31
52
|
model?: string;
|
|
@@ -47,6 +68,10 @@ export interface BgNotifyRecord {
|
|
|
47
68
|
* "Full transcript: <path>" 指针行,父 LLM 可按需读全文;one-shot 不透传,
|
|
48
69
|
* 通知输出逐字节不变。缺失时 buildLlmContent 省略整行。 */
|
|
49
70
|
sessionFile?: string;
|
|
71
|
+
/** [U2] 通知身份键(投影边界物化 = dedupe key:`id` / `id:round`)。账本条目 /
|
|
72
|
+
* 回执匹配 / 幂等去重共用——details 携带(不进文案,G4 字节锁定不受影响),
|
|
73
|
+
* 重复注入条目凭此可识别为同一条(G2 at-least-once 幂等键)。 */
|
|
74
|
+
notifyId?: string;
|
|
50
75
|
/** [C-2] close 终态通知的轮次统计(文案 "completed after N rounds." 用)。
|
|
51
76
|
* 仅 chatMode close 语义(notifyClosed)构造时携带——此时 dedup 身份 round 已被
|
|
52
77
|
* 置 undefined(与轮次通知的 id:round key 区分,终态不被吞),轮数改由本字段进
|
|
@@ -57,10 +82,10 @@ export interface BgNotifyRecord {
|
|
|
57
82
|
/** notifier 依赖的宿主最小接口(解耦,便于测试)。
|
|
58
83
|
* 迁移后:仅用于构造 DeliveryPort 的底层依赖。 */
|
|
59
84
|
export interface NotifierHost {
|
|
60
|
-
/**
|
|
85
|
+
/** 注入消息到主对话(U2 单通道:options 只接受 triggerTurn——多通道投递已删)。 */
|
|
61
86
|
sendMessage(
|
|
62
87
|
message: { customType: string; content: string; display: boolean; details?: unknown },
|
|
63
|
-
options?: { triggerTurn?: boolean
|
|
88
|
+
options?: { triggerTurn?: boolean },
|
|
64
89
|
): void;
|
|
65
90
|
/** 是否还有 running 的 background 任务(用于滑动窗口立即 flush 判断)。 */
|
|
66
91
|
hasRunningBackground(): boolean;
|
|
@@ -77,9 +102,6 @@ export interface NotifierHost {
|
|
|
77
102
|
onAgentSettled?(handler: () => void): void;
|
|
78
103
|
}
|
|
79
104
|
|
|
80
|
-
/** 发送给主对话的 customType(bg-notify-render 消费)。 */
|
|
81
|
-
const NOTIFY_CUSTOM_TYPE = "subagent-bg-notify";
|
|
82
|
-
|
|
83
105
|
/**
|
|
84
106
|
* 将 BgNotifyRecord 格式化为 LLM 可读的 notification content。
|
|
85
107
|
*
|
|
@@ -98,20 +120,19 @@ function buildLlmContent(record: BgNotifyRecord): string {
|
|
|
98
120
|
: "";
|
|
99
121
|
switch (record.status) {
|
|
100
122
|
case "closed": {
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
123
|
+
// U3 C-outcome:终态文案只读 outcome(notify() 投影边界已物化;?? 兑底为防御
|
|
124
|
+
// 完整性——单一权威函数,非同构重写)。判定先于 patchFile——失败轮也会写
|
|
125
|
+
// patchFile(doFinalizeRecord Step 0 对 worktreeHandle 无条件 collectPatch),
|
|
126
|
+
// failed 分支不展示 patch 提示,否则 worktree 失败并存时 LLM 被告知 completed
|
|
127
|
+
// (历史 bug 存档见 deriveOutcome 注释)。
|
|
128
|
+
const outcome = record.outcome ?? deriveOutcome(record.closedReason, record.error);
|
|
129
|
+
if (outcome === "cancelled") {
|
|
104
130
|
return `Subagent "${agent}" (${id}) cancelled.`;
|
|
105
131
|
}
|
|
106
|
-
|
|
107
|
-
// patchFile(doFinalizeRecord Step 0 对 worktreeHandle 无条件 collectPatch),若
|
|
108
|
-
// patch 分支在前,gc 失败 + worktree 并存时 LLM 被告知 completed。顺序与三处
|
|
109
|
-
// 同构契约的另外两处一致(shared/subagent.ts deriveClosedDisplay、
|
|
110
|
-
// bg-notify-render.ts renderRecordLines:cancelled → gc+error → patch/result)。
|
|
111
|
-
if (record.error && reason === "gc") {
|
|
132
|
+
if (outcome === "failed") {
|
|
112
133
|
return `Subagent "${agent}" (${id}) failed: ${record.error}`;
|
|
113
134
|
}
|
|
114
|
-
//
|
|
135
|
+
// 成功完成或通用结束:展示结果。
|
|
115
136
|
// [C-2] chatMode close 终态通知附轮次统计(设计 D2 路径①"completed after N rounds")。
|
|
116
137
|
// totalRounds 仅 close 语义携带(notifyClosed);one-shot 完成通知不设置(round 无轮次
|
|
117
138
|
// 语义),文案保持 "completed. Result:" 逐字节(G4 硬约束,one-shot 字节锁测试锚定)。
|
|
@@ -150,10 +171,13 @@ export interface BgNotifier {
|
|
|
150
171
|
}
|
|
151
172
|
|
|
152
173
|
/**
|
|
153
|
-
* 创建 Background
|
|
174
|
+
* 创建 Background 完成通知器(薄工厂)。
|
|
154
175
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
176
|
+
* ledger 装配时(bindNotifyLedgerHost):四步生命周期接线(写账 → courier 边沿
|
|
177
|
+
* 投递 → 回执销账 → notifyId 幂等,见 notify-ledger.ts)。未装配时:装配
|
|
178
|
+
* @xyz-agent/session-delivery 内核——gate(isIdle 退避)/ 合批窗口(滑动 60s)/
|
|
179
|
+
* dedup(按 id:round)/ flush / shutdown flush 均委托内核。
|
|
180
|
+
* 本函数职责:BgNotifyRecord → 预格式化 content + notifyId 物化。
|
|
157
181
|
*
|
|
158
182
|
* @param host 宿主能力注入(pi.sendMessage + hasRunningBackground + isIdle)
|
|
159
183
|
* @returns BgNotifier 接口(与旧类同形)
|
|
@@ -187,7 +211,12 @@ export function createNotifier(host: NotifierHost): BgNotifier {
|
|
|
187
211
|
disposed = true;
|
|
188
212
|
};
|
|
189
213
|
},
|
|
190
|
-
send: (msg,
|
|
214
|
+
send: (msg, _intent) => {
|
|
215
|
+
// D5 单通道:投递意图唯一化——steer/followUp/nextTurn 多通道全部删除,
|
|
216
|
+
// 唯一发送形态 = sendCustomMessage({triggerTurn:true})。busy 场景由 ledger
|
|
217
|
+
//(settled 边沿 + isIdle 二次复查)或内核 settled 订阅在空闲边沿驱动,
|
|
218
|
+
// 不在此层分流。返回 void = 受理成功(同步无异常;SendReceipt 扩展位留待
|
|
219
|
+
// 升级方接入)。
|
|
191
220
|
host.sendMessage(
|
|
192
221
|
{
|
|
193
222
|
customType: NOTIFY_CUSTOM_TYPE,
|
|
@@ -195,9 +224,7 @@ export function createNotifier(host: NotifierHost): BgNotifier {
|
|
|
195
224
|
display: true,
|
|
196
225
|
details: msg.payload.kind === "custom" ? msg.payload.details : undefined,
|
|
197
226
|
},
|
|
198
|
-
|
|
199
|
-
? { triggerTurn: true, deliverAs: "steer" }
|
|
200
|
-
: { triggerTurn: true, deliverAs: "followUp" },
|
|
227
|
+
{ triggerTurn: true },
|
|
201
228
|
);
|
|
202
229
|
},
|
|
203
230
|
};
|
|
@@ -216,6 +243,10 @@ export function createNotifier(host: NotifierHost): BgNotifier {
|
|
|
216
243
|
// 同 key 可再入)。当前 key 空间(id / id:round,id 每 spawn 唯一)无实际差异;
|
|
217
244
|
// 后续复用方勿按「60s 内不重复」假设接入(同 key 通知会被永久吞)。
|
|
218
245
|
dedupe: { maxKeys: 1000 },
|
|
246
|
+
// U4 warn 出口参数化:内核投递失败警告接 extensionLogger(appendEntry 落
|
|
247
|
+
// session JSONL + XYZ_AGENT_DEBUG 落 <dataDir>/logs/),不再走 console.warn
|
|
248
|
+
// (stderr tee 不到日志盘——排查无痕,设计 §5 U4)。
|
|
249
|
+
warn: (msg, err) => notifyLogger.warn(msg, err),
|
|
219
250
|
});
|
|
220
251
|
let handle: DeliveryHandle = createHandle();
|
|
221
252
|
|
|
@@ -223,29 +254,68 @@ export function createNotifier(host: NotifierHost): BgNotifier {
|
|
|
223
254
|
notify(record: BgNotifyRecord): void {
|
|
224
255
|
if (disposed) return;
|
|
225
256
|
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
|
|
257
|
+
// U3 C-outcome:投影边界物化 outcome——closed payload 缺省时按单一权威
|
|
258
|
+
// deriveOutcome 兑底填充,content 与 details(GUI pane 消费)均携带一等 outcome;
|
|
259
|
+
// 所有可达流程下与 record.outcome 等价(toNotifyRecord 在 completeRecord 之后
|
|
260
|
+
// 构造)。running(轮次通知)语义上无 outcome,不物化。消源自 record 的浅拷贝
|
|
261
|
+
// ——不改写入方对象(BgNotifyRecord 由调用方持有)。notifyId 同批物化(U2:
|
|
262
|
+
// dedupe key 与账本身份键同源,details 携带供回执匹配)。
|
|
263
|
+
const notifyId = record.round != null ? `${record.id}:${record.round}` : record.id;
|
|
264
|
+
const payload: BgNotifyRecord =
|
|
265
|
+
record.status === "closed"
|
|
266
|
+
? { ...record, outcome: record.outcome ?? deriveOutcome(record.closedReason, record.error), notifyId }
|
|
267
|
+
: { ...record, notifyId };
|
|
229
268
|
|
|
269
|
+
const content = buildLlmContent(payload);
|
|
270
|
+
|
|
271
|
+
// U2 B-ledger 四步接线(设计 D4/D5):账本装配时①写账(appendEntry 先于一切
|
|
272
|
+
// 投递尝试)→ ②courier 边沿投递(settled 边沿 + 120s 看门狗 + isIdle 二次
|
|
273
|
+
// 复查)→ ③回执销账 / ④重放幂等均在 ledger 内。record 返回 false = 同
|
|
274
|
+
// notifyId 已在账或已销账(幂等去重,含重启恢复后的已送达账号零重发)。
|
|
275
|
+
const ledger = getBoundNotifyLedger();
|
|
276
|
+
if (ledger) {
|
|
277
|
+
if (!ledger.record(notifyId, content, payload)) return;
|
|
278
|
+
ledger.attemptDeliver();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 无 ledger 装配(旧装配 / 部分测试):内核路径——合批窗口 / settled 边沿 /
|
|
283
|
+
// dedupe(按 notifyId,key 规则与旧 dedupeKey 一致:id 或 id:round)不变。
|
|
284
|
+
// [C-ext-06 配套] 降级留痕:bind 缺失(含 jiti 单例分裂致跨模块读不到绑定的
|
|
285
|
+
// 失效形态)本是无声分岔,U2 at-least-once 在此退化为内核路径——warn 一条
|
|
286
|
+
// 供诊断检索,不改变向后兼容行为。
|
|
287
|
+
notifyLogger.warn("notify ledger not bound, falling back to delivery kernel path (at-most-once)", {
|
|
288
|
+
notifyId,
|
|
289
|
+
});
|
|
230
290
|
handle.send({
|
|
231
291
|
payload: {
|
|
232
292
|
kind: "custom",
|
|
233
293
|
customType: NOTIFY_CUSTOM_TYPE,
|
|
234
|
-
content
|
|
294
|
+
content,
|
|
235
295
|
display: true,
|
|
236
|
-
details:
|
|
296
|
+
details: payload,
|
|
237
297
|
},
|
|
238
|
-
dedupeKey,
|
|
298
|
+
dedupeKey: notifyId,
|
|
239
299
|
});
|
|
240
300
|
},
|
|
241
301
|
|
|
242
302
|
flushPendingNotifications(): void {
|
|
303
|
+
// ledger 路径:立即投递尝试(isIdle 复查,busy 则挂 pending 等边沿——账已落盘,
|
|
304
|
+
// 重启恢复兑底);内核路径:flush。
|
|
305
|
+
const ledger = getBoundNotifyLedger();
|
|
306
|
+
if (ledger) {
|
|
307
|
+
ledger.attemptDeliver();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
243
310
|
handle.flush();
|
|
244
311
|
},
|
|
245
312
|
|
|
246
313
|
dispose(): void {
|
|
247
314
|
disposed = true;
|
|
248
315
|
handle.dispose();
|
|
316
|
+
// ledger 销毁(清看门狗 + 摘模块级绑定;settled 回调由 disposed 标志静默)。
|
|
317
|
+
// 未 bind 时 no-op。bind 归 index.ts session_start 装配,对称免受。
|
|
318
|
+
getBoundNotifyLedger()?.dispose();
|
|
249
319
|
},
|
|
250
320
|
|
|
251
321
|
revive(): void {
|