@zhushanwen/subagent-core 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/chunk-KXVBIVOU.js +16 -0
- package/dist/chunk-T2SYBW3I.js +24 -0
- package/dist/chunk-VURUAGMM.js +2305 -0
- package/dist/{types-C3fE3R9x.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +770 -668
- package/dist/{types-C3fE3R9x.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +770 -668
- package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
- package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
- package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
- package/dist/execution/engine/engine-discovery-scan.js +20 -0
- package/dist/execution/engine/paths.cjs +6 -23
- package/dist/execution/engine/paths.d.cts +1 -8
- package/dist/execution/engine/paths.d.ts +1 -8
- package/dist/execution/engine/paths.js +1 -1
- package/dist/execution/relay-env.cjs +12 -28
- package/dist/execution/relay-env.d.cts +1 -33
- package/dist/execution/relay-env.d.ts +1 -33
- package/dist/execution/relay-env.js +5 -3
- package/dist/index.cjs +14045 -16059
- package/dist/index.d.cts +1035 -1579
- package/dist/index.d.ts +1035 -1579
- package/dist/index.js +9838 -13800
- package/dist.bundle/index.cjs +28875 -30323
- package/package.json +20 -26
- package/src/__tests__/record-store-last-line.test.ts +16 -7
- package/src/core/error-message.ts +7 -6
- package/src/core/host-services.ts +8 -2
- package/src/core/notify-ports.ts +17 -2
- package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
- package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
- package/src/execution/__tests__/collect-coordinator-service.test.ts +36 -47
- package/src/execution/__tests__/collect-mixed-dispatch.test.ts +20 -53
- package/src/execution/__tests__/delivery-methods.test.ts +179 -237
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
- package/src/execution/__tests__/execute-nesting.test.ts +20 -13
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +20 -33
- package/src/execution/__tests__/gc-timer.test.ts +7 -22
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
- package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
- package/src/execution/__tests__/idle-gc.test.ts +200 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
- package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
- package/src/execution/__tests__/record-store.test.ts +60 -6
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
- package/src/execution/__tests__/round-supervisor.test.ts +293 -0
- package/src/execution/__tests__/session-pending.test.ts +119 -17
- package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +1 -11
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
- package/src/execution/__tests__/subagent-service.test.ts +2 -2
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
- package/src/execution/__tests__/sync-collect-recovery.test.ts +21 -36
- package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
- package/src/execution/agent-registry.ts +6 -2
- package/src/execution/dialog-queue.ts +6 -73
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
- package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
- package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
- package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +81 -54
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
- package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
- package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +7 -2
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
- package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
- package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
- package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
- package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
- package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
- package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
- package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
- package/src/execution/engine/__tests__/registry.test.ts +206 -1
- package/src/execution/engine/__tests__/routing.test.ts +97 -0
- package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
- package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
- package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
- package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
- package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
- package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
- package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
- package/src/execution/engine/client/client-options.ts +103 -0
- package/src/execution/engine/client/engine-client.ts +687 -0
- package/src/execution/engine/client/mirror.ts +167 -0
- package/src/execution/engine/client/pid-file.ts +392 -0
- package/src/execution/engine/client/reaper.ts +95 -0
- package/src/execution/engine/client/remote-engine.ts +441 -0
- package/src/execution/engine/client/reverse-router.ts +255 -0
- package/src/execution/engine/common/capability-gate.ts +109 -22
- package/src/execution/engine/common/errors.ts +9 -25
- package/src/execution/engine/common/event-journal.ts +4 -50
- package/src/execution/engine/common/journal-replay.ts +3 -1
- package/src/execution/engine/common/kill-chain.ts +18 -251
- package/src/execution/engine/common/nesting-guard.ts +18 -114
- package/src/execution/engine/common/pool-manager.ts +4 -0
- package/src/execution/engine/common/session-view-service.ts +56 -84
- package/src/execution/engine/config.ts +151 -0
- package/src/execution/engine/d8-compat.ts +346 -0
- package/src/execution/engine/engine-discovery-roots.ts +107 -0
- package/src/execution/engine/engine-discovery-scan.ts +269 -0
- package/src/execution/engine/engine-discovery.ts +30 -7
- package/src/execution/engine/engine-inspect-package.ts +282 -0
- package/src/execution/engine/engine-manifest.ts +244 -0
- package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
- package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
- package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
- package/src/execution/engine/host/host-bridge.ts +150 -0
- package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
- package/src/execution/engine/host/pi-host-binding.ts +91 -0
- package/src/execution/engine/host/spawned-children.ts +139 -0
- package/src/execution/engine/host-task-spec.ts +29 -2
- package/src/execution/engine/model-validation.ts +55 -2
- package/src/execution/engine/paths.ts +16 -41
- package/src/execution/engine/port.ts +48 -2
- package/src/execution/engine/registry.ts +153 -34
- package/src/execution/engine/routing.ts +225 -52
- package/src/execution/engine/types.ts +25 -200
- package/src/execution/finalize-record.ts +25 -0
- package/src/execution/idle-gc.ts +89 -14
- package/src/execution/lifecycle-manager.ts +10 -7
- package/src/execution/lifecycle-predicates.ts +10 -8
- package/src/execution/record-store.ts +29 -2
- package/src/execution/relay-env.ts +19 -36
- package/src/execution/round-supervisor/domain.ts +89 -0
- package/src/execution/round-supervisor/index.ts +33 -0
- package/src/execution/round-supervisor/notify-accounting.ts +87 -0
- package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
- package/src/execution/round-supervisor/service-binding.test.ts +661 -0
- package/src/execution/round-supervisor/service-binding.ts +273 -0
- package/src/execution/round-supervisor/supervisor.ts +426 -0
- package/src/execution/session-pending.ts +68 -74
- package/src/execution/settled-watchdog.ts +39 -0
- package/src/execution/subagent-service.ts +746 -400
- package/src/execution/subprocess-agent-runner.ts +16 -8
- package/src/execution/types.ts +41 -117
- package/src/execution/ui-channels.ts +13 -214
- package/src/execution/ui-request-handler-factory.ts +7 -4
- package/src/execution/ui-request-observability.ts +2 -2
- package/src/execution/workflow-state-root.ts +67 -0
- package/src/execution/worktree-git-ops.ts +5 -0
- package/src/execution/worktree-manager.ts +11 -1
- package/src/index.ts +55 -26
- package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
- package/src/orchestration/execute-agent-call.ts +1 -1
- package/src/orchestration/file-run-store.ts +144 -59
- package/src/orchestration/models/types.ts +43 -42
- package/src/shared/zcode-model-ref.ts +31 -0
- package/dist/chunk-43ONBFZX.js +0 -240
- package/dist/chunk-A4IVZWVX.js +0 -31
- package/dist/chunk-APZY4IME.js +0 -27
- package/dist/chunk-V3VHZ2VX.js +0 -59
- package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
- package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
- package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
- package/dist/execution/engine/engines/zcode/constants.js +0 -54
- package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
- package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
- package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
- package/dist/execution/engine/engines/zcode/reader.js +0 -9
- package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
- package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
- package/src/__tests__/session-runner.test.ts +0 -69
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
- package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
- package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
- package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
- package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
- package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
- package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
- package/src/execution/__tests__/output-collector.test.ts +0 -469
- package/src/execution/__tests__/pi-invocation.test.ts +0 -166
- package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
- package/src/execution/__tests__/rpc-mode.test.ts +0 -89
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
- package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
- package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
- package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
- package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
- package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
- package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
- package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
- package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
- package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
- package/src/execution/__tests__/spawn-args.test.ts +0 -446
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
- package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
- package/src/execution/__tests__/spawned-children.test.ts +0 -236
- package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
- package/src/execution/__tests__/stdin-writer.test.ts +0 -464
- package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
- package/src/execution/__tests__/temp-prompt.test.ts +0 -53
- package/src/execution/__tests__/timeout-integration.test.ts +0 -616
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
- package/src/execution/__tests__/turn-limiter.test.ts +0 -65
- package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
- package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
- package/src/execution/engine/__tests__/paths.test.ts +0 -39
- package/src/execution/engine/common/schema-emulation.ts +0 -189
- package/src/execution/engine/common/session-view-projection.ts +0 -51
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
- package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
- package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
- package/src/execution/engine/engines/pi/output-collector.ts +0 -317
- package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
- package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
- package/src/execution/engine/engines/pi/reader.ts +0 -59
- package/src/execution/engine/engines/pi/registration.ts +0 -37
- package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
- package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
- package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
- package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
- package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
- package/src/execution/engine/engines/zcode/connection.ts +0 -598
- package/src/execution/engine/engines/zcode/constants.ts +0 -176
- package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
- package/src/execution/engine/engines/zcode/parser.ts +0 -102
- package/src/execution/engine/engines/zcode/preparer.ts +0 -235
- package/src/execution/engine/engines/zcode/reader.ts +0 -381
- package/src/execution/engine/engines/zcode/registration.ts +0 -37
- package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
- package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
- package/src/execution/round-settlement.ts +0 -93
- package/src/execution/ui-request-queue.ts +0 -215
- package/src/shared/schema-env.ts +0 -44
|
@@ -1,811 +0,0 @@
|
|
|
1
|
-
// src/execution/engine/engines/pi/pi-engine.ts
|
|
2
|
-
//
|
|
3
|
-
// PiEngine:pi 执行链的引擎边界(dual-track-convergence D2 后形态)。设计权威源:
|
|
4
|
-
// docs/architecture/subagent-engine-abstraction.md §3.3.1 / D1(四面 + handle 契约 +
|
|
5
|
-
// abort 分级)、D3(capabilities 链路接通口径)、docs/design/subagent-dual-track-convergence.md
|
|
6
|
-
// §3.3 D2(pi 执行轨物理下沉 + Service 旧轨收敛)+ D6(任务形状合流:run 入参 =
|
|
7
|
-
// AgentCallOpts 单一形状,本文件内一次直出为 spawn 编排参数,无中间态还原)。
|
|
8
|
-
//
|
|
9
|
-
// 四件套物理归属 engines/pi/(launcher=session-runner / parser=spawn-event-adapter /
|
|
10
|
-
// preparer=temp-prompt+argv-mirror / reader=reader.ts),本文件是引擎边界:
|
|
11
|
-
// - run:chat 域轮次(ChatRoundTicket 交接——编排层预建 record/identity 后经
|
|
12
|
-
// EnginePort 进入)与 workflow 域(executeAndAwait 委托)两分支;
|
|
13
|
-
// - interact:原生实现(D2——原编排层 deliverMessage/resumeRound 的 pi RPC stdin
|
|
14
|
-
// 协议知识下沉:sendPromptCommand 直调 + streamingBehavior 映射 + EPIPE 兜底 +
|
|
15
|
-
// 冷路径 resume 交接),编排层经 engine.interact 调用;
|
|
16
|
-
// - read:第①级委托共享 reader readPiSessionView(engines/pi/reader.ts)。
|
|
17
|
-
//
|
|
18
|
-
// pi 专有语义的隔离点在 agentCallToExecuteOptions(本文件的直出映射:prompt→task、
|
|
19
|
-
// description→slug 截断、schemaEnv 派生)——本文件不出现第二个 pi 语义翻译点。
|
|
20
|
-
|
|
21
|
-
import { execFile } from "node:child_process";
|
|
22
|
-
import * as fs from "node:fs";
|
|
23
|
-
import * as path from "node:path";
|
|
24
|
-
|
|
25
|
-
import { getLogger } from "../../../../core/logger.ts";
|
|
26
|
-
|
|
27
|
-
import type { AgentCallOpts, AgentResult as WorkflowAgentResult } from "../../../../orchestration/models/types.ts";
|
|
28
|
-
import { SLUG_MAX_LENGTH } from "../../../../orchestration/models/types.ts";
|
|
29
|
-
import { stringifySchemaCached } from "../../../../shared/schema-jsonify.ts";
|
|
30
|
-
import type { PiInvocation } from "./pi-invocation.ts";
|
|
31
|
-
import { getPiInvocation } from "./pi-invocation.ts";
|
|
32
|
-
import type { AgentConfig, ModelInfo, ResolvedModel } from "../../../model-resolver.ts";
|
|
33
|
-
import type { StatusFilter } from "../../../record-store.ts";
|
|
34
|
-
import type { SubagentStream } from "../../../stream-sink.ts";
|
|
35
|
-
import type { AgentEvent, AgentResult, ExecutionRecord, ExecuteOptions, SubagentRecord } from "../../../types.ts";
|
|
36
|
-
import type { EnginePort, EngineRunResult, RunContext } from "../../port.ts";
|
|
37
|
-
import { replayJournalToSessionView } from "../../common/journal-replay.ts";
|
|
38
|
-
import type {
|
|
39
|
-
AgentOutcome,
|
|
40
|
-
EngineCapabilities,
|
|
41
|
-
EngineHandle,
|
|
42
|
-
InteractAction,
|
|
43
|
-
InteractResult,
|
|
44
|
-
ProbeReport,
|
|
45
|
-
SessionView,
|
|
46
|
-
} from "../../types.ts";
|
|
47
|
-
import { readPiSessionView } from "./reader.ts";
|
|
48
|
-
import type { SessionRunnerContext, SpawnResumeOpts } from "./session-runner.ts";
|
|
49
|
-
import { getChildByRecord, killRecordChildWithEscalation, spawnedChildren } from "./session-runner.ts";
|
|
50
|
-
import {
|
|
51
|
-
acquireActivateLock,
|
|
52
|
-
armIdleTimer,
|
|
53
|
-
DEFAULT_IDLE_TIMEOUT_MS,
|
|
54
|
-
disarmIdleTimer,
|
|
55
|
-
} from "../../../lifecycle-manager.ts";
|
|
56
|
-
import { bestEffort } from "../../../best-effort.ts";
|
|
57
|
-
import {
|
|
58
|
-
clearEpipeFailure,
|
|
59
|
-
EPIPE_FAILURE_THRESHOLD,
|
|
60
|
-
recordEpipeFailure,
|
|
61
|
-
sendPromptCommand,
|
|
62
|
-
} from "./stdin-writer.ts";
|
|
63
|
-
import { toErrorMessage } from "../../../../core/error-message.ts";
|
|
64
|
-
|
|
65
|
-
const logger = getLogger("subagents");
|
|
66
|
-
|
|
67
|
-
/** pi 引擎的 registry key(D9:缺省引擎 = 'pi',回填期零风险默认)。 */
|
|
68
|
-
export const PI_ENGINE_ID = "pi";
|
|
69
|
-
|
|
70
|
-
/** pi 适配器版本(handle.adapterVersion 数据源——golden 样本对齐排查锚点)。 */
|
|
71
|
-
export const PI_ADAPTER_VERSION = "1.0.0";
|
|
72
|
-
|
|
73
|
-
/** pi 无隔离池(PI_CODING_AGENT_DIR 全局一份,设计 §3.3.9),poolKey 恒 'shared'。 */
|
|
74
|
-
export const PI_POOL_KEY = "shared";
|
|
75
|
-
|
|
76
|
-
/** probe 的版本探测超时(ms)。二进制无响应时按探针失败处理,不静默挂死(目标 4)。 */
|
|
77
|
-
const PROBE_VERSION_TIMEOUT_MS = 10_000;
|
|
78
|
-
|
|
79
|
-
/** interact 经 sessionFile 兜底定位 record 时的 collectRecords 扫描上限。 */
|
|
80
|
-
const INTERACT_SCAN_LIMIT = 1000;
|
|
81
|
-
|
|
82
|
-
/** chat 域轮次的身份快照(编排层 resolveIdentity 的产物形态——引擎侧透传给 launcher;
|
|
83
|
-
* 身份解析产物是 host 编排件,不并入任务声明 AgentCallOpts,见 ChatRoundTicket 注释)。 */
|
|
84
|
-
export interface ChatRoundIdentity {
|
|
85
|
-
agent: string;
|
|
86
|
-
agentConfig: AgentConfig | undefined;
|
|
87
|
-
resolved: ResolvedModel;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* chat 域预备轮次交接包(D2 单轨):编排层(executeViaEngine / 冷路径续轮)预建的
|
|
92
|
-
* record/opts/identity/host 上下文经 run 的 ctx.taskId 交接给引擎。
|
|
93
|
-
*
|
|
94
|
-
* [u-3b D6 双形态消化评估结论:双形态保留(结构性裁决,非过渡态)]
|
|
95
|
-
* run 的 task 形参(AgentCallOpts,任务声明)与 ticket 是两个变化轴的载体,不可合一:
|
|
96
|
-
* - task 形参 = 纯数据任务声明(跨引擎持久化语义,workflow 域由 SAR 直传);
|
|
97
|
-
* - ticket = chat 域 host 编排交接件,主体是运行期对象与 Service 内部形态——
|
|
98
|
-
* ① ctx: SessionRunnerContext 回调簇 / signal / priority / stream 是运行期句柄
|
|
99
|
-
* (port 设计 §3.3.5 删字段去向:运行期对象不入任务声明);
|
|
100
|
-
* ② record: ExecutionRecord 是 Service 内部 record 生命周期对象;
|
|
101
|
-
* ③ resume: SpawnResumeOpts 是 session-runner 私有 spawn 选项;
|
|
102
|
-
* ④ opts: ExecuteOptions 是 Service 内部编排形状(D6 明确保留的类型),且是
|
|
103
|
-
* forkFromSessionFile 的唯一 lossless 载体(该字段仅 pi chat 域消费,不入
|
|
104
|
-
* 合流形状——chat pi fork-from 链路 = 壳 fork-from action → ExecuteOptions
|
|
105
|
-
* .forkFromSessionFile → 本 ticket → runChatRound → runAndFinalize → runSpawn,
|
|
106
|
-
* 全程不经任务声明,由 pi-engine 测试锁定零回归)。
|
|
107
|
-
* 把任一类塞进 AgentCallOpts 会让 engine 契约反向耦合 execution 内部类型。故 chat
|
|
108
|
-
* 分支 ticket 优先消费(task 形参仅满足 port 签名),workflow 分支消费 task 形参——
|
|
109
|
-
* 这是「编排交接」与「任务声明」的职责分界,不是待消除的中间态。
|
|
110
|
-
*/
|
|
111
|
-
export interface ChatRoundTicket {
|
|
112
|
-
record: ExecutionRecord;
|
|
113
|
-
opts: ExecuteOptions;
|
|
114
|
-
identity: ChatRoundIdentity;
|
|
115
|
-
ctx: SessionRunnerContext;
|
|
116
|
-
signal: AbortSignal | undefined;
|
|
117
|
-
priority: number;
|
|
118
|
-
stream?: SubagentStream;
|
|
119
|
-
/** resume 选项(冷路径续轮):重开已 idle 的 session 续聊。undefined = 新 session。 */
|
|
120
|
-
resume?: SpawnResumeOpts;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* PiEngine 委托的编排服务面——SubagentService 的结构子集(鸭子类型:生产实现是
|
|
125
|
-
* SubagentService 单例;测试可注入 fake,不必构造整个 Service)。
|
|
126
|
-
* 为什么用结构接口而非直接 import SubagentService 类型:pi-engine 只依赖它实际消费的
|
|
127
|
-
* 方法面,防 Service 内部演进(增删私有方法)连锁影响引擎适配层。
|
|
128
|
-
*
|
|
129
|
-
* chat 域轮次面(takeChatRound/runChatRound/resumeChatRound/reportRecordTransition)
|
|
130
|
-
* 声明为可选项(u-2c 聚合后形态):两处生产绑定(registration.ts / SAR)均绑
|
|
131
|
-
* subagentService.asEngineService(= piEngineServiceAdapter),可选面实际恒被提供——
|
|
132
|
-
* 并非「仅 chat 绑定的实例提供、SAR 的实例不提供」。两域分支改由 taskId 空间区分:
|
|
133
|
-
* chat ticket 的 key 是 record.id(编排层预建时 set),SAR 的 taskId 是独立铸造的
|
|
134
|
-
* `sa-<uuid>`(subprocess-agent-runner.ts),恒不在 ticket map 中 → run 的
|
|
135
|
-
* takeChatRound 探测恒 miss → 恒走 executeAndAwait 分支。可选面保留 optional 的意义
|
|
136
|
-
* 是测试可注入仅含 workflow 面的 fake;requireResumeFace / runChatTicket 的 throw
|
|
137
|
-
* 分支在生产绑定(asEngineService 全量提供)下不可达,仅防 fake 绑定漏面时静默
|
|
138
|
-
* 丢消息/丢轮次。
|
|
139
|
-
*/
|
|
140
|
-
export interface PiEngineService {
|
|
141
|
-
executeAndAwait(
|
|
142
|
-
opts: ExecuteOptions,
|
|
143
|
-
signal?: AbortSignal,
|
|
144
|
-
onEvent?: (event: AgentEvent) => void,
|
|
145
|
-
stream?: SubagentStream,
|
|
146
|
-
): Promise<WorkflowAgentResult>;
|
|
147
|
-
getRecordForAction(id: string): ExecutionRecord;
|
|
148
|
-
closeSubagent(record: ExecutionRecord, force: boolean): Promise<void>;
|
|
149
|
-
cancel(id: string): boolean;
|
|
150
|
-
collectRecords(limit: number, statusFilter?: StatusFilter): SubagentRecord[];
|
|
151
|
-
/** chat 域轮次交接(run 的 chat 分支入口):按 taskId 取走预备包(一次性消费)。 */
|
|
152
|
-
takeChatRound?(taskId: string): ChatRoundTicket | undefined;
|
|
153
|
-
/** 执行预备的 chat 轮次(编排归 Service:pool 槽 + runSpawn + 终态迁移)。 */
|
|
154
|
-
runChatRound?(ticket: ChatRoundTicket): Promise<AgentResult>;
|
|
155
|
-
/** 冷路径续轮(interact message 分支的编排回调:守卫 + record 迁移 + 预备轮次 kick-off)。 */
|
|
156
|
-
resumeChatRound?(record: ExecutionRecord, text: string): void;
|
|
157
|
-
/** record 状态迁移上报(热路径投递后让 runtime 派生缓存失效 / GUI 回流)。 */
|
|
158
|
-
reportRecordTransition?(record: ExecutionRecord): void;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** PiEngine 构造依赖。 */
|
|
162
|
-
export interface PiEngineDeps {
|
|
163
|
-
/**
|
|
164
|
-
* 编排服务定位器(惰性求值——服务在 session_start 才注入,构造期可能尚不可用)。
|
|
165
|
-
* 组合根(index.ts registerPiEngine)绑 getSubagentService();per-session DI 场景
|
|
166
|
-
* (SAR)绑自身持有的服务引用。
|
|
167
|
-
*/
|
|
168
|
-
getService: () => PiEngineService | null;
|
|
169
|
-
/**
|
|
170
|
-
* 版本探测执行器(probe check "version" 的执行体)。默认实现 spawn `<command>
|
|
171
|
-
* --version`——测试注入 fake 避免真实子进程(vitest 下 command=node 会真的跑
|
|
172
|
-
* vitest --version,秒级开销且与被测逻辑无关)。
|
|
173
|
-
*/
|
|
174
|
-
probeVersion?: (invocation: PiInvocation) => Promise<string | undefined>;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* pi 引擎适配器(EnginePort 实现)。
|
|
179
|
-
*
|
|
180
|
-
* run 的错误语义(设计 §3.3.5 三条,pi 回填口径):
|
|
181
|
-
* ① 服务不可用(prepare 期)→ reject,不产生 handle;
|
|
182
|
-
* ② 运行中失败不 reject——现有链路(runSpawn → collectResult)已把失败收口为
|
|
183
|
-
* error 字段的 AgentResult,本层照直透传(record 收尾语义不变);
|
|
184
|
-
* executeAndAwait 的 throw(嵌套超限 ForkDepthExceededError / worktree 创建失败)
|
|
185
|
-
* 属创建期异常,按 ① 语义向上传播——与 SAR 接线前的 catch 行为闭环一致;
|
|
186
|
-
* ③ abort:ctx.signal 直通 executeAndAwait 第 2 参(下游 signal → proc.kill,
|
|
187
|
-
* D1 abort 分级的现链路形态)。
|
|
188
|
-
*/
|
|
189
|
-
export class PiEngine implements EnginePort {
|
|
190
|
-
readonly id = PI_ENGINE_ID;
|
|
191
|
-
|
|
192
|
-
private readonly deps: PiEngineDeps;
|
|
193
|
-
/** probe 结果缓存(版本变化检测由 P4 接线;force 强探跳过)。 */
|
|
194
|
-
private probeCache: ProbeReport | undefined;
|
|
195
|
-
|
|
196
|
-
constructor(deps: PiEngineDeps) {
|
|
197
|
-
this.deps = deps;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* pi 链路实际接通的能力(D3 链路接通口径,非 pi RPC 理论能力)。
|
|
202
|
-
* 声明升级(如 steer 接通 rpc stdin 通道后 unsupported → native)必须先改链路再改声明。
|
|
203
|
-
*/
|
|
204
|
-
capabilities(): EngineCapabilities {
|
|
205
|
-
return {
|
|
206
|
-
// PI_WORKFLOW_SCHEMA env 注入 + structured-output 扩展(方案 A 唯一校验权威)
|
|
207
|
-
schemaEnforcement: "native",
|
|
208
|
-
// pi RPC 有 steer,但 spawn 链路未接通(session-runner turn-limiter steer no-op)
|
|
209
|
-
steer: "unsupported",
|
|
210
|
-
// chatMode idle 复用 + message/close/cancel 交互面已接通
|
|
211
|
-
conversation: "native",
|
|
212
|
-
// persona 经 --skill / --append-system-prompt flag 通道注入
|
|
213
|
-
personaInjection: "flag",
|
|
214
|
-
// 30+ 事件流(六引擎最细粒度)
|
|
215
|
-
eventGranularity: "stream",
|
|
216
|
-
// 无 OS sandbox;worktree 隔离(公共层)= emulated
|
|
217
|
-
sandbox: "emulated",
|
|
218
|
-
// pi session JSONL 完整重建(session-reconstructor)
|
|
219
|
-
sessionRead: "full",
|
|
220
|
-
// chatMode 同进程 idle 复用(热)+ --session 冷续写
|
|
221
|
-
resume: "native",
|
|
222
|
-
// 现链路 abort = SIGTERM(pi 子进程 trap 后 graceful shutdown);rpc abort 命令未接通
|
|
223
|
-
interrupt: "kill-only",
|
|
224
|
-
// argv-mirror 镜像主进程 --approve 等 flag
|
|
225
|
-
permissionMode: "native",
|
|
226
|
-
// [D3-④] turn limiter + spawn watchdog 估算兑现轮数上限(预检 gate 据此放行)
|
|
227
|
-
maxTurns: true,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/** 探针(D7):invocation 可解析(二进制/脚本存在)+ 版本解析。pi 契约稳定(rpc.md
|
|
232
|
-
* 官方),探针取轻量两级——不做干跑回归(那是 zcode 类逆向契约引擎的需求)。 */
|
|
233
|
-
async probe(opts?: { force?: boolean }): Promise<ProbeReport> {
|
|
234
|
-
if (!opts?.force && this.probeCache) return this.probeCache;
|
|
235
|
-
|
|
236
|
-
// check 1:invocation 可解析(node 脚本存在 / standalone binary / PATH 可见)。
|
|
237
|
-
// relay:false 显式直连——探针测 pi 本体可解析性,经 relay 探到的是 runtime 健康,语义错位(§5.1)。
|
|
238
|
-
const invocation = getPiInvocation(["--version"], { relay: false });
|
|
239
|
-
const invocationOk = isInvocationResolvable(invocation);
|
|
240
|
-
const checks: ProbeReport["checks"] = [
|
|
241
|
-
{
|
|
242
|
-
name: "invocation",
|
|
243
|
-
ok: invocationOk,
|
|
244
|
-
detail: invocationOk
|
|
245
|
-
? `${invocation.command} ${invocation.args.join(" ")}`
|
|
246
|
-
: `cannot resolve pi executable: ${invocation.command} (script missing and not on PATH)`,
|
|
247
|
-
},
|
|
248
|
-
];
|
|
249
|
-
|
|
250
|
-
// check 2:版本解析(可解析才尝试——invocation 已失败时不再 spawn 必败进程)
|
|
251
|
-
let engineVersion = "";
|
|
252
|
-
if (invocationOk) {
|
|
253
|
-
const runVersion = this.deps.probeVersion ?? defaultProbeVersion;
|
|
254
|
-
const version = await runVersion(invocation);
|
|
255
|
-
const versionOk = version !== undefined && version.length > 0;
|
|
256
|
-
checks.push({
|
|
257
|
-
name: "version",
|
|
258
|
-
ok: versionOk,
|
|
259
|
-
detail: versionOk ? version : "pi --version returned empty or failed",
|
|
260
|
-
});
|
|
261
|
-
engineVersion = version ?? "";
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const ok = checks.every((c) => c.ok);
|
|
265
|
-
const report: ProbeReport = {
|
|
266
|
-
ok,
|
|
267
|
-
engineVersion,
|
|
268
|
-
checks,
|
|
269
|
-
...(ok ? {} : {
|
|
270
|
-
// 恢复指引(§3.3.3 终态四):版本确认命令 + 探针重跑命令
|
|
271
|
-
error: {
|
|
272
|
-
code: "engine_probe_failed",
|
|
273
|
-
recovery:
|
|
274
|
-
`Run \`${invocation.command} --version\` to confirm the pi binary works, then retry the probe. ` +
|
|
275
|
-
`If the binary is missing, reinstall pi (npm i -g @earendil-works/pi-coding-agent) or fix PATH.`,
|
|
276
|
-
},
|
|
277
|
-
}),
|
|
278
|
-
};
|
|
279
|
-
this.probeCache = report;
|
|
280
|
-
return report;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/** D1 主语义:合流任务声明(AgentCallOpts)一次直出 spawn 编排参数,委托
|
|
284
|
-
* executeAndAwait(行为零变化——D6 后无 spec 往返中间态)。 */
|
|
285
|
-
async run(task: AgentCallOpts, ctx: RunContext): Promise<EngineRunResult> {
|
|
286
|
-
const service = this.requireService();
|
|
287
|
-
// [D2 单轨] chat 域轮次:编排层(executeViaEngine / 冷路径续轮)预建的
|
|
288
|
-
// record/identity/host 上下文经 ctx.taskId 交接(ChatRoundTicket)。chat 域任务
|
|
289
|
-
// 由 ticket lossless 携带(record/identity/SessionRunnerContext/
|
|
290
|
-
// forkFromSessionFile/resume 是 host 编排件,不入任务声明——双形态保留的结构性
|
|
291
|
-
// 裁决见 ChatRoundTicket 注释),task 形参仅满足 port 签名;workflow 域(SAR)
|
|
292
|
-
// 无 ticket,走下方 executeAndAwait 分支。
|
|
293
|
-
const ticket = service.takeChatRound?.(ctx.taskId);
|
|
294
|
-
if (ticket) return this.runChatTicket(service, ticket);
|
|
295
|
-
const opts = agentCallToExecuteOptions(task, ctx.ctxModel);
|
|
296
|
-
// P4 引擎留痕(D9①):record 侧投影——实际执行引擎 id 恒 pi(本引擎身份);
|
|
297
|
-
// engineFallback 由路由层经 RunContext 透传(无 fallback 缺省,record 字段零噪声)
|
|
298
|
-
opts.engine = PI_ENGINE_ID;
|
|
299
|
-
if (ctx.engineFallback !== undefined) opts.engineFallback = ctx.engineFallback;
|
|
300
|
-
// pi 无隔离池(PI_CODING_AGENT_DIR 全局一份,poolKey 恒 'shared')——恒值声明,
|
|
301
|
-
// 宿主 journal writer 无需重定向(对齐点③的 pi 侧闭合)
|
|
302
|
-
ctx.onPoolResolved?.(PI_POOL_KEY);
|
|
303
|
-
// ctx.stream / onEvent / signal 直通——与 SAR 接线前的传参完全一致(双通道互斥设计
|
|
304
|
-
// 保持在 service/session-runner 内,引擎层不二次包装)
|
|
305
|
-
const wfResult = await service.executeAndAwait(opts, ctx.signal, ctx.onEvent, ctx.stream);
|
|
306
|
-
return {
|
|
307
|
-
handle: this.buildHandle(wfResult, ctx),
|
|
308
|
-
outcome: workflowResultToOutcome(wfResult, ctx),
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* D1 交互控制面:message 分支原生实现(D2——原编排层 deliverMessage/resumeRound 的
|
|
314
|
-
* pi RPC stdin 协议知识下沉引擎边界);close=closeSubagent / cancel=cancel 委托编排面。
|
|
315
|
-
* message 的 interrupt(steer 抢占 vs followUp 排队)映射为 pi streamingBehavior——
|
|
316
|
-
* pi 权威裁决 busy/idle:busy 时 followUp 入队/steer 抢占,idle 时开新 turn。
|
|
317
|
-
*/
|
|
318
|
-
async interact(handle: EngineHandle, action: InteractAction): Promise<InteractResult> {
|
|
319
|
-
const service = this.requireService();
|
|
320
|
-
try {
|
|
321
|
-
if (action.kind === "cancel") {
|
|
322
|
-
const recordId = this.resolveRecordId(service, handle);
|
|
323
|
-
if (!recordId) return notResumable(handle);
|
|
324
|
-
return service.cancel(recordId)
|
|
325
|
-
? { ok: true, delivered: true }
|
|
326
|
-
: { ok: false, code: "engine_interact_failed", message: `cancel returned false for ${recordId} (record already finalized or unknown)` };
|
|
327
|
-
}
|
|
328
|
-
const record = this.resolveRecord(service, handle);
|
|
329
|
-
if (!record) return notResumable(handle);
|
|
330
|
-
return await this.interactRecord(record, action);
|
|
331
|
-
} catch (err) {
|
|
332
|
-
// 投递/交互面以 throw 表达业务拒绝(not ready / EPIPE 兜底耗尽等,文案自带行动语言)
|
|
333
|
-
// ——转结构化失败,调用方拿 code+message 而非异常
|
|
334
|
-
return {
|
|
335
|
-
ok: false,
|
|
336
|
-
code: "engine_interact_failed",
|
|
337
|
-
message: toErrorMessage(err),
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* interact 的 record 锚定形态(编排层直传已归属校验的 record——chat 域编排面
|
|
344
|
-
* deliverChatMessage 的调用入口;port face interact = handle 解析 + 本方法)。
|
|
345
|
-
* 不做二次 store 查找/归属校验:调用方(编排层)持有同一 record 对象,与旧直调
|
|
346
|
-
* 形态的字段读写逐字节一致。
|
|
347
|
-
*/
|
|
348
|
-
async interactRecord(record: ExecutionRecord, action: InteractAction): Promise<InteractResult> {
|
|
349
|
-
const service = this.requireService();
|
|
350
|
-
try {
|
|
351
|
-
return await this.interactRecordInner(service, record, action);
|
|
352
|
-
} catch (err) {
|
|
353
|
-
// 投递/交互面以 throw 表达业务拒绝(not ready / EPIPE 兜底耗尽等,文案自带行动语言)
|
|
354
|
-
// ——转结构化失败,调用方拿 code+message 而非异常
|
|
355
|
-
return {
|
|
356
|
-
ok: false,
|
|
357
|
-
code: "engine_interact_failed",
|
|
358
|
-
message: toErrorMessage(err),
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/** interact 两形态的公共主体(cancel 归 handle 形态——record 锚定形态的调用方用
|
|
364
|
-
* 编排层 cancel,不走这里)。 */
|
|
365
|
-
private async interactRecordInner(
|
|
366
|
-
service: PiEngineService,
|
|
367
|
-
record: ExecutionRecord,
|
|
368
|
-
action: InteractAction,
|
|
369
|
-
): Promise<InteractResult> {
|
|
370
|
-
if (action.kind === "message") {
|
|
371
|
-
await this.deliverPrompt(service, record, action.payload, action.interrupt === true);
|
|
372
|
-
return { ok: true, delivered: true };
|
|
373
|
-
}
|
|
374
|
-
if (action.kind === "close") {
|
|
375
|
-
await service.closeSubagent(record, action.payload?.force === true);
|
|
376
|
-
return { ok: true, delivered: true };
|
|
377
|
-
}
|
|
378
|
-
throw new Error(
|
|
379
|
-
`[pi-engine] cancel is handle-form only (record-anchored callers use the orchestration cancel). ` +
|
|
380
|
-
`Recovery: internal wiring error — use interact(handle, {kind:'cancel'}) instead.`,
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* message 投递原生实现(协议知识下沉的本体,V2 决策 3):按**进程死活**分流,不按
|
|
386
|
-
* record.status——
|
|
387
|
-
*
|
|
388
|
-
* 热路径(进程活):prompt + streamingBehavior 直写 child.stdin(sendPromptCommand)。
|
|
389
|
-
* 冷路径(进程死):acquireActivateLock + 编排侧续轮回调(resumeChatRound——重开
|
|
390
|
-
* session + prompt;仅崩溃/timeout kill/跨重启命中)。
|
|
391
|
-
*
|
|
392
|
-
* EPIPE 兜底:stdin 管道断(进程实际已死但 close 事件未到)→ 按值守卫清理死句柄 +
|
|
393
|
-
* 连续失败计数 → 冷路径 resume + 原消息重放;连续达阈值(防死循环)throw 行动语言。
|
|
394
|
-
*/
|
|
395
|
-
private async deliverPrompt(
|
|
396
|
-
service: PiEngineService,
|
|
397
|
-
record: ExecutionRecord,
|
|
398
|
-
text: string,
|
|
399
|
-
interrupt: boolean,
|
|
400
|
-
): Promise<void> {
|
|
401
|
-
// 新 turn,disarm idle timer(防 turn 期间误杀活进程,V2 决策 4)
|
|
402
|
-
disarmIdleTimer(record.id);
|
|
403
|
-
const child = getChildByRecord(record.id);
|
|
404
|
-
if (child && !child.killed) {
|
|
405
|
-
// 热路径:进程活(running/idle 都可能是热路径——V2 进程长驻,idle 态进程仍在内存)
|
|
406
|
-
record.status = "running";
|
|
407
|
-
// 刷新 pid 内存记账(resume spawn 后 child.pid 已变,顺便更新)
|
|
408
|
-
if (child.pid !== undefined) record.pid = child.pid;
|
|
409
|
-
try {
|
|
410
|
-
sendPromptCommand(child, text, { streamingBehavior: interrupt ? "steer" : "followUp" });
|
|
411
|
-
// 热路径成功,清零 EPIPE 连续失败计数([v4 A-1] 计数器在 stdin-writer)
|
|
412
|
-
clearEpipeFailure(record.id);
|
|
413
|
-
// [race-F5] 写后死进程检测:write 同步成功只代表数据进了内核 pipe 缓冲,子进程
|
|
414
|
-
// 可能在读取前死亡(gate/idle kill 竞速)。exitCode/signalCode 已非 null = 进程已死
|
|
415
|
-
//(close 事件可能尚未到达),缓冲中的消息将被静默丢弃。只 warn 留证(含消息类型),
|
|
416
|
-
// 不抛错不重试:终态回收已由 kill 路径保证,对死进程重试反而可能二次写。
|
|
417
|
-
if (child.exitCode !== null || child.signalCode !== null) {
|
|
418
|
-
logger.warn(
|
|
419
|
-
`[subagents] deliverMessage: child ${record.id} died around stdin write, message may be lost`,
|
|
420
|
-
{
|
|
421
|
-
msgType: interrupt ? "steer" : "followUp",
|
|
422
|
-
exitCode: child.exitCode,
|
|
423
|
-
signalCode: child.signalCode,
|
|
424
|
-
},
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
// 轮始执行态信号清除 + 迁移上报(residual-fixes U3 补全,与冷路径续轮对称):
|
|
428
|
-
// 新一轮开跑 = 无轮终信号——清上一轮 result(§5.4 isStreaming 公式要求
|
|
429
|
-
// result undefined 才显示 streaming)与 resumable,appendEntry 让 runtime/W18
|
|
430
|
-
// 派生缓存失效、GUI 侧从 waiting 切回 spinner。仅在投递成功后清(失败保留
|
|
431
|
-
// 上一轮信号,EPIPE 兜底走续轮时由其再清)。
|
|
432
|
-
record.result = undefined;
|
|
433
|
-
record.resumable = undefined;
|
|
434
|
-
service.reportRecordTransition?.(record);
|
|
435
|
-
} catch (err) {
|
|
436
|
-
// EPIPE 兜底:stdin 管道已断,进程实际已死但 close 事件尚未到达。
|
|
437
|
-
// 检测 EPIPE 关键词 → 进程按 dead 处理 → 自动转冷路径 resume + 消息重放。
|
|
438
|
-
// 本兜底不持 activateLock,但与冷路径共用续轮的在途守卫(编排侧
|
|
439
|
-
// resumesInFlight):resume 已在途时兜底的续轮调用被拒(throw 行动语言),
|
|
440
|
-
// 不会二次 spawn。
|
|
441
|
-
if (err instanceof Error && err.message.includes("EPIPE")) {
|
|
442
|
-
logger.warn(`[subagents] EPIPE on hot path for ${record.id}, falling back to cold path resume`, {
|
|
443
|
-
detail: err.message,
|
|
444
|
-
});
|
|
445
|
-
// 清理 spawnedChildren 中的死进程条目(让续轮能重新 spawn)。
|
|
446
|
-
// [M4] 按值守卫:仅当 Map 当前值仍是本次写 EPIPE 的 child 才删——若已被 resume
|
|
447
|
-
// spawn 覆盖为新 child(close 事件先于本 catch 到达的极端时序),不误删新注册
|
|
448
|
-
//(与 session-runner removeChildRegistration 同语义)。
|
|
449
|
-
if (spawnedChildren.get(record.id) === child) {
|
|
450
|
-
spawnedChildren.delete(record.id);
|
|
451
|
-
}
|
|
452
|
-
// 递增连续 EPIPE 计数([v4 A-1] helper 合并同步/异步路径计数)
|
|
453
|
-
const count = recordEpipeFailure(record.id);
|
|
454
|
-
if (count >= EPIPE_FAILURE_THRESHOLD) {
|
|
455
|
-
// 连续达阈值 EPIPE → 不再尝试 resume,throw 含恢复指引
|
|
456
|
-
clearEpipeFailure(record.id);
|
|
457
|
-
throw new Error(
|
|
458
|
-
`[subagents] EPIPE fallback exhausted for ${record.id}: ${count} consecutive EPIPE failures. ` +
|
|
459
|
-
`Recovery: use action:'close' to clean up, then action:'start' a new subagent.`,
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
// 冷路径 resume + 原消息重放(v4 B-1: status 已 running,续轮守卫直接放行)
|
|
463
|
-
this.requireResumeFace(service)(record, text);
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
// [T2⑧ / PS-3] 非 EPIPE 写失败(如 ERR_STREAM_DESTROYED——错误分类只认 "EPIPE"
|
|
467
|
-
// 子串,过窄):deliverPrompt 入口已无条件 disarmIdleTimer,若直接 rethrow 不
|
|
468
|
-
// re-arm——「timer armed」这个防泄漏前提在失败路径永久丢失:进程无人回收
|
|
469
|
-
//(无 timer、无轮、record=running)泄漏至宿主退出,hasRunningBackground 恒真
|
|
470
|
-
// 还会顺延其他完成通知。rethrow 前再武装 idle timer(含防御性降级),恢复进程
|
|
471
|
-
// 回收通道 + 通知放行门;调用方错误语义不变(仍 rethrow)。re-arm 挂载位在本
|
|
472
|
-
// catch(引擎边界)而非编排层 interactRecord:只有这里能区分「stdin 写失败」
|
|
473
|
-
// 与业务拒绝,编排层盲目 re-arm 会误武装。
|
|
474
|
-
this.rearmIdleTimerAfterHotPathFailure(record, err);
|
|
475
|
-
// 非 EPIPE 错误——不应发生,重新抛出让调用方处理
|
|
476
|
-
throw err;
|
|
477
|
-
}
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
// 冷路径:进程死(idle timer reap / 崩溃 / 跨重启),record 应为 idle-resumable → resume spawn。
|
|
481
|
-
// D3:acquireActivateLock 双保险——注意锁只覆盖续轮同步段,释放在子进程注册
|
|
482
|
-
//(session-runner spawnedChildren.set)之前(中间隔 pool.acquire await + tempFile 等异步点)。
|
|
483
|
-
// 真正的单写者守卫是续轮的在途守卫(编排侧 resumesInFlight):锁释放后、child
|
|
484
|
-
// 注册前到达的第二次冷路径 message 在续轮处被拒,不会二次 spawn。
|
|
485
|
-
const releaseLock = await acquireActivateLock(record.id);
|
|
486
|
-
try {
|
|
487
|
-
this.requireResumeFace(service)(record, text);
|
|
488
|
-
} finally {
|
|
489
|
-
releaseLock();
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* [T2⑧ / PS-3] 热路径非 EPIPE 写失败后的 idle timer 再武装(deliverPrompt 入口
|
|
495
|
-
* 无条件 disarm 的对称恢复)。首选 record.idleTimeoutMs(armIdleTimer 对超域值
|
|
496
|
-
* fail-fast throw,U1),非法时防御性降级 DEFAULT_IDLE_TIMEOUT_MS;两级都失败落
|
|
497
|
-
* bestEffort 留证(不再向上抛——本方法服务于「进程回收通道不丢」,错误本体由
|
|
498
|
-
* 调用方 rethrow)。超时处置 = killRecordChildWithEscalation(与 idle GC 常规路径
|
|
499
|
-
* 同款升级杀链)。
|
|
500
|
-
*/
|
|
501
|
-
private rearmIdleTimerAfterHotPathFailure(record: ExecutionRecord, cause: unknown): void {
|
|
502
|
-
const detail = toErrorMessage(cause);
|
|
503
|
-
const onTimeout = (): void => {
|
|
504
|
-
killRecordChildWithEscalation(record.id, "idle timer (hot-path failure fallback)");
|
|
505
|
-
};
|
|
506
|
-
try {
|
|
507
|
-
armIdleTimer(record.id, onTimeout, record.idleTimeoutMs);
|
|
508
|
-
} catch {
|
|
509
|
-
try {
|
|
510
|
-
armIdleTimer(record.id, onTimeout, DEFAULT_IDLE_TIMEOUT_MS);
|
|
511
|
-
} catch (fallbackErr) {
|
|
512
|
-
bestEffort(fallbackErr, "rearmIdleTimer fallback (deliverPrompt non-EPIPE failure)", "error");
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
logger.warn(
|
|
517
|
-
`[subagents] deliverPrompt hot path failed for ${record.id}; idle timer re-armed to keep process recovery bounded`,
|
|
518
|
-
{ detail },
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
/** chat 域轮次执行回调的守卫提取(可选项缺失 = 引擎绑定形态错误——chat 编排面
|
|
523
|
-
* 必须由 chat 绑定的适配器提供;静默 no-op 会丢消息,宁可显式 throw)。 */
|
|
524
|
-
private requireResumeFace(service: PiEngineService): (record: ExecutionRecord, text: string) => void {
|
|
525
|
-
const resume = service.resumeChatRound;
|
|
526
|
-
if (!resume) {
|
|
527
|
-
throw new Error(
|
|
528
|
-
`[pi-engine] chat resume face unavailable on this engine binding (workflow-only service binding). ` +
|
|
529
|
-
`Recovery: internal wiring error — message delivery requires the chat-bound engine instance; report this.`,
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
return resume;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* chat 轮次执行(run 的 chat 分支主体):编排归 Service(runChatRound——pool 槽 +
|
|
537
|
-
* runSpawn + 终态迁移,行为零变化),引擎侧构造 handle/outcome。chat 编排不消费
|
|
538
|
-
* 返回值(notify/终态迁移在编排侧 kickOff 回调),handle/outcome 是 port 契约的
|
|
539
|
-
* 形态完备(interact/read 以 recordId/sessionFile 定位)。
|
|
540
|
-
*/
|
|
541
|
-
private async runChatTicket(service: PiEngineService, ticket: ChatRoundTicket): Promise<EngineRunResult> {
|
|
542
|
-
const runChatRound = service.runChatRound;
|
|
543
|
-
if (!runChatRound) {
|
|
544
|
-
throw new Error(
|
|
545
|
-
`[pi-engine] chat round runner unavailable on this engine binding (workflow-only service binding). ` +
|
|
546
|
-
`Recovery: internal wiring error — chat rounds require the chat-bound engine instance; report this.`,
|
|
547
|
-
);
|
|
548
|
-
}
|
|
549
|
-
const result = await runChatRound(ticket);
|
|
550
|
-
const sessionFile = ticket.record.sessionFile;
|
|
551
|
-
return {
|
|
552
|
-
handle: {
|
|
553
|
-
data: {
|
|
554
|
-
v: 1,
|
|
555
|
-
engineId: PI_ENGINE_ID,
|
|
556
|
-
// pi 定位符:recordId(interact 控制面 key)+ sessionFile(read 第①级)
|
|
557
|
-
sessionRef: {
|
|
558
|
-
recordId: ticket.record.id,
|
|
559
|
-
...(sessionFile !== undefined ? { sessionFile } : {}),
|
|
560
|
-
},
|
|
561
|
-
poolKey: PI_POOL_KEY,
|
|
562
|
-
adapterVersion: PI_ADAPTER_VERSION,
|
|
563
|
-
},
|
|
564
|
-
},
|
|
565
|
-
outcome: chatResultToOutcome(result, sessionFile, ticket.record.worktreeHandle?.path),
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* D6 read 三级降级:①pi session JSONL 原生读取(readPiSessionView——共享 reader
|
|
571
|
-
* 单一实现,sessionRead: 'full' 的依据)→ ②宿主 event journal 重放(P4 接线:
|
|
572
|
-
* common/journal-replay 复用 live reducer,重放等价性见 §3.3.6)→ ③outcome-only。
|
|
573
|
-
*/
|
|
574
|
-
async read(handle: EngineHandle): Promise<SessionView> {
|
|
575
|
-
const sessionFile = refString(handle.data.sessionRef, "sessionFile");
|
|
576
|
-
const native = sessionFile !== undefined ? await readPiSessionView(sessionFile) : undefined;
|
|
577
|
-
if (!native) {
|
|
578
|
-
// ②级:journal 重放(journalPath 缺省 / 文件不存在 / 无事件 → undefined 落③级)
|
|
579
|
-
const journaled = replayJournalToSessionView(handle, PI_ENGINE_ID);
|
|
580
|
-
if (journaled !== undefined) return journaled;
|
|
581
|
-
return { engineId: PI_ENGINE_ID, turns: [], source: "outcome-only" };
|
|
582
|
-
}
|
|
583
|
-
return native;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// ── 内部 ──
|
|
587
|
-
|
|
588
|
-
private requireService(): PiEngineService {
|
|
589
|
-
const service = this.deps.getService();
|
|
590
|
-
if (!service) {
|
|
591
|
-
throw new Error(
|
|
592
|
-
`[pi-engine] SubagentService unavailable (initSession not called or extension disposed). ` +
|
|
593
|
-
`Recovery: ensure the subagent-workflow extension is initialized in this session before dispatching.`,
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
return service;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/** run 出口构造可持久化 handle(自描述:engineId + sessionRef + poolKey + 版本)。 */
|
|
600
|
-
private buildHandle(wfResult: WorkflowAgentResult, ctx: RunContext): EngineHandle {
|
|
601
|
-
const sessionFile = wfResult.sessionFile;
|
|
602
|
-
return {
|
|
603
|
-
data: {
|
|
604
|
-
v: 1,
|
|
605
|
-
engineId: PI_ENGINE_ID,
|
|
606
|
-
// pi 定位符:sessionFile(read 第①级);recordId 由宿主 record 层补记
|
|
607
|
-
//(executeAndAwait 不外露 record id,交互定位走 resolveRecord 的扫描兜底)
|
|
608
|
-
sessionRef: sessionFile !== undefined ? { sessionFile } : {},
|
|
609
|
-
poolKey: ctx.poolKey !== "" ? ctx.poolKey : PI_POOL_KEY,
|
|
610
|
-
adapterVersion: PI_ADAPTER_VERSION,
|
|
611
|
-
},
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
/** interact 定位 record:sessionRef.recordId 直查;缺省时按 sessionFile 扫描兜底。 */
|
|
616
|
-
private resolveRecord(service: PiEngineService, handle: EngineHandle): ExecutionRecord | undefined {
|
|
617
|
-
const recordId = this.resolveRecordId(service, handle);
|
|
618
|
-
if (recordId === undefined) return undefined;
|
|
619
|
-
try {
|
|
620
|
-
return service.getRecordForAction(recordId);
|
|
621
|
-
} catch {
|
|
622
|
-
return undefined;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
private resolveRecordId(service: PiEngineService, handle: EngineHandle): string | undefined {
|
|
627
|
-
if (handle.data.engineId !== PI_ENGINE_ID) return undefined;
|
|
628
|
-
const recordId = refString(handle.data.sessionRef, "recordId");
|
|
629
|
-
if (recordId !== undefined) return recordId;
|
|
630
|
-
const sessionFile = refString(handle.data.sessionRef, "sessionFile");
|
|
631
|
-
if (sessionFile === undefined) return undefined;
|
|
632
|
-
// sessionFile 兜底:扫描 record 清单匹配定位符(interact 频率低,扫描成本可接受)
|
|
633
|
-
const found = service
|
|
634
|
-
.collectRecords(INTERACT_SCAN_LIMIT, "all")
|
|
635
|
-
.find((r) => r.sessionFile === sessionFile);
|
|
636
|
-
return found?.id;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// ── 模块级纯函数(可单测)──
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* [D6 一次直出] 合流任务声明 AgentCallOpts → ExecuteOptions(pi spawn 编排参数)。
|
|
644
|
-
*
|
|
645
|
-
* 这是 pi 边界的唯一任务形状映射点,取代已删除的两级链路
|
|
646
|
-
* (execute-options-mapper.mapToExecuteOptions → task-spec-mapper.taskSpecToExecuteOptions
|
|
647
|
-
* ——原链路 AgentCallOpts→ExecuteOptions→AgentTaskSpec→ExecuteOptions 三态两映射,
|
|
648
|
-
* 缺省路径付双份多引擎税)。逐字段等值性(合流前 ↔ 合流后):
|
|
649
|
-
* task ← prompt(原 prompt→ExecuteOptions.task→spec.task 恒等往返)
|
|
650
|
-
* slug ← description ?? agent ?? "workflow-agent",超 SLUG_MAX_LENGTH(35) 截断
|
|
651
|
-
* (原 mapToExecuteOptions 的派生规则原样内联;截断常量迁至
|
|
652
|
-
* orchestration/models/types.ts 与 description 字段同文件)
|
|
653
|
-
* agent ← agent(原 spec.agent ?? persona.agentRef——agentRef 无生产写入方,
|
|
654
|
-
* 恒 undefined,随 PersonaSpec 裁撤)
|
|
655
|
-
* model ← model(显式 override 透传,ctxModel 不压扁填底)
|
|
656
|
-
* thinkingLevel ← thinkingLevel(原 effort 恒等映射层删除)
|
|
657
|
-
* skillPath / appendSystemPrompt ← 同名平铺(原 persona 收拢/还原层删除——往返恒等)
|
|
658
|
-
* schema ← schema
|
|
659
|
-
* schemaEnv ← schema 派生优先(stringifySchemaCached compact,与
|
|
660
|
-
* agent-opts-resolver 同函数同缓存,逐字节等值);无 schema 时
|
|
661
|
-
* schemaEnv 兜底(原 RunContext.schemaEnv 兜底通道的合流形态等价
|
|
662
|
-
* ——SAR 填 ctx.schemaEnv 的源即 opts.schemaEnv);均无 → undefined
|
|
663
|
-
* (BC-6:childEnv 不注入)
|
|
664
|
-
* maxTurns / graceTurns / fork / worktree / cwd / conversation / idleTimeoutMs ← 同名
|
|
665
|
-
* ctxModel ← runCtx.ctxModel(运行期字段,D-008 兼底链路)
|
|
666
|
-
* engine / engineFallback 由 run 调用方追加(P4 引擎留痕,D9①)
|
|
667
|
-
* 调用方扩展字段(scene/timeoutMs/skill/schemaEnv 已述/engine/returnMeta)中引擎
|
|
668
|
-
* 不消费的:timeoutMs 由 SAR mergeTimeoutSignal 合并进 signal、engine 由 SAR 路由层
|
|
669
|
-
* 消费、scene/returnMeta 为 worker 层字段——均不入 spawn 参数。
|
|
670
|
-
*
|
|
671
|
-
* 字段完整性对照表(实施期门⑤产物)固化在 __tests__/spawn-opts-direct.test.ts,
|
|
672
|
-
* 逐字段断言覆盖。
|
|
673
|
-
*/
|
|
674
|
-
export function agentCallToExecuteOptions(
|
|
675
|
-
task: AgentCallOpts,
|
|
676
|
-
ctxModel?: ModelInfo,
|
|
677
|
-
): ExecuteOptions {
|
|
678
|
-
// slug:优先 description,缺失回落 agent 名(保证非空)。超长截断。
|
|
679
|
-
const rawSlug = task.description ?? task.agent ?? "workflow-agent";
|
|
680
|
-
return {
|
|
681
|
-
task: task.prompt,
|
|
682
|
-
slug: rawSlug.length > SLUG_MAX_LENGTH ? rawSlug.slice(0, SLUG_MAX_LENGTH) : rawSlug,
|
|
683
|
-
agent: task.agent,
|
|
684
|
-
model: task.model,
|
|
685
|
-
thinkingLevel: task.thinkingLevel,
|
|
686
|
-
skillPath: task.skillPath,
|
|
687
|
-
appendSystemPrompt: task.appendSystemPrompt,
|
|
688
|
-
schema: task.schema,
|
|
689
|
-
// schemaEnv 派生优先(等值依据见函数注释);无 schema 且调用方持有预编码值时兜底
|
|
690
|
-
schemaEnv: task.schema !== undefined
|
|
691
|
-
? stringifySchemaCached(task.schema, "compact")
|
|
692
|
-
: task.schemaEnv,
|
|
693
|
-
maxTurns: task.maxTurns,
|
|
694
|
-
graceTurns: task.graceTurns,
|
|
695
|
-
ctxModel,
|
|
696
|
-
fork: task.fork,
|
|
697
|
-
worktree: task.worktree,
|
|
698
|
-
cwd: task.cwd,
|
|
699
|
-
conversation: task.conversation,
|
|
700
|
-
idleTimeoutMs: task.idleTimeoutMs,
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/** sessionRef 取 string 值的运行时 guard(Record 索引读经 typeof 收窄,非裸取)。 */
|
|
705
|
-
function refString(ref: Record<string, string>, key: string): string | undefined {
|
|
706
|
-
const v = ref[key];
|
|
707
|
-
return typeof v === "string" ? v : undefined;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
/** 默认版本探测:spawn `<command> --version`(probe 超时按探针失败处理)。 */
|
|
711
|
-
async function defaultProbeVersion(invocation: PiInvocation): Promise<string | undefined> {
|
|
712
|
-
try {
|
|
713
|
-
return await new Promise<string | undefined>((resolve, reject) => {
|
|
714
|
-
execFile(
|
|
715
|
-
invocation.command,
|
|
716
|
-
invocation.args,
|
|
717
|
-
{ encoding: "utf8", timeout: PROBE_VERSION_TIMEOUT_MS },
|
|
718
|
-
(err: Error | null, stdout: string) => {
|
|
719
|
-
if (err) reject(err);
|
|
720
|
-
else resolve(stdout.trim().split("\n")[0]?.trim() || undefined);
|
|
721
|
-
},
|
|
722
|
-
);
|
|
723
|
-
});
|
|
724
|
-
} catch (err) {
|
|
725
|
-
// 版本探测是 best-effort(失败经 checks 反映进 ProbeReport,不影响执行主流程),
|
|
726
|
-
// debug 级留诊断线索即可,不刷 info/warn
|
|
727
|
-
logger.debug(
|
|
728
|
-
`[pi-engine] probe version check failed (best-effort continue): ${
|
|
729
|
-
toErrorMessage(err)
|
|
730
|
-
}`,
|
|
731
|
-
);
|
|
732
|
-
return undefined;
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* invocation 是否可解析:非 PATH 依赖形态(node+script / standalone binary)直接认
|
|
738
|
-
* (command 是已存在的绝对路径);PATH 形态(command === "pi")扫 PATH 目录核实。
|
|
739
|
-
*/
|
|
740
|
-
function isInvocationResolvable(invocation: PiInvocation): boolean {
|
|
741
|
-
if (invocation.command !== "pi") return true;
|
|
742
|
-
const pathEnv = process.env.PATH ?? "";
|
|
743
|
-
for (const dir of pathEnv.split(path.delimiter)) {
|
|
744
|
-
if (dir === "") continue;
|
|
745
|
-
try {
|
|
746
|
-
if (fs.existsSync(path.join(dir, "pi"))) return true;
|
|
747
|
-
} catch (err) {
|
|
748
|
-
// 单目录探测失败(权限等)继续扫下一个——debug 级留线索即可
|
|
749
|
-
logger.debug(
|
|
750
|
-
`[pi-engine] PATH dir probe failed (continue scanning): ${dir}: ${
|
|
751
|
-
toErrorMessage(err)
|
|
752
|
-
}`,
|
|
753
|
-
);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
return false;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
/** execution AgentResult(chat 轮次)→ AgentOutcome(port 契约形态完备;chat 编排
|
|
760
|
-
* 侧不消费——终态迁移/notify 在编排层)。 */
|
|
761
|
-
function chatResultToOutcome(
|
|
762
|
-
result: AgentResult,
|
|
763
|
-
sessionFile: string | undefined,
|
|
764
|
-
worktreePath: string | undefined,
|
|
765
|
-
): AgentOutcome {
|
|
766
|
-
return {
|
|
767
|
-
content: result.text,
|
|
768
|
-
...(result.parsedOutput !== undefined ? { parsedOutput: result.parsedOutput } : {}),
|
|
769
|
-
durationMs: result.durationMs,
|
|
770
|
-
...(result.error !== undefined ? { error: result.error } : {}),
|
|
771
|
-
sessionId: result.sessionId,
|
|
772
|
-
...(sessionFile !== undefined ? { sessionFile } : {}),
|
|
773
|
-
...(worktreePath !== undefined ? { worktreePath } : {}),
|
|
774
|
-
// toolCalls 不映射(execution ToolCall ≠ AgentOutcome 的 orchestration ToolCallEntry,
|
|
775
|
-
// chat 编排侧不消费本 outcome——终态记账在编排层 record 通路)
|
|
776
|
-
engineId: PI_ENGINE_ID,
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
/** orchestration AgentResult → AgentOutcome(补 engineId;exitCode 无来源缺省)。 */
|
|
781
|
-
function workflowResultToOutcome(wf: WorkflowAgentResult, ctx?: RunContext): AgentOutcome {
|
|
782
|
-
return {
|
|
783
|
-
content: wf.content,
|
|
784
|
-
// [D5-③] 失败分诊标签透传(产出侧 output-collector → mapper → 此处 → SAR →
|
|
785
|
-
// executeAgentCall 读字段分诊)。成功路径 undefined 不落键。
|
|
786
|
-
...(wf.failureKind !== undefined ? { failureKind: wf.failureKind } : {}),
|
|
787
|
-
parsedOutput: wf.parsedOutput,
|
|
788
|
-
usage: wf.usage,
|
|
789
|
-
durationMs: wf.durationMs,
|
|
790
|
-
error: wf.error,
|
|
791
|
-
sessionId: wf.sessionId,
|
|
792
|
-
sessionFile: wf.sessionFile,
|
|
793
|
-
worktreePath: wf.worktreePath,
|
|
794
|
-
toolCalls: wf.toolCalls,
|
|
795
|
-
engineId: PI_ENGINE_ID,
|
|
796
|
-
// D9① fallback 留痕(路由层经 RunContext 透传;无 fallback 缺省)
|
|
797
|
-
...(ctx?.engineFallback !== undefined ? { engineFallback: ctx.engineFallback } : {}),
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
/** 死 handle 的统一拒绝(D1 推论:engine_session_not_resumable 指向 cold resume 路径)。 */
|
|
802
|
-
function notResumable(handle: EngineHandle): InteractResult {
|
|
803
|
-
return {
|
|
804
|
-
ok: false,
|
|
805
|
-
code: "engine_session_not_resumable",
|
|
806
|
-
message:
|
|
807
|
-
`the pi session behind this handle is not resumable via interact (record not found for ` +
|
|
808
|
-
`sessionRef ${JSON.stringify(handle.data.sessionRef)}). Idle-process reuse does not survive a main-session ` +
|
|
809
|
-
`reload. Recovery: use a cold resume path (pi --session / --resume with the session file), or start a new subagent.`,
|
|
810
|
-
};
|
|
811
|
-
}
|