@zhushanwen/subagent-core 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
- package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
- package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
- package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
- package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
- package/dist/execution/engine/engines/zcode/constants.js +17 -27
- 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 +2 -2
- package/dist/index.cjs +17100 -14082
- package/dist/index.d.cts +5336 -4033
- package/dist/index.d.ts +5336 -4033
- package/dist/index.js +17020 -14055
- package/dist/{types-DpUO16pj.d.cts → types-C3fE3R9x.d.cts} +280 -112
- package/dist/{types-DpUO16pj.d.ts → types-C3fE3R9x.d.ts} +280 -112
- package/dist.bundle/index.cjs +34933 -0
- package/package.json +3 -3
- 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 +4 -2
- package/src/__tests__/review-fix-loop-utils.test.ts +10 -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/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 +4 -4
- 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 +2 -2
- package/src/execution/__tests__/alive-store.test.ts +1 -1
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
- package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
- package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
- package/src/execution/__tests__/collect-budget.test.ts +291 -0
- package/src/execution/__tests__/collect-coordinator-service.test.ts +327 -0
- package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
- package/src/execution/__tests__/collect-mixed-dispatch.test.ts +262 -0
- package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
- package/src/execution/__tests__/config.test.ts +1 -1
- package/src/execution/__tests__/delivery-methods.test.ts +75 -62
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
- package/src/execution/__tests__/dialog-queue.test.ts +2 -2
- package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +29 -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 +5 -5
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +4 -4
- package/src/execution/__tests__/finalize-record.test.ts +173 -17
- package/src/execution/__tests__/finalized-marker.test.ts +1 -1
- package/src/execution/__tests__/gc-timer.test.ts +56 -3
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
- package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
- 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 +50 -148
- package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
- 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 +1 -1
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
- 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-batch.test.ts +500 -0
- package/src/execution/__tests__/notify-ledger.test.ts +1000 -18
- package/src/execution/__tests__/output-collector.test.ts +117 -6
- package/src/execution/__tests__/pi-invocation.test.ts +36 -4
- package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
- package/src/execution/__tests__/record-store.test.ts +10 -9
- 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__/relay-env.test.ts +12 -1
- 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 +15 -9
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
- package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
- package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
- 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 +4 -4
- package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
- package/src/execution/__tests__/session-file-gc.test.ts +36 -1
- package/src/execution/__tests__/session-pending.test.ts +257 -14
- package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
- package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
- package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
- 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 +282 -47
- 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 +3 -3
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
- package/src/execution/__tests__/spawned-children.test.ts +2 -1
- package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
- package/src/execution/__tests__/start-sync-model-guard.test.ts +5 -5
- 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 +48 -22
- package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +21 -8
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
- 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__/sync-collect-recovery.test.ts +1117 -0
- package/src/execution/__tests__/temp-prompt.test.ts +1 -1
- package/src/execution/__tests__/timeout-integration.test.ts +6 -5
- package/src/execution/__tests__/tombstone-store.test.ts +1 -1
- 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 +12 -6
- package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
- package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
- package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
- 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 +2 -2
- package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
- package/src/execution/__tests__/worktree-registry.test.ts +1 -1
- package/src/execution/agent-registry.ts +74 -30
- package/src/execution/agent-result-mapper.ts +3 -0
- package/src/execution/cold-resurrect.ts +199 -0
- package/src/execution/collect-coordinator.ts +200 -0
- package/src/execution/config.ts +77 -5
- 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/event-journal.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 +213 -53
- 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 +22 -112
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +2 -2
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
- 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 +4 -2
- 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/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} +1 -1
- 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/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
- 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 +54 -27
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
- 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 +20 -8
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +26 -320
- 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 +48 -148
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
- package/src/execution/engine/engines/zcode/connection.ts +24 -11
- package/src/execution/engine/engines/zcode/constants.ts +75 -71
- package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
- package/src/execution/engine/engines/zcode/parser.ts +12 -346
- package/src/execution/engine/engines/zcode/preparer.ts +12 -143
- package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
- package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
- 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 +24 -6
- package/src/execution/engine/routing.ts +75 -1
- package/src/execution/engine/types.ts +30 -103
- package/src/execution/execution-record.ts +229 -144
- package/src/execution/finalize-record.ts +141 -99
- package/src/execution/idle-gc.ts +28 -0
- package/src/execution/lifecycle-predicates.ts +1 -1
- package/src/execution/notifier.ts +234 -25
- package/src/execution/notify-host.ts +232 -0
- package/src/execution/record-entry.ts +16 -0
- package/src/execution/record-store.ts +280 -103
- package/src/execution/round-settlement.ts +93 -0
- package/src/execution/session-file-gc.ts +96 -62
- package/src/execution/session-reconstructor.ts +256 -143
- package/src/execution/sessions-index.ts +117 -47
- package/src/execution/settled-watchdog.ts +264 -52
- package/src/execution/subagent-actions-core.ts +84 -28
- package/src/execution/subagent-service.ts +1225 -964
- package/src/execution/subprocess-agent-runner.ts +144 -97
- package/src/execution/sync-rebuild.ts +87 -0
- package/src/execution/types.ts +72 -0
- package/src/execution/ui-request-handler-factory.ts +102 -74
- package/src/execution/ui-request-queue.ts +50 -19
- package/src/index.ts +442 -316
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
- package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
- package/src/orchestration/__tests__/args-validator.test.ts +14 -0
- package/src/orchestration/__tests__/config-loader.test.ts +1 -1
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
- package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
- package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
- package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
- package/src/orchestration/__tests__/script-generate.test.ts +5 -5
- package/src/orchestration/__tests__/script-lint.test.ts +17 -0
- package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
- 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} +27 -19
- 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 +1 -1
- package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
- package/src/orchestration/agent-opts-resolver.ts +1 -1
- package/src/orchestration/args-validator.ts +53 -31
- package/src/orchestration/execute-agent-call.ts +19 -73
- package/src/orchestration/launcher.ts +128 -81
- package/src/orchestration/lifecycle.ts +43 -45
- package/src/orchestration/models/ports.ts +4 -4
- package/src/orchestration/models/run-runtime.ts +2 -2
- package/src/orchestration/models/trace.ts +1 -1
- package/src/orchestration/models/types.ts +111 -22
- package/src/orchestration/models/workflow-run.ts +1 -1
- package/src/orchestration/script-lint.ts +91 -58
- package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
- package/src/shared/__tests__/atomic-write.test.ts +1 -1
- package/src/shared/__tests__/meta-parser.test.ts +151 -0
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
- package/src/shared/__tests__/resource-discovery.test.ts +258 -215
- package/src/shared/atomic-write.ts +12 -9
- package/src/shared/meta-parser.ts +158 -84
- package/src/shared/model-ref.ts +15 -2
- package/src/shared/resource-discovery.ts +9 -183
- package/src/shared/schema-jsonify.ts +1 -1
- package/workflows/review-fix-loop-utils.cjs +151 -89
- package/workflows/review-fix-loop-utils.d.cts +287 -0
- 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__/preparer-appserver.test.ts +0 -387
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
- package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
- package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/execute-options-mapper.ts +0 -115
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
|
@@ -1,442 +0,0 @@
|
|
|
1
|
-
// src/execution/engine/engines/zcode/appserver-home.ts
|
|
2
|
-
//
|
|
3
|
-
// [R4 D7] app-server 常驻 HOME 管理(设计 zcode-engine-appserver-resident.md §3.3
|
|
4
|
-
// D7 全量 + D6③ pidfile 孤儿自愈)。从 preparer.ts 拆出(单一关注点:常驻 HOME 的
|
|
5
|
-
// 所有权与引导;preparer.ts 保留 spawn 池语义):
|
|
6
|
-
// - 常驻 HOME = resolvePoolDir(engineDataDir,'zcode','home-appserver')(poolKey
|
|
7
|
-
// 固定名,锚定不变量 poolDir==HOME==db 所在目录——SQLite 落 HOME/.zcode/cli/db/
|
|
8
|
-
// db.sqlite,journal 同落该池目录);
|
|
9
|
-
// - config.json 写入全部带 apiKey 的 provider(allProviders 引导——per-session
|
|
10
|
-
// model 经 create 参数传递,不走 config.main 串池);
|
|
11
|
-
// - 凭据刷新按「provider 注册表内容 hash」比对(model.main 不参与——任务间换模型
|
|
12
|
-
// 不触发连接重建);
|
|
13
|
-
// - 目录锁(O_EXCL + 持锁宿主 pid + 心跳 mtime;活持有判定 = pid 活即持有,心跳
|
|
14
|
-
// 不参与否决)与 pidfile 孤儿回收(三重判据)。
|
|
15
|
-
//
|
|
16
|
-
// 依赖方向:本模块 import preparer 的源 config 读取件与结构化错误(同目录平级,无环)。
|
|
17
|
-
|
|
18
|
-
import { execFile } from "node:child_process";
|
|
19
|
-
import * as crypto from "node:crypto";
|
|
20
|
-
import * as fs from "node:fs";
|
|
21
|
-
import * as path from "node:path";
|
|
22
|
-
|
|
23
|
-
import { getLogger } from "../../../../core/logger.ts";
|
|
24
|
-
|
|
25
|
-
import { resolvePoolDir } from "../../paths.ts";
|
|
26
|
-
import { writeAtomicFileSync } from "../../../../shared/atomic-write.ts";
|
|
27
|
-
import {
|
|
28
|
-
ZCODE_APPSERVER_LOCKFILE_NAME,
|
|
29
|
-
ZCODE_APPSERVER_LOCK_HEARTBEAT_MS,
|
|
30
|
-
ZCODE_APPSERVER_MAX_DERIVED_HOMES,
|
|
31
|
-
ZCODE_APPSERVER_PIDFILE_GRACE_MS,
|
|
32
|
-
ZCODE_APPSERVER_PIDFILE_NAME,
|
|
33
|
-
ZCODE_APPSERVER_POOL_KEY,
|
|
34
|
-
ZCODE_ENGINE_ID,
|
|
35
|
-
ZCODE_POOL_CONFIG_SUFFIX,
|
|
36
|
-
} from "./constants.ts";
|
|
37
|
-
import {
|
|
38
|
-
CONFIG_INDENT_SPACES,
|
|
39
|
-
ZcodePrepareError,
|
|
40
|
-
defaultV2ConfigPath,
|
|
41
|
-
hasApiKey,
|
|
42
|
-
isProviderEntry,
|
|
43
|
-
isRecord,
|
|
44
|
-
readSourceConfig,
|
|
45
|
-
type ZcodeProviderEntry,
|
|
46
|
-
type ZcodeSourcePaths,
|
|
47
|
-
} from "./preparer.ts";
|
|
48
|
-
|
|
49
|
-
// core log facade(engines/zcode 目录惯例:模块顶层缓存,best-effort 清理路径的
|
|
50
|
-
// 最小留痕——见 taste/no-silent-catch 的「至少记录」要求)
|
|
51
|
-
const logger = getLogger("subagents");
|
|
52
|
-
|
|
53
|
-
/** 孤儿回收的退出轮询间隔(ms)。 */
|
|
54
|
-
const PID_REAP_POLL_INTERVAL_MS = 50;
|
|
55
|
-
|
|
56
|
-
/** 错误/日志出声用的 message 提取(非 Error 值不抛二次异常)。 */
|
|
57
|
-
function errMessage(err: unknown): string {
|
|
58
|
-
return err instanceof Error ? err.message : String(err);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ============================================================
|
|
62
|
-
// [R4 D7] app-server 常驻 HOME:allProviders 引导 + 内容 hash 刷新
|
|
63
|
-
// ============================================================
|
|
64
|
-
|
|
65
|
-
/** v2 config 内全部带 apiKey 的 provider(appserver HOME 的引导面——allProviders 方案)。 */
|
|
66
|
-
function providersWithKey(sources?: ZcodeSourcePaths): Map<string, ZcodeProviderEntry> {
|
|
67
|
-
const v2 = readSourceConfig(sources?.v2ConfigPath ?? defaultV2ConfigPath());
|
|
68
|
-
return new Map([...v2.providers.entries()].filter(([, e]) => hasApiKey(e)));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* provider 注册表的规范化内容 hash(D7 凭据刷新判据)。只覆盖 provider 数据——
|
|
73
|
-
* model.main 刻意排除:任务间换模型是常态(per-session model),若计入 hash 会在
|
|
74
|
-
* 每次模型切换时误判「凭据变化」杀掉常驻进程,冷启动收益归零。provider id 排序后
|
|
75
|
-
* 序列化,防 v2 config 键序变化产生假差异。
|
|
76
|
-
*/
|
|
77
|
-
export function hashProviderRegistry(providers: Map<string, ZcodeProviderEntry>): string {
|
|
78
|
-
const canonical: Array<[string, unknown]> = [...providers.keys()].sort().map((id) => [id, providers.get(id)]);
|
|
79
|
-
return crypto.createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** 常驻 HOME 内 config.json 的 provider 段 hash(读不出/损坏/无 provider 段 = undefined——恒判需重写)。 */
|
|
83
|
-
export function hashPoolConfigProviders(configPath: string): string | undefined {
|
|
84
|
-
let parsed: unknown;
|
|
85
|
-
try {
|
|
86
|
-
parsed = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
87
|
-
} catch {
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
if (!isRecord(parsed) || !isRecord(parsed["provider"])) return undefined;
|
|
91
|
-
const providers = new Map<string, ZcodeProviderEntry>();
|
|
92
|
-
for (const [id, entry] of Object.entries(parsed["provider"])) {
|
|
93
|
-
if (isProviderEntry(entry)) providers.set(id, entry);
|
|
94
|
-
}
|
|
95
|
-
return hashProviderRegistry(providers);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export interface AppServerConfigBootstrap {
|
|
99
|
-
configPath: string;
|
|
100
|
-
/** 本次是否实际重写(内容 hash 命中时 false——连接不重建,零开销)。 */
|
|
101
|
-
wroteConfig: boolean;
|
|
102
|
-
/** 引导后的 provider 注册表 hash(凭据刷新比对的落盘基准)。 */
|
|
103
|
-
providerHash: string;
|
|
104
|
-
/** 写入的 provider id 清单(诊断/断言面)。 */
|
|
105
|
-
providerIds: string[];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* 引导 app-server 常驻 HOME 的 config(D7①):写入全部带 apiKey 的 provider
|
|
110
|
-
* (进程启动即要求 $HOME/.zcode/cli/config.json 有模型配置——缺失则 create 恒
|
|
111
|
-
* -32603)。重写判据 = provider 注册表内容 hash 与池内现状不一致(内容基准取代
|
|
112
|
-
* spawn 池的 mtime 基准:常驻 HOME 的 config 可能被本进程早前任务写就,mtime 比对
|
|
113
|
-
* 无法识别「内容回退」形态)。model.main 一并落(create 未带 model 的兜底),但
|
|
114
|
-
* 不参与 hash(见 hashProviderRegistry)。
|
|
115
|
-
*/
|
|
116
|
-
export function bootstrapAppServerConfig(opts: {
|
|
117
|
-
homeDir: string;
|
|
118
|
-
modelRef: string;
|
|
119
|
-
sources?: ZcodeSourcePaths;
|
|
120
|
-
}): AppServerConfigBootstrap {
|
|
121
|
-
const providers = providersWithKey(opts.sources);
|
|
122
|
-
if (providers.size === 0) {
|
|
123
|
-
throw new ZcodePrepareError(
|
|
124
|
-
"engine_credential_missing",
|
|
125
|
-
`zcode 常驻 HOME 引导失败:v2 config 无任何带 apiKey 的 provider(${opts.sources?.v2ConfigPath ?? defaultV2ConfigPath()})。` +
|
|
126
|
-
`恢复指引:先在 ZCode 桌面端登录并配置 provider 后重试。`,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
const configPath = path.join(opts.homeDir, ...ZCODE_POOL_CONFIG_SUFFIX);
|
|
130
|
-
const providerHash = hashProviderRegistry(providers);
|
|
131
|
-
let wroteConfig = false;
|
|
132
|
-
if (hashPoolConfigProviders(configPath) !== providerHash) {
|
|
133
|
-
const providerObj: Record<string, unknown> = {};
|
|
134
|
-
for (const [id, entry] of providers) providerObj[id] = entry;
|
|
135
|
-
const payload = JSON.stringify({ model: { main: opts.modelRef }, provider: providerObj }, null, CONFIG_INDENT_SPACES);
|
|
136
|
-
// tmp+rename 原子写(shared/atomic-write 统一原语,U6b 迁移)——跨进程并发下
|
|
137
|
-
// 读者永远看到完整文件;spawn 池同款纪律(ensureDir 对齐旧显式 mkdirSync)
|
|
138
|
-
writeAtomicFileSync(configPath, payload);
|
|
139
|
-
wroteConfig = true;
|
|
140
|
-
}
|
|
141
|
-
return { configPath, wroteConfig, providerHash, providerIds: [...providers.keys()] };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ============================================================
|
|
145
|
-
// [R4 D7] 目录锁(lockfile:O_EXCL + 持锁宿主 pid + 心跳 mtime)
|
|
146
|
-
// ============================================================
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* pid 活性探测(信号 0 探针)。EPERM = 进程存在但非本方所有(仍活);
|
|
150
|
-
* ESRCH = 已死。pid 归属纪律:本函数只用于 lockfile.pid(宿主进程)与 pidfile.pid
|
|
151
|
-
* (app-server 进程)的活性判据,两文件的语义互不混用(D7 pid 归属分离钉死)。
|
|
152
|
-
*/
|
|
153
|
-
export function isPidAlive(pid: number): boolean {
|
|
154
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
155
|
-
try {
|
|
156
|
-
process.kill(pid, 0);
|
|
157
|
-
return true;
|
|
158
|
-
} catch (err) {
|
|
159
|
-
return (err as NodeJS.ErrnoException).code === "EPERM";
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** lockfile 内容(持锁宿主进程 pid + 获取时间;心跳只刷 mtime 不改内容)。 */
|
|
164
|
-
interface LockFileContent {
|
|
165
|
-
pid: number;
|
|
166
|
-
acquiredAt: number;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** O_EXCL 创建 lockfile(已存在返回 false——互斥判据即 open 'wx' 的原子性)。 */
|
|
170
|
-
function tryCreateLock(lockPath: string): boolean {
|
|
171
|
-
try {
|
|
172
|
-
const fd = fs.openSync(lockPath, "wx");
|
|
173
|
-
try {
|
|
174
|
-
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, acquiredAt: Date.now() } satisfies LockFileContent));
|
|
175
|
-
} finally {
|
|
176
|
-
fs.closeSync(fd);
|
|
177
|
-
}
|
|
178
|
-
return true;
|
|
179
|
-
} catch (err) {
|
|
180
|
-
if ((err as NodeJS.ErrnoException).code === "EEXIST") return false;
|
|
181
|
-
throw err;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/** 读 lockfile 的持锁 pid(损坏/缺失 = undefined——按无主处理走接管)。 */
|
|
186
|
-
function readLockPid(lockPath: string): number | undefined {
|
|
187
|
-
try {
|
|
188
|
-
const parsed: unknown = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
189
|
-
if (isRecord(parsed) && typeof parsed["pid"] === "number") return parsed["pid"];
|
|
190
|
-
return undefined;
|
|
191
|
-
} catch {
|
|
192
|
-
return undefined;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** 本进程是否仍持有该锁(引擎「已持有则跳过重复获取」的判据——自持锁对判定循环表现为活持有,会误派生)。 */
|
|
197
|
-
export function isLockHeldByUs(lockPath: string): boolean {
|
|
198
|
-
return readLockPid(lockPath) === process.pid;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export interface AppServerHomeLockHooks {
|
|
202
|
-
/**
|
|
203
|
-
* 接管方删旧锁之后、O_EXCL 重建之前的窗口回调(测试注入——双接管者竞争闭环的
|
|
204
|
-
* 确定性构造点:窗口内他方先行建锁,本方 O_EXCL 必失败、重走判定循环读到活 pid
|
|
205
|
-
* → 派生)。生产不传。
|
|
206
|
-
*/
|
|
207
|
-
afterTakeoverUnlink?: (lockPath: string) => void;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export interface AppServerHomeLock {
|
|
211
|
-
/** 实际落定的池 key(固定名或派生后缀名——handle.poolKey 数据源)。 */
|
|
212
|
-
poolKey: string;
|
|
213
|
-
homeDir: string;
|
|
214
|
-
lockPath: string;
|
|
215
|
-
/** 是否经「锁无主接管」获得(D6③:仅接管路径触发 pidfile 孤儿回收)。 */
|
|
216
|
-
tookOver: boolean;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* 常驻 HOME 目录锁判定(D7 所有权隔离)。「活持有」判定钉死:lockfile.pid 活 ⇒
|
|
221
|
-
* 一律视为持有(新实例派生后缀目录 home-appserver-2…作自己的 HOME)——心跳 mtime
|
|
222
|
-
* **不参与活持有否决**(桌面睡眠/长 GC 致心跳过期时误判死 → 偷锁双写同一 SQLite,
|
|
223
|
-
* 不可接受),仅 pid 已死时用于锁破坏加速的观测。宿主死 ⇒ 锁无主 ⇒ 接管方接管:
|
|
224
|
-
* 删旧锁 + O_EXCL 重建;O_EXCL 失败 = 他方已先行接管,失败方重走判定循环(读到
|
|
225
|
-
* 对方活 pid → 派生)。
|
|
226
|
-
*/
|
|
227
|
-
export function acquireAppServerHomeLock(
|
|
228
|
-
engineDataDir: string,
|
|
229
|
-
opts: { pidAlive?: (pid: number) => boolean; hooks?: AppServerHomeLockHooks } = {},
|
|
230
|
-
): AppServerHomeLock {
|
|
231
|
-
const pidAlive = opts.pidAlive ?? isPidAlive;
|
|
232
|
-
for (let n = 1; n <= ZCODE_APPSERVER_MAX_DERIVED_HOMES; n++) {
|
|
233
|
-
const name = n === 1 ? ZCODE_APPSERVER_POOL_KEY : `${ZCODE_APPSERVER_POOL_KEY}-${n}`;
|
|
234
|
-
const homeDir = resolvePoolDir(engineDataDir, ZCODE_ENGINE_ID, name);
|
|
235
|
-
fs.mkdirSync(homeDir, { recursive: true });
|
|
236
|
-
const lockPath = path.join(homeDir, ZCODE_APPSERVER_LOCKFILE_NAME);
|
|
237
|
-
let tookOver = false;
|
|
238
|
-
for (;;) {
|
|
239
|
-
if (tryCreateLock(lockPath)) return { poolKey: name, homeDir, lockPath, tookOver };
|
|
240
|
-
const holder = readLockPid(lockPath);
|
|
241
|
-
if (holder !== undefined && pidAlive(holder)) {
|
|
242
|
-
// 活持有(心跳 mtime 不参与否决):派生下一个目录,不碰他人 HOME/pidfile
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
// 锁无主(持锁宿主已死)/损坏残留:接管 = 删旧锁 + O_EXCL 重建
|
|
246
|
-
tookOver = true;
|
|
247
|
-
try {
|
|
248
|
-
fs.unlinkSync(lockPath);
|
|
249
|
-
} catch (err) {
|
|
250
|
-
// 恰被并发清理/消失——继续 O_EXCL 争夺即可
|
|
251
|
-
logger.debug(`[zcode-preparer] 接管删锁失败(${lockPath},继续争夺): ${errMessage(err)}`);
|
|
252
|
-
}
|
|
253
|
-
opts.hooks?.afterTakeoverUnlink?.(lockPath);
|
|
254
|
-
// 循环重走:O_EXCL 成功 = 接管完成;失败(EEXIST)= 他方先行——重读 pid,
|
|
255
|
-
// 活 pid → break 派生;仍无主 → 再度接管争夺
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
throw new Error(
|
|
259
|
-
`[engine_run_failed] zcode 常驻 HOME 目录锁竞争超限(${ZCODE_APPSERVER_MAX_DERIVED_HOMES} 个派生目录全被活宿主持有)。` +
|
|
260
|
-
`恢复指引:清理 ${resolvePoolDir(engineDataDir, ZCODE_ENGINE_ID, ZCODE_APPSERVER_POOL_KEY)} 下无主 lockfile 后重试。`,
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* 目录锁心跳(D7):定期刷新 lockfile mtime。语义边界(钉死):心跳只作观测面
|
|
266
|
-
* (pid 已死时区分「崩溃残留 vs 活持有」的加速信息),绝不参与活持有否决。
|
|
267
|
-
*
|
|
268
|
-
* @returns 停跳函数(引擎 dispose 语义下通常无需停——锁随宿主进程存活,进程退出
|
|
269
|
-
* 即无主;unref 保证不阻塞进程退出)
|
|
270
|
-
*/
|
|
271
|
-
export function startLockHeartbeat(lockPath: string): () => void {
|
|
272
|
-
const timer = setInterval(() => {
|
|
273
|
-
try {
|
|
274
|
-
const now = new Date();
|
|
275
|
-
fs.utimesSync(lockPath, now, now);
|
|
276
|
-
} catch (err) {
|
|
277
|
-
// 锁文件被接管方清理等场景:心跳停跳即可——debug 留痕,不外溢
|
|
278
|
-
logger.debug(`[zcode-preparer] 锁心跳 touch 失败(${lockPath},停跳): ${errMessage(err)}`);
|
|
279
|
-
}
|
|
280
|
-
}, ZCODE_APPSERVER_LOCK_HEARTBEAT_MS);
|
|
281
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
282
|
-
return () => clearInterval(timer);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ============================================================
|
|
286
|
-
// [R4 D6③] pidfile 孤儿自愈(三重判据回收)
|
|
287
|
-
// ============================================================
|
|
288
|
-
|
|
289
|
-
/** pidfile 内容:app-server 进程 pid + 启动时间戳(ps lstart 输出原文)。 */
|
|
290
|
-
interface PidFileContent {
|
|
291
|
-
pid: number;
|
|
292
|
-
lstart?: string;
|
|
293
|
-
startedAt: number;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function pidfilePath(homeDir: string): string {
|
|
297
|
-
return path.join(homeDir, ZCODE_APPSERVER_PIDFILE_NAME);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function psField(pid: number, field: string): Promise<string | undefined> {
|
|
301
|
-
return new Promise((resolve) => {
|
|
302
|
-
execFile("ps", ["-p", String(pid), `-o`, `${field}=`], { encoding: "utf8", timeout: 2_000 }, (err, stdout) => {
|
|
303
|
-
// ps 失败/进程消失:undefined(三重判据按不满足处理——漏回收可接受,误杀不可)
|
|
304
|
-
resolve(err ? undefined : stdout.trim() || undefined);
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/** 进程启动时间戳(ps lstart 原文——pid 复用封死判据)。 */
|
|
310
|
-
export function probePidLstart(pid: number): Promise<string | undefined> {
|
|
311
|
-
return psField(pid, "lstart");
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/** 进程命令行(ps command——app-server 形态匹配判据)。 */
|
|
315
|
-
export function probePidCommand(pid: number): Promise<string | undefined> {
|
|
316
|
-
return psField(pid, "command");
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* 常驻进程 spawn 后写 pidfile(D6③)。lstart 采集失败仍写 pid(届时三重判据中的
|
|
321
|
-
* 时间戳判据不可满足 → 宁漏回收不误杀,wrapper 形态假阴性同口径)。
|
|
322
|
-
*/
|
|
323
|
-
export async function writeAppServerPidFile(homeDir: string, pid: number): Promise<void> {
|
|
324
|
-
const content: PidFileContent = {
|
|
325
|
-
pid,
|
|
326
|
-
startedAt: Date.now(),
|
|
327
|
-
...(pid > 0 ? { lstart: await probePidLstart(pid) } : {}),
|
|
328
|
-
};
|
|
329
|
-
// ensureDir:false:homeDir 已由 prepare 阶段 bootstrap(目录被删属异常态,
|
|
330
|
-
// 维持旧实现 fail-fast 上抛,不静默重建)
|
|
331
|
-
writeAtomicFileSync(pidfilePath(homeDir), JSON.stringify(content, null, CONFIG_INDENT_SPACES), { ensureDir: false });
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/** 回收判定结论(诊断/测试断言面)。 */
|
|
335
|
-
export type OrphanReapStatus =
|
|
336
|
-
| "no-pidfile"
|
|
337
|
-
| "pid-dead"
|
|
338
|
-
| "criteria-mismatch"
|
|
339
|
-
| "reaped";
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* pidfile 孤儿自愈(D6③)。前提时序(钉死):**先过 D7 目录锁判定**——本函数只在
|
|
343
|
-
* 「锁无主、本方接管 HOME」后被调用(锁被活宿主持有时派生新 HOME,不触碰他人
|
|
344
|
-
* pidfile)。三重判据全过才回收:pid 仍活 AND ps lstart 与 pidfile 记录一致(封死
|
|
345
|
-
* pid 复用误杀)AND 命令行匹配 app-server 形态(wrapper 形态假阴性漏回收可接受)。
|
|
346
|
-
* pidfile 在所有分支处理毕即清理(死 pid / 判据不符 = 陈旧记录,无保留价值)。
|
|
347
|
-
*/
|
|
348
|
-
export async function reapOrphanAppServer(homeDir: string, opts: { graceMs?: number } = {}): Promise<OrphanReapStatus> {
|
|
349
|
-
const file = pidfilePath(homeDir);
|
|
350
|
-
let content: PidFileContent | undefined;
|
|
351
|
-
try {
|
|
352
|
-
const parsed: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
353
|
-
if (isRecord(parsed) && typeof parsed["pid"] === "number") {
|
|
354
|
-
content = { pid: parsed["pid"], startedAt: 0, ...(typeof parsed["lstart"] === "string" ? { lstart: parsed["lstart"] } : {}) };
|
|
355
|
-
}
|
|
356
|
-
} catch {
|
|
357
|
-
content = undefined;
|
|
358
|
-
}
|
|
359
|
-
if (content === undefined) return "no-pidfile";
|
|
360
|
-
const unlinkPidfile = (): void => {
|
|
361
|
-
try {
|
|
362
|
-
fs.unlinkSync(file);
|
|
363
|
-
} catch (err) {
|
|
364
|
-
logger.debug(`[zcode-preparer] pidfile 清理失败(${file},已被并发清理则无妨): ${errMessage(err)}`);
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
if (!isPidAlive(content.pid)) {
|
|
368
|
-
unlinkPidfile();
|
|
369
|
-
return "pid-dead";
|
|
370
|
-
}
|
|
371
|
-
// 两次独立探测串行(互不依赖也互不需齐备——任一 undefined 即判据不满足)
|
|
372
|
-
const lstart = await probePidLstart(content.pid);
|
|
373
|
-
const command = await probePidCommand(content.pid);
|
|
374
|
-
const lstartMatches = content.lstart !== undefined && lstart !== undefined && lstart === content.lstart;
|
|
375
|
-
const commandMatches = command !== undefined && command.includes("app-server");
|
|
376
|
-
if (lstartMatches && commandMatches) {
|
|
377
|
-
reapPid(content.pid, opts.graceMs ?? ZCODE_APPSERVER_PIDFILE_GRACE_MS);
|
|
378
|
-
unlinkPidfile();
|
|
379
|
-
return "reaped";
|
|
380
|
-
}
|
|
381
|
-
// 判据不符(pid 被复用成无关进程 / wrapper 形态):不杀,清陈旧 pidfile
|
|
382
|
-
unlinkPidfile();
|
|
383
|
-
return "criteria-mismatch";
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/** 非 child 进程的回收:SIGTERM → 轮询 grace → SIGKILL(best-effort,异常吞掉)。 */
|
|
387
|
-
function reapPid(pid: number, graceMs: number): void {
|
|
388
|
-
const killWith = (signal: NodeJS.Signals): void => {
|
|
389
|
-
try {
|
|
390
|
-
process.kill(pid, signal);
|
|
391
|
-
} catch (err) {
|
|
392
|
-
// 进程恰已退出——回收目的已达,debug 留痕
|
|
393
|
-
logger.debug(`[zcode-preparer] 孤儿回收信号 ${signal} 未送达(pid ${pid},可能已退出): ${errMessage(err)}`);
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
killWith("SIGTERM");
|
|
397
|
-
const deadline = Date.now() + graceMs;
|
|
398
|
-
const poll = (): void => {
|
|
399
|
-
if (!isPidAlive(pid)) return;
|
|
400
|
-
if (Date.now() >= deadline) {
|
|
401
|
-
killWith("SIGKILL");
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
const t = setTimeout(poll, PID_REAP_POLL_INTERVAL_MS);
|
|
405
|
-
if (typeof t.unref === "function") t.unref();
|
|
406
|
-
};
|
|
407
|
-
poll();
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// ============================================================
|
|
411
|
-
// [R4 D7] 常驻 HOME 组合入口(锁 → 孤儿回收 → config 引导)
|
|
412
|
-
// ============================================================
|
|
413
|
-
|
|
414
|
-
export interface AppServerHomeHandle extends AppServerHomeLock, AppServerConfigBootstrap {
|
|
415
|
-
/** 孤儿回收结论(非接管路径恒 "no-pidfile" 语义的 not-applicable 占位)。 */
|
|
416
|
-
orphanReap: OrphanReapStatus | "not-applicable";
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* 获取 app-server 常驻 HOME(每任务调用——含凭据刷新比对)。首次:目录锁判定
|
|
421
|
-
* (活持有 → 派生;无主 → 接管 + pidfile 孤儿回收)+ config 引导。后续:config
|
|
422
|
-
* 内容 hash 比对(不一致 → 重写,调用方据此重建连接)。锚定不变量:poolDir ==
|
|
423
|
-
* HOME == db 所在目录(SQLite 落 HOME/.zcode/cli/db/db.sqlite;journal 同落该池,
|
|
424
|
-
* 文件名 = record id 无冲突)。
|
|
425
|
-
*/
|
|
426
|
-
export async function acquireAppServerHome(opts: {
|
|
427
|
-
engineDataDir: string;
|
|
428
|
-
modelRef: string;
|
|
429
|
-
sources?: ZcodeSourcePaths;
|
|
430
|
-
pidAlive?: (pid: number) => boolean;
|
|
431
|
-
hooks?: AppServerHomeLockHooks;
|
|
432
|
-
}): Promise<AppServerHomeHandle> {
|
|
433
|
-
const lock = acquireAppServerHomeLock(opts.engineDataDir, {
|
|
434
|
-
...(opts.pidAlive !== undefined ? { pidAlive: opts.pidAlive } : {}),
|
|
435
|
-
...(opts.hooks !== undefined ? { hooks: opts.hooks } : {}),
|
|
436
|
-
});
|
|
437
|
-
// D6③ 时序钉死:仅接管路径(锁无主)才回收 pidfile——锁被活宿主持有时派生目录,
|
|
438
|
-
// 派生目录内的 pidfile(若有)属本方历史残留之外的形态,不碰
|
|
439
|
-
const orphanReap = lock.tookOver ? await reapOrphanAppServer(lock.homeDir) : "not-applicable";
|
|
440
|
-
const boot = bootstrapAppServerConfig({ homeDir: lock.homeDir, modelRef: opts.modelRef, sources: opts.sources });
|
|
441
|
-
return { ...lock, ...boot, orphanReap };
|
|
442
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
// src/execution/engine/engines/zcode/appserver-probe.ts
|
|
2
|
-
//
|
|
3
|
-
// [R5 D8] app-server 协议冒烟探针。设计权威源:docs/design/zcode-engine-appserver-
|
|
4
|
-
// resident.md §3.3 D8①(独立连接 create 探针会话 → close → shutdown、校验应答形状
|
|
5
|
-
// 与 sessionId 提取、预算 10s、不触发模型请求不产生费用)+ D2 降级链第①步(探针
|
|
6
|
-
// 失败 → 本任务起直接走 spawn)。
|
|
7
|
-
//
|
|
8
|
-
// 关键约束:
|
|
9
|
-
// - 独立短命连接(AppServerConnection 全新实例,不与常驻主连接共享进程/状态);
|
|
10
|
-
// 探针结束 shutdown 杀链收割,绝不留孤儿进程;
|
|
11
|
-
// - 必须用**已引导的常驻 HOME**(D7 教训:先 bootstrap 再 probe,否则 app-server
|
|
12
|
-
// 启动即因 config 缺失报 -32603,探针永远误降级 spawn)——homeDir 由调用方
|
|
13
|
-
// (zcode-engine 的 probe 门控)经 ensureAppServerHome 保证;
|
|
14
|
-
// - create 帧键集与 SessionChannel.createSession 逐字同源(A.2 ① strict——对面
|
|
15
|
-
// zod 拒未知键),但不带 model、绝不发 session/send(无模型请求、无费用);
|
|
16
|
-
// - 预算整体 race 兜底:请求级超时之外,进程假死/spawn 挂起由外层 deadline 收割。
|
|
17
|
-
//
|
|
18
|
-
// 结论消费方:zcode-engine 的 probe 门控(结论与 CLI mtime 绑定缓存——zcode 升级
|
|
19
|
-
// mtime 变化后首个任务前重探,D2③)。
|
|
20
|
-
|
|
21
|
-
import { getLogger } from "../../../../core/logger.ts";
|
|
22
|
-
|
|
23
|
-
import { AppServerConnection, buildAppServerEnv } from "./connection.ts";
|
|
24
|
-
import { extractCreatedSessionId, stableWorkspaceKey } from "./session-channel.ts";
|
|
25
|
-
import {
|
|
26
|
-
ZCODE_APPSERVER_PROBE_BUDGET_MS,
|
|
27
|
-
ZCODE_APPSERVER_PROBE_CONN_ENV,
|
|
28
|
-
ZCODE_KILL_GRACE_MS,
|
|
29
|
-
} from "./constants.ts";
|
|
30
|
-
|
|
31
|
-
const logger = getLogger("subagents");
|
|
32
|
-
|
|
33
|
-
/** 应答形状异常时的摘要截断长度(够定位形态、不刷屏)。 */
|
|
34
|
-
const CREATE_REPLY_LOG_CHARS = 300;
|
|
35
|
-
|
|
36
|
-
/** 冒烟探针参数(依赖全量注入:cliPath/homeDir/env 来自引擎,测试可换 tmp 路径)。 */
|
|
37
|
-
export interface AppServerSmokeProbeOptions {
|
|
38
|
-
/** zcode CLI 路径(node 脚本)。 */
|
|
39
|
-
cliPath: string;
|
|
40
|
-
/** 已引导的常驻 HOME(D7:探针连接的 --cwd 与 $HOME)。 */
|
|
41
|
-
homeDir: string;
|
|
42
|
-
/** env 基底(引擎 deps.processEnv;探针 env 在其上叠 HOME/嵌套标记/探针连接标记)。 */
|
|
43
|
-
baseEnv: NodeJS.ProcessEnv;
|
|
44
|
-
/** stderr tee 落盘路径(append;与常驻连接分文件——探针故障独立取证)。 */
|
|
45
|
-
stderrLogPath: string;
|
|
46
|
-
/** 总预算(缺省 ZCODE_APPSERVER_PROBE_BUDGET_MS;测试注入短预算验证超时路径)。 */
|
|
47
|
-
budgetMs?: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** 冒烟探针结论(ok=false 时 detail 携带失败素材——降级 reason 的数据源)。 */
|
|
51
|
-
export interface AppServerSmokeProbeResult {
|
|
52
|
-
ok: boolean;
|
|
53
|
-
detail: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** 错误出声用 message 提取(非 Error 值不抛二次异常)。 */
|
|
57
|
-
function errMessage(err: unknown): string {
|
|
58
|
-
return err instanceof Error ? err.message : String(err);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 跑一次协议冒烟:独立连接上 create 探针会话(校验 sessionId 可提取)→ close →
|
|
63
|
-
* shutdown。任何一步失败/超预算 → ok=false(调用方降级 spawn,不抛——探针失败是
|
|
64
|
-
* 结论不是异常)。
|
|
65
|
-
*/
|
|
66
|
-
export async function runAppServerSmokeProbe(opts: AppServerSmokeProbeOptions): Promise<AppServerSmokeProbeResult> {
|
|
67
|
-
const budgetMs = opts.budgetMs ?? ZCODE_APPSERVER_PROBE_BUDGET_MS;
|
|
68
|
-
const conn = new AppServerConnection({
|
|
69
|
-
cliPath: opts.cliPath,
|
|
70
|
-
cwd: opts.homeDir,
|
|
71
|
-
env: {
|
|
72
|
-
...buildAppServerEnv(opts.homeDir, opts.baseEnv),
|
|
73
|
-
// 探针连接标记(fake/诊断侧区分探针进程与常驻进程的判据;对真 CLI 透明)
|
|
74
|
-
[ZCODE_APPSERVER_PROBE_CONN_ENV]: "1",
|
|
75
|
-
},
|
|
76
|
-
stderrLogPath: opts.stderrLogPath,
|
|
77
|
-
requestTimeoutMs: budgetMs,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// 探针工作面:create(形状校验)→ close。绝不 send(无模型请求)。
|
|
81
|
-
const work = (async (): Promise<string> => {
|
|
82
|
-
const created = await conn.request("session/create", {
|
|
83
|
-
workspace: {
|
|
84
|
-
workspacePath: opts.homeDir,
|
|
85
|
-
workspaceKey: stableWorkspaceKey(opts.homeDir),
|
|
86
|
-
},
|
|
87
|
-
mode: "yolo",
|
|
88
|
-
persistence: "immediate",
|
|
89
|
-
});
|
|
90
|
-
const sessionId = extractCreatedSessionId(created);
|
|
91
|
-
if (sessionId === undefined) {
|
|
92
|
-
throw new Error(
|
|
93
|
-
`session/create 应答无可提取 sessionId: ${JSON.stringify(created).slice(0, CREATE_REPLY_LOG_CHARS)}`,
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
await conn.request("session/close", { sessionId });
|
|
97
|
-
return sessionId;
|
|
98
|
-
})();
|
|
99
|
-
|
|
100
|
-
// 预算 race:进程假死/spawn 挂起时请求级超时不够(帧可能永不到达)——外层
|
|
101
|
-
// deadline 兜底,超时即判探针失败(错误规格表末行:probe 预算耗尽 → 降级 spawn)
|
|
102
|
-
let timedOut = false;
|
|
103
|
-
const deadline = new Promise<void>((resolve) => {
|
|
104
|
-
const t = setTimeout(() => {
|
|
105
|
-
timedOut = true;
|
|
106
|
-
resolve();
|
|
107
|
-
}, budgetMs);
|
|
108
|
-
if (typeof t.unref === "function") t.unref();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
let failure: unknown;
|
|
112
|
-
const sessionId = await Promise.race([
|
|
113
|
-
work.catch((err: unknown) => {
|
|
114
|
-
failure = err;
|
|
115
|
-
return undefined;
|
|
116
|
-
}),
|
|
117
|
-
deadline.then(() => undefined),
|
|
118
|
-
]);
|
|
119
|
-
|
|
120
|
-
// 无论成败都收割探针进程(短命语义:shutdown 是杀链全序,幂等;失败只 debug——
|
|
121
|
-
// 探针结论已定,收割失败留给宿主 dispose 兜底)
|
|
122
|
-
await conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS }).catch((err: unknown) => {
|
|
123
|
-
logger.debug(`[zcode-probe] 探针连接 shutdown 失败(best-effort): ${errMessage(err)}`);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
if (failure !== undefined) {
|
|
127
|
-
return { ok: false, detail: `探针会话失败: ${errMessage(failure)}` };
|
|
128
|
-
}
|
|
129
|
-
if (sessionId === undefined) {
|
|
130
|
-
return {
|
|
131
|
-
ok: false,
|
|
132
|
-
detail: timedOut
|
|
133
|
-
? `探针 ${budgetMs}ms 预算耗尽(create/close 未完成——协议无响应或进程假死)`
|
|
134
|
-
: "探针未产出会话 id(未知形态)",
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
return {
|
|
138
|
-
ok: true,
|
|
139
|
-
detail: `协议冒烟通过(create→close→shutdown,探针会话 ${sessionId},未发模型请求)`,
|
|
140
|
-
};
|
|
141
|
-
}
|