@zhushanwen/pi-subagent-workflow 8.8.0 → 8.8.1
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 +8 -8
- package/src/host/pi-host.ts +5 -4
- package/src/index.ts +145 -464
- package/src/injectors/__tests__/engine-awareness.test.ts +4 -2
- package/src/injectors/__tests__/engine-section-stability.test.ts +42 -29
- package/src/injectors/engine-awareness.ts +80 -4
- package/src/injectors/resource-list-injector.ts +209 -0
- package/src/injectors/subagent-list-injector.ts +30 -143
- package/src/injectors/workflow-list-injector.ts +23 -150
- package/src/interface/__tests__/detectors.test.ts +1 -1
- package/src/interface/__tests__/subagent-tool-path-guard.test.ts +42 -26
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +10 -2
- package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +1 -1
- package/src/interface/bg-notify-render.ts +4 -4
- package/src/interface/commands.ts +115 -75
- package/src/interface/format.ts +339 -241
- package/src/interface/helpers.ts +2 -2
- package/src/interface/list-component.ts +163 -116
- package/src/interface/list-shared.ts +2 -2
- package/src/interface/list-view.ts +129 -72
- package/src/interface/subagent-actions.ts +5 -5
- package/src/interface/subagent-tool-schema.ts +6 -5
- package/src/interface/subagent-tool.ts +4 -3
- package/src/interface/subagents.ts +8 -7
- package/src/interface/tool-render.ts +2 -2
- package/src/interface/tool-workflow-script.ts +15 -14
- package/src/interface/tool-workflow.ts +14 -11
- package/src/interface/tui-kit.ts +305 -0
- package/src/interface/views/WorkflowsView.ts +267 -196
- package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
- package/src/interface/views/__tests__/detail-content-session-file.test.ts +7 -4
- package/src/interface/views/detail-content.ts +16 -14
- package/src/interface/views/view-constants.ts +19 -0
- package/src/jsonl-run-store.ts +11 -7
- package/src/interface/views/format.ts +0 -281
package/src/index.ts
CHANGED
|
@@ -13,57 +13,30 @@
|
|
|
13
13
|
* 设计基线:D-004(旧包不动)/ ADR-025(进程内执行)/ D-8(pi.__workflowRun 签名)。
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import
|
|
17
|
-
import * as path from "node:path";
|
|
18
|
-
|
|
19
|
-
import type { BeforeAgentStartEvent, ExtensionAPI, ExtensionContext, SessionCompactEvent, SessionShutdownEvent, SessionStartEvent, SessionTreeEvent } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import type { ExtensionAPI, ExtensionContext, SessionCompactEvent, SessionShutdownEvent, SessionStartEvent, SessionTreeEvent } from "@earendil-works/pi-coding-agent";
|
|
20
17
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
21
18
|
import { getLogger, setPiHandle } from "@zhushanwen/pi-extension-logger";
|
|
22
19
|
|
|
23
20
|
// ═══ core 宿主端口接线(subagent-core 包抽离 u0-wire;实现见 src/host/pi-host.ts) ═══
|
|
24
|
-
import { configureCore } from "@zhushanwen/subagent-core
|
|
25
|
-
import { configureNotifyDomain } from "@zhushanwen/subagent-core
|
|
21
|
+
import { configureCore } from "@zhushanwen/subagent-core";
|
|
22
|
+
import { configureNotifyDomain } from "@zhushanwen/subagent-core";
|
|
26
23
|
import { createPiHostServices, createPiNotifyDomainPorts } from "./host/pi-host.ts";
|
|
27
24
|
|
|
28
|
-
import {
|
|
29
|
-
import { bestEffort } from "@zhushanwen/subagent-core/execution/best-effort.ts";
|
|
25
|
+
import { bestEffort } from "@zhushanwen/subagent-core";
|
|
30
26
|
// ═══ execution/ 层(subagents 核心 + 运行时) ═══
|
|
31
|
-
import { getOrCreateChannelRegistry } from "@zhushanwen/subagent-core/execution/channel-registry-access.ts";
|
|
32
|
-
import { DialogGlobalQueue } from "@zhushanwen/subagent-core/execution/dialog-queue.ts";
|
|
33
|
-
// [engine-awareness U3] 全局 config 三态读取(检测 poll 与 session_start 基线共用)
|
|
34
|
-
import { readGlobalConfig } from "@zhushanwen/subagent-core/execution/config.ts";
|
|
35
27
|
// [U7] 引擎列表状态文件(registry → engines.json,GUI 引擎选择器数据源)
|
|
36
|
-
import { syncEnginesFile } from "@zhushanwen/subagent-core
|
|
37
|
-
// [U7] 引擎模型段注入(defaultEngine 非 pi 时 system prompt 补 <available_<engine>_models>)
|
|
38
|
-
// [engine-awareness U3] 补恒在状态段 <current_subagent_engine>(D6,pi 引擎也声明)
|
|
39
|
-
import { buildEngineModelsPromptAppend, buildSubagentEngineSection } from "@zhushanwen/subagent-core/execution/engine/model-prompt.ts";
|
|
28
|
+
import { syncEnginesFile } from "@zhushanwen/subagent-core";
|
|
40
29
|
// [P1 引擎接线] 组合根登记 'pi' 引擎进 registry(引擎获取统一经 getEngine,缺省 id 'pi')
|
|
41
|
-
import { registerPiEngine } from "@zhushanwen/subagent-core
|
|
30
|
+
import { registerPiEngine } from "@zhushanwen/subagent-core";
|
|
42
31
|
// [P3 引擎接线] 组合根登记 'zcode' 引擎(spawn 单轮模式;engineDataDir 默认走
|
|
43
32
|
// common/data-dir SSOT,见 engines/zcode/registration.ts)
|
|
44
|
-
import { registerZcodeEngine } from "@zhushanwen/subagent-core
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
} from "
|
|
51
|
-
import { bindNotifyLedgerHost, getBoundNotifyLedger, type NotifyLedgerHost } from "@zhushanwen/subagent-core/execution/notify-ledger.ts";
|
|
52
|
-
import { IDENTITY_CUSTOM_TYPE, type SubagentIdentityData } from "@zhushanwen/subagent-core/execution/session-reconstructor.ts";
|
|
53
|
-
import type { ExecutionMode } from "@zhushanwen/subagent-core/execution/types.ts";
|
|
54
|
-
import { maybeCleanupExpiredSessionFiles } from "@zhushanwen/subagent-core/execution/session-file-gc.ts";
|
|
55
|
-
import {
|
|
56
|
-
getSubagentService,
|
|
57
|
-
setSubagentService,
|
|
58
|
-
SubagentService,
|
|
59
|
-
} from "@zhushanwen/subagent-core/execution/subagent-service.ts";
|
|
60
|
-
import { killAllSpawnedChildren } from "@zhushanwen/subagent-core/execution/session-runner.ts";
|
|
61
|
-
import { SubprocessAgentRunner } from "@zhushanwen/subagent-core/execution/subprocess-agent-runner.ts";
|
|
62
|
-
import { WorktreeManager } from "@zhushanwen/subagent-core/execution/worktree-manager.ts";
|
|
63
|
-
// [engine-awareness U3] per-turn 引擎检测编排(D1/D1b/D2/D3/D5);normalizeEngineId
|
|
64
|
-
// 单一权威源在 core registry(原经 engine-awareness 再导出,导入面已折叠直连)
|
|
65
|
-
import { normalizeEngineId } from "@zhushanwen/subagent-core/execution/engine/registry.ts";
|
|
66
|
-
import { runEngineAwarenessTurn } from "./injectors/engine-awareness.ts";
|
|
33
|
+
import { registerZcodeEngine } from "@zhushanwen/subagent-core";
|
|
34
|
+
import { getModelConfigService } from "@zhushanwen/subagent-core";
|
|
35
|
+
import { getBoundNotifyLedger } from "@zhushanwen/subagent-core";
|
|
36
|
+
import { getSubagentService } from "@zhushanwen/subagent-core";
|
|
37
|
+
import { killAllSpawnedChildren } from "@zhushanwen/subagent-core";
|
|
38
|
+
// [engine-awareness U3/D7-④] per-turn 引擎检测编排 + before_agent_start 链尾接线
|
|
39
|
+
import { setupEngineAwarenessInjector } from "./injectors/engine-awareness.ts";
|
|
67
40
|
import { setupModelListInjector } from "./injectors/model-list-injector.ts";
|
|
68
41
|
import { setupSubagentListInjector } from "./injectors/subagent-list-injector.ts";
|
|
69
42
|
import { setupWorkflowListInjector } from "./injectors/workflow-list-injector.ts";
|
|
@@ -76,21 +49,26 @@ import { registerSubagentTool } from "./interface/subagent-tool.ts";
|
|
|
76
49
|
import { registerSubagentsCommand } from "./interface/subagents.ts";
|
|
77
50
|
import { registerWorkflowTool } from "./interface/tool-workflow.ts";
|
|
78
51
|
import { registerWorkflowScriptTool } from "./interface/tool-workflow-script.ts";
|
|
79
|
-
import { JsonlRunStore } from "./jsonl-run-store.ts";
|
|
80
|
-
import { clearSkillPathCache } from "@zhushanwen/subagent-core/orchestration/skill-discovery.ts";
|
|
81
52
|
// ═══ orchestration/ 层(workflow engine + infra) ═══
|
|
82
|
-
import type { LauncherDeps } from "@zhushanwen/subagent-core
|
|
83
|
-
import { executeNestedWorkflow, runAndWait, type WorkflowRunResult } from "@zhushanwen/subagent-core
|
|
53
|
+
import type { LauncherDeps } from "@zhushanwen/subagent-core";
|
|
54
|
+
import { executeNestedWorkflow, runAndWait, type WorkflowRunResult } from "@zhushanwen/subagent-core";
|
|
84
55
|
import {
|
|
85
56
|
evictDoneRunsBeyondCap,
|
|
86
57
|
MAX_RETAINED_DONE_RUNS,
|
|
87
|
-
recoverCrashedRuns,
|
|
88
58
|
scheduleTimeBudget,
|
|
89
59
|
terminateRunningRuns,
|
|
90
|
-
} from "@zhushanwen/subagent-core
|
|
91
|
-
import type { WorkflowRun } from "@zhushanwen/subagent-core
|
|
92
|
-
import { WorkerHostImpl } from "@zhushanwen/subagent-core
|
|
93
|
-
import { WorkflowScriptRegistryImpl } from "@zhushanwen/subagent-core
|
|
60
|
+
} from "@zhushanwen/subagent-core";
|
|
61
|
+
import type { WorkflowRun } from "@zhushanwen/subagent-core";
|
|
62
|
+
import { WorkerHostImpl } from "@zhushanwen/subagent-core";
|
|
63
|
+
import { WorkflowScriptRegistryImpl } from "@zhushanwen/subagent-core";
|
|
64
|
+
// ═══ session 生命周期装配 seam(bootstrap seam,设计 §3.1/D1) ═══
|
|
65
|
+
import {
|
|
66
|
+
getOrCreateDialogQueue,
|
|
67
|
+
setupSessionLifecycle,
|
|
68
|
+
type SessionLifecycleDeps,
|
|
69
|
+
type SessionLifecycleResult,
|
|
70
|
+
} from "./session-lifecycle.ts";
|
|
71
|
+
import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
94
72
|
|
|
95
73
|
// ── pi.__workflowRun 类型扩展(D-8 签名) ─────────────────
|
|
96
74
|
|
|
@@ -128,7 +106,7 @@ function reapSpawnedChildrenOnShutdown(): void {
|
|
|
128
106
|
// 不静默吞错,对齐「错误必须可操作」。
|
|
129
107
|
logger.debug(
|
|
130
108
|
"[subagents] process shutdown reap best-effort failed (killAllSpawnedChildren SIGTERM)",
|
|
131
|
-
{ reason:
|
|
109
|
+
{ reason: toErrorMessage(err) },
|
|
132
110
|
);
|
|
133
111
|
}
|
|
134
112
|
}
|
|
@@ -192,29 +170,9 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
192
170
|
setupWorkflowListInjector(pi);
|
|
193
171
|
setupModelListInjector(pi);
|
|
194
172
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return cachedMainSessionFile;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* 按 sessionId 解析主 session 文件路径(文件名约定 `<ISO 时间戳>_<sessionId>.jsonl`)。
|
|
203
|
-
* [E2E 实测] attach 场景下 ctx.sessionManager.getSessionFile() 会返回前一 session 的
|
|
204
|
-
* 文件(session_start(root=01a01bf5) 时仍返回刚新建 session 的路径)——恢复逻辑
|
|
205
|
-
* 读错文件会整段漏判。此处按 id 从 sessions 目录解析为准;新 session 文件未 flush
|
|
206
|
-
* 时(AGENTS.md 规则 6:首条 assistant 消息前可能不存在)返回 undefined,调用方
|
|
207
|
-
* (fork 解析 / 孤儿恢复)对该场景本就无 entry 可读。
|
|
208
|
-
*/
|
|
209
|
-
function resolveMainSessionFileById(sessionId: string): string | undefined {
|
|
210
|
-
const sessionsDir = path.join(getAgentDir(), "..", "sessions");
|
|
211
|
-
try {
|
|
212
|
-
const match = fs.readdirSync(sessionsDir).find((f) => f.endsWith(`_${sessionId}.jsonl`));
|
|
213
|
-
return match === undefined ? undefined : path.join(sessionsDir, match);
|
|
214
|
-
} catch {
|
|
215
|
-
return undefined;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
173
|
+
// [主 session 文件解析随迁] cachedMainSessionFile 缓存与 resolveMainSessionFileById
|
|
174
|
+
// 随 session_start 装配块整体迁入 session-lifecycle.ts(唯一消费方是随迁块;
|
|
175
|
+
// SubagentService 的 getMainSessionFile getter 由 createOrReuseServices 提供)。
|
|
218
176
|
|
|
219
177
|
// resources_discover:不再注册 handler(v4 决策:不再注入额外 skill 目录,
|
|
220
178
|
// ADR-031 废弃 discovery.json)。pi 核心 auto-discovery 已覆盖 .agents/skills
|
|
@@ -232,31 +190,13 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
232
190
|
const workerHost = new WorkerHostImpl();
|
|
233
191
|
const registry = new WorkflowScriptRegistryImpl();
|
|
234
192
|
|
|
235
|
-
// SAR 改为 per-session 构造(需要 ctxModel 填底 D-008 + subagentService
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
// per-session 状态(session_start
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
{
|
|
243
|
-
store: JsonlRunStore;
|
|
244
|
-
runs: Map<string, WorkflowRun>;
|
|
245
|
-
sessionDir: string;
|
|
246
|
-
/** D-008 per-session SAR(需要 ctxModel + subagentService) */
|
|
247
|
-
runner: SubprocessAgentRunner;
|
|
248
|
-
/** session 上下文(notifyDone 需要 GuiContext) */
|
|
249
|
-
ctx?: ExtensionContext;
|
|
250
|
-
/** MF-1: store 健康度。session_start 时 store.loadAll 失败则置 false,
|
|
251
|
-
* workflow 域启动时 fail-fast,避免后续 store.save 再次失败导致 run 状态不落地。
|
|
252
|
-
* subagent 域不依赖 store,不受此标志影响。 */
|
|
253
|
-
storeHealthy: boolean;
|
|
254
|
-
/** [engine-awareness D1b] 上一次已知默认引擎(session_start 初始化,per-turn 检测
|
|
255
|
-
* diff 基准)。undefined = 初始化时 config 读失败——首 turn 检测遇 undefined
|
|
256
|
-
* 静默基线化,不算变更、不发通知(防首 turn 伪通知)。 */
|
|
257
|
-
lastEngine?: string;
|
|
258
|
-
}
|
|
259
|
-
>();
|
|
193
|
+
// SAR 改为 per-session 构造(需要 ctxModel 填底 D-008 + subagentService 委托目标),
|
|
194
|
+
// 构造点随 session_start 装配块迁入 session-lifecycle.ts,经 SessionLifecycleResult
|
|
195
|
+
// 传回(见 setupSessionLifecycle)。
|
|
196
|
+
|
|
197
|
+
// per-session 状态(session_start 时重建)。value = SessionLifecycleResult
|
|
198
|
+
// (setupSessionLifecycle 装配结果,ctx 必有)。
|
|
199
|
+
const sessionState = new Map<string, SessionLifecycleResult>();
|
|
260
200
|
|
|
261
201
|
function log(
|
|
262
202
|
level: "debug" | "info" | "warn" | "error",
|
|
@@ -277,21 +217,20 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
277
217
|
}
|
|
278
218
|
}
|
|
279
219
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
220
|
+
// [workflow state 目录解析随迁] resolveSessionDir 随 session_start 装配块整体迁入
|
|
221
|
+
// session-lifecycle.ts(唯一消费方是随迁块)。
|
|
222
|
+
|
|
223
|
+
/** 组合根侧生产 deps 工厂:SessionLifecycleDeps 全部成员有生产默认实现(住
|
|
224
|
+
* session-lifecycle.ts——createOrReuseServices 单例语义 / WorktreeManager 每次
|
|
225
|
+
* 扫描新建 / JsonlRunStore per-session 新建),此处无本地构造可注入;工厂形态
|
|
226
|
+
* 保留为 index 侧注入点(测试或后续演进可在此覆盖)。
|
|
227
|
+
* 测试注入路径:不挂载 index.ts,直接调 setupSessionLifecycle(pi, ctx, fakeDeps)。 */
|
|
228
|
+
function makeLifecycleDeps(): SessionLifecycleDeps {
|
|
229
|
+
return {};
|
|
286
230
|
}
|
|
287
231
|
|
|
288
232
|
function makeDeps(
|
|
289
|
-
state:
|
|
290
|
-
store: JsonlRunStore;
|
|
291
|
-
runs: Map<string, WorkflowRun>;
|
|
292
|
-
sessionDir: string;
|
|
293
|
-
runner: SubprocessAgentRunner;
|
|
294
|
-
},
|
|
233
|
+
state: Pick<SessionLifecycleResult, "store" | "runs" | "sessionDir" | "runner">,
|
|
295
234
|
sessionCtx?: ExtensionContext,
|
|
296
235
|
) {
|
|
297
236
|
const deps: LauncherDeps = {
|
|
@@ -339,273 +278,16 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
339
278
|
|
|
340
279
|
// ════════════════════════════════════════════════════════════
|
|
341
280
|
// session_start:初始化 subagents + workflow 两域
|
|
281
|
+
//
|
|
282
|
+
// 六职责编排(identity 重建 / ledger 装配 / 双 Service 装配 / GC+恢复 / kill-9
|
|
283
|
+
// 循环 / SAR+engine 基线)已随迁 session-lifecycle.ts(bootstrap seam,设计
|
|
284
|
+
// §3.1/D1/D2 原样搬移)。此处仅接线:lastSessionId 先行赋值(时序与原 handler
|
|
285
|
+
// 开头一致)+ 装配结果写入 per-session sessionState。
|
|
342
286
|
// ════════════════════════════════════════════════════════════
|
|
343
287
|
pi.on("session_start", async (_event: SessionStartEvent, ctx: ExtensionContext) => {
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
lsRef.lastSessionId = sessionId;
|
|
348
|
-
|
|
349
|
-
// [U7] 引擎列表同步 engines.json(幂等零写 + fail-safe;组合根注册已在
|
|
350
|
-
// extension 工厂体完成,此处 registry 已含全部引擎)。写 agentDir 全局文件属
|
|
351
|
-
// 跨 session 副作用——oncePerProcess 守卫防 factory 二调/handler 累积双跑(u-audit-fix)。
|
|
352
|
-
oncePerProcess("subagent-workflow:sync-engines-file", () => syncEnginesFile(agentDir));
|
|
353
|
-
|
|
354
|
-
// skill 路径两级缓存 session 级失效:pi 同进程可能有多个 session(TUI /new、/fork),
|
|
355
|
-
// 运行中安装的 skill 需对新 session 可见(含曾 miss 缓存的 undefined 条目与 npm 新装
|
|
356
|
-
// 包的候选目录)。session 内复用收益不变(IF8/DM3 消重发生在同 session 的重复调用)。
|
|
357
|
-
clearSkillPathCache();
|
|
358
|
-
|
|
359
|
-
// ── [M4] identity 子进程写入(V2 决策 5)──
|
|
360
|
-
// 子进程经 env(PI_SUBAGENT_*)接收自己的 identity,在 session_start 用 pi.appendEntry
|
|
361
|
-
// 写 subagent-identity custom entry。pi 自动生成 id/parentId → message tree 连续。
|
|
362
|
-
// 旧实现父进程 fs.appendFileSync 补写的 custom entry 缺 id/parentId → 污染 _buildIndex
|
|
363
|
-
// leafId 指针 → message tree 断成两棵 → 多轮对话丢上下文(bug 根因)。
|
|
364
|
-
// 主/子进程判定:PI_SUBAGENT_SELF_RECORD_ID 仅 session-runner spawn 子进程时注入,
|
|
365
|
-
// 主进程无此 env → 跳过(identity 只在子进程写一次)。
|
|
366
|
-
const selfRecordId = process.env.PI_SUBAGENT_SELF_RECORD_ID;
|
|
367
|
-
if (selfRecordId) {
|
|
368
|
-
try {
|
|
369
|
-
const modeEnv = process.env.PI_SUBAGENT_MODE;
|
|
370
|
-
// ExecutionMode 联合窄化:父进程经 env 注入(record.mode 恒为 "background"),
|
|
371
|
-
// 运行时校验合法值,非法兜底 background(避免裸 cast,符合 taste/no-unsafe-cast)。
|
|
372
|
-
const mode: ExecutionMode = modeEnv === "background" ? modeEnv : "background";
|
|
373
|
-
const identity: SubagentIdentityData = {
|
|
374
|
-
id: selfRecordId,
|
|
375
|
-
agent: process.env.PI_SUBAGENT_AGENT ?? "",
|
|
376
|
-
mode,
|
|
377
|
-
task: process.env.PI_SUBAGENT_TASK ?? "",
|
|
378
|
-
slug: process.env.PI_SUBAGENT_SLUG,
|
|
379
|
-
startedAt: Number(process.env.PI_SUBAGENT_STARTED_AT ?? Date.now()),
|
|
380
|
-
rootSessionId: process.env.PI_SUBAGENT_ROOT_SESSION_ID,
|
|
381
|
-
parentRecordId: process.env.PI_SUBAGENT_PARENT_RECORD_ID,
|
|
382
|
-
depth:
|
|
383
|
-
process.env.PI_SUBAGENT_DEPTH !== undefined
|
|
384
|
-
? Number(process.env.PI_SUBAGENT_DEPTH)
|
|
385
|
-
: undefined,
|
|
386
|
-
forkDepth:
|
|
387
|
-
process.env.PI_SUBAGENT_FORK_DEPTH !== undefined
|
|
388
|
-
? Number(process.env.PI_SUBAGENT_FORK_DEPTH)
|
|
389
|
-
: undefined,
|
|
390
|
-
chatMode: process.env.PI_SUBAGENT_CHAT_MODE === "true",
|
|
391
|
-
// [review round2] worktree 隔离标志(session-runner 注入):跨重启重建路径据此
|
|
392
|
-
// 拒绝续聊(handle 不可序列化,reattach 不可行)。
|
|
393
|
-
worktree: process.env.PI_SUBAGENT_WORKTREE === "true",
|
|
394
|
-
};
|
|
395
|
-
pi.appendEntry(IDENTITY_CUSTOM_TYPE, identity);
|
|
396
|
-
} catch (err) {
|
|
397
|
-
logger.warn("[subagents] identity appendEntry failed in session_start", {
|
|
398
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// ── [U2] 通知账本装配 + 重启恢复(设计 D4:存在性 / 可达性分离)──
|
|
404
|
-
// bind 先于 service.initSession(notifier.revive 在其内——notify() 经
|
|
405
|
-
// getBoundNotifyLedger 消费账本)。recoverFromSession 扫 ledger/ack 两列 entry
|
|
406
|
-
// 差集:未销账号重放投递(已销账零重发,notifyId 幂等);fork 继承未销账
|
|
407
|
-
// pending 属可接受语义(D4 归属规则——扫描域 = 单 session 文件,幂等键作用域
|
|
408
|
-
// 随文件域隔离)。compaction 存活情况归 session_compact handler 的条件降级(P-B4
|
|
409
|
-
// 探针阶段 5 实测,见 notify-ledger.ts compactionCheck)。
|
|
410
|
-
try {
|
|
411
|
-
const ledgerHost: NotifyLedgerHost = {
|
|
412
|
-
appendLedgerEntry: (customType, data) => {
|
|
413
|
-
pi.appendEntry(customType, data);
|
|
414
|
-
},
|
|
415
|
-
readSessionEntries: () => ctx.sessionManager.getEntries(),
|
|
416
|
-
isIdle: () => ctx.isIdle(),
|
|
417
|
-
onAgentSettled: (handler) => {
|
|
418
|
-
pi.on("agent_settled", handler);
|
|
419
|
-
},
|
|
420
|
-
sendDelivery: (message) => {
|
|
421
|
-
// D5 单通道:唯一发送形态 = sendCustomMessage({triggerTurn:true}),
|
|
422
|
-
// courier 已在发送前二次复查 isIdle,多通道投递选项已删(D5)。
|
|
423
|
-
pi.sendMessage(message, { triggerTurn: true });
|
|
424
|
-
},
|
|
425
|
-
};
|
|
426
|
-
// U4:重放观测已内聚到 ledger 分桶日志(recoveryReplays 桶经 extensionLogger
|
|
427
|
-
// 通道落盘),此处不再重复打日志。
|
|
428
|
-
bindNotifyLedgerHost(ledgerHost).recoverFromSession();
|
|
429
|
-
} catch (err) {
|
|
430
|
-
// 账本装配失败不阻断 session_start(通知退回 notifier 的内核路径)
|
|
431
|
-
logger.warn("[subagents] notify ledger bind failed", {
|
|
432
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// ── subagents 域:双 Service 装配 ──
|
|
437
|
-
const existingService = getSubagentService();
|
|
438
|
-
const existingModelService = getModelConfigService();
|
|
439
|
-
const modelService = existingModelService ?? new ModelConfigService({ agentDir, cwd });
|
|
440
|
-
const service = existingService ?? new SubagentService({ cwd, modelService, getMainSessionFile: getCachedMainSessionFile });
|
|
441
|
-
|
|
442
|
-
modelService.initModel({
|
|
443
|
-
modelRegistry: ctx.modelRegistry,
|
|
444
|
-
sessionId: ctx.sessionManager.getSessionId(),
|
|
445
|
-
ctxModel: ctx.model ?? undefined,
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
// ── W3: handler 注入链路接通 ──
|
|
449
|
-
// 进程级单例:channel registry + dialog queue 跨 session 复用
|
|
450
|
-
//(与 SubagentService 单例模式一致,globalThis Symbol 持有避免 jiti 多实例分裂)。
|
|
451
|
-
const channelRegistry = getOrCreateChannelRegistry();
|
|
452
|
-
const dialogQueue = getOrCreateDialogQueue();
|
|
453
|
-
const uiRequestHandler = createUiRequestHandlerForMode(ctx, channelRegistry, dialogQueue);
|
|
454
|
-
// SR-3: 无论 new 还是 existing(/resume /fork 复用),session_start 都必须注入 handler
|
|
455
|
-
service.setUiRequestHandler(uiRequestHandler);
|
|
456
|
-
|
|
457
|
-
// 主 session 文件:按 sessionId 解析(getSessionFile() 在 attach 场景会返回前一
|
|
458
|
-
// session 的文件,E2E 实测),未 flush 的新 session 回退 getSessionFile()。
|
|
459
|
-
// 值直传 initSession——jiti 多实例分裂下闭包缓存(cachedMainSessionFile)不跨
|
|
460
|
-
// 实例共享,恢复逻辑经缓存读会拿到滞后一个事件的值(E2E 实测 ENOENT 漏判);
|
|
461
|
-
// 缓存本身保留给既有 getter 消费者(fork source 解析)。
|
|
462
|
-
cachedMainSessionFile =
|
|
463
|
-
resolveMainSessionFileById(ctx.sessionManager.getSessionId()) ??
|
|
464
|
-
ctx.sessionManager.getSessionFile() ??
|
|
465
|
-
undefined;
|
|
466
|
-
|
|
467
|
-
service.initSession({
|
|
468
|
-
pi,
|
|
469
|
-
sessionId: ctx.sessionManager.getSessionId(),
|
|
470
|
-
mainSessionFile: cachedMainSessionFile,
|
|
471
|
-
// 注入 ctx.ui.setWidget 作为 streaming sink(只绑方法,不持有整个 ctx)。
|
|
472
|
-
// background subagent 执行期间,text_delta 经 SubagentStream 合并后由此通道转发。
|
|
473
|
-
// [W1 修复] ctx.mode === 'rpc' 守卫:TUI/json/print 下 streamSink = undefined(无 widget 噪音),
|
|
474
|
-
// rpc mode(GUI/xyz-agent)下保持原行为(ctx.ui.setWidget → sidecar → chatStore)。
|
|
475
|
-
// streamSink API 不变(SubagentStream.onDelta 仍可调,只是 TUI 下 stream 不会被创建)。
|
|
476
|
-
streamSink: ctx.mode === "rpc"
|
|
477
|
-
? { setWidget: (key, lines) => ctx.ui.setWidget(key, lines) }
|
|
478
|
-
: undefined,
|
|
479
|
-
// [#24] uiRequestHandler 单一注入入口:上方 setUiRequestHandler 已注入(SR-3 语义,
|
|
480
|
-
// new/existing service 均覆盖)。此处不再重复传 initSession.uiRequestHandler,避免
|
|
481
|
-
// 同一 handler 双路径注入造成的语义混淆与“哪一个是 source of truth”歧义。
|
|
482
|
-
// mode 仍需 session 级注入(uiObservability.setMode 依赖它,与 handler 无关)。
|
|
483
|
-
mode: ctx.mode,
|
|
484
|
-
// SR-4:注入 L2 dialog 队列——session-runner child close 时调 rejectChildDialogs
|
|
485
|
-
// 清理该 child 在 L2 的 pending dialog,防全局死锁(C1 修复:清理路径接通)。
|
|
486
|
-
dialogQueue,
|
|
487
|
-
// [竞态修复] 注入 ctx.isIdle:notifier flush 在主 agent busy 时退避,idle 后再
|
|
488
|
-
// sendMessage(triggerTurn),规避 agent_end→finishRun 窗口里走 steer 分支丢失通知。
|
|
489
|
-
isIdle: () => ctx.isIdle(),
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
if (!existingService) {
|
|
493
|
-
setModelConfigService(modelService);
|
|
494
|
-
setSubagentService(service);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// S-2: 启动 idle record GC 定时器(30 天 TTL,每小时检查一次)。
|
|
498
|
-
// 注册 setInterval 属进程级副作用——oncePerProcess 守卫防双跑(u-audit-fix)。
|
|
499
|
-
oncePerProcess("subagent-workflow:start-gc-timer", () => service.startGcTimer());
|
|
500
|
-
|
|
501
|
-
try {
|
|
502
|
-
// 递归扫描 <agentDir>/subagents + unlink 超 TTL 跨 session 文件属进程级维护
|
|
503
|
-
// ——oncePerProcess 守卫防双跑(u-audit-fix)。
|
|
504
|
-
oncePerProcess("subagent-workflow:cleanup-expired-session-files", () =>
|
|
505
|
-
maybeCleanupExpiredSessionFiles(agentDir, cwd));
|
|
506
|
-
} catch (err) {
|
|
507
|
-
logger.warn("[subagents] expired session file cleanup failed", {
|
|
508
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// ADR-035 启动恢复:扫描 manifest tmp 残留(崩溃打断的 writeManifest 留下,promote/unlink)。
|
|
513
|
-
// 扫描属进程级维护——oncePerProcess 守卫防双跑(u-audit-fix);第二派发重放首次
|
|
514
|
-
// Promise(结果缓存语义),recovered 计数日志可能重打,无文件副作用。
|
|
515
|
-
try {
|
|
516
|
-
const recovered = await oncePerProcess("subagent-workflow:recover-manifest-tmp-files", () =>
|
|
517
|
-
service.recoverManifestTmpFiles());
|
|
518
|
-
if (recovered.recovered > 0 || recovered.deleted > 0) {
|
|
519
|
-
logger.warn(`[subagents] manifest tmp recovery: ${recovered.recovered} promoted, ${recovered.deleted} deleted`);
|
|
520
|
-
}
|
|
521
|
-
} catch (err) {
|
|
522
|
-
logger.warn("[subagents] manifest tmp recovery failed", {
|
|
523
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
// 孤儿 worktree 清理(git/rm 进程操作 + 注册表/目录扫描)属进程级维护
|
|
529
|
-
// ——oncePerProcess 守卫防双跑(u-audit-fix)。
|
|
530
|
-
await oncePerProcess("subagent-workflow:worktree-scan", () => new WorktreeManager(agentDir).scan());
|
|
531
|
-
} catch (err) {
|
|
532
|
-
logger.warn("[subagents] worktree reaper scan failed", {
|
|
533
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// ── workflow 域:per-session store + runs ──
|
|
538
|
-
const sessionDir = resolveSessionDir();
|
|
539
|
-
const store = new JsonlRunStore({
|
|
540
|
-
sessionDir,
|
|
541
|
-
pi,
|
|
542
|
-
ctx,
|
|
543
|
-
});
|
|
544
|
-
const runs = new Map<string, WorkflowRun>();
|
|
545
|
-
|
|
546
|
-
// F-4/D-003: agent 发现走 shared/resource-discovery(ADR-031),modelService
|
|
547
|
-
// 自持 AgentRegistry(subagents/workflow 两域共用同一发现结果)。
|
|
548
|
-
// M2 修正:workflow 域 resolveAgentOpts 不再消费 agentRegistry(agent ref 交
|
|
549
|
-
// resolveIdentity),无需经 state 透传——modelService 是唯一 registry 源。
|
|
550
|
-
|
|
551
|
-
// MF-1: store 健康度跟踪。loadAll 失败 → storeHealthy=false,workflow 域启动时 fail-fast。
|
|
552
|
-
// 崩溃恢复四步(loadAll → failed → save → evict)收口到 core recoverCrashedRuns(D8:
|
|
553
|
-
// 宿主各写一遍正是 failure-mode-B);pending:unregister 经 hooks 外置发射(位置在
|
|
554
|
-
// transition 后、save 前,对齐原内联实现);save 走 store 冷路径(done 绕过去抖)——
|
|
555
|
-
// 冷路径语义在 JsonlRunStore.save 内,不随循环归属转移。loadAll 失败的 fail-fast
|
|
556
|
-
// (storeHealthy=false 停初始化)是宿主职责,core 原样上抛、这里 catch 兜住。
|
|
557
|
-
let storeHealthy = true;
|
|
558
|
-
try {
|
|
559
|
-
// 崩溃恢复 loadAll 扫 cwd 共享 sessionDir(同 cwd 跨 session 共享)并把 running run
|
|
560
|
-
// 转 failed 落盘——写非本 session 的 run state 文件属跨 session 副作用,oncePerProcess
|
|
561
|
-
// 守卫防双跑(u-audit-fix)。第二派发重放首次 Promise:不再落盘、不再 emit。
|
|
562
|
-
await oncePerProcess(
|
|
563
|
-
"subagent-workflow:recover-crashed-runs",
|
|
564
|
-
() =>
|
|
565
|
-
recoverCrashedRuns(
|
|
566
|
-
store,
|
|
567
|
-
runs,
|
|
568
|
-
"Process killed (kill-9 or crash recovery)",
|
|
569
|
-
{
|
|
570
|
-
onRunRecovered: (payload) => {
|
|
571
|
-
pi.events.emit("pending:unregister", payload);
|
|
572
|
-
},
|
|
573
|
-
},
|
|
574
|
-
),
|
|
575
|
-
);
|
|
576
|
-
} catch (err) {
|
|
577
|
-
// QMF-4 fix: store.loadAll 失败是关键路径错误,workflow 域将未初始化
|
|
578
|
-
logger.error("[subagent-workflow] store.loadAll failed, workflow domain uninitialized", {
|
|
579
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
580
|
-
});
|
|
581
|
-
storeHealthy = false;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// D-008: per-session SAR(需要 ctxModel 填底 + subagentService 委托目标)。
|
|
585
|
-
// old: const runner = new SubprocessAgentRunner()(module-level singleton,无 deps)
|
|
586
|
-
// new: per-session session_start 时创建,通过 sessionState 传给 makeDeps。
|
|
587
|
-
const runner = new SubprocessAgentRunner({
|
|
588
|
-
subagentService: service,
|
|
589
|
-
ctxModel: ctx.model ?? undefined,
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
// [engine-awareness D1b] lastEngine 初始化:构造性同源——单次 reloadGlobalConfig
|
|
593
|
-
// 读取同时刷新 Service 路由缓存与 lastEngine 基准,消灭 initModel 与本处两次独立
|
|
594
|
-
// 读取间的分叉窗口(两读值不一致时检测走 unchanged 分支不 reload,状态段/路由
|
|
595
|
-
// 永停旧值且永不通知)。ok/absent → 归一后的当前引擎;failed → undefined(首 turn
|
|
596
|
-
// 检测静默基线化兜底,此时缓存亦保持不动)。/resume、/fork 同样走 session_start
|
|
597
|
-
// (SR-3),基线天然覆盖。
|
|
598
|
-
const engineRead = modelService.reloadGlobalConfig();
|
|
599
|
-
sessionState.set(sessionId, {
|
|
600
|
-
store,
|
|
601
|
-
runs,
|
|
602
|
-
sessionDir,
|
|
603
|
-
runner,
|
|
604
|
-
ctx,
|
|
605
|
-
storeHealthy,
|
|
606
|
-
lastEngine:
|
|
607
|
-
engineRead.status === "failed" ? undefined : normalizeEngineId(engineRead.config.defaultEngine),
|
|
608
|
-
});
|
|
288
|
+
lsRef.lastSessionId = ctx.sessionManager.getSessionId();
|
|
289
|
+
const result = await setupSessionLifecycle(pi, ctx, makeLifecycleDeps());
|
|
290
|
+
sessionState.set(result.sessionId, result);
|
|
609
291
|
});
|
|
610
292
|
|
|
611
293
|
// ════════════════════════════════════════════════════════════
|
|
@@ -622,7 +304,7 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
622
304
|
}
|
|
623
305
|
} catch (err) {
|
|
624
306
|
logger.warn("[subagents] notify ledger compactionCheck failed", {
|
|
625
|
-
reason:
|
|
307
|
+
reason: toErrorMessage(err),
|
|
626
308
|
});
|
|
627
309
|
}
|
|
628
310
|
});
|
|
@@ -630,43 +312,16 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
630
312
|
// ════════════════════════════════════════════════════════════
|
|
631
313
|
// [U7 + engine-awareness U3] before_agent_start:引擎感知注入(链尾注册,D7——
|
|
632
314
|
// 段内容变化只断 system prompt 尾部 cache 前缀)。
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
// (D3,不设 triggerTurn——P1 探针已证此形态消息进入本 turn LLM 上下文,
|
|
637
|
-
// 证据:真机 pi rpc payload dump + 0.84.4 dist sendMessage→_appendCustomMessage
|
|
638
|
-
// →agent.state.messages.push→createContextSnapshot 调用链)→ 更新 lastEngine。
|
|
639
|
-
// ② 恒在状态段 <current_subagent_engine>(D6)+ 引擎清单段 <available_<engine>_models>。
|
|
640
|
-
// apply 后 getGlobalConfig() 即新值——通知、状态段、路由三处同 turn 对齐(G2)。
|
|
641
|
-
// 段序:状态段在前(文案声明 "listed ... below"),清单段在后;provider models 段
|
|
642
|
-
// 由更早注册的 handler 注入、位于上方。fail-safe 任何异常不注入不阻塞 agent loop。
|
|
315
|
+
// D7-④:接线收编于 engine-awareness.ts 的 setupEngineAwarenessInjector(与上方
|
|
316
|
+
// 三个 setup* 同形,注入链序由调用先后表达);per-session lastEngine 经
|
|
317
|
+
// sessionState 存取器注入。编排/渲染/链尾依据的完整注释随迁至该函数。
|
|
643
318
|
// ════════════════════════════════════════════════════════════
|
|
644
|
-
pi
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
readConfig: () => readGlobalConfig(getAgentDir()),
|
|
651
|
-
applyRead: (read) => service.applyGlobalConfig(read),
|
|
652
|
-
sendMessage: (message) => {
|
|
653
|
-
// D3:不设 triggerTurn——切换是用户主动行为,无需唤醒 AI 立即行动
|
|
654
|
-
pi.sendMessage(message, {});
|
|
655
|
-
},
|
|
656
|
-
getLastEngine: () => sessionState.get(sid)?.lastEngine,
|
|
657
|
-
setLastEngine: (engine) => {
|
|
658
|
-
const state = sessionState.get(sid);
|
|
659
|
-
if (state) state.lastEngine = engine;
|
|
660
|
-
},
|
|
661
|
-
});
|
|
662
|
-
const defaultEngine = service.getGlobalConfig().defaultEngine;
|
|
663
|
-
const append = [buildSubagentEngineSection(defaultEngine), buildEngineModelsPromptAppend(defaultEngine)]
|
|
664
|
-
.filter((part) => part !== "")
|
|
665
|
-
.join("\n\n");
|
|
666
|
-
return { systemPrompt: `${event.systemPrompt}\n\n${append}` };
|
|
667
|
-
} catch {
|
|
668
|
-
return undefined;
|
|
669
|
-
}
|
|
319
|
+
setupEngineAwarenessInjector(pi, {
|
|
320
|
+
getLastEngine: (sid) => sessionState.get(sid)?.lastEngine,
|
|
321
|
+
setLastEngine: (sid, engine) => {
|
|
322
|
+
const state = sessionState.get(sid);
|
|
323
|
+
if (state) state.lastEngine = engine;
|
|
324
|
+
},
|
|
670
325
|
});
|
|
671
326
|
|
|
672
327
|
// ════════════════════════════════════════════════════════════
|
|
@@ -775,7 +430,7 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
775
430
|
await state.store.dispose().catch((err: unknown) => {
|
|
776
431
|
logger.debug(
|
|
777
432
|
`[subagent-workflow] session_shutdown store.dispose failed (sessionId=${sessionId}, sessionDir=${state.sessionDir})`,
|
|
778
|
-
{ reason:
|
|
433
|
+
{ reason: toErrorMessage(err) },
|
|
779
434
|
);
|
|
780
435
|
});
|
|
781
436
|
sessionState.delete(sessionId);
|
|
@@ -833,6 +488,32 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
833
488
|
process.on("SIGINT", sigintHandler);
|
|
834
489
|
process.on("beforeExit", reapSpawnedChildrenOnShutdown);
|
|
835
490
|
|
|
491
|
+
// ════════════════════════════════════════════════════════════
|
|
492
|
+
// workflow deps 守卫(单一出口)
|
|
493
|
+
//
|
|
494
|
+
// [守卫合一] 原 pi.__workflowRun 内联守卫 + getDeps 守卫两份重复(state 缺失 /
|
|
495
|
+
// storeHealthy=false),合并为单一 getWorkflowDeps:返回 discriminated union,
|
|
496
|
+
// 两个消费点各自决定失败形态——pi.__workflowRun(D-8 API)返回错误对象(不
|
|
497
|
+
// throw,保住调用方 Promise 契约),getDeps(3 个 tool 的 lazy deps 源)throw
|
|
498
|
+
// (pi tool 框架将其转译为 tool 错误结果)。错误消息逐字保留(crash-recovery
|
|
499
|
+
// 测试锁 "store unavailable" / "loadAll failed" 子串)。
|
|
500
|
+
// ════════════════════════════════════════════════════════════
|
|
501
|
+
type WorkflowDepsResolution =
|
|
502
|
+
| { ok: true; deps: LauncherDeps }
|
|
503
|
+
| { ok: false; reason: string };
|
|
504
|
+
|
|
505
|
+
const getWorkflowDeps = (sessionId: string): WorkflowDepsResolution => {
|
|
506
|
+
const state = sessionState.get(sessionId);
|
|
507
|
+
if (!state) {
|
|
508
|
+
return { ok: false, reason: "Session not initialized" };
|
|
509
|
+
}
|
|
510
|
+
// MF-1: store 不健康时 fail-fast,避免 store.save 再次失败导致 run 状态不落地。
|
|
511
|
+
if (!state.storeHealthy) {
|
|
512
|
+
return { ok: false, reason: "Workflow store unavailable (loadAll failed in session_start)" };
|
|
513
|
+
}
|
|
514
|
+
return { ok: true, deps: makeDeps(state, state.ctx) };
|
|
515
|
+
};
|
|
516
|
+
|
|
836
517
|
// ════════════════════════════════════════════════════════════
|
|
837
518
|
// pi.__workflowRun(D-8 签名)
|
|
838
519
|
// ════════════════════════════════════════════════════════════
|
|
@@ -845,28 +526,19 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
845
526
|
// 注意:lastSessionId 是单值假设——Pi 当前保证单 session 串行(一次只一个活跃 session)。
|
|
846
527
|
// 若未来 Pi 支持多 session 并发,此处需改为从 ctx.sessionManager.getSessionId() 显式传入。
|
|
847
528
|
// M-2 已记录此假设。
|
|
848
|
-
const
|
|
849
|
-
if (!
|
|
529
|
+
const resolved = getWorkflowDeps(lsRef.lastSessionId);
|
|
530
|
+
if (!resolved.ok) {
|
|
850
531
|
return {
|
|
851
532
|
status: "done",
|
|
852
533
|
reason: "failed",
|
|
853
|
-
error:
|
|
854
|
-
runId: "",
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
// MF-1: store 不健康时 fail-fast,避免 store.save 再次失败导致 run 状态不落地。
|
|
858
|
-
if (!state.storeHealthy) {
|
|
859
|
-
return {
|
|
860
|
-
status: "done",
|
|
861
|
-
reason: "failed",
|
|
862
|
-
error: "Workflow store unavailable (loadAll failed in session_start)",
|
|
534
|
+
error: resolved.reason,
|
|
863
535
|
runId: "",
|
|
864
536
|
};
|
|
865
537
|
}
|
|
866
538
|
return runAndWait(
|
|
867
539
|
workflowName,
|
|
868
540
|
workflowArgs,
|
|
869
|
-
|
|
541
|
+
resolved.deps,
|
|
870
542
|
workflowSignal,
|
|
871
543
|
workflowTimeoutMs,
|
|
872
544
|
);
|
|
@@ -874,46 +546,65 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
874
546
|
|
|
875
547
|
// ════════════════════════════════════════════════════════════
|
|
876
548
|
// Tools(3 个)—— lazy deps 注入
|
|
549
|
+
//
|
|
550
|
+
// lazyDeps:属性访问触发 getWorkflowDeps 守卫 + makeDeps 求值(每属性独立)。
|
|
551
|
+
// 守卫合一后 getWorkflowDeps 返回 discriminated union,getter 内消费时 throw
|
|
552
|
+
// (与 __workflowRun 的 return 错误对象对齐——同源同消息,session-lifecycle.test.ts 锁定)。
|
|
877
553
|
// ════════════════════════════════════════════════════════════
|
|
878
|
-
const getDeps = () => {
|
|
879
|
-
// 注意:lastSessionId 是单值假设——Pi 当前保证单 session 串行(一次只一个活跃 session)。
|
|
880
|
-
// 若未来 Pi 支持多 session 并发,此处需改为从 ctx.sessionManager.getSessionId() 显式传入。
|
|
881
|
-
// M-2 已记录此假设。
|
|
882
|
-
const state = sessionState.get(lsRef.lastSessionId);
|
|
883
|
-
if (!state) throw new Error("Session not initialized");
|
|
884
|
-
// MF-1: store 不健康时 fail-fast,避免 store.save 再次失败导致 run 状态不落地。
|
|
885
|
-
if (!state.storeHealthy) {
|
|
886
|
-
throw new Error("Workflow store unavailable (loadAll failed in session_start)");
|
|
887
|
-
}
|
|
888
|
-
return makeDeps(state, state.ctx);
|
|
889
|
-
};
|
|
890
|
-
|
|
891
554
|
const lazyDeps: LauncherDeps = {
|
|
892
555
|
get store() {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
get runner() {
|
|
897
|
-
return getDeps().runner;
|
|
556
|
+
const resolved = getWorkflowDeps(lsRef.lastSessionId);
|
|
557
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
558
|
+
return resolved.deps.store;
|
|
898
559
|
},
|
|
899
560
|
get runs() {
|
|
900
|
-
|
|
561
|
+
const resolved = getWorkflowDeps(lsRef.lastSessionId);
|
|
562
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
563
|
+
return resolved.deps.runs;
|
|
564
|
+
},
|
|
565
|
+
get registry() {
|
|
566
|
+
const resolved = getWorkflowDeps(lsRef.lastSessionId);
|
|
567
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
568
|
+
return resolved.deps.registry;
|
|
901
569
|
},
|
|
902
|
-
registry,
|
|
903
570
|
get onRunDone() {
|
|
904
|
-
|
|
571
|
+
const resolved = getWorkflowDeps(lsRef.lastSessionId);
|
|
572
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
573
|
+
return resolved.deps.onRunDone;
|
|
905
574
|
},
|
|
906
575
|
get eventBus() {
|
|
907
|
-
|
|
576
|
+
const resolved = getWorkflowDeps(lsRef.lastSessionId);
|
|
577
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
578
|
+
return resolved.deps.eventBus;
|
|
908
579
|
},
|
|
580
|
+
get workerHost() {
|
|
581
|
+
const resolved = getWorkflowDeps(lsRef.lastSessionId);
|
|
582
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
583
|
+
return resolved.deps.workerHost;
|
|
584
|
+
},
|
|
585
|
+
get runner() {
|
|
586
|
+
const resolved = getWorkflowDeps(lsRef.lastSessionId);
|
|
587
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
588
|
+
return resolved.deps.runner;
|
|
589
|
+
},
|
|
590
|
+
// scheduleTimeBudget / onWorkflowCall 不可缺席(ports.ts D-12 regression fix):
|
|
591
|
+
// rebuildRuntime 重排 run 级墙钟预算计时器、worker 脚本嵌套 workflow() 调用都经
|
|
592
|
+
// 这两个成员消费——lazyDeps 缺席会让消费点拿到 undefined(可选属性静默放行),
|
|
593
|
+
// 带时间预算的 run 命中一次错误重试后计时器静默失效。转发形态与其余成员一致。
|
|
909
594
|
get scheduleTimeBudget() {
|
|
910
|
-
|
|
595
|
+
const resolved = getWorkflowDeps(lsRef.lastSessionId);
|
|
596
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
597
|
+
return resolved.deps.scheduleTimeBudget;
|
|
911
598
|
},
|
|
912
599
|
get onWorkflowCall() {
|
|
913
|
-
|
|
600
|
+
const resolved = getWorkflowDeps(lsRef.lastSessionId);
|
|
601
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
602
|
+
return resolved.deps.onWorkflowCall;
|
|
914
603
|
},
|
|
915
604
|
get log() {
|
|
916
|
-
|
|
605
|
+
const resolved = getWorkflowDeps(lsRef.lastSessionId);
|
|
606
|
+
if (!resolved.ok) throw new Error(resolved.reason);
|
|
607
|
+
return resolved.deps.log;
|
|
917
608
|
},
|
|
918
609
|
};
|
|
919
610
|
|
|
@@ -938,19 +629,9 @@ export default function subagentsWorkflowExtension(pi: ExtensionAPI): void {
|
|
|
938
629
|
// ============================================================
|
|
939
630
|
|
|
940
631
|
// channel registry 经 channel-registry-access.ts 公开访问(跨扩展 API)。
|
|
941
|
-
// dialog queue
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
/** 获取或创建进程级 dialog queue 单例。
|
|
945
|
-
* L2 跨子进程串行队列——所有子进程的 dialog 类请求共享同一队列实例。 */
|
|
946
|
-
function getOrCreateDialogQueue(): DialogGlobalQueue {
|
|
947
|
-
let queue = Reflect.get(globalThis, DIALOG_QUEUE_KEY) as DialogGlobalQueue | undefined;
|
|
948
|
-
if (!queue) {
|
|
949
|
-
queue = new DialogGlobalQueue();
|
|
950
|
-
Reflect.set(globalThis, DIALOG_QUEUE_KEY, queue);
|
|
951
|
-
}
|
|
952
|
-
return queue;
|
|
953
|
-
}
|
|
632
|
+
// dialog queue 单例随 session_start 装配域迁居 session-lifecycle.ts
|
|
633
|
+
// (getOrCreateDialogQueue;消费方 = 该文件 createOrReuseServices + 本文件
|
|
634
|
+
// session_shutdown),Symbol key 单定义点随迁,避免双份定义漂移。
|
|
954
635
|
|
|
955
636
|
// 跨扩展 channel handler 注册入口已收口到 core 深路径
|
|
956
637
|
// `@zhushanwen/subagent-core/execution/channel-registry-access.ts`
|