@zhushanwen/pi-subagent-workflow 8.13.0 → 8.14.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 +10 -10
- package/src/host/inflight-reporter.ts +32 -14
- package/src/injectors/model-list-injector.ts +2 -1
- package/src/interface/format.ts +5 -2
- package/src/interface/subagent-tool-schema.ts +23 -28
- package/src/interface/subagent-tool.ts +8 -9
- package/src/jsonl-run-store.ts +1 -11
- package/src/session-lifecycle.ts +2 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-subagent-workflow",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"description": "Unified subagent execution and multi-agent workflow orchestration for Pi — spawned-process agent runtime with sync/background modes, stateful workflow management with persistence, state machine, and execution tracing.",
|
|
@@ -54,21 +54,21 @@
|
|
|
54
54
|
]
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@xyz-agent/extension-protocol": "0.
|
|
58
|
-
"@xyz-agent/session-delivery": "0.
|
|
59
|
-
"@zhushanwen/pi-ext-guards": "0.
|
|
60
|
-
"@zhushanwen/
|
|
61
|
-
"@zhushanwen/subagent-
|
|
62
|
-
"@zhushanwen/
|
|
63
|
-
"@zhushanwen/
|
|
57
|
+
"@xyz-agent/extension-protocol": "0.11.0",
|
|
58
|
+
"@xyz-agent/session-delivery": "0.4.1",
|
|
59
|
+
"@zhushanwen/pi-ext-guards": "0.4.0",
|
|
60
|
+
"@zhushanwen/subagent-core": "0.10.1",
|
|
61
|
+
"@zhushanwen/pi-subagent-cli": "0.4.0",
|
|
62
|
+
"@zhushanwen/zcode-subagent-cli": "0.3.1",
|
|
63
|
+
"@zhushanwen/pi-extension-logger": "0.6.0"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"@earendil-works/pi-ai": "^0.84.4",
|
|
67
67
|
"@earendil-works/pi-coding-agent": "^0.84.4",
|
|
68
68
|
"@earendil-works/pi-tui": "^0.84.4",
|
|
69
69
|
"typebox": "*",
|
|
70
|
-
"@zhushanwen/pi-pending-notifications": "0.7.
|
|
71
|
-
"@zhushanwen/pi-structured-output": "5.1.
|
|
70
|
+
"@zhushanwen/pi-pending-notifications": "0.7.2",
|
|
71
|
+
"@zhushanwen/pi-structured-output": "5.1.7"
|
|
72
72
|
},
|
|
73
73
|
"peerDependenciesMeta": {
|
|
74
74
|
"@earendil-works/pi-coding-agent": {
|
|
@@ -37,9 +37,10 @@
|
|
|
37
37
|
// (30min 有界),不丢 errs-safe 兜底。
|
|
38
38
|
|
|
39
39
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
40
|
-
import { SUBAGENT_INFLIGHT_MARKER, isInFlightReportAck } from "@xyz-agent/extension-protocol";
|
|
40
|
+
import { SUBAGENT_INFLIGHT_MARKER, callMarkerRpc, isInFlightReportAck } from "@xyz-agent/extension-protocol";
|
|
41
41
|
import { getInFlightSnapshot } from "@zhushanwen/subagent-core";
|
|
42
42
|
import { getLogger } from "@zhushanwen/pi-extension-logger";
|
|
43
|
+
import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
43
44
|
|
|
44
45
|
/** select 通道级超时(控制面单请求,秒级校准——超时默认原则规则 19)。取值对齐
|
|
45
46
|
* plugin-bridge 启动 sync 的 2s 自愈闸:session_start 首帧可能早于 runtime adapter
|
|
@@ -142,25 +143,36 @@ export function createInFlightReporter(opts: InFlightReporterOpts = {}): InFligh
|
|
|
142
143
|
sessionId: getSessionId(active),
|
|
143
144
|
emittedAt: Date.now(),
|
|
144
145
|
});
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
// 发送+折叠半边走 protocol 的 callMarkerRpc 原语(D8,fire-and-forget:void 发起
|
|
147
|
+
// 不变):ok:false 四态(cancelled/timeout/channel-error/non-json)统一折叠进下方
|
|
148
|
+
// 延迟重试路径;送达判据 = ack 全等匹配(不是 JSON 消费),留在本侧。原语的失败
|
|
149
|
+
// 留痕经注入的 log 承载本侧「首败 warn / 后续 debug」防刷屏策略。
|
|
150
|
+
// guiCtx = ExtensionContext 的 GuiContext 最小子集(ask-user runRpcInteraction 同款
|
|
151
|
+
// 先例:ui.custom 泛型签名静态不兼容,callMarkerRpc 只读 ui.select)。
|
|
152
|
+
const guiCtx = {
|
|
153
|
+
mode: active.mode,
|
|
154
|
+
hasUI: active.hasUI,
|
|
155
|
+
ui: { select: active.ui.select.bind(active.ui) },
|
|
156
|
+
};
|
|
157
|
+
const result = await callMarkerRpc(guiCtx, SUBAGENT_INFLIGHT_MARKER, payload, {
|
|
158
|
+
timeout: selectTimeoutMs,
|
|
159
|
+
log: primitiveLog,
|
|
160
|
+
});
|
|
153
161
|
attemptInFlight = false;
|
|
154
|
-
if (
|
|
162
|
+
if (result.ok && isInFlightReportAck(result.value)) {
|
|
155
163
|
// 送达确认:清重试与失败计数,补推积压脏帧。
|
|
156
164
|
failureCount = 0;
|
|
157
165
|
clearRetryTimer();
|
|
158
166
|
if (dirty && ctx !== null) kick();
|
|
159
167
|
return;
|
|
160
168
|
}
|
|
161
|
-
// 失败折叠(resolve undefined =
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
// 失败折叠(resolve undefined = 超时/取消/无路由 / 回包非 ack / 通道异常)→ 延迟
|
|
170
|
+
// 重试,累计到顶放弃(放弃后镜像按 absent-report 走 errs 推迟,30min 有界,
|
|
171
|
+
// errs-safe 兜底不丢)。
|
|
172
|
+
logFailure(
|
|
173
|
+
result.ok ? "no ack (non-ack response)" : `no ack (${result.reason})`,
|
|
174
|
+
result.ok ? result.value : undefined,
|
|
175
|
+
);
|
|
164
176
|
failureCount += 1;
|
|
165
177
|
if (failureCount >= maxAttempts) {
|
|
166
178
|
givenUp = true;
|
|
@@ -181,11 +193,17 @@ export function createInFlightReporter(opts: InFlightReporterOpts = {}): InFligh
|
|
|
181
193
|
}
|
|
182
194
|
}
|
|
183
195
|
|
|
196
|
+
/** 原语留痕注入(D8):msg/detail 由 callMarkerRpc 产出;防刷屏策略(首败 warn /
|
|
197
|
+
* 后续 debug)留本侧 logFailure。detail 是原语侧小对象,序列化保信息。 */
|
|
198
|
+
function primitiveLog(msg: string, detail?: object): void {
|
|
199
|
+
logFailure(msg, detail === undefined ? undefined : JSON.stringify(detail));
|
|
200
|
+
}
|
|
201
|
+
|
|
184
202
|
function logFailure(reason: string, detail: unknown): void {
|
|
185
203
|
if (!firstFailureLogged) {
|
|
186
204
|
firstFailureLogged = true;
|
|
187
205
|
logger.warn(`[subagent-inflight] in-flight report failed (${reason}); retrying every ${retryDelayMs}ms (bounded at ${maxAttempts} attempts)`, {
|
|
188
|
-
detail:
|
|
206
|
+
detail: toErrorMessage(detail),
|
|
189
207
|
});
|
|
190
208
|
return;
|
|
191
209
|
}
|
|
@@ -42,6 +42,7 @@ import { getLogger } from "@zhushanwen/pi-extension-logger";
|
|
|
42
42
|
|
|
43
43
|
// C5①/C5⑦:渲染统一走 core barrel(formatModelList + ModelEntry 类型为 barrel 导出面)
|
|
44
44
|
import { formatModelList, type ModelEntry } from "@zhushanwen/subagent-core";
|
|
45
|
+
import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
45
46
|
|
|
46
47
|
const logger = getLogger("injector");
|
|
47
48
|
|
|
@@ -87,7 +88,7 @@ export function setupModelListInjector(pi: ExtensionAPI): void {
|
|
|
87
88
|
return { systemPrompt: event.systemPrompt + injection };
|
|
88
89
|
} catch (err) {
|
|
89
90
|
logger.error("[model-list-injector] before_agent_start failed", {
|
|
90
|
-
reason:
|
|
91
|
+
reason: toErrorMessage(err),
|
|
91
92
|
});
|
|
92
93
|
}
|
|
93
94
|
},
|
package/src/interface/format.ts
CHANGED
|
@@ -13,6 +13,8 @@ import os from "node:os";
|
|
|
13
13
|
|
|
14
14
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
15
15
|
|
|
16
|
+
import { firstContentText } from "@xyz-agent/extension-protocol";
|
|
17
|
+
|
|
16
18
|
import type { AgentEventLogEntry, DisplayItem, ExecutionStatus } from "@zhushanwen/subagent-core";
|
|
17
19
|
import { DEFAULT_AGENT_NAME } from "@zhushanwen/subagent-core";
|
|
18
20
|
import type {
|
|
@@ -562,12 +564,13 @@ export function formatTokenStat(
|
|
|
562
564
|
/**
|
|
563
565
|
* renderResult 的文本兜底:从 result.content[0] 提取纯文本。
|
|
564
566
|
* 多处 tool 的 renderResult 曾各自内联此逻辑,提取后统一调用。
|
|
567
|
+
* 内核收敛至 extension-protocol firstContentText(ext-simplify-17 D9);
|
|
568
|
+
* 本包装保留 content 可选的宽入参形态(tool renderResult 回调契约 content 可缺省)。
|
|
565
569
|
*/
|
|
566
570
|
export function renderTextFallback(
|
|
567
571
|
result: { content?: Array<{ type: string; text?: string }> },
|
|
568
572
|
): string {
|
|
569
|
-
|
|
570
|
-
return first?.type === "text" ? (first.text ?? "") : "";
|
|
573
|
+
return firstContentText({ content: result.content ?? [] });
|
|
571
574
|
}
|
|
572
575
|
|
|
573
576
|
/** Format a single activity line: ToolName(argsPreview). */
|
|
@@ -28,7 +28,7 @@ export { SLUG_MAX_LENGTH };
|
|
|
28
28
|
|
|
29
29
|
// Params schema(跨包契约测试的真实 typebox 校验入口)。
|
|
30
30
|
//
|
|
31
|
-
// action:"start" 的
|
|
31
|
+
// action:"start" 的 16 字段(task/slug/agent/model/...)拍平在顶层,不再用 startParam
|
|
32
32
|
// 嵌套容器包。原因:弱模型(GLM/DeepSeek)信任 schema 结构信号 > 文本信号,经常省略
|
|
33
33
|
// startParam 嵌套层把 task/slug 直接平铺到顶层导致调用失败。拍平后 schema 结构与模型
|
|
34
34
|
// 的自然倾向一致,消除这层误用。task/slug 必填性由 startHandler runtime 校验(flat
|
|
@@ -40,7 +40,7 @@ export { SLUG_MAX_LENGTH };
|
|
|
40
40
|
// 反映必填性。勿在此基础上继续堆 action 条件逻辑——要加就拆 tool。
|
|
41
41
|
export const SubagentParams = Type.Object({
|
|
42
42
|
action: StringEnum(["start", "list", "cancel", "message", "close", "fork-from"], {
|
|
43
|
-
description: "Operation: 'start' runs a subagent, 'list' shows subagents, 'cancel' stops a background subagent, 'message' sends a follow-up to any of your subagents (running or idle — an idle one transparently revives and continues on its original session file
|
|
43
|
+
description: "Operation: 'start' runs a subagent, 'list' shows subagents, 'cancel' stops a background subagent, 'message' sends a follow-up to any of your subagents (running or idle — an idle one transparently revives and continues on its original session file), 'close' archives a subagent (immediately when idle; after the current round, or immediately with force:true, when running), 'fork-from' spawns a NEW subagent inheriting an older one's history (recovery for restart-disconnected subagents; the old record is untouched).",
|
|
44
44
|
}),
|
|
45
45
|
// ── action:"start" fields (flattened to top level). task/slug REQUIRED for start. ──
|
|
46
46
|
// Missing/empty task or slug throws at runtime (startHandler).
|
|
@@ -88,40 +88,34 @@ export const SubagentParams = Type.Object({
|
|
|
88
88
|
description: 'Override the working directory for the subagent execution. Must be an absolute path (no "~" shorthand, no relative paths); ".." segments are rejected. Defaults to the parent session\'s cwd.',
|
|
89
89
|
pattern: "^/",
|
|
90
90
|
})),
|
|
91
|
-
conversation
|
|
92
|
-
|
|
93
|
-
"Enable continuous chat with this subagent. When true, the subagent stays available after each reply — you can send follow-up messages (action:'message') and it keeps the full conversation context across rounds, with no need to re-spawn or re-explain. " +
|
|
94
|
-
"\nUse conversation:true for: multi-round collaboration (iterative review-fix loops, back-and-forth refinement), any task where you expect to send follow-up messages after the initial result. " +
|
|
95
|
-
"\nOmit (or false) for: one-shot tasks — single exploration, lookup, file read, code generation that needs no follow-up. The subagent runs once, notifies on completion, and is cleaned up automatically (default). " +
|
|
96
|
-
"\nFor long-interval collaboration (each round spaced >5min apart), set conversation:true AND increase idleTimeoutMs to avoid premature timeout. " +
|
|
97
|
-
"Cost: a conversation-mode subagent holds resources (memory, and a worktree if enabled) until you explicitly end it with action:'close'. Always close when done.",
|
|
98
|
-
})),
|
|
91
|
+
// [modeless 波5] conversation 参数已删除:chatMode 字段消亡后「模式」不存在——
|
|
92
|
+
// 一切 record 永续可续聊(idle 后 message 即续、fork-from 可继承),无模式开关可表达。
|
|
99
93
|
idleTimeoutMs: Type.Optional(Type.Number({
|
|
100
94
|
description:
|
|
101
|
-
"Idle
|
|
102
|
-
"Default: 300000 (5min).
|
|
103
|
-
"Pass 0 or a negative value to DISABLE idle
|
|
104
|
-
"
|
|
95
|
+
"Idle-recycle cadence for ALL subagents (modeless: every subagent stays continuable — this is NOT a mode switch). Controls how long an idle subagent (between rounds, no activity) stays before being automatically archived. " +
|
|
96
|
+
"Default: 300000 (5min). Raise it for long-interval collaboration where your next message may arrive more than 5min after a round ends. " +
|
|
97
|
+
"Pass 0 or a negative value to DISABLE idle recycling entirely (subagent stays available until you close it). " +
|
|
98
|
+
"Priority: this param > env XYZ_SUBAGENT_IDLE_TIMEOUT_MS > default.",
|
|
105
99
|
})),
|
|
106
100
|
engine: Type.Optional(StringEnum(["pi", "zcode"], {
|
|
107
101
|
description:
|
|
108
102
|
"Execution engine for this subagent. Omit to inherit the global config. " +
|
|
109
103
|
"Three-layer priority: this parameter > agent .md frontmatter engine > config.json defaultEngine. " +
|
|
110
|
-
"Non-pi engines do not support
|
|
104
|
+
"Non-pi engines do not support fork/worktree (rejected before the subagent is created).",
|
|
111
105
|
})),
|
|
112
106
|
collect: Type.Optional(StringEnum(["async", "sync"], {
|
|
113
107
|
description:
|
|
114
|
-
"Completion-notification
|
|
108
|
+
"Completion-notification routing (NOT a record mode — batch membership is routing bookkeeping only). " +
|
|
115
109
|
"Omit to use the config default (currently async). " +
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
123
|
-
"
|
|
124
|
-
"
|
|
110
|
+
"'async' = each subagent's completion notifies immediately. " +
|
|
111
|
+
"'sync' = batch wake-up: when you dispatch >=2 independent subagents whose results you will " +
|
|
112
|
+
"combine, their completions are held until ALL pending sync members finish, then delivered as " +
|
|
113
|
+
"ONE batch notification (single wake-up, results inline); when the batch closes, its members " +
|
|
114
|
+
"are automatically archived. Batch members cannot be messaged — use action:'fork-from' to " +
|
|
115
|
+
"continue from one instead. You may keep dispatching more sync subagents in later turns — " +
|
|
116
|
+
"they join the same pending batch. Independent means no member's prompt or work depends on " +
|
|
117
|
+
"another member's output — dependent tasks must be chained across messages (one start after " +
|
|
118
|
+
"the prior completes), never batched.",
|
|
125
119
|
})),
|
|
126
120
|
// action:"list" → listParam OPTIONAL (all fields optional, defaults apply). Ignored by other actions.
|
|
127
121
|
listParam: Type.Optional(Type.Object({
|
|
@@ -147,11 +141,12 @@ export const SubagentParams = Type.Object({
|
|
|
147
141
|
})),
|
|
148
142
|
// action:"message" → messageParam.subagentId + text REQUIRED. Any reachable subagent works —
|
|
149
143
|
// running joins the in-flight round (D2 打断入队);idle transparently revives on the same
|
|
150
|
-
// session file([U4 §3.2.3] 万物可续——形态枚举 gate
|
|
151
|
-
//
|
|
144
|
+
// session file([U4 §3.2.3] 万物可续——形态枚举 gate 与 SP-5 升级路径均消亡,message 直接
|
|
145
|
+
// 续聊任何 idle record)。引擎续聊能力轴 gate 保留(core messageHandler 入口)。
|
|
146
|
+
// description 与实现锚点见 messageHandler。
|
|
152
147
|
messageParam: Type.Optional(Type.Object({
|
|
153
148
|
subagentId: Type.String({
|
|
154
|
-
description: "REQUIRED for action:'message'. The subagentId to message. Any subagent reachable in this session tree works, running or idle: an idle subagent transparently revives on the same id and continues writing its original session file
|
|
149
|
+
description: "REQUIRED for action:'message'. The subagentId to message. Any subagent reachable in this session tree works, running or idle: an idle subagent transparently revives on the same id and continues writing its original session file; a running subagent has your message interrupt-and-join its in-flight round. Rejections: unknown id, session file held by another live process, a record from a different session tree, workflow-origin records (their results belong to the workflow run), and records on engines that do not support continuation (fork-from or re-dispatch instead).",
|
|
155
150
|
}),
|
|
156
151
|
text: Type.String({
|
|
157
152
|
description: "REQUIRED for action:'message'. The message to send. Whitespace-only throws.",
|
|
@@ -162,11 +162,11 @@ action:"list" before action:"start" — a reusable subagent may exist; compactio
|
|
|
162
162
|
|
|
163
163
|
## Actions
|
|
164
164
|
|
|
165
|
-
- action:"start" — run a subagent. Pass task and slug as top-level fields (REQUIRED). Optional: agent, model, thinkingLevel, engine, collect, skillPath, appendSystemPrompt, schema, maxTurns, graceTurns, fork, worktree, cwd,
|
|
166
|
-
- action:"message" — send a follow-up to any of your subagents — running or idle (idle revives in place;
|
|
165
|
+
- action:"start" — run a subagent. Pass task and slug as top-level fields (REQUIRED). Optional: agent, model, thinkingLevel, engine, collect, skillPath, appendSystemPrompt, schema, maxTurns, graceTurns, fork, worktree, cwd, idleTimeoutMs. Background only: returns a subagentId immediately, notifies on completion.
|
|
166
|
+
- action:"message" — send a follow-up to any of your subagents — running or idle (idle revives in place; full context retained). REQUIRED messageParam: { subagentId, text }. The reply auto-notifies.
|
|
167
167
|
- action:"close" — archive a subagent (hidden from list, recoverable): idle closes immediately; running finishes the current round first unless force:true (then terminates mid-round). REQUIRED closeParam: { subagentId }.
|
|
168
168
|
- action:"list" — list subagents. listParam: { includeFinished?, includeWorkflow?, limit? } (all optional; includeWorkflow defaults false — workflow-dispatched subagents are hidden unless true). Read an item's sessionFile for full detail.
|
|
169
|
-
- action:"cancel" — stop a background subagent (
|
|
169
|
+
- action:"cancel" — stop a background subagent (to archive it instead, use close). REQUIRED cancelParam: { subagentId }.
|
|
170
170
|
- action:"fork-from" — restart-disconnect recovery: spawn a NEW subagent inheriting the old one's history via --fork. REQUIRED forkFromParam: { sourceSubagentId }. Optional: prompt (continuation; default handover frame). Returns { newSubagentId, sourceSessionFile }. Rejects still-running / foreign-live / worktree-bound sources; unparseable history anchors are guided to action:"message" (same-id reopen).
|
|
171
171
|
|
|
172
172
|
## Examples
|
|
@@ -191,8 +191,8 @@ Completion auto-notifies you (steer wakes the next turn):
|
|
|
191
191
|
|
|
192
192
|
## Batch collection (collect)
|
|
193
193
|
|
|
194
|
-
- collect:"sync" — >=2 independent
|
|
195
|
-
- collect:"async" (default, omit) — immediate per-subagent completion;
|
|
194
|
+
- collect:"sync" — >=2 independent subagents whose results you will combine: completions are held until every pending sync member finishes, then ONE batch notification delivers all results inline (one wake-up) and batch members auto-archive. Later sync starts join the same batch; each sync start response reports {"collect":{"mode":"sync","pendingSyncCount":N}}. Batch members cannot be messaged — fork-from continues from one.
|
|
195
|
+
- collect:"async" (default, omit) — immediate per-subagent completion; use when each result is needed early.
|
|
196
196
|
- Subagents in one sync batch must not depend on each other's output — dependent tasks must be chained across messages (see Calling patterns), never batched.
|
|
197
197
|
Items over budget are truncated with a pointer: session_read {"action":"result","session":"<id>"} fetches the full text.
|
|
198
198
|
|
|
@@ -204,16 +204,15 @@ Items over budget are truncated with a pointer: session_read {"action":"result",
|
|
|
204
204
|
- Treating subagent results as authoritative without verification.
|
|
205
205
|
- Canceling by guessing a subagentId instead of using action:"list" first.
|
|
206
206
|
|
|
207
|
-
##
|
|
207
|
+
## Continuing a subagent (modeless)
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
idleTimeoutMs: idle
|
|
209
|
+
Every subagent stays continuable — no mode switch: action:"message" revives an idle record in place (or joins a running one's round), action:"fork-from" branches a new subagent from old history, action:"close" archives it.
|
|
210
|
+
idleTimeoutMs: idle-recycle cadence for ALL subagents — idle records auto-archive on expiry (default 300000 / 5min; 0/negative disables; env XYZ_SUBAGENT_IDLE_TIMEOUT_MS: global default, per-call wins).
|
|
211
211
|
|
|
212
212
|
## You cannot
|
|
213
213
|
|
|
214
214
|
- Get a synchronous/inline result — start always returns a subagentId immediately (background).
|
|
215
215
|
- Read mid-flight streaming output — wait for the completion notification.
|
|
216
|
-
- Combine collect:"sync" with conversation:true — rejected before start; sync is one-shot only (remove one).
|
|
217
216
|
- See intermediate signals while a sync batch waits — nothing arrives until the whole batch closes. Hung member: action:"list" shows what is still running; action:"cancel" it — cancelled members count as terminal and the batch closes.
|
|
218
217
|
|
|
219
218
|
## Calling patterns
|
package/src/jsonl-run-store.ts
CHANGED
|
@@ -90,7 +90,7 @@ import {
|
|
|
90
90
|
pruneStateFilesBeyondCap,
|
|
91
91
|
type RunSnapshot,
|
|
92
92
|
} from "@zhushanwen/subagent-core";
|
|
93
|
-
import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
93
|
+
import { isEnoentError, toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
94
94
|
|
|
95
95
|
// ── Workflow-record self-describing entry (W17, D4) ─────────
|
|
96
96
|
|
|
@@ -226,16 +226,6 @@ async function loadRunFromStateFile(filePath: string): Promise<WorkflowRun | nul
|
|
|
226
226
|
|
|
227
227
|
// ── JsonlRunStore ────────────────────────────────────────────
|
|
228
228
|
|
|
229
|
-
/** Node fs 错误 code 判定(ENOENT = 路径不存在,并发删除场景)。 */
|
|
230
|
-
function isEnoentError(err: unknown): boolean {
|
|
231
|
-
return (
|
|
232
|
-
typeof err === "object" &&
|
|
233
|
-
err !== null &&
|
|
234
|
-
"code" in err &&
|
|
235
|
-
(err as { code: unknown }).code === "ENOENT"
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
229
|
const logger = getLogger("subagents");
|
|
240
230
|
|
|
241
231
|
/**
|
package/src/session-lifecycle.ts
CHANGED
|
@@ -300,7 +300,6 @@ function appendSubagentIdentityEntry(pi: ExtensionAPI): void {
|
|
|
300
300
|
process.env.PI_SUBAGENT_FORK_DEPTH !== undefined
|
|
301
301
|
? Number(process.env.PI_SUBAGENT_FORK_DEPTH)
|
|
302
302
|
: undefined,
|
|
303
|
-
chatMode: process.env.PI_SUBAGENT_CHAT_MODE === "true",
|
|
304
303
|
// [review round2] worktree 隔离标志(session-runner 注入):跨重启重建路径据此
|
|
305
304
|
// 拒绝续聊(handle 不可序列化,reattach 不可行)。
|
|
306
305
|
worktree: process.env.PI_SUBAGENT_WORKTREE === "true",
|
|
@@ -373,7 +372,8 @@ export function bindLedgerHostAndRecover(pi: ExtensionAPI, ctx: ExtensionContext
|
|
|
373
372
|
/**
|
|
374
373
|
* 随迁块 4 的进程级维护三连(各 try-catch「失败记日志不阻断」,设计 §3.4):
|
|
375
374
|
* 过期 session 文件清理 / ADR-035 manifest tmp 恢复 / ADR-035 worktree reaper 扫描。
|
|
376
|
-
*
|
|
375
|
+
* ([modeless 波5] 原 [E1] sync 批崩溃恢复接线已摘除——collectMode 记录态消亡后
|
|
376
|
+
* core 侧 recoverSyncCollectBatch 已是 accepted-no-op,调用点随之退役。)
|
|
377
377
|
*/
|
|
378
378
|
async function runProcessLevelMaintenance(
|
|
379
379
|
agentDir: string,
|
|
@@ -381,22 +381,6 @@ async function runProcessLevelMaintenance(
|
|
|
381
381
|
service: SubagentService,
|
|
382
382
|
deps: SessionLifecycleDeps,
|
|
383
383
|
): Promise<void> {
|
|
384
|
-
// [E1] sync 批崩溃恢复(subagent-sync-collect 设计 §3.1.5 E1,U5 接线):扫描主
|
|
385
|
-
// session 末条 entry 重建批缓冲;全员终态未投递 → notifyBatch 补发 + 统一补
|
|
386
|
-
// batchFinalized 标记(账本同 hash 幂等拒绝也算已投递)。须晚于 initSession(孤儿
|
|
387
|
-
// 终态恢复先行收敛 running 成员,createOrReuseServices 内部同步完成)与 ledger
|
|
388
|
-
// bind(补发走 notifyBatch 写账链,见上方 bindNotifyLedgerHost)。per-session 域
|
|
389
|
-
// (主 session 文件),不用 oncePerProcess;best-effort 不阻断 session_start。
|
|
390
|
-
// 时序约束在调用点 setupSessionLifecycle 已满足(bindLedgerHostAndRecover 与
|
|
391
|
-
// createOrReuseServices 均先于本 helper 调用)。
|
|
392
|
-
try {
|
|
393
|
-
service.recoverSyncCollectBatch();
|
|
394
|
-
} catch (err) {
|
|
395
|
-
logger.warn("[subagents] sync collect batch recovery failed", {
|
|
396
|
-
reason: toErrorMessage(err),
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
|
|
400
384
|
try {
|
|
401
385
|
// 递归扫描 <agentDir>/subagents + unlink 超 TTL 跨 session 文件属进程级维护
|
|
402
386
|
// ——oncePerProcess 守卫防双跑(u-audit-fix)。
|