@zhushanwen/subagent-core 0.2.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/README.md +114 -0
- package/dist/chunk-3VOERJPJ.js +22 -0
- package/dist/chunk-A4IVZWVX.js +31 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +54 -0
- package/dist/execution/engine/engines/zcode/constants.d.cts +31 -0
- package/dist/execution/engine/engines/zcode/constants.d.ts +31 -0
- package/dist/execution/engine/engines/zcode/constants.js +22 -0
- package/dist/execution/engine/engines/zcode/reader.cjs +276 -0
- package/dist/execution/engine/engines/zcode/reader.d.cts +20 -0
- package/dist/execution/engine/engines/zcode/reader.d.ts +20 -0
- package/dist/execution/engine/engines/zcode/reader.js +239 -0
- package/dist/execution/engine/paths.cjs +55 -0
- package/dist/execution/engine/paths.d.cts +8 -0
- package/dist/execution/engine/paths.d.ts +8 -0
- package/dist/execution/engine/paths.js +26 -0
- package/dist/execution/relay-env.cjs +62 -0
- package/dist/execution/relay-env.d.cts +33 -0
- package/dist/execution/relay-env.d.ts +33 -0
- package/dist/execution/relay-env.js +20 -0
- package/dist/index.cjs +1852 -0
- package/dist/index.d.cts +1184 -0
- package/dist/index.d.ts +1184 -0
- package/dist/index.js +1810 -0
- package/dist/types-BxyAidGf.d.cts +875 -0
- package/dist/types-BxyAidGf.d.ts +875 -0
- package/package.json +99 -0
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +142 -0
- package/src/__tests__/append-system-prompt-assembly.test.ts +242 -0
- package/src/__tests__/builtin-workflows-structure.test.ts +180 -0
- package/src/__tests__/derive-closed-display-parity.test.ts +93 -0
- package/src/__tests__/fr4-get-state-handshake.test.ts +125 -0
- package/src/__tests__/fr8-orphan-recovery.test.ts +259 -0
- package/src/__tests__/m2-append-content-probe.test.ts +73 -0
- package/src/__tests__/manifest-store.test.ts +368 -0
- package/src/__tests__/record-store-cache.test.ts +335 -0
- package/src/__tests__/record-store-index.test.ts +560 -0
- package/src/__tests__/record-store-last-line.test.ts +129 -0
- package/src/__tests__/review-fix-loop-script.test.ts +288 -0
- package/src/__tests__/review-fix-loop-utils.test.ts +2377 -0
- package/src/__tests__/robustness-low-batch1.test.ts +286 -0
- package/src/__tests__/robustness-medium-batch1.test.ts +69 -0
- package/src/__tests__/robustness-medium-batch2.test.ts +65 -0
- package/src/__tests__/robustness-medium-batch3.test.ts +30 -0
- package/src/__tests__/robustness-medium-batch4.test.ts +358 -0
- package/src/__tests__/session-runner.test.ts +69 -0
- package/src/__tests__/smoke.test.ts +9 -0
- package/src/__tests__/stream-sink.test.ts +203 -0
- package/src/core/__tests__/host-services.test.ts +144 -0
- package/src/core/__tests__/logger.test.ts +78 -0
- package/src/core/__tests__/notify-ports.test.ts +104 -0
- package/src/core/host-services.ts +97 -0
- package/src/core/logger.ts +47 -0
- package/src/core/notify-ports.ts +126 -0
- package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +53 -0
- package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +150 -0
- package/src/execution/__tests__/alive-store.test.ts +147 -0
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +490 -0
- package/src/execution/__tests__/channel-registry-handshake.test.ts +242 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +610 -0
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
- package/src/execution/__tests__/concurrency-pool.test.ts +250 -0
- package/src/execution/__tests__/config.test.ts +302 -0
- package/src/execution/__tests__/delivery-methods.test.ts +424 -0
- package/src/execution/__tests__/dialog-queue.test.ts +299 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +243 -0
- package/src/execution/__tests__/execute-nesting.test.ts +323 -0
- package/src/execution/__tests__/execute-options-mapper.test.ts +200 -0
- package/src/execution/__tests__/execution-record.test.ts +1394 -0
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +171 -0
- package/src/execution/__tests__/finalize-record.test.ts +367 -0
- package/src/execution/__tests__/finalized-marker.test.ts +104 -0
- package/src/execution/__tests__/format-schema-instruction.test.ts +169 -0
- package/src/execution/__tests__/gc-timer.test.ts +184 -0
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
- package/src/execution/__tests__/helpers/mock-extension-api.ts +30 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +242 -0
- package/src/execution/__tests__/host-mode.test.ts +87 -0
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +383 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
- package/src/execution/__tests__/manifest-parentid.test.ts +117 -0
- package/src/execution/__tests__/model-resolver.test.ts +465 -0
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
- package/src/execution/__tests__/nested-visibility.test.ts +325 -0
- package/src/execution/__tests__/output-collector.test.ts +358 -0
- package/src/execution/__tests__/path-encoding.test.ts +104 -0
- package/src/execution/__tests__/pi-invocation.test.ts +134 -0
- package/src/execution/__tests__/record-store.test.ts +1057 -0
- package/src/execution/__tests__/records-cwd-isolation.test.ts +91 -0
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +339 -0
- package/src/execution/__tests__/recursive-visibility-env.test.ts +273 -0
- package/src/execution/__tests__/relay-env.test.ts +42 -0
- package/src/execution/__tests__/resource-policy.test.ts +109 -0
- package/src/execution/__tests__/rpc-mode.test.ts +89 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +687 -0
- package/src/execution/__tests__/run-spawn-integration.test.ts +933 -0
- package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +196 -0
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +199 -0
- package/src/execution/__tests__/session-context-resolver.test.ts +167 -0
- package/src/execution/__tests__/session-file-gc.test.ts +293 -0
- package/src/execution/__tests__/session-reconstructor.test.ts +379 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +350 -0
- package/src/execution/__tests__/spawn-args.test.ts +445 -0
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +189 -0
- package/src/execution/__tests__/spawn-event-adapter.test.ts +167 -0
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +207 -0
- package/src/execution/__tests__/spawned-children.test.ts +92 -0
- package/src/execution/__tests__/start-sync-model-guard.test.ts +150 -0
- package/src/execution/__tests__/stdin-writer.test.ts +462 -0
- package/src/execution/__tests__/stream-sink-retirement.test.ts +261 -0
- package/src/execution/__tests__/subagent-service-abort.test.ts +60 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +629 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
- package/src/execution/__tests__/subagent-service.test.ts +788 -0
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +310 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +612 -0
- package/src/execution/__tests__/temp-prompt.test.ts +53 -0
- package/src/execution/__tests__/timeout-integration.test.ts +615 -0
- package/src/execution/__tests__/tombstone-store.test.ts +73 -0
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
- package/src/execution/__tests__/turn-limiter.test.ts +65 -0
- package/src/execution/__tests__/ui-channels.test.ts +187 -0
- package/src/execution/__tests__/ui-interaction-model.test.ts +67 -0
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +369 -0
- package/src/execution/__tests__/ui-request-handler.test.ts +204 -0
- package/src/execution/__tests__/ui-request-observability.test.ts +113 -0
- package/src/execution/__tests__/ui-request-queue.test.ts +133 -0
- package/src/execution/__tests__/worktree-manager.test.ts +633 -0
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +229 -0
- package/src/execution/__tests__/worktree-reconcile.integration.test.ts +181 -0
- package/src/execution/__tests__/worktree-registry.test.ts +199 -0
- package/src/execution/agent-registry.ts +199 -0
- package/src/execution/agent-result-mapper.ts +90 -0
- package/src/execution/alive-store.ts +119 -0
- package/src/execution/argv-mirror.ts +113 -0
- package/src/execution/best-effort.ts +38 -0
- package/src/execution/channel-registry-access.ts +144 -0
- package/src/execution/concurrency-pool.ts +116 -0
- package/src/execution/config.ts +165 -0
- package/src/execution/dialog-queue.ts +330 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +78 -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/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__/model-prompt.test.ts +197 -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 +66 -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 +71 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +127 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +580 -0
- package/src/execution/engine/engines/zcode/constants.ts +43 -0
- package/src/execution/engine/engines/zcode/golden-sample.ts +39 -0
- package/src/execution/engine/engines/zcode/launcher.ts +161 -0
- package/src/execution/engine/engines/zcode/parser.ts +436 -0
- package/src/execution/engine/engines/zcode/preparer.ts +363 -0
- package/src/execution/engine/engines/zcode/reader.ts +381 -0
- package/src/execution/engine/engines/zcode/registration.ts +37 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +658 -0
- package/src/execution/engine/host-task-spec.ts +47 -0
- package/src/execution/engine/model-prompt.ts +158 -0
- package/src/execution/engine/paths.ts +42 -0
- package/src/execution/engine/port.ts +153 -0
- package/src/execution/engine/registry.ts +133 -0
- package/src/execution/engine/routing.ts +218 -0
- package/src/execution/engine/types.ts +309 -0
- package/src/execution/execute-options-mapper.ts +115 -0
- package/src/execution/execution-record.ts +984 -0
- package/src/execution/finalize-record.ts +254 -0
- package/src/execution/finalized-marker.ts +70 -0
- package/src/execution/get-state-handshake.ts +104 -0
- package/src/execution/host-mode.ts +56 -0
- package/src/execution/idle-gc.ts +47 -0
- package/src/execution/lifecycle-manager.ts +513 -0
- package/src/execution/lifecycle-predicates.ts +65 -0
- package/src/execution/manifest-store.ts +256 -0
- package/src/execution/model-config-service.ts +250 -0
- package/src/execution/model-resolver.ts +248 -0
- package/src/execution/notifier.ts +372 -0
- package/src/execution/notify-ledger.ts +580 -0
- package/src/execution/output-collector.ts +228 -0
- package/src/execution/path-encoding.ts +60 -0
- package/src/execution/pi-invocation.ts +120 -0
- package/src/execution/record-entry.ts +132 -0
- package/src/execution/record-store.ts +1265 -0
- package/src/execution/relay-env.ts +37 -0
- package/src/execution/session-context-resolver.ts +64 -0
- package/src/execution/session-file-gc.ts +120 -0
- package/src/execution/session-pending.ts +170 -0
- package/src/execution/session-reconstructor.ts +678 -0
- package/src/execution/session-runner.ts +1789 -0
- package/src/execution/sessions-index.ts +304 -0
- package/src/execution/spawn-event-adapter.ts +363 -0
- package/src/execution/stdin-writer.ts +198 -0
- package/src/execution/stream-sink.ts +126 -0
- package/src/execution/subagent-service.ts +2268 -0
- package/src/execution/subprocess-agent-runner.ts +317 -0
- package/src/execution/temp-prompt.ts +62 -0
- package/src/execution/tombstone-store.ts +72 -0
- package/src/execution/turn-limiter.ts +102 -0
- package/src/execution/types.ts +1023 -0
- package/src/execution/ui-channels.ts +216 -0
- package/src/execution/ui-interaction-model.ts +48 -0
- package/src/execution/ui-request-handler-factory.ts +243 -0
- package/src/execution/ui-request-observability.ts +81 -0
- package/src/execution/ui-request-queue.ts +184 -0
- package/src/execution/worktree-manager.ts +688 -0
- package/src/execution/worktree-registry.ts +279 -0
- package/src/index.ts +87 -0
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +349 -0
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +202 -0
- package/src/orchestration/__tests__/agent-call-stream.test.ts +152 -0
- package/src/orchestration/__tests__/args-validator.test.ts +143 -0
- package/src/orchestration/__tests__/config-loader.test.ts +503 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +809 -0
- package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +404 -0
- package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +56 -0
- package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +166 -0
- package/src/orchestration/__tests__/execute-agent-call.test.ts +451 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +600 -0
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +96 -0
- package/src/orchestration/__tests__/lifecycle.test.ts +659 -0
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +95 -0
- package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +183 -0
- package/src/orchestration/__tests__/script-lint.test.ts +831 -0
- package/src/orchestration/__tests__/skill-discovery.test.ts +210 -0
- package/src/orchestration/__tests__/test-mocks.ts +197 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +368 -0
- package/src/orchestration/__tests__/worker-host.test.ts +120 -0
- package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +164 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +563 -0
- package/src/orchestration/__tests__/worker-script-builder.test.ts +309 -0
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +129 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +317 -0
- package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
- package/src/orchestration/agent-opts-resolver.ts +174 -0
- package/src/orchestration/args-validator.ts +127 -0
- package/src/orchestration/config-loader.ts +316 -0
- package/src/orchestration/error-recovery.ts +1018 -0
- package/src/orchestration/execute-agent-call.ts +256 -0
- package/src/orchestration/launcher.ts +455 -0
- package/src/orchestration/lifecycle.ts +399 -0
- package/src/orchestration/models/__tests__/budget.test.ts +307 -0
- package/src/orchestration/models/agent-call.ts +83 -0
- package/src/orchestration/models/budget.ts +118 -0
- package/src/orchestration/models/ports.ts +164 -0
- package/src/orchestration/models/run-runtime.ts +104 -0
- package/src/orchestration/models/run-spec.ts +83 -0
- package/src/orchestration/models/run-state.ts +44 -0
- package/src/orchestration/models/trace.ts +183 -0
- package/src/orchestration/models/types.ts +301 -0
- package/src/orchestration/models/workflow-run.ts +254 -0
- package/src/orchestration/models/workflow-script-registry.ts +35 -0
- package/src/orchestration/models/workflow-script.ts +117 -0
- package/src/orchestration/script-lint.ts +766 -0
- package/src/orchestration/skill-discovery.ts +135 -0
- package/src/orchestration/worker-handle.ts +115 -0
- package/src/orchestration/worker-host.ts +98 -0
- package/src/orchestration/worker-script-builder.ts +421 -0
- package/src/orchestration/workflow-files.ts +85 -0
- package/src/orchestration/workflow-script-registry-impl.ts +133 -0
- package/src/shared/__tests__/agent-ref.test.ts +34 -0
- package/src/shared/__tests__/meta-parser.test.ts +304 -0
- package/src/shared/__tests__/model-ref.test.ts +306 -0
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +288 -0
- package/src/shared/__tests__/resource-discovery.test.ts +749 -0
- package/src/shared/__tests__/resource-meta.test.ts +51 -0
- package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
- package/src/shared/__tests__/timer-delay.test.ts +61 -0
- package/src/shared/agent-event.ts +13 -0
- package/src/shared/agent-ref.ts +57 -0
- package/src/shared/meta-parser.ts +261 -0
- package/src/shared/model-ref.ts +286 -0
- package/src/shared/resource-discovery.ts +835 -0
- package/src/shared/resource-meta.ts +65 -0
- package/src/shared/schema-env.ts +44 -0
- package/src/shared/schema-jsonify.ts +58 -0
- package/src/shared/timer-delay.ts +54 -0
- package/src/shared/xml-injection.ts +35 -0
- package/workflows/README.md +81 -0
- package/workflows/_shared/agent-refs.cjs +40 -0
- package/workflows/chain.js +137 -0
- package/workflows/map-reduce.js +180 -0
- package/workflows/parallel.js +154 -0
- package/workflows/review-fix-loop-utils.cjs +1542 -0
- package/workflows/review-fix-loop.js +1605 -0
- package/workflows/scatter-gather.js +184 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// src/execution/subprocess-agent-runner.ts
|
|
2
|
+
//
|
|
3
|
+
// Wave 4: SubprocessAgentRunner 委托重写
|
|
4
|
+
//
|
|
5
|
+
// 从"自己 spawn pi"改为"委托 SubagentService.executeAndAwait"。
|
|
6
|
+
// MF-3: 从 orchestration/infra 迁入 execution,同层委托 SubagentService。
|
|
7
|
+
//
|
|
8
|
+
// 接线层级:
|
|
9
|
+
// [跨模块 port] implements AgentRunner(orchestration/models/ports.ts)
|
|
10
|
+
// [模块内直调] mapToExecuteOptions + mergeTimeoutSignal(execute-options-mapper)
|
|
11
|
+
// [P4 路由层] engine/routing.ts routeEngine(三层优先级 + probe + fallback 三守卫,D9)
|
|
12
|
+
// [引擎层] EnginePort.run(pi = 本地 DI 绑定实例;非 pi = registry.getEngine 动态获取)
|
|
13
|
+
// [模块内直调] this.subagentService.executeAndAwait(PiEngine 内部委托目标)
|
|
14
|
+
//
|
|
15
|
+
// 设计基线:
|
|
16
|
+
// D-A2(映射归 adapter)/ D-A8(onEvent 桥接)/ D-A9(timeoutMs 合并 signal)/
|
|
17
|
+
// D-008(model 填底,不调 resolveModel)/ BC-9(timeoutMs 行为)/ BC-10(live-record 进度)
|
|
18
|
+
|
|
19
|
+
import type { AgentRunner } from "../orchestration/models/ports.ts";
|
|
20
|
+
import type { AgentCallOpts, AgentResult } from "../orchestration/models/types.ts";
|
|
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";
|
|
31
|
+
import { mapToExecuteOptions, mergeTimeoutSignal } from "./execute-options-mapper.ts";
|
|
32
|
+
import { getModelConfigService } from "./model-config-service.ts";
|
|
33
|
+
import type { ModelInfo } from "./model-resolver.ts";
|
|
34
|
+
import { modelRefFromVerified } from "../shared/model-ref";
|
|
35
|
+
import { registerSpawnedChildForRecord } from "./session-runner.ts";
|
|
36
|
+
import type { SubagentStream } from "./stream-sink.ts";
|
|
37
|
+
import type { SubagentService } from "./subagent-service.ts";
|
|
38
|
+
import type { ExecuteOptions } from "./types.ts";
|
|
39
|
+
|
|
40
|
+
// ── 构造依赖(per-session 注入)──
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* SAR 构造参数。
|
|
44
|
+
*
|
|
45
|
+
* per-session(makeDeps 时创建,随 session 销毁):
|
|
46
|
+
* - subagentService: 进程单例(getSubagentService()),委托目标
|
|
47
|
+
* - ctxModel: 当前 session 主 agent 模型(D-008 model 填底,opts.model 空时用此)
|
|
48
|
+
*
|
|
49
|
+
* resolveAgentOpts 在 orchestration 层完成(M2 修正后单参数,只处理 schema SO 指令 + skill),
|
|
50
|
+
* 结果填进 AgentCallOpts.skillPath/schemaEnv/appendSystemPrompt,SAR 收已解析的 opts。
|
|
51
|
+
* 不含 agent/session 目录/临时文件集合依赖(agent ref 交 resolveIdentity,无临时文件)。
|
|
52
|
+
*/
|
|
53
|
+
export interface SubprocessAgentRunnerDeps {
|
|
54
|
+
subagentService: SubagentService;
|
|
55
|
+
ctxModel?: ModelInfo;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* AgentRunner port 实现——委托 SubagentService.executeAndAwait。
|
|
60
|
+
*
|
|
61
|
+
* 层归属:execution(MF-3 从 orchestration 迁入)。implements orchestration 层 port。
|
|
62
|
+
*
|
|
63
|
+
* 行为契约(BC-1/BC-2/BC-9/BC-10):
|
|
64
|
+
* - opts 形状不变(AgentCallOpts,含 resolveAgentOpts 填的 skillPath/schemaEnv)
|
|
65
|
+
* - result 形状不变(workflow AgentResult: content/parsedOutput/usage/error/toolCalls)
|
|
66
|
+
* - 不 reject——失败信息入 result.error(与 executeAgentCall 契约一致)
|
|
67
|
+
* - timeoutMs 合并 signal(D-A9);onEvent 桥接 AgentEvent→workflow liveRecord(D-A8)
|
|
68
|
+
*
|
|
69
|
+
* [P1 引擎接线] 执行经 EnginePort。pi 引擎绑定本 SAR 的服务引用(per-session DI——
|
|
70
|
+
* 单测注入 mock 时全局单例不可见;生产环境两者是同一进程单例对象),行为零变化;
|
|
71
|
+
* 非 pi 引擎(P4 路由可达:frontmatter/调用参数/全局默认指定)经 registry.getEngine
|
|
72
|
+
* 动态获取——「引擎身份」的归属边界在注册表,SAR 不感知具体引擎实现。
|
|
73
|
+
*
|
|
74
|
+
* [P4 配置路由] 每次运行经 engine/routing.ts 解析三层优先级(调用参数 opts.engine >
|
|
75
|
+
* agent frontmatter engine > 全局默认 'pi')+ probe + fallback 三守卫(D9)。路由失败
|
|
76
|
+
* (engine_not_found / engine_probe_failed / model_not_available)不 reject,入
|
|
77
|
+
* result.error——与 executeAgentCall 契约一致。
|
|
78
|
+
*/
|
|
79
|
+
export class SubprocessAgentRunner implements AgentRunner {
|
|
80
|
+
private readonly subagentService: SubagentService;
|
|
81
|
+
private ctxModel: ModelInfo | undefined;
|
|
82
|
+
/** pi 引擎(per-session DI 绑定,缺省执行路径——见类注释)。 */
|
|
83
|
+
private readonly piEngine: EnginePort;
|
|
84
|
+
|
|
85
|
+
constructor(deps: SubprocessAgentRunnerDeps) {
|
|
86
|
+
this.subagentService = deps.subagentService;
|
|
87
|
+
this.ctxModel = deps.ctxModel;
|
|
88
|
+
this.piEngine = createPiEngine(() => this.subagentService);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 刷新主 agent model 缓存。model_select 事件时由 index.ts 调用。
|
|
93
|
+
* H1 修复:旧实现 ctxModel 是 readonly,session_start 后固化,
|
|
94
|
+
* model_select 只刷新 ModelConfigService._ctxModel 不更新 SAR →
|
|
95
|
+
* workflow 路径用过期模型。
|
|
96
|
+
*/
|
|
97
|
+
updateCtxModel(model: ModelInfo | undefined): void {
|
|
98
|
+
this.ctxModel = model;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 执行单次 agent 调用:路由(P4)→ EnginePort.run → PiEngine 委托
|
|
103
|
+
* SubagentService.executeAndAwait。
|
|
104
|
+
*
|
|
105
|
+
* 接线链路:
|
|
106
|
+
* routeEngine(三层 + probe + 守卫)→ mergeTimeoutSignal → mapToExecuteOptions →
|
|
107
|
+
* AgentTaskSpec → engine.run(PiEngine:spec → ExecuteOptions 还原 + engine 留痕)→
|
|
108
|
+
* executeAndAwait → AgentOutcome → AgentResult
|
|
109
|
+
*
|
|
110
|
+
* 错误处理:不 reject。
|
|
111
|
+
* - 路由失败(未注册 id / probe 失败 + 守卫或 strict)→ AgentResult.error(错误码前缀)
|
|
112
|
+
* - executeAndAwait 内部失败 → 返回 AgentResult(success:false) → 已映射 error 字段
|
|
113
|
+
* - executeAndAwait throw(嵌套超限 BC-12)→ catch → AgentResult.error
|
|
114
|
+
* - spawn 级失败已由 runSpawn 内部收口为 failed AgentResult(不逃逸)
|
|
115
|
+
*/
|
|
116
|
+
async run(
|
|
117
|
+
opts: AgentCallOpts,
|
|
118
|
+
signal: AbortSignal,
|
|
119
|
+
onEvent?: (event: AgentEvent) => void,
|
|
120
|
+
stream?: SubagentStream,
|
|
121
|
+
): Promise<AgentResult> {
|
|
122
|
+
const startedAt = Date.now();
|
|
123
|
+
|
|
124
|
+
// ── P4 路由:三层优先级 + probe + fallback 三守卫(D9①/D7)──
|
|
125
|
+
// 路由在最前——引擎身份决定 journal 路径与后续一切执行面;失败(未注册 id /
|
|
126
|
+
// probe 失败 + strict/守卫)按「不 reject」契约转 result.error。
|
|
127
|
+
// pi 快路径同步短路(不经 routeEngine 的 await):pi 恒免探、无 fallback 可言、
|
|
128
|
+
// engineFor('pi') 本地 DI 绑定恒可用——不引入微任务边界,缺省路径时序与 P1 接线
|
|
129
|
+
// 前完全一致(下游依赖「run 内首个 await 前已触达 executeAndAwait」的时序契约)。
|
|
130
|
+
let routingInput: EngineRoutingInput;
|
|
131
|
+
let route: EngineRouteResult;
|
|
132
|
+
try {
|
|
133
|
+
routingInput = this.buildRoutingInput(opts);
|
|
134
|
+
const routing = resolveEngineRouting(routingInput);
|
|
135
|
+
if (routing.engineId === "pi") {
|
|
136
|
+
route = {
|
|
137
|
+
engine: this.piEngine,
|
|
138
|
+
engineId: "pi",
|
|
139
|
+
requestedEngineId: "pi",
|
|
140
|
+
source: routing.source,
|
|
141
|
+
};
|
|
142
|
+
} else {
|
|
143
|
+
route = await routeEngine({
|
|
144
|
+
routing: routingInput,
|
|
145
|
+
taskModel: opts.model,
|
|
146
|
+
strict: getModelConfigService()?.getGlobalConfig().engineRouting?.strict === true,
|
|
147
|
+
probe: (engineId) => this.engineFor(engineId).probe(),
|
|
148
|
+
getEngineFn: (engineId) => this.engineFor(engineId),
|
|
149
|
+
// pi 经本地 DI 绑定恒可用(不依赖 registry 全局注册态——SAR 持有服务引用),
|
|
150
|
+
// has/list 注入同一口径,engine_not_found 文案不会把本地 pi 漏报成未注册
|
|
151
|
+
hasEngineFn: (engineId) => engineId === "pi" || hasEngine(engineId),
|
|
152
|
+
listEnginesFn: () => (hasEngine("pi") ? listEngines() : ["pi", ...listEngines()]),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
// buildRoutingInput 的 agent 解析期校验(未注册 frontmatter engine)与路由失败
|
|
157
|
+
// (probe + strict/守卫)一并在此收口——「不 reject」契约
|
|
158
|
+
return errorResult(err, startedAt);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── P2 event journal 接线(设计 D6 第②级;对齐点③:路径权威 = 引擎池 key)──
|
|
162
|
+
// host 在 onEvent 回调内统一落盘(全引擎免费获得②级数据源)。初始 poolKey 用 pi
|
|
163
|
+
// 缺省占位(pi 恒 'shared');非池化稳定引擎(zcode)在 prepare 期经
|
|
164
|
+
// RunContext.onPoolResolved 声明实际池 key → writer.retarget——保证 journal 落盘
|
|
165
|
+
// 路径与 handle.poolKey 同源(handle.journalPath 由本方法 run 返回后回填)。
|
|
166
|
+
//
|
|
167
|
+
// taskId 为宿主侧任务标识(journal 文件名与池引用计数 key)——executeAndAwait
|
|
168
|
+
// 不外露内部 record id(取真实 id 需 hook record store 且改 createRecordForMode
|
|
169
|
+
// 签名,影响面大;P4 决策:保留占位,`sa-` 前缀与 record id 同构。影响面:record
|
|
170
|
+
// GC 时无法按 taskId 联动删 journal(journal 依赖 30 天 TTL 自然回收),read ②级
|
|
171
|
+
// 经 handle.journalPath 自描述定位不受影响)。
|
|
172
|
+
const taskId = `sa-${crypto.randomUUID()}`;
|
|
173
|
+
const journal = new JournalWriter({
|
|
174
|
+
path: resolveJournalPath(getEngineDataDir(), route.engineId, PI_POOL_KEY, taskId),
|
|
175
|
+
taskId,
|
|
176
|
+
engineId: route.engineId,
|
|
177
|
+
});
|
|
178
|
+
const retargetJournal = (poolKey: string): void => {
|
|
179
|
+
journal.retarget(resolveJournalPath(getEngineDataDir(), route.engineId, poolKey, taskId));
|
|
180
|
+
};
|
|
181
|
+
// 包装:先写 journal 再转发原 onEvent(原 onEvent 未传时也恒传包装版——
|
|
182
|
+
// 下游 onEvent 通道是事件生成后的纯转发,无行为分支,仅多一次入队)
|
|
183
|
+
const journalingOnEvent = (event: AgentEvent): void => {
|
|
184
|
+
journal.append(event);
|
|
185
|
+
onEvent?.(event);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// ── [U1 D2] RunContext.modelRef 接入:ctxModel 继承路径的孪生守卫 ──
|
|
190
|
+
// ctxModel 是运行时已验证的 ModelInfo(豁免 registry 存在性复查),但继承产出的
|
|
191
|
+
// canonical 串与显式入参走同一个 pi pattern 引擎,孪生守卫同等适用(modelRefFromVerified)。
|
|
192
|
+
// 守卫在 engine.run 之前同步拒绝:含孪生 registry 下不产生任何 record/spawn,
|
|
193
|
+
// 失败走下方 catch → errorResult(错误文案含恢复指引)。
|
|
194
|
+
// RunContext 类型本身定义在 engine/port.ts(跨模块 port,不在本单元领地),
|
|
195
|
+
// 故接入点为构造 runCtx 前的守卫调用;pi 链路下游 resolveModel 的 ctxModel 分支
|
|
196
|
+
// 有同一守卫(model-resolver.ts),两处共用同一入口函数。
|
|
197
|
+
if (this.ctxModel) {
|
|
198
|
+
const modelService = getModelConfigService();
|
|
199
|
+
if (modelService) modelRefFromVerified(this.ctxModel, modelService.getModelRegistry());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── D-A9: timeoutMs 合并 signal(超时 abort 带 HOST_TIMEOUT_ABORT_REASON 标记)──
|
|
203
|
+
const mergedSignal = mergeTimeoutSignal(signal, opts.timeoutMs);
|
|
204
|
+
|
|
205
|
+
// ── D-A2 + D-008: AgentCallOpts → ExecuteOptions 映射 ──
|
|
206
|
+
const mappedOpts: ExecuteOptions = mapToExecuteOptions(opts, this.ctxModel);
|
|
207
|
+
|
|
208
|
+
// ── P1/P4 引擎接线:EnginePort.run ──
|
|
209
|
+
const runCtx: RunContext = {
|
|
210
|
+
taskId,
|
|
211
|
+
poolKey: PI_POOL_KEY,
|
|
212
|
+
signal: mergedSignal,
|
|
213
|
+
onEvent: journalingOnEvent,
|
|
214
|
+
ctxModel: this.ctxModel,
|
|
215
|
+
...(route.engineFallback !== undefined ? { engineFallback: route.engineFallback } : {}),
|
|
216
|
+
onPoolResolved: retargetJournal,
|
|
217
|
+
// [U0 D10] 终止链路径①:引擎 spawn 的子进程注册进 session-runner 的
|
|
218
|
+
// spawnedChildren 记账(dispose killAll 收割兜底对 workflow 域引擎任务生效);
|
|
219
|
+
// taskId('sa-' 前缀)即记账 key,与 chat 域 kickOffEngineRun 的 record.id 同构
|
|
220
|
+
onChildSpawned: (child) => registerSpawnedChildForRecord(taskId, child),
|
|
221
|
+
...(stream !== undefined ? { stream } : {}),
|
|
222
|
+
// 解耦形态(有 schemaEnv 无 schema)的兜底通道——耦合形态下引擎从 task.schema
|
|
223
|
+
// 派生等值,此值被忽略(见 RunContext.schemaEnv 注释)
|
|
224
|
+
...(mappedOpts.schemaEnv !== undefined ? { schemaEnv: mappedOpts.schemaEnv } : {}),
|
|
225
|
+
};
|
|
226
|
+
const { handle, outcome } = await route.engine.run(
|
|
227
|
+
// 泛化为中立声明(PiEngine 内部再还原回 ExecuteOptions——往返保真,
|
|
228
|
+
// 由 engines/pi/__tests__/task-spec-mapper.test.ts 逐字段锁定)
|
|
229
|
+
executeOptionsToTaskSpec(mappedOpts),
|
|
230
|
+
runCtx,
|
|
231
|
+
);
|
|
232
|
+
// handle.journalPath 回填(§3.3.6:read ②级的自描述定位符——运行期落盘路径
|
|
233
|
+
// 权威在 writer,handle 记录最终路径供跨重启 read 消费)
|
|
234
|
+
backfillJournalPath(handle, journal.path);
|
|
235
|
+
return outcomeToRunnerResult(outcome);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
// executeAndAwait throw(嵌套超限 ForkDepthExceededError,BC-12)或未预期异常 → 不 reject,入 error。
|
|
238
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
239
|
+
return {
|
|
240
|
+
content: "",
|
|
241
|
+
durationMs: Date.now() - startedAt,
|
|
242
|
+
error: message,
|
|
243
|
+
toolCalls: [],
|
|
244
|
+
};
|
|
245
|
+
} finally {
|
|
246
|
+
// run 终态后 flush + fsync 一次(§3.3.6 写入纪律);写失败已由 writer 内部
|
|
247
|
+
// warn + failed 收口,close 不抛(journal 是②级尽力而为数据源)
|
|
248
|
+
await journal.close();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── 内部 ──
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 引擎获取:pi 走 per-session DI 绑定(mock 语义 + 生产同单例,P1 行为零变化);
|
|
256
|
+
* 非 pi 经 registry.getEngine 动态获取(P4:引擎身份归属注册表,未注册 id 抛
|
|
257
|
+
* EngineNotFoundError——路由期已前置校验,这里是防御性兜底)。
|
|
258
|
+
*/
|
|
259
|
+
private engineFor(engineId: string): EnginePort {
|
|
260
|
+
if (engineId === "pi") return this.piEngine;
|
|
261
|
+
return getEngine(engineId);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 三层路由输入装配(D9):调用参数(opts.engine,workflow step 级透传)> agent
|
|
266
|
+
* frontmatter(ModelConfigService.getAgentConfig——loadByPath mtime 缓存,幂等)>
|
|
267
|
+
* 全局默认(config.json defaultEngine)。单例未就绪(session_start 前/测试 mock)
|
|
268
|
+
* 时各层缺省——落内置 'pi'。
|
|
269
|
+
*/
|
|
270
|
+
private buildRoutingInput(opts: AgentCallOpts): EngineRoutingInput {
|
|
271
|
+
const service = getModelConfigService();
|
|
272
|
+
const agentEngine =
|
|
273
|
+
opts.agent !== undefined && opts.agent !== "" ? service?.getAgentConfig(opts.agent)?.engine : undefined;
|
|
274
|
+
const globalDefault = service?.getGlobalConfig().defaultEngine;
|
|
275
|
+
return {
|
|
276
|
+
...(opts.engine !== undefined && opts.engine !== "" ? { callEngine: opts.engine } : {}),
|
|
277
|
+
...(agentEngine !== undefined && agentEngine !== "" ? { agentEngine } : {}),
|
|
278
|
+
...(globalDefault !== undefined && globalDefault !== "" ? { globalDefaultEngine: globalDefault } : {}),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** 路由期错误 → AgentResult.error(错误码前缀格式保留——engine_not_found 等)。 */
|
|
284
|
+
function errorResult(err: unknown, startedAt: number): AgentResult {
|
|
285
|
+
return {
|
|
286
|
+
content: "",
|
|
287
|
+
durationMs: Date.now() - startedAt,
|
|
288
|
+
error: err instanceof Error ? err.message : String(err),
|
|
289
|
+
toolCalls: [],
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** handle.journalPath 回填(一次写者:SAR 是 handle 的首个消费者)。 */
|
|
294
|
+
function backfillJournalPath(handle: EngineHandle, journalPath: string): void {
|
|
295
|
+
handle.data.journalPath = journalPath;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* AgentOutcome → workflow AgentResult:剥离引擎层新增字段(engineId/engineFallback/
|
|
300
|
+
* exitCode——engineFallback 经 record(pi 路径)/outcome(zcode 路径)留痕,GUI 投影
|
|
301
|
+
* 通道在后续 wave 接线,workflow 引擎不消费)。其余字段由 PiEngine 从 executeAndAwait
|
|
302
|
+
* 的返回值逐字段映射而来,字段全集完整性由 pi-engine 单测锁定(缺字段会在该处转红,
|
|
303
|
+
* 不会静默丢失)。
|
|
304
|
+
*/
|
|
305
|
+
function outcomeToRunnerResult(outcome: AgentOutcome): AgentResult {
|
|
306
|
+
return {
|
|
307
|
+
content: outcome.content,
|
|
308
|
+
parsedOutput: outcome.parsedOutput,
|
|
309
|
+
usage: outcome.usage,
|
|
310
|
+
durationMs: outcome.durationMs,
|
|
311
|
+
error: outcome.error,
|
|
312
|
+
sessionId: outcome.sessionId,
|
|
313
|
+
sessionFile: outcome.sessionFile,
|
|
314
|
+
worktreePath: outcome.worktreePath,
|
|
315
|
+
toolCalls: outcome.toolCalls,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// src/core/temp-prompt.ts
|
|
2
|
+
//
|
|
3
|
+
// 将 agent systemPrompt 写入临时文件,供 pi CLI --append-system-prompt 使用。
|
|
4
|
+
// Core 叶子原语(node 内置 + 同目录 bestEffort 清理 helper)。注意 bestEffort
|
|
5
|
+
// 经 extension-logger 有到 pi 的传递依赖(仅日志通路,无 pi 运行时调用)。
|
|
6
|
+
//
|
|
7
|
+
// pi CLI 的 --append-system-prompt 接受文件路径(非内联字符串),故 spawn 前
|
|
8
|
+
// 需把 systemPrompt 落盘。每次调用创建唯一临时目录,用完由 runSpawn 清理。
|
|
9
|
+
//
|
|
10
|
+
// 移植自 nicobailon subagent example,去掉 withFileMutationQueue(目录已唯一,
|
|
11
|
+
// 无并发写入风险,queue 冗余)。
|
|
12
|
+
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import * as os from "node:os";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
|
|
17
|
+
import { bestEffort } from "./best-effort.ts";
|
|
18
|
+
|
|
19
|
+
/** 临时 prompt 文件创建结果。dir 供调用方清理。 */
|
|
20
|
+
export interface TempPromptFile {
|
|
21
|
+
/** 临时目录绝对路径(mkdtemp 创建,调用方负责删除)。 */
|
|
22
|
+
dir: string;
|
|
23
|
+
/** prompt 文件绝对路径(dir 下)。 */
|
|
24
|
+
filePath: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 将 systemPrompt 写入临时文件。
|
|
29
|
+
*
|
|
30
|
+
* @param agentName agent 名称(用于文件名,非法字符替换为 _)
|
|
31
|
+
* @param prompt systemPrompt 全文
|
|
32
|
+
* @returns 临时文件信息(dir + filePath)
|
|
33
|
+
*
|
|
34
|
+
* 安全:文件权限 0o600(仅 owner 读写),防止其他用户读取 prompt 内容。
|
|
35
|
+
* 目录用 mkdtemp 保证唯一,无需额外锁。
|
|
36
|
+
*/
|
|
37
|
+
export async function writePromptToTempFile(
|
|
38
|
+
agentName: string,
|
|
39
|
+
prompt: string,
|
|
40
|
+
): Promise<TempPromptFile> {
|
|
41
|
+
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "pi-subagent-"));
|
|
42
|
+
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
43
|
+
const filePath = path.join(dir, `prompt-${safeName}.md`);
|
|
44
|
+
await fs.promises.writeFile(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
|
|
45
|
+
return { dir, filePath };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 删除临时 prompt 目录(含文件)。best-effort,失败不抛。
|
|
50
|
+
*
|
|
51
|
+
* runSpawn 的 finally 调用,确保临时文件不泄漏。子进程可能仍持有文件句柄
|
|
52
|
+
* (--append-system-prompt 读取后即释放),rm 用 recursive 容错。
|
|
53
|
+
*/
|
|
54
|
+
export async function cleanupTempPrompt(file: TempPromptFile): Promise<void> {
|
|
55
|
+
try {
|
|
56
|
+
await fs.promises.rm(file.dir, { recursive: true, force: true });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
// best-effort:临时文件泄漏不影响功能,OS tmpdir 清理机制兜底;失败经
|
|
59
|
+
// bestEffort 记 debug 日志(对齐 idle-gc / finalize-record 的清理惯例)
|
|
60
|
+
bestEffort(err, `cleanup temp prompt dir ${file.dir}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// src/runtime/execution/tombstone-store.ts
|
|
2
|
+
//
|
|
3
|
+
// Cancelled 状态的 sidecar 持久化。
|
|
4
|
+
//
|
|
5
|
+
// 背景:用户 cancel background subagent 时 session.abort() → prompt() reject →
|
|
6
|
+
// session.jsonl 被中途截断(无最终 assistant message,无 stopReason 标记)。所以
|
|
7
|
+
// cancelled 状态无法从 session.jsonl 可靠检测。本模块在 session.jsonl 旁写一个
|
|
8
|
+
// `.cancelled` sidecar 文件(单行 JSON),collectRecords 重建时读它 override status。
|
|
9
|
+
//
|
|
10
|
+
// 设计:单条 cancelled = 单 sidecar,无全局 index 可损坏,无并发问题,被现有
|
|
11
|
+
// session-file-gc 的目录扫描自然清理(GC 已扩展为也清理 .cancelled)。YAGNI 更重方案。
|
|
12
|
+
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
|
|
15
|
+
// ============================================================
|
|
16
|
+
// 类型
|
|
17
|
+
// ============================================================
|
|
18
|
+
|
|
19
|
+
/** Tombstone 内容(单行 JSON)。 */
|
|
20
|
+
export interface CancelledTombstone {
|
|
21
|
+
id: string;
|
|
22
|
+
status: "cancelled";
|
|
23
|
+
agent: string;
|
|
24
|
+
startedAt: number;
|
|
25
|
+
endedAt: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ============================================================
|
|
29
|
+
// 公开函数
|
|
30
|
+
// ============================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 在 session.jsonl 旁写 cancelled tombstone。
|
|
34
|
+
* best-effort:任何 I/O 错误静默(cancel 已成功,持久化是次要的——内存 record 即将被
|
|
35
|
+
* archive 淘汰,下次读靠 tombstone 标记 cancelled)。
|
|
36
|
+
*/
|
|
37
|
+
export function writeCancelledTombstone(sessionFile: string, meta: CancelledTombstone): void {
|
|
38
|
+
try {
|
|
39
|
+
const tombstonePath = `${sessionFile}.cancelled`;
|
|
40
|
+
fs.writeFileSync(tombstonePath, `${JSON.stringify(meta)}\n`, "utf-8");
|
|
41
|
+
} catch (_e) {
|
|
42
|
+
void _e; // 静默:写失败不阻断 cancel 主流程(status 已在内存 record 上设好)。
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 读 session.jsonl 旁的 cancelled tombstone。
|
|
48
|
+
* 返回 undefined:sidecar 不存在 / 损坏 / 解析失败。
|
|
49
|
+
*/
|
|
50
|
+
export function readCancelledTombstone(sessionFile: string): CancelledTombstone | undefined {
|
|
51
|
+
let raw: string;
|
|
52
|
+
try {
|
|
53
|
+
raw = fs.readFileSync(`${sessionFile}.cancelled`, "utf-8");
|
|
54
|
+
} catch {
|
|
55
|
+
return undefined; // sidecar 不存在(正常——非 cancelled record 无 sidecar)。
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(raw) as Partial<CancelledTombstone>;
|
|
59
|
+
if (
|
|
60
|
+
typeof parsed.id === "string" &&
|
|
61
|
+
parsed.status === "cancelled" &&
|
|
62
|
+
typeof parsed.agent === "string" &&
|
|
63
|
+
typeof parsed.startedAt === "number" &&
|
|
64
|
+
typeof parsed.endedAt === "number"
|
|
65
|
+
) {
|
|
66
|
+
return parsed as CancelledTombstone;
|
|
67
|
+
}
|
|
68
|
+
return undefined; // 结构不合法 → 降级。
|
|
69
|
+
} catch {
|
|
70
|
+
return undefined; // JSON 损坏 → 降级。
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/core/turn-limiter.ts
|
|
2
|
+
//
|
|
3
|
+
// soft/hard turn 限制器。maxTurns 到达 → steer 提醒收尾;
|
|
4
|
+
// graceTurns 后仍不结束 → abort。
|
|
5
|
+
|
|
6
|
+
/** steer 提醒消息:要求 agent 总结已完成/未完成/下一步,不得谎报完成。 */
|
|
7
|
+
const WRAP_UP_MESSAGE = [
|
|
8
|
+
"You have reached your turn limit. Wrap up now:",
|
|
9
|
+
"1. Summarize what you have completed (with evidence: file paths, command output).",
|
|
10
|
+
"2. List what remains undone and why.",
|
|
11
|
+
"3. State the single most important next step for whoever continues.",
|
|
12
|
+
"Do NOT claim the task is complete if any part remains unfinished.",
|
|
13
|
+
].join(" ");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 预防性 wrap-up 提示(启动时注入 --append-system-prompt)。
|
|
17
|
+
*
|
|
18
|
+
* spawn 模式下 runSpawn 无法在达 maxTurns 时运行时注入 steer(pi `--mode json` 是
|
|
19
|
+
* single-shot,无 stdin 命令通道;见 session-runner.ts limiter 注释)。作为短期补偿,
|
|
20
|
+
* 在启动时预置此预防性指令,让 agent 在感知接近上限时主动收尾。
|
|
21
|
+
*
|
|
22
|
+
* 长期方案是切到 pi `--mode rpc`(支持运行时 steer 命令),见 follow-up。
|
|
23
|
+
*/
|
|
24
|
+
export const WRAP_UP_HINT = [
|
|
25
|
+
`You have a turn limit. As you sense you are approaching it, proactively wrap up:`,
|
|
26
|
+
"1. Summarize what you have completed (with evidence: file paths, command output).",
|
|
27
|
+
"2. List what remains undone and why.",
|
|
28
|
+
"3. State the single most important next step for whoever continues.",
|
|
29
|
+
"Do NOT claim the task is complete if any part remains unfinished.",
|
|
30
|
+
].join(" ");
|
|
31
|
+
|
|
32
|
+
/** turn limiter 配置。 */
|
|
33
|
+
export interface TurnLimiterOptions {
|
|
34
|
+
maxTurns: number;
|
|
35
|
+
graceTurns: number;
|
|
36
|
+
steer: (msg: string) => void;
|
|
37
|
+
abort: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* soft/hard turn 限制。
|
|
42
|
+
*
|
|
43
|
+
* onTurnEnd(currentTurns):
|
|
44
|
+
* 已 aborted 或 maxTurns<=0(禁用)→ 直接 return
|
|
45
|
+
* currentTurns >= maxTurns 且未 steer → steer(WRAP_UP_MESSAGE)(仅一次)
|
|
46
|
+
* 已 steer 且 currentTurns >= maxTurns + graceTurns → abort()(仅一次)
|
|
47
|
+
*
|
|
48
|
+
* maxTurns<=0 表示不限(limit=Infinity,永不触发)。
|
|
49
|
+
* graceTurns<=0 时 steer 后下一 turn 即 abort。
|
|
50
|
+
*/
|
|
51
|
+
export interface TurnLimiter {
|
|
52
|
+
/** 每次 turn_end 调用。 */
|
|
53
|
+
onTurnEnd(currentTurns: number): void;
|
|
54
|
+
/**
|
|
55
|
+
* 重置 steered/aborted 标志(新一轮开始)。
|
|
56
|
+
*
|
|
57
|
+
* chatMode 下 agent_settled 时调用——每轮(message → agent_settled)独立计数,
|
|
58
|
+
* maxTurns 不跨轮累计(续聊本质是无限轮,累计上限违背 G1)。
|
|
59
|
+
* 非 chatMode 不调用,行为不变(全程累计)。
|
|
60
|
+
*/
|
|
61
|
+
reset(): void;
|
|
62
|
+
/** 是否已发过 steer(诊断用)。 */
|
|
63
|
+
readonly didSteer: boolean;
|
|
64
|
+
/** 是否已 abort(诊断用)。 */
|
|
65
|
+
readonly didAbort: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 工厂函数。 */
|
|
69
|
+
export function createTurnLimiter(opts: TurnLimiterOptions): TurnLimiter {
|
|
70
|
+
let steered = false;
|
|
71
|
+
let aborted = false;
|
|
72
|
+
const limit = opts.maxTurns > 0 ? opts.maxTurns : Infinity;
|
|
73
|
+
const grace = opts.graceTurns > 0 ? opts.graceTurns : 0;
|
|
74
|
+
|
|
75
|
+
const onTurnEnd = (turn: number): void => {
|
|
76
|
+
if (aborted || !Number.isFinite(limit)) return;
|
|
77
|
+
if (!steered && turn >= limit) {
|
|
78
|
+
steered = true;
|
|
79
|
+
opts.steer(WRAP_UP_MESSAGE);
|
|
80
|
+
}
|
|
81
|
+
if (steered && turn >= limit + grace) {
|
|
82
|
+
aborted = true;
|
|
83
|
+
opts.abort();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const reset = (): void => {
|
|
88
|
+
steered = false;
|
|
89
|
+
aborted = false;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
onTurnEnd,
|
|
94
|
+
reset,
|
|
95
|
+
get didSteer(): boolean {
|
|
96
|
+
return steered;
|
|
97
|
+
},
|
|
98
|
+
get didAbort(): boolean {
|
|
99
|
+
return aborted;
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|