@zhushanwen/subagent-core 0.2.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -2
- package/agents/analyst.md +60 -0
- package/agents/coder.md +69 -0
- package/agents/debugger.md +66 -0
- package/agents/doc-reviewer.md +49 -0
- package/agents/explorer.md +63 -0
- package/agents/general-purpose.md +32 -0
- package/agents/orchestrator.md +61 -0
- package/agents/planner.md +53 -0
- package/agents/researcher.md +67 -0
- package/agents/reviewer.md +73 -0
- package/dist/chunk-43ONBFZX.js +240 -0
- package/dist/chunk-APZY4IME.js +27 -0
- package/dist/chunk-V3VHZ2VX.js +59 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
- package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
- package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
- package/dist/execution/engine/engines/zcode/constants.js +39 -7
- package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
- package/dist/execution/engine/engines/zcode/reader.js +4 -234
- package/dist/execution/engine/paths.js +7 -19
- package/dist/index.cjs +18234 -1277
- package/dist/index.d.cts +5646 -673
- package/dist/index.d.ts +5646 -673
- package/dist/index.js +17828 -1277
- package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
- package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
- package/package.json +7 -4
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
- package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
- package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
- package/src/__tests__/m2-append-content-probe.test.ts +0 -1
- package/src/__tests__/manifest-store.test.ts +10 -9
- package/src/__tests__/record-store-cache.test.ts +0 -2
- package/src/__tests__/record-store-index.test.ts +1 -2
- package/src/__tests__/review-fix-loop-script.test.ts +93 -3
- package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
- package/src/__tests__/robustness-low-batch1.test.ts +6 -5
- package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
- package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
- package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
- package/src/__tests__/session-runner.test.ts +1 -1
- package/src/__tests__/smoke.test.ts +9 -2
- package/src/core/__tests__/host-services.test.ts +2 -2
- package/src/core/__tests__/logger.test.ts +1 -1
- package/src/core/error-message.ts +9 -0
- package/src/core/host-services.ts +21 -5
- package/src/core/notify-ports.ts +18 -5
- package/src/execution/__tests__/agent-profile.test.ts +232 -0
- package/src/execution/__tests__/agent-registry.test.ts +370 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
- package/src/execution/__tests__/agents-assembly.test.ts +219 -0
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
- package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
- package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
- package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
- package/src/execution/__tests__/delivery-methods.test.ts +77 -62
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
- package/src/execution/__tests__/dialog-queue.test.ts +199 -1
- package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
- package/src/execution/__tests__/execute-nesting.test.ts +34 -188
- package/src/execution/__tests__/execution-record.test.ts +217 -60
- package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
- package/src/execution/__tests__/finalize-record.test.ts +278 -16
- package/src/execution/__tests__/gc-timer.test.ts +57 -2
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
- package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
- package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
- package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
- package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
- package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
- package/src/execution/__tests__/model-resolver.test.ts +2 -2
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
- package/src/execution/__tests__/nested-visibility.test.ts +2 -0
- package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
- package/src/execution/__tests__/output-collector.test.ts +117 -6
- package/src/execution/__tests__/pi-invocation.test.ts +1 -1
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
- package/src/execution/__tests__/record-store.test.ts +89 -5
- package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
- package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
- package/src/execution/__tests__/rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
- package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
- package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
- package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
- package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
- package/src/execution/__tests__/session-file-gc.test.ts +35 -0
- package/src/execution/__tests__/session-pending.test.ts +471 -0
- package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
- package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
- package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
- package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
- package/src/execution/__tests__/spawn-args.test.ts +11 -10
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
- package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
- package/src/execution/__tests__/spawned-children.test.ts +155 -11
- package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
- package/src/execution/__tests__/stdin-writer.test.ts +6 -4
- package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
- package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
- package/src/execution/__tests__/subagent-service.test.ts +37 -30
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
- package/src/execution/__tests__/temp-prompt.test.ts +1 -1
- package/src/execution/__tests__/timeout-integration.test.ts +13 -12
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
- package/src/execution/__tests__/turn-limiter.test.ts +1 -1
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
- package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
- package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
- package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
- package/src/execution/__tests__/worktree-manager.test.ts +14 -5
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
- package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
- package/src/execution/agent-registry.ts +298 -30
- package/src/execution/agent-result-mapper.ts +3 -0
- package/src/execution/agents-assembly.ts +88 -0
- package/src/execution/cold-resurrect.ts +199 -0
- package/src/execution/concurrency-pool.ts +71 -9
- package/src/execution/dialog-queue.ts +101 -3
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
- package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
- package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
- package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
- package/src/execution/engine/__tests__/registry.test.ts +94 -3
- package/src/execution/engine/__tests__/routing.test.ts +115 -6
- package/src/execution/engine/common/capability-gate.ts +92 -0
- package/src/execution/engine/common/journal-wiring.ts +97 -0
- package/src/execution/engine/common/kill-chain.ts +42 -10
- package/src/execution/engine/common/nesting-guard.ts +66 -0
- package/src/execution/engine/common/persona-router.ts +18 -8
- package/src/execution/engine/common/pool-manager.ts +332 -78
- package/src/execution/engine/common/session-view-service.ts +506 -0
- package/src/execution/engine/common/session-view-types.ts +125 -0
- package/src/execution/engine/engine-discovery.ts +12 -16
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
- package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
- package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
- package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
- package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
- package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
- package/src/execution/engine/engines/pi/reader.ts +11 -0
- package/src/execution/engine/engines/pi/registration.ts +4 -2
- package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
- package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
- package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
- package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
- package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
- package/src/execution/engine/engines/zcode/connection.ts +598 -0
- package/src/execution/engine/engines/zcode/constants.ts +147 -14
- package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
- package/src/execution/engine/engines/zcode/parser.ts +12 -346
- package/src/execution/engine/engines/zcode/preparer.ts +22 -150
- package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
- package/src/execution/engine/host-task-spec.ts +32 -36
- package/src/execution/engine/model-validation.ts +177 -0
- package/src/execution/engine/port.ts +65 -7
- package/src/execution/engine/registry.ts +53 -0
- package/src/execution/engine/routing.ts +75 -1
- package/src/execution/engine/types.ts +30 -103
- package/src/execution/execution-record.ts +222 -144
- package/src/execution/finalize-record.ts +181 -85
- package/src/execution/idle-gc.ts +28 -0
- package/src/execution/lifecycle-manager.ts +27 -3
- package/src/execution/lifecycle-predicates.ts +1 -1
- package/src/execution/manifest-store.ts +53 -62
- package/src/execution/notifier.ts +10 -10
- package/src/execution/notify-host.ts +212 -0
- package/src/execution/notify-ledger.ts +117 -16
- package/src/execution/record-entry.ts +8 -2
- package/src/execution/record-store.ts +148 -88
- package/src/execution/round-settlement.ts +93 -0
- package/src/execution/session-file-gc.ts +96 -62
- package/src/execution/session-pending.ts +213 -62
- package/src/execution/session-reconstructor.ts +256 -143
- package/src/execution/sessions-index.ts +127 -102
- package/src/execution/settled-watchdog.ts +311 -0
- package/src/execution/subagent-actions-core.ts +686 -0
- package/src/execution/subagent-service.ts +1106 -907
- package/src/execution/subprocess-agent-runner.ts +144 -97
- package/src/execution/types.ts +9 -0
- package/src/execution/ui-request-handler-factory.ts +126 -75
- package/src/execution/ui-request-queue.ts +50 -19
- package/src/execution/worktree-git-ops.ts +397 -0
- package/src/execution/worktree-manager.ts +72 -10
- package/src/execution/worktree-registry.ts +6 -12
- package/src/index.ts +542 -9
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
- package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
- package/src/orchestration/__tests__/args-meta.test.ts +358 -0
- package/src/orchestration/__tests__/args-validator.test.ts +14 -0
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
- package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
- package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
- package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
- package/src/orchestration/__tests__/script-generate.test.ts +314 -0
- package/src/orchestration/__tests__/script-lint.test.ts +17 -0
- package/src/orchestration/__tests__/test-mocks.ts +2 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
- package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
- package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
- package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
- package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
- package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
- package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
- package/src/orchestration/agent-opts-resolver.ts +8 -8
- package/src/orchestration/args-meta.ts +198 -0
- package/src/orchestration/args-validator.ts +53 -31
- package/src/orchestration/execute-agent-call.ts +25 -79
- package/src/orchestration/file-run-store.ts +327 -0
- package/src/orchestration/launcher.ts +160 -104
- package/src/orchestration/lifecycle.ts +371 -97
- package/src/orchestration/models/agent-call.ts +7 -7
- package/src/orchestration/models/budget.ts +5 -5
- package/src/orchestration/models/ports.ts +4 -4
- package/src/orchestration/models/run-runtime.ts +15 -15
- package/src/orchestration/models/trace.ts +9 -9
- package/src/orchestration/models/types.ts +111 -22
- package/src/orchestration/models/workflow-run.ts +28 -28
- package/src/orchestration/models/workflow-script.ts +4 -4
- package/src/orchestration/run-snapshot.ts +266 -0
- package/src/orchestration/script-generate.ts +154 -0
- package/src/orchestration/script-lint.ts +123 -90
- package/src/orchestration/worker-handle.ts +10 -10
- package/src/orchestration/worker-host.ts +10 -10
- package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
- package/src/orchestration/worker-script-builder.ts +401 -346
- package/src/orchestration/workflow-files.ts +37 -11
- package/src/orchestration/workflow-run-summary.ts +69 -0
- package/src/orchestration/workflow-script-registry-impl.ts +31 -9
- package/src/shared/__tests__/agent-ref.test.ts +209 -2
- package/src/shared/__tests__/atomic-write.test.ts +267 -0
- package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
- package/src/shared/__tests__/injection-render.test.ts +518 -0
- package/src/shared/__tests__/meta-parser.test.ts +151 -0
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
- package/src/shared/__tests__/resource-discovery.test.ts +261 -217
- package/src/shared/agent-ref.ts +143 -2
- package/src/shared/atomic-write.ts +323 -0
- package/src/shared/bounded-serialize.ts +154 -0
- package/src/shared/injection-render.ts +279 -0
- package/src/shared/meta-parser.ts +182 -68
- package/src/shared/model-ref.ts +15 -2
- package/src/shared/resource-discovery.ts +97 -204
- package/src/shared/resource-meta.ts +14 -0
- package/src/shared/schema-jsonify.ts +1 -1
- package/src/shared/xml-injection.ts +9 -9
- package/workflows/README.md +9 -9
- package/workflows/chain.js +4 -2
- package/workflows/map-reduce.js +5 -3
- package/workflows/parallel.js +5 -3
- package/workflows/review-fix-loop-utils.cjs +165 -92
- package/workflows/review-fix-loop-utils.d.cts +287 -0
- package/workflows/review-fix-loop.js +18 -7
- package/workflows/scatter-gather.js +4 -2
- package/dist/chunk-3VOERJPJ.js +0 -22
- package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/execute-options-mapper.ts +0 -115
- package/src/execution/session-runner.ts +0 -1789
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// src/orchestration/__tests__/workflow-files.test.ts
|
|
2
|
+
//
|
|
3
|
+
// C4-core-script-pipeline(convergence W4 / D-6)目录参数化测试:
|
|
4
|
+
// - saveWorkflow/deleteWorkflow 注入非 .pi 目录:真实落盘 / rename 迁移 / 删除;
|
|
5
|
+
// - 既有语义回归(文案逐字):tmp 不存在拒、目标已存在拒、运行中拒、
|
|
6
|
+
// 两候选路径均无拒;
|
|
7
|
+
// - 缺省目录 = pi 布局(相对 cwd resolve,pi 现两参调用形态行为不变)。
|
|
8
|
+
// 设计权威源:docs/design/subagent-core-convergence.md §3.2 D-6。
|
|
9
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
import { describe, expect, it, vi } from "vitest";
|
|
14
|
+
|
|
15
|
+
import { deleteWorkflow, saveWorkflow } from "../workflow-files.ts";
|
|
16
|
+
|
|
17
|
+
/** 构造注入布局(非 .pi):{root}/host-tmp + {root}/host-saved(saved 不预创建,
|
|
18
|
+
* 覆盖 saveWorkflow 内 mkdirSync recursive 自动建目录路径)。 */
|
|
19
|
+
function makeHostLayout(): { root: string; tmpDir: string; savedDir: string } {
|
|
20
|
+
const root = mkdtempSync(join(tmpdir(), "subagent-core-wf-"));
|
|
21
|
+
const tmpDir = join(root, "host-tmp");
|
|
22
|
+
const savedDir = join(root, "host-saved");
|
|
23
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
24
|
+
return { root, tmpDir, savedDir };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function seedTmp(tmpDir: string, name: string, body = "// tmp body"): void {
|
|
28
|
+
writeFileSync(join(tmpDir, `${name}.js`), body);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ============================================================
|
|
32
|
+
// saveWorkflow(目录注入)
|
|
33
|
+
// ============================================================
|
|
34
|
+
|
|
35
|
+
describe("saveWorkflow 目录注入(非 .pi 布局)", () => {
|
|
36
|
+
it("注入 tmpDir/savedDir:tmp 落盘 → rename 迁移,savedDir 自动创建", async () => {
|
|
37
|
+
const { root, tmpDir, savedDir } = makeHostLayout();
|
|
38
|
+
try {
|
|
39
|
+
seedTmp(tmpDir, "alpha", "// alpha body");
|
|
40
|
+
const msg = await saveWorkflow("alpha", undefined, { tmpDir, savedDir });
|
|
41
|
+
expect(msg).toBe(`Saved 'alpha' → 'alpha' (${join(savedDir, "alpha.js")})`);
|
|
42
|
+
// rename 语义:tmp 文件保存后消失
|
|
43
|
+
expect(existsSync(join(tmpDir, "alpha.js"))).toBe(false);
|
|
44
|
+
expect(readFileSync(join(savedDir, "alpha.js"), "utf-8")).toBe("// alpha body");
|
|
45
|
+
} finally {
|
|
46
|
+
rmSync(root, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("newName 改名:tmp/alpha.js → saved/beta.js", async () => {
|
|
51
|
+
const { root, tmpDir, savedDir } = makeHostLayout();
|
|
52
|
+
try {
|
|
53
|
+
seedTmp(tmpDir, "alpha", "// renamed body");
|
|
54
|
+
const msg = await saveWorkflow("alpha", "beta", { tmpDir, savedDir });
|
|
55
|
+
expect(msg).toBe(`Saved 'alpha' → 'beta' (${join(savedDir, "beta.js")})`);
|
|
56
|
+
expect(existsSync(join(savedDir, "alpha.js"))).toBe(false);
|
|
57
|
+
expect(existsSync(join(savedDir, "beta.js"))).toBe(true);
|
|
58
|
+
} finally {
|
|
59
|
+
rmSync(root, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("目标已存在 → 拒(文案逐字)", async () => {
|
|
64
|
+
const { root, tmpDir, savedDir } = makeHostLayout();
|
|
65
|
+
try {
|
|
66
|
+
seedTmp(tmpDir, "alpha");
|
|
67
|
+
mkdirSync(savedDir, { recursive: true });
|
|
68
|
+
writeFileSync(join(savedDir, "beta.js"), "// existing");
|
|
69
|
+
await expect(saveWorkflow("alpha", "beta", { tmpDir, savedDir })).rejects.toThrow(
|
|
70
|
+
"'beta' already exists in saved workflows. Use a different name.",
|
|
71
|
+
);
|
|
72
|
+
// 原文件不动
|
|
73
|
+
expect(existsSync(join(tmpDir, "alpha.js"))).toBe(true);
|
|
74
|
+
} finally {
|
|
75
|
+
rmSync(root, { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("tmp 不存在 → 拒(文案逐字)", async () => {
|
|
80
|
+
const { root, tmpDir, savedDir } = makeHostLayout();
|
|
81
|
+
try {
|
|
82
|
+
await expect(saveWorkflow("ghost", undefined, { tmpDir, savedDir })).rejects.toThrow(
|
|
83
|
+
"Temporary workflow 'ghost' not found",
|
|
84
|
+
);
|
|
85
|
+
} finally {
|
|
86
|
+
rmSync(root, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ============================================================
|
|
92
|
+
// deleteWorkflow(目录注入)
|
|
93
|
+
// ============================================================
|
|
94
|
+
|
|
95
|
+
describe("deleteWorkflow 目录注入(非 .pi 布局)", () => {
|
|
96
|
+
it("注入目录:tmp 候选优先删除", () => {
|
|
97
|
+
const { root, tmpDir, savedDir } = makeHostLayout();
|
|
98
|
+
try {
|
|
99
|
+
seedTmp(tmpDir, "dup", "// tmp copy");
|
|
100
|
+
mkdirSync(savedDir, { recursive: true });
|
|
101
|
+
writeFileSync(join(savedDir, "dup.js"), "// saved copy");
|
|
102
|
+
const msg = deleteWorkflow("dup", () => false, { tmpDir, savedDir });
|
|
103
|
+
expect(msg).toBe(`Deleted workflow 'dup' (${join(tmpDir, "dup.js")})`);
|
|
104
|
+
expect(existsSync(join(tmpDir, "dup.js"))).toBe(false);
|
|
105
|
+
expect(existsSync(join(savedDir, "dup.js"))).toBe(true);
|
|
106
|
+
} finally {
|
|
107
|
+
rmSync(root, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("注入目录:tmp 无 → saved 候选删除", () => {
|
|
112
|
+
const { root, tmpDir, savedDir } = makeHostLayout();
|
|
113
|
+
try {
|
|
114
|
+
mkdirSync(savedDir, { recursive: true });
|
|
115
|
+
writeFileSync(join(savedDir, "gamma.js"), "// saved body");
|
|
116
|
+
const msg = deleteWorkflow("gamma", () => false, { tmpDir, savedDir });
|
|
117
|
+
expect(msg).toBe(`Deleted workflow 'gamma' (${join(savedDir, "gamma.js")})`);
|
|
118
|
+
expect(existsSync(join(savedDir, "gamma.js"))).toBe(false);
|
|
119
|
+
} finally {
|
|
120
|
+
rmSync(root, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("运行中 → 拒(文案逐字,防删运行中脚本)", () => {
|
|
125
|
+
const { root, tmpDir, savedDir } = makeHostLayout();
|
|
126
|
+
try {
|
|
127
|
+
seedTmp(tmpDir, "busy");
|
|
128
|
+
expect(() =>
|
|
129
|
+
deleteWorkflow("busy", (name) => name === "busy", { tmpDir, savedDir }),
|
|
130
|
+
).toThrow("Cannot delete 'busy': workflow is currently running. Abort it first.");
|
|
131
|
+
expect(existsSync(join(tmpDir, "busy.js"))).toBe(true);
|
|
132
|
+
} finally {
|
|
133
|
+
rmSync(root, { recursive: true, force: true });
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("两候选均无 → 拒(文案逐字)", () => {
|
|
138
|
+
const { root, tmpDir, savedDir } = makeHostLayout();
|
|
139
|
+
try {
|
|
140
|
+
expect(() => deleteWorkflow("void", () => false, { tmpDir, savedDir })).toThrow(
|
|
141
|
+
"Workflow file 'void' not found",
|
|
142
|
+
);
|
|
143
|
+
} finally {
|
|
144
|
+
rmSync(root, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ============================================================
|
|
150
|
+
// 缺省目录(pi 布局回归——向后兼容:pi 现两参调用形态行为不变)
|
|
151
|
+
// ============================================================
|
|
152
|
+
|
|
153
|
+
describe("缺省目录 = pi 布局(向后兼容回归)", () => {
|
|
154
|
+
it("save 缺省:<cwd>/.pi/workflows/.tmp → <cwd>/.pi/workflows", async () => {
|
|
155
|
+
const fakeCwd = mkdtempSync(join(tmpdir(), "subagent-core-cwd-"));
|
|
156
|
+
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(fakeCwd);
|
|
157
|
+
try {
|
|
158
|
+
const tmpDir = join(fakeCwd, ".pi", "workflows", ".tmp");
|
|
159
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
160
|
+
seedTmp(tmpDir, "pi-wf", "// pi layout");
|
|
161
|
+
const msg = await saveWorkflow("pi-wf");
|
|
162
|
+
expect(msg).toBe(`Saved 'pi-wf' → 'pi-wf' (${join(fakeCwd, ".pi", "workflows", "pi-wf.js")})`);
|
|
163
|
+
expect(existsSync(join(tmpDir, "pi-wf.js"))).toBe(false);
|
|
164
|
+
expect(existsSync(join(fakeCwd, ".pi", "workflows", "pi-wf.js"))).toBe(true);
|
|
165
|
+
} finally {
|
|
166
|
+
cwdSpy.mockRestore();
|
|
167
|
+
rmSync(fakeCwd, { recursive: true, force: true });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("delete 缺省:两候选 = .pi/workflows/.tmp 与 .pi/workflows", () => {
|
|
172
|
+
const fakeCwd = mkdtempSync(join(tmpdir(), "subagent-core-cwd-"));
|
|
173
|
+
const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(fakeCwd);
|
|
174
|
+
try {
|
|
175
|
+
const savedDir = join(fakeCwd, ".pi", "workflows");
|
|
176
|
+
mkdirSync(savedDir, { recursive: true });
|
|
177
|
+
writeFileSync(join(savedDir, "saved-only.js"), "// body");
|
|
178
|
+
const msg = deleteWorkflow("saved-only", () => false);
|
|
179
|
+
expect(msg).toBe(`Deleted workflow 'saved-only' (${join(savedDir, "saved-only.js")})`);
|
|
180
|
+
expect(existsSync(join(savedDir, "saved-only.js"))).toBe(false);
|
|
181
|
+
} finally {
|
|
182
|
+
cwdSpy.mockRestore();
|
|
183
|
+
rmSync(fakeCwd, { recursive: true, force: true });
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -24,7 +24,7 @@ vi.mock("../lifecycle.ts", () => ({
|
|
|
24
24
|
scheduleTimeBudget: vi.fn(() => undefined),
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
|
-
import { handleWorkerMessage } from "../
|
|
27
|
+
import { handleWorkerMessage } from "../worker-message-pump.ts";
|
|
28
28
|
import { executeNestedWorkflow, type LauncherDeps } from "../launcher.ts";
|
|
29
29
|
import { runWorkflow } from "../lifecycle.ts";
|
|
30
30
|
import { Budget } from "../models/budget.ts";
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// workflow-run-summary.test.ts —— runSummary 投影 + isScriptRunning 判定测试(U7/B5/D8)。
|
|
2
|
+
//
|
|
3
|
+
// 覆盖(验收条款③):
|
|
4
|
+
// - runSummary:字段投影全断言(running / done 两形态;slug/error/completedAt 缺省透传)
|
|
5
|
+
// - isScriptRunning:真(同名 running)/ 假(同名 done、异名 running、空 Map)两分支
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
|
|
8
|
+
import { isScriptRunning, runSummary } from "../workflow-run-summary.ts";
|
|
9
|
+
import { Budget } from "../models/budget.ts";
|
|
10
|
+
import type { RunSpec } from "../models/run-spec.ts";
|
|
11
|
+
import { Trace } from "../models/trace.ts";
|
|
12
|
+
import { WorkflowRun } from "../models/workflow-run.ts";
|
|
13
|
+
|
|
14
|
+
function makeSpec(scriptName: string, slug?: string): RunSpec {
|
|
15
|
+
return {
|
|
16
|
+
scriptSource: "execute() {}",
|
|
17
|
+
args: {},
|
|
18
|
+
scriptName,
|
|
19
|
+
...(slug !== undefined ? { slug } : {}),
|
|
20
|
+
scriptPath: "/fake/test.js",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeRun(
|
|
25
|
+
runId: string,
|
|
26
|
+
opts: {
|
|
27
|
+
status?: "running" | "done";
|
|
28
|
+
scriptName?: string;
|
|
29
|
+
slug?: string;
|
|
30
|
+
reason?: "completed" | "failed" | "aborted" | "time_limited";
|
|
31
|
+
error?: string;
|
|
32
|
+
startedAt?: string;
|
|
33
|
+
completedAt?: string;
|
|
34
|
+
} = {},
|
|
35
|
+
): WorkflowRun {
|
|
36
|
+
const status = opts.status ?? "running";
|
|
37
|
+
return WorkflowRun.reconstruct(
|
|
38
|
+
runId,
|
|
39
|
+
makeSpec(opts.scriptName ?? "deploy-site", opts.slug),
|
|
40
|
+
{
|
|
41
|
+
status,
|
|
42
|
+
...(status === "done" ? { reason: opts.reason ?? "completed" } : {}),
|
|
43
|
+
budget: new Budget({ maxTokens: 1000 }),
|
|
44
|
+
calls: new Map(),
|
|
45
|
+
trace: new Trace(),
|
|
46
|
+
errorLogs: [],
|
|
47
|
+
...(opts.error !== undefined ? { error: opts.error } : {}),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
startedAt: opts.startedAt ?? "2026-08-30T00:00:00.000Z",
|
|
51
|
+
...(opts.completedAt !== undefined ? { completedAt: opts.completedAt } : {}),
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe("runSummary — 字段投影(字段以 core WorkflowRun 为准)", () => {
|
|
57
|
+
it("running run:name=scriptName、slug、status、startedAt 投影;completedAt/reason 为 undefined", () => {
|
|
58
|
+
const run = makeRun("wf-r1", { status: "running", scriptName: "deploy-site", slug: "deploy" });
|
|
59
|
+
|
|
60
|
+
expect(runSummary(run)).toEqual({
|
|
61
|
+
runId: "wf-r1",
|
|
62
|
+
name: "deploy-site",
|
|
63
|
+
slug: "deploy",
|
|
64
|
+
status: "running",
|
|
65
|
+
reason: undefined,
|
|
66
|
+
startedAt: "2026-08-30T00:00:00.000Z",
|
|
67
|
+
completedAt: undefined,
|
|
68
|
+
error: undefined,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("done run:reason/completedAt/error 投影(对齐 pi toRunSummary 字段集)", () => {
|
|
73
|
+
const run = makeRun("wf-d1", {
|
|
74
|
+
status: "done",
|
|
75
|
+
reason: "failed",
|
|
76
|
+
error: "agent timeout",
|
|
77
|
+
startedAt: "2026-08-30T10:00:00.000Z",
|
|
78
|
+
completedAt: "2026-08-30T10:05:00.000Z",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(runSummary(run)).toEqual({
|
|
82
|
+
runId: "wf-d1",
|
|
83
|
+
name: "deploy-site",
|
|
84
|
+
slug: undefined,
|
|
85
|
+
status: "done",
|
|
86
|
+
reason: "failed",
|
|
87
|
+
startedAt: "2026-08-30T10:00:00.000Z",
|
|
88
|
+
completedAt: "2026-08-30T10:05:00.000Z",
|
|
89
|
+
error: "agent timeout",
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("isScriptRunning — 真假两分支", () => {
|
|
95
|
+
it("真:同名 script 仍在 running", () => {
|
|
96
|
+
const runs = new Map<string, WorkflowRun>([
|
|
97
|
+
["wf-1", makeRun("wf-1", { status: "done", scriptName: "other-wf" })],
|
|
98
|
+
["wf-2", makeRun("wf-2", { status: "running", scriptName: "deploy-site" })],
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
expect(isScriptRunning(runs, "deploy-site")).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("假:同名但已 done(running 状态白名单)", () => {
|
|
105
|
+
const runs = new Map<string, WorkflowRun>([
|
|
106
|
+
["wf-1", makeRun("wf-1", { status: "done", scriptName: "deploy-site" })],
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
expect(isScriptRunning(runs, "deploy-site")).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("假:异名 running / 空 Map", () => {
|
|
113
|
+
const runs = new Map<string, WorkflowRun>([
|
|
114
|
+
["wf-1", makeRun("wf-1", { status: "running", scriptName: "other-wf" })],
|
|
115
|
+
]);
|
|
116
|
+
expect(isScriptRunning(runs, "deploy-site")).toBe(false);
|
|
117
|
+
expect(isScriptRunning(new Map(), "deploy-site")).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workflow-script-registry-impl 测试:sink 设计 U1 导出形态。
|
|
3
|
+
*
|
|
4
|
+
* 覆盖:WorkflowScript 类 re-export 可用性 + loadWorkflowScriptByPath 工厂加载
|
|
5
|
+
* (真实临时脚本文件)、非法引用拒绝(含 ⛔2 `..` 段收紧对工厂生效)、
|
|
6
|
+
* available=false stub 语义。
|
|
7
|
+
*
|
|
8
|
+
* 策略对齐 config-loader.test.ts:真实临时目录 + 真实脚本文件(meta 提取走真实
|
|
9
|
+
* 文件读取),只 mock findWorkspaceRoot(缓存桶 key,耦合 process.cwd 难隔离)与
|
|
10
|
+
* homedir(`~/` 引用展开锚点)。
|
|
11
|
+
*/
|
|
12
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
16
|
+
|
|
17
|
+
vi.mock("node:os", async (importOriginal) => {
|
|
18
|
+
const actual = await importOriginal<typeof import("node:os")>();
|
|
19
|
+
return { ...actual, homedir: vi.fn(() => "/nonexistent-home-for-tests") };
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
vi.mock("../../shared/resource-discovery.ts", async (importOriginal) => {
|
|
23
|
+
const actual = await importOriginal<
|
|
24
|
+
typeof import("../../shared/resource-discovery.ts")
|
|
25
|
+
>();
|
|
26
|
+
return {
|
|
27
|
+
...actual,
|
|
28
|
+
findWorkspaceRoot: vi.fn(() => "/unused-by-default"),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
import { invalidateCache } from "../config-loader.ts";
|
|
33
|
+
import { loadWorkflowScriptByPath, WorkflowScript } from "../workflow-script-registry-impl.ts";
|
|
34
|
+
import { homedir } from "node:os";
|
|
35
|
+
|
|
36
|
+
const mockedHomedir = vi.mocked(homedir);
|
|
37
|
+
|
|
38
|
+
/** 有效 workflow 脚本内容(@pi-meta 块注释 + agent 入口,同 config-loader.test 范式;phases 须 YAML 数组)。 */
|
|
39
|
+
const SCRIPT_SOURCE =
|
|
40
|
+
"/* @pi-meta\nname: factory-wf\ndescription: factory test\nphases: []\n*/\nagent({ prompt: \"x\" });\n";
|
|
41
|
+
|
|
42
|
+
async function writeScript(content: string, fileName = "factory-wf.js"): Promise<string> {
|
|
43
|
+
const dir = await mkdtemp(join(tmpdir(), "wf-registry-impl-test-"));
|
|
44
|
+
const p = join(dir, fileName);
|
|
45
|
+
await writeFile(p, content, "utf-8");
|
|
46
|
+
return p;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe("loadWorkflowScriptByPath / WorkflowScript 导出形态(U1)", () => {
|
|
50
|
+
let tempDirs: string[] = [];
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
invalidateCache();
|
|
54
|
+
// 重置 `~/` 用例留下的 homedir mock 残留(mockReturnValue 跨测试持续)
|
|
55
|
+
mockedHomedir.mockReturnValue("/nonexistent-home-for-tests");
|
|
56
|
+
tempDirs = [];
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(async () => {
|
|
60
|
+
invalidateCache();
|
|
61
|
+
for (const dir of tempDirs) {
|
|
62
|
+
await rm(dir, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
async function trackedWrite(content: string, fileName?: string): Promise<string> {
|
|
67
|
+
const p = await writeScript(content, fileName);
|
|
68
|
+
tempDirs.push(join(p, ".."));
|
|
69
|
+
return p;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
it("按绝对路径加载:返回 WorkflowScript 实例(meta/sourceCode 填充,available=true)", async () => {
|
|
73
|
+
const p = await trackedWrite(SCRIPT_SOURCE);
|
|
74
|
+
const script = await loadWorkflowScriptByPath(p);
|
|
75
|
+
expect(script).toBeInstanceOf(WorkflowScript);
|
|
76
|
+
expect(script!.name).toBe("factory-wf");
|
|
77
|
+
expect(script!.available).toBe(true);
|
|
78
|
+
expect(script!.path).toBe(p);
|
|
79
|
+
expect(script!.sourceCode).toBe(SCRIPT_SOURCE);
|
|
80
|
+
expect(script!.meta.description).toBe("factory test");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("re-export 的 WorkflowScript 类与实例同源:validate/toExecutable 实体操作可用", async () => {
|
|
84
|
+
const p = await trackedWrite(SCRIPT_SOURCE);
|
|
85
|
+
const script = await loadWorkflowScriptByPath(p);
|
|
86
|
+
expect(script).toBeInstanceOf(WorkflowScript);
|
|
87
|
+
// 实体操作(validate 委托 lintScript / toExecutable 返回可执行源)经导出类实例可用
|
|
88
|
+
const lint = script!.validate();
|
|
89
|
+
expect(lint.valid).toBe(true);
|
|
90
|
+
expect(Array.isArray(lint.findings)).toBe(true);
|
|
91
|
+
expect(script!.toExecutable()).toBe(SCRIPT_SOURCE);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("文件不可读/不存在:available=false 的 stub(非 undefined——loader never throws)", async () => {
|
|
95
|
+
const p = await trackedWrite(SCRIPT_SOURCE, "gone.js");
|
|
96
|
+
await rm(p);
|
|
97
|
+
const script = await loadWorkflowScriptByPath(p);
|
|
98
|
+
expect(script).toBeDefined();
|
|
99
|
+
expect(script!.available).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("非法引用返回 undefined:相对路径 / 非 .js 扩展名", async () => {
|
|
103
|
+
await trackedWrite(SCRIPT_SOURCE);
|
|
104
|
+
expect(await loadWorkflowScriptByPath("relative/wf.js")).toBeUndefined();
|
|
105
|
+
expect(await loadWorkflowScriptByPath("/proj/wf.txt")).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("⛔2:含 .. 段的路径引用被拒(undefined——收紧对工厂消费面生效)", async () => {
|
|
109
|
+
await trackedWrite(SCRIPT_SOURCE);
|
|
110
|
+
expect(await loadWorkflowScriptByPath("/x/../factory-wf.js")).toBeUndefined();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("~/ 前缀引用:展开 homedir 后加载(barrel 消费面的合法引用形态)", async () => {
|
|
114
|
+
const fakeHome = await mkdtemp(join(tmpdir(), "wf-registry-impl-home-"));
|
|
115
|
+
tempDirs.push(fakeHome);
|
|
116
|
+
mockedHomedir.mockReturnValue(fakeHome);
|
|
117
|
+
const p = join(fakeHome, "home-wf.js");
|
|
118
|
+
await writeFile(p, SCRIPT_SOURCE, "utf-8");
|
|
119
|
+
const script = await loadWorkflowScriptByPath("~/home-wf.js");
|
|
120
|
+
expect(script).toBeInstanceOf(WorkflowScript);
|
|
121
|
+
expect(script!.name).toBe("factory-wf");
|
|
122
|
+
expect(script!.path).toBe(p);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* agent ref 处理(systemPrompt/model/thinkingLevel)已移交 resolveIdentity(execution 层,
|
|
11
11
|
* 经 getAgentConfig + resolveModel 完整覆盖),消除双重注入与 model 层级混乱。
|
|
12
12
|
*
|
|
13
|
-
* 调用方:engine/
|
|
13
|
+
* 调用方:engine/worker-message-pump.ts dispatchAgentCall(每次 agent-call 消息)。
|
|
14
14
|
* - skill → resolveSkillPath → skillPath(--skill)
|
|
15
15
|
* - schema → 结构化输出指令内容直传 appendSystemPrompt(--append-system-prompt)+ schemaEnv(PI_WORKFLOW_SCHEMA)
|
|
16
16
|
*
|
|
@@ -145,7 +145,7 @@ export function formatSchemaInstruction(schema: Record<string, unknown>): string
|
|
|
145
145
|
export function resolveAgentOpts(opts: AgentCallOpts): ResolveResult {
|
|
146
146
|
const appendSystemPrompt: string[] = [];
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
// Resolve skill name to SKILL.md path
|
|
149
149
|
if (opts.skill) {
|
|
150
150
|
const skillPath = resolveSkillPath(opts.skill);
|
|
151
151
|
if (!skillPath) {
|
|
@@ -154,17 +154,17 @@ export function resolveAgentOpts(opts: AgentCallOpts): ResolveResult {
|
|
|
154
154
|
opts = { ...opts, skillPath };
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
// Inject schema as structured-output instruction into appendSystemPrompt (content,
|
|
158
|
+
// not temp file) and set environment variable for conditional tool + hook activation.
|
|
159
|
+
// M2 fix: previously wrote the instruction to a temp file and pushed the FILE PATH,
|
|
160
|
+
// which got concatenated into the final append file as path garbage — the SO instruction
|
|
161
|
+
// never reached the subprocess. Now the instruction content is pushed directly.
|
|
162
162
|
if (opts.schema) {
|
|
163
163
|
// IF7(#13):formatSchemaInstruction 与 schemaEnv 对同一 schema 对象引用共享
|
|
164
164
|
// WeakMap 缓存条目(compact stringify 整个 dispatch 只发生一次)
|
|
165
165
|
appendSystemPrompt.push(formatSchemaInstruction(opts.schema));
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
// Set env var for structured-output extension to activate tool + hook
|
|
168
168
|
opts = { ...opts, schemaEnv: stringifySchemaCached(opts.schema, "compact") };
|
|
169
169
|
}
|
|
170
170
|
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// src/orchestration/args-meta.ts
|
|
2
|
+
//
|
|
3
|
+
// U9(D9 / B3 / B4):workflow @pi-meta parameters(JSON Schema)→ 已知参数键集、
|
|
4
|
+
// args 平铺检测、参数归一组装。平移自 pi-sw tool-workflow.ts:136-196(m6「schema 即
|
|
5
|
+
// SSOT」——替代 21 键硬编码 KNOWN_ARG_KEYS,消除与参数定义的漂移面)。
|
|
6
|
+
//
|
|
7
|
+
// 相对 pi-sw 原实现的两处平台耦合剥离(行为等值,见实施计划偏差登记表):
|
|
8
|
+
// - TOOL_TOP_LEVEL(pi workflow tool 顶层键集,pi 侧 schema 事实)→ 参数化 reservedKeys
|
|
9
|
+
// 由宿主注入(同 U1 knownNames / D7 queuePolicy 的宿主差异参数化先例);core 引擎
|
|
10
|
+
// 无关,不内嵌任何宿主 tool schema。pi-sw 消费改造时传自己的 TOOL_TOP_LEVEL 即得
|
|
11
|
+
// 等值行为。
|
|
12
|
+
// - @zhushanwen/pi-extension-logger → core logger facade(同语义 warn,平台中立)。
|
|
13
|
+
//
|
|
14
|
+
// 平铺检测语义(m6 定稿,勿改):弱模型把 args 子字段平铺到 params 顶层是 P0 级
|
|
15
|
+
// 静默失败(args ?? {} 会静默空启动),检测命中必须显式暴露——core 以 warnings 数据
|
|
16
|
+
// 承载,是否升级为 fatal 由宿主裁决(pi 现行为是 throw,消费改造时 adapter 对
|
|
17
|
+
// flattened_args 警告 throw 即等值)。
|
|
18
|
+
|
|
19
|
+
import { getLogger } from "../core/logger.ts";
|
|
20
|
+
|
|
21
|
+
const logger = getLogger("args-meta");
|
|
22
|
+
|
|
23
|
+
const EMPTY_RESERVED_KEYS: ReadonlySet<string> = new Set<string>();
|
|
24
|
+
|
|
25
|
+
/** 已知参数键集:exact = properties 精确键;patterns = patternProperties 原样转正则。 */
|
|
26
|
+
export interface ArgKeySet {
|
|
27
|
+
readonly exact: ReadonlySet<string>;
|
|
28
|
+
readonly patterns: readonly RegExp[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 键集构建选项(宿主差异注入点)。 */
|
|
32
|
+
export interface ArgMetaOptions {
|
|
33
|
+
/**
|
|
34
|
+
* 宿主调用信封顶层保留键(如 pi workflow tool 的 action/name/slug/args/model/...)。
|
|
35
|
+
* workflow 参数名与保留键撞名时,顶层同名键是信封参数而非平铺(m6 评审 M-3):
|
|
36
|
+
* exact 构建排除保留键;能命中任一保留键的 pattern 整条跳过(^run.*$ 类会误伤
|
|
37
|
+
* runId/name 等合法调用,m6 exec-review S1)。缺省空集(core 中性形态)。
|
|
38
|
+
*/
|
|
39
|
+
readonly reservedKeys?: ReadonlySet<string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* properties → exact 精确键集(排除 reservedKeys——workflow 参数名与保留键撞名时,
|
|
44
|
+
* 顶层同名键是信封参数而非平铺,m6 评审 M-3)。非对象形状(含 undefined/null)
|
|
45
|
+
* 返回空集。
|
|
46
|
+
*/
|
|
47
|
+
function collectExactKeys(props: unknown, reserved: ReadonlySet<string>): Set<string> {
|
|
48
|
+
const exact = new Set<string>();
|
|
49
|
+
if (props === null || typeof props !== "object") return exact;
|
|
50
|
+
for (const k of Object.keys(props as Record<string, unknown>)) {
|
|
51
|
+
if (!reserved.has(k)) exact.add(k);
|
|
52
|
+
}
|
|
53
|
+
return exact;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* patternProperties → 正则数组(schema pattern 已是正则源码,直接 new RegExp;
|
|
58
|
+
* 自动兼容 \d{2} 等变体)。跳过能命中保留键的 pattern——否则 ^run.*$ 类 pattern
|
|
59
|
+
* 会匹配 runId/name 等信封键,合法调用恒误报(m6 exec-review S1)。非法 pattern
|
|
60
|
+
* (schema 校验 m3 已保证合法,双保险)跳过并 warn。
|
|
61
|
+
*/
|
|
62
|
+
function compilePatterns(pp: unknown, reserved: ReadonlySet<string>): RegExp[] {
|
|
63
|
+
const patterns: RegExp[] = [];
|
|
64
|
+
if (pp === null || typeof pp !== "object") return patterns;
|
|
65
|
+
for (const p of Object.keys(pp as Record<string, unknown>)) {
|
|
66
|
+
try {
|
|
67
|
+
const re = new RegExp(p);
|
|
68
|
+
if ([...reserved].some((tk) => re.test(tk))) continue;
|
|
69
|
+
patterns.push(re);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
logger.warn(`[args-meta] patternProperties 非法正则跳过: ${p}`, {
|
|
72
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return patterns;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 从 workflow 参数 schema(@pi-meta parameters)动态构建平铺检测的已知键集。
|
|
81
|
+
*
|
|
82
|
+
* - exact:properties keys(精确匹配,排除 reservedKeys)
|
|
83
|
+
* - patterns:patternProperties 原样转正则数组(schema pattern 已是正则源码,
|
|
84
|
+
* 直接 new RegExp;自动兼容 \d{2} 等变体)
|
|
85
|
+
* - meta 缺失/非对象 → 空键集(legacy const-meta 类无参数契约,检测跳过)
|
|
86
|
+
*/
|
|
87
|
+
export function argKeysFromMeta(
|
|
88
|
+
meta: Record<string, unknown> | undefined | null,
|
|
89
|
+
options?: ArgMetaOptions,
|
|
90
|
+
): ArgKeySet {
|
|
91
|
+
const reserved = options?.reservedKeys ?? EMPTY_RESERVED_KEYS;
|
|
92
|
+
if (meta === undefined || meta === null || typeof meta !== "object") {
|
|
93
|
+
return { exact: new Set<string>(), patterns: [] };
|
|
94
|
+
}
|
|
95
|
+
const schema = meta as Record<string, unknown>;
|
|
96
|
+
return {
|
|
97
|
+
exact: collectExactKeys(schema.properties, reserved),
|
|
98
|
+
patterns: compilePatterns(schema.patternProperties, reserved),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 平铺检测内部谓词(接收已构建键集)——normalizeArgsByMeta 复用以避免二次构建
|
|
104
|
+
* 键集触发重复 warn;公开入口 findFlattenedArgKeys(params, meta) 按设计签名接收
|
|
105
|
+
* meta 并内联构建。两路径谓词逐字同源,行为必然一致。
|
|
106
|
+
*/
|
|
107
|
+
function filterFlattenedKeys(p: Record<string, unknown>, keys: ArgKeySet): string[] {
|
|
108
|
+
const args = typeof p.args === "object" && p.args !== null ? p.args : undefined;
|
|
109
|
+
const isKnownKey = (k: string) =>
|
|
110
|
+
keys.exact.has(k) || keys.patterns.some((re) => re.test(k));
|
|
111
|
+
// hasOwnProperty.call 而非 in(原型链——constructor/toString 类参数名不被继承键掩盖)
|
|
112
|
+
return Object.keys(p).filter(
|
|
113
|
+
(k) =>
|
|
114
|
+
isKnownKey(k) &&
|
|
115
|
+
!(args !== undefined && Object.prototype.hasOwnProperty.call(args, k)),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 检测弱模型把 args 子字段平铺到 params 顶层(P0 静默失败防护)。
|
|
121
|
+
* 返回被平铺的键名列表(空 = 未平铺)。匹配谓词:exact 命中 || pattern 命中
|
|
122
|
+
* (pattern 自带数字后缀语义——loose startsWith 会误报 batchl/target1);args 内
|
|
123
|
+
* 已存在的键不算平铺(顶层 + args 共存不算平铺)。
|
|
124
|
+
*
|
|
125
|
+
* 参数取 unknown 以解耦宿主 params 类型限制、便于测试构造任意对象。
|
|
126
|
+
*/
|
|
127
|
+
export function findFlattenedArgKeys(
|
|
128
|
+
params: unknown,
|
|
129
|
+
meta: Record<string, unknown> | undefined | null,
|
|
130
|
+
options?: ArgMetaOptions,
|
|
131
|
+
): string[] {
|
|
132
|
+
if (typeof params !== "object" || params === null) return [];
|
|
133
|
+
const keys = argKeysFromMeta(meta, options);
|
|
134
|
+
return filterFlattenedKeys(params as Record<string, unknown>, keys);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 组装层警告(结构化承载,宿主决定展示/日志/fatal 升级)。 */
|
|
138
|
+
export type ArgMetaWarning =
|
|
139
|
+
| {
|
|
140
|
+
/** 无参数契约(meta 未声明或解析为空)——平铺检测跳过,args 不校验(m6 M-2 显式信号)。 */
|
|
141
|
+
code: "no_parameter_contract";
|
|
142
|
+
message: string;
|
|
143
|
+
}
|
|
144
|
+
| {
|
|
145
|
+
/** args 子字段被平铺到顶层——修正动作留宿主(pi 现行为是带 Correct 正例 throw)。 */
|
|
146
|
+
code: "flattened_args";
|
|
147
|
+
message: string;
|
|
148
|
+
keys: readonly string[];
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/** normalizeArgsByMeta 产物。 */
|
|
152
|
+
export interface NormalizedArgs {
|
|
153
|
+
/**
|
|
154
|
+
* 归一后的 args:params.args ?? {}(params 非对象时为 {})。args 字段为非对象
|
|
155
|
+
* 标量时原样透传——类型校验责任在 args-validator(schema chokepoint),本函数
|
|
156
|
+
* 不发明约束(与 m3 exec-review M2 裁决一致)。
|
|
157
|
+
*/
|
|
158
|
+
readonly args: unknown;
|
|
159
|
+
readonly warnings: readonly ArgMetaWarning[];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 组装函数:按 meta 归一 params 为 { args, warnings }(pi actionRun 参数处理段的
|
|
164
|
+
* 纯函数化——argKeysFromMeta + 空契约信号 + 平铺检测 + args 归一四步单点收口)。
|
|
165
|
+
*
|
|
166
|
+
* 警告语义与 pi 现行为对位:
|
|
167
|
+
* - no_parameter_contract ↔ pi logger.warn「未声明参数契约——平铺检测跳过」(M-2)
|
|
168
|
+
* - flattened_args ↔ pi throw「Detected ... they belong inside 'args'」(Correct
|
|
169
|
+
* 正例含宿主 tool 键 action/name——平台事实留宿主拼接,core 文案保持中立)
|
|
170
|
+
*/
|
|
171
|
+
export function normalizeArgsByMeta(
|
|
172
|
+
params: unknown,
|
|
173
|
+
meta: Record<string, unknown> | undefined | null,
|
|
174
|
+
options?: ArgMetaOptions,
|
|
175
|
+
): NormalizedArgs {
|
|
176
|
+
const keys = argKeysFromMeta(meta, options);
|
|
177
|
+
const warnings: ArgMetaWarning[] = [];
|
|
178
|
+
if (keys.exact.size === 0 && keys.patterns.length === 0) {
|
|
179
|
+
// m6 exec-review M1:无参数契约(未声明/解析空)→ 显式警告——静默退化变显式
|
|
180
|
+
warnings.push({
|
|
181
|
+
code: "no_parameter_contract",
|
|
182
|
+
message: "未声明参数契约(或解析为空)——平铺检测跳过,args 不校验",
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (typeof params === "object" && params !== null) {
|
|
186
|
+
const p = params as Record<string, unknown>;
|
|
187
|
+
const flattened = filterFlattenedKeys(p, keys);
|
|
188
|
+
if (flattened.length > 0) {
|
|
189
|
+
warnings.push({
|
|
190
|
+
code: "flattened_args",
|
|
191
|
+
keys: flattened,
|
|
192
|
+
message: `Detected ${flattened.join(", ")} at top level — they belong inside 'args'.`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return { args: p.args ?? {}, warnings };
|
|
196
|
+
}
|
|
197
|
+
return { args: {}, warnings };
|
|
198
|
+
}
|