@zhushanwen/subagent-core 0.4.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/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
- package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
- package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
- package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
- package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
- package/dist/execution/engine/engines/zcode/constants.js +17 -27
- package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
- package/dist/execution/engine/engines/zcode/reader.js +2 -2
- package/dist/index.cjs +16418 -14070
- package/dist/index.d.cts +5136 -4016
- package/dist/index.d.ts +5136 -4016
- package/dist/index.js +16343 -14048
- package/dist/{types-DpUO16pj.d.cts → types-Dv4QhSJ_.d.cts} +215 -112
- package/dist/{types-DpUO16pj.d.ts → types-Dv4QhSJ_.d.ts} +215 -112
- package/package.json +3 -3
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
- package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
- package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
- package/src/__tests__/m2-append-content-probe.test.ts +0 -1
- package/src/__tests__/manifest-store.test.ts +10 -9
- package/src/__tests__/record-store-cache.test.ts +0 -2
- package/src/__tests__/record-store-index.test.ts +1 -2
- package/src/__tests__/review-fix-loop-script.test.ts +4 -2
- package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
- package/src/__tests__/robustness-low-batch1.test.ts +6 -5
- package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
- package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
- package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
- package/src/__tests__/session-runner.test.ts +1 -1
- package/src/core/__tests__/host-services.test.ts +2 -2
- package/src/core/__tests__/logger.test.ts +1 -1
- package/src/core/error-message.ts +9 -0
- package/src/core/host-services.ts +21 -5
- package/src/core/notify-ports.ts +18 -5
- package/src/execution/__tests__/agent-profile.test.ts +4 -4
- package/src/execution/__tests__/agent-registry.test.ts +370 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
- package/src/execution/__tests__/agents-assembly.test.ts +2 -2
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
- package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
- package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
- package/src/execution/__tests__/delivery-methods.test.ts +75 -62
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
- package/src/execution/__tests__/dialog-queue.test.ts +2 -2
- package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
- package/src/execution/__tests__/execute-nesting.test.ts +34 -188
- package/src/execution/__tests__/execution-record.test.ts +217 -60
- package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
- package/src/execution/__tests__/finalize-record.test.ts +173 -17
- package/src/execution/__tests__/gc-timer.test.ts +55 -2
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
- package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
- package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
- package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
- package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
- package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
- package/src/execution/__tests__/model-resolver.test.ts +2 -2
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
- package/src/execution/__tests__/nested-visibility.test.ts +2 -0
- package/src/execution/__tests__/notify-ledger.test.ts +973 -18
- 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.test.ts +10 -9
- package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
- package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
- package/src/execution/__tests__/rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
- package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
- package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
- package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
- package/src/execution/__tests__/session-file-gc.test.ts +35 -0
- package/src/execution/__tests__/session-pending.test.ts +257 -14
- package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
- package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
- package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
- package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
- package/src/execution/__tests__/spawn-args.test.ts +11 -10
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
- package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
- package/src/execution/__tests__/spawned-children.test.ts +2 -1
- package/src/execution/__tests__/start-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 +42 -22
- package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +8 -8
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
- package/src/execution/__tests__/subagent-service.test.ts +37 -30
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
- package/src/execution/__tests__/temp-prompt.test.ts +1 -1
- package/src/execution/__tests__/timeout-integration.test.ts +6 -5
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
- package/src/execution/__tests__/turn-limiter.test.ts +1 -1
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
- package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
- package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
- package/src/execution/__tests__/worktree-manager.test.ts +14 -5
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
- package/src/execution/agent-registry.ts +74 -30
- package/src/execution/agent-result-mapper.ts +3 -0
- package/src/execution/cold-resurrect.ts +199 -0
- package/src/execution/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 +22 -112
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
- package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
- package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
- package/src/execution/engine/__tests__/registry.test.ts +4 -2
- package/src/execution/engine/__tests__/routing.test.ts +115 -6
- package/src/execution/engine/common/capability-gate.ts +92 -0
- package/src/execution/engine/common/journal-wiring.ts +97 -0
- package/src/execution/engine/common/kill-chain.ts +42 -10
- package/src/execution/engine/common/nesting-guard.ts +66 -0
- package/src/execution/engine/common/persona-router.ts +18 -8
- package/src/execution/engine/common/pool-manager.ts +332 -78
- package/src/execution/engine/common/session-view-service.ts +506 -0
- package/src/execution/engine/common/session-view-types.ts +125 -0
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
- package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
- package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
- package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
- package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
- package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
- package/src/execution/engine/engines/pi/reader.ts +11 -0
- package/src/execution/engine/engines/pi/registration.ts +4 -2
- package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
- package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
- package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
- package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
- package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +25 -319
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
- package/src/execution/engine/engines/zcode/connection.ts +24 -11
- package/src/execution/engine/engines/zcode/constants.ts +75 -71
- package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
- package/src/execution/engine/engines/zcode/parser.ts +12 -346
- package/src/execution/engine/engines/zcode/preparer.ts +12 -143
- package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
- package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
- package/src/execution/engine/host-task-spec.ts +32 -36
- package/src/execution/engine/model-validation.ts +177 -0
- package/src/execution/engine/port.ts +24 -6
- package/src/execution/engine/routing.ts +75 -1
- package/src/execution/engine/types.ts +30 -103
- package/src/execution/execution-record.ts +222 -144
- package/src/execution/finalize-record.ts +131 -93
- package/src/execution/idle-gc.ts +28 -0
- package/src/execution/lifecycle-predicates.ts +1 -1
- package/src/execution/notify-host.ts +212 -0
- package/src/execution/record-store.ts +119 -73
- package/src/execution/round-settlement.ts +93 -0
- package/src/execution/session-file-gc.ts +96 -62
- package/src/execution/session-reconstructor.ts +256 -143
- package/src/execution/sessions-index.ts +117 -47
- package/src/execution/settled-watchdog.ts +264 -52
- package/src/execution/subagent-actions-core.ts +22 -22
- package/src/execution/subagent-service.ts +815 -963
- package/src/execution/subprocess-agent-runner.ts +144 -97
- package/src/execution/types.ts +9 -0
- package/src/execution/ui-request-handler-factory.ts +102 -74
- package/src/execution/ui-request-queue.ts +50 -19
- package/src/index.ts +442 -316
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
- package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
- package/src/orchestration/__tests__/args-validator.test.ts +14 -0
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
- package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
- package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
- 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} +27 -19
- package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
- package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
- package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
- package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
- package/src/orchestration/agent-opts-resolver.ts +1 -1
- package/src/orchestration/args-validator.ts +53 -31
- package/src/orchestration/execute-agent-call.ts +19 -73
- package/src/orchestration/launcher.ts +128 -81
- package/src/orchestration/lifecycle.ts +43 -45
- package/src/orchestration/models/ports.ts +4 -4
- package/src/orchestration/models/run-runtime.ts +2 -2
- package/src/orchestration/models/trace.ts +1 -1
- package/src/orchestration/models/types.ts +111 -22
- package/src/orchestration/models/workflow-run.ts +1 -1
- package/src/orchestration/script-lint.ts +91 -58
- package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
- package/src/shared/__tests__/meta-parser.test.ts +151 -0
- package/src/shared/__tests__/resource-discovery.test.ts +258 -215
- package/src/shared/atomic-write.ts +12 -9
- package/src/shared/meta-parser.ts +158 -84
- package/src/shared/model-ref.ts +15 -2
- package/src/shared/resource-discovery.ts +9 -183
- package/src/shared/schema-jsonify.ts +1 -1
- package/workflows/review-fix-loop-utils.cjs +151 -89
- package/workflows/review-fix-loop-utils.d.cts +287 -0
- package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
- package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
- package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
- package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/execute-options-mapper.ts +0 -115
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* finalizeRun — D5-② 终态 coda 单写点测试。
|
|
3
|
+
*
|
|
4
|
+
* 「transition → save → pending:unregister → onRunDone」四步终态序列的唯一
|
|
5
|
+
* 定义点(worker-message-pump.ts)。收敛前 8 处逐字复制(本文件 6 处 + lifecycle
|
|
6
|
+
* 2 处)——本文件锁定:
|
|
7
|
+
* 1. 四步恰好一次且有序(transition 先于 save 先于 unregister 先于 onRunDone)
|
|
8
|
+
* 2. notifyDone:false 真差异承载(terminateRunningRuns 不发 onRunDone、unregister 仍发)
|
|
9
|
+
* 3. transition 让位(并发终态化)→ 后三步全不执行
|
|
10
|
+
* 4. save best-effort(SW-DATA-3)→ unregister/onRunDone 不被落盘失败短路
|
|
11
|
+
*
|
|
12
|
+
* 其余终态路径的「恰好一次四步」行为断言(save×1 + unregister + onRunDone×1)
|
|
13
|
+
* 已由既有测试覆盖:handleWorkerError/handleScriptError/handleWorkerExit 超限
|
|
14
|
+
* (worker-message-pump-handlers / worker-exit-without-result)、time_limited
|
|
15
|
+
* (worker-message-pump-handlers race-F3 describe)、abortRun(lifecycle.test)、
|
|
16
|
+
* handleReturn(worker-exit-without-result SW-DATA-3 describe)。本文件补齐
|
|
17
|
+
* budget_limited 路径(dispatchAgentCall coda)与 notifyDone:false 路径。
|
|
18
|
+
*/
|
|
19
|
+
import { describe, expect, it, vi } from "vitest";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
finalizeRun,
|
|
23
|
+
handleWorkerMessage,
|
|
24
|
+
} from "../worker-message-pump.ts";
|
|
25
|
+
import { Budget } from "../models/budget.ts";
|
|
26
|
+
import { RunRuntime } from "../models/run-runtime.ts";
|
|
27
|
+
import { Trace } from "../models/trace.ts";
|
|
28
|
+
import type { AgentResult } from "../models/types.ts";
|
|
29
|
+
import { WorkflowRun } from "../models/workflow-run.ts";
|
|
30
|
+
import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
|
|
31
|
+
import type { WorkerHandle } from "../worker-handle.ts";
|
|
32
|
+
|
|
33
|
+
// ── helpers ──────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/** 构造真实 WorkflowRun(真实状态机 transition)+ 初始 runtime。 */
|
|
36
|
+
function makeRealRun(runId: string): WorkflowRun {
|
|
37
|
+
const run = new WorkflowRun(
|
|
38
|
+
runId,
|
|
39
|
+
{
|
|
40
|
+
scriptName: "test-wf",
|
|
41
|
+
scriptSource: "agent('hi')",
|
|
42
|
+
args: {},
|
|
43
|
+
scriptPath: "/tmp/test-wf.js",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
status: "running",
|
|
47
|
+
budget: new Budget(),
|
|
48
|
+
calls: new Map(),
|
|
49
|
+
trace: new Trace(),
|
|
50
|
+
errorLogs: [],
|
|
51
|
+
},
|
|
52
|
+
{ startedAt: new Date().toISOString() },
|
|
53
|
+
);
|
|
54
|
+
const worker = {
|
|
55
|
+
postMessage: vi.fn(),
|
|
56
|
+
terminate: vi.fn(async () => {}),
|
|
57
|
+
} as unknown as WorkerHandle;
|
|
58
|
+
run.assignRuntime(new RunRuntime(worker, new AbortController()));
|
|
59
|
+
return run;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** deps mock:副作用打点进单一顺序数组(断言四步顺序)。 */
|
|
63
|
+
function makeTracingDeps(): LifecycleDeps & {
|
|
64
|
+
order: string[];
|
|
65
|
+
store: { save: ReturnType<typeof vi.fn> };
|
|
66
|
+
} {
|
|
67
|
+
const order: string[] = [];
|
|
68
|
+
return {
|
|
69
|
+
order,
|
|
70
|
+
store: {
|
|
71
|
+
save: vi.fn(async () => {
|
|
72
|
+
order.push("save");
|
|
73
|
+
}),
|
|
74
|
+
},
|
|
75
|
+
workerHost: { start: vi.fn(() => ({ postMessage: vi.fn() })) },
|
|
76
|
+
runner: { run: vi.fn(async () => ({}) as AgentResult) },
|
|
77
|
+
runs: new Map(),
|
|
78
|
+
eventBus: {
|
|
79
|
+
emit: vi.fn((event: string) => {
|
|
80
|
+
order.push(`emit:${event}`);
|
|
81
|
+
}),
|
|
82
|
+
},
|
|
83
|
+
onRunDone: vi.fn(() => {
|
|
84
|
+
order.push("onRunDone");
|
|
85
|
+
}),
|
|
86
|
+
log: vi.fn(),
|
|
87
|
+
} as unknown as ReturnType<typeof makeTracingDeps>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** flush 微任务队列(void finalizeRun 的 async 链推进到稳定态)。 */
|
|
91
|
+
async function flushMicrotasks(ticks = 20): Promise<void> {
|
|
92
|
+
for (let i = 0; i < ticks; i++) {
|
|
93
|
+
// eslint-disable-next-line no-await-in-loop -- 排空微任务队列的固定 tick 循环,非逐项等待
|
|
94
|
+
await Promise.resolve();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── finalizeRun 直测 ─────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
describe("finalizeRun(D5-② 单写点直测)", () => {
|
|
101
|
+
it("四步恰好一次且有序:transition → save → pending:unregister → onRunDone", async () => {
|
|
102
|
+
const run = makeRealRun("wf-fin-1");
|
|
103
|
+
const deps = makeTracingDeps();
|
|
104
|
+
|
|
105
|
+
const transitionSpy = vi.spyOn(run, "transition");
|
|
106
|
+
const ok = await finalizeRun(run, deps, "completed", { context: "test" });
|
|
107
|
+
|
|
108
|
+
expect(ok).toBe(true);
|
|
109
|
+
expect(run.state.status).toBe("done");
|
|
110
|
+
expect(run.state.reason).toBe("completed");
|
|
111
|
+
// 四步各恰好一次
|
|
112
|
+
expect(transitionSpy).toHaveBeenCalledTimes(1);
|
|
113
|
+
expect(deps.store.save).toHaveBeenCalledTimes(1);
|
|
114
|
+
expect(deps.eventBus!.emit).toHaveBeenCalledTimes(1);
|
|
115
|
+
expect(deps.onRunDone).toHaveBeenCalledTimes(1);
|
|
116
|
+
// 顺序:save → unregister → onRunDone(transition 已同步先行)
|
|
117
|
+
expect(deps.order).toEqual(["save", "emit:pending:unregister", "onRunDone"]);
|
|
118
|
+
// unregister reason = transition 后的 state.reason
|
|
119
|
+
expect(deps.eventBus!.emit).toHaveBeenCalledWith("pending:unregister", {
|
|
120
|
+
id: "wf-fin-1",
|
|
121
|
+
reason: "completed",
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("notifyDone:false → onRunDone 不调、unregister 仍发(terminateRunningRuns 真差异经参数承载)", async () => {
|
|
126
|
+
const run = makeRealRun("wf-fin-2");
|
|
127
|
+
const deps = makeTracingDeps();
|
|
128
|
+
|
|
129
|
+
const ok = await finalizeRun(run, deps, "failed", {
|
|
130
|
+
context: "terminateRunningRuns",
|
|
131
|
+
notifyDone: false,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(ok).toBe(true);
|
|
135
|
+
expect(run.state.reason).toBe("failed");
|
|
136
|
+
expect(deps.eventBus!.emit).toHaveBeenCalledTimes(1);
|
|
137
|
+
expect(deps.eventBus!.emit).toHaveBeenCalledWith("pending:unregister", {
|
|
138
|
+
id: "wf-fin-2",
|
|
139
|
+
reason: "failed",
|
|
140
|
+
});
|
|
141
|
+
expect(deps.onRunDone).not.toHaveBeenCalled();
|
|
142
|
+
expect(deps.order).toEqual(["save", "emit:pending:unregister"]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("transition 抛错(并发 abort 抢先终态化)→ 返回 false,后三步全不执行", async () => {
|
|
146
|
+
const run = makeRealRun("wf-fin-3");
|
|
147
|
+
// 抢先终态化——此后 running→done 转移抛 illegal-transition
|
|
148
|
+
run.transition("done", "aborted");
|
|
149
|
+
const deps = makeTracingDeps();
|
|
150
|
+
|
|
151
|
+
const ok = await finalizeRun(run, deps, "failed", { context: "test" });
|
|
152
|
+
|
|
153
|
+
expect(ok).toBe(false);
|
|
154
|
+
// 抢先方已兑现 unregister/onRunDone——本路径让位,不重复执行
|
|
155
|
+
expect(deps.store.save).not.toHaveBeenCalled();
|
|
156
|
+
expect(deps.eventBus!.emit).not.toHaveBeenCalled();
|
|
157
|
+
expect(deps.onRunDone).not.toHaveBeenCalled();
|
|
158
|
+
expect(run.state.reason).toBe("aborted");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("save 抛错(ENOSPC)→ best-effort:unregister + onRunDone 照常(SW-DATA-3 统一)", async () => {
|
|
162
|
+
const run = makeRealRun("wf-fin-4");
|
|
163
|
+
const deps = makeTracingDeps();
|
|
164
|
+
deps.store.save = vi.fn(async () => {
|
|
165
|
+
throw new Error("ENOSPC: no space left on device");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const ok = await finalizeRun(run, deps, "failed", { context: "test" });
|
|
169
|
+
|
|
170
|
+
expect(ok).toBe(true);
|
|
171
|
+
expect(run.state.status).toBe("done");
|
|
172
|
+
expect(deps.eventBus!.emit).toHaveBeenCalledTimes(1);
|
|
173
|
+
expect(deps.onRunDone).toHaveBeenCalledTimes(1);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("onRunDone 抛错 → 被捕获记日志(M12:真实副作用错误不静默吞、不上抛)", async () => {
|
|
177
|
+
const run = makeRealRun("wf-fin-5");
|
|
178
|
+
const deps = makeTracingDeps();
|
|
179
|
+
deps.onRunDone = vi.fn(() => {
|
|
180
|
+
throw new Error("interface notify blew up");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await expect(
|
|
184
|
+
finalizeRun(run, deps, "completed", { context: "test" }),
|
|
185
|
+
).resolves.toBe(true);
|
|
186
|
+
expect(deps.eventBus!.emit).toHaveBeenCalledTimes(1);
|
|
187
|
+
expect(run.state.status).toBe("done");
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// ── budget_limited 路径(dispatchAgentCall coda)四步恰好一次 ────────────
|
|
192
|
+
|
|
193
|
+
describe("budget_limited 终态路径(dispatchAgentCall → finalizeRun)", () => {
|
|
194
|
+
it("agent call 后预算超限 → 四步恰好一次(unregister reason=budget_limited + onRunDone)", async () => {
|
|
195
|
+
const run = makeRealRun("wf-budget-1");
|
|
196
|
+
const deps = makeTracingDeps();
|
|
197
|
+
// budget.isExceeded 恒 true——runner.run 成功返回后命中 C-2 coda
|
|
198
|
+
(run.state.budget as unknown as { isExceeded: () => boolean }).isExceeded =
|
|
199
|
+
() => true;
|
|
200
|
+
deps.runner.run = vi.fn(async () =>
|
|
201
|
+
({ content: "ok", durationMs: 1, error: undefined, toolCalls: [] }) as AgentResult,
|
|
202
|
+
);
|
|
203
|
+
const handlers: WorkerHandlers = {
|
|
204
|
+
onMessage: vi.fn(async () => {}),
|
|
205
|
+
onError: vi.fn(async () => {}),
|
|
206
|
+
onExit: vi.fn(async () => {}),
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
await handleWorkerMessage(
|
|
210
|
+
run,
|
|
211
|
+
{ type: "agent-call", callId: 1, opts: { prompt: "p", description: "d" } },
|
|
212
|
+
deps,
|
|
213
|
+
handlers,
|
|
214
|
+
);
|
|
215
|
+
await flushMicrotasks();
|
|
216
|
+
|
|
217
|
+
expect(run.state.status).toBe("done");
|
|
218
|
+
expect(run.state.reason).toBe("budget_limited");
|
|
219
|
+
// save 2 次 = call 完成快照(dispatchAgentCall .then 的常规持久化)+ budget
|
|
220
|
+
// 终态快照(finalizeRun 内)——两次语义不同,收敛前后一致
|
|
221
|
+
expect(deps.store.save).toHaveBeenCalledTimes(2);
|
|
222
|
+
expect(deps.eventBus!.emit).toHaveBeenCalledTimes(1);
|
|
223
|
+
expect(deps.eventBus!.emit).toHaveBeenCalledWith("pending:unregister", {
|
|
224
|
+
id: "wf-budget-1",
|
|
225
|
+
reason: "budget_limited",
|
|
226
|
+
});
|
|
227
|
+
expect(deps.onRunDone).toHaveBeenCalledTimes(1);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* worker-message-pump handlers — handleWorkerExit/Error/ScriptError + postBudgetUpdate 测试。
|
|
3
3
|
*
|
|
4
|
-
* 参考
|
|
4
|
+
* 参考 worker-message-pump-workflow-call.test.ts 的 mock 构建。通过 vi.useFakeTimers() 跳过
|
|
5
5
|
* scheduleRebuild 的指数退避(1s/2s/4s)。
|
|
6
6
|
*
|
|
7
7
|
* 覆盖:
|
|
@@ -23,11 +23,11 @@ import {
|
|
|
23
23
|
handleWorkerMessage,
|
|
24
24
|
postBudgetUpdate,
|
|
25
25
|
rebuildRuntime,
|
|
26
|
-
} from "../
|
|
26
|
+
} from "../worker-message-pump.ts";
|
|
27
27
|
import { Budget } from "../models/budget.ts";
|
|
28
28
|
import { RunRuntime } from "../models/run-runtime.ts";
|
|
29
29
|
import { Trace } from "../models/trace.ts";
|
|
30
|
-
import type { AgentResult } from "../models/types.ts";
|
|
30
|
+
import type { AgentResult, DoneReason, RunStatus } from "../models/types.ts";
|
|
31
31
|
import { WorkflowRun } from "../models/workflow-run.ts";
|
|
32
32
|
import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
|
|
33
33
|
import type { WorkerHandle } from "../worker-handle.ts";
|
|
@@ -71,26 +71,34 @@ function makeRunningRun(opts: {
|
|
|
71
71
|
receivedTerminalMessage: opts.receivedTerminalMessage,
|
|
72
72
|
},
|
|
73
73
|
// transition 副作用——run.state.status 由调用方通过 mock 控制后再次断言
|
|
74
|
-
transition(target:
|
|
74
|
+
transition(this: WorkflowRun, target: RunStatus, reason?: DoneReason): void {
|
|
75
75
|
this.state.status = target;
|
|
76
76
|
if (target === "done") this.state.reason = reason;
|
|
77
77
|
},
|
|
78
|
-
replaceRuntime(rt:
|
|
78
|
+
replaceRuntime(this: WorkflowRun, rt: NonNullable<WorkflowRun["runtime"]>): void {
|
|
79
79
|
this.runtime = rt;
|
|
80
80
|
},
|
|
81
81
|
} as unknown as WorkflowRun;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
/** LifecycleDeps mock:store/workerHost/runner/eventBus/scheduleTimeBudget 可观察。
|
|
84
|
+
/** LifecycleDeps mock:store/workerHost/runner/eventBus/scheduleTimeBudget 可观察。
|
|
85
|
+
* mock 成员 = 真实签名 & vi.fn 能力(交叉纯 Mock 会丢真实签名,传回被测函数即报错)。 */
|
|
86
|
+
type MockLifecycleDeps = Omit<
|
|
87
|
+
LifecycleDeps,
|
|
88
|
+
"store" | "workerHost" | "runner" | "eventBus" | "onRunDone" | "log"
|
|
89
|
+
> & {
|
|
90
|
+
// 真实类型整体保留(RunStore/WorkerHost 等接口成员完整),仅 mock 方法交叉 vi.fn 能力
|
|
91
|
+
store: LifecycleDeps["store"] & { save: LifecycleDeps["store"]["save"] & ReturnType<typeof vi.fn> };
|
|
92
|
+
workerHost: LifecycleDeps["workerHost"] & { start: LifecycleDeps["workerHost"]["start"] & ReturnType<typeof vi.fn> };
|
|
93
|
+
runner: LifecycleDeps["runner"] & { run: LifecycleDeps["runner"]["run"] & ReturnType<typeof vi.fn> };
|
|
94
|
+
eventBus: NonNullable<LifecycleDeps["eventBus"]> & { emit: NonNullable<LifecycleDeps["eventBus"]>["emit"] & ReturnType<typeof vi.fn> };
|
|
95
|
+
onRunDone: LifecycleDeps["onRunDone"] & ReturnType<typeof vi.fn>;
|
|
96
|
+
log: LifecycleDeps["log"] & ReturnType<typeof vi.fn>;
|
|
97
|
+
};
|
|
98
|
+
|
|
85
99
|
function makeDeps(opts: {
|
|
86
100
|
scheduleTimeBudget?: LifecycleDeps["scheduleTimeBudget"];
|
|
87
|
-
} = {}):
|
|
88
|
-
store: { save: ReturnType<typeof vi.fn> };
|
|
89
|
-
workerHost: { start: ReturnType<typeof vi.fn> };
|
|
90
|
-
eventBus: { emit: ReturnType<typeof vi.fn> };
|
|
91
|
-
onRunDone: ReturnType<typeof vi.fn>;
|
|
92
|
-
log: ReturnType<typeof vi.fn>;
|
|
93
|
-
} {
|
|
101
|
+
} = {}): MockLifecycleDeps {
|
|
94
102
|
return {
|
|
95
103
|
store: { save: vi.fn(async () => {}) },
|
|
96
104
|
workerHost: { start: vi.fn(() => ({ postMessage: vi.fn() })) },
|
|
@@ -344,7 +352,7 @@ describe("rebuildRuntime", () => {
|
|
|
344
352
|
|
|
345
353
|
it("带 budgetTimeMs 时重排 scheduleTimeBudget 计时器", () => {
|
|
346
354
|
const run = makeRunningRun({ budgetTimeMs: 5000 });
|
|
347
|
-
const scheduleTimeBudget = vi.fn(() => undefined);
|
|
355
|
+
const scheduleTimeBudget = vi.fn((_runId: string, _budgetTimeMs: number) => undefined);
|
|
348
356
|
const deps = makeDeps({ scheduleTimeBudget });
|
|
349
357
|
|
|
350
358
|
rebuildRuntime(run, deps, makeHandlers());
|
|
@@ -413,7 +421,7 @@ describe("race-F3: rebuild 时间预算折算", () => {
|
|
|
413
421
|
const run = makeRunningRun({ budgetTimeMs: 5000 });
|
|
414
422
|
// 已耗 70%(3500ms)→ 剩余 1500ms;fake Date 冻结,elapsed 精确
|
|
415
423
|
run.meta.startedAt = new Date(Date.now() - 3500).toISOString();
|
|
416
|
-
const scheduleTimeBudget = vi.fn(() => undefined);
|
|
424
|
+
const scheduleTimeBudget = vi.fn((_runId: string, _budgetTimeMs: number) => undefined);
|
|
417
425
|
const deps = makeDeps({ scheduleTimeBudget });
|
|
418
426
|
|
|
419
427
|
rebuildRuntime(run, deps, makeHandlers());
|
|
@@ -429,7 +437,7 @@ describe("race-F3: rebuild 时间预算折算", () => {
|
|
|
429
437
|
const run = makeRunningRun({ budgetTimeMs: 5000 });
|
|
430
438
|
// 已耗 6000ms > 预算 5000ms(退避 advance 1000ms 后已耗 7000ms,仍耗尽)
|
|
431
439
|
run.meta.startedAt = new Date(Date.now() - 6000).toISOString();
|
|
432
|
-
const scheduleTimeBudget = vi.fn(() => undefined);
|
|
440
|
+
const scheduleTimeBudget = vi.fn((_runId: string, _budgetTimeMs: number) => undefined);
|
|
433
441
|
const deps = makeDeps({ scheduleTimeBudget });
|
|
434
442
|
|
|
435
443
|
const p = handleScriptError(run, "boom", [], deps, makeHandlers());
|
|
@@ -452,7 +460,7 @@ describe("race-F3: rebuild 时间预算折算", () => {
|
|
|
452
460
|
|
|
453
461
|
it("预算未耗尽(fresh run)→ 照常 rebuild,不误转 time_limited(防误伤回归)", async () => {
|
|
454
462
|
const run = makeRunningRun({ budgetTimeMs: 5000 }); // startedAt ≈ now,remaining 满
|
|
455
|
-
const scheduleTimeBudget = vi.fn(() => undefined);
|
|
463
|
+
const scheduleTimeBudget = vi.fn((_runId: string, _budgetTimeMs: number) => undefined);
|
|
456
464
|
const deps = makeDeps({ scheduleTimeBudget });
|
|
457
465
|
|
|
458
466
|
const p = handleScriptError(run, "boom", [], deps, makeHandlers());
|
|
@@ -745,7 +753,7 @@ describe("rebuildRuntime 可观察性(OB3 日志点)", () => {
|
|
|
745
753
|
|
|
746
754
|
it("U7: 带 budgetTimeMs + scheduleTimeBudget 注入时含 L2(在 L1 之后、L3 之前,payload 含 budgetTimeMs)", () => {
|
|
747
755
|
const run = makeRealRun("wf-rebuild-log-2", { budgetTimeMs: 5000 });
|
|
748
|
-
const scheduleTimeBudget = vi.fn(() => undefined);
|
|
756
|
+
const scheduleTimeBudget = vi.fn((_runId: string, _budgetTimeMs: number) => undefined);
|
|
749
757
|
const deps = makeDeps({ scheduleTimeBudget });
|
|
750
758
|
|
|
751
759
|
rebuildRuntime(run, deps, makeHandlers());
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/orchestration/__tests__/
|
|
1
|
+
// src/orchestration/__tests__/worker-message-pump-postmessage-defense.test.ts
|
|
2
2
|
//
|
|
3
3
|
// W2: 主线程层 postMessage 防御测试。
|
|
4
4
|
//
|
|
@@ -35,13 +35,13 @@ vi.mock("../../core/logger.ts", () => ({
|
|
|
35
35
|
getLogger: () => loggerMock,
|
|
36
36
|
}));
|
|
37
37
|
|
|
38
|
-
import { handleWorkerMessage, postBudgetUpdate } from "../
|
|
38
|
+
import { handleWorkerMessage, postBudgetUpdate } from "../worker-message-pump.ts";
|
|
39
39
|
import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
|
|
40
40
|
import type { WorkflowRun } from "../models/workflow-run.ts";
|
|
41
41
|
|
|
42
42
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
43
|
-
const
|
|
44
|
-
join(__dirname, "../
|
|
43
|
+
const WORKER_MESSAGE_PUMP_SRC = readFileSync(
|
|
44
|
+
join(__dirname, "../worker-message-pump.ts"),
|
|
45
45
|
"utf-8",
|
|
46
46
|
);
|
|
47
47
|
|
|
@@ -273,7 +273,7 @@ describe("W2b: dispatchWorkflowCall postResult 防御 DataCloneError", () => {
|
|
|
273
273
|
// 子进程)。利用这一点构造行为测试——往 cached.result 里塞不可克隆值(function),
|
|
274
274
|
// mock postMessage 在首次发送 agent-result 时抛 DataCloneError,验证 fallback 路径。
|
|
275
275
|
//
|
|
276
|
-
// 另外补充:验证
|
|
276
|
+
// 另外补充:验证 worker-message-pump.ts 源码中三处 postMessage 调用点都有 try/catch 包裹,
|
|
277
277
|
// 防止未来重构误删防御(类似 worker-script-builder.test.ts 的源码字符串断言模式)。
|
|
278
278
|
|
|
279
279
|
/** 构造 status="running" 且 calls 已含一个 done 结果(含不可克隆成员)的 mock run。
|
|
@@ -376,7 +376,7 @@ describe("W2c: postAgentResult 防御(源码结构断言)", () => {
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
it("postAgentResult 包含 try/catch + fallback result", () => {
|
|
379
|
-
const match =
|
|
379
|
+
const match = WORKER_MESSAGE_PUMP_SRC.match(/function postAgentResult\([\s\S]*?\n\}/);
|
|
380
380
|
expect(match, "postAgentResult 函数定义应存在").toBeTruthy();
|
|
381
381
|
expect(hasTryCatchAroundPostMessage(match![0])).toBe(true);
|
|
382
382
|
// fallback 通过共享工厂 makeSerializeFailedResult 构造纯字符串 result(必可克隆)
|
|
@@ -387,14 +387,14 @@ describe("W2c: postAgentResult 防御(源码结构断言)", () => {
|
|
|
387
387
|
});
|
|
388
388
|
|
|
389
389
|
it("postBudgetUpdate 包含 try/catch", () => {
|
|
390
|
-
const match =
|
|
390
|
+
const match = WORKER_MESSAGE_PUMP_SRC.match(/export function postBudgetUpdate\([\s\S]*?\n\}/);
|
|
391
391
|
expect(match, "postBudgetUpdate 函数定义应存在").toBeTruthy();
|
|
392
392
|
expect(hasTryCatchAroundPostMessage(match![0])).toBe(true);
|
|
393
393
|
});
|
|
394
394
|
|
|
395
395
|
it("dispatchWorkflowCall postResult 闭包包含 try/catch + fallback", () => {
|
|
396
396
|
// postResult 是 const 闭包,匹配到下一个 }; 结尾
|
|
397
|
-
const match =
|
|
397
|
+
const match = WORKER_MESSAGE_PUMP_SRC.match(/const postResult = \(result[\s\S]*?\n \};/);
|
|
398
398
|
expect(match, "postResult 闭包定义应存在").toBeTruthy();
|
|
399
399
|
expect(hasTryCatchAroundPostMessage(match![0])).toBe(true);
|
|
400
400
|
expect(match![0]).toContain("Workflow result serialization failed");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/orchestration/__tests__/
|
|
1
|
+
// src/orchestration/__tests__/worker-message-pump-serialize-failed-result.test.ts
|
|
2
2
|
//
|
|
3
3
|
// makeSerializeFailedResult 独立单测(W2 防御关键纯函数)。
|
|
4
4
|
//
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { describe, expect, it } from "vitest";
|
|
17
17
|
|
|
18
|
-
import { makeSerializeFailedResult } from "../
|
|
18
|
+
import { makeSerializeFailedResult } from "../worker-message-pump.ts";
|
|
19
19
|
|
|
20
20
|
describe("makeSerializeFailedResult", () => {
|
|
21
21
|
it("返回 {content:'', error:'<prefix>: <errMsg>'} 形状(必可克隆 fallback)", () => {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { describe, expect, it, vi } from "vitest";
|
|
12
12
|
|
|
13
|
-
import { handleWorkerMessage } from "../
|
|
13
|
+
import { handleWorkerMessage } from "../worker-message-pump.ts";
|
|
14
14
|
import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
|
|
15
15
|
import type { WorkflowRun } from "../models/workflow-run.ts";
|
|
16
16
|
|
|
@@ -105,7 +105,7 @@ function runWorker(userScript: string, opts: RunOptions = {}): Promise<RunResult
|
|
|
105
105
|
worker.on("message", (raw: unknown) => {
|
|
106
106
|
if (isAgentCall(raw)) {
|
|
107
107
|
result.agentCallCount += 1;
|
|
108
|
-
opts.onAgentCall?.(raw as { callId: number; opts: Record<string, unknown> }, worker);
|
|
108
|
+
opts.onAgentCall?.(raw as unknown as { callId: number; opts: Record<string, unknown> }, worker);
|
|
109
109
|
return;
|
|
110
110
|
}
|
|
111
111
|
if (isReturn(raw)) {
|
|
@@ -225,7 +225,7 @@ describe("buildWorkerScript — W2 agent() returnMeta mode", () => {
|
|
|
225
225
|
// ── thinkingLevel 参数透传(agent() 三分支) ──
|
|
226
226
|
// 验证 thinkingLevel 在 string / task-agent / object.prompt 三个分支均被透传至
|
|
227
227
|
// agent-call 的 opts,且加入 _knownFields 白名单(object.prompt 分支整体透传,
|
|
228
|
-
// 不被 unknown-fields warn 误报)。底层 AgentCallOpts
|
|
228
|
+
// 不被 unknown-fields warn 误报)。底层 AgentCallOpts(D6 合流后 EnginePort 直传,
|
|
229
229
|
// buildSpawnArgs 拼 pi CLI --model provider/modelId:thinkingLevel 已打通,
|
|
230
230
|
// 此处只验入口层 wiring。
|
|
231
231
|
|
|
@@ -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";
|
|
@@ -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
|
*
|
|
@@ -58,6 +58,57 @@ function formatMessage(name: string, errors: readonly unknown[]): string {
|
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// ── validateRunArgs 阶段 helper(按 预处理/编译 提取,主函数只留编排) ────
|
|
62
|
+
|
|
63
|
+
/** schema.properties 提取(畸形/缺失 → {},null-scan 在空 properties 上安全退化)。 */
|
|
64
|
+
function extractSchemaProperties(schema: Record<string, unknown>): Record<string, unknown> {
|
|
65
|
+
return schema.properties !== null && typeof schema.properties === "object"
|
|
66
|
+
? (schema.properties as Record<string, unknown>)
|
|
67
|
+
: {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 单个 property 声明是否 nullable(type 含 "null" 或 type === "null")。 */
|
|
71
|
+
function isNullableProp(prop: unknown): boolean {
|
|
72
|
+
if (prop === null || typeof prop !== "object") return false;
|
|
73
|
+
const propType = (prop as Record<string, unknown>).type;
|
|
74
|
+
return Array.isArray(propType)
|
|
75
|
+
? (propType as unknown[]).includes("null")
|
|
76
|
+
: propType === "null";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* null-scan:null 值视为缺失(coerceTypes 会把 null→"" 放行 required,绕过 fail-fast)。
|
|
81
|
+
* 只删 schema 未声明 nullable 的键——type 含 "null"(如 ["string","null"])的合法 null
|
|
82
|
+
* 输入保留(m3 exec-review M1 探针实证:全键删除会拒掉 nullable required 的合法值)。
|
|
83
|
+
*/
|
|
84
|
+
function deleteNonNullableNullArgs(
|
|
85
|
+
args: Record<string, unknown>,
|
|
86
|
+
schema: Record<string, unknown>,
|
|
87
|
+
): void {
|
|
88
|
+
const properties = extractSchemaProperties(schema);
|
|
89
|
+
for (const key of Object.keys(args)) {
|
|
90
|
+
if (args[key] !== null) continue;
|
|
91
|
+
if (!isNullableProp(properties[key])) delete args[key];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** ajv 编译(畸形 schema → 结构化 ArgsValidationError,不泄漏原始 throw)。 */
|
|
96
|
+
function compileSchema(
|
|
97
|
+
parameters: Record<string, unknown>,
|
|
98
|
+
scriptName: string,
|
|
99
|
+
): ReturnType<Ajv["compile"]> {
|
|
100
|
+
try {
|
|
101
|
+
return ajv.compile(parameters);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
// 真畸形 schema(如 type:'not-a-type')→ 结构化 ArgsValidationError,不泄漏原始 throw
|
|
104
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
105
|
+
throw new ArgsValidationError(
|
|
106
|
+
scriptName,
|
|
107
|
+
`Workflow '${scriptName}' has an invalid parameter schema: ${detail}. Read the workflow script file (location from <available_workflows>) to inspect it.`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
61
112
|
/**
|
|
62
113
|
* 校验 spec.parameters(JSON Schema draft-07)对 spec.args 的约束。
|
|
63
114
|
*
|
|
@@ -79,38 +130,9 @@ export function validateRunArgs(spec: RunSpec): void {
|
|
|
79
130
|
);
|
|
80
131
|
}
|
|
81
132
|
|
|
82
|
-
|
|
83
|
-
// 只删 schema 未声明 nullable 的键——type 含 "null"(如 ["string","null"])的合法 null
|
|
84
|
-
// 输入保留(m3 exec-review M1 探针实证:全键删除会拒掉 nullable required 的合法值)。
|
|
85
|
-
const schema = parameters as Record<string, unknown>;
|
|
86
|
-
const properties =
|
|
87
|
-
schema.properties !== null && typeof schema.properties === "object"
|
|
88
|
-
? (schema.properties as Record<string, unknown>)
|
|
89
|
-
: {};
|
|
90
|
-
for (const key of Object.keys(args)) {
|
|
91
|
-
if (args[key] !== null) continue;
|
|
92
|
-
const prop = properties[key];
|
|
93
|
-
let isNullable = false;
|
|
94
|
-
if (prop !== null && typeof prop === "object") {
|
|
95
|
-
const propType = (prop as Record<string, unknown>).type;
|
|
96
|
-
isNullable = Array.isArray(propType)
|
|
97
|
-
? (propType as unknown[]).includes("null")
|
|
98
|
-
: propType === "null";
|
|
99
|
-
}
|
|
100
|
-
if (!isNullable) delete args[key];
|
|
101
|
-
}
|
|
133
|
+
deleteNonNullableNullArgs(args, parameters);
|
|
102
134
|
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
validate = ajv.compile(parameters);
|
|
106
|
-
} catch (err) {
|
|
107
|
-
// 真畸形 schema(如 type:'not-a-type')→ 结构化 ArgsValidationError,不泄漏原始 throw
|
|
108
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
109
|
-
throw new ArgsValidationError(
|
|
110
|
-
scriptName,
|
|
111
|
-
`Workflow '${scriptName}' has an invalid parameter schema: ${detail}. Read the workflow script file (location from <available_workflows>) to inspect it.`,
|
|
112
|
-
);
|
|
113
|
-
}
|
|
135
|
+
const validate = compileSchema(parameters, scriptName);
|
|
114
136
|
|
|
115
137
|
if (!validate(args)) {
|
|
116
138
|
throw new ArgsValidationError(
|