@zhushanwen/subagent-core 0.2.0 → 0.5.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 +45 -2
- package/agents/analyst.md +60 -0
- package/agents/coder.md +69 -0
- package/agents/debugger.md +66 -0
- package/agents/doc-reviewer.md +49 -0
- package/agents/explorer.md +63 -0
- package/agents/general-purpose.md +32 -0
- package/agents/orchestrator.md +61 -0
- package/agents/planner.md +53 -0
- package/agents/researcher.md +67 -0
- package/agents/reviewer.md +73 -0
- package/dist/chunk-43ONBFZX.js +240 -0
- package/dist/chunk-APZY4IME.js +27 -0
- package/dist/chunk-V3VHZ2VX.js +59 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
- package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
- package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
- package/dist/execution/engine/engines/zcode/constants.js +39 -7
- package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
- package/dist/execution/engine/engines/zcode/reader.js +4 -234
- package/dist/execution/engine/paths.js +7 -19
- package/dist/index.cjs +18234 -1277
- package/dist/index.d.cts +5646 -673
- package/dist/index.d.ts +5646 -673
- package/dist/index.js +17828 -1277
- package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
- package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
- package/package.json +7 -4
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
- package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
- package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
- package/src/__tests__/m2-append-content-probe.test.ts +0 -1
- package/src/__tests__/manifest-store.test.ts +10 -9
- package/src/__tests__/record-store-cache.test.ts +0 -2
- package/src/__tests__/record-store-index.test.ts +1 -2
- package/src/__tests__/review-fix-loop-script.test.ts +93 -3
- package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
- package/src/__tests__/robustness-low-batch1.test.ts +6 -5
- package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
- package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
- package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
- package/src/__tests__/session-runner.test.ts +1 -1
- package/src/__tests__/smoke.test.ts +9 -2
- package/src/core/__tests__/host-services.test.ts +2 -2
- package/src/core/__tests__/logger.test.ts +1 -1
- package/src/core/error-message.ts +9 -0
- package/src/core/host-services.ts +21 -5
- package/src/core/notify-ports.ts +18 -5
- package/src/execution/__tests__/agent-profile.test.ts +232 -0
- package/src/execution/__tests__/agent-registry.test.ts +370 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
- package/src/execution/__tests__/agents-assembly.test.ts +219 -0
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
- package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
- package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
- package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
- package/src/execution/__tests__/delivery-methods.test.ts +77 -62
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
- package/src/execution/__tests__/dialog-queue.test.ts +199 -1
- package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
- package/src/execution/__tests__/execute-nesting.test.ts +34 -188
- package/src/execution/__tests__/execution-record.test.ts +217 -60
- package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
- package/src/execution/__tests__/finalize-record.test.ts +278 -16
- package/src/execution/__tests__/gc-timer.test.ts +57 -2
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
- package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
- package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
- package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
- package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
- package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
- package/src/execution/__tests__/model-resolver.test.ts +2 -2
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
- package/src/execution/__tests__/nested-visibility.test.ts +2 -0
- package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
- package/src/execution/__tests__/output-collector.test.ts +117 -6
- package/src/execution/__tests__/pi-invocation.test.ts +1 -1
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
- package/src/execution/__tests__/record-store.test.ts +89 -5
- package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
- package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
- package/src/execution/__tests__/rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
- package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
- package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
- package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
- package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
- package/src/execution/__tests__/session-file-gc.test.ts +35 -0
- package/src/execution/__tests__/session-pending.test.ts +471 -0
- package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
- package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
- package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
- package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
- package/src/execution/__tests__/spawn-args.test.ts +11 -10
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
- package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
- package/src/execution/__tests__/spawned-children.test.ts +155 -11
- package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
- package/src/execution/__tests__/stdin-writer.test.ts +6 -4
- package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
- package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
- package/src/execution/__tests__/subagent-service.test.ts +37 -30
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
- package/src/execution/__tests__/temp-prompt.test.ts +1 -1
- package/src/execution/__tests__/timeout-integration.test.ts +13 -12
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
- package/src/execution/__tests__/turn-limiter.test.ts +1 -1
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
- package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
- package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
- package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
- package/src/execution/__tests__/worktree-manager.test.ts +14 -5
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
- package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
- package/src/execution/agent-registry.ts +298 -30
- package/src/execution/agent-result-mapper.ts +3 -0
- package/src/execution/agents-assembly.ts +88 -0
- package/src/execution/cold-resurrect.ts +199 -0
- package/src/execution/concurrency-pool.ts +71 -9
- package/src/execution/dialog-queue.ts +101 -3
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
- package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
- package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
- package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
- package/src/execution/engine/__tests__/registry.test.ts +94 -3
- package/src/execution/engine/__tests__/routing.test.ts +115 -6
- package/src/execution/engine/common/capability-gate.ts +92 -0
- package/src/execution/engine/common/journal-wiring.ts +97 -0
- package/src/execution/engine/common/kill-chain.ts +42 -10
- package/src/execution/engine/common/nesting-guard.ts +66 -0
- package/src/execution/engine/common/persona-router.ts +18 -8
- package/src/execution/engine/common/pool-manager.ts +332 -78
- package/src/execution/engine/common/session-view-service.ts +506 -0
- package/src/execution/engine/common/session-view-types.ts +125 -0
- package/src/execution/engine/engine-discovery.ts +12 -16
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
- package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
- package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
- package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
- package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
- package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
- package/src/execution/engine/engines/pi/reader.ts +11 -0
- package/src/execution/engine/engines/pi/registration.ts +4 -2
- package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
- package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
- package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
- package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
- package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
- package/src/execution/engine/engines/zcode/connection.ts +598 -0
- package/src/execution/engine/engines/zcode/constants.ts +147 -14
- package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
- package/src/execution/engine/engines/zcode/parser.ts +12 -346
- package/src/execution/engine/engines/zcode/preparer.ts +22 -150
- package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
- package/src/execution/engine/host-task-spec.ts +32 -36
- package/src/execution/engine/model-validation.ts +177 -0
- package/src/execution/engine/port.ts +65 -7
- package/src/execution/engine/registry.ts +53 -0
- package/src/execution/engine/routing.ts +75 -1
- package/src/execution/engine/types.ts +30 -103
- package/src/execution/execution-record.ts +222 -144
- package/src/execution/finalize-record.ts +181 -85
- package/src/execution/idle-gc.ts +28 -0
- package/src/execution/lifecycle-manager.ts +27 -3
- package/src/execution/lifecycle-predicates.ts +1 -1
- package/src/execution/manifest-store.ts +53 -62
- package/src/execution/notifier.ts +10 -10
- package/src/execution/notify-host.ts +212 -0
- package/src/execution/notify-ledger.ts +117 -16
- package/src/execution/record-entry.ts +8 -2
- package/src/execution/record-store.ts +148 -88
- package/src/execution/round-settlement.ts +93 -0
- package/src/execution/session-file-gc.ts +96 -62
- package/src/execution/session-pending.ts +213 -62
- package/src/execution/session-reconstructor.ts +256 -143
- package/src/execution/sessions-index.ts +127 -102
- package/src/execution/settled-watchdog.ts +311 -0
- package/src/execution/subagent-actions-core.ts +686 -0
- package/src/execution/subagent-service.ts +1106 -907
- package/src/execution/subprocess-agent-runner.ts +144 -97
- package/src/execution/types.ts +9 -0
- package/src/execution/ui-request-handler-factory.ts +126 -75
- package/src/execution/ui-request-queue.ts +50 -19
- package/src/execution/worktree-git-ops.ts +397 -0
- package/src/execution/worktree-manager.ts +72 -10
- package/src/execution/worktree-registry.ts +6 -12
- package/src/index.ts +542 -9
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
- package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
- package/src/orchestration/__tests__/args-meta.test.ts +358 -0
- package/src/orchestration/__tests__/args-validator.test.ts +14 -0
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
- package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
- package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
- package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
- package/src/orchestration/__tests__/script-generate.test.ts +314 -0
- package/src/orchestration/__tests__/script-lint.test.ts +17 -0
- package/src/orchestration/__tests__/test-mocks.ts +2 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
- package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
- package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
- package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
- package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
- package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
- package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
- package/src/orchestration/agent-opts-resolver.ts +8 -8
- package/src/orchestration/args-meta.ts +198 -0
- package/src/orchestration/args-validator.ts +53 -31
- package/src/orchestration/execute-agent-call.ts +25 -79
- package/src/orchestration/file-run-store.ts +327 -0
- package/src/orchestration/launcher.ts +160 -104
- package/src/orchestration/lifecycle.ts +371 -97
- package/src/orchestration/models/agent-call.ts +7 -7
- package/src/orchestration/models/budget.ts +5 -5
- package/src/orchestration/models/ports.ts +4 -4
- package/src/orchestration/models/run-runtime.ts +15 -15
- package/src/orchestration/models/trace.ts +9 -9
- package/src/orchestration/models/types.ts +111 -22
- package/src/orchestration/models/workflow-run.ts +28 -28
- package/src/orchestration/models/workflow-script.ts +4 -4
- package/src/orchestration/run-snapshot.ts +266 -0
- package/src/orchestration/script-generate.ts +154 -0
- package/src/orchestration/script-lint.ts +123 -90
- package/src/orchestration/worker-handle.ts +10 -10
- package/src/orchestration/worker-host.ts +10 -10
- package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
- package/src/orchestration/worker-script-builder.ts +401 -346
- package/src/orchestration/workflow-files.ts +37 -11
- package/src/orchestration/workflow-run-summary.ts +69 -0
- package/src/orchestration/workflow-script-registry-impl.ts +31 -9
- package/src/shared/__tests__/agent-ref.test.ts +209 -2
- package/src/shared/__tests__/atomic-write.test.ts +267 -0
- package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
- package/src/shared/__tests__/injection-render.test.ts +518 -0
- package/src/shared/__tests__/meta-parser.test.ts +151 -0
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
- package/src/shared/__tests__/resource-discovery.test.ts +261 -217
- package/src/shared/agent-ref.ts +143 -2
- package/src/shared/atomic-write.ts +323 -0
- package/src/shared/bounded-serialize.ts +154 -0
- package/src/shared/injection-render.ts +279 -0
- package/src/shared/meta-parser.ts +182 -68
- package/src/shared/model-ref.ts +15 -2
- package/src/shared/resource-discovery.ts +97 -204
- package/src/shared/resource-meta.ts +14 -0
- package/src/shared/schema-jsonify.ts +1 -1
- package/src/shared/xml-injection.ts +9 -9
- package/workflows/README.md +9 -9
- package/workflows/chain.js +4 -2
- package/workflows/map-reduce.js +5 -3
- package/workflows/parallel.js +5 -3
- package/workflows/review-fix-loop-utils.cjs +165 -92
- package/workflows/review-fix-loop-utils.d.cts +287 -0
- package/workflows/review-fix-loop.js +18 -7
- package/workflows/scatter-gather.js +4 -2
- package/dist/chunk-3VOERJPJ.js +0 -22
- package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/execute-options-mapper.ts +0 -115
- package/src/execution/session-runner.ts +0 -1789
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
// src/execution/engine/engines/zcode/connection.ts
|
|
2
|
+
//
|
|
3
|
+
// ZcodeEngine app-server 常驻连接层(R2)。设计权威源:
|
|
4
|
+
// docs/design/zcode-engine-appserver-resident.md §3.2 方案 A(单常驻进程 + per-session
|
|
5
|
+
// model)/ §3.3 D1(惰性启动、进程退出即失效下次使用重建、probe 用独立短命连接)/
|
|
6
|
+
// D9(反向请求常量表应答 + 未知一律回空 result)/ 附录 A.1(传输与帧型——协议权威)/
|
|
7
|
+
// A.3(错误码表)。旧实现参照:zsw 仓 84b63a0^ lib/runner-appserver.js 的
|
|
8
|
+
// AppServerConnection + createFrameDispatcher(协议断言逐字级保留,TS 重写)。
|
|
9
|
+
//
|
|
10
|
+
// 协议事实(A.1,全部来自旧实现实测 + 本机 0.16.5 二进制验证):
|
|
11
|
+
// - stdio NDJSON(每行一个 JSON),非标准 JSON-RPC:**不带 jsonrpc 字段**,
|
|
12
|
+
// 我们发出的帧必须精确键集(对面 zod strict 拒未知键);入站帧宽容解析
|
|
13
|
+
// (未知键不拒收——strict 校验是对面的职责,我们是客户端)。
|
|
14
|
+
// - 四帧型:客户端请求 {id, method, params} / 应答 {id, result} 或
|
|
15
|
+
// {id, error:{code,message,data}} / 服务端推送 {method, params}(无 id)/
|
|
16
|
+
// 服务端反向请求 {id, method, params}(必须应答,不答 15s 超时断连,旧实测 -32022)。
|
|
17
|
+
// - 首帧可能是 {protocol:{name,version}} 自报(或 {method:'protocol'} 推送形态),
|
|
18
|
+
// 记入 protocolInfo 后忽略,不抛。
|
|
19
|
+
//
|
|
20
|
+
// 与 R4 的接线边界(本模块刻意保持的解耦):
|
|
21
|
+
// - 启动参数矩阵(D10 的 --stdio/--surface)不在此层——D10 已定案:基线 argv 恒
|
|
22
|
+
// `node <cliPath> app-server --cwd <dir>`,不带 --stdio/--surface(三态矩阵补验
|
|
23
|
+
// 留跨仓真机段);
|
|
24
|
+
// - env 全量从构造参数注入(buildAppServerEnv 是沿用 launcher 惯例的便捷组装器,
|
|
25
|
+
// R4 也可复用 launcher 的 buildZcodeEnv 自行组装后传入——本模块不 import launcher);
|
|
26
|
+
// - stderr tee 路径参数化(引擎数据目录由 R4 注入;测试用 tmp 目录);
|
|
27
|
+
// - dispose 完整编排(close 全部会话 → 杀链)归 R4 引擎层——本模块只提供连接自身
|
|
28
|
+
// 的关闭原语(close 同步 SIGTERM 面 / shutdown 完整杀链面,均幂等;close 后调
|
|
29
|
+
// shutdown 等待同一杀链完成再 resolve——resolve 于进程退出的契约不因入口顺序而破)。
|
|
30
|
+
//
|
|
31
|
+
// 重建语义(D1 + §3.4 不变量 4):进程死亡(崩溃 / 我方 shutdown / spawn 失败)→
|
|
32
|
+
// 当前代全部 pending reject(错误附 stderr 尾 400 字符)→ child 置空 → 下次
|
|
33
|
+
// request() 走同一 ensureStarted 路径重起新代。「崩溃重建」「dispose 后重建」
|
|
34
|
+
// 「惰性启动」三条路径在实现上同一条代码路径。
|
|
35
|
+
|
|
36
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
37
|
+
import * as fs from "node:fs";
|
|
38
|
+
import { dirname } from "node:path";
|
|
39
|
+
|
|
40
|
+
import { getLogger } from "../../../../core/logger.ts";
|
|
41
|
+
|
|
42
|
+
import { killChain } from "../../common/kill-chain.ts";
|
|
43
|
+
import { buildNestedSpawnEnv } from "../../common/nesting-guard.ts";
|
|
44
|
+
import {
|
|
45
|
+
ZCODE_APPSERVER_REQUEST_TIMEOUT_MS,
|
|
46
|
+
ZCODE_APPSERVER_STDERR_TAIL_BUFFER_CHARS,
|
|
47
|
+
ZCODE_APPSERVER_STDERR_TAIL_CHARS,
|
|
48
|
+
ZCODE_KILL_GRACE_MS,
|
|
49
|
+
} from "./constants.ts";
|
|
50
|
+
import { toErrorMessage } from "../../../../core/error-message.ts";
|
|
51
|
+
|
|
52
|
+
const logger = getLogger("subagents");
|
|
53
|
+
|
|
54
|
+
/** 坏帧/长帧进日志的截断长度(够诊断、不刷屏——ZCODE_ERROR_TAIL_CHARS 同精神)。 */
|
|
55
|
+
const RAW_FRAME_LOG_CHARS = 200;
|
|
56
|
+
|
|
57
|
+
// ============================================================
|
|
58
|
+
// D9 常量表应答
|
|
59
|
+
// ============================================================
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* session/requestRuntimePreferences 的应答:逐字段来自旧实现实测(zsw 仓
|
|
63
|
+
* runner-appserver.js RUNTIME_PREFERENCES,2026-08-23 真机抓包),改动前先有新实测依据。
|
|
64
|
+
*/
|
|
65
|
+
export const RUNTIME_PREFERENCES = Object.freeze({
|
|
66
|
+
nativeSearchEnhancementsEnabled: true,
|
|
67
|
+
memoryEnabled: false,
|
|
68
|
+
askUserQuestionAutoResolutionEnabled: true,
|
|
69
|
+
modelContextBudgetStrategy: "preflight-v1",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 连接级反向请求 handler 表(D9):本表之外的反向请求(permission/elicitation 等)
|
|
74
|
+
* 一律回空 result 并 warn 留痕——不答会 15s 超时断连(旧实测 -32022),拖死共享
|
|
75
|
+
* 连接上的全部会话。
|
|
76
|
+
*/
|
|
77
|
+
const DEFAULT_REVERSE_HANDLERS: Readonly<Record<string, (params: unknown) => unknown>> = {
|
|
78
|
+
"session/requestRuntimePreferences": () => RUNTIME_PREFERENCES,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// ============================================================
|
|
82
|
+
// 帧类型(宽容解析:入站未知键不拒收,出站精确键集)
|
|
83
|
+
// ============================================================
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* app-server RPC 错误(应答帧 error 形态挂到 reject Error 上)。code 语义见设计
|
|
87
|
+
* 附录 A.3:-32601/-32602 漂移类、-32004/-32010 会话类、-32603 内部错误(含
|
|
88
|
+
* "Model config is missing")。R5 降级链按 code 归类。
|
|
89
|
+
*/
|
|
90
|
+
export interface AppServerRpcError extends Error {
|
|
91
|
+
code?: number;
|
|
92
|
+
data?: unknown;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 运行时判别(消费方 R3/R5 归类错误用,避免裸 as 断言)。 */
|
|
96
|
+
export function isAppServerRpcError(err: unknown): err is AppServerRpcError {
|
|
97
|
+
return err instanceof Error && typeof (err as AppServerRpcError).code === "number";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
101
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** 帧 id 的合法形态(客户端请求 id 恒数字;服务端反向请求 id 实测字符串)。 */
|
|
105
|
+
function frameIdOf(frame: Record<string, unknown>): string | number | undefined {
|
|
106
|
+
const id = frame.id;
|
|
107
|
+
return typeof id === "string" || typeof id === "number" ? id : undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 错误/日志出声用的 message 提取(非 Error 值不抛二次异常)。 */
|
|
111
|
+
function errMessage(err: unknown): string {
|
|
112
|
+
return toErrorMessage(err);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ============================================================
|
|
116
|
+
// env 组装(沿用 launcher 惯例;完整 env 也可由调用方自行构造注入)
|
|
117
|
+
// ============================================================
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 组装 app-server 子进程 env:嵌套防护经公共 nesting-guard(注入统一
|
|
121
|
+
* XYZ_AGENT_SUBAGENT=1 + 剥离引擎原生嵌套标记),遥测关闭(旧实现实证)。
|
|
122
|
+
* 2026-09 起共享宿主 HOME——不再覆写 HOME:db/plugins/MCP 继承宿主 ~/.zcode/
|
|
123
|
+
* (引擎会话与 GUI 共写同一 SQLite,WAL 并发安全),HOME 依赖副作用(如 pnpm
|
|
124
|
+
* store 路径翻转)随之消失。凭据不在此层注入:CLI 形态 app-server 只从
|
|
125
|
+
* ~/.zcode/cli/config.json 读 provider(GUI 登录态在 v2/config.json),由
|
|
126
|
+
* launcherScript 指向的 fs 拦截 wrapper 把该读取重定向为「真实文件 + v2
|
|
127
|
+
* provider 注入」内存合并(同 id 时 v2 整条优先——见 appserver-launcher.ts)。
|
|
128
|
+
*/
|
|
129
|
+
export function buildAppServerEnv(baseEnv: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
130
|
+
return {
|
|
131
|
+
...buildNestedSpawnEnv(baseEnv),
|
|
132
|
+
ZCODE_MODEL_TELEMETRY_ENABLED: "false",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ============================================================
|
|
137
|
+
// 连接
|
|
138
|
+
// ============================================================
|
|
139
|
+
|
|
140
|
+
/** AppServerConnection 构造参数(依赖全量注入:cliPath 到 env 全部来自调用方)。 */
|
|
141
|
+
export interface AppServerConnectionOptions {
|
|
142
|
+
/** zcode CLI 路径(node 脚本,如 zcode.cjs)。 */
|
|
143
|
+
cliPath: string;
|
|
144
|
+
/** app-server 进程 --cwd 参数值(CLI flag,不是 OS 级 spawn cwd——与旧实现一致)。 */
|
|
145
|
+
cwd: string;
|
|
146
|
+
/** 完整子进程 env(buildAppServerEnv 产物或调用方自行组装)。 */
|
|
147
|
+
env: NodeJS.ProcessEnv;
|
|
148
|
+
/**
|
|
149
|
+
* fs 拦截 wrapper 脚本路径(appserver-launcher.ts 落盘产物)。设置时 spawn 的
|
|
150
|
+
* script 换为本路径(cliPath 经 env ZCODE_ENG_CLI_PATH 传给 wrapper)——共享宿主
|
|
151
|
+
* HOME 形态的凭据供数机制;缺省 undefined = 直接 spawn cliPath(测试 fake 直连)。
|
|
152
|
+
*/
|
|
153
|
+
launcherScript?: string;
|
|
154
|
+
/**
|
|
155
|
+
* stderr tee 落盘路径(append;引擎数据目录由 R4 注入,测试用 tmp 目录)。
|
|
156
|
+
* 懒打开:首条 stderr 才建文件,无 stderr 的短命连接零文件;写失败静默降级
|
|
157
|
+
* (取证面不能拖垮主通道)。
|
|
158
|
+
*/
|
|
159
|
+
stderrLogPath: string;
|
|
160
|
+
/** node 二进制(缺省 'node' 走 PATH)。 */
|
|
161
|
+
nodeBin?: string;
|
|
162
|
+
/** 控制面请求默认超时(缺省 ZCODE_APPSERVER_REQUEST_TIMEOUT_MS)。 */
|
|
163
|
+
requestTimeoutMs?: number;
|
|
164
|
+
/** 反向请求 handler 表(缺省 D9 常量表;测试注入故障 handler 用)。 */
|
|
165
|
+
reverseHandlers?: Readonly<Record<string, (params: unknown) => unknown>>;
|
|
166
|
+
/**
|
|
167
|
+
* [R4] 每代进程 spawn 成功后的同步回调。pidfile 机制删除后引擎侧不再传——现为
|
|
168
|
+
* 测试专有的代级观测面(采集 spawn 时序 / 子进程句柄)。与 RunContext.onChildSpawned
|
|
169
|
+
* 无关:常驻进程不进宿主 spawnedChildren(D6——其生命周期归引擎 dispose)。
|
|
170
|
+
* 不抛保证:回调异常吞掉记 warn,不影响帧泵。
|
|
171
|
+
*/
|
|
172
|
+
onSpawned?: (child: ChildProcess) => void;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** request() 的单次可选项。 */
|
|
176
|
+
export interface AppServerRequestOptions {
|
|
177
|
+
/** 本请求超时预算(缺省用连接级默认值)。 */
|
|
178
|
+
timeoutMs?: number;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** 在途请求登记项。 */
|
|
182
|
+
interface PendingEntry {
|
|
183
|
+
resolve: (value: unknown) => void;
|
|
184
|
+
reject: (err: Error) => void;
|
|
185
|
+
timer: NodeJS.Timeout;
|
|
186
|
+
method: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 长驻 app-server 连接:NDJSON 帧分发 + 请求 id 关联 + 反向请求应答 + 崩溃收割。
|
|
191
|
+
*
|
|
192
|
+
* 生命周期:构造零成本(不 spawn);首个 request() 惰性启动;进程死亡收割当前代
|
|
193
|
+
* (全部 pending reject)后下次使用自动重建。推送事件经 onNotification(method)
|
|
194
|
+
* 按 method 订阅(R3 会话层消费 session/event 与 v4/telemetry/event)。
|
|
195
|
+
*/
|
|
196
|
+
export class AppServerConnection {
|
|
197
|
+
private readonly cliPath: string;
|
|
198
|
+
private readonly cwd: string;
|
|
199
|
+
private readonly env: NodeJS.ProcessEnv;
|
|
200
|
+
private readonly stderrLogPath: string;
|
|
201
|
+
private readonly nodeBin: string;
|
|
202
|
+
private readonly requestTimeoutMs: number;
|
|
203
|
+
private readonly reverseHandlers: Readonly<Record<string, (params: unknown) => unknown>>;
|
|
204
|
+
private readonly onSpawned: ((child: ChildProcess) => void) | undefined;
|
|
205
|
+
private readonly launcherScript: string | undefined;
|
|
206
|
+
|
|
207
|
+
/** 当前代子进程;null = 无活进程(未启动或已死,下次使用重建)。 */
|
|
208
|
+
private child: ChildProcess | null = null;
|
|
209
|
+
private pending = new Map<number, PendingEntry>();
|
|
210
|
+
/** 请求 id 自增序(每代重置——新进程无历史 pending,id 空间互不冲突)。 */
|
|
211
|
+
private reqSeq = 0;
|
|
212
|
+
/** stderr 滚动缓冲(代级:崩溃诊断用)。 */
|
|
213
|
+
private stderrTail = "";
|
|
214
|
+
private stderrStream: fs.WriteStream | null = null;
|
|
215
|
+
private stderrStreamFailed = false;
|
|
216
|
+
/**
|
|
217
|
+
* 本代我方杀链 promise(close/shutdown 共用)。入口顺序不破「shutdown resolve 于
|
|
218
|
+
* 进程退出」契约:close 先行发起时 shutdown await 同一 promise 而非直接 return。
|
|
219
|
+
* killChain 永不 reject(内部 raceTimeout 吞 reject)——close 的 fire-and-forget
|
|
220
|
+
* 无 unhandled rejection 面。
|
|
221
|
+
*/
|
|
222
|
+
private killChainPromise: Promise<"terminated" | "killed"> | undefined;
|
|
223
|
+
private generation = 0;
|
|
224
|
+
private readonly pushHandlers = new Map<string, Set<(params: unknown) => void>>();
|
|
225
|
+
/** [R4] 连接级崩溃通知面(onClose 订阅者集合;与 pushHandlers 同构的 refCount 形态)。 */
|
|
226
|
+
private readonly closeHandlers = new Set<(reason: string) => void>();
|
|
227
|
+
private capturedProtocolInfo: Readonly<Record<string, unknown>> | null = null;
|
|
228
|
+
|
|
229
|
+
constructor(opts: AppServerConnectionOptions) {
|
|
230
|
+
this.cliPath = opts.cliPath;
|
|
231
|
+
this.launcherScript = opts.launcherScript;
|
|
232
|
+
this.cwd = opts.cwd;
|
|
233
|
+
this.env = opts.env;
|
|
234
|
+
this.stderrLogPath = opts.stderrLogPath;
|
|
235
|
+
this.nodeBin = opts.nodeBin ?? "node";
|
|
236
|
+
this.requestTimeoutMs = opts.requestTimeoutMs ?? ZCODE_APPSERVER_REQUEST_TIMEOUT_MS;
|
|
237
|
+
this.reverseHandlers = opts.reverseHandlers ?? DEFAULT_REVERSE_HANDLERS;
|
|
238
|
+
this.onSpawned = opts.onSpawned;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** 当前代进程 pid(未启动/已死为 undefined)。 */
|
|
242
|
+
get pid(): number | undefined {
|
|
243
|
+
return this.child?.pid;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** 当前代是否有活进程(关闭原语与重建测试的观测面)。 */
|
|
247
|
+
get alive(): boolean {
|
|
248
|
+
return this.child !== null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** 协议自报信息(A.1 首帧 {protocol:{...}} 或 {method:'protocol'} 推送形态;未收到为 null)。 */
|
|
252
|
+
get protocolInfo(): Readonly<Record<string, unknown>> | null {
|
|
253
|
+
return this.capturedProtocolInfo;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 订阅服务端推送(按 method)。handler 异常由本模块吞掉并 warn(不拖死帧泵)。
|
|
258
|
+
*
|
|
259
|
+
* @returns 退订函数
|
|
260
|
+
*/
|
|
261
|
+
onNotification(method: string, handler: (params: unknown) => void): () => void {
|
|
262
|
+
let set = this.pushHandlers.get(method);
|
|
263
|
+
if (!set) {
|
|
264
|
+
set = new Set();
|
|
265
|
+
this.pushHandlers.set(method, set);
|
|
266
|
+
}
|
|
267
|
+
set.add(handler);
|
|
268
|
+
return () => {
|
|
269
|
+
set?.delete(handler);
|
|
270
|
+
if (set && set.size === 0) this.pushHandlers.delete(method);
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* [R4] 订阅连接崩溃/关闭通知:当前代进程退出(崩溃、spawn 失败、我方杀链收尾)时
|
|
276
|
+
* 回调一次(reason 含 stderr 尾部——失败路径 2 的错误素材)。时序保证:在途
|
|
277
|
+
* pending 全部 reject **之后**触发(订阅方可安全假定「在途请求已死」)。仅在
|
|
278
|
+
* 「有进程死亡」时触发——从未启动过的连接不通知。
|
|
279
|
+
*
|
|
280
|
+
* @returns 退订函数
|
|
281
|
+
*/
|
|
282
|
+
onClose(handler: (reason: string) => void): () => void {
|
|
283
|
+
this.closeHandlers.add(handler);
|
|
284
|
+
return () => {
|
|
285
|
+
this.closeHandlers.delete(handler);
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* [R4] 发一个客户端请求帧但不等待应答(fire-and-forget)。dispose 编排用:
|
|
291
|
+
* 停机时 session/close 帧「发出即算」不阻塞在应答上(对面进程可能正被并发收割,
|
|
292
|
+
* 等应答会让 dispose 挂到请求超时)。不登记 pending——迟到应答按无匹配 id 忽略。
|
|
293
|
+
*/
|
|
294
|
+
post(method: string, params?: unknown): boolean {
|
|
295
|
+
this.ensureStarted();
|
|
296
|
+
return this.writeFrame({ id: ++this.reqSeq, method, params });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 发一个客户端请求并等应答。惰性启动(首次调用 spawn);error 应答帧转 reject
|
|
301
|
+
* (code/data 挂到 Error 上,-32602 的 zod 诊断随 data 带出便于逆向 schema)。
|
|
302
|
+
* 出站帧精确键集 {id, method, params}——无 jsonrpc 字段(A.1)。
|
|
303
|
+
*/
|
|
304
|
+
request<T = unknown>(method: string, params?: unknown, opts?: AppServerRequestOptions): Promise<T> {
|
|
305
|
+
this.ensureStarted();
|
|
306
|
+
if (this.child === null) {
|
|
307
|
+
return Promise.reject(new Error("app-server 连接不可用(进程未启动或已退出)"));
|
|
308
|
+
}
|
|
309
|
+
const timeoutMs = opts?.timeoutMs ?? this.requestTimeoutMs;
|
|
310
|
+
const id = ++this.reqSeq;
|
|
311
|
+
return new Promise<T>((resolve, reject) => {
|
|
312
|
+
const timer = setTimeout(() => {
|
|
313
|
+
this.pending.delete(id);
|
|
314
|
+
reject(new Error(`请求 ${method} 超时(${timeoutMs}ms)`));
|
|
315
|
+
}, timeoutMs);
|
|
316
|
+
this.pending.set(id, {
|
|
317
|
+
// 泛型 T 是调用方对线上 unknown 结果的视图声明,连接层不做运行时校验
|
|
318
|
+
// (协议层保持透明,结构校验归会话层/消费方)。
|
|
319
|
+
resolve: resolve as (value: unknown) => void,
|
|
320
|
+
reject,
|
|
321
|
+
timer,
|
|
322
|
+
method,
|
|
323
|
+
});
|
|
324
|
+
if (!this.writeFrame({ id, method, params })) {
|
|
325
|
+
// 同步写失败(进程将死):立即失败该请求,不等超时兜底
|
|
326
|
+
this.pending.delete(id);
|
|
327
|
+
clearTimeout(timer);
|
|
328
|
+
reject(new Error(`请求 ${method} 写入失败(app-server 进程不可写)`));
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* 同步关闭面(R4 dispose 编排的原语):立即发 SIGTERM(同步系统调用,返回前信号
|
|
335
|
+
* 已发出),grace→SIGKILL 升级由杀链 promise 跟进(fire-and-forget)。幂等;对未启动
|
|
336
|
+
* 连接是零成本 no-op。close 之后调 shutdown 会 await 同一杀链再 resolve(见
|
|
337
|
+
* killChainPromise 字段注释)。
|
|
338
|
+
*/
|
|
339
|
+
close(): void {
|
|
340
|
+
const child = this.child;
|
|
341
|
+
if (child === null || this.killChainPromise !== undefined) return;
|
|
342
|
+
this.killChainPromise = killChain(child, { graceMs: ZCODE_KILL_GRACE_MS });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* 异步完整关闭面:SIGTERM → grace → SIGKILL 杀链走完(含收尸),resolve 于进程
|
|
347
|
+
* 退出。幂等:与 close 共享同一杀链 promise——close 先行时 await 它再 resolve,
|
|
348
|
+
* 不另起杀链(graceMs 取首次发起值)。之后首个 request() 自动重建(与崩溃重建
|
|
349
|
+
* 同路径,§3.4 不变量 4)。
|
|
350
|
+
*/
|
|
351
|
+
async shutdown(opts?: { graceMs?: number }): Promise<void> {
|
|
352
|
+
if (this.killChainPromise === undefined) {
|
|
353
|
+
const child = this.child;
|
|
354
|
+
if (child === null) return;
|
|
355
|
+
this.killChainPromise = killChain(child, { graceMs: opts?.graceMs ?? ZCODE_KILL_GRACE_MS });
|
|
356
|
+
}
|
|
357
|
+
await this.killChainPromise;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ============================================================
|
|
361
|
+
// 进程生命周期(代管理)
|
|
362
|
+
// ============================================================
|
|
363
|
+
|
|
364
|
+
private ensureStarted(): void {
|
|
365
|
+
if (this.child !== null) return;
|
|
366
|
+
this.spawnGeneration();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private spawnGeneration(): void {
|
|
370
|
+
// 每代全量重置(崩溃/关闭/spawn 失败后的重建同走本方法)
|
|
371
|
+
this.pending = new Map();
|
|
372
|
+
this.reqSeq = 0;
|
|
373
|
+
this.stderrTail = "";
|
|
374
|
+
this.killChainPromise = undefined;
|
|
375
|
+
this.generation += 1;
|
|
376
|
+
const gen = this.generation;
|
|
377
|
+
|
|
378
|
+
const script = this.launcherScript ?? this.cliPath;
|
|
379
|
+
const child = spawn(this.nodeBin, [script, "app-server", "--cwd", this.cwd], {
|
|
380
|
+
env: this.env,
|
|
381
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
382
|
+
});
|
|
383
|
+
this.child = child;
|
|
384
|
+
if (this.onSpawned !== undefined) {
|
|
385
|
+
try {
|
|
386
|
+
this.onSpawned(child);
|
|
387
|
+
} catch (err) {
|
|
388
|
+
logger.warn(`onSpawned 回调异常(忽略): ${errMessage(err)}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let finalized = false;
|
|
393
|
+
// 收割当前代:关闭 tee 流、child 置空(触发下次重建)、全部 pending reject。
|
|
394
|
+
// 双守卫:finalized 防同代 close/error 双触发;generation 比对防迟到的上一代
|
|
395
|
+
// 事件误杀新代(正常时序不可达,防御性保留)。
|
|
396
|
+
const finalize = (reason: string): void => {
|
|
397
|
+
if (finalized || this.generation !== gen) return;
|
|
398
|
+
finalized = true;
|
|
399
|
+
this.closeStderrStream();
|
|
400
|
+
this.child = null;
|
|
401
|
+
const err = new Error(`app-server ${reason}`);
|
|
402
|
+
for (const entry of this.pending.values()) {
|
|
403
|
+
clearTimeout(entry.timer);
|
|
404
|
+
entry.reject(err);
|
|
405
|
+
}
|
|
406
|
+
this.pending.clear();
|
|
407
|
+
// pending 全部 reject 后才通知崩溃订阅方(onClose 契约:触发时在途已死)
|
|
408
|
+
for (const fn of [...this.closeHandlers]) {
|
|
409
|
+
try {
|
|
410
|
+
fn(reason);
|
|
411
|
+
} catch (err) {
|
|
412
|
+
logger.warn(`close handler 异常: ${errMessage(err)}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
child.stdout.setEncoding("utf8");
|
|
418
|
+
let buffer = "";
|
|
419
|
+
child.stdout.on("data", (chunk: string) => {
|
|
420
|
+
buffer += chunk;
|
|
421
|
+
let nl = buffer.indexOf("\n");
|
|
422
|
+
while (nl >= 0) {
|
|
423
|
+
const line = buffer.slice(0, nl);
|
|
424
|
+
buffer = buffer.slice(nl + 1);
|
|
425
|
+
this.handleLine(line);
|
|
426
|
+
nl = buffer.indexOf("\n");
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
child.stderr.setEncoding("utf8");
|
|
431
|
+
child.stderr.on("data", (chunk: string) => {
|
|
432
|
+
this.stderrTail = (this.stderrTail + chunk).slice(-ZCODE_APPSERVER_STDERR_TAIL_BUFFER_CHARS);
|
|
433
|
+
this.appendStderrLog(chunk);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// 子进程死后的残留写会以 EPIPE 冒泡到 stdin 流,不吞会让宿主进程崩
|
|
437
|
+
child.stdin?.on("error", () => {});
|
|
438
|
+
|
|
439
|
+
child.once("close", (code, signal) => {
|
|
440
|
+
finalize(
|
|
441
|
+
`进程退出(code=${code} signal=${signal ?? "none"})stderr 尾部: ${this.stderrTail.slice(-ZCODE_APPSERVER_STDERR_TAIL_CHARS)}`,
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
child.once("error", (err) => finalize(`spawn 失败: ${err.message}`));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ============================================================
|
|
448
|
+
// 帧分发(A.1 四帧型;宽容解析,坏行跳过不断流)
|
|
449
|
+
// ============================================================
|
|
450
|
+
|
|
451
|
+
private handleLine(line: string): void {
|
|
452
|
+
const text = line.trim();
|
|
453
|
+
if (!text) return;
|
|
454
|
+
let frame: unknown;
|
|
455
|
+
try {
|
|
456
|
+
frame = JSON.parse(text);
|
|
457
|
+
} catch {
|
|
458
|
+
logger.warn(`无法解析的协议行(忽略): ${text.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
this.handleFrame(frame, text);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private handleFrame(frame: unknown, rawText: string): void {
|
|
465
|
+
if (!isRecord(frame)) {
|
|
466
|
+
logger.warn(`非对象协议帧(忽略): ${rawText.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (isRecord(frame.protocol)) this.capturedProtocolInfo = frame.protocol;
|
|
470
|
+
|
|
471
|
+
// 反向请求 / 推送(有 method)
|
|
472
|
+
if (typeof frame.method === "string") {
|
|
473
|
+
const reverseId = frameIdOf(frame);
|
|
474
|
+
if (reverseId !== undefined) {
|
|
475
|
+
this.answerReverse(reverseId, frame.method, frame.params);
|
|
476
|
+
} else {
|
|
477
|
+
this.dispatchPush(frame.method, frame.params);
|
|
478
|
+
}
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// 应答(有 id 无 method)
|
|
483
|
+
if (frame.id !== undefined && frame.id !== null) {
|
|
484
|
+
if (typeof frame.id !== "number") {
|
|
485
|
+
logger.warn(`应答 id 非数字(忽略): ${rawText.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
this.settlePending(frame.id, frame);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// protocol 自报首帧(无 id 无 method):已消费,不是坏帧
|
|
493
|
+
if (isRecord(frame.protocol)) return;
|
|
494
|
+
logger.warn(`无法归类的协议帧(忽略): ${rawText.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private settlePending(id: number, frame: Record<string, unknown>): void {
|
|
498
|
+
const entry = this.pending.get(id);
|
|
499
|
+
if (!entry) {
|
|
500
|
+
logger.warn(`响应无匹配请求 id=${id}(忽略)`);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
this.pending.delete(id);
|
|
504
|
+
clearTimeout(entry.timer);
|
|
505
|
+
if (frame.error !== undefined) {
|
|
506
|
+
const e = isRecord(frame.error) ? frame.error : {};
|
|
507
|
+
const code = typeof e.code === "number" ? e.code : undefined;
|
|
508
|
+
const message = typeof e.message === "string" ? e.message : JSON.stringify(e.message ?? e);
|
|
509
|
+
const dataSlice =
|
|
510
|
+
e.data === undefined ? "" : `;data: ${JSON.stringify(e.data).slice(0, ZCODE_APPSERVER_STDERR_TAIL_CHARS)}`;
|
|
511
|
+
const err = new Error(`${entry.method} 失败: [${code ?? "?"}] ${message}${dataSlice}`) as AppServerRpcError;
|
|
512
|
+
err.code = code;
|
|
513
|
+
err.data = e.data;
|
|
514
|
+
entry.reject(err);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
entry.resolve(frame.result);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private dispatchPush(method: string, params: unknown): void {
|
|
521
|
+
// 协议自报的推送形态({method:'protocol', params:{...}})与首帧形态收敛到同一字段
|
|
522
|
+
if (method === "protocol") this.capturedProtocolInfo = isRecord(params) ? params : null;
|
|
523
|
+
const set = this.pushHandlers.get(method);
|
|
524
|
+
if (!set) return;
|
|
525
|
+
for (const fn of set) {
|
|
526
|
+
try {
|
|
527
|
+
fn(params);
|
|
528
|
+
} catch (err) {
|
|
529
|
+
logger.warn(`push handler 异常(${method}): ${errMessage(err)}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ============================================================
|
|
535
|
+
// 反向请求应答(D9)
|
|
536
|
+
// ============================================================
|
|
537
|
+
|
|
538
|
+
private answerReverse(id: string | number, method: string, params: unknown): void {
|
|
539
|
+
const handler = this.reverseHandlers[method];
|
|
540
|
+
if (!handler) {
|
|
541
|
+
logger.warn(`未知反向请求 ${method}(id=${id}):回空 result(不答会 15s 超时断连,旧实测 -32022)`);
|
|
542
|
+
this.writeFrame({ id, result: {} });
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
// fire-and-forget:handler 失败回 error 帧,绝不拖死连接
|
|
546
|
+
Promise.resolve()
|
|
547
|
+
.then(() => handler(params))
|
|
548
|
+
.then((result) => this.writeFrame({ id, result: result ?? {} }))
|
|
549
|
+
.catch((err: unknown) => {
|
|
550
|
+
logger.warn(`反向请求 ${method} handler 异常: ${errMessage(err)}`);
|
|
551
|
+
this.writeFrame({ id, error: { code: -32000, message: errMessage(err) } });
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ============================================================
|
|
556
|
+
// 写帧 / stderr tee
|
|
557
|
+
// ============================================================
|
|
558
|
+
|
|
559
|
+
/** 写一帧(JSON + 换行)。返回 false = 进程不可写(调用方决定如何失败)。 */
|
|
560
|
+
private writeFrame(frame: unknown): boolean {
|
|
561
|
+
const child = this.child;
|
|
562
|
+
if (child === null || child.stdin === null) return false;
|
|
563
|
+
try {
|
|
564
|
+
child.stdin.write(`${JSON.stringify(frame)}\n`);
|
|
565
|
+
return true;
|
|
566
|
+
} catch (err) {
|
|
567
|
+
logger.warn(`写入 app-server 失败: ${errMessage(err)}`);
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** stderr 实时 append 落盘(懒打开;失败静默——取证面不能拖垮主通道)。 */
|
|
573
|
+
private appendStderrLog(chunk: string): void {
|
|
574
|
+
if (this.stderrStreamFailed) return;
|
|
575
|
+
if (this.stderrStream === null) {
|
|
576
|
+
try {
|
|
577
|
+
fs.mkdirSync(dirname(this.stderrLogPath), { recursive: true });
|
|
578
|
+
this.stderrStream = fs.createWriteStream(this.stderrLogPath, { flags: "a" });
|
|
579
|
+
this.stderrStream.on("error", () => {
|
|
580
|
+
this.stderrStreamFailed = true;
|
|
581
|
+
});
|
|
582
|
+
} catch {
|
|
583
|
+
this.stderrStreamFailed = true;
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
this.stderrStream.write(chunk);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** 代收尾时关闭 tee 流(下一代懒重开,同路径 append——崩溃重建集中同一文件)。 */
|
|
591
|
+
private closeStderrStream(): void {
|
|
592
|
+
if (this.stderrStream !== null) {
|
|
593
|
+
const stream = this.stderrStream;
|
|
594
|
+
this.stderrStream = null;
|
|
595
|
+
stream.end();
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|