@zhushanwen/pi-subagent-workflow 9.2.1 → 9.4.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 +16 -16
- package/src/host/__tests__/inflight-reporter.test.ts +44 -56
- package/src/host/__tests__/journal-reporter.test.ts +230 -0
- package/src/host/inflight-reporter.ts +29 -82
- package/src/host/journal-reporter.ts +204 -0
- package/src/index.ts +5 -2
- package/src/interface/command/__tests__/commands-resume.test.ts +53 -8
- package/src/interface/command/__tests__/subagent-model.test.ts +242 -0
- package/src/interface/command/command-actions.ts +6 -5
- package/src/interface/command/commands.ts +47 -7
- package/src/interface/command/subagent-model.ts +268 -0
- package/src/interface/command/subagents.ts +2 -2
- package/src/interface/tool/__tests__/tool-workflow-resume.test.ts +20 -0
- package/src/interface/tool/subagent-tool.ts +6 -5
- package/src/interface/tool/tool-workflow-script.ts +13 -9
- package/src/interface/tool/tool-workflow.ts +33 -4
- package/src/jsonl-run-store.ts +13 -3
- package/src/session-lifecycle.ts +4 -6
- package/src/workflow-events.ts +66 -5
|
@@ -130,6 +130,26 @@ describe("actionResume", () => {
|
|
|
130
130
|
expect(options.budgetTokens).toBe(50_000);
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
it("model 形参透传(F1-26 后续项:core 落统一覆盖记账——补切 + 无参 resume 合一步)", async () => {
|
|
134
|
+
const recordPath = writeRecordStream([runCreatedFrame('{"a":1}')]);
|
|
135
|
+
await actionResume(
|
|
136
|
+
{ action: "resume", runId: "wf-test", args: { a: 1 }, model: "p2/m2" } as never,
|
|
137
|
+
makeDeps(recordPath) as never,
|
|
138
|
+
);
|
|
139
|
+
const options = vi.mocked(resumeRun).mock.calls[0]![2] as { model?: string };
|
|
140
|
+
expect(options.model).toBe("p2/m2");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("model 缺省 → options 不带 model 键(无参 resume 不写覆盖记账)", async () => {
|
|
144
|
+
const recordPath = writeRecordStream([runCreatedFrame('{"a":1}')]);
|
|
145
|
+
await actionResume(
|
|
146
|
+
{ action: "resume", runId: "wf-test", args: { a: 1 } } as never,
|
|
147
|
+
makeDeps(recordPath) as never,
|
|
148
|
+
);
|
|
149
|
+
const options = vi.mocked(resumeRun).mock.calls[0]![2] as { model?: string };
|
|
150
|
+
expect("model" in options).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
133
153
|
it("tokens 缺省 → options 不带 budgetTokens 键(core 侧跳过覆盖、走三档回落)", async () => {
|
|
134
154
|
await actionResume(
|
|
135
155
|
{ action: "resume", runId: "wf-test" } as never,
|
|
@@ -92,11 +92,12 @@ function isModelOverrideObj(a: unknown): a is { model?: unknown; thinkingLevel?:
|
|
|
92
92
|
/**
|
|
93
93
|
* start 路径类参数(skillPath / cwd)运行时守卫:绝对路径 + 禁 `..` 穿越。
|
|
94
94
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
95
|
+
* 校验链事实(语义登记 PS-20,verifiedWith 以 pi-semantics.json 为准,逐 claim 行号见该条目
|
|
96
|
+
* piAnchor):pi agent-loop 对注册 typebox schema
|
|
97
|
+
* 有运行时强校验——在 beforeToolCall / execute 之前调
|
|
98
|
+
* validateToolArguments(pi-ai validation.js:Value.Convert + Compile
|
|
99
|
+
* + Check,失败 throw `Validation failed for tool`)→ catch 走
|
|
100
|
+
* immediate error,execute 不被调用。schema 的
|
|
100
101
|
* pattern(skillPath/cwd `^/`)/ maxLength(slug 35)是运行时强制而非仅模型可见
|
|
101
102
|
* 契约;tool-definition-wrapper.js:11 只原样透传 params,校验发生在上游 agent-loop 层。
|
|
102
103
|
*
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* C5②(convergence D-6):generate 五道闸校验管线 + tmp 写盘下沉 core
|
|
14
14
|
* generateWorkflowScript(报错文案逐字平移,CA2 前提);save/delete 改调 core
|
|
15
15
|
* barrel(第三可选目录参数缺省即 pi 布局 .pi/workflows——pi 现两参调用形态零变化)。
|
|
16
|
-
* 结构化 {ok}|{error} → pi 的 execute-throw 契约转换在本层(pi
|
|
17
|
-
*
|
|
16
|
+
* 结构化 {ok}|{error} → pi 的 execute-throw 契约转换在本层(pi 1.0.0:execute 抛出经
|
|
17
|
+
* pi-agent-core agent-loop.js executePreparedToolCall 外层 catch 统一转 isError:true);
|
|
18
|
+
* AbortSignal 的 aborted 检查在 execute 入口统一拦截(与
|
|
18
19
|
* workflow / subagents 两 tool 同源,tool-shared assertNotAborted;C4 偏差 #4)。
|
|
19
20
|
*
|
|
20
21
|
* 层归属:Interface。依赖 Pi SDK + engine script-lint + infra workflow-files。
|
|
@@ -183,7 +184,8 @@ export function registerWorkflowScriptTool(
|
|
|
183
184
|
): Promise<WorkflowScriptExecuteResult> {
|
|
184
185
|
// P1-2 abort 前置(三 tool 对称):5 个 action 统一在 execute 入口拦截——
|
|
185
186
|
// 原先只在 generate 内检查,lint/save/delete/list 四路径漏拦。throw(W4b):
|
|
186
|
-
//
|
|
187
|
+
// 错误路径统一 throw(单一错误路径纪律;pi 1.0.0 agent-loop 对抛出统一转
|
|
188
|
+
// isError:true error result)。
|
|
187
189
|
assertNotAborted(signal);
|
|
188
190
|
let result: WorkflowScriptExecuteResult;
|
|
189
191
|
switch (params.action) {
|
|
@@ -203,8 +205,9 @@ export function registerWorkflowScriptTool(
|
|
|
203
205
|
result = await actionList(registry);
|
|
204
206
|
break;
|
|
205
207
|
default:
|
|
206
|
-
// 防御性(schema StringEnum 先拦):throw(W4b
|
|
207
|
-
//
|
|
208
|
+
// 防御性(schema StringEnum 先拦):throw(W4b)——错误路径统一 throw
|
|
209
|
+
// (pi 1.0.0 pi-agent-core agent-loop.js executePreparedToolCall :579-588:
|
|
210
|
+
// 抛出经外层 catch 转 isError:true error result)。
|
|
208
211
|
throw new Error(`Unknown action: ${String(params.action)}`);
|
|
209
212
|
}
|
|
210
213
|
// GUI 协议:RPC 模式下附加 __gui__ 到 details(attach 单点在 tool-shared)
|
|
@@ -229,7 +232,8 @@ export function registerWorkflowScriptTool(
|
|
|
229
232
|
// ── generate action ──────────────────────────────────────────
|
|
230
233
|
|
|
231
234
|
export function actionGenerate(params: ScriptParams): WorkflowScriptExecuteResult {
|
|
232
|
-
// throw(W4b
|
|
235
|
+
// throw(W4b):错误路径统一 throw(单一错误路径纪律;pi 1.0.0 agent-loop 对抛出
|
|
236
|
+
// 统一转 isError:true error result)。
|
|
233
237
|
// abort 前置在 execute 入口统一拦截(三 tool 同源,tool-shared assertNotAborted),
|
|
234
238
|
// 本函数不再自带 signal 检查。
|
|
235
239
|
const name = params.name ?? "";
|
|
@@ -386,9 +390,9 @@ async function actionList(registry: WorkflowScriptRegistry): Promise<WorkflowScr
|
|
|
386
390
|
// ── helper ───────────────────────────────────────────────────
|
|
387
391
|
|
|
388
392
|
/**
|
|
389
|
-
* 构造纯文本非错误结果(W4b:isError
|
|
390
|
-
*
|
|
391
|
-
*
|
|
393
|
+
* 构造纯文本非错误结果(W4b:isError 参数已删除——异常错误路径一律 throw(单一
|
|
394
|
+
* 错误路径纪律;pi 1.0.0 pi-agent-core agent-loop.js :579-588 对抛出统一转
|
|
395
|
+
* isError:true error result),编译器兜底防回潮)。
|
|
392
396
|
*/
|
|
393
397
|
function textResult(text: string): WorkflowScriptExecuteResult {
|
|
394
398
|
return {
|
|
@@ -114,7 +114,11 @@ const WorkflowParams = Type.Object({
|
|
|
114
114
|
Type.String({ description: "Error/reason message (optional, used with abort)" }),
|
|
115
115
|
),
|
|
116
116
|
model: Type.Optional(Type.String({
|
|
117
|
-
description:
|
|
117
|
+
description:
|
|
118
|
+
"Run-level model override in 'provider/modelId' format. When set, all agents spawned by this run inherit it by default (unless a per-call agent() opts.model is set). " +
|
|
119
|
+
"Also accepted on action:\"resume\" — recorded as the run's user override (same channel as a panel switch) and drives all re-dispatched steps. " +
|
|
120
|
+
"A user-issued runtime model override (panel model switch / /subagent-model command) takes precedence over this parameter — user override wins over any explicitly scripted model. " +
|
|
121
|
+
"Omit to inherit the main agent's model.",
|
|
118
122
|
})),
|
|
119
123
|
thinkingLevel: Type.Optional(StringEnum(THINKING_ORDER, {
|
|
120
124
|
description: "Run-level thinkingLevel override (off/minimal/low/medium/high/xhigh/max). All agents in this run inherit it by default. Omit to default each agent to its model's highest available level.",
|
|
@@ -232,14 +236,21 @@ export function registerWorkflowTool(
|
|
|
232
236
|
"completed calls from the record at zero token cost and continues where the run stopped. " +
|
|
233
237
|
"Do NOT resume a settled (done/failed/cancelled) run — start a new run instead.",
|
|
234
238
|
"resume: pass the SAME args as the original run (they are verified field-by-field; a mismatch is rejected " +
|
|
235
|
-
"with the differing fields listed). Omit args to reuse the original ones. Changed args = different intent = new run."
|
|
239
|
+
"with the differing fields listed). Omit args to reuse the original ones. Changed args = different intent = new run. " +
|
|
240
|
+
"To resume with a specific model: pass model ('provider/modelId') on the resume itself — it is recorded as the " +
|
|
241
|
+
"run's user override (same channel as a panel switch), drives all re-dispatched steps, and persists for later " +
|
|
242
|
+
"resumes without the parameter. Alternatively switch the run's model first (panel selector or /subagent-model) " +
|
|
243
|
+
"then resume without model — both are the same override channel.",
|
|
236
244
|
"Call shapes (JSON): " +
|
|
237
245
|
"- run: {\"action\":\"run\",\"name\":\"<script>\",\"args\":{...},\"tokens\":N,\"time\":N,\"model\":\"<provider/modelId>\",\"thinkingLevel\":\"<level>\"}. " +
|
|
238
246
|
"- status: {\"action\":\"status\"}. " +
|
|
239
247
|
"- abort: {\"action\":\"abort\",\"runId\":\"<id>\"} (optional: {\"error\":\"<reason>\"}). " +
|
|
240
|
-
"- resume: {\"action\":\"resume\",\"runId\":\"<id>\",\"args\":{...},\"tokens\":N,\"time\":N} — args/tokens/time optional.",
|
|
248
|
+
"- resume: {\"action\":\"resume\",\"runId\":\"<id>\",\"args\":{...},\"tokens\":N,\"time\":N,\"model\":\"<provider/modelId>\"} — args/tokens/time/model optional.",
|
|
241
249
|
"Budget: Do NOT set tokens/time unless the user explicitly requests a limit. Built-in workflows run unlimited by default.",
|
|
242
|
-
"Model/thinkingLevel: omit by default (inherit main agent's model). Only set model/thinkingLevel when the user explicitly requests a specific model or thinking depth for this run."
|
|
250
|
+
"Model/thinkingLevel: omit by default (inherit main agent's model). Only set model/thinkingLevel when the user explicitly requests a specific model or thinking depth for this run. " +
|
|
251
|
+
"Note: a user-issued runtime model override (panel switch / /subagent-model) takes precedence over the model parameter — an explicitly scripted thinkingLevel still applies " +
|
|
252
|
+
"(explicit levels outrank the override's inline level). If a run's explicitly scripted model seems ignored, " +
|
|
253
|
+
"check for an active user override (run journal model-override frame) before assuming a resolution bug.",
|
|
243
254
|
"Anti-patterns: Flattening args sub-fields (task/items/...) to the top level — they belong inside args. Calling {\"action\":\"run\"} without name.",
|
|
244
255
|
"CRITICAL: For orchestration patterns, ALWAYS use action:run with the <location> absolute " +
|
|
245
256
|
"path of the matching listed workflow — NEVER use workflow-script action:generate to recreate patterns " +
|
|
@@ -565,10 +576,28 @@ export async function actionResume(
|
|
|
565
576
|
assertEntryTimeBudget(params.time);
|
|
566
577
|
assertEntryTokenBudget(params.tokens);
|
|
567
578
|
|
|
579
|
+
// 显式 model 参数的目录预检(run action 同款 D8 拒单语义——查无同步 throw 零写,
|
|
580
|
+
// 单例缺席降级跳过 + warn,派发期 identity 解析仍是权威裁决)。
|
|
581
|
+
if (params.model !== undefined) {
|
|
582
|
+
const modelService = getModelConfigService();
|
|
583
|
+
if (modelService === null) {
|
|
584
|
+
logger.warn(
|
|
585
|
+
"[tool-workflow] model catalog unavailable (model service not initialized) — resume model catalog check skipped; dispatch-time identity resolution remains authoritative",
|
|
586
|
+
);
|
|
587
|
+
} else {
|
|
588
|
+
assertModelInCatalog(params.model, modelService.getModelRegistry(), {
|
|
589
|
+
source: "resume model override",
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
568
594
|
const options: ResumeRunOptions = {
|
|
569
595
|
...(params.time !== undefined ? { budgetTimeMs: params.time } : {}),
|
|
570
596
|
// token 预算显式覆盖(与 time 同款通道;三档回落与落盘归 core 单点)
|
|
571
597
|
...(params.tokens !== undefined ? { budgetTokens: params.tokens } : {}),
|
|
598
|
+
// [F1-26 后续项] 显式 model 透传(core 落统一覆盖记账——「补切 + 无参 resume」
|
|
599
|
+
// 的合一步;格式与目录已在本入口预检,记账落盘归 core 锁段单点)
|
|
600
|
+
...(params.model !== undefined ? { model: params.model } : {}),
|
|
572
601
|
// [§2.5] args 原样下传由 core 判定(D14 单源);journalDir 传壳的 store 同源目录,
|
|
573
602
|
// 否则 core 会按模块锚解析 record 路径——多 session 场景会静默读成「无记录」
|
|
574
603
|
//(D14 静默放行 = 安全语义反转;core 锚点与壳 store 锚点必须同源)。
|
package/src/jsonl-run-store.ts
CHANGED
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
type WorkflowRunEvent,
|
|
77
77
|
} from "@zhushanwen/subagent-core";
|
|
78
78
|
import { WorkflowRun } from "@zhushanwen/subagent-core";
|
|
79
|
-
import { errorLogsFromEvents, rebuildBudget, runAccountingFromEvents } from "@zhushanwen/subagent-core";
|
|
79
|
+
import { errorLogsFromEvents, latestModelOverride, rebuildBudget, runAccountingFromEvents } from "@zhushanwen/subagent-core";
|
|
80
80
|
import { guardStaleCtx, isEnoentError, toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
81
81
|
|
|
82
82
|
// ── [W1 / D1] v2 条目读面(注册定界 + 终态条目抑制)────────────────
|
|
@@ -283,7 +283,7 @@ interface CallDraft {
|
|
|
283
283
|
/**
|
|
284
284
|
* record 事件流 → WorkflowRun 聚合重建(纯函数,零 IO)。
|
|
285
285
|
*
|
|
286
|
-
* fold 语义(词表 = run-events.ts
|
|
286
|
+
* fold 语义(词表 = run-events.ts 事件词表,单源):
|
|
287
287
|
* - `run-created`:spec.scriptSource(全文,[D1])+ args(全文,设计 §3.1 载荷表;
|
|
288
288
|
* 旧格式帧回落 argsSummary 尽力恢复——parseLegacyArgsSummary)+ startedAt 锚点
|
|
289
289
|
* (帧 ts 优先,回落注册条目 startedAt);
|
|
@@ -294,7 +294,10 @@ interface CallDraft {
|
|
|
294
294
|
* 形态建占位行——同 journal fold 兜底);sessionFile/sessionId 从 result 透传
|
|
295
295
|
* (对齐 finalizeCall 的 call 字段填充纪律);
|
|
296
296
|
* - `run-settled`:终局(status=done + reason 映射 + completedAt=帧 ts);无帧 =
|
|
297
|
-
* running
|
|
297
|
+
* running(交恢复链收编);
|
|
298
|
+
* - `model-override`:latestModelOverride 折叠 → meta.modelOverride(最新一条生效,
|
|
299
|
+
* 覆盖旧覆盖值不叠加;无覆盖不造键——详见 foldRecordStreamToRun 内
|
|
300
|
+
* [subagent-model-switch §6.6①/§7.4] 注释,不在此重复展开)。
|
|
298
301
|
*
|
|
299
302
|
* record 流不承载的字段(budget 计数/errorLogs/trace 完整面)按恢复语义最小形态
|
|
300
303
|
* 缺省——步骤级详情的恢复读面 = record 流直读(session-reader 家族链),不经本聚合。
|
|
@@ -506,6 +509,11 @@ function foldRecordStreamToRun(
|
|
|
506
509
|
const calls = draftsToAgentCalls(drafts, sharedNodes, nodes);
|
|
507
510
|
|
|
508
511
|
const settledEvent = lastRunSettledEvent(events);
|
|
512
|
+
// [subagent-model-switch §6.6①/§7.4] 覆盖记账折叠(派生视图半边):生效覆盖值 =
|
|
513
|
+
// 最新一条(latestModelOverride core 单点——覆盖旧覆盖值不叠加,不变量 2);
|
|
514
|
+
// 无覆盖 undefined 不造键。fold 未跟时本行缺失 = 覆盖状态静默丢行——
|
|
515
|
+
// jsonl-run-store-loadall 的 fold 消费用例是此丢行风险的机器防线(P8 第五道)。
|
|
516
|
+
const modelOverride = latestModelOverride(events);
|
|
509
517
|
if (settledEvent === undefined) {
|
|
510
518
|
const interruptedAt = lastInterruptedAt(events);
|
|
511
519
|
return WorkflowRun.reconstruct(
|
|
@@ -524,6 +532,7 @@ function foldRecordStreamToRun(
|
|
|
524
532
|
//(CLI/TUI 不显示僵尸「运行中」;resume 资格判据在 core fold lifecycle,
|
|
525
533
|
// 不受本投影影响)。
|
|
526
534
|
...(interruptedAt !== undefined ? { interruptedAt } : {}),
|
|
535
|
+
...(modelOverride !== undefined ? { modelOverride } : {}),
|
|
527
536
|
},
|
|
528
537
|
);
|
|
529
538
|
}
|
|
@@ -543,6 +552,7 @@ function foldRecordStreamToRun(
|
|
|
543
552
|
{
|
|
544
553
|
startedAt: startedAtIso,
|
|
545
554
|
completedAt: new Date(settledEvent.ts).toISOString(),
|
|
555
|
+
...(modelOverride !== undefined ? { modelOverride } : {}),
|
|
546
556
|
},
|
|
547
557
|
);
|
|
548
558
|
}
|
package/src/session-lifecycle.ts
CHANGED
|
@@ -523,19 +523,17 @@ function appendSubagentIdentityEntry(pi: ExtensionAPI): void {
|
|
|
523
523
|
* 返回 undefined。
|
|
524
524
|
*/
|
|
525
525
|
export function bindLedgerHostAndRecover(pi: ExtensionAPI, ctx: ExtensionContext): NotifyLedgerHost | undefined {
|
|
526
|
-
// host 五端口接线 + 送达 stale 防御(D5 单通道 {triggerTurn:true} / stale
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
// sendDisplayMessage = 生产 bind 恒实现(NotifyLedgerHost 可选端口的本侧既有语义)。
|
|
526
|
+
// host 五端口接线 + 送达 stale 防御(D5 单通道 {triggerTurn:true} / stale 静默降级)
|
|
527
|
+
// 收敛在 @zhushanwen/pi-notify-ledger-host 工厂,权威注释随迁工厂内;
|
|
528
|
+
// component/logger 参数保持本包 label 前缀与 warn 通道归因不变。
|
|
530
529
|
const ledgerHost = createPiNotifyLedgerHost(pi, ctx, {
|
|
531
530
|
component: "subagent-workflow",
|
|
532
531
|
logger,
|
|
533
|
-
sendDisplayMessage: true,
|
|
534
532
|
});
|
|
535
533
|
// bind 与 recover 拆独立 try(失败归因不同):
|
|
536
534
|
// - bind 失败:槽上无 ledger,消费方(getBoundNotifyLedger)退回内核直发路径;
|
|
537
535
|
// - recover 失败:bind 已成功、槽上 ledger 仍在,消费方照常走账本路径(重启重放
|
|
538
|
-
//
|
|
536
|
+
// 缺席,settled 边沿仍投新通知)——不得共用 "bind failed" 文案误报。
|
|
539
537
|
let ledger: ReturnType<typeof bindNotifyLedgerHost>;
|
|
540
538
|
try {
|
|
541
539
|
ledger = bindNotifyLedgerHost(ledgerHost);
|
package/src/workflow-events.ts
CHANGED
|
@@ -52,6 +52,9 @@ import { getSubagentService } from "@zhushanwen/subagent-core";
|
|
|
52
52
|
import type { LauncherDeps } from "@zhushanwen/subagent-core";
|
|
53
53
|
import { terminateRunningRuns } from "@zhushanwen/subagent-core";
|
|
54
54
|
import { setInFlightListener } from "@zhushanwen/subagent-core";
|
|
55
|
+
// [event-push-channel W-P1] journal 落盘事件出口接线:core 两域落盘提交点 → 出口
|
|
56
|
+
// 回调 → 壳层 journal reporter(select marker 通道带回执推送 runtime 派生视图)。
|
|
57
|
+
import { setJournalAppendListener } from "@zhushanwen/subagent-core";
|
|
55
58
|
import {
|
|
56
59
|
evictDoneRunsBeyondCap,
|
|
57
60
|
MAX_RETAINED_DONE_RUNS,
|
|
@@ -65,6 +68,10 @@ import { WorkflowScriptRegistryImpl } from "@zhushanwen/subagent-core";
|
|
|
65
68
|
// [u7a D5] 在途上报出口:实例由本 seam 创建并接线 setInFlightListener(组合根零
|
|
66
69
|
// 管道),session_start / session_shutdown 驱动 attach/detach;测试可注入 fake。
|
|
67
70
|
import { createInFlightReporter, type InFlightReporter } from "./host/inflight-reporter.ts";
|
|
71
|
+
// [event-push-channel W-P1] journal 推送出口:实例由本 seam 创建并接线
|
|
72
|
+
// setJournalAppendListener(与 inflight reporter 同 seam 同款),session_start /
|
|
73
|
+
// session_shutdown 驱动 attach/detach;测试可注入 fake。
|
|
74
|
+
import { createJournalReporter, type JournalReporter } from "./host/journal-reporter.ts";
|
|
68
75
|
// [W2/V1 D1 第 8 行] 判活类消费面(isScriptRunning)经 core 投影单点
|
|
69
76
|
//(runSummary.status 三态投影——终局判定源 = 终局记录注册表)。
|
|
70
77
|
import { type RunSettlementRecord } from "./jsonl-run-store.ts";
|
|
@@ -274,7 +281,7 @@ export interface WorkflowDomainHandle {
|
|
|
274
281
|
*/
|
|
275
282
|
export function setupWorkflowDomain(
|
|
276
283
|
pi: ExtensionAPI,
|
|
277
|
-
wiring: { inflightReporter?: InFlightReporter } = {},
|
|
284
|
+
wiring: { inflightReporter?: InFlightReporter; journalReporter?: JournalReporter } = {},
|
|
278
285
|
): WorkflowDomainHandle {
|
|
279
286
|
// [u7a D5] 在途聚合上报接线(自组合根 index.ts 收编):core 状态迁移
|
|
280
287
|
// (spawn/close/arm/disarm)→ 出口回调 → reporter 经 select 通道推绝对计数帧。
|
|
@@ -285,6 +292,14 @@ export function setupWorkflowDomain(
|
|
|
285
292
|
// 组合根对实例零知识(纯管道参数已删)。
|
|
286
293
|
const inflightReporter = wiring.inflightReporter ?? createInFlightReporter();
|
|
287
294
|
setInFlightListener(inflightReporter.onInFlightChanged);
|
|
295
|
+
// [event-push-channel W-P1] journal 推送出口接线(与 inflight reporter 同 seam 同款):
|
|
296
|
+
// core 两域落盘提交点 → 出口回调 → reporter 按 (domain,fileKey) 分组合并 → select
|
|
297
|
+
// marker 通道带回执推送 runtime。出口为进程级单监听,后注册覆盖先注册(jiti 重载
|
|
298
|
+
// 幂等);回调同步 void,不进任何落盘 await 链。ctx 由 session_start 注入
|
|
299
|
+
// (factory 阶段无 ui)。wiring.journalReporter 是测试注入面(fake reporter 观察
|
|
300
|
+
// attach/detach 驱动时点);生产路径缺省真实创建——组合根对实例零知识。
|
|
301
|
+
const journalReporter = wiring.journalReporter ?? createJournalReporter();
|
|
302
|
+
setJournalAppendListener(journalReporter.onJournalAppended);
|
|
288
303
|
// [skill-reload D2] handle.state 即槽对象本身(不做解构重包装——否则容器每次
|
|
289
304
|
// factory 重跑新建,调用方拿不到「同一 domain state 引用」的接管前提)。
|
|
290
305
|
const domainState = getOrCreateWorkflowDomainState();
|
|
@@ -387,6 +402,28 @@ export function setupWorkflowDomain(
|
|
|
387
402
|
}
|
|
388
403
|
return service.executeWorkflowAgent(opts, parentRunId, signal, undefined, undefined, stepIndex);
|
|
389
404
|
},
|
|
405
|
+
// [F1-18 修复] resume 显式 model 落账后的宿主投影同步(生产接线——单例现读
|
|
406
|
+
// 同 workflowAgentDispatch 形态)。实现 = SubagentService.applyRunOverrideProjection
|
|
407
|
+
// (覆盖内存表写值 + 重建负缓存作废);throw 走 resume 侧回滚围栏(run 回
|
|
408
|
+
// interrupted 可重试),service 缺席文案同款 [C2] 闭环。
|
|
409
|
+
onResumeModelOverrideCommitted: (runId, override) => {
|
|
410
|
+
const service = getSubagentService();
|
|
411
|
+
if (!service) {
|
|
412
|
+
throw new Error(
|
|
413
|
+
"resume model override projection unavailable: subagent service not initialized " +
|
|
414
|
+
"(session_start assembly failed). Recovery: retry the resume once the session " +
|
|
415
|
+
"is initialized; the override frame is already persisted in the run journal.",
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
service.applyRunOverrideProjection(runId, override);
|
|
419
|
+
},
|
|
420
|
+
// [dmg-r2-5] 宿主会话锚现读口(run-created 帧 rootSessionId 载荷源):与
|
|
421
|
+
// workflowAgentDispatch 同款 service 单例现读形态——sessionRootId 根进程语义
|
|
422
|
+
// 与 record 域 rootSessionId 同源(根进程 = 本 session id,嵌套 = env ROOT)。
|
|
423
|
+
// 同 cwd 多会话共享 workflow-state 目录,runtime 网关 runId 路由靠该字段精确
|
|
424
|
+
// 判归属;service 缺席返回 null(帧不落字段,读侧旧格式行放行——不抛,创建
|
|
425
|
+
// 主链不因锚缺席失败)。
|
|
426
|
+
getSessionRootId: () => getSubagentService()?.getSessionRootId() ?? null,
|
|
390
427
|
log,
|
|
391
428
|
};
|
|
392
429
|
return deps;
|
|
@@ -436,16 +473,20 @@ export function setupWorkflowDomain(
|
|
|
436
473
|
// 开头一致)+ 装配结果写入 per-session sessionState。
|
|
437
474
|
// ════════════════════════════════════════════════════════════
|
|
438
475
|
pi.on("session_start", async (event: SessionStartEvent, ctx: ExtensionContext) => {
|
|
439
|
-
// 装配链异常不向 pi 事件分发逃逸(
|
|
440
|
-
// 直接炸进程——E1
|
|
476
|
+
// 装配链异常不向 pi 事件分发逃逸(0.84.4 的 extension handler 未捕获 rejection
|
|
477
|
+
// 直接炸进程——E1 同机制;1.0.0 起 runner.emit 以 try/catch 吸收转 emitError 上报,
|
|
478
|
+
// 围栏仍保留——handler 不依赖 pi 侧异常处理语义):error 留痕(含 sessionId)后保持 handler 不抛。
|
|
441
479
|
// lsRef 在 try 内先行赋值:getSessionId 自身抛错时 catch 里拿到的是上一 session
|
|
442
480
|
// 的 id(留痕仍可检索)。
|
|
443
481
|
try {
|
|
444
482
|
lsRef.lastSessionId = ctx.sessionManager.getSessionId();
|
|
445
483
|
// [u7a D5] 初始上报(count=当下绝对计数):触发时点 = extension 加载完成 / session
|
|
446
|
-
// 就绪(factory 无 ctx/ui,session_start 是最早带 ctx
|
|
447
|
-
//
|
|
484
|
+
// 就绪(factory 无 ctx/ui,session_start 是最早带 ctx 的钩子)。fire-and-forget
|
|
485
|
+
// 在 await 装配链之前发起,不阻塞也不被阻塞。
|
|
448
486
|
inflightReporter.attachSession(ctx);
|
|
487
|
+
// [event-push-channel W-P1] journal 推送出口 attach(事件驱动,无初始帧——
|
|
488
|
+
// 报告语义是增量事件,缺席语义归消费方冷读)。
|
|
489
|
+
journalReporter.attachSession(ctx);
|
|
449
490
|
// [skill-reload D4] adoption 入参接线:reason 是 handler 独占信息(event 参数),
|
|
450
491
|
// existing 是 sessionState(domainState 闭包)里的既有条目——两者都是
|
|
451
492
|
// session-lifecycle seam 的 adoption 分流判据,经 SessionStartOptions 传入。
|
|
@@ -546,6 +587,9 @@ export function setupWorkflowDomain(
|
|
|
546
587
|
getSubagentService()?.invalidatePiBinding("session replacement (reload)");
|
|
547
588
|
// b 动作保留(理由见上方 D1 分支说明)。
|
|
548
589
|
inflightReporter.detachSession();
|
|
590
|
+
// [event-push-channel W-P1] journal 推送通道随 reload 同步摘 ctx(旧 ctx 的
|
|
591
|
+
// session 已被 pi 替换,推送帧无法归属;新 session_start 重新 attach)。
|
|
592
|
+
journalReporter.detachSession();
|
|
549
593
|
|
|
550
594
|
// [skill-reload D8] reload 分支归因日志:preserved 计数取自跳过清理时的存活
|
|
551
595
|
// 对象真实统计(runs = 各 session 内存 run 聚合根数;records = run.state.calls
|
|
@@ -582,6 +626,9 @@ export function setupWorkflowDomain(
|
|
|
582
626
|
// [u7a D5] 在途上报通道随 session 终结:摘 ctx + 停重试(session 已死,重试直至
|
|
583
627
|
// 成功的语义只对活 session 成立;进程级出口监听保留——后续 /new 重新 attach)。
|
|
584
628
|
inflightReporter.detachSession();
|
|
629
|
+
// [event-push-channel W-P1] journal 推送通道随 session 终结:摘 ctx + 丢弃待推
|
|
630
|
+
// 缓冲(未推事件由消费方冷读/缺口补读收敛,不重推——设计 D5)。
|
|
631
|
+
journalReporter.detachSession();
|
|
585
632
|
|
|
586
633
|
// ── workflow 域:terminate 所有 running run + store 收尾 + 清理 temp files ──
|
|
587
634
|
// H-5: 遍历所有 sessionState 条目清理(而不只 lastSessionId——
|
|
@@ -677,6 +724,20 @@ export function setupWorkflowDomain(
|
|
|
677
724
|
// "sar-unattached" → armed 回执落账键错 → fold 出 created 态 → IllegalTransitionError
|
|
678
725
|
// 让位,run journal 恒缺 armed 帧(真机 wf-1790034646281-w7tp4f 实证链,R2 裁决)。
|
|
679
726
|
get workflowAgentDispatch() { return createLazy(resolveDeps, "workflowAgentDispatch"); },
|
|
727
|
+
// [dmg-r2-5] 宿主会话锚必须随 lazyDeps 转发(workflowAgentDispatch 同款先例):
|
|
728
|
+
// workflow tool / subagents tool 的 run action 以 lazyDeps 为 deps 启动 run,漏本
|
|
729
|
+
// 成员则 run-created 帧不落 rootSessionId(可选成员静默放行)——同 cwd 多会话下
|
|
730
|
+
// runtime 网关 runId 路由恒回落目录存在性弱锚,精确归属形同虚设。
|
|
731
|
+
get getSessionRootId() { return createLazy(resolveDeps, "getSessionRootId"); },
|
|
732
|
+
// [dmg-r1-1] resume 显式 model 落账回调必须随 lazyDeps 转发(getSessionRootId
|
|
733
|
+
// 同款先例,同族第三例):三个生产入口(registerWorkflowTool / registerSubagentsTool /
|
|
734
|
+
// registerWorkflowsCommand)都以 lazyDeps 为 deps 启动 run,resume with model 的
|
|
735
|
+
// 落账点(resume-run appendResumeModelOverride)落完 journal B 帧后经本回调同步
|
|
736
|
+
// 宿主投影——漏本成员则可选链静默短路(可选成员静默放行):覆盖内存表 /
|
|
737
|
+
// 负缓存 stale 不失效,重派被旧投影遮蔽(旧 model 命中或覆盖丢失按 run-created.model
|
|
738
|
+
// 解析),pi 仍 notify「resuming with model B」成功文案。转发清单对账测试防第四处
|
|
739
|
+
// 漏转发(workflow-events.test.ts lazyDeps 转发清单对账 describe)。
|
|
740
|
+
get onResumeModelOverrideCommitted() { return createLazy(resolveDeps, "onResumeModelOverrideCommitted"); },
|
|
680
741
|
// scheduleTimeBudget / appendEntry 不可缺席(ports.ts D-12 regression fix 同族):
|
|
681
742
|
// rebuildRuntime 重排 run 级墙钟预算计时器、finalizeRun 的 pending:unregister
|
|
682
743
|
// 直落都经这两个成员消费——lazyDeps 缺席会让消费点拿到 undefined(可选属性
|