@zhushanwen/subagent-core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/chunk-3VOERJPJ.js +22 -0
- package/dist/chunk-A4IVZWVX.js +31 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +54 -0
- package/dist/execution/engine/engines/zcode/constants.d.cts +31 -0
- package/dist/execution/engine/engines/zcode/constants.d.ts +31 -0
- package/dist/execution/engine/engines/zcode/constants.js +22 -0
- package/dist/execution/engine/engines/zcode/reader.cjs +276 -0
- package/dist/execution/engine/engines/zcode/reader.d.cts +20 -0
- package/dist/execution/engine/engines/zcode/reader.d.ts +20 -0
- package/dist/execution/engine/engines/zcode/reader.js +239 -0
- package/dist/execution/engine/paths.cjs +55 -0
- package/dist/execution/engine/paths.d.cts +8 -0
- package/dist/execution/engine/paths.d.ts +8 -0
- package/dist/execution/engine/paths.js +26 -0
- package/dist/execution/relay-env.cjs +62 -0
- package/dist/execution/relay-env.d.cts +33 -0
- package/dist/execution/relay-env.d.ts +33 -0
- package/dist/execution/relay-env.js +20 -0
- package/dist/index.cjs +1852 -0
- package/dist/index.d.cts +1184 -0
- package/dist/index.d.ts +1184 -0
- package/dist/index.js +1810 -0
- package/dist/types-BxyAidGf.d.cts +875 -0
- package/dist/types-BxyAidGf.d.ts +875 -0
- package/package.json +99 -0
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +142 -0
- package/src/__tests__/append-system-prompt-assembly.test.ts +242 -0
- package/src/__tests__/builtin-workflows-structure.test.ts +180 -0
- package/src/__tests__/derive-closed-display-parity.test.ts +93 -0
- package/src/__tests__/fr4-get-state-handshake.test.ts +125 -0
- package/src/__tests__/fr8-orphan-recovery.test.ts +259 -0
- package/src/__tests__/m2-append-content-probe.test.ts +73 -0
- package/src/__tests__/manifest-store.test.ts +368 -0
- package/src/__tests__/record-store-cache.test.ts +335 -0
- package/src/__tests__/record-store-index.test.ts +560 -0
- package/src/__tests__/record-store-last-line.test.ts +129 -0
- package/src/__tests__/review-fix-loop-script.test.ts +288 -0
- package/src/__tests__/review-fix-loop-utils.test.ts +2377 -0
- package/src/__tests__/robustness-low-batch1.test.ts +286 -0
- package/src/__tests__/robustness-medium-batch1.test.ts +69 -0
- package/src/__tests__/robustness-medium-batch2.test.ts +65 -0
- package/src/__tests__/robustness-medium-batch3.test.ts +30 -0
- package/src/__tests__/robustness-medium-batch4.test.ts +358 -0
- package/src/__tests__/session-runner.test.ts +69 -0
- package/src/__tests__/smoke.test.ts +9 -0
- package/src/__tests__/stream-sink.test.ts +203 -0
- package/src/core/__tests__/host-services.test.ts +144 -0
- package/src/core/__tests__/logger.test.ts +78 -0
- package/src/core/__tests__/notify-ports.test.ts +104 -0
- package/src/core/host-services.ts +97 -0
- package/src/core/logger.ts +47 -0
- package/src/core/notify-ports.ts +126 -0
- package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +53 -0
- package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +150 -0
- package/src/execution/__tests__/alive-store.test.ts +147 -0
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +490 -0
- package/src/execution/__tests__/channel-registry-handshake.test.ts +242 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +610 -0
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
- package/src/execution/__tests__/concurrency-pool.test.ts +250 -0
- package/src/execution/__tests__/config.test.ts +302 -0
- package/src/execution/__tests__/delivery-methods.test.ts +424 -0
- package/src/execution/__tests__/dialog-queue.test.ts +299 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +243 -0
- package/src/execution/__tests__/execute-nesting.test.ts +323 -0
- package/src/execution/__tests__/execute-options-mapper.test.ts +200 -0
- package/src/execution/__tests__/execution-record.test.ts +1394 -0
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +171 -0
- package/src/execution/__tests__/finalize-record.test.ts +367 -0
- package/src/execution/__tests__/finalized-marker.test.ts +104 -0
- package/src/execution/__tests__/format-schema-instruction.test.ts +169 -0
- package/src/execution/__tests__/gc-timer.test.ts +184 -0
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
- package/src/execution/__tests__/helpers/mock-extension-api.ts +30 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +242 -0
- package/src/execution/__tests__/host-mode.test.ts +87 -0
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +383 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
- package/src/execution/__tests__/manifest-parentid.test.ts +117 -0
- package/src/execution/__tests__/model-resolver.test.ts +465 -0
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
- package/src/execution/__tests__/nested-visibility.test.ts +325 -0
- package/src/execution/__tests__/output-collector.test.ts +358 -0
- package/src/execution/__tests__/path-encoding.test.ts +104 -0
- package/src/execution/__tests__/pi-invocation.test.ts +134 -0
- package/src/execution/__tests__/record-store.test.ts +1057 -0
- package/src/execution/__tests__/records-cwd-isolation.test.ts +91 -0
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +339 -0
- package/src/execution/__tests__/recursive-visibility-env.test.ts +273 -0
- package/src/execution/__tests__/relay-env.test.ts +42 -0
- package/src/execution/__tests__/resource-policy.test.ts +109 -0
- package/src/execution/__tests__/rpc-mode.test.ts +89 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +687 -0
- package/src/execution/__tests__/run-spawn-integration.test.ts +933 -0
- package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +196 -0
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +199 -0
- package/src/execution/__tests__/session-context-resolver.test.ts +167 -0
- package/src/execution/__tests__/session-file-gc.test.ts +293 -0
- package/src/execution/__tests__/session-reconstructor.test.ts +379 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +350 -0
- package/src/execution/__tests__/spawn-args.test.ts +445 -0
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +189 -0
- package/src/execution/__tests__/spawn-event-adapter.test.ts +167 -0
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +207 -0
- package/src/execution/__tests__/spawned-children.test.ts +92 -0
- package/src/execution/__tests__/start-sync-model-guard.test.ts +150 -0
- package/src/execution/__tests__/stdin-writer.test.ts +462 -0
- package/src/execution/__tests__/stream-sink-retirement.test.ts +261 -0
- package/src/execution/__tests__/subagent-service-abort.test.ts +60 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +629 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
- package/src/execution/__tests__/subagent-service.test.ts +788 -0
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +310 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +612 -0
- package/src/execution/__tests__/temp-prompt.test.ts +53 -0
- package/src/execution/__tests__/timeout-integration.test.ts +615 -0
- package/src/execution/__tests__/tombstone-store.test.ts +73 -0
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
- package/src/execution/__tests__/turn-limiter.test.ts +65 -0
- package/src/execution/__tests__/ui-channels.test.ts +187 -0
- package/src/execution/__tests__/ui-interaction-model.test.ts +67 -0
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +369 -0
- package/src/execution/__tests__/ui-request-handler.test.ts +204 -0
- package/src/execution/__tests__/ui-request-observability.test.ts +113 -0
- package/src/execution/__tests__/ui-request-queue.test.ts +133 -0
- package/src/execution/__tests__/worktree-manager.test.ts +633 -0
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +229 -0
- package/src/execution/__tests__/worktree-reconcile.integration.test.ts +181 -0
- package/src/execution/__tests__/worktree-registry.test.ts +199 -0
- package/src/execution/agent-registry.ts +199 -0
- package/src/execution/agent-result-mapper.ts +90 -0
- package/src/execution/alive-store.ts +119 -0
- package/src/execution/argv-mirror.ts +113 -0
- package/src/execution/best-effort.ts +38 -0
- package/src/execution/channel-registry-access.ts +144 -0
- package/src/execution/concurrency-pool.ts +116 -0
- package/src/execution/config.ts +165 -0
- package/src/execution/dialog-queue.ts +330 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +78 -0
- package/src/execution/engine/__tests__/common/errors.test.ts +132 -0
- package/src/execution/engine/__tests__/common/event-journal.test.ts +177 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +192 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +81 -0
- package/src/execution/engine/__tests__/common/persona-router.test.ts +123 -0
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +154 -0
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +128 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +28 -0
- package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +141 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +109 -0
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +101 -0
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +77 -0
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +104 -0
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +201 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +76 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +79 -0
- package/src/execution/engine/__tests__/engine-discovery.test.ts +87 -0
- package/src/execution/engine/__tests__/model-prompt.test.ts +197 -0
- package/src/execution/engine/__tests__/paths.test.ts +39 -0
- package/src/execution/engine/__tests__/registry.test.ts +120 -0
- package/src/execution/engine/__tests__/routing.test.ts +231 -0
- package/src/execution/engine/common/data-dir.ts +66 -0
- package/src/execution/engine/common/errors.ts +183 -0
- package/src/execution/engine/common/event-journal.ts +254 -0
- package/src/execution/engine/common/journal-replay.ts +62 -0
- package/src/execution/engine/common/kill-chain.ts +221 -0
- package/src/execution/engine/common/nesting-guard.ts +50 -0
- package/src/execution/engine/common/persona-router.ts +108 -0
- package/src/execution/engine/common/pool-manager.ts +226 -0
- package/src/execution/engine/common/schema-emulation.ts +189 -0
- package/src/execution/engine/common/session-view-projection.ts +51 -0
- package/src/execution/engine/engine-discovery.ts +65 -0
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +469 -0
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +155 -0
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +164 -0
- package/src/execution/engine/engines/pi/pi-engine.ts +415 -0
- package/src/execution/engine/engines/pi/reader.ts +48 -0
- package/src/execution/engine/engines/pi/registration.ts +35 -0
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +100 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +39 -0
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +150 -0
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +246 -0
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +228 -0
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +71 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +127 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +580 -0
- package/src/execution/engine/engines/zcode/constants.ts +43 -0
- package/src/execution/engine/engines/zcode/golden-sample.ts +39 -0
- package/src/execution/engine/engines/zcode/launcher.ts +161 -0
- package/src/execution/engine/engines/zcode/parser.ts +436 -0
- package/src/execution/engine/engines/zcode/preparer.ts +363 -0
- package/src/execution/engine/engines/zcode/reader.ts +381 -0
- package/src/execution/engine/engines/zcode/registration.ts +37 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +658 -0
- package/src/execution/engine/host-task-spec.ts +47 -0
- package/src/execution/engine/model-prompt.ts +158 -0
- package/src/execution/engine/paths.ts +42 -0
- package/src/execution/engine/port.ts +153 -0
- package/src/execution/engine/registry.ts +133 -0
- package/src/execution/engine/routing.ts +218 -0
- package/src/execution/engine/types.ts +309 -0
- package/src/execution/execute-options-mapper.ts +115 -0
- package/src/execution/execution-record.ts +984 -0
- package/src/execution/finalize-record.ts +254 -0
- package/src/execution/finalized-marker.ts +70 -0
- package/src/execution/get-state-handshake.ts +104 -0
- package/src/execution/host-mode.ts +56 -0
- package/src/execution/idle-gc.ts +47 -0
- package/src/execution/lifecycle-manager.ts +513 -0
- package/src/execution/lifecycle-predicates.ts +65 -0
- package/src/execution/manifest-store.ts +256 -0
- package/src/execution/model-config-service.ts +250 -0
- package/src/execution/model-resolver.ts +248 -0
- package/src/execution/notifier.ts +372 -0
- package/src/execution/notify-ledger.ts +580 -0
- package/src/execution/output-collector.ts +228 -0
- package/src/execution/path-encoding.ts +60 -0
- package/src/execution/pi-invocation.ts +120 -0
- package/src/execution/record-entry.ts +132 -0
- package/src/execution/record-store.ts +1265 -0
- package/src/execution/relay-env.ts +37 -0
- package/src/execution/session-context-resolver.ts +64 -0
- package/src/execution/session-file-gc.ts +120 -0
- package/src/execution/session-pending.ts +170 -0
- package/src/execution/session-reconstructor.ts +678 -0
- package/src/execution/session-runner.ts +1789 -0
- package/src/execution/sessions-index.ts +304 -0
- package/src/execution/spawn-event-adapter.ts +363 -0
- package/src/execution/stdin-writer.ts +198 -0
- package/src/execution/stream-sink.ts +126 -0
- package/src/execution/subagent-service.ts +2268 -0
- package/src/execution/subprocess-agent-runner.ts +317 -0
- package/src/execution/temp-prompt.ts +62 -0
- package/src/execution/tombstone-store.ts +72 -0
- package/src/execution/turn-limiter.ts +102 -0
- package/src/execution/types.ts +1023 -0
- package/src/execution/ui-channels.ts +216 -0
- package/src/execution/ui-interaction-model.ts +48 -0
- package/src/execution/ui-request-handler-factory.ts +243 -0
- package/src/execution/ui-request-observability.ts +81 -0
- package/src/execution/ui-request-queue.ts +184 -0
- package/src/execution/worktree-manager.ts +688 -0
- package/src/execution/worktree-registry.ts +279 -0
- package/src/index.ts +87 -0
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +349 -0
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +202 -0
- package/src/orchestration/__tests__/agent-call-stream.test.ts +152 -0
- package/src/orchestration/__tests__/args-validator.test.ts +143 -0
- package/src/orchestration/__tests__/config-loader.test.ts +503 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +809 -0
- package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +404 -0
- package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +56 -0
- package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +166 -0
- package/src/orchestration/__tests__/execute-agent-call.test.ts +451 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +600 -0
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +96 -0
- package/src/orchestration/__tests__/lifecycle.test.ts +659 -0
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +95 -0
- package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +183 -0
- package/src/orchestration/__tests__/script-lint.test.ts +831 -0
- package/src/orchestration/__tests__/skill-discovery.test.ts +210 -0
- package/src/orchestration/__tests__/test-mocks.ts +197 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +368 -0
- package/src/orchestration/__tests__/worker-host.test.ts +120 -0
- package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +164 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +563 -0
- package/src/orchestration/__tests__/worker-script-builder.test.ts +309 -0
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +129 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +317 -0
- package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
- package/src/orchestration/agent-opts-resolver.ts +174 -0
- package/src/orchestration/args-validator.ts +127 -0
- package/src/orchestration/config-loader.ts +316 -0
- package/src/orchestration/error-recovery.ts +1018 -0
- package/src/orchestration/execute-agent-call.ts +256 -0
- package/src/orchestration/launcher.ts +455 -0
- package/src/orchestration/lifecycle.ts +399 -0
- package/src/orchestration/models/__tests__/budget.test.ts +307 -0
- package/src/orchestration/models/agent-call.ts +83 -0
- package/src/orchestration/models/budget.ts +118 -0
- package/src/orchestration/models/ports.ts +164 -0
- package/src/orchestration/models/run-runtime.ts +104 -0
- package/src/orchestration/models/run-spec.ts +83 -0
- package/src/orchestration/models/run-state.ts +44 -0
- package/src/orchestration/models/trace.ts +183 -0
- package/src/orchestration/models/types.ts +301 -0
- package/src/orchestration/models/workflow-run.ts +254 -0
- package/src/orchestration/models/workflow-script-registry.ts +35 -0
- package/src/orchestration/models/workflow-script.ts +117 -0
- package/src/orchestration/script-lint.ts +766 -0
- package/src/orchestration/skill-discovery.ts +135 -0
- package/src/orchestration/worker-handle.ts +115 -0
- package/src/orchestration/worker-host.ts +98 -0
- package/src/orchestration/worker-script-builder.ts +421 -0
- package/src/orchestration/workflow-files.ts +85 -0
- package/src/orchestration/workflow-script-registry-impl.ts +133 -0
- package/src/shared/__tests__/agent-ref.test.ts +34 -0
- package/src/shared/__tests__/meta-parser.test.ts +304 -0
- package/src/shared/__tests__/model-ref.test.ts +306 -0
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +288 -0
- package/src/shared/__tests__/resource-discovery.test.ts +749 -0
- package/src/shared/__tests__/resource-meta.test.ts +51 -0
- package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
- package/src/shared/__tests__/timer-delay.test.ts +61 -0
- package/src/shared/agent-event.ts +13 -0
- package/src/shared/agent-ref.ts +57 -0
- package/src/shared/meta-parser.ts +261 -0
- package/src/shared/model-ref.ts +286 -0
- package/src/shared/resource-discovery.ts +835 -0
- package/src/shared/resource-meta.ts +65 -0
- package/src/shared/schema-env.ts +44 -0
- package/src/shared/schema-jsonify.ts +58 -0
- package/src/shared/timer-delay.ts +54 -0
- package/src/shared/xml-injection.ts +35 -0
- package/workflows/README.md +81 -0
- package/workflows/_shared/agent-refs.cjs +40 -0
- package/workflows/chain.js +137 -0
- package/workflows/map-reduce.js +180 -0
- package/workflows/parallel.js +154 -0
- package/workflows/review-fix-loop-utils.cjs +1542 -0
- package/workflows/review-fix-loop.js +1605 -0
- package/workflows/scatter-gather.js +184 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Extension — Worker Script Builder
|
|
3
|
+
*
|
|
4
|
+
* 生成运行 workflow 脚本的 Worker 线程源码字符串:注入全局函数
|
|
5
|
+
* agent/parallel/pipeline/phase/log,并在 worker 内部处理
|
|
6
|
+
* parentPort 消息循环(agent-call / agent-result / abort / budget-update)。
|
|
7
|
+
*
|
|
8
|
+
* 层归属:Infra(源码字符串生成,纯文本拼接,无 Pi 依赖)。
|
|
9
|
+
*
|
|
10
|
+
* 设计:
|
|
11
|
+
* - WorkerLogEntry 类型来源 engine/models/types.js(不在本文件重复声明)。
|
|
12
|
+
* - **AC-4 不变式**:buildWorkerScript 生成的脚本格式逐字保留——
|
|
13
|
+
* 用户资产(workflow 脚本依赖 agent/parallel/pipeline/$ARGS/$BUDGET 等契约)。
|
|
14
|
+
*
|
|
15
|
+
* 兼容 Claude Code Workflow 脚本格式:
|
|
16
|
+
* - agent(promptString) / agent(promptString, { label?, schema?, model?, scene? }) /
|
|
17
|
+
* agent({ prompt, schema?, model?, scene?, description? })
|
|
18
|
+
* - parallel([agent(...), ...]) 或 parallel([{ task, agent }, ...])
|
|
19
|
+
* - pipeline([stageFn, ...])
|
|
20
|
+
* - phase(name), log(msg)
|
|
21
|
+
* - $ARGS, $WORKSPACE, $BUDGET
|
|
22
|
+
* - module.exports = { meta, execute } 自动调用
|
|
23
|
+
*
|
|
24
|
+
* 生成的源码通过 `new Worker(code, { eval: true, workerData })` 在隔离的 Worker 线程运行。
|
|
25
|
+
*
|
|
26
|
+
* 通信协议(AC-4 契约,逐字保留):
|
|
27
|
+
* Worker → Main (postMessage):
|
|
28
|
+
* { type: "agent-call", callId: number, opts: AgentCallOpts }
|
|
29
|
+
* { type: "workflow-call", callId: number, name: string, args: Record<string, unknown> }
|
|
30
|
+
* { type: "return", runId: string, result: unknown }
|
|
31
|
+
* { type: "error", runId: string, error: string }
|
|
32
|
+
* { type: "log", phase: string, message: string }
|
|
33
|
+
*
|
|
34
|
+
* Main → Worker (parentPort.on("message")):
|
|
35
|
+
* { type: "agent-result", callId: number, result: AgentResult, cached: boolean }
|
|
36
|
+
* { type: "workflow-result", callId: number, result: unknown }
|
|
37
|
+
* { type: "budget-update", budget: unknown }
|
|
38
|
+
* { type: "abort", reason: string }
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
// ── Build worker source ─────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Template constant segments(模块级 hoisting,IF5/TC8)——
|
|
45
|
+
* 常量段在模块加载时构造一次,buildWorkerScript 每次调用只做三段 concat,
|
|
46
|
+
* 消除每次 run 的 330+ 元素数组重建 + join 分配(#11 低优先形态)。
|
|
47
|
+
*
|
|
48
|
+
* 输出与 hoisting 前逐字节一致(join("\n") 拼装语义精确保留),由
|
|
49
|
+
* __tests__/worker-script-template-snapshot.test.ts 的 byte-identical 快照锚定
|
|
50
|
+
* (fixture 由改造前实现生成)。
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/** PRE 段:use strict … User workflow script 注释行(不含 userScript)。 */
|
|
54
|
+
const WORKER_TEMPLATE_PRE = [
|
|
55
|
+
'"use strict";',
|
|
56
|
+
'// Module-scope helpers: accessible to BOTH the IIFE body AND the outer',
|
|
57
|
+
'// .then()/.catch() handlers (which run outside the IIFE). _workerLogs/',
|
|
58
|
+
'// _pushWorkerLog/_safePost + the parentPort/workerData handles must all live',
|
|
59
|
+
'// here — a previous version declared _safePost inside the IIFE, so the',
|
|
60
|
+
'// .then()/.catch() return/error handlers threw ReferenceError: _safePost is',
|
|
61
|
+
'// not defined, crashing the Worker on EVERY script return (exit code 1) and',
|
|
62
|
+
'// losing all diagnostics. require() is cached, so destructuring once at module',
|
|
63
|
+
'// load and reusing everywhere avoids the redundant require calls that used to',
|
|
64
|
+
'// appear in the IIFE and the outer .then/.catch.',
|
|
65
|
+
'const { parentPort: _parentPort, workerData: _workerData } = require("node:worker_threads");',
|
|
66
|
+
'const _workerLogs = [];',
|
|
67
|
+
'// IF6(#12): known agent() fields — hoisted to module scope, built once per worker',
|
|
68
|
+
'// (was rebuilt inside agent() on every call; field set is call-invariant).',
|
|
69
|
+
'const _KNOWN_FIELDS = new Set(["prompt", "description", "schema", "model", "scene", "label", "task", "agent", "phase", "skill", "timeoutMs", "maxTurns", "cwd", "fork", "worktree", "returnMeta", "thinkingLevel", "engine"]);',
|
|
70
|
+
'function _pushWorkerLog(level, args) {',
|
|
71
|
+
' try { _workerLogs.push({ level, message: args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ") }); } catch (e) { /* swallow */ }',
|
|
72
|
+
'}',
|
|
73
|
+
'// ── safePostMessage wrapper: 统一 postMessage 防御(DataCloneError 等)──',
|
|
74
|
+
'// Module-scope so the outer .then/.catch return/error handlers can use it.',
|
|
75
|
+
'// context 取值约定(诊断标识):固定为消息类型字面量——',
|
|
76
|
+
'// "agent-call" / "workflow-call" / "return" / "error",调用方据此在日志里',
|
|
77
|
+
'// 一眼定位是哪类 postMessage 失败。新增调用点必须传对应 context。',
|
|
78
|
+
'function _safePost(msg, context) {',
|
|
79
|
+
' try { _parentPort.postMessage(msg); return true; }',
|
|
80
|
+
' catch (e) {',
|
|
81
|
+
' const errMsg = e && e.message ? e.message : String(e);',
|
|
82
|
+
' const stack = e && e.stack ? e.stack : "";',
|
|
83
|
+
' _pushWorkerLog("error", ["[postMessage failed:" + context + "]", errMsg, stack]);',
|
|
84
|
+
' return false;',
|
|
85
|
+
' }',
|
|
86
|
+
'}',
|
|
87
|
+
'(async () => {',
|
|
88
|
+
' const parentPort = _parentPort;',
|
|
89
|
+
' const workerData = _workerData;',
|
|
90
|
+
'',
|
|
91
|
+
' if (!parentPort) {',
|
|
92
|
+
' throw new Error("Workflow worker: parentPort is null — not running in a Worker thread");',
|
|
93
|
+
' }',
|
|
94
|
+
'',
|
|
95
|
+
' // ── Intercept console.* to avoid leaking worker diagnostics into the input area ──',
|
|
96
|
+
' console.log = function (...args) { _pushWorkerLog("log", args); };',
|
|
97
|
+
' console.warn = function (...args) { _pushWorkerLog("warn", args); };',
|
|
98
|
+
' console.error = function (...args) { _pushWorkerLog("error", args); };',
|
|
99
|
+
' console.info = function (...args) { _pushWorkerLog("info", args); };',
|
|
100
|
+
'',
|
|
101
|
+
' // ── Internal state ──',
|
|
102
|
+
' let _callIdCounter = 0;',
|
|
103
|
+
' let _agentCallCount = 0;',
|
|
104
|
+
' const _pendingCalls = new Map();',
|
|
105
|
+
' const _callCache = workerData.callCache instanceof Map',
|
|
106
|
+
' ? workerData.callCache',
|
|
107
|
+
' : new Map(Object.entries(workerData.callCache || {}).map(([k, v]) => [Number(k), v]));',
|
|
108
|
+
'',
|
|
109
|
+
' // ── Injected globals ──',
|
|
110
|
+
' const $ARGS = (workerData.args && typeof workerData.args === "object") ? workerData.args : {};\n' +
|
|
111
|
+
' const args = $ARGS;',
|
|
112
|
+
' const $WORKSPACE = typeof workerData.workspace === "string" ? workerData.workspace : "";',
|
|
113
|
+
' const _budgetData = {',
|
|
114
|
+
' total: (workerData.budget && workerData.budget.maxTokens) || 0,',
|
|
115
|
+
' _spentTokens: (workerData.budget && workerData.budget.usedTokens) ?? 0,',
|
|
116
|
+
' _spentCost: (workerData.budget && workerData.budget.usedCost) ?? 0,',
|
|
117
|
+
' };',
|
|
118
|
+
' const $BUDGET = {',
|
|
119
|
+
' get total() { return _budgetData.total; },',
|
|
120
|
+
' spent() { return _budgetData._spentTokens; },',
|
|
121
|
+
' remaining() { return Math.max(0, _budgetData.total - _budgetData._spentTokens); },',
|
|
122
|
+
' };',
|
|
123
|
+
'',
|
|
124
|
+
' // ── Run-level model/thinkingLevel override (Option B symmetric single-path) ──',
|
|
125
|
+
' // Injected from workerData (set by workerHost from RunSpec.model/thinkingLevel).',
|
|
126
|
+
' // agent() reads these as run-level defaults; per-call explicit opts still win.',
|
|
127
|
+
' const $MODEL = (workerData.model && typeof workerData.model === "string") ? workerData.model : undefined;',
|
|
128
|
+
' const $THINKING_LEVEL = (workerData.thinkingLevel && typeof workerData.thinkingLevel === "string") ? workerData.thinkingLevel : undefined;',
|
|
129
|
+
'',
|
|
130
|
+
' // ── WorkflowAbortedError ──',
|
|
131
|
+
' class WorkflowAbortedError extends Error {',
|
|
132
|
+
' constructor(reason) {',
|
|
133
|
+
' super("Workflow aborted: " + (reason || "No reason"));',
|
|
134
|
+
' this.name = "WorkflowAbortedError";',
|
|
135
|
+
' this.reason = reason || "";',
|
|
136
|
+
' }',
|
|
137
|
+
' }',
|
|
138
|
+
'',
|
|
139
|
+
' // ── Message handler (main thread → worker) ──',
|
|
140
|
+
' parentPort.on("message", (msg) => {',
|
|
141
|
+
' if (msg.type === "agent-result") {',
|
|
142
|
+
' const pending = _pendingCalls.get(msg.callId);',
|
|
143
|
+
' if (pending) {',
|
|
144
|
+
' _pendingCalls.delete(msg.callId);',
|
|
145
|
+
' if (typeof msg.result !== "undefined") {',
|
|
146
|
+
' _callCache.set(msg.callId, msg.result);',
|
|
147
|
+
' }',
|
|
148
|
+
' // 失败不传播到 agent 外部:resolve 而非 reject。',
|
|
149
|
+
' // 旧实现在 result.error 时 reject,单 agent 失败会冒到 worker 顶层 .catch()',
|
|
150
|
+
' // → 发 type:"error" → handleScriptError → rebuildRuntime → SIGKILL 同伴进程,',
|
|
151
|
+
' // 把单点失败放大成整批崩溃。改为始终 resolve(错误时回退到 content 文本),',
|
|
152
|
+
' // 让 parallel() 下的脚本容错循环(parseResult → null → skip)自然接管。',
|
|
153
|
+
' // 错误原因已由主线程 executeAgentCall → trace.update(result.error) 保留在 trace/TUI,',
|
|
154
|
+
' // 不丢失。失败 resolve 为空字符串是既定容错策略。',
|
|
155
|
+
' // [MF-4] schema 模式下失败时 agent() 仍 resolve(content 回退、不 throw)——',
|
|
156
|
+
' // 需要检查错误时请用 returnMeta:true(resolve 值含 error 字段)。',
|
|
157
|
+
' // parsedOutput: validated data object from structured-output execute().',
|
|
158
|
+
' // Fallback to content (raw text) when no schema was requested or on error.',
|
|
159
|
+
' // W2 改动 9(b):returnMeta===true 时 resolve {value,sessionFile,worktreePath,error,usage,durationMs,sessionId}',
|
|
160
|
+
' // (对称缓存重放 9c);否则单值。usage/durationMs/sessionId 为 rfl 仪表透传(tier-1 §7.1,',
|
|
161
|
+
' // AgentResult 已含、此处原样转发,缺省 undefined 兼容旧主线程)。',
|
|
162
|
+
' const _value = msg.result.parsedOutput ?? msg.result.content;',
|
|
163
|
+
' if (pending.returnMeta) {',
|
|
164
|
+
' pending.resolve({',
|
|
165
|
+
' value: _value,',
|
|
166
|
+
' sessionFile: msg.result.sessionFile,',
|
|
167
|
+
' worktreePath: msg.result.worktreePath,',
|
|
168
|
+
' error: msg.result.error,',
|
|
169
|
+
' usage: msg.result.usage,',
|
|
170
|
+
' durationMs: msg.result.durationMs,',
|
|
171
|
+
' sessionId: msg.result.sessionId,',
|
|
172
|
+
' });',
|
|
173
|
+
' } else {',
|
|
174
|
+
' pending.resolve(_value);',
|
|
175
|
+
' }',
|
|
176
|
+
' }',
|
|
177
|
+
' } else if (msg.type === "workflow-result") {',
|
|
178
|
+
' const pending = _pendingCalls.get(msg.callId);',
|
|
179
|
+
' if (pending) {',
|
|
180
|
+
' _pendingCalls.delete(msg.callId);',
|
|
181
|
+
' pending.resolve(msg.result);',
|
|
182
|
+
' }',
|
|
183
|
+
' } else if (msg.type === "abort") {',
|
|
184
|
+
' const err = new WorkflowAbortedError(msg.reason);',
|
|
185
|
+
' _pendingCalls.forEach((p) => { p.reject(err); });',
|
|
186
|
+
' _pendingCalls.clear();',
|
|
187
|
+
' } else if (msg.type === "budget-update" && msg.budget) {',
|
|
188
|
+
' _budgetData._spentTokens = msg.budget.usedTokens ?? _budgetData._spentTokens;',
|
|
189
|
+
' _budgetData._spentCost = msg.budget.usedCost ?? _budgetData._spentCost;',
|
|
190
|
+
' }',
|
|
191
|
+
' // "budget-warning" is informational; no required handling',
|
|
192
|
+
' });',
|
|
193
|
+
'',
|
|
194
|
+
// ── phase global ──
|
|
195
|
+
' let _currentPhase = "";',
|
|
196
|
+
' function phase(name) { _currentPhase = String(name); }',
|
|
197
|
+
'',
|
|
198
|
+
// ── log global ──
|
|
199
|
+
' function log(msg) {',
|
|
200
|
+
' try { parentPort.postMessage({ type: "log", phase: _currentPhase, message: String(msg) }); } catch(e) { /* swallow */ }',
|
|
201
|
+
' }',
|
|
202
|
+
'',
|
|
203
|
+
// ── agent global — CC-compatible multi-signature ──
|
|
204
|
+
' async function agent(firstArg, secondArg) {',
|
|
205
|
+
' let opts;',
|
|
206
|
+
' if (typeof firstArg === "string") {',
|
|
207
|
+
' opts = {',
|
|
208
|
+
' prompt: firstArg,',
|
|
209
|
+
' description: (secondArg && typeof secondArg === "object" && secondArg.label) || undefined,',
|
|
210
|
+
' schema: (secondArg && typeof secondArg === "object" && secondArg.schema) || undefined,',
|
|
211
|
+
' model: (secondArg && typeof secondArg === "object" && secondArg.model) || $MODEL,',
|
|
212
|
+
' scene: (secondArg && typeof secondArg === "object" && secondArg.scene) || undefined,\n' +
|
|
213
|
+
' phase: (secondArg && typeof secondArg === "object" && secondArg.phase) || undefined,',
|
|
214
|
+
' thinkingLevel: (secondArg && typeof secondArg === "object" && secondArg.thinkingLevel) || $THINKING_LEVEL,',
|
|
215
|
+
' // step 级 turn 上限(turn limiter;显式 0/负 = 显式不限,压过 spawn watchdog env 兑底,SP-6)\n' +
|
|
216
|
+
' // ?? 语义保真:仅 null/undefined 归 undefined(走 env 兑底),显式 0 保留(U5 参数 > env)',
|
|
217
|
+
' maxTurns: (secondArg && typeof secondArg === "object" ? secondArg.maxTurns : undefined) ?? undefined,',
|
|
218
|
+
' // P4 D9③:step 级 engine 显式指定(仅限必须某引擎独有能力的场景)',
|
|
219
|
+
' engine: (secondArg && typeof secondArg === "object" && secondArg.engine) || undefined,',
|
|
220
|
+
' };',
|
|
221
|
+
' } else if (typeof firstArg === "object" && firstArg !== null) {',
|
|
222
|
+
' if (firstArg.prompt) {',
|
|
223
|
+
' opts = firstArg;',
|
|
224
|
+
' } else if (firstArg.task || firstArg.agent) {',
|
|
225
|
+
' // fork/worktree/returnMeta forwarded here (parity with agent({prompt,...}) and direct-pass',
|
|
226
|
+
' // branches). Previously this shortcut dropped them (w1 only noted, not wired).',
|
|
227
|
+
' opts = {',
|
|
228
|
+
' prompt: firstArg.task || firstArg.prompt || "",',
|
|
229
|
+
' description: firstArg.label || firstArg.description,',
|
|
230
|
+
' agent: firstArg.agent,',
|
|
231
|
+
' schema: firstArg.schema,',
|
|
232
|
+
' model: firstArg.model || $MODEL,',
|
|
233
|
+
' scene: firstArg.scene,',
|
|
234
|
+
' skill: firstArg.skill,',
|
|
235
|
+
' timeoutMs: firstArg.timeoutMs,',
|
|
236
|
+
' maxTurns: firstArg.maxTurns,',
|
|
237
|
+
' cwd: firstArg.cwd,',
|
|
238
|
+
' fork: firstArg.fork,',
|
|
239
|
+
' worktree: firstArg.worktree,',
|
|
240
|
+
' returnMeta: firstArg.returnMeta,',
|
|
241
|
+
' thinkingLevel: firstArg.thinkingLevel || $THINKING_LEVEL,',
|
|
242
|
+
' engine: firstArg.engine,',
|
|
243
|
+
' };',
|
|
244
|
+
' } else {',
|
|
245
|
+
' opts = firstArg;',
|
|
246
|
+
' }',
|
|
247
|
+
' } else {',
|
|
248
|
+
' throw new Error("agent() requires a prompt string or options object as first argument");',
|
|
249
|
+
' }',
|
|
250
|
+
'',
|
|
251
|
+
' // Run-level fallback for object branches (opts = firstArg direct assign).',
|
|
252
|
+
' // Priority: agent() explicit > $MODEL global (set from workerData.model).',
|
|
253
|
+
' if (!opts.model && $MODEL) opts.model = $MODEL;',
|
|
254
|
+
' if (!opts.thinkingLevel && $THINKING_LEVEL) opts.thinkingLevel = $THINKING_LEVEL;',
|
|
255
|
+
'',
|
|
256
|
+
' // Validate known agent() fields to catch API misuse early (_KNOWN_FIELDS at module scope)',
|
|
257
|
+
' const _unknownFields = Object.keys(opts).filter((k) => !_KNOWN_FIELDS.has(k));',
|
|
258
|
+
' if (_unknownFields.length > 0) {',
|
|
259
|
+
' _pushWorkerLog("warn", ["[workflow] agent() received unknown fields: " + _unknownFields.join(", ") + ". Known fields: prompt, description, schema, model, scene, label, task, agent, phase, skill, timeoutMs, maxTurns, cwd, fork, worktree, returnMeta, thinkingLevel, engine"]);',
|
|
260
|
+
' }',
|
|
261
|
+
'',
|
|
262
|
+
' const callId = _callIdCounter;',
|
|
263
|
+
' _callIdCounter++;',
|
|
264
|
+
' _agentCallCount++;',
|
|
265
|
+
' if (_callCache.has(callId)) {',
|
|
266
|
+
' const cached = _callCache.get(callId);',
|
|
267
|
+
' // 与 live handler 对齐:失败也 resolve(回退 content),不 throw。',
|
|
268
|
+
' // 见 agent-result 消息处理的注释:拒绝传播失败到 agent 外部。',
|
|
269
|
+
' // W2 改动 9(c):returnMeta 分支(对称 handler 9b),opts 仍在闭包作用域。',
|
|
270
|
+
' if (!cached) return undefined;',
|
|
271
|
+
' const _cachedValue = cached.parsedOutput ?? cached.content;',
|
|
272
|
+
' if (opts.returnMeta === true) {',
|
|
273
|
+
' return {',
|
|
274
|
+
' value: _cachedValue,',
|
|
275
|
+
' sessionFile: cached.sessionFile,',
|
|
276
|
+
' worktreePath: cached.worktreePath,',
|
|
277
|
+
' error: cached.error,',
|
|
278
|
+
' usage: cached.usage,',
|
|
279
|
+
' durationMs: cached.durationMs,',
|
|
280
|
+
' sessionId: cached.sessionId,',
|
|
281
|
+
' };',
|
|
282
|
+
' }',
|
|
283
|
+
' return _cachedValue;',
|
|
284
|
+
' }',
|
|
285
|
+
'',
|
|
286
|
+
' const _effectivePhase = opts.phase || _currentPhase;\n' +
|
|
287
|
+
' delete opts.phase;\n' +
|
|
288
|
+
'\n' +
|
|
289
|
+
' if (!_safePost({ type: "agent-call", callId, opts, phase: _effectivePhase }, "agent-call")) {',
|
|
290
|
+
' return Promise.reject(new Error("postMessage failed for agent-call (callId=" + callId + "): see workerLogs"));',
|
|
291
|
+
' }',
|
|
292
|
+
' return new Promise((resolve, reject) => {',
|
|
293
|
+
' _pendingCalls.set(callId, { resolve, reject, returnMeta: opts.returnMeta === true });',
|
|
294
|
+
' });',
|
|
295
|
+
' }',
|
|
296
|
+
'',
|
|
297
|
+
// ── parallel global — CC-compatible ──
|
|
298
|
+
// allSettled 语义:单个 agent 的意外 reject(postMessage 失败等基础设施异常、abort)
|
|
299
|
+
// 不拖垮整批。rejected 结果降级为错误消息字符串(与 agent() 的 error→content 回退一致,
|
|
300
|
+
// parseResult(string) → null → 脚本 soft-fail)。B1 之后 agent() 不再因 agent 失败 reject,
|
|
301
|
+
// 这里作为纵深防御保留。
|
|
302
|
+
//
|
|
303
|
+
// **Promise 项处理**:CC-compatible 写法 `parallel([agent({...}), ...])` 传入的是已实例化
|
|
304
|
+
// 的 Promise 数组(agent() 同步返回 Promise)。Promise 是 object 但无 .then 鸭辨分支时
|
|
305
|
+
// 会落到 `agent(c)` 把 Promise 当 opts 传 → postMessage DataCloneError。必须在函数/opts
|
|
306
|
+
// 分支前用 thenable 鸭辨直接返回 in-flight Promise,让 allSettled 接管。
|
|
307
|
+
' async function parallel(calls) {',
|
|
308
|
+
' if (typeof calls === "function") { return calls(); }',
|
|
309
|
+
' const settled = await Promise.allSettled(calls.map((c) => {',
|
|
310
|
+
' if (c && typeof c.then === "function") { return c; }',
|
|
311
|
+
' if (typeof c === "function") { return c(); }',
|
|
312
|
+
' if (typeof c === "object" && c !== null && (c.task || c.agent)) { return agent(c); }',
|
|
313
|
+
' return agent(c);',
|
|
314
|
+
' }));',
|
|
315
|
+
' return settled.map((r) => {',
|
|
316
|
+
' if (r.status === "fulfilled") {',
|
|
317
|
+
' const v = r.value;',
|
|
318
|
+
' if (v !== null && typeof v === "object" && !Array.isArray(v)) {',
|
|
319
|
+
' // 主线程 fallback(postAgentResult/postResult serialization failed)回发的对象含 error 字段',
|
|
320
|
+
' // → 归一化为 failed 形状,与脚本侧 r.status === "failed" 检查统一',
|
|
321
|
+
' if (typeof v.error === "string" && v.error.length > 0) return { status: "failed", error: v.error };',
|
|
322
|
+
' return v;',
|
|
323
|
+
' }',
|
|
324
|
+
' return { status: "failed", error: "agent returned non-object result (type=" + typeof v + ")" };',
|
|
325
|
+
' }',
|
|
326
|
+
' const reason = r.reason;',
|
|
327
|
+
' const errMsg = reason instanceof Error ? reason.message : String(reason);',
|
|
328
|
+
' return { status: "failed", error: errMsg };',
|
|
329
|
+
' });',
|
|
330
|
+
' }',
|
|
331
|
+
'',
|
|
332
|
+
// ── pipeline global ──
|
|
333
|
+
' async function pipeline(firstArg, ...restStages) {',
|
|
334
|
+
' // Single-arg mode: pipeline([stage1, stage2, ...])',
|
|
335
|
+
' if (Array.isArray(firstArg) && restStages.length === 0) {',
|
|
336
|
+
' let result;',
|
|
337
|
+
' for (let i = 0; i < firstArg.length; i++) {',
|
|
338
|
+
' try { result = await firstArg[i](result); }',
|
|
339
|
+
' catch (e) {',
|
|
340
|
+
' const msg = e && e.message ? e.message : String(e);',
|
|
341
|
+
' _pushWorkerLog("error", ["[pipeline stage " + i + " failed]", msg]);',
|
|
342
|
+
' throw e;',
|
|
343
|
+
' }',
|
|
344
|
+
' }',
|
|
345
|
+
' return result;',
|
|
346
|
+
' }',
|
|
347
|
+
' // Cartesian product mode: pipeline([items], stage1, stage2, ...)',
|
|
348
|
+
' if (Array.isArray(firstArg) && restStages.length > 0 && typeof restStages[0] === "function") {',
|
|
349
|
+
' const results = [];',
|
|
350
|
+
' for (let idx = 0; idx < firstArg.length; idx++) {',
|
|
351
|
+
' const item = firstArg[idx];',
|
|
352
|
+
' let val = item;',
|
|
353
|
+
' let failed = false;',
|
|
354
|
+
' for (const stage of restStages) {',
|
|
355
|
+
' if (failed) break;',
|
|
356
|
+
' try { val = await stage(val); }',
|
|
357
|
+
' catch (e) {',
|
|
358
|
+
' const msg = e && e.message ? e.message : String(e);',
|
|
359
|
+
' _pushWorkerLog("error", ["[pipeline cartesian stage failed for item " + (idx + 1) + "]", msg]);',
|
|
360
|
+
' val = null; failed = true;',
|
|
361
|
+
' }',
|
|
362
|
+
' }',
|
|
363
|
+
' results.push(val);',
|
|
364
|
+
' }',
|
|
365
|
+
' return results;',
|
|
366
|
+
' }',
|
|
367
|
+
' throw new Error("pipeline() expects pipeline([stage1, ...]) or pipeline([items], stage1, ...)");',
|
|
368
|
+
' }',
|
|
369
|
+
'',
|
|
370
|
+
' // ── workflow global — nested workflow invocation ──',
|
|
371
|
+
' async function workflow(name, args) {',
|
|
372
|
+
' if (typeof name !== "string" || name.length === 0) {',
|
|
373
|
+
' throw new Error("workflow() requires a workflow name string as first argument");',
|
|
374
|
+
' }',
|
|
375
|
+
' const workflowArgs = (typeof args === "object" && args !== null) ? args : {};',
|
|
376
|
+
' const callId = _callIdCounter;',
|
|
377
|
+
' _callIdCounter++;',
|
|
378
|
+
' if (!_safePost({ type: "workflow-call", callId, name, args: workflowArgs }, "workflow-call")) {',
|
|
379
|
+
' return Promise.reject(new Error("postMessage failed for workflow-call (name=" + name + "): see workerLogs"));',
|
|
380
|
+
' }',
|
|
381
|
+
' return new Promise((resolve, reject) => {',
|
|
382
|
+
' _pendingCalls.set(callId, { resolve, reject });',
|
|
383
|
+
' });',
|
|
384
|
+
' }',
|
|
385
|
+
'',
|
|
386
|
+
' // ── User workflow script ──',
|
|
387
|
+
].join("\n");
|
|
388
|
+
|
|
389
|
+
/** POST 段:auto-invoke execute() + 顶层 .then/.catch return/error handler。 */
|
|
390
|
+
const WORKER_TEMPLATE_POST = [
|
|
391
|
+
' // ── Auto-invoke execute() for module.exports pattern ──',
|
|
392
|
+
' if (typeof module !== "undefined" && module.exports && typeof module.exports.execute === "function") {',
|
|
393
|
+
' return await module.exports.execute({ agent, parallel, pipeline, phase, log, workflow, $ARGS, $WORKSPACE, $BUDGET });',
|
|
394
|
+
' }',
|
|
395
|
+
'})().then((result) => {',
|
|
396
|
+
' const runId = (_workerData.args && typeof _workerData.args === "object" && _workerData.args._runId) || "";',
|
|
397
|
+
' if (!_safePost({ type: "return", runId, result, workerLogs: _workerLogs }, "return")) {',
|
|
398
|
+
' // [F1] return 值不可克隆(含 function/Symbol/循环引用 → DataCloneError)时 _safePost',
|
|
399
|
+
' // 只能记日志返回 false——若不补救,worker 将静默 exit(0),主线程收不到任何终态消息,',
|
|
400
|
+
' // run 永久 running、runAndWait 悬挂。回发可克隆的 error 消息(DataCloneError 详情',
|
|
401
|
+
' // 已由 _safePost 记入 _workerLogs 随消息带回),让主线程 handleScriptError 接管,',
|
|
402
|
+
' // run 经既有重试矩阵收敛到终态 failed。',
|
|
403
|
+
' _safePost({ type: "error", runId, error: "Workflow return value could not be delivered (structured-clone failed) — see workerLogs for the postMessage error", workerLogs: _workerLogs }, "error");',
|
|
404
|
+
' }',
|
|
405
|
+
'}).catch((err) => {',
|
|
406
|
+
' const runId = (_workerData.args && typeof _workerData.args === "object" && _workerData.args._runId) || "";',
|
|
407
|
+
' _safePost({ type: "error", runId, error: err.message || String(err), workerLogs: _workerLogs }, "error");',
|
|
408
|
+
'});',
|
|
409
|
+
].join("\n");
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Build the complete worker source text by wrapping the user's workflow script
|
|
413
|
+
* with infrastructure code and injected global functions.
|
|
414
|
+
*
|
|
415
|
+
* AC-4:脚本格式不变(用户资产)。逐字保留旧 buildWorkerScript 的拼接逻辑——
|
|
416
|
+
* 三段 concat 的换行语义 = 原单数组 join("\n")(userScript 段带 2 空格缩进,
|
|
417
|
+
* 段后一个空行接 auto-invoke 段),byte-identical 快照锚定。
|
|
418
|
+
*/
|
|
419
|
+
export function buildWorkerScript(userScript: string): string {
|
|
420
|
+
return WORKER_TEMPLATE_PRE + "\n " + userScript + "\n\n" + WORKER_TEMPLATE_POST;
|
|
421
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow 文件持久化操作(save / delete)。
|
|
3
|
+
*
|
|
4
|
+
* 历史:saveWorkflow 曾有两套实现——commands.ts 用 renameSync 仅 project scope,
|
|
5
|
+
* WorkflowsView.ts 用 copyFileSync 支持 user scope。本次统一为 rename + 仅 project
|
|
6
|
+
* scope(决策 2):tmp 文件保存后自动消失,保存位置固定 .pi/workflows/。
|
|
7
|
+
*
|
|
8
|
+
* 代价:TUI 失去 user scope Tab 切换(功能倒退,已接受);
|
|
9
|
+
* Windows/跨设备 rename 可能失败(已知风险,接受)。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, mkdirSync, renameSync, unlinkSync } from "node:fs";
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
|
|
15
|
+
// ── Path helpers (computed at call time to respect cwd changes in tests) ──
|
|
16
|
+
|
|
17
|
+
function getTmpDir(): string {
|
|
18
|
+
return resolve(".pi/workflows/.tmp");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getSavedDir(): string {
|
|
22
|
+
return resolve(".pi/workflows");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── Save ──────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 保存临时 workflow:.pi/workflows/.tmp/{name}.js → .pi/workflows/{newName||name}.js
|
|
29
|
+
* 用 rename(tmp 文件保存后消失)。仅 project scope。
|
|
30
|
+
*
|
|
31
|
+
* 直接按路径查找 tmp 文件,不调 config-loader 全扫——save 只需知道 tmp 文件
|
|
32
|
+
* 的路径,不需要 meta 提取或跨目录去重。
|
|
33
|
+
*
|
|
34
|
+
* @throws 若 tmp workflow 不存在、目标已存在、或 rename 失败
|
|
35
|
+
*/
|
|
36
|
+
export async function saveWorkflow(tmpName: string, newName?: string): Promise<string> {
|
|
37
|
+
const srcPath = resolve(getTmpDir(), `${tmpName}.js`);
|
|
38
|
+
if (!existsSync(srcPath)) {
|
|
39
|
+
throw new Error(`Temporary workflow '${tmpName}' not found`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const destName = newName ?? tmpName;
|
|
43
|
+
const savedDir = getSavedDir();
|
|
44
|
+
const destPath = resolve(savedDir, `${destName}.js`);
|
|
45
|
+
|
|
46
|
+
if (existsSync(destPath)) {
|
|
47
|
+
throw new Error(`'${destName}' already exists in saved workflows. Use a different name.`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
mkdirSync(savedDir, { recursive: true });
|
|
51
|
+
renameSync(srcPath, destPath);
|
|
52
|
+
return `Saved '${tmpName}' → '${destName}' (${destPath})`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Delete ────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 删除 workflow 脚本文件(tmp 或 saved)。
|
|
59
|
+
* @param isRunning 回调,判断某 name 是否正在运行(运行中拒绝删除)
|
|
60
|
+
* @throws 若正在运行、或文件不存在
|
|
61
|
+
*/
|
|
62
|
+
export function deleteWorkflow(
|
|
63
|
+
name: string,
|
|
64
|
+
isRunning: (name: string) => boolean,
|
|
65
|
+
): string {
|
|
66
|
+
if (isRunning(name)) {
|
|
67
|
+
throw new Error(`Cannot delete '${name}': workflow is currently running. Abort it first.`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const tmpDir = getTmpDir();
|
|
71
|
+
const savedDir = getSavedDir();
|
|
72
|
+
const candidates = [
|
|
73
|
+
resolve(tmpDir, `${name}.js`),
|
|
74
|
+
resolve(savedDir, `${name}.js`),
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
for (const filePath of candidates) {
|
|
78
|
+
if (existsSync(filePath)) {
|
|
79
|
+
unlinkSync(filePath);
|
|
80
|
+
return `Deleted workflow '${name}' (${filePath})`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throw new Error(`Workflow file '${name}' not found`);
|
|
85
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Extension — WorkflowScriptRegistryImpl
|
|
3
|
+
*
|
|
4
|
+
* WorkflowScriptRegistry port 的 Infra 实现。
|
|
5
|
+
*
|
|
6
|
+
* 职责:扫描 .pi/workflows/ + ~/.pi/agent/workflows/ 目录,按 regex 提取
|
|
7
|
+
* meta(不执行用户代码),按 tmp>project>user 优先级去重,60s TTL 缓存。
|
|
8
|
+
*
|
|
9
|
+
* 层归属:Infra(D-12)。implements Engine 层的 WorkflowScriptRegistry port。
|
|
10
|
+
*
|
|
11
|
+
* 设计:
|
|
12
|
+
* - WorkflowScriptRegistryImpl 是 port 的实现,但底层扫描/缓存/去重仍委托
|
|
13
|
+
* config-loader.ts 的 loadWorkflows/getWorkflow/invalidateCache 自由函数
|
|
14
|
+
* (config-loader 是稳定 Infra 工具,registry 在其上包装为 WorkflowScript 实体)。
|
|
15
|
+
* - 返回 WorkflowScript 实体(而非裸 CachedWorkflowMeta)。
|
|
16
|
+
* - get(name) 精确匹配;fuzzy 匹配由 Interface 层 tool 负责。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { WorkflowScript } from "./models/workflow-script.ts";
|
|
20
|
+
import { getCachedFileContent } from "../shared/resource-discovery.ts";
|
|
21
|
+
import type { WorkflowScriptRegistry } from "./models/workflow-script-registry.ts";
|
|
22
|
+
import {
|
|
23
|
+
type CachedWorkflowMeta,
|
|
24
|
+
discoverWorkflows,
|
|
25
|
+
getWorkflow,
|
|
26
|
+
getWorkflowByPath,
|
|
27
|
+
invalidateCache,
|
|
28
|
+
loadWorkflows,
|
|
29
|
+
type WorkflowScanConfig,
|
|
30
|
+
} from "./config-loader.ts";
|
|
31
|
+
|
|
32
|
+
// ── WorkflowScriptRegistryImpl ───────────────────────────────
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* WorkflowScriptRegistry port 的 Infra 实现。
|
|
36
|
+
*
|
|
37
|
+
* @param config 可选扫描配置。传入时 registry 只扫 config 声明的目录
|
|
38
|
+
* (测试隔离用);省略时走生产默认(全局 ~/.pi/agent/* 目录)。
|
|
39
|
+
*/
|
|
40
|
+
export class WorkflowScriptRegistryImpl implements WorkflowScriptRegistry {
|
|
41
|
+
constructor(private readonly config?: WorkflowScanConfig) {}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 扫描所有 workflow 脚本(project + user + tmp),按 tmp>project>user 优先级
|
|
45
|
+
* 去重,返回 WorkflowScript 实体数组(含 available=false 的解析失败项)。
|
|
46
|
+
*
|
|
47
|
+
* 60s TTL 缓存——同 workspace 60s 内重复调用走缓存。
|
|
48
|
+
*/
|
|
49
|
+
async loadAll(): Promise<WorkflowScript[]> {
|
|
50
|
+
const metas = this.config
|
|
51
|
+
? await discoverWorkflows(this.config)
|
|
52
|
+
: await loadWorkflows();
|
|
53
|
+
return metas.map((m) => this.toScript(m));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 按名查单个脚本。精确匹配。
|
|
58
|
+
* 返回 undefined 当 name 不存在。
|
|
59
|
+
*
|
|
60
|
+
* 注:fuzzy 匹配由 Interface 层 tool-workflow负责——registry 只做精确查。
|
|
61
|
+
*
|
|
62
|
+
* 性能注记:无 config(生产路径)时走 getWorkflow 的 60s TTL 单条缓存。
|
|
63
|
+
* 有 config(测试隔离)时退化为每次 discoverWorkflows 全扫——测试场景
|
|
64
|
+
* 可接受,生产路径不受影响。
|
|
65
|
+
*/
|
|
66
|
+
async get(name: string): Promise<WorkflowScript | undefined> {
|
|
67
|
+
const meta = this.config
|
|
68
|
+
? (await discoverWorkflows(this.config)).find((w) => w.name === name)
|
|
69
|
+
: await getWorkflow(name);
|
|
70
|
+
return meta ? this.toScript(meta) : undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 按绝对路径加载单个脚本(S2 路径统一)。任意路径(不限扫描源)。
|
|
75
|
+
* 供 workflow tool 的 run/info(name 参数 = workflowRef)。
|
|
76
|
+
*/
|
|
77
|
+
async getPath(ref: string): Promise<WorkflowScript | undefined> {
|
|
78
|
+
const meta = await getWorkflowByPath(ref);
|
|
79
|
+
return meta ? this.toScript(meta) : undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 失效缓存——下次 loadAll/get 重新扫描文件系统。 */
|
|
83
|
+
invalidate(): void {
|
|
84
|
+
invalidateCache();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 把 CachedWorkflowMeta 转换为 WorkflowScript 实体。
|
|
89
|
+
*
|
|
90
|
+
* m2:整对象透传——m 已是 WorkflowMeta(CachedWorkflowMeta extends WorkflowMeta),
|
|
91
|
+
* 直接传 meta: m,不再 {name,description,phases} 重建。消灭第 3 处重映射,
|
|
92
|
+
* parameters/usage/when/notFor 一路流到 script.meta。
|
|
93
|
+
*
|
|
94
|
+
* sourceCode 在此 readFile 填充(FR-2:registry 是唯一读文件处)。
|
|
95
|
+
* available:meta 提取失败或文件不可读时为 false。
|
|
96
|
+
*/
|
|
97
|
+
private toScript(m: CachedWorkflowMeta): WorkflowScript {
|
|
98
|
+
// FR-2: registry 是唯一读文件处。readFileSync 填 sourceCode —— 这样 launcher/tool
|
|
99
|
+
// 直接调 toExecutable/validate 即可,无需各自 readFile(避免重复读,60s TTL 缓存生效)。
|
|
100
|
+
let sourceCode = "";
|
|
101
|
+
let available = m.available;
|
|
102
|
+
if (available) {
|
|
103
|
+
try {
|
|
104
|
+
const cachedContent = getCachedFileContent(m.path); // m5:统一 mtime 缓存层
|
|
105
|
+
if (cachedContent === null) {
|
|
106
|
+
// ENOENT/不可读竞态 → available=false(exec-review major-3:?? '' 会静默
|
|
107
|
+
// 返回空源码 workflow——恢复旧 readFileSync throw → catch → available=false 语义)
|
|
108
|
+
sourceCode = "";
|
|
109
|
+
available = false;
|
|
110
|
+
} else {
|
|
111
|
+
sourceCode = cachedContent;
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
// 文件不可读(race condition 删除、权限等)——标 available=false,
|
|
115
|
+
// 与 meta 提取失败的现有语义一致(loader "never throws")。
|
|
116
|
+
sourceCode = "";
|
|
117
|
+
available = false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return new WorkflowScript({
|
|
121
|
+
name: m.name,
|
|
122
|
+
source: m.source,
|
|
123
|
+
path: m.path,
|
|
124
|
+
sourceCode,
|
|
125
|
+
// meta: m 整对象透传(m2 决策):故意不做显式投影——投影会重引入
|
|
126
|
+
// {name,description,phases} 解构重映射反模式(m2 消灭的丢字段 bug)。
|
|
127
|
+
// CachedWorkflowMeta 多带的 path/available/source 是良性泄漏(无消费者
|
|
128
|
+
// 序列化 script.meta);若未来有消费者,改为组合式 { meta, path, ... } 而非投影。
|
|
129
|
+
meta: m,
|
|
130
|
+
available,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|