@zhushanwen/pi-subagent-workflow 8.3.0 → 8.5.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 +18 -4
- 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__/chat-engine-routing.test.ts +597 -0
- package/src/execution/__tests__/execution-record.test.ts +127 -1
- 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__/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 +53 -5
- package/src/execution/agent-registry.ts +10 -0
- 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 +567 -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 +648 -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 +304 -0
- package/src/execution/execute-options-mapper.ts +5 -1
- package/src/execution/execution-record.ts +6 -0
- package/src/execution/model-resolver.ts +6 -0
- 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 +24 -0
- package/src/execution/stream-sink.ts +26 -0
- package/src/execution/subagent-service.ts +249 -11
- package/src/execution/subprocess-agent-runner.ts +196 -14
- package/src/execution/types.ts +56 -0
- package/src/index.ts +46 -1
- package/src/interface/command-actions.ts +72 -8
- package/src/interface/subagent-actions.ts +8 -2
- package/src/interface/subagent-tool.ts +6 -0
- package/src/interface/subagents.ts +198 -30
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +5 -2
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +3 -3
- package/src/orchestration/models/types.ts +7 -0
- package/src/orchestration/worker-script-builder.ts +5 -2
- package/src/shared/meta-parser.ts +5 -1
- package/src/shared/resource-meta.ts +5 -0
|
@@ -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 {
|
|
@@ -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
|
|
|
@@ -1389,6 +1437,10 @@ export class SubagentService {
|
|
|
1389
1437
|
depth,
|
|
1390
1438
|
chatMode: opts.conversation === true,
|
|
1391
1439
|
idleTimeoutMs: opts.idleTimeoutMs,
|
|
1440
|
+
// P4 引擎留痕(D9①):opts.engine/engineFallback 由引擎适配层写入(PiEngine.run
|
|
1441
|
+
// 从 RunContext 回填;缺省 = pi 投影,存量调用方零感知)
|
|
1442
|
+
engine: opts.engine,
|
|
1443
|
+
engineFallback: opts.engineFallback,
|
|
1392
1444
|
controller,
|
|
1393
1445
|
});
|
|
1394
1446
|
|
|
@@ -1403,6 +1455,194 @@ export class SubagentService {
|
|
|
1403
1455
|
return { mode: "background", subagentId: record.id, sessionFile: record.sessionFile, details };
|
|
1404
1456
|
}
|
|
1405
1457
|
|
|
1458
|
+
// ── 引擎分支(D4/D10:非 pi 引擎的 chat 域执行骨架,U0)──────────
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* 路由到非 pi 引擎的执行入口:routeEngine(注册表校验 + probe/守卫)已由 execute
|
|
1462
|
+
* 完成——这里只剩 unsupported 预检 → record 创建+盖章 → detached 引擎 run。
|
|
1463
|
+
* 全部同步拒绝发生在 record 创建前(不产生孤儿 record)。
|
|
1464
|
+
*/
|
|
1465
|
+
private executeViaEngine(
|
|
1466
|
+
opts: ExecuteOptions,
|
|
1467
|
+
identity: ResolvedIdentity,
|
|
1468
|
+
route: EngineRouteResult,
|
|
1469
|
+
): ExecutionHandle {
|
|
1470
|
+
const engine = route.engine;
|
|
1471
|
+
this.assertEngineParamSupport(engine, opts);
|
|
1472
|
+
// record 盖章路由结果(D5 仅 pi 缺省不盖章;非 pi 显式留痕,createRecordForMode
|
|
1473
|
+
// 经 opts.engine/engineFallback 读入 record identity——engine 为实际执行引擎,
|
|
1474
|
+
// fallback 路径 from=请求引擎留痕,probe 通过的常态路径恒缺省)
|
|
1475
|
+
const record = this.createRecordForMode(
|
|
1476
|
+
identity,
|
|
1477
|
+
{
|
|
1478
|
+
...opts,
|
|
1479
|
+
engine: route.engineId,
|
|
1480
|
+
...(route.engineFallback !== undefined ? { engineFallback: route.engineFallback } : {}),
|
|
1481
|
+
},
|
|
1482
|
+
"background",
|
|
1483
|
+
);
|
|
1484
|
+
emitPendingRegister(this.pi, record.id, record.agent);
|
|
1485
|
+
this.kickOffEngineRun(record, opts, engine);
|
|
1486
|
+
return { mode: "background", subagentId: record.id, sessionFile: record.sessionFile, details: project(record) };
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* 非 pi 引擎的 unsupported 参数预检(D11 处置「调用前拒绝」的判据 = capabilities)。
|
|
1491
|
+
* conversation / fork / worktree 三参数对首期接入的引擎(zcode)均不可用:
|
|
1492
|
+
* conversation 依赖同进程 idle 复用、fork 依赖父 pi session 上下文继承、worktree 依赖
|
|
1493
|
+
* 文件隔离(capabilities.sandbox='none')。同步 throw,文案含 capabilities 依据与恢复指引。
|
|
1494
|
+
*/
|
|
1495
|
+
private assertEngineParamSupport(engine: EnginePort, opts: ExecuteOptions): void {
|
|
1496
|
+
const caps = engine.capabilities();
|
|
1497
|
+
if (opts.conversation === true && caps.conversation === "unsupported") {
|
|
1498
|
+
throw new EngineError(
|
|
1499
|
+
"engine_capability_unsupported",
|
|
1500
|
+
`engine '${engine.id}' 不支持 conversation(capabilities.conversation = 'unsupported',` +
|
|
1501
|
+
`spawn 单轮模式无同进程 idle 复用,message/close 交互控制面不可用)`,
|
|
1502
|
+
`改用 engine: pi(支持 conversation 续聊),或不传该参数(一次性任务默认形态)`,
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
if (opts.fork === true) {
|
|
1506
|
+
throw new EngineError(
|
|
1507
|
+
"engine_capability_unsupported",
|
|
1508
|
+
`engine '${engine.id}' 不支持 fork(fork 依赖父 pi session 上下文继承,` +
|
|
1509
|
+
`capabilities.steer = '${caps.steer}'——非 pi 引擎无父 session 分叉通道)`,
|
|
1510
|
+
`把所需父上下文写进 task 正文后不传 fork,或改用 engine: pi`,
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
if ((opts.worktree === true || typeof opts.worktree === "object") && caps.sandbox === "none") {
|
|
1514
|
+
throw new EngineError(
|
|
1515
|
+
"engine_capability_unsupported",
|
|
1516
|
+
`engine '${engine.id}' 不支持 worktree 隔离(capabilities.sandbox = 'none',` +
|
|
1517
|
+
`引擎未接文件系统隔离层)`,
|
|
1518
|
+
`改用 engine: pi(worktree 隔离可用),或不传该参数(在 parent cwd 执行)`,
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/**
|
|
1524
|
+
* 非 pi 引擎的 detached 执行编排(与 kickOffBackground 同构的 background 语义):
|
|
1525
|
+
* pool 并发槽(maxConcurrent 对非 pi 引擎同样生效)→ journal 接线(D6 第②级:
|
|
1526
|
+
* taskId=record.id,初始池 key 占位 'shared',onPoolResolved retarget 到引擎实际
|
|
1527
|
+
* 池 key——路径与 paths.ts 同源推导)→ engine.run(signal 接 record controller,
|
|
1528
|
+
* kill-chain 两级生效)→ engineHandle 回填(终态迁移落 entry 前)→ 终态迁移 →
|
|
1529
|
+
* bg notify(chat 域宿主职责,与 pi 完成通知同语义)。
|
|
1530
|
+
*/
|
|
1531
|
+
private kickOffEngineRun(record: ExecutionRecord, opts: ExecuteOptions, engine: EnginePort): void {
|
|
1532
|
+
const signal = record.controller?.signal;
|
|
1533
|
+
void (async () => {
|
|
1534
|
+
try {
|
|
1535
|
+
await this.pool.acquire(PRIORITY_BACKGROUND, this.effectiveMaxConcurrentFor(record), signal);
|
|
1536
|
+
} catch {
|
|
1537
|
+
// S1: 排队中被 abort(signal.aborted)走 cancelled,与已运行被 abort 一致(runAndFinalize 同款)
|
|
1538
|
+
if (signal?.aborted) {
|
|
1539
|
+
await this.finalizeAborted(record);
|
|
1540
|
+
} else {
|
|
1541
|
+
await this.finalizeFailed(record, new Error("aborted"));
|
|
1542
|
+
}
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
// [review MF1] acquire 成功后必须 finally release:不 release 则
|
|
1546
|
+
// DefaultConcurrencyPool._active 永不递减——每次引擎任务泄漏一个并发槽,累计
|
|
1547
|
+
// maxConcurrent 次后全部 background subagent(pi 与引擎共用同一池)在 acquire 队列永久挂起
|
|
1548
|
+
try {
|
|
1549
|
+
await this.runEngineTask(record, opts, engine, signal);
|
|
1550
|
+
// cancel 抢先(closedReason='cancelled')时 cancelBackground 自己 notify,跳过
|
|
1551
|
+
if (record.closedReason !== "cancelled") {
|
|
1552
|
+
this.notifyComplete(record);
|
|
1553
|
+
}
|
|
1554
|
+
} finally {
|
|
1555
|
+
this.pool.release();
|
|
1556
|
+
}
|
|
1557
|
+
})();
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
/**
|
|
1561
|
+
* kickOffEngineRun 的 acquire 后主体:journal 接线(D6 第②级:taskId=record.id,
|
|
1562
|
+
* 初始池 key 占位 'shared',onPoolResolved retarget 到引擎实际池 key)→ engine.run
|
|
1563
|
+
* (signal 接 record controller,kill-chain 两级生效)→ engineHandle 回填(终态迁移
|
|
1564
|
+
* 落 entry 前)→ 终态迁移。bg notify 归编排侧(与 kickOffBackground 收尾通知归编排对称)。
|
|
1565
|
+
*/
|
|
1566
|
+
private async runEngineTask(
|
|
1567
|
+
record: ExecutionRecord,
|
|
1568
|
+
opts: ExecuteOptions,
|
|
1569
|
+
engine: EnginePort,
|
|
1570
|
+
signal: AbortSignal | undefined,
|
|
1571
|
+
): Promise<void> {
|
|
1572
|
+
const journal = new JournalWriter({
|
|
1573
|
+
path: resolveJournalPath(getEngineDataDir(), engine.id, "shared", record.id),
|
|
1574
|
+
taskId: record.id,
|
|
1575
|
+
engineId: engine.id,
|
|
1576
|
+
});
|
|
1577
|
+
const retargetJournal = (poolKey: string): void => {
|
|
1578
|
+
journal.retarget(resolveJournalPath(getEngineDataDir(), engine.id, poolKey, record.id));
|
|
1579
|
+
};
|
|
1580
|
+
// 对齐点③:journal 路径权威 = 引擎声明的池 key(writer 初始用占位,retarget 后
|
|
1581
|
+
// 与 handle.poolKey 同源)。模式对齐 SAR 的 journalingOnEvent:先落盘再转发。
|
|
1582
|
+
const runCtx: RunContext = {
|
|
1583
|
+
taskId: record.id,
|
|
1584
|
+
poolKey: "shared",
|
|
1585
|
+
signal,
|
|
1586
|
+
ctxModel: opts.ctxModel,
|
|
1587
|
+
onEvent: (event) => journal.append(event),
|
|
1588
|
+
onPoolResolved: retargetJournal,
|
|
1589
|
+
// D9①:路由层 fallback 留痕投影进 outcome(zcode 无独立 record 通路)
|
|
1590
|
+
...(record.engineFallback !== undefined ? { engineFallback: record.engineFallback } : {}),
|
|
1591
|
+
// D10 终止链:engine spawn 的子进程注册进 spawnedChildren 记账
|
|
1592
|
+
//(cancelBackground SIGTERM / dispose killAll 收割对非 pi record 生效)
|
|
1593
|
+
onChildSpawned: (child) => registerSpawnedChildForRecord(record.id, child),
|
|
1594
|
+
};
|
|
1595
|
+
try {
|
|
1596
|
+
const { handle, outcome } = await engine.run(executeOptionsToEngineTaskSpec(opts), runCtx);
|
|
1597
|
+
// engineHandle 完整回填(U2:终态迁移落 entry 前)。sessionRef 整体透传——
|
|
1598
|
+
// 失败终态 sessionId 缺失时也回填已有部分(dbPath/poolKey),读侧①级降②级
|
|
1599
|
+
// 的防御形态;journalPath 取 retarget 后的实际落盘路径(writer 是路径权威)。
|
|
1600
|
+
record.engineHandle = {
|
|
1601
|
+
sessionRef: handle.data.sessionRef,
|
|
1602
|
+
poolKey: handle.data.poolKey,
|
|
1603
|
+
journalPath: journal.path,
|
|
1604
|
+
};
|
|
1605
|
+
await journal.close();
|
|
1606
|
+
await this.finalizeEngineOutcome(record, outcome);
|
|
1607
|
+
} catch (err) {
|
|
1608
|
+
// engine.run prepare 期 reject(进程创建前)→ failed 终态(与 runAndFinalize catch 同语义);
|
|
1609
|
+
// journal 尽力而为收口(②级数据源写失败已由 writer 内部 warn 收敛)
|
|
1610
|
+
await journal.close();
|
|
1611
|
+
await this.finalizeFailed(record, err);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
/**
|
|
1616
|
+
* 分层并发配额:depth 越深可用配额越少(下限 1)。fork 深度护栏在池维度的投影,
|
|
1617
|
+
* 公式约定以 concurrency-pool.ts 注释为登记处、此处为唯一代码锚点。
|
|
1618
|
+
*/
|
|
1619
|
+
private effectiveMaxConcurrentFor(record: ExecutionRecord): number {
|
|
1620
|
+
return Math.max(1, this.pool.maxConcurrent - record.depth);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
/**
|
|
1624
|
+
* engine.run resolve 的终态迁移:outcome.error → failed(success=false + error 文案);
|
|
1625
|
+
* 否则 done(result=content)。CAS 抢锁(tryTransition)防与 cancelBackground 双收尾。
|
|
1626
|
+
*/
|
|
1627
|
+
private async finalizeEngineOutcome(record: ExecutionRecord, outcome: AgentOutcome): Promise<void> {
|
|
1628
|
+
if (outcome.sessionFile !== undefined) {
|
|
1629
|
+
record.sessionFile = outcome.sessionFile;
|
|
1630
|
+
}
|
|
1631
|
+
const result: AgentResult = {
|
|
1632
|
+
text: outcome.content,
|
|
1633
|
+
turns: outcome.usage?.turns ?? 0,
|
|
1634
|
+
durationMs: outcome.durationMs ?? Date.now() - record.startedAt,
|
|
1635
|
+
success: outcome.error === undefined,
|
|
1636
|
+
...(outcome.error !== undefined ? { error: outcome.error } : {}),
|
|
1637
|
+
sessionId: outcome.sessionId ?? record.id,
|
|
1638
|
+
toolCalls: [],
|
|
1639
|
+
...(outcome.parsedOutput !== undefined ? { parsedOutput: outcome.parsedOutput } : {}),
|
|
1640
|
+
};
|
|
1641
|
+
if (tryTransition(record, "closed", "gc")) {
|
|
1642
|
+
await this.finalizeRecord(record, result, "closed", "gc");
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1406
1646
|
// ── 执行内部:run + finalize(sync/bg 共用)──────────────
|
|
1407
1647
|
|
|
1408
1648
|
/** 共享的"干活 + 收尾"——sync 直接 await,background 在 detached 里调。 */
|
|
@@ -1421,9 +1661,8 @@ export class SubagentService {
|
|
|
1421
1661
|
const pooled = record.mode === "background";
|
|
1422
1662
|
let acquired = false;
|
|
1423
1663
|
if (pooled) {
|
|
1424
|
-
const effectiveMaxConcurrent = Math.max(1, this.pool.maxConcurrent - record.depth);
|
|
1425
1664
|
try {
|
|
1426
|
-
await this.pool.acquire(priority,
|
|
1665
|
+
await this.pool.acquire(priority, this.effectiveMaxConcurrentFor(record), signal);
|
|
1427
1666
|
acquired = true;
|
|
1428
1667
|
} catch {
|
|
1429
1668
|
// S1: 排队中被 abort(signal.aborted)走 cancelled,与已运行被 abort 一致。
|
|
@@ -1575,10 +1814,9 @@ export class SubagentService {
|
|
|
1575
1814
|
/** resume 选项(M2-B1):透传 runAndFinalize→runSpawn。undefined = 新 session。 */
|
|
1576
1815
|
resume?: SpawnResumeOpts,
|
|
1577
1816
|
): void {
|
|
1578
|
-
// 创建 streaming
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
: undefined;
|
|
1817
|
+
// 创建 streaming 生命周期对象。策略(含 widget 退役步骤 2:GUI + relay 激活时停发
|
|
1818
|
+
// 私货、TUI/未激活原样创建、sink 未注入降级 undefined)集中在 createBackgroundStream。
|
|
1819
|
+
const stream = createBackgroundStream(record.id, this.streamSink, ctx.mode, process.env);
|
|
1582
1820
|
|
|
1583
1821
|
void this.runAndFinalize(
|
|
1584
1822
|
record, opts, ctx, identity, signal, priority,
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
// 接线层级:
|
|
9
9
|
// [跨模块 port] implements AgentRunner(orchestration/models/ports.ts)
|
|
10
10
|
// [模块内直调] mapToExecuteOptions + mergeTimeoutSignal(execute-options-mapper)
|
|
11
|
-
// [
|
|
11
|
+
// [P4 路由层] engine/routing.ts routeEngine(三层优先级 + probe + fallback 三守卫,D9)
|
|
12
|
+
// [引擎层] EnginePort.run(pi = 本地 DI 绑定实例;非 pi = registry.getEngine 动态获取)
|
|
13
|
+
// [模块内直调] this.subagentService.executeAndAwait(PiEngine 内部委托目标)
|
|
12
14
|
//
|
|
13
15
|
// 设计基线:
|
|
14
16
|
// D-A2(映射归 adapter)/ D-A8(onEvent 桥接)/ D-A9(timeoutMs 合并 signal)/
|
|
@@ -17,8 +19,19 @@
|
|
|
17
19
|
import type { AgentRunner } from "../orchestration/models/ports.ts";
|
|
18
20
|
import type { AgentCallOpts, AgentResult } from "../orchestration/models/types.ts";
|
|
19
21
|
import type { AgentEvent } from "../shared/agent-event.ts";
|
|
22
|
+
import { getEngineDataDir } from "./engine/common/data-dir.ts";
|
|
23
|
+
import { JournalWriter } from "./engine/common/event-journal.ts";
|
|
24
|
+
import { createPiEngine, PI_POOL_KEY } from "./engine/engines/pi/registration.ts";
|
|
25
|
+
import { executeOptionsToTaskSpec } from "./engine/engines/pi/task-spec-mapper.ts";
|
|
26
|
+
import { resolveJournalPath } from "./engine/paths.ts";
|
|
27
|
+
import type { EnginePort, RunContext } from "./engine/port.ts";
|
|
28
|
+
import { routeEngine, resolveEngineRouting, type EngineRouteResult, type EngineRoutingInput } from "./engine/routing.ts";
|
|
29
|
+
import { getEngine, hasEngine, listEngines } from "./engine/registry.ts";
|
|
30
|
+
import type { AgentOutcome, EngineHandle } from "./engine/types.ts";
|
|
20
31
|
import { mapToExecuteOptions, mergeTimeoutSignal } from "./execute-options-mapper.ts";
|
|
32
|
+
import { getModelConfigService } from "./model-config-service.ts";
|
|
21
33
|
import type { ModelInfo } from "./model-resolver.ts";
|
|
34
|
+
import { registerSpawnedChildForRecord } from "./session-runner.ts";
|
|
22
35
|
import type { SubagentStream } from "./stream-sink.ts";
|
|
23
36
|
import type { SubagentService } from "./subagent-service.ts";
|
|
24
37
|
import type { ExecuteOptions } from "./types.ts";
|
|
@@ -51,14 +64,27 @@ export interface SubprocessAgentRunnerDeps {
|
|
|
51
64
|
* - result 形状不变(workflow AgentResult: content/parsedOutput/usage/error/toolCalls)
|
|
52
65
|
* - 不 reject——失败信息入 result.error(与 executeAgentCall 契约一致)
|
|
53
66
|
* - timeoutMs 合并 signal(D-A9);onEvent 桥接 AgentEvent→workflow liveRecord(D-A8)
|
|
67
|
+
*
|
|
68
|
+
* [P1 引擎接线] 执行经 EnginePort。pi 引擎绑定本 SAR 的服务引用(per-session DI——
|
|
69
|
+
* 单测注入 mock 时全局单例不可见;生产环境两者是同一进程单例对象),行为零变化;
|
|
70
|
+
* 非 pi 引擎(P4 路由可达:frontmatter/调用参数/全局默认指定)经 registry.getEngine
|
|
71
|
+
* 动态获取——「引擎身份」的归属边界在注册表,SAR 不感知具体引擎实现。
|
|
72
|
+
*
|
|
73
|
+
* [P4 配置路由] 每次运行经 engine/routing.ts 解析三层优先级(调用参数 opts.engine >
|
|
74
|
+
* agent frontmatter engine > 全局默认 'pi')+ probe + fallback 三守卫(D9)。路由失败
|
|
75
|
+
* (engine_not_found / engine_probe_failed / model_not_available)不 reject,入
|
|
76
|
+
* result.error——与 executeAgentCall 契约一致。
|
|
54
77
|
*/
|
|
55
78
|
export class SubprocessAgentRunner implements AgentRunner {
|
|
56
79
|
private readonly subagentService: SubagentService;
|
|
57
80
|
private ctxModel: ModelInfo | undefined;
|
|
81
|
+
/** pi 引擎(per-session DI 绑定,缺省执行路径——见类注释)。 */
|
|
82
|
+
private readonly piEngine: EnginePort;
|
|
58
83
|
|
|
59
84
|
constructor(deps: SubprocessAgentRunnerDeps) {
|
|
60
85
|
this.subagentService = deps.subagentService;
|
|
61
86
|
this.ctxModel = deps.ctxModel;
|
|
87
|
+
this.piEngine = createPiEngine(() => this.subagentService);
|
|
62
88
|
}
|
|
63
89
|
|
|
64
90
|
/**
|
|
@@ -72,13 +98,16 @@ export class SubprocessAgentRunner implements AgentRunner {
|
|
|
72
98
|
}
|
|
73
99
|
|
|
74
100
|
/**
|
|
75
|
-
* 执行单次 agent
|
|
101
|
+
* 执行单次 agent 调用:路由(P4)→ EnginePort.run → PiEngine 委托
|
|
102
|
+
* SubagentService.executeAndAwait。
|
|
76
103
|
*
|
|
77
104
|
* 接线链路:
|
|
78
|
-
* mergeTimeoutSignal → mapToExecuteOptions →
|
|
79
|
-
*
|
|
105
|
+
* routeEngine(三层 + probe + 守卫)→ mergeTimeoutSignal → mapToExecuteOptions →
|
|
106
|
+
* AgentTaskSpec → engine.run(PiEngine:spec → ExecuteOptions 还原 + engine 留痕)→
|
|
107
|
+
* executeAndAwait → AgentOutcome → AgentResult
|
|
80
108
|
*
|
|
81
109
|
* 错误处理:不 reject。
|
|
110
|
+
* - 路由失败(未注册 id / probe 失败 + 守卫或 strict)→ AgentResult.error(错误码前缀)
|
|
82
111
|
* - executeAndAwait 内部失败 → 返回 AgentResult(success:false) → 已映射 error 字段
|
|
83
112
|
* - executeAndAwait throw(嵌套超限 BC-12)→ catch → AgentResult.error
|
|
84
113
|
* - spawn 级失败已由 runSpawn 内部收口为 failed AgentResult(不逃逸)
|
|
@@ -91,22 +120,105 @@ export class SubprocessAgentRunner implements AgentRunner {
|
|
|
91
120
|
): Promise<AgentResult> {
|
|
92
121
|
const startedAt = Date.now();
|
|
93
122
|
|
|
123
|
+
// ── P4 路由:三层优先级 + probe + fallback 三守卫(D9①/D7)──
|
|
124
|
+
// 路由在最前——引擎身份决定 journal 路径与后续一切执行面;失败(未注册 id /
|
|
125
|
+
// probe 失败 + strict/守卫)按「不 reject」契约转 result.error。
|
|
126
|
+
// pi 快路径同步短路(不经 routeEngine 的 await):pi 恒免探、无 fallback 可言、
|
|
127
|
+
// engineFor('pi') 本地 DI 绑定恒可用——不引入微任务边界,缺省路径时序与 P1 接线
|
|
128
|
+
// 前完全一致(下游依赖「run 内首个 await 前已触达 executeAndAwait」的时序契约)。
|
|
129
|
+
let routingInput: EngineRoutingInput;
|
|
130
|
+
let route: EngineRouteResult;
|
|
131
|
+
try {
|
|
132
|
+
routingInput = this.buildRoutingInput(opts);
|
|
133
|
+
const routing = resolveEngineRouting(routingInput);
|
|
134
|
+
if (routing.engineId === "pi") {
|
|
135
|
+
route = {
|
|
136
|
+
engine: this.piEngine,
|
|
137
|
+
engineId: "pi",
|
|
138
|
+
requestedEngineId: "pi",
|
|
139
|
+
source: routing.source,
|
|
140
|
+
};
|
|
141
|
+
} else {
|
|
142
|
+
route = await routeEngine({
|
|
143
|
+
routing: routingInput,
|
|
144
|
+
taskModel: opts.model,
|
|
145
|
+
strict: getModelConfigService()?.getGlobalConfig().engineRouting?.strict === true,
|
|
146
|
+
probe: (engineId) => this.engineFor(engineId).probe(),
|
|
147
|
+
getEngineFn: (engineId) => this.engineFor(engineId),
|
|
148
|
+
// pi 经本地 DI 绑定恒可用(不依赖 registry 全局注册态——SAR 持有服务引用),
|
|
149
|
+
// has/list 注入同一口径,engine_not_found 文案不会把本地 pi 漏报成未注册
|
|
150
|
+
hasEngineFn: (engineId) => engineId === "pi" || hasEngine(engineId),
|
|
151
|
+
listEnginesFn: () => (hasEngine("pi") ? listEngines() : ["pi", ...listEngines()]),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
// buildRoutingInput 的 agent 解析期校验(未注册 frontmatter engine)与路由失败
|
|
156
|
+
// (probe + strict/守卫)一并在此收口——「不 reject」契约
|
|
157
|
+
return errorResult(err, startedAt);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── P2 event journal 接线(设计 D6 第②级;对齐点③:路径权威 = 引擎池 key)──
|
|
161
|
+
// host 在 onEvent 回调内统一落盘(全引擎免费获得②级数据源)。初始 poolKey 用 pi
|
|
162
|
+
// 缺省占位(pi 恒 'shared');非池化稳定引擎(zcode)在 prepare 期经
|
|
163
|
+
// RunContext.onPoolResolved 声明实际池 key → writer.retarget——保证 journal 落盘
|
|
164
|
+
// 路径与 handle.poolKey 同源(handle.journalPath 由本方法 run 返回后回填)。
|
|
165
|
+
//
|
|
166
|
+
// taskId 为宿主侧任务标识(journal 文件名与池引用计数 key)——executeAndAwait
|
|
167
|
+
// 不外露内部 record id(取真实 id 需 hook record store 且改 createRecordForMode
|
|
168
|
+
// 签名,影响面大;P4 决策:保留占位,`sa-` 前缀与 record id 同构。影响面:record
|
|
169
|
+
// GC 时无法按 taskId 联动删 journal(journal 依赖 30 天 TTL 自然回收),read ②级
|
|
170
|
+
// 经 handle.journalPath 自描述定位不受影响)。
|
|
171
|
+
const taskId = `sa-${crypto.randomUUID()}`;
|
|
172
|
+
const journal = new JournalWriter({
|
|
173
|
+
path: resolveJournalPath(getEngineDataDir(), route.engineId, PI_POOL_KEY, taskId),
|
|
174
|
+
taskId,
|
|
175
|
+
engineId: route.engineId,
|
|
176
|
+
});
|
|
177
|
+
const retargetJournal = (poolKey: string): void => {
|
|
178
|
+
journal.retarget(resolveJournalPath(getEngineDataDir(), route.engineId, poolKey, taskId));
|
|
179
|
+
};
|
|
180
|
+
// 包装:先写 journal 再转发原 onEvent(原 onEvent 未传时也恒传包装版——
|
|
181
|
+
// 下游 onEvent 通道是事件生成后的纯转发,无行为分支,仅多一次入队)
|
|
182
|
+
const journalingOnEvent = (event: AgentEvent): void => {
|
|
183
|
+
journal.append(event);
|
|
184
|
+
onEvent?.(event);
|
|
185
|
+
};
|
|
186
|
+
|
|
94
187
|
try {
|
|
95
|
-
// ── D-A9: timeoutMs 合并 signal
|
|
188
|
+
// ── D-A9: timeoutMs 合并 signal(超时 abort 带 HOST_TIMEOUT_ABORT_REASON 标记)──
|
|
96
189
|
const mergedSignal = mergeTimeoutSignal(signal, opts.timeoutMs);
|
|
97
190
|
|
|
98
191
|
// ── D-A2 + D-008: AgentCallOpts → ExecuteOptions 映射 ──
|
|
99
192
|
const mappedOpts: ExecuteOptions = mapToExecuteOptions(opts, this.ctxModel);
|
|
100
193
|
|
|
101
|
-
// ──
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
194
|
+
// ── P1/P4 引擎接线:EnginePort.run ──
|
|
195
|
+
const runCtx: RunContext = {
|
|
196
|
+
taskId,
|
|
197
|
+
poolKey: PI_POOL_KEY,
|
|
198
|
+
signal: mergedSignal,
|
|
199
|
+
onEvent: journalingOnEvent,
|
|
200
|
+
ctxModel: this.ctxModel,
|
|
201
|
+
...(route.engineFallback !== undefined ? { engineFallback: route.engineFallback } : {}),
|
|
202
|
+
onPoolResolved: retargetJournal,
|
|
203
|
+
// [U0 D10] 终止链路径①:引擎 spawn 的子进程注册进 session-runner 的
|
|
204
|
+
// spawnedChildren 记账(dispose killAll 收割兜底对 workflow 域引擎任务生效);
|
|
205
|
+
// taskId('sa-' 前缀)即记账 key,与 chat 域 kickOffEngineRun 的 record.id 同构
|
|
206
|
+
onChildSpawned: (child) => registerSpawnedChildForRecord(taskId, child),
|
|
207
|
+
...(stream !== undefined ? { stream } : {}),
|
|
208
|
+
// 解耦形态(有 schemaEnv 无 schema)的兜底通道——耦合形态下引擎从 task.schema
|
|
209
|
+
// 派生等值,此值被忽略(见 RunContext.schemaEnv 注释)
|
|
210
|
+
...(mappedOpts.schemaEnv !== undefined ? { schemaEnv: mappedOpts.schemaEnv } : {}),
|
|
211
|
+
};
|
|
212
|
+
const { handle, outcome } = await route.engine.run(
|
|
213
|
+
// 泛化为中立声明(PiEngine 内部再还原回 ExecuteOptions——往返保真,
|
|
214
|
+
// 由 engines/pi/__tests__/task-spec-mapper.test.ts 逐字段锁定)
|
|
215
|
+
executeOptionsToTaskSpec(mappedOpts),
|
|
216
|
+
runCtx,
|
|
217
|
+
);
|
|
218
|
+
// handle.journalPath 回填(§3.3.6:read ②级的自描述定位符——运行期落盘路径
|
|
219
|
+
// 权威在 writer,handle 记录最终路径供跨重启 read 消费)
|
|
220
|
+
backfillJournalPath(handle, journal.path);
|
|
221
|
+
return outcomeToRunnerResult(outcome);
|
|
110
222
|
} catch (err) {
|
|
111
223
|
// executeAndAwait throw(嵌套超限 ForkDepthExceededError,BC-12)或未预期异常 → 不 reject,入 error。
|
|
112
224
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -116,6 +228,76 @@ export class SubprocessAgentRunner implements AgentRunner {
|
|
|
116
228
|
error: message,
|
|
117
229
|
toolCalls: [],
|
|
118
230
|
};
|
|
231
|
+
} finally {
|
|
232
|
+
// run 终态后 flush + fsync 一次(§3.3.6 写入纪律);写失败已由 writer 内部
|
|
233
|
+
// warn + failed 收口,close 不抛(journal 是②级尽力而为数据源)
|
|
234
|
+
await journal.close();
|
|
119
235
|
}
|
|
120
236
|
}
|
|
237
|
+
|
|
238
|
+
// ── 内部 ──
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 引擎获取:pi 走 per-session DI 绑定(mock 语义 + 生产同单例,P1 行为零变化);
|
|
242
|
+
* 非 pi 经 registry.getEngine 动态获取(P4:引擎身份归属注册表,未注册 id 抛
|
|
243
|
+
* EngineNotFoundError——路由期已前置校验,这里是防御性兜底)。
|
|
244
|
+
*/
|
|
245
|
+
private engineFor(engineId: string): EnginePort {
|
|
246
|
+
if (engineId === "pi") return this.piEngine;
|
|
247
|
+
return getEngine(engineId);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* 三层路由输入装配(D9):调用参数(opts.engine,workflow step 级透传)> agent
|
|
252
|
+
* frontmatter(ModelConfigService.getAgentConfig——loadByPath mtime 缓存,幂等)>
|
|
253
|
+
* 全局默认(config.json defaultEngine)。单例未就绪(session_start 前/测试 mock)
|
|
254
|
+
* 时各层缺省——落内置 'pi'。
|
|
255
|
+
*/
|
|
256
|
+
private buildRoutingInput(opts: AgentCallOpts): EngineRoutingInput {
|
|
257
|
+
const service = getModelConfigService();
|
|
258
|
+
const agentEngine =
|
|
259
|
+
opts.agent !== undefined && opts.agent !== "" ? service?.getAgentConfig(opts.agent)?.engine : undefined;
|
|
260
|
+
const globalDefault = service?.getGlobalConfig().defaultEngine;
|
|
261
|
+
return {
|
|
262
|
+
...(opts.engine !== undefined && opts.engine !== "" ? { callEngine: opts.engine } : {}),
|
|
263
|
+
...(agentEngine !== undefined && agentEngine !== "" ? { agentEngine } : {}),
|
|
264
|
+
...(globalDefault !== undefined && globalDefault !== "" ? { globalDefaultEngine: globalDefault } : {}),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** 路由期错误 → AgentResult.error(错误码前缀格式保留——engine_not_found 等)。 */
|
|
270
|
+
function errorResult(err: unknown, startedAt: number): AgentResult {
|
|
271
|
+
return {
|
|
272
|
+
content: "",
|
|
273
|
+
durationMs: Date.now() - startedAt,
|
|
274
|
+
error: err instanceof Error ? err.message : String(err),
|
|
275
|
+
toolCalls: [],
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** handle.journalPath 回填(一次写者:SAR 是 handle 的首个消费者)。 */
|
|
280
|
+
function backfillJournalPath(handle: EngineHandle, journalPath: string): void {
|
|
281
|
+
handle.data.journalPath = journalPath;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* AgentOutcome → workflow AgentResult:剥离引擎层新增字段(engineId/engineFallback/
|
|
286
|
+
* exitCode——engineFallback 经 record(pi 路径)/outcome(zcode 路径)留痕,GUI 投影
|
|
287
|
+
* 通道在后续 wave 接线,workflow 引擎不消费)。其余字段由 PiEngine 从 executeAndAwait
|
|
288
|
+
* 的返回值逐字段映射而来,字段全集完整性由 pi-engine 单测锁定(缺字段会在该处转红,
|
|
289
|
+
* 不会静默丢失)。
|
|
290
|
+
*/
|
|
291
|
+
function outcomeToRunnerResult(outcome: AgentOutcome): AgentResult {
|
|
292
|
+
return {
|
|
293
|
+
content: outcome.content,
|
|
294
|
+
parsedOutput: outcome.parsedOutput,
|
|
295
|
+
usage: outcome.usage,
|
|
296
|
+
durationMs: outcome.durationMs,
|
|
297
|
+
error: outcome.error,
|
|
298
|
+
sessionId: outcome.sessionId,
|
|
299
|
+
sessionFile: outcome.sessionFile,
|
|
300
|
+
worktreePath: outcome.worktreePath,
|
|
301
|
+
toolCalls: outcome.toolCalls,
|
|
302
|
+
};
|
|
121
303
|
}
|