@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
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
* - dispose 清除 widget + 清 timer
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import type { ExtensionMode } from "./host-mode.ts";
|
|
20
|
+
import { resolveHostMode } from "./host-mode.ts";
|
|
21
|
+
import { isRelayActive } from "./relay-env.ts";
|
|
22
|
+
|
|
19
23
|
/** UI streaming sink 的最小接口(ctx.ui.setWidget 的 duck-typed 子集)。
|
|
20
24
|
*
|
|
21
25
|
* 当前只有一个 adapter(index.ts session_start 包装 ctx.ui.setWidget)。
|
|
@@ -98,3 +102,25 @@ export class SubagentStream {
|
|
|
98
102
|
]);
|
|
99
103
|
}
|
|
100
104
|
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* background stream 创建策略(widget 私货通道退役步骤 2,E 方案 §6.3)。
|
|
108
|
+
*
|
|
109
|
+
* GUI(mode=rpc)+ relay 激活时不创建——drawer 实时改由 runtime tee 供数
|
|
110
|
+
* (session.subagentEntriesAppended 帧),extension 停发 setWidget 私货,主 pi stdout
|
|
111
|
+
* 卸载。TUI(widget 行是 TUI 用户的实时预览)与 relay 未激活环境(独立 pi / 无
|
|
112
|
+
* xyz-agent runtime)原样创建——universal 包自足性不绑架。
|
|
113
|
+
*
|
|
114
|
+
* env/mode 显式入参而非内部读 process.env:调用方语义(本 record 的宿主形态)一次
|
|
115
|
+
* 确定,测试可注入,函数保持纯判定。
|
|
116
|
+
*/
|
|
117
|
+
export function createBackgroundStream(
|
|
118
|
+
recordId: string,
|
|
119
|
+
sink: StreamSink | null,
|
|
120
|
+
mode: ExtensionMode | undefined,
|
|
121
|
+
env: NodeJS.ProcessEnv | Record<string, string | undefined>,
|
|
122
|
+
): SubagentStream | undefined {
|
|
123
|
+
if (sink === null) return undefined;
|
|
124
|
+
if (isRelayActive(env) && resolveHostMode(mode) === "gui") return undefined;
|
|
125
|
+
return new SubagentStream(recordId, sink);
|
|
126
|
+
}
|
|
@@ -29,6 +29,15 @@ import {
|
|
|
29
29
|
tryTransition,
|
|
30
30
|
} from "./execution-record.ts";
|
|
31
31
|
import { doFinalizeRecord, doFinalizeRoundToIdle } from "./finalize-record.ts";
|
|
32
|
+
import { getEngineDataDir } from "./engine/common/data-dir.ts";
|
|
33
|
+
import { EngineError } from "./engine/common/errors.ts";
|
|
34
|
+
import { JournalWriter } from "./engine/common/event-journal.ts";
|
|
35
|
+
import { resolveJournalPath } from "./engine/paths.ts";
|
|
36
|
+
import { executeOptionsToEngineTaskSpec } from "./engine/host-task-spec.ts";
|
|
37
|
+
import type { EnginePort, RunContext } from "./engine/port.ts";
|
|
38
|
+
import { DEFAULT_ENGINE_ID, getEngine } from "./engine/registry.ts";
|
|
39
|
+
import { type EngineRouteResult, resolveEngineRouting, routeEngine } from "./engine/routing.ts";
|
|
40
|
+
import type { AgentOutcome } from "./engine/types.ts";
|
|
32
41
|
import { ManifestStore } from "./manifest-store.ts";
|
|
33
42
|
import type { ModelConfigService } from "./model-config-service.ts";
|
|
34
43
|
import type { AgentConfig, ModelInfo, ResolvedModel } from "./model-resolver.ts";
|
|
@@ -38,7 +47,7 @@ import { getSubagentRecordsDir, getSubagentSessionDir } from "./path-encoding.ts
|
|
|
38
47
|
import type { StatusFilter } from "./record-store.ts";
|
|
39
48
|
import { RecordStore } from "./record-store.ts";
|
|
40
49
|
import { MAX_FORK_DEPTH } from "./session-context-resolver.ts";
|
|
41
|
-
import { getChildByRecord, killAllSpawnedChildren, runSpawn, spawnedChildren, type SessionRunnerContext, type SpawnResumeOpts } from "./session-runner.ts";
|
|
50
|
+
import { getChildByRecord, killAllSpawnedChildren, registerSpawnedChildForRecord, runSpawn, spawnedChildren, type SessionRunnerContext, type SpawnResumeOpts } from "./session-runner.ts";
|
|
42
51
|
import { isIdle, isResumable, hasLiveProcessHandle } from "./lifecycle-predicates.ts";
|
|
43
52
|
import { startIdleGc } from "./idle-gc.ts";
|
|
44
53
|
import {
|
|
@@ -48,8 +57,8 @@ import {
|
|
|
48
57
|
resetAllEpipeFailures,
|
|
49
58
|
sendPromptCommand,
|
|
50
59
|
} from "./stdin-writer.ts";
|
|
51
|
-
import type { StreamSink } from "./stream-sink.ts";
|
|
52
|
-
import {
|
|
60
|
+
import type { StreamSink, SubagentStream } from "./stream-sink.ts";
|
|
61
|
+
import { createBackgroundStream } from "./stream-sink.ts";
|
|
53
62
|
import { writeCancelledTombstone } from "./tombstone-store.ts";
|
|
54
63
|
import type { WorktreeHandle } from "./types.ts";
|
|
55
64
|
import type {
|
|
@@ -102,7 +111,7 @@ interface PiLike {
|
|
|
102
111
|
events: { emit(channel: string, data: unknown): void };
|
|
103
112
|
sendMessage(
|
|
104
113
|
message: { customType: string; content: string; display: boolean; details?: unknown },
|
|
105
|
-
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
|
114
|
+
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }, // g4-allow: 类型注解——PiLike 接口形状(pi.sendMessage 签面子集),非投递调用
|
|
106
115
|
): void;
|
|
107
116
|
/** 订阅 pi 事件(D8:notifier 的 settled 边沿订阅用 'agent_settled')。
|
|
108
117
|
* pi 0.84.1 的 on 返回 void 且无 off——退订语义由调用侧 disposed 标志包装兑现。
|
|
@@ -684,8 +693,47 @@ export class SubagentService {
|
|
|
684
693
|
// ── 1. IDENTITY 解析(确认 → agentConfig → resolveModel)──
|
|
685
694
|
const identity = await this.resolveIdentity(opts);
|
|
686
695
|
|
|
696
|
+
// ── 1.5 引擎路由(D4 chat 入口分叉;U2 升级为 routeEngine 编排)──
|
|
697
|
+
// 三层解析(调用参数 > agent frontmatter > config.json defaultEngine)仍是同步纯
|
|
698
|
+
// 函数;解析为非 pi 时升级走 routeEngine(probe 编排 + fallback 三守卫)。时机
|
|
699
|
+
// 选择:路由(含 probe)在 record 创建前完成——兜底时 record 直接按 pi 语义创建 +
|
|
700
|
+
// engineFallback 留痕(D5 字节级守护只约束「无 fallback 的纯缺省路径」,兜底路径
|
|
701
|
+
// 的 entry 允许含 engine/engineFallback 字段);守卫命中/strict 时 routeEngine 在此
|
|
702
|
+
// throw,不产生孤儿 record。
|
|
703
|
+
const routingInput = {
|
|
704
|
+
callEngine: opts.engine,
|
|
705
|
+
agentEngine: identity.agentConfig?.engine,
|
|
706
|
+
globalDefaultEngine: this.modelService.getGlobalConfig().defaultEngine,
|
|
707
|
+
};
|
|
708
|
+
const routing = resolveEngineRouting(routingInput);
|
|
709
|
+
let route: EngineRouteResult | undefined;
|
|
710
|
+
if (routing.engineId !== DEFAULT_ENGINE_ID) {
|
|
711
|
+
route = await routeEngine({
|
|
712
|
+
routing: routingInput,
|
|
713
|
+
// 守卫 c 判据只看调用方显式指定的 model(resolved model 含 ctxModel 兼底,
|
|
714
|
+
// 恒非空会把一切兜底误判为 model 绑定命中)
|
|
715
|
+
taskModel: opts.model,
|
|
716
|
+
strict: this.modelService.getGlobalConfig().engineRouting?.strict === true,
|
|
717
|
+
probe: (engineId) => getEngine(engineId).probe(),
|
|
718
|
+
});
|
|
719
|
+
if (route.engineId !== DEFAULT_ENGINE_ID) {
|
|
720
|
+
return this.executeViaEngine(opts, identity, route);
|
|
721
|
+
}
|
|
722
|
+
// 兜底成功(典型:默认路由 + probe 失败 + 无守卫命中)→ 落回下方 pi 主路径,
|
|
723
|
+
// record 创建时按 pi 语义 + engine/engineFallback 留痕(engine = 实际执行引擎)
|
|
724
|
+
}
|
|
725
|
+
// D5 字节级守护:无 fallback 的 pi 路由剥掉 opts.engine——createRecordForMode
|
|
726
|
+
// 不盖章(pi record entry 序列化产物不得新增 engine 键,undefined 经 JSON 省略)。
|
|
727
|
+
// 兜底路径显式盖 engine='pi' + engineFallback(见上方时机注释)。
|
|
728
|
+
const piOpts =
|
|
729
|
+
route?.engineFallback !== undefined
|
|
730
|
+
? { ...opts, engine: DEFAULT_ENGINE_ID, engineFallback: route.engineFallback }
|
|
731
|
+
: opts.engine === undefined
|
|
732
|
+
? opts
|
|
733
|
+
: { ...opts, engine: undefined };
|
|
734
|
+
|
|
687
735
|
// ── 2. RECORD 创建 + 注册 ──
|
|
688
|
-
const record = this.createRecordForMode(identity,
|
|
736
|
+
const record = this.createRecordForMode(identity, piOpts, mode);
|
|
689
737
|
emitPendingRegister(this.pi, record.id, record.agent);
|
|
690
738
|
|
|
691
739
|
// ── 2.5 worktree 创建(仅 worktree===true 或已传入 handle 时)──
|
|
@@ -724,7 +772,7 @@ export class SubagentService {
|
|
|
724
772
|
// ── 4-7. background 包 detached 立即返回 id ──
|
|
725
773
|
// background detached 运行对 tool 层不可见,完成由 notify 驱动新 turn。
|
|
726
774
|
const bgDetails = project(record);
|
|
727
|
-
this.kickOffBackground(record, { ...
|
|
775
|
+
this.kickOffBackground(record, { ...piOpts, worktree: worktreeHandle }, ctx, identity, signal, priority);
|
|
728
776
|
return { mode: "background", subagentId: record.id, sessionFile: record.sessionFile, details: bgDetails };
|
|
729
777
|
}
|
|
730
778
|
|
|
@@ -919,6 +967,20 @@ export class SubagentService {
|
|
|
919
967
|
sendPromptCommand(child, text, { streamingBehavior: interrupt ? "steer" : "followUp" });
|
|
920
968
|
// 热路径成功,清零 EPIPE 连续失败计数([v4 A-1] 计数器已迁移到 stdin-writer)
|
|
921
969
|
clearEpipeFailure(record.id);
|
|
970
|
+
// [race-F5] 写后死进程检测:write 同步成功只代表数据进了内核 pipe 缓冲,子进程
|
|
971
|
+
// 可能在读取前死亡(gate/idle kill 竞速)。exitCode/signalCode 已非 null = 进程已死
|
|
972
|
+
//(close 事件可能尚未到达),缓冲中的消息将被静默丢弃。只 warn 留证(含 runId 与
|
|
973
|
+
// 消息类型),不抛错不重试:终态回收已由 kill 路径保证,对死进程重试反而可能二次写。
|
|
974
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
975
|
+
logger.warn(
|
|
976
|
+
`[subagents] deliverMessage: child ${record.id} died around stdin write, message may be lost`,
|
|
977
|
+
{
|
|
978
|
+
msgType: interrupt ? "steer" : "followUp",
|
|
979
|
+
exitCode: child.exitCode,
|
|
980
|
+
signalCode: child.signalCode,
|
|
981
|
+
},
|
|
982
|
+
);
|
|
983
|
+
}
|
|
922
984
|
// 轮始执行态信号清除 + 迁移上报(residual-fixes U3 补全,与冷路径 resumeRound
|
|
923
985
|
// 对称):新一轮开跑 = 无轮终信号——清上一轮 result(§5.4 isStreaming 公式要求
|
|
924
986
|
// result undefined 才显示 streaming)与 resumable,appendEntry 让 runtime/W18
|
|
@@ -1344,7 +1406,15 @@ export class SubagentService {
|
|
|
1344
1406
|
// 直接用 override → 主 agent model。DEFAULT_AGENT_NAME 仅作 record 显示名
|
|
1345
1407
|
// (TUI 层 extractAgentName 共用,保证显示一致)。
|
|
1346
1408
|
const agent = opts.agent ?? DEFAULT_AGENT_NAME;
|
|
1347
|
-
|
|
1409
|
+
// 显式 agent ref(用户点名)失败必须报错,不静默降级:无 require 的 loadByPath
|
|
1410
|
+
// 对相对路径/裸名/文件缺失都返回 undefined → agentConfig undefined → resolveModel
|
|
1411
|
+
// 静默回落 override→主 agent model,用户拿到的 subagent 无 systemPrompt/工具白名单
|
|
1412
|
+
// 且零反馈。require:true 让失败抛出带 <available_subagents> 指引的错误(对齐
|
|
1413
|
+
// workflow name not found 反馈风格);不传 agent = 默认 general-purpose 语义,
|
|
1414
|
+
// agentConfig 保持 undefined(合法缺省,走 override → ctxModel 兑底)。
|
|
1415
|
+
const agentConfig = opts.agent
|
|
1416
|
+
? this.modelService.getRequiredAgentConfig(opts.agent)
|
|
1417
|
+
: undefined;
|
|
1348
1418
|
|
|
1349
1419
|
const resolved = this.modelService.resolveModel(
|
|
1350
1420
|
opts.agent ?? "",
|
|
@@ -1389,6 +1459,10 @@ export class SubagentService {
|
|
|
1389
1459
|
depth,
|
|
1390
1460
|
chatMode: opts.conversation === true,
|
|
1391
1461
|
idleTimeoutMs: opts.idleTimeoutMs,
|
|
1462
|
+
// P4 引擎留痕(D9①):opts.engine/engineFallback 由引擎适配层写入(PiEngine.run
|
|
1463
|
+
// 从 RunContext 回填;缺省 = pi 投影,存量调用方零感知)
|
|
1464
|
+
engine: opts.engine,
|
|
1465
|
+
engineFallback: opts.engineFallback,
|
|
1392
1466
|
controller,
|
|
1393
1467
|
});
|
|
1394
1468
|
|
|
@@ -1403,6 +1477,194 @@ export class SubagentService {
|
|
|
1403
1477
|
return { mode: "background", subagentId: record.id, sessionFile: record.sessionFile, details };
|
|
1404
1478
|
}
|
|
1405
1479
|
|
|
1480
|
+
// ── 引擎分支(D4/D10:非 pi 引擎的 chat 域执行骨架,U0)──────────
|
|
1481
|
+
|
|
1482
|
+
/**
|
|
1483
|
+
* 路由到非 pi 引擎的执行入口:routeEngine(注册表校验 + probe/守卫)已由 execute
|
|
1484
|
+
* 完成——这里只剩 unsupported 预检 → record 创建+盖章 → detached 引擎 run。
|
|
1485
|
+
* 全部同步拒绝发生在 record 创建前(不产生孤儿 record)。
|
|
1486
|
+
*/
|
|
1487
|
+
private executeViaEngine(
|
|
1488
|
+
opts: ExecuteOptions,
|
|
1489
|
+
identity: ResolvedIdentity,
|
|
1490
|
+
route: EngineRouteResult,
|
|
1491
|
+
): ExecutionHandle {
|
|
1492
|
+
const engine = route.engine;
|
|
1493
|
+
this.assertEngineParamSupport(engine, opts);
|
|
1494
|
+
// record 盖章路由结果(D5 仅 pi 缺省不盖章;非 pi 显式留痕,createRecordForMode
|
|
1495
|
+
// 经 opts.engine/engineFallback 读入 record identity——engine 为实际执行引擎,
|
|
1496
|
+
// fallback 路径 from=请求引擎留痕,probe 通过的常态路径恒缺省)
|
|
1497
|
+
const record = this.createRecordForMode(
|
|
1498
|
+
identity,
|
|
1499
|
+
{
|
|
1500
|
+
...opts,
|
|
1501
|
+
engine: route.engineId,
|
|
1502
|
+
...(route.engineFallback !== undefined ? { engineFallback: route.engineFallback } : {}),
|
|
1503
|
+
},
|
|
1504
|
+
"background",
|
|
1505
|
+
);
|
|
1506
|
+
emitPendingRegister(this.pi, record.id, record.agent);
|
|
1507
|
+
this.kickOffEngineRun(record, opts, engine);
|
|
1508
|
+
return { mode: "background", subagentId: record.id, sessionFile: record.sessionFile, details: project(record) };
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/**
|
|
1512
|
+
* 非 pi 引擎的 unsupported 参数预检(D11 处置「调用前拒绝」的判据 = capabilities)。
|
|
1513
|
+
* conversation / fork / worktree 三参数对首期接入的引擎(zcode)均不可用:
|
|
1514
|
+
* conversation 依赖同进程 idle 复用、fork 依赖父 pi session 上下文继承、worktree 依赖
|
|
1515
|
+
* 文件隔离(capabilities.sandbox='none')。同步 throw,文案含 capabilities 依据与恢复指引。
|
|
1516
|
+
*/
|
|
1517
|
+
private assertEngineParamSupport(engine: EnginePort, opts: ExecuteOptions): void {
|
|
1518
|
+
const caps = engine.capabilities();
|
|
1519
|
+
if (opts.conversation === true && caps.conversation === "unsupported") {
|
|
1520
|
+
throw new EngineError(
|
|
1521
|
+
"engine_capability_unsupported",
|
|
1522
|
+
`engine '${engine.id}' 不支持 conversation(capabilities.conversation = 'unsupported',` +
|
|
1523
|
+
`spawn 单轮模式无同进程 idle 复用,message/close 交互控制面不可用)`,
|
|
1524
|
+
`改用 engine: pi(支持 conversation 续聊),或不传该参数(一次性任务默认形态)`,
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
if (opts.fork === true) {
|
|
1528
|
+
throw new EngineError(
|
|
1529
|
+
"engine_capability_unsupported",
|
|
1530
|
+
`engine '${engine.id}' 不支持 fork(fork 依赖父 pi session 上下文继承,` +
|
|
1531
|
+
`capabilities.steer = '${caps.steer}'——非 pi 引擎无父 session 分叉通道)`,
|
|
1532
|
+
`把所需父上下文写进 task 正文后不传 fork,或改用 engine: pi`,
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
if ((opts.worktree === true || typeof opts.worktree === "object") && caps.sandbox === "none") {
|
|
1536
|
+
throw new EngineError(
|
|
1537
|
+
"engine_capability_unsupported",
|
|
1538
|
+
`engine '${engine.id}' 不支持 worktree 隔离(capabilities.sandbox = 'none',` +
|
|
1539
|
+
`引擎未接文件系统隔离层)`,
|
|
1540
|
+
`改用 engine: pi(worktree 隔离可用),或不传该参数(在 parent cwd 执行)`,
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
/**
|
|
1546
|
+
* 非 pi 引擎的 detached 执行编排(与 kickOffBackground 同构的 background 语义):
|
|
1547
|
+
* pool 并发槽(maxConcurrent 对非 pi 引擎同样生效)→ journal 接线(D6 第②级:
|
|
1548
|
+
* taskId=record.id,初始池 key 占位 'shared',onPoolResolved retarget 到引擎实际
|
|
1549
|
+
* 池 key——路径与 paths.ts 同源推导)→ engine.run(signal 接 record controller,
|
|
1550
|
+
* kill-chain 两级生效)→ engineHandle 回填(终态迁移落 entry 前)→ 终态迁移 →
|
|
1551
|
+
* bg notify(chat 域宿主职责,与 pi 完成通知同语义)。
|
|
1552
|
+
*/
|
|
1553
|
+
private kickOffEngineRun(record: ExecutionRecord, opts: ExecuteOptions, engine: EnginePort): void {
|
|
1554
|
+
const signal = record.controller?.signal;
|
|
1555
|
+
void (async () => {
|
|
1556
|
+
try {
|
|
1557
|
+
await this.pool.acquire(PRIORITY_BACKGROUND, this.effectiveMaxConcurrentFor(record), signal);
|
|
1558
|
+
} catch {
|
|
1559
|
+
// S1: 排队中被 abort(signal.aborted)走 cancelled,与已运行被 abort 一致(runAndFinalize 同款)
|
|
1560
|
+
if (signal?.aborted) {
|
|
1561
|
+
await this.finalizeAborted(record);
|
|
1562
|
+
} else {
|
|
1563
|
+
await this.finalizeFailed(record, new Error("aborted"));
|
|
1564
|
+
}
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
// [review MF1] acquire 成功后必须 finally release:不 release 则
|
|
1568
|
+
// DefaultConcurrencyPool._active 永不递减——每次引擎任务泄漏一个并发槽,累计
|
|
1569
|
+
// maxConcurrent 次后全部 background subagent(pi 与引擎共用同一池)在 acquire 队列永久挂起
|
|
1570
|
+
try {
|
|
1571
|
+
await this.runEngineTask(record, opts, engine, signal);
|
|
1572
|
+
// cancel 抢先(closedReason='cancelled')时 cancelBackground 自己 notify,跳过
|
|
1573
|
+
if (record.closedReason !== "cancelled") {
|
|
1574
|
+
this.notifyComplete(record);
|
|
1575
|
+
}
|
|
1576
|
+
} finally {
|
|
1577
|
+
this.pool.release();
|
|
1578
|
+
}
|
|
1579
|
+
})();
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* kickOffEngineRun 的 acquire 后主体:journal 接线(D6 第②级:taskId=record.id,
|
|
1584
|
+
* 初始池 key 占位 'shared',onPoolResolved retarget 到引擎实际池 key)→ engine.run
|
|
1585
|
+
* (signal 接 record controller,kill-chain 两级生效)→ engineHandle 回填(终态迁移
|
|
1586
|
+
* 落 entry 前)→ 终态迁移。bg notify 归编排侧(与 kickOffBackground 收尾通知归编排对称)。
|
|
1587
|
+
*/
|
|
1588
|
+
private async runEngineTask(
|
|
1589
|
+
record: ExecutionRecord,
|
|
1590
|
+
opts: ExecuteOptions,
|
|
1591
|
+
engine: EnginePort,
|
|
1592
|
+
signal: AbortSignal | undefined,
|
|
1593
|
+
): Promise<void> {
|
|
1594
|
+
const journal = new JournalWriter({
|
|
1595
|
+
path: resolveJournalPath(getEngineDataDir(), engine.id, "shared", record.id),
|
|
1596
|
+
taskId: record.id,
|
|
1597
|
+
engineId: engine.id,
|
|
1598
|
+
});
|
|
1599
|
+
const retargetJournal = (poolKey: string): void => {
|
|
1600
|
+
journal.retarget(resolveJournalPath(getEngineDataDir(), engine.id, poolKey, record.id));
|
|
1601
|
+
};
|
|
1602
|
+
// 对齐点③:journal 路径权威 = 引擎声明的池 key(writer 初始用占位,retarget 后
|
|
1603
|
+
// 与 handle.poolKey 同源)。模式对齐 SAR 的 journalingOnEvent:先落盘再转发。
|
|
1604
|
+
const runCtx: RunContext = {
|
|
1605
|
+
taskId: record.id,
|
|
1606
|
+
poolKey: "shared",
|
|
1607
|
+
signal,
|
|
1608
|
+
ctxModel: opts.ctxModel,
|
|
1609
|
+
onEvent: (event) => journal.append(event),
|
|
1610
|
+
onPoolResolved: retargetJournal,
|
|
1611
|
+
// D9①:路由层 fallback 留痕投影进 outcome(zcode 无独立 record 通路)
|
|
1612
|
+
...(record.engineFallback !== undefined ? { engineFallback: record.engineFallback } : {}),
|
|
1613
|
+
// D10 终止链:engine spawn 的子进程注册进 spawnedChildren 记账
|
|
1614
|
+
//(cancelBackground SIGTERM / dispose killAll 收割对非 pi record 生效)
|
|
1615
|
+
onChildSpawned: (child) => registerSpawnedChildForRecord(record.id, child),
|
|
1616
|
+
};
|
|
1617
|
+
try {
|
|
1618
|
+
const { handle, outcome } = await engine.run(executeOptionsToEngineTaskSpec(opts), runCtx);
|
|
1619
|
+
// engineHandle 完整回填(U2:终态迁移落 entry 前)。sessionRef 整体透传——
|
|
1620
|
+
// 失败终态 sessionId 缺失时也回填已有部分(dbPath/poolKey),读侧①级降②级
|
|
1621
|
+
// 的防御形态;journalPath 取 retarget 后的实际落盘路径(writer 是路径权威)。
|
|
1622
|
+
record.engineHandle = {
|
|
1623
|
+
sessionRef: handle.data.sessionRef,
|
|
1624
|
+
poolKey: handle.data.poolKey,
|
|
1625
|
+
journalPath: journal.path,
|
|
1626
|
+
};
|
|
1627
|
+
await journal.close();
|
|
1628
|
+
await this.finalizeEngineOutcome(record, outcome);
|
|
1629
|
+
} catch (err) {
|
|
1630
|
+
// engine.run prepare 期 reject(进程创建前)→ failed 终态(与 runAndFinalize catch 同语义);
|
|
1631
|
+
// journal 尽力而为收口(②级数据源写失败已由 writer 内部 warn 收敛)
|
|
1632
|
+
await journal.close();
|
|
1633
|
+
await this.finalizeFailed(record, err);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
/**
|
|
1638
|
+
* 分层并发配额:depth 越深可用配额越少(下限 1)。fork 深度护栏在池维度的投影,
|
|
1639
|
+
* 公式约定以 concurrency-pool.ts 注释为登记处、此处为唯一代码锚点。
|
|
1640
|
+
*/
|
|
1641
|
+
private effectiveMaxConcurrentFor(record: ExecutionRecord): number {
|
|
1642
|
+
return Math.max(1, this.pool.maxConcurrent - record.depth);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
/**
|
|
1646
|
+
* engine.run resolve 的终态迁移:outcome.error → failed(success=false + error 文案);
|
|
1647
|
+
* 否则 done(result=content)。CAS 抢锁(tryTransition)防与 cancelBackground 双收尾。
|
|
1648
|
+
*/
|
|
1649
|
+
private async finalizeEngineOutcome(record: ExecutionRecord, outcome: AgentOutcome): Promise<void> {
|
|
1650
|
+
if (outcome.sessionFile !== undefined) {
|
|
1651
|
+
record.sessionFile = outcome.sessionFile;
|
|
1652
|
+
}
|
|
1653
|
+
const result: AgentResult = {
|
|
1654
|
+
text: outcome.content,
|
|
1655
|
+
turns: outcome.usage?.turns ?? 0,
|
|
1656
|
+
durationMs: outcome.durationMs ?? Date.now() - record.startedAt,
|
|
1657
|
+
success: outcome.error === undefined,
|
|
1658
|
+
...(outcome.error !== undefined ? { error: outcome.error } : {}),
|
|
1659
|
+
sessionId: outcome.sessionId ?? record.id,
|
|
1660
|
+
toolCalls: [],
|
|
1661
|
+
...(outcome.parsedOutput !== undefined ? { parsedOutput: outcome.parsedOutput } : {}),
|
|
1662
|
+
};
|
|
1663
|
+
if (tryTransition(record, "closed", "gc")) {
|
|
1664
|
+
await this.finalizeRecord(record, result, "closed", "gc");
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1406
1668
|
// ── 执行内部:run + finalize(sync/bg 共用)──────────────
|
|
1407
1669
|
|
|
1408
1670
|
/** 共享的"干活 + 收尾"——sync 直接 await,background 在 detached 里调。 */
|
|
@@ -1421,9 +1683,8 @@ export class SubagentService {
|
|
|
1421
1683
|
const pooled = record.mode === "background";
|
|
1422
1684
|
let acquired = false;
|
|
1423
1685
|
if (pooled) {
|
|
1424
|
-
const effectiveMaxConcurrent = Math.max(1, this.pool.maxConcurrent - record.depth);
|
|
1425
1686
|
try {
|
|
1426
|
-
await this.pool.acquire(priority,
|
|
1687
|
+
await this.pool.acquire(priority, this.effectiveMaxConcurrentFor(record), signal);
|
|
1427
1688
|
acquired = true;
|
|
1428
1689
|
} catch {
|
|
1429
1690
|
// S1: 排队中被 abort(signal.aborted)走 cancelled,与已运行被 abort 一致。
|
|
@@ -1575,10 +1836,9 @@ export class SubagentService {
|
|
|
1575
1836
|
/** resume 选项(M2-B1):透传 runAndFinalize→runSpawn。undefined = 新 session。 */
|
|
1576
1837
|
resume?: SpawnResumeOpts,
|
|
1577
1838
|
): void {
|
|
1578
|
-
// 创建 streaming
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
: undefined;
|
|
1839
|
+
// 创建 streaming 生命周期对象。策略(含 widget 退役步骤 2:GUI + relay 激活时停发
|
|
1840
|
+
// 私货、TUI/未激活原样创建、sink 未注入降级 undefined)集中在 createBackgroundStream。
|
|
1841
|
+
const stream = createBackgroundStream(record.id, this.streamSink, ctx.mode, process.env);
|
|
1582
1842
|
|
|
1583
1843
|
void this.runAndFinalize(
|
|
1584
1844
|
record, opts, ctx, identity, signal, priority,
|