@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
package/src/shared/agent-ref.ts
CHANGED
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
*
|
|
9
9
|
* 名字不再是引用——只是注入段展示标签。执行侧拿到引用后一律 normalizeRef →
|
|
10
10
|
* 文件加载(AgentRegistry.loadByPath / WorkflowScriptRegistry.getPath)。
|
|
11
|
+
*
|
|
12
|
+
* `..` 路径段拒绝(sink 设计 ⛔2 声明的安全收紧,agent .md 与 workflow .js 引用
|
|
13
|
+
* 同受此面——两宿主现状均放行,非等值变更):`/x/../y.md` 形态允许引用逃逸出
|
|
14
|
+
* 注入段声明的目录集(G1 例 2),引用串含 `..` 段一律拒绝。检测在 `~/` 展开前的
|
|
15
|
+
* 原始引用上——join 展开会 normalize 消解 `..` 段(`~/../x` → `<home>/../x` 被解析
|
|
16
|
+
* 成已消解路径),原始串检测使 `~/../x.md` 与 `/x/../y.md` 同判(`..` 逃逸向量与
|
|
17
|
+
* 前缀形态无关)。保留 `.` 段与 `~/` 展开——最小收紧,不伤合法引用。
|
|
11
18
|
*/
|
|
12
19
|
import { homedir } from "node:os";
|
|
13
20
|
import { isAbsolute, join } from "node:path";
|
|
@@ -16,15 +23,25 @@ import { isAbsolute, join } from "node:path";
|
|
|
16
23
|
const HOME_DIR_PREFIX = "~/";
|
|
17
24
|
|
|
18
25
|
/**
|
|
19
|
-
*
|
|
26
|
+
* 引用串是否含 `..` 段(`/x/../y`、`x\..\y`、`~/../x` 同判——展开前检测)。
|
|
27
|
+
* 手动 split(/[\\/]) 与 displayAgentName 同款跨平台切分(本模块避免平台分支)。
|
|
28
|
+
*/
|
|
29
|
+
function hasParentSegment(ref: string): boolean {
|
|
30
|
+
return ref.split(/[\\/]/).includes("..");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 归一化资源引用:~ 展开 + 绝对路径校验 + `..` 段拒绝(⛔2 安全收紧)。
|
|
20
35
|
*
|
|
21
36
|
* @param ref 原始引用(注入段 location / 工具参数值)
|
|
22
37
|
* @param ext 期望扩展名(如 ".md" / ".js"),不匹配返回 null
|
|
23
|
-
* @returns
|
|
38
|
+
* @returns 归一化绝对路径;非法(空/相对路径/含 `..` 段/扩展名不符)返回 null
|
|
24
39
|
*/
|
|
25
40
|
export function normalizeRef(ref: string, ext?: string): string | null {
|
|
26
41
|
const trimmed = ref.trim();
|
|
27
42
|
if (!trimmed) return null;
|
|
43
|
+
// ⛔2:`..` 段 = 目录逃逸向量,展开前检测(join 会 normalize 消解 `..`,展开后判会漏)
|
|
44
|
+
if (hasParentSegment(trimmed)) return null;
|
|
28
45
|
|
|
29
46
|
const expanded = trimmed.startsWith(HOME_DIR_PREFIX)
|
|
30
47
|
? join(homedir(), trimmed.slice(HOME_DIR_PREFIX.length))
|
|
@@ -35,6 +52,130 @@ export function normalizeRef(ref: string, ext?: string): string | null {
|
|
|
35
52
|
return expanded;
|
|
36
53
|
}
|
|
37
54
|
|
|
55
|
+
// ── 报错文案工厂(sink 设计 U1)──────────────────────────────
|
|
56
|
+
|
|
57
|
+
/** invalidAgentRefMessage 的可选注入:宿主的清单段名(缺省 pi 的注入段)。 */
|
|
58
|
+
export interface InvalidAgentRefMessageOptions {
|
|
59
|
+
/**
|
|
60
|
+
* 清单指引:宿主注入段名。缺省 `<available_subagents>`(pi 注入段);第三宿主
|
|
61
|
+
* 注入段名不同(zsw `<available_agents>` 等)时注入替代,文案口径不变。
|
|
62
|
+
*/
|
|
63
|
+
howToList?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* agent ref 非法的统一报错文案(消费方唯一文案口径——不各自拼 message)。
|
|
68
|
+
*
|
|
69
|
+
* 基准 = AgentRegistry.loadByPath(require) 的既有 throw 文案
|
|
70
|
+
* ("Invalid agent ref: ... absolute paths to .md files ..."),工厂收敛后该消费点
|
|
71
|
+
* 与后续宿主接线点共用此处。`..` 形态附纠正指引(⛔2 新失败路径——错误必须指向
|
|
72
|
+
* 恢复动作:去掉 `..` 段、改用注入段 location)。
|
|
73
|
+
*/
|
|
74
|
+
export function invalidAgentRefMessage(
|
|
75
|
+
ref: string,
|
|
76
|
+
opts?: InvalidAgentRefMessageOptions,
|
|
77
|
+
): string {
|
|
78
|
+
const howToList = opts?.howToList ?? "<available_subagents>";
|
|
79
|
+
if (hasParentSegment(ref)) {
|
|
80
|
+
return (
|
|
81
|
+
`Invalid agent ref: ${ref}. Agent refs must be absolute paths to .md files ` +
|
|
82
|
+
`without ".." path segments (use <location> from ${howToList}).`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return (
|
|
86
|
+
`Invalid agent ref: ${ref}. Agent refs must be absolute paths to .md files ` +
|
|
87
|
+
`(use <location> from ${howToList}).`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── workflow ref 原语(sink 设计 U1 / §2.2 D3 行)────────────
|
|
92
|
+
|
|
93
|
+
/** normalizeWorkflowRef 的可选注入:宿主已知的 workflow 名清单。 */
|
|
94
|
+
export interface NormalizeWorkflowRefOptions {
|
|
95
|
+
/**
|
|
96
|
+
* 宿主注入的已知 workflow 名清单(内置 + 用户级,宿主按自身优先级合并去重——
|
|
97
|
+
* pi 为 config-loader 的 tmp>project>npm>user 去重产物,内置名优先已由该清单
|
|
98
|
+
* 体现)。省略 = 无已知名,全部裸名按 unknown_name 拒绝。
|
|
99
|
+
*/
|
|
100
|
+
knownNames?: Iterable<string>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** workflow ref 名分支的保留字:`.` / `..` 是文件系统相对路径段保留语义,任何域不可作名引用。 */
|
|
104
|
+
export const WORKFLOW_REF_RESERVED_NAMES: readonly string[] = [".", ".."];
|
|
105
|
+
|
|
106
|
+
/** normalizeWorkflowRef 拒绝原因(结构化裁决——文案留宿主,与 knownNames 宿主注入哲学一致)。 */
|
|
107
|
+
export type WorkflowRefInvalidReason =
|
|
108
|
+
| "empty" // 空引用
|
|
109
|
+
| "reserved" // 名分支命中保留字(保留字裁决优先于 knownNames)
|
|
110
|
+
| "unknown_name" // 裸名未命中 knownNames
|
|
111
|
+
| "not_absolute" // 路径分支:相对路径无基准
|
|
112
|
+
| "parent_segment" // 路径分支:含 `..` 段(⛔2 同面)
|
|
113
|
+
| "bad_ext"; // 路径分支:非 .js
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* normalizeWorkflowRef 的三分裁决结果:
|
|
117
|
+
* - name → 裸名命中 knownNames,按名引用(内置名优先已由宿主清单体现)
|
|
118
|
+
* - path → 路径形态经 normalizeRef 全套校验(~/ 展开、绝对路径、.js、`..` 拒绝)
|
|
119
|
+
* - invalid → 拒绝(reason 给精确裁决原因,消费方出恢复指引)
|
|
120
|
+
*/
|
|
121
|
+
export type NormalizedWorkflowRef =
|
|
122
|
+
| { kind: "name"; name: string }
|
|
123
|
+
| { kind: "path"; path: string }
|
|
124
|
+
| { kind: "invalid"; ref: string; reason: WorkflowRefInvalidReason };
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* workflow 引用统一原语(名/路径二分 + 保留字裁决 + 内置名优先,三层口径单源化——
|
|
128
|
+
* pi「收裸名+路径」、zsw「入口拒裸名/内层宽松」的统一替代)。
|
|
129
|
+
*
|
|
130
|
+
* 二分判据:含 `/`、`\` 分隔符或 `~` 前缀 → 路径分支;否则 → 裸名分支。对齐 pi
|
|
131
|
+
* 现行为「名字是简单标识符,路径串不会撞 workflow 名」(tool-workflow actionRun:
|
|
132
|
+
* get(name) 先于 getPath——名命中即用,不猜路径,内置名优先由此成立)。
|
|
133
|
+
*
|
|
134
|
+
* 裁决顺序(裸名分支):保留字 > knownNames > unknown——保留字命中即拒,即便宿主
|
|
135
|
+
* 清单异常含保留字也不放行(`..` 若被当名接受会绕过路径域的 ⛔2 收紧)。
|
|
136
|
+
*
|
|
137
|
+
* 路径分支复用 normalizeRef(WORKFLOW_REF_EXT):`..` 段拒绝、~/ 展开、扩展名校验
|
|
138
|
+
* 与 agent 域同面(G1:同一校验语义)。
|
|
139
|
+
*/
|
|
140
|
+
export function normalizeWorkflowRef(
|
|
141
|
+
ref: string,
|
|
142
|
+
opts?: NormalizeWorkflowRefOptions,
|
|
143
|
+
): NormalizedWorkflowRef {
|
|
144
|
+
const trimmed = ref.trim();
|
|
145
|
+
if (!trimmed) return { kind: "invalid", ref, reason: "empty" };
|
|
146
|
+
|
|
147
|
+
const looksLikePath = trimmed.includes("/") || trimmed.includes("\\") || trimmed.startsWith("~");
|
|
148
|
+
if (looksLikePath) {
|
|
149
|
+
const path = normalizeRef(trimmed, WORKFLOW_REF_EXT);
|
|
150
|
+
if (path !== null) return { kind: "path", path };
|
|
151
|
+
// 失败原因细分(normalizeRef 返回 null 不带原因——此处按同一判据重放;重放
|
|
152
|
+
// 顺序与 normalizeRef 判序逐位对齐:parent_segment 先于 not_absolute,否则
|
|
153
|
+
// `a/../b.js` 类「相对路径 + 含 .. 段」被误归因 not_absolute,指引改绝对路径
|
|
154
|
+
// 后仍被 .. 段拒绝——两步误导。`..` 段对原始串检测,与展开前判位一致)
|
|
155
|
+
const expanded = trimmed.startsWith(HOME_DIR_PREFIX)
|
|
156
|
+
? join(homedir(), trimmed.slice(HOME_DIR_PREFIX.length))
|
|
157
|
+
: trimmed;
|
|
158
|
+
const reason: WorkflowRefInvalidReason = hasParentSegment(trimmed)
|
|
159
|
+
? "parent_segment"
|
|
160
|
+
: !isAbsolute(expanded)
|
|
161
|
+
? "not_absolute"
|
|
162
|
+
: "bad_ext";
|
|
163
|
+
return { kind: "invalid", ref, reason };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 裸名分支:保留字 > knownNames > unknown
|
|
167
|
+
if (WORKFLOW_REF_RESERVED_NAMES.includes(trimmed)) {
|
|
168
|
+
return { kind: "invalid", ref, reason: "reserved" };
|
|
169
|
+
}
|
|
170
|
+
const known = opts?.knownNames;
|
|
171
|
+
if (known !== undefined) {
|
|
172
|
+
for (const name of known) {
|
|
173
|
+
if (name === trimmed) return { kind: "name", name: trimmed };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { kind: "invalid", ref, reason: "unknown_name" };
|
|
177
|
+
}
|
|
178
|
+
|
|
38
179
|
/** agentRef 扩展名。 */
|
|
39
180
|
export const AGENT_REF_EXT = ".md";
|
|
40
181
|
/** workflowRef 扩展名。 */
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 原子写原语(sink 设计 U6a / B6):tmp+rename 的单一实现。
|
|
3
|
+
*
|
|
4
|
+
* 背景:core 内部曾散布 6-7 份 tmp+rename 写点(manifest-store / sessions-index /
|
|
5
|
+
* worktree-registry / engine-discovery / zcode preparer / zcode appserver-home),
|
|
6
|
+
* tmp 命名各异(`.tmp.<pid>` / `.tmp_<pid>_<rand>` / `.tmp-<pid>-<ts>`)、失败路径
|
|
7
|
+
* 清理纪律不齐(worktree-registry 失败时漏清残留 tmp)。本模块给出统一原语供全
|
|
8
|
+
* 部写点收敛(调用点迁移归 u-wire 单元,本单元只建原语)。现存消费方 4 处:
|
|
9
|
+
* manifest-store / sessions-index / worktree-registry / engine-discovery——zcode
|
|
10
|
+
* preparer 与 zcode appserver-home 两处写点已随 2026-09 共享宿主 HOME 重构删除。
|
|
11
|
+
*
|
|
12
|
+
* **统一 tmp 命名约定**:`<最终路径>.tmp.<pid>.<seq>-<rand>`。
|
|
13
|
+
* - `.tmp.` 标记向后兼容 manifest-store 既有扫描(`x.json` 的 tmp 名为
|
|
14
|
+
* `x.json.tmp.…`,命中其 `.json.tmp.` 模式);
|
|
15
|
+
* - pid 防两进程共用同一 tmp,seq+rand 防同进程内并发写同目标共用同一 tmp
|
|
16
|
+
* (对齐 sessions-index tmp 后缀的双重防撞设计)。
|
|
17
|
+
*
|
|
18
|
+
* **失败清理语义**:写入或 rename 失败 → 尽力 unlink 自身 tmp(失败仅 debug
|
|
19
|
+
* 记录)→ 原错误原样上抛(不掩盖、不包装)。rename 成功后 tmp 已不存在,无需
|
|
20
|
+
* 清理。跨进程/跨历史的陈旧残留 tmp 不由写入路径处理——统一走
|
|
21
|
+
* listStaleTmpFiles / cleanupStaleTmpFiles 扫描入口(崩溃残留恢复语义的单点,
|
|
22
|
+
* 见 sink 设计 §4 S6)。
|
|
23
|
+
*
|
|
24
|
+
* **两种耐久档位**(对齐现存两族写点的生产模式):
|
|
25
|
+
* - sync(writeAtomicFileSync):writeFileSync + renameSync,无 fsync——对齐
|
|
26
|
+
* worktree-registry / engine-discovery 两处同步写点现状(注册表/发现缓存类,
|
|
27
|
+
* 进程崩溃窗口可容忍);
|
|
28
|
+
* - async(writeAtomicFile):fsync 文件 → rename → 尽力 fsync 目录——对齐
|
|
29
|
+
* manifest-store / sessions-index 的生产耐久模式(掉电也不丢已确认写入)。
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { mkdirSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
33
|
+
import * as fsPromises from "node:fs/promises";
|
|
34
|
+
import * as path from "node:path";
|
|
35
|
+
|
|
36
|
+
import { getLogger } from "../core/logger.ts";
|
|
37
|
+
import { toErrorMessage } from "../core/error-message.ts";
|
|
38
|
+
|
|
39
|
+
const logger = getLogger("subagents");
|
|
40
|
+
|
|
41
|
+
/** tmp 命名标记:`${最终路径}.tmp.${pid}.${uniq}`。 */
|
|
42
|
+
const TMP_MARKER = ".tmp.";
|
|
43
|
+
|
|
44
|
+
/** 解析约定 tmp 名:贪婪目标段 + `.tmp.` + pid + `.` + uniq(alnum/-)。 */
|
|
45
|
+
const TMP_NAME_PATTERN = /^(.+)\.tmp\.(\d+)\.[0-9A-Za-z-]+$/;
|
|
46
|
+
|
|
47
|
+
/** 同进程内 tmp 单调序号(与随机段联合防并发写同目标撞名)。 */
|
|
48
|
+
let tmpSeq = 0;
|
|
49
|
+
|
|
50
|
+
/** 随机段生成参数:base36 编码去掉「0.x」前缀后取 6 字符。 */
|
|
51
|
+
const RAND_RADIX = 36;
|
|
52
|
+
const RAND_SLICE_START = 2;
|
|
53
|
+
const RAND_SLICE_END = 8;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 目标文件的原子写 tmp 路径(统一约定:`<最终路径>.tmp.<pid>.<seq>-<rand>`)。
|
|
57
|
+
*
|
|
58
|
+
* 独立导出供调用方预告 tmp 名(如崩溃恢复扫描按约定反查)与测试锚定命名形态;
|
|
59
|
+
* 两个 write 原语内部各自调用(每次写独立 tmp,并发写同目标互不串写)。
|
|
60
|
+
*/
|
|
61
|
+
export function atomicTmpPathFor(filePath: string): string {
|
|
62
|
+
tmpSeq += 1;
|
|
63
|
+
const rand = Math.random().toString(RAND_RADIX).slice(RAND_SLICE_START, RAND_SLICE_END);
|
|
64
|
+
return `${filePath}${TMP_MARKER}${process.pid}.${tmpSeq}-${rand}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 约定 tmp 路径的解析视图。 */
|
|
68
|
+
export interface AtomicTmpRef {
|
|
69
|
+
/** tmp 文件自身完整路径。 */
|
|
70
|
+
tmpPath: string;
|
|
71
|
+
/** 该 tmp 写入的目标路径(约定前缀还原)。 */
|
|
72
|
+
targetPath: string;
|
|
73
|
+
/** 创建该 tmp 的进程 pid(扫描方可据此实现存活过滤等策略)。 */
|
|
74
|
+
pid: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 解析约定 tmp 路径为目标视图;非约定形态返回 null。
|
|
79
|
+
*
|
|
80
|
+
* 注意贪婪匹配方向:目标名自身含 `.tmp.` 时(如 `foo.tmp.json.tmp.1-x`)
|
|
81
|
+
* 前缀段正确还原为 `foo.tmp.json`。反之,恰以 `.tmp.<数字>.<uniq>` 结尾的
|
|
82
|
+
* 用户文件会被误认——清理入口只应在受管目录(runtime 数据目录)内使用。
|
|
83
|
+
*/
|
|
84
|
+
export function parseAtomicTmpPath(tmpPath: string): AtomicTmpRef | null {
|
|
85
|
+
const match = TMP_NAME_PATTERN.exec(tmpPath);
|
|
86
|
+
if (match === null) return null;
|
|
87
|
+
return { tmpPath, targetPath: match[1], pid: Number(match[2]) };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── 写入原语 ─────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
export interface AtomicWriteOptions {
|
|
93
|
+
/** 字符串内容的编码(默认 "utf8";Uint8Array 内容忽略此项)。 */
|
|
94
|
+
encoding?: BufferEncoding;
|
|
95
|
+
/** 目标父目录缺失时递归创建(默认 true,对齐四处同步写点的 mkdir 纪律)。 */
|
|
96
|
+
ensureDir?: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface AtomicWriteFileOptions extends AtomicWriteOptions {
|
|
100
|
+
/**
|
|
101
|
+
* rename 成功后尽力 fsync 目标目录(默认 true)。POSIX 不要求;失败不否定
|
|
102
|
+
* 已成功的 rename(对齐 manifest-store/sessions-index 的 best-effort 目录
|
|
103
|
+
* fsync)。设 false 跳过(省两次目录句柄开销,弱耐久场景)。
|
|
104
|
+
*/
|
|
105
|
+
fsyncDir?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const DEFAULT_ENCODING: BufferEncoding = "utf8";
|
|
109
|
+
|
|
110
|
+
/** 失败路径清理自身 tmp:尽力 unlink,失败仅 debug(不掩盖原错误)。 */
|
|
111
|
+
function removeTmpBestEffortSync(tmpPath: string): void {
|
|
112
|
+
try {
|
|
113
|
+
unlinkSync(tmpPath);
|
|
114
|
+
} catch (cleanupErr) {
|
|
115
|
+
// tmp 可能已被 rename 消费或从未创建;原错误由调用方上抛
|
|
116
|
+
logger.debug("[subagent-core] atomic-write cleanup tmp failed", {
|
|
117
|
+
detail: toErrorMessage(cleanupErr),
|
|
118
|
+
tmpPath,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 同步原子写(writeFileSync + renameSync,无 fsync)。
|
|
125
|
+
*
|
|
126
|
+
* 读者要么看到旧版完整内容、要么看到新版完整内容,绝无半成品(rename 原子性)。
|
|
127
|
+
* 失败语义见模块头。适用 prep/注册表类高频小文件;需掉电耐久用 writeAtomicFile。
|
|
128
|
+
*/
|
|
129
|
+
export function writeAtomicFileSync(
|
|
130
|
+
filePath: string,
|
|
131
|
+
content: string | Uint8Array,
|
|
132
|
+
options: AtomicWriteOptions = {},
|
|
133
|
+
): void {
|
|
134
|
+
const encoding = options.encoding ?? DEFAULT_ENCODING;
|
|
135
|
+
if (options.ensureDir !== false) {
|
|
136
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
137
|
+
}
|
|
138
|
+
const tmpPath = atomicTmpPathFor(filePath);
|
|
139
|
+
try {
|
|
140
|
+
writeFileSync(tmpPath, content, encoding);
|
|
141
|
+
renameSync(tmpPath, filePath);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
// rename 未成功 → 清理残留 tmp(尽力,不掩盖原错误)
|
|
144
|
+
removeTmpBestEffortSync(tmpPath);
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 异步原子写(fsync 文件 → rename → 尽力 fsync 目录)。
|
|
151
|
+
*
|
|
152
|
+
* manifest-store.writeManifest / sessions-index.saveIndex 生产耐久模式的统一
|
|
153
|
+
* 实现。失败语义见模块头。真异步(fs.promises,不阻塞 event loop)。
|
|
154
|
+
*/
|
|
155
|
+
export async function writeAtomicFile(
|
|
156
|
+
filePath: string,
|
|
157
|
+
content: string | Uint8Array,
|
|
158
|
+
options: AtomicWriteFileOptions = {},
|
|
159
|
+
): Promise<void> {
|
|
160
|
+
const encoding = options.encoding ?? DEFAULT_ENCODING;
|
|
161
|
+
const fsyncDir = options.fsyncDir ?? true;
|
|
162
|
+
if (options.ensureDir !== false) {
|
|
163
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
164
|
+
}
|
|
165
|
+
const tmpPath = atomicTmpPathFor(filePath);
|
|
166
|
+
const dirPath = path.dirname(filePath);
|
|
167
|
+
|
|
168
|
+
let renamed = false;
|
|
169
|
+
try {
|
|
170
|
+
// 1. 写 tmp → fsync 文件
|
|
171
|
+
const fh = await fsPromises.open(tmpPath, "w");
|
|
172
|
+
try {
|
|
173
|
+
await fh.writeFile(content, encoding);
|
|
174
|
+
await fh.sync();
|
|
175
|
+
} finally {
|
|
176
|
+
await fh.close();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 2. rename tmp → final(放在 try 内:失败时 catch 清理 tmp)
|
|
180
|
+
await fsPromises.rename(tmpPath, filePath);
|
|
181
|
+
renamed = true;
|
|
182
|
+
|
|
183
|
+
// 3. fsync 目录(尽力:POSIX 不要求,失败不否定已成功的 rename)
|
|
184
|
+
if (fsyncDir) {
|
|
185
|
+
try {
|
|
186
|
+
const dirFh = await fsPromises.open(dirPath, "r");
|
|
187
|
+
try {
|
|
188
|
+
await dirFh.sync();
|
|
189
|
+
} finally {
|
|
190
|
+
await dirFh.close();
|
|
191
|
+
}
|
|
192
|
+
} catch (dirSyncErr) {
|
|
193
|
+
logger.debug("[subagent-core] atomic-write fsync dir failed", {
|
|
194
|
+
detail: toErrorMessage(dirSyncErr),
|
|
195
|
+
dirPath,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch (err) {
|
|
200
|
+
// rename 未成功 → 清理残留 tmp(尽力,不掩盖原错误)
|
|
201
|
+
if (!renamed) {
|
|
202
|
+
try {
|
|
203
|
+
await fsPromises.unlink(tmpPath);
|
|
204
|
+
} catch (cleanupErr) {
|
|
205
|
+
logger.debug("[subagent-core] atomic-write cleanup tmp failed", {
|
|
206
|
+
detail: toErrorMessage(cleanupErr),
|
|
207
|
+
tmpPath,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
throw err;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── 崩溃残留扫描 / 清理(S6 统一恢复语义的单点)──────────────
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 扫描目录内全部约定形态的 tmp 文件(不做删除)。
|
|
219
|
+
*
|
|
220
|
+
* 供两类消费方:
|
|
221
|
+
* - cleanupStaleTmpFiles 的内部步骤;
|
|
222
|
+
* - 需要按域校验内容再决定「删 or 提升为正式文件」的宿主恢复逻辑
|
|
223
|
+
* (manifest-store.recoverTmpFiles 模式:tmp 合法且目标缺失 → rename 提升)。
|
|
224
|
+
*
|
|
225
|
+
* 目录不存在 → 返回空数组(恢复扫描对未初始化布局宽容)。非约定形态文件
|
|
226
|
+
* 一律不认(用户数据零误伤边界见 parseAtomicTmpPath 注释)。
|
|
227
|
+
*/
|
|
228
|
+
export function listStaleTmpFiles(dir: string): AtomicTmpRef[] {
|
|
229
|
+
let names: string[];
|
|
230
|
+
try {
|
|
231
|
+
names = readdirSync(dir);
|
|
232
|
+
} catch (readdirErr) {
|
|
233
|
+
if (typeof (readdirErr as NodeJS.ErrnoException).code === "string" &&
|
|
234
|
+
(readdirErr as NodeJS.ErrnoException).code === "ENOENT") {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
throw readdirErr;
|
|
238
|
+
}
|
|
239
|
+
const refs: AtomicTmpRef[] = [];
|
|
240
|
+
for (const name of names) {
|
|
241
|
+
// 以 join 后完整路径解析:targetPath 需带目录前缀(恢复提升 rename 用)
|
|
242
|
+
const ref = parseAtomicTmpPath(path.join(dir, name));
|
|
243
|
+
if (ref !== null) {
|
|
244
|
+
refs.push(ref);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return refs;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface CleanupStaleTmpOptions {
|
|
251
|
+
/**
|
|
252
|
+
* 只清理 mtime 早于 now - maxAgeMs 的残留(进程内在途写入的兜底保护窗口)。
|
|
253
|
+
* 缺省 = 全清(启动期恢复场景,目录归本进程管)。需按 pid 存活过滤的调用方
|
|
254
|
+
* 改用 listStaleTmpFiles 自行实现策略(ref.pid 可判活)。
|
|
255
|
+
*/
|
|
256
|
+
maxAgeMs?: number;
|
|
257
|
+
/** maxAgeMs 的基准时刻(缺省 Date.now();测试注入确定性时钟)。 */
|
|
258
|
+
now?: number;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface CleanupStaleTmpResult {
|
|
262
|
+
/** 已删除(含扫描与删除之间已消失的——幂等终态)。 */
|
|
263
|
+
removed: string[];
|
|
264
|
+
/** 因 maxAgeMs 窗口内被保留的(疑似他方在途写入)。 */
|
|
265
|
+
kept: string[];
|
|
266
|
+
/** unlink 失败的(尽力语义,错误已 debug 记录)。 */
|
|
267
|
+
failed: string[];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 清理目录内约定形态的 tmp 残留(单条失败不阻断其余条目,逐条结果回传)。
|
|
272
|
+
*
|
|
273
|
+
* 恢复语义(对齐 manifest-store.recoverTmpFiles 的删除分支泛化):本函数只做
|
|
274
|
+
* 「删除」级恢复;「校验后提升为正式文件」需域知识(manifest 记录合法性),
|
|
275
|
+
* 由调用方基于 listStaleTmpFiles + parseAtomicTmpPath().targetPath 自行实现。
|
|
276
|
+
*/
|
|
277
|
+
export function cleanupStaleTmpFiles(
|
|
278
|
+
dir: string,
|
|
279
|
+
options: CleanupStaleTmpOptions = {},
|
|
280
|
+
): CleanupStaleTmpResult {
|
|
281
|
+
const now = options.now ?? Date.now();
|
|
282
|
+
const result: CleanupStaleTmpResult = { removed: [], kept: [], failed: [] };
|
|
283
|
+
for (const ref of listStaleTmpFiles(dir)) {
|
|
284
|
+
if (options.maxAgeMs !== undefined) {
|
|
285
|
+
try {
|
|
286
|
+
const mtimeMs = statSync(ref.tmpPath).mtimeMs;
|
|
287
|
+
if (now - mtimeMs < options.maxAgeMs) {
|
|
288
|
+
result.kept.push(ref.tmpPath);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
} catch (statErr) {
|
|
292
|
+
if (typeof (statErr as NodeJS.ErrnoException).code === "string" &&
|
|
293
|
+
(statErr as NodeJS.ErrnoException).code === "ENOENT") {
|
|
294
|
+
// 扫描与 stat 之间已消失(他方收尾)——按已删除的幂等终态记账
|
|
295
|
+
result.removed.push(ref.tmpPath);
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
logger.debug("[subagent-core] cleanupStaleTmpFiles stat failed", {
|
|
299
|
+
detail: toErrorMessage(statErr),
|
|
300
|
+
tmpPath: ref.tmpPath,
|
|
301
|
+
});
|
|
302
|
+
result.failed.push(ref.tmpPath);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
unlinkSync(ref.tmpPath);
|
|
308
|
+
result.removed.push(ref.tmpPath);
|
|
309
|
+
} catch (unlinkErr) {
|
|
310
|
+
if (typeof (unlinkErr as NodeJS.ErrnoException).code === "string" &&
|
|
311
|
+
(unlinkErr as NodeJS.ErrnoException).code === "ENOENT") {
|
|
312
|
+
result.removed.push(ref.tmpPath);
|
|
313
|
+
} else {
|
|
314
|
+
logger.debug("[subagent-core] cleanupStaleTmpFiles unlink failed", {
|
|
315
|
+
detail: toErrorMessage(unlinkErr),
|
|
316
|
+
tmpPath: ref.tmpPath,
|
|
317
|
+
});
|
|
318
|
+
result.failed.push(ref.tmpPath);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bounded JSON pretty 序列化原语(sink 设计 U6a / B7)。
|
|
3
|
+
*
|
|
4
|
+
* 来源:自 pi-sw(extensions/universal/subagent-workflow)interface/helpers.ts
|
|
5
|
+
* 的同名私有函数**逐字平移**(函数体逐字符一致,仅补 export)——输出与该实现
|
|
6
|
+
* 字节级一致,由 `__tests__/bounded-serialize.test.ts` 的等价锚定 + 硬编码字节
|
|
7
|
+
* 快照断言守护。pi-sw 本地实现删除与消费切换归 u-sw-misc 单元(设计 U11)。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ── bounded pretty 序列化(IF13/#19,TC5/ES5)────────────────
|
|
11
|
+
|
|
12
|
+
const JSON_INDENT = 2;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* bounded JSON pretty 序列化:只生成会被保留的前缀(≤8000 输出与
|
|
16
|
+
* JSON.stringify(value, null, JSON_INDENT) 逐字节一致;>8000 输出与
|
|
17
|
+
* 全量序列化后 .slice(0, budget) + "\n... (truncated)" 逐字节一致——
|
|
18
|
+
* 等价测试锚定,见 __tests__/bounded-serialize.test.ts)。
|
|
19
|
+
*
|
|
20
|
+
* 现状成本:全量 stringify 产生数 MB 中间串再丢弃 99%;本函数在输出字符流
|
|
21
|
+
* 越过 budget 后停止一切生成。保真策略:原语(string/number/boolean/null/bigint)
|
|
22
|
+
* 逐值复用 JSON.stringify(转义/Unicode/数字格式原生一致,零重实现),仅结构
|
|
23
|
+
* 拼装(2 空格缩进/逗号/括号)自实现。
|
|
24
|
+
*
|
|
25
|
+
* 特殊值语义(TC5 a-d,与原生 JSON.stringify 全值域对齐):
|
|
26
|
+
* (a) 含 toJSON 的对象 → 该子树整体走一次 JSON.stringify(subtree)(原生会先调
|
|
27
|
+
* toJSON,如 Date 输出带引号序列化串;bounded 拼装展开会与原生不同)。注意
|
|
28
|
+
* 边界:toJSON 返回对象时原生 pretty 会按缩进展开,本实现按原语串接其
|
|
29
|
+
* compact 形态——设计 TC5 锚定面为 Date 型(返回字符串)toJSON。
|
|
30
|
+
* (b) 任何 stringify 抛出(BigInt → TypeError)→ 整体回退 String(value)(对齐
|
|
31
|
+
* 旧实现整体 try/catch 的整串回退;禁止逐节点回退——输出形态与整串回退不同)。
|
|
32
|
+
* (c) 对象属性值为 undefined/function/symbol → 拼装时跳过(原生省略)。
|
|
33
|
+
* (d) 数组元素为 undefined/function/symbol → 序列化为 "null"(原生行为)。
|
|
34
|
+
*
|
|
35
|
+
* 截断边界(输出字符流层级,与对完整串 slice 逐字节等价):逐段 append 时本段
|
|
36
|
+
* 会使总长越过 budget → 只 append 本段前 (budget - 已累积) 字符(恰好 budget,
|
|
37
|
+
* 可切在转义序列/括号中间,不补任何结构闭合);越过(exceeded)才追加
|
|
38
|
+
* "\n... (truncated)" 标记——恰好 ===budget 不加(> 判定)。
|
|
39
|
+
*
|
|
40
|
+
* 祖先 Set 循环引用守卫:命中 → 整体回退 String(value)(同 (b) 整串回退语义)。
|
|
41
|
+
*/
|
|
42
|
+
export function boundedPrettySerialize(value: unknown, budget: number): string {
|
|
43
|
+
const ancestors = new Set<object>();
|
|
44
|
+
let out = "";
|
|
45
|
+
let exceeded = false;
|
|
46
|
+
|
|
47
|
+
/** 逐段追加:越过 budget 时截到恰好 budget 并置 exceeded(后续生成全部剪枝)。 */
|
|
48
|
+
function append(s: string): void {
|
|
49
|
+
if (exceeded) return;
|
|
50
|
+
if (out.length + s.length <= budget) {
|
|
51
|
+
out += s;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
out += s.slice(0, budget - out.length);
|
|
55
|
+
exceeded = true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 数组分支:元素逐个缩进序列化((d) undefined/function/symbol → "null")。 */
|
|
59
|
+
function serializeArray(
|
|
60
|
+
arr: unknown[],
|
|
61
|
+
depth: number,
|
|
62
|
+
childIndent: string,
|
|
63
|
+
closeIndent: string,
|
|
64
|
+
): void {
|
|
65
|
+
if (arr.length === 0) {
|
|
66
|
+
append("[]");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
append("[\n");
|
|
70
|
+
for (let i = 0; i < arr.length && !exceeded; i++) {
|
|
71
|
+
if (i > 0) append(",\n");
|
|
72
|
+
append(childIndent);
|
|
73
|
+
const el = arr[i];
|
|
74
|
+
// (d) 数组元素 undefined/function/symbol → "null"
|
|
75
|
+
if (el === undefined || typeof el === "function" || typeof el === "symbol") {
|
|
76
|
+
append("null");
|
|
77
|
+
} else {
|
|
78
|
+
serialize(el, depth + 1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
append("\n" + closeIndent + "]");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 对象分支:(c) 属性 undefined/function/symbol 跳过后逐对序列化。 */
|
|
85
|
+
function serializeObject(
|
|
86
|
+
obj: object,
|
|
87
|
+
depth: number,
|
|
88
|
+
childIndent: string,
|
|
89
|
+
closeIndent: string,
|
|
90
|
+
): void {
|
|
91
|
+
// (c) 对象属性 undefined/function/symbol 跳过(Object.entries 同原生:
|
|
92
|
+
// 只取 own enumerable,symbol 键天然不出现;getter 求值语义与原生一致)
|
|
93
|
+
const entries = Object.entries(obj).filter(
|
|
94
|
+
([, val]) => val !== undefined && typeof val !== "function" && typeof val !== "symbol",
|
|
95
|
+
);
|
|
96
|
+
if (entries.length === 0) {
|
|
97
|
+
append("{}");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
append("{\n");
|
|
101
|
+
for (let i = 0; i < entries.length && !exceeded; i++) {
|
|
102
|
+
if (i > 0) append(",\n");
|
|
103
|
+
append(childIndent + JSON.stringify(entries[i][0]) + ": ");
|
|
104
|
+
serialize(entries[i][1], depth + 1);
|
|
105
|
+
}
|
|
106
|
+
append("\n" + closeIndent + "}");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function serialize(v: unknown, depth: number): void {
|
|
110
|
+
if (exceeded) return;
|
|
111
|
+
const t = typeof v;
|
|
112
|
+
// 原语逐值复用原生序列化;bigint 的 JSON.stringify 抛 TypeError → 顶层 catch
|
|
113
|
+
// 整体回退 (b);NaN/Infinity → "null"(原生行为)
|
|
114
|
+
if (v === null || t === "string" || t === "number" || t === "boolean" || t === "bigint") {
|
|
115
|
+
append(JSON.stringify(v));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (t === "object" && v !== null) {
|
|
119
|
+
const obj = v as object;
|
|
120
|
+
if (ancestors.has(obj)) {
|
|
121
|
+
// 循环引用 → 整体回退 String(value)(旧实现同款整串回退)
|
|
122
|
+
throw new TypeError("circular reference");
|
|
123
|
+
}
|
|
124
|
+
// (a) toJSON 子树整体 stringify(Reflect.get 取属性,避免不安全断言)
|
|
125
|
+
if (typeof Reflect.get(obj, "toJSON") === "function") {
|
|
126
|
+
append(JSON.stringify(obj));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
ancestors.add(obj);
|
|
130
|
+
const childIndent = " ".repeat(JSON_INDENT * (depth + 1));
|
|
131
|
+
const closeIndent = " ".repeat(JSON_INDENT * depth);
|
|
132
|
+
if (Array.isArray(obj)) {
|
|
133
|
+
serializeArray(obj as unknown[], depth, childIndent, closeIndent);
|
|
134
|
+
} else {
|
|
135
|
+
serializeObject(obj, depth, childIndent, closeIndent);
|
|
136
|
+
}
|
|
137
|
+
ancestors.delete(obj);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// 顶层 undefined/function/symbol(调用方守卫后不可达;防御兜底对齐原生 "null")
|
|
141
|
+
append(JSON.stringify(v) ?? "null");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
serialize(value, 0);
|
|
146
|
+
return exceeded ? out + "\n... (truncated)" : out;
|
|
147
|
+
} catch {
|
|
148
|
+
// (b) 整体回退:String(value)(超 budget 仍截断 + 标记,与旧实现截断路径一致)
|
|
149
|
+
const fallback = String(value);
|
|
150
|
+
return fallback.length > budget
|
|
151
|
+
? fallback.slice(0, budget) + "\n... (truncated)"
|
|
152
|
+
: fallback;
|
|
153
|
+
}
|
|
154
|
+
}
|