@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
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1852 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
CORE_PACKAGE_VERSION: () => CORE_PACKAGE_VERSION,
|
|
34
|
+
DEFAULT_DATA_ROOT: () => DEFAULT_DATA_ROOT,
|
|
35
|
+
abortRun: () => abortRun,
|
|
36
|
+
configureCore: () => configureCore,
|
|
37
|
+
configureNotifyDomain: () => configureNotifyDomain,
|
|
38
|
+
getLogger: () => getLogger,
|
|
39
|
+
routeEngine: () => routeEngine,
|
|
40
|
+
runWorkflow: () => runWorkflow
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(src_exports);
|
|
43
|
+
|
|
44
|
+
// src/core/host-services.ts
|
|
45
|
+
var import_node_os = require("os");
|
|
46
|
+
var import_node_path = require("path");
|
|
47
|
+
var DEFAULT_DATA_ROOT = (0, import_node_path.join)((0, import_node_os.homedir)(), ".subagent-core");
|
|
48
|
+
var configuredHost;
|
|
49
|
+
function configureCore(host) {
|
|
50
|
+
configuredHost = host;
|
|
51
|
+
}
|
|
52
|
+
var NULL_HOST = {
|
|
53
|
+
dataRoot() {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"[subagent-core] core_host_not_configured: HostServices is not configured \u2014 the host shell must call configureCore(host) during initialization, before any core API that needs host services is consumed. Recovery: pi shell wires HostServices in src/host/pi-host.ts of @zhushanwen/pi-subagent-workflow; zsw shell: see the wiring example in the @zhushanwen/subagent-core package README."
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
log(level, component, message, data) {
|
|
59
|
+
const line = `[${component}] ${message}`;
|
|
60
|
+
if (level === "error") {
|
|
61
|
+
if (data === void 0) console.error(line);
|
|
62
|
+
else console.error(line, data);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (level === "warn") {
|
|
66
|
+
if (data === void 0) console.warn(line);
|
|
67
|
+
else console.warn(line, data);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// discoveryRoots 刻意不实现:可选端口缺席 = undefined,由调用方走缺省发现语义。
|
|
72
|
+
};
|
|
73
|
+
function getHostServices() {
|
|
74
|
+
return configuredHost ?? NULL_HOST;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/core/logger.ts
|
|
78
|
+
var facadeCache = /* @__PURE__ */ new Map();
|
|
79
|
+
function getLogger(component) {
|
|
80
|
+
const existing = facadeCache.get(component);
|
|
81
|
+
if (existing) return existing;
|
|
82
|
+
const facade = {
|
|
83
|
+
debug(msg, data) {
|
|
84
|
+
getHostServices().log("debug", component, msg, data);
|
|
85
|
+
},
|
|
86
|
+
warn(msg, data) {
|
|
87
|
+
getHostServices().log("warn", component, msg, data);
|
|
88
|
+
},
|
|
89
|
+
error(msg, data) {
|
|
90
|
+
getHostServices().log("error", component, msg, data);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
facadeCache.set(component, facade);
|
|
94
|
+
return facade;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/core/notify-ports.ts
|
|
98
|
+
var configuredPorts;
|
|
99
|
+
function configureNotifyDomain(ports) {
|
|
100
|
+
configuredPorts = ports;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/execution/engine/common/errors.ts
|
|
104
|
+
var EngineError = class extends Error {
|
|
105
|
+
code;
|
|
106
|
+
/** 恢复指引:指向具体下一步(命令 / 配置路径 / 替代方案),非安慰性文案。 */
|
|
107
|
+
recovery;
|
|
108
|
+
constructor(code, detail, recovery) {
|
|
109
|
+
super(`${code}: ${detail}`);
|
|
110
|
+
this.name = "EngineError";
|
|
111
|
+
this.code = code;
|
|
112
|
+
this.recovery = recovery;
|
|
113
|
+
}
|
|
114
|
+
/** 结构化投影(InteractResult.code/message 与 GUI 警告条共用形态)。 */
|
|
115
|
+
toStructured() {
|
|
116
|
+
return { code: this.code, message: this.message, recovery: this.recovery };
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// src/execution/engine/registry.ts
|
|
121
|
+
var DEFAULT_ENGINE_ID = "pi";
|
|
122
|
+
var EngineNotFoundError = class extends Error {
|
|
123
|
+
/** 结构化错误码(§3.3.3 错误规格表的 code 列,供调用方程序化分流)。 */
|
|
124
|
+
code = "engine_not_found";
|
|
125
|
+
/** 请求的(未注册的)引擎 id。 */
|
|
126
|
+
engineId;
|
|
127
|
+
/** 请求时刻的已注册清单快照(防错误对象跨时间读 Map 的失真)。 */
|
|
128
|
+
registered;
|
|
129
|
+
/** 错误来源定位(agent .md 文件路径 / 配置键等;运行期 getEngine 无来源不传)。 */
|
|
130
|
+
source;
|
|
131
|
+
constructor(engineId, registered, source) {
|
|
132
|
+
super(
|
|
133
|
+
`engine_not_found: engine '${engineId}' is not registered. Registered engines: ${registered.length > 0 ? registered.join(", ") : "(none)"}. Recovery: check the engine id in the agent .md frontmatter (engine: field) or the global default engine setting, fix the typo, or install/register the engine first (registered engines are listed above).` + (source !== void 0 ? ` Source: ${source}.` : "")
|
|
134
|
+
);
|
|
135
|
+
this.name = "EngineNotFoundError";
|
|
136
|
+
this.engineId = engineId;
|
|
137
|
+
this.registered = registered;
|
|
138
|
+
this.source = source;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var ENGINE_REGISTRY_SLOT_KEY = /* @__PURE__ */ Symbol.for("@zhushanwen/pi-subagent-workflow.engineRegistry");
|
|
142
|
+
function getRegistrySlot() {
|
|
143
|
+
let slot = Reflect.get(globalThis, ENGINE_REGISTRY_SLOT_KEY);
|
|
144
|
+
if (!slot) {
|
|
145
|
+
slot = { factories: /* @__PURE__ */ new Map(), singletons: /* @__PURE__ */ new Map() };
|
|
146
|
+
Reflect.set(globalThis, ENGINE_REGISTRY_SLOT_KEY, slot);
|
|
147
|
+
}
|
|
148
|
+
return slot;
|
|
149
|
+
}
|
|
150
|
+
function getEngine(id) {
|
|
151
|
+
const slot = getRegistrySlot();
|
|
152
|
+
const cached = slot.singletons.get(id);
|
|
153
|
+
if (cached) return cached;
|
|
154
|
+
const factory = slot.factories.get(id);
|
|
155
|
+
if (!factory) {
|
|
156
|
+
throw new EngineNotFoundError(id, listEngines());
|
|
157
|
+
}
|
|
158
|
+
const engine = factory();
|
|
159
|
+
slot.singletons.set(id, engine);
|
|
160
|
+
return engine;
|
|
161
|
+
}
|
|
162
|
+
function hasEngine(id) {
|
|
163
|
+
return getRegistrySlot().factories.has(id);
|
|
164
|
+
}
|
|
165
|
+
function listEngines() {
|
|
166
|
+
return [...getRegistrySlot().factories.keys()];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/execution/engine/routing.ts
|
|
170
|
+
function hasText(v) {
|
|
171
|
+
return v !== void 0 && v !== "";
|
|
172
|
+
}
|
|
173
|
+
function resolveEngineRouting(input) {
|
|
174
|
+
if (hasText(input.callEngine)) {
|
|
175
|
+
return { engineId: input.callEngine, source: "call" };
|
|
176
|
+
}
|
|
177
|
+
if (hasText(input.agentEngine)) {
|
|
178
|
+
return { engineId: input.agentEngine, source: "frontmatter" };
|
|
179
|
+
}
|
|
180
|
+
if (hasText(input.globalDefaultEngine)) {
|
|
181
|
+
return { engineId: input.globalDefaultEngine, source: "default" };
|
|
182
|
+
}
|
|
183
|
+
return { engineId: DEFAULT_ENGINE_ID, source: "default" };
|
|
184
|
+
}
|
|
185
|
+
async function routeEngine(opts) {
|
|
186
|
+
const has = opts.hasEngineFn ?? hasEngine;
|
|
187
|
+
const get = opts.getEngineFn ?? getEngine;
|
|
188
|
+
const routing = resolveEngineRouting(opts.routing);
|
|
189
|
+
if (!has(routing.engineId)) {
|
|
190
|
+
throw new EngineNotFoundError(routing.engineId, opts.listEnginesFn?.() ?? listEngines(), describeRoutingSource(opts.routing));
|
|
191
|
+
}
|
|
192
|
+
if (routing.engineId === DEFAULT_ENGINE_ID) {
|
|
193
|
+
return {
|
|
194
|
+
engine: get(routing.engineId),
|
|
195
|
+
engineId: routing.engineId,
|
|
196
|
+
requestedEngineId: routing.engineId,
|
|
197
|
+
source: routing.source
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const report = await opts.probe(routing.engineId);
|
|
201
|
+
if (report.ok) {
|
|
202
|
+
return {
|
|
203
|
+
engine: get(routing.engineId),
|
|
204
|
+
engineId: routing.engineId,
|
|
205
|
+
requestedEngineId: routing.engineId,
|
|
206
|
+
source: routing.source
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (opts.strict) {
|
|
210
|
+
throw probeFailedError(routing.engineId, report, "engineRouting.strict=true\uFF1Aprobe \u5931\u8D25\u4E00\u5F8B\u62A5\u9519\uFF08\u4E0D fallback\uFF09");
|
|
211
|
+
}
|
|
212
|
+
if (routing.source === "call") {
|
|
213
|
+
throw probeFailedError(routing.engineId, report, "engine \u6765\u81EA\u8C03\u7528\u53C2\u6570\u663E\u5F0F\u6307\u5B9A\uFF08\u80FD\u529B\u4F9D\u8D56\u58F0\u660E\uFF09\u2014\u2014\u4E0D\u515C\u5E95");
|
|
214
|
+
}
|
|
215
|
+
const fallbackId = fallbackTargetId(opts.routing, routing);
|
|
216
|
+
if (hasText(opts.taskModel) && fallbackId !== routing.engineId) {
|
|
217
|
+
throw new EngineError(
|
|
218
|
+
"model_not_available",
|
|
219
|
+
`engine '${routing.engineId}' probe \u5931\u8D25\u4E14\u4EFB\u52A1\u663E\u5F0F\u6307\u5B9A model '${opts.taskModel}'\u2014\u2014model \u4E0E\u5F15\u64CE provider \u4F53\u7CFB\u7ED1\u5B9A\uFF0C\u6362\u5F15\u64CE\uFF08fallback \u5230 '${fallbackId}'\uFF09\u4E0D\u9759\u9ED8\u6267\u884C`,
|
|
220
|
+
`\u4FEE\u590D engine '${routing.engineId}' \u7684\u63A2\u9488\u5931\u8D25\uFF08\u89C1\u4E0A\u65B9\u6062\u590D\u6307\u5F15\uFF09\u540E\u91CD\u8BD5\uFF0C\u6216\u53BB\u6389 model \u6307\u5B9A / \u663E\u5F0F\u4F20 engine: '${fallbackId}' \u786E\u8BA4\u6A21\u578B\u53EF\u7528\u540E\u518D\u6D3E\u53D1`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
engine: get(fallbackId),
|
|
225
|
+
engineId: fallbackId,
|
|
226
|
+
requestedEngineId: routing.engineId,
|
|
227
|
+
source: "default",
|
|
228
|
+
engineFallback: { from: routing.engineId, reason: "engine_probe_failed" }
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function fallbackTargetId(routingInput, resolved) {
|
|
232
|
+
if (resolved.source === "default") return DEFAULT_ENGINE_ID;
|
|
233
|
+
const global = routingInput.globalDefaultEngine;
|
|
234
|
+
if (hasText(global) && global !== DEFAULT_ENGINE_ID && global !== resolved.engineId) {
|
|
235
|
+
return global;
|
|
236
|
+
}
|
|
237
|
+
return DEFAULT_ENGINE_ID;
|
|
238
|
+
}
|
|
239
|
+
function probeFailedError(engineId, report, guard) {
|
|
240
|
+
const checks = report.checks.map((c) => `${c.name}:${c.ok ? "ok" : "FAIL"}`).join(", ");
|
|
241
|
+
return new EngineError(
|
|
242
|
+
"engine_probe_failed",
|
|
243
|
+
`engine '${engineId}' probe \u5931\u8D25\uFF08${guard}\uFF09\u3002checks: [${checks}]`,
|
|
244
|
+
report.error?.recovery ?? `Confirm the engine binary and version, then re-run the probe (probe({force:true}) or re-initialize the engine).`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
function describeRoutingSource(routing) {
|
|
248
|
+
if (hasText(routing.callEngine)) {
|
|
249
|
+
return `call parameter engine='${routing.callEngine}'`;
|
|
250
|
+
}
|
|
251
|
+
if (hasText(routing.agentEngine)) {
|
|
252
|
+
return `agent frontmatter engine='${routing.agentEngine}'`;
|
|
253
|
+
}
|
|
254
|
+
return void 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/shared/timer-delay.ts
|
|
258
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
259
|
+
function assertSafeTimerDelay(ms, source) {
|
|
260
|
+
if (!Number.isFinite(ms)) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`[subagent-workflow] ${source} = ${ms} is not a finite number (NaN/\xB1Infinity). Non-finite delays collapse to 1ms in Node setTimeout and fire immediately. Recovery: fix the upstream computation that produced this value (e.g. guard division/parse results before passing them in) and retry.`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (ms > MAX_TIMER_DELAY_MS) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`[subagent-workflow] ${source} = ${ms} exceeds the Node setTimeout limit (${MAX_TIMER_DELAY_MS} ms = 2^31-1); larger delays silently collapse to 1ms and fire immediately. Recovery: clamp the value to <= ${MAX_TIMER_DELAY_MS} (e.g. omit the option for "unlimited" semantics, or clamp explicitly) and retry.`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/orchestration/args-validator.ts
|
|
273
|
+
var import_ajv = __toESM(require("ajv"), 1);
|
|
274
|
+
var ArgsValidationError = class extends Error {
|
|
275
|
+
workflowName;
|
|
276
|
+
/** ajv 校验错误数组(畸形 schema 时为 undefined)。 */
|
|
277
|
+
errors;
|
|
278
|
+
constructor(workflowName, message, errors) {
|
|
279
|
+
super(message);
|
|
280
|
+
this.name = "ArgsValidationError";
|
|
281
|
+
this.workflowName = workflowName;
|
|
282
|
+
this.errors = errors;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
var ajv = new import_ajv.default({
|
|
286
|
+
coerceTypes: true,
|
|
287
|
+
strictSchema: false,
|
|
288
|
+
allErrors: true,
|
|
289
|
+
useDefaults: false
|
|
290
|
+
});
|
|
291
|
+
function formatMessage(name, errors) {
|
|
292
|
+
const lines = errors.map((e) => {
|
|
293
|
+
let path2 = "/";
|
|
294
|
+
let msg = "invalid";
|
|
295
|
+
if (e !== null && typeof e === "object") {
|
|
296
|
+
const err = e;
|
|
297
|
+
if (typeof err.instancePath === "string" && err.instancePath) path2 = err.instancePath;
|
|
298
|
+
if (typeof err.message === "string") msg = err.message;
|
|
299
|
+
}
|
|
300
|
+
return `- ${path2}: ${msg}`;
|
|
301
|
+
});
|
|
302
|
+
return `Invalid args for workflow '${name}': ${errors.length} error(s)
|
|
303
|
+
${lines.join("\n")}
|
|
304
|
+
Read the workflow script file (location from <available_workflows>) for the parameter schema and usage.`;
|
|
305
|
+
}
|
|
306
|
+
function validateRunArgs(spec) {
|
|
307
|
+
const { parameters, args, scriptName } = spec;
|
|
308
|
+
if (parameters === void 0) return;
|
|
309
|
+
if (parameters === null || typeof parameters !== "object" || Array.isArray(parameters)) {
|
|
310
|
+
throw new ArgsValidationError(
|
|
311
|
+
scriptName,
|
|
312
|
+
`Workflow '${scriptName}' has an invalid parameter schema (expected object). Read the workflow script file (location from <available_workflows>) to inspect it.`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const schema = parameters;
|
|
316
|
+
const properties = schema.properties !== null && typeof schema.properties === "object" ? schema.properties : {};
|
|
317
|
+
for (const key of Object.keys(args)) {
|
|
318
|
+
if (args[key] !== null) continue;
|
|
319
|
+
const prop = properties[key];
|
|
320
|
+
let isNullable = false;
|
|
321
|
+
if (prop !== null && typeof prop === "object") {
|
|
322
|
+
const propType = prop.type;
|
|
323
|
+
isNullable = Array.isArray(propType) ? propType.includes("null") : propType === "null";
|
|
324
|
+
}
|
|
325
|
+
if (!isNullable) delete args[key];
|
|
326
|
+
}
|
|
327
|
+
let validate;
|
|
328
|
+
try {
|
|
329
|
+
validate = ajv.compile(parameters);
|
|
330
|
+
} catch (err) {
|
|
331
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
332
|
+
throw new ArgsValidationError(
|
|
333
|
+
scriptName,
|
|
334
|
+
`Workflow '${scriptName}' has an invalid parameter schema: ${detail}. Read the workflow script file (location from <available_workflows>) to inspect it.`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
if (!validate(args)) {
|
|
338
|
+
throw new ArgsValidationError(
|
|
339
|
+
scriptName,
|
|
340
|
+
formatMessage(scriptName, validate.errors ?? []),
|
|
341
|
+
validate.errors ?? void 0
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/execution/engine/common/kill-chain.ts
|
|
347
|
+
var logger = getLogger("subagents");
|
|
348
|
+
|
|
349
|
+
// src/execution/execute-options-mapper.ts
|
|
350
|
+
var SLUG_MAX_LENGTH = 35;
|
|
351
|
+
|
|
352
|
+
// src/execution/execution-record.ts
|
|
353
|
+
function addUsage(prev, next) {
|
|
354
|
+
if (prev === void 0) {
|
|
355
|
+
return {
|
|
356
|
+
input: next.input ?? 0,
|
|
357
|
+
output: next.output ?? 0,
|
|
358
|
+
cacheRead: next.cacheRead ?? 0,
|
|
359
|
+
cacheWrite: next.cacheWrite ?? 0,
|
|
360
|
+
cost: next.cost
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
input: (prev.input ?? 0) + (next.input ?? 0),
|
|
365
|
+
output: (prev.output ?? 0) + (next.output ?? 0),
|
|
366
|
+
cacheRead: (prev.cacheRead ?? 0) + (next.cacheRead ?? 0),
|
|
367
|
+
cacheWrite: (prev.cacheWrite ?? 0) + (next.cacheWrite ?? 0),
|
|
368
|
+
cost: (prev.cost ?? 0) + (next.cost ?? 0)
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function emptyTurn() {
|
|
372
|
+
return { text: "", thinking: "", toolCalls: [], usageDelta: void 0, closed: false };
|
|
373
|
+
}
|
|
374
|
+
function createRecord(id, identity) {
|
|
375
|
+
return {
|
|
376
|
+
id,
|
|
377
|
+
agent: identity.agent,
|
|
378
|
+
model: identity.model,
|
|
379
|
+
thinkingLevel: identity.thinkingLevel,
|
|
380
|
+
mode: identity.mode,
|
|
381
|
+
task: identity.task,
|
|
382
|
+
slug: identity.slug,
|
|
383
|
+
startedAt: identity.startedAt,
|
|
384
|
+
rootSessionId: identity.rootSessionId,
|
|
385
|
+
parentRecordId: identity.parentRecordId,
|
|
386
|
+
depth: identity.depth ?? 0,
|
|
387
|
+
chatMode: identity.chatMode,
|
|
388
|
+
idleTimeoutMs: identity.idleTimeoutMs,
|
|
389
|
+
engine: identity.engine,
|
|
390
|
+
engineFallback: identity.engineFallback,
|
|
391
|
+
// 状态(实时更新)
|
|
392
|
+
status: "running",
|
|
393
|
+
// turns[] 初始化为 [空 turn]——第一个 turn 从创建即存在,
|
|
394
|
+
// updateFromEvent 直接往 turns[last] 累积,无需「无 turn」分支判断。
|
|
395
|
+
turns: [emptyTurn()],
|
|
396
|
+
turnCount: 0,
|
|
397
|
+
totalTokens: 0,
|
|
398
|
+
lastError: void 0,
|
|
399
|
+
// 对话轮次计数(首轮 = 0,每完成一轮 finalizeRoundToIdle +1)。非 chatMode 不自增。
|
|
400
|
+
round: 0,
|
|
401
|
+
// 完成(completeRecord 唯一写点)
|
|
402
|
+
endedAt: void 0,
|
|
403
|
+
result: void 0,
|
|
404
|
+
error: void 0,
|
|
405
|
+
agentResult: void 0,
|
|
406
|
+
// 控制(仅 background 持有 controller;sync 为 undefined)
|
|
407
|
+
controller: identity.controller
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function currentTurn(record) {
|
|
411
|
+
const last = record.turns[record.turns.length - 1];
|
|
412
|
+
if (last !== void 0 && !last.closed) return last;
|
|
413
|
+
const fresh = emptyTurn();
|
|
414
|
+
record.turns.push(fresh);
|
|
415
|
+
return fresh;
|
|
416
|
+
}
|
|
417
|
+
function findRunningToolCall(record, toolName) {
|
|
418
|
+
for (let t = record.turns.length - 1; t >= 0; t--) {
|
|
419
|
+
const turn = record.turns[t];
|
|
420
|
+
if (turn === void 0) continue;
|
|
421
|
+
for (let i = turn.toolCalls.length - 1; i >= 0; i--) {
|
|
422
|
+
const tc = turn.toolCalls[i];
|
|
423
|
+
if (tc?._status === "running" && tc.toolName === toolName) {
|
|
424
|
+
return [turn, i];
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
var runningToolIndex = /* @__PURE__ */ new WeakMap();
|
|
431
|
+
function indexToolStart(record, turn, toolName) {
|
|
432
|
+
let byName = runningToolIndex.get(record);
|
|
433
|
+
if (byName === void 0) {
|
|
434
|
+
byName = /* @__PURE__ */ new Map();
|
|
435
|
+
runningToolIndex.set(record, byName);
|
|
436
|
+
}
|
|
437
|
+
const arr = byName.get(toolName);
|
|
438
|
+
if (arr === void 0) {
|
|
439
|
+
byName.set(toolName, [{ turn, idx: turn.toolCalls.length - 1 }]);
|
|
440
|
+
} else {
|
|
441
|
+
arr.push({ turn, idx: turn.toolCalls.length - 1 });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function updateFromEvent(record, event) {
|
|
445
|
+
switch (event.type) {
|
|
446
|
+
// ── text / thinking:流式累积进当前 turn ──
|
|
447
|
+
case "text_delta": {
|
|
448
|
+
currentTurn(record).text += event.delta;
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
case "thinking_delta": {
|
|
452
|
+
currentTurn(record).thinking += event.delta;
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
// ── tool_start:push 一个 running 的 InternalToolCall(带 startedTs)──
|
|
456
|
+
case "tool_start": {
|
|
457
|
+
const tc = {
|
|
458
|
+
toolName: event.toolName,
|
|
459
|
+
args: event.args,
|
|
460
|
+
result: void 0,
|
|
461
|
+
isError: false,
|
|
462
|
+
_status: "running",
|
|
463
|
+
startedTs: Date.now()
|
|
464
|
+
};
|
|
465
|
+
const turn = currentTurn(record);
|
|
466
|
+
turn.toolCalls.push(tc);
|
|
467
|
+
indexToolStart(record, turn, event.toolName);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
// ── tool_end:索引弹尾 O(1) 定位 running 同名 toolCall,miss 回退全扫兜底 ──
|
|
471
|
+
case "tool_end": {
|
|
472
|
+
let matched;
|
|
473
|
+
const byName = runningToolIndex.get(record);
|
|
474
|
+
const arr = byName?.get(event.toolName);
|
|
475
|
+
if (arr !== void 0 && arr.length > 0) {
|
|
476
|
+
const item = arr[arr.length - 1];
|
|
477
|
+
arr.pop();
|
|
478
|
+
const tc = item.turn.toolCalls[item.idx];
|
|
479
|
+
if (tc !== void 0 && tc._status === "running") {
|
|
480
|
+
matched = [item.turn, item.idx];
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (matched === void 0) {
|
|
484
|
+
matched = findRunningToolCall(record, event.toolName);
|
|
485
|
+
}
|
|
486
|
+
if (matched !== void 0) {
|
|
487
|
+
const [turn, i] = matched;
|
|
488
|
+
const tc = turn.toolCalls[i];
|
|
489
|
+
tc.args = event.args ?? tc.args;
|
|
490
|
+
tc.result = event.result;
|
|
491
|
+
tc.isError = event.isError ?? false;
|
|
492
|
+
tc._status = event.isError ? "failed" : "done";
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
currentTurn(record).toolCalls.push({
|
|
496
|
+
toolName: event.toolName,
|
|
497
|
+
args: event.args,
|
|
498
|
+
result: event.result,
|
|
499
|
+
isError: event.isError ?? false,
|
|
500
|
+
_status: event.isError ? "failed" : "done",
|
|
501
|
+
startedTs: Date.now()
|
|
502
|
+
});
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
// ── turn_end:闭合当前 turn,记 closedTs,turnCount++,清 lastError ──
|
|
506
|
+
case "turn_end": {
|
|
507
|
+
const turn = currentTurn(record);
|
|
508
|
+
turn.closed = true;
|
|
509
|
+
turn.closedTs = Date.now();
|
|
510
|
+
record.turnCount += 1;
|
|
511
|
+
record.lastError = void 0;
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
// ── message_end:usage 增量累加进 currentTurn().usageDelta,totalTokens 累加 ──
|
|
515
|
+
//
|
|
516
|
+
// usageDelta 按 message_end **累加**(非覆盖)——同一 turn 内若多次 message_end
|
|
517
|
+
// 到达(或 turn_end 后的滞后 message_end 落到 currentTurn 开的新 turn),
|
|
518
|
+
// 累加保证不丢 usage。getTotalUsage 扁平求和所有 turn,归属 turn 的精确性
|
|
519
|
+
// 不影响最终 total(无消费方读单 turn usage)。
|
|
520
|
+
case "message_end": {
|
|
521
|
+
if (event.usage) {
|
|
522
|
+
const turn = currentTurn(record);
|
|
523
|
+
turn.usageDelta = addUsage(turn.usageDelta, event.usage);
|
|
524
|
+
record.totalTokens += (event.usage.input ?? 0) + (event.usage.output ?? 0) + (event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
|
|
525
|
+
}
|
|
526
|
+
if (event.error) {
|
|
527
|
+
record.lastError = event.error;
|
|
528
|
+
}
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
// ── error:存 record.lastError(getEventLog 派生 error 条目)──
|
|
532
|
+
case "error": {
|
|
533
|
+
record.lastError = event.message;
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
// ── compaction:不产生数据(不变)──
|
|
537
|
+
case "compaction": {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
default: {
|
|
541
|
+
const _exhaustive = event;
|
|
542
|
+
return _exhaustive;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/execution/stream-sink.ts
|
|
548
|
+
var STREAM_FLUSH_MS = 100;
|
|
549
|
+
var MAX_WIDGET_LINES = 200;
|
|
550
|
+
var SubagentStream = class {
|
|
551
|
+
widgetKey;
|
|
552
|
+
sink;
|
|
553
|
+
buffer = "";
|
|
554
|
+
timer;
|
|
555
|
+
hasFlushed = false;
|
|
556
|
+
disposed = false;
|
|
557
|
+
constructor(recordId, sink) {
|
|
558
|
+
this.widgetKey = `subagent-stream-${recordId}`;
|
|
559
|
+
this.sink = sink;
|
|
560
|
+
}
|
|
561
|
+
/** 接收一个 text_delta 增量。空串静默丢弃(不消耗 leading edge)。 */
|
|
562
|
+
onDelta(delta) {
|
|
563
|
+
if (this.disposed || delta.length === 0) return;
|
|
564
|
+
this.buffer += delta;
|
|
565
|
+
if (!this.hasFlushed) {
|
|
566
|
+
this.hasFlushed = true;
|
|
567
|
+
this.flush();
|
|
568
|
+
} else if (this.timer === void 0) {
|
|
569
|
+
this.timer = setTimeout(() => this.flush(), STREAM_FLUSH_MS);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/** 终态清理:清除 widget + 清 timer(幂等)。 */
|
|
573
|
+
dispose() {
|
|
574
|
+
if (this.disposed) return;
|
|
575
|
+
this.disposed = true;
|
|
576
|
+
if (this.timer !== void 0) {
|
|
577
|
+
clearTimeout(this.timer);
|
|
578
|
+
this.timer = void 0;
|
|
579
|
+
}
|
|
580
|
+
this.sink.setWidget(this.widgetKey, void 0);
|
|
581
|
+
}
|
|
582
|
+
flush() {
|
|
583
|
+
this.timer = void 0;
|
|
584
|
+
if (this.buffer.length === 0 || this.disposed) return;
|
|
585
|
+
const lines = this.buffer.split("\n");
|
|
586
|
+
if (lines.length <= MAX_WIDGET_LINES) {
|
|
587
|
+
this.sink.setWidget(this.widgetKey, lines);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const dropped = lines.length - MAX_WIDGET_LINES;
|
|
591
|
+
this.sink.setWidget(this.widgetKey, [
|
|
592
|
+
`(... ${dropped} earlier lines truncated, full output in /subagents detail)`,
|
|
593
|
+
...lines.slice(dropped)
|
|
594
|
+
]);
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// src/orchestration/skill-discovery.ts
|
|
599
|
+
var fs = __toESM(require("fs"), 1);
|
|
600
|
+
var path = __toESM(require("path"), 1);
|
|
601
|
+
var skillCandidatesCache = /* @__PURE__ */ new Map();
|
|
602
|
+
function getNpmSkillCandidates(npmSkillsDir) {
|
|
603
|
+
const cached = skillCandidatesCache.get(npmSkillsDir);
|
|
604
|
+
if (cached) return cached;
|
|
605
|
+
const candidates = [];
|
|
606
|
+
try {
|
|
607
|
+
for (const pkg of fs.readdirSync(npmSkillsDir)) {
|
|
608
|
+
candidates.push(path.join(npmSkillsDir, pkg, "skills"));
|
|
609
|
+
}
|
|
610
|
+
} catch {
|
|
611
|
+
}
|
|
612
|
+
skillCandidatesCache.set(npmSkillsDir, candidates);
|
|
613
|
+
return candidates;
|
|
614
|
+
}
|
|
615
|
+
var skillMemo = /* @__PURE__ */ new Map();
|
|
616
|
+
function resolveWithinRoot(rootDir, skillName) {
|
|
617
|
+
const root = path.resolve(rootDir);
|
|
618
|
+
const resolved = path.resolve(root, skillName);
|
|
619
|
+
if (resolved !== root && !resolved.startsWith(root + path.sep)) {
|
|
620
|
+
return void 0;
|
|
621
|
+
}
|
|
622
|
+
return resolved;
|
|
623
|
+
}
|
|
624
|
+
function resolveSkillPath(skillName) {
|
|
625
|
+
if (skillMemo.has(skillName)) {
|
|
626
|
+
return skillMemo.get(skillName);
|
|
627
|
+
}
|
|
628
|
+
const candidates = [];
|
|
629
|
+
const pushCandidate = (dir) => {
|
|
630
|
+
if (dir !== void 0) candidates.push(dir);
|
|
631
|
+
};
|
|
632
|
+
pushCandidate(resolveWithinRoot(path.resolve(process.cwd(), ".agents/skills"), skillName));
|
|
633
|
+
const injectedSkillRoots = getHostServices().discoveryRoots?.()?.skills ?? [];
|
|
634
|
+
const userSkillsRoot = injectedSkillRoots.find((root) => root.source === "user-pi");
|
|
635
|
+
if (userSkillsRoot !== void 0) {
|
|
636
|
+
pushCandidate(resolveWithinRoot(userSkillsRoot.dir, skillName));
|
|
637
|
+
}
|
|
638
|
+
const npmSkillsRoot = injectedSkillRoots.find((root) => root.source === "npm");
|
|
639
|
+
if (npmSkillsRoot !== void 0) {
|
|
640
|
+
for (const pkgSkillsBase of getNpmSkillCandidates(npmSkillsRoot.dir)) {
|
|
641
|
+
pushCandidate(resolveWithinRoot(pkgSkillsBase, skillName));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
for (const dir of candidates) {
|
|
645
|
+
if (fs.existsSync(dir)) {
|
|
646
|
+
skillMemo.set(skillName, dir);
|
|
647
|
+
return dir;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
skillMemo.set(skillName, void 0);
|
|
651
|
+
return void 0;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// src/shared/schema-jsonify.ts
|
|
655
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
656
|
+
var PRETTY_PRINT_INDENT_SPACES = 2;
|
|
657
|
+
function stringifySchemaCached(schema, mode) {
|
|
658
|
+
let entry = cache.get(schema);
|
|
659
|
+
if (!entry) {
|
|
660
|
+
entry = {};
|
|
661
|
+
cache.set(schema, entry);
|
|
662
|
+
}
|
|
663
|
+
const hit = entry[mode];
|
|
664
|
+
if (hit !== void 0) return hit;
|
|
665
|
+
const serialized = mode === "compact" ? JSON.stringify(schema) : JSON.stringify(schema, null, PRETTY_PRINT_INDENT_SPACES);
|
|
666
|
+
if (serialized === void 0) {
|
|
667
|
+
throw new Error(
|
|
668
|
+
`[subagent-workflow] stringifySchemaCached: JSON.stringify returned undefined (mode=${mode}) \u2014 the schema object defines a toJSON hook returning undefined. Recovery: check the schema source (agent definition / workflow script), remove that toJSON or make it return a JSON-serializable value.`
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
entry[mode] = serialized;
|
|
672
|
+
return serialized;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// src/orchestration/agent-opts-resolver.ts
|
|
676
|
+
function isPlainObject(value) {
|
|
677
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
678
|
+
}
|
|
679
|
+
function isObjectRootSchema(schema) {
|
|
680
|
+
if (!isPlainObject(schema)) return false;
|
|
681
|
+
if (schema.type === "object") return true;
|
|
682
|
+
if (Array.isArray(schema.type) && schema.type.includes("object")) return true;
|
|
683
|
+
const OBJECT_ONLY_KEYS = [
|
|
684
|
+
"properties",
|
|
685
|
+
"required",
|
|
686
|
+
"patternProperties",
|
|
687
|
+
"additionalProperties",
|
|
688
|
+
"minProperties",
|
|
689
|
+
"maxProperties",
|
|
690
|
+
"dependencies",
|
|
691
|
+
"dependentRequired",
|
|
692
|
+
"propertyNames"
|
|
693
|
+
];
|
|
694
|
+
return OBJECT_ONLY_KEYS.some((k) => k in schema);
|
|
695
|
+
}
|
|
696
|
+
function formatSchemaInstruction(schema) {
|
|
697
|
+
const schemaJson = stringifySchemaCached(schema, "compact");
|
|
698
|
+
const isObjectRoot = isObjectRootSchema(schema);
|
|
699
|
+
const argsContractLine = isObjectRoot ? "Your call arguments ARE the result data itself \u2014 the tool's parameter schema IS the required shape of your result." : "The tool's single argument must be an object `{value: <data>}` \u2014 put the result itself in `value`, and it must conform to the schema below. Validation errors may reference paths starting with `value.` (e.g. `value.0`, `value.name`): that prefix addresses the wrapper, not your data.";
|
|
700
|
+
const rulesCallLine = isObjectRoot ? "- Call the structured-output tool with your result data as its arguments. The system validates them against the schema above automatically." : "- Call the structured-output tool with `{value: <your result data>}`. The system validates the `value` field against the schema above automatically.";
|
|
701
|
+
const apLine = schema.additionalProperties === void 0 ? "- Fields not defined in this schema are rejected \u2014 do not add extra fields." : "- Extra fields follow this schema's own additionalProperties declaration.";
|
|
702
|
+
return [
|
|
703
|
+
"## MANDATORY: Structured Output Requirement",
|
|
704
|
+
"",
|
|
705
|
+
"This task requires structured output.",
|
|
706
|
+
"Your FINAL action must be calling the `structured-output` tool.",
|
|
707
|
+
"",
|
|
708
|
+
argsContractLine,
|
|
709
|
+
`Your result must conform to this schema:`,
|
|
710
|
+
"```json",
|
|
711
|
+
schemaJson,
|
|
712
|
+
"```",
|
|
713
|
+
"",
|
|
714
|
+
"Rules:",
|
|
715
|
+
rulesCallLine,
|
|
716
|
+
"- Do NOT output JSON in your text response \u2014 use the structured-output tool.",
|
|
717
|
+
"- Do NOT skip this step. The structured-output call IS your result.",
|
|
718
|
+
"- Complete all other work FIRST, then call structured-output as the last action.",
|
|
719
|
+
apLine
|
|
720
|
+
].join("\n");
|
|
721
|
+
}
|
|
722
|
+
function resolveAgentOpts(opts) {
|
|
723
|
+
const appendSystemPrompt = [];
|
|
724
|
+
if (opts.skill) {
|
|
725
|
+
const skillPath = resolveSkillPath(opts.skill);
|
|
726
|
+
if (!skillPath) {
|
|
727
|
+
return { opts, error: `Skill not found: ${opts.skill}. Searched .agents/skills/ and ~/.pi/agent/skills/` };
|
|
728
|
+
}
|
|
729
|
+
opts = { ...opts, skillPath };
|
|
730
|
+
}
|
|
731
|
+
if (opts.schema) {
|
|
732
|
+
appendSystemPrompt.push(formatSchemaInstruction(opts.schema));
|
|
733
|
+
opts = { ...opts, schemaEnv: stringifySchemaCached(opts.schema, "compact") };
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
opts: { ...opts, ...appendSystemPrompt.length > 0 ? { appendSystemPrompt } : {} }
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// src/orchestration/execute-agent-call.ts
|
|
741
|
+
var BACKOFF_BASE_MS = 1e3;
|
|
742
|
+
var BACKOFF_EXPONENT_BASE = 2;
|
|
743
|
+
var MAX_ATTEMPTS = 3;
|
|
744
|
+
var STALE_CONTEXT_PATTERNS = [
|
|
745
|
+
"ctx is stale",
|
|
746
|
+
"stale after session replacement",
|
|
747
|
+
"context canceled",
|
|
748
|
+
"aborted"
|
|
749
|
+
];
|
|
750
|
+
function isStaleContextErrorMsg(msg) {
|
|
751
|
+
if (!msg) return false;
|
|
752
|
+
const lower = msg.toLowerCase();
|
|
753
|
+
return STALE_CONTEXT_PATTERNS.some((p) => lower.includes(p));
|
|
754
|
+
}
|
|
755
|
+
var DETERMINISTIC_SCHEMA_FAILURE_PREFIX = "Structured output failed deterministically:";
|
|
756
|
+
function isDeterministicSchemaFailureMsg(msg) {
|
|
757
|
+
if (!msg) return false;
|
|
758
|
+
return msg.includes(DETERMINISTIC_SCHEMA_FAILURE_PREFIX);
|
|
759
|
+
}
|
|
760
|
+
function backoffDelay(retryIndex) {
|
|
761
|
+
return BACKOFF_BASE_MS * Math.pow(BACKOFF_EXPONENT_BASE, retryIndex - 1);
|
|
762
|
+
}
|
|
763
|
+
function finalizeCall(call, result, trace, isOrphaned) {
|
|
764
|
+
call.markDone(result);
|
|
765
|
+
const status = result.error === void 0 ? "completed" : "failed";
|
|
766
|
+
if (result.sessionId !== void 0) call.setSessionId(result.sessionId);
|
|
767
|
+
if (result.sessionFile !== void 0) call.setSessionFile(result.sessionFile);
|
|
768
|
+
if (isOrphaned?.()) return;
|
|
769
|
+
trace.update(call.id, {
|
|
770
|
+
status,
|
|
771
|
+
result,
|
|
772
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
773
|
+
sessionId: result.sessionId,
|
|
774
|
+
sessionFile: result.sessionFile
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
function delay(ms) {
|
|
778
|
+
return new Promise((resolve2) => {
|
|
779
|
+
const timer = setTimeout(resolve2, ms);
|
|
780
|
+
timer.unref();
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
async function executeAgentCall(call, runner, budget, signal, trace, onEvent, stream, isOrphaned) {
|
|
784
|
+
call.markRunning();
|
|
785
|
+
const result = await runner.run(call.opts, signal, onEvent, stream);
|
|
786
|
+
if (result.usage) {
|
|
787
|
+
budget.consume(result.usage);
|
|
788
|
+
}
|
|
789
|
+
if (result.error !== void 0 && isStaleContextErrorMsg(result.error)) {
|
|
790
|
+
finalizeCall(call, result, trace, isOrphaned);
|
|
791
|
+
budget.incrementCallCount();
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (result.error !== void 0 && isDeterministicSchemaFailureMsg(result.error)) {
|
|
795
|
+
finalizeCall(call, result, trace, isOrphaned);
|
|
796
|
+
budget.incrementCallCount();
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (signal.aborted) {
|
|
800
|
+
finalizeCall(call, result, trace, isOrphaned);
|
|
801
|
+
budget.incrementCallCount();
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (result.error !== void 0 && budget.isExceeded()) {
|
|
805
|
+
finalizeCall(call, result, trace, isOrphaned);
|
|
806
|
+
budget.incrementCallCount();
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
if (result.error !== void 0 && call.attempts < MAX_ATTEMPTS) {
|
|
810
|
+
await delay(backoffDelay(call.attempts));
|
|
811
|
+
if (signal.aborted) {
|
|
812
|
+
finalizeCall(call, result, trace, isOrphaned);
|
|
813
|
+
budget.incrementCallCount();
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
await executeAgentCall(call, runner, budget, signal, trace, onEvent, stream, isOrphaned);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
finalizeCall(call, result, trace, isOrphaned);
|
|
820
|
+
budget.incrementCallCount();
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/orchestration/models/agent-call.ts
|
|
824
|
+
var AgentCall = class {
|
|
825
|
+
id;
|
|
826
|
+
opts;
|
|
827
|
+
status = "pending";
|
|
828
|
+
attempts = 0;
|
|
829
|
+
result;
|
|
830
|
+
/** Pi subprocess session ID(uuidv7,G-017 归此)。 */
|
|
831
|
+
sessionId;
|
|
832
|
+
/** Session JSONL 绝对路径(finalizeCall 后从 result.sessionFile 填入,对齐 sessionId 模式)。 */
|
|
833
|
+
sessionFile;
|
|
834
|
+
/** 与 Trace 共享的节点引用(D-10 单源)。AgentCall 不直接改其字段。 */
|
|
835
|
+
traceNode;
|
|
836
|
+
constructor(id, opts, traceNode) {
|
|
837
|
+
this.id = id;
|
|
838
|
+
this.opts = opts;
|
|
839
|
+
this.traceNode = traceNode;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* 标记进入 running 状态(dispatch 前)。attempts++(含首次)。
|
|
843
|
+
* @throws 若已 done(不可重启)
|
|
844
|
+
*/
|
|
845
|
+
markRunning() {
|
|
846
|
+
if (this.status === "done") {
|
|
847
|
+
throw new Error(`AgentCall ${this.id} already done \u2014 cannot mark running`);
|
|
848
|
+
}
|
|
849
|
+
this.status = "running";
|
|
850
|
+
this.attempts += 1;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* 标记完成(成功或失败均调用——result.error 区分)。
|
|
854
|
+
* @throws 若当前非 running(pending 不能直接跳 done,必须先 markRunning)
|
|
855
|
+
*/
|
|
856
|
+
markDone(result) {
|
|
857
|
+
if (this.status !== "running") {
|
|
858
|
+
throw new Error(`AgentCall ${this.id} must be running to mark done (was ${this.status})`);
|
|
859
|
+
}
|
|
860
|
+
this.result = result;
|
|
861
|
+
this.status = "done";
|
|
862
|
+
}
|
|
863
|
+
/** 记录 pi subprocess session ID(dispatch 成功后)。 */
|
|
864
|
+
setSessionId(sessionId) {
|
|
865
|
+
this.sessionId = sessionId;
|
|
866
|
+
}
|
|
867
|
+
/** 记录 session JSONL 绝对路径(finalizeCall 后,对齐 setSessionId 模式)。 */
|
|
868
|
+
setSessionFile(sessionFile) {
|
|
869
|
+
this.sessionFile = sessionFile;
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
// src/orchestration/models/run-runtime.ts
|
|
874
|
+
var RunRuntime = class {
|
|
875
|
+
/** Worker 线程句柄。 */
|
|
876
|
+
worker;
|
|
877
|
+
/** per-running-segment AbortController(一次性,无法复用——G3-001)。 */
|
|
878
|
+
controller;
|
|
879
|
+
/** Run 级墙钟时间预算计时器(spec.budgetTimeMs > 0 时由 lifecycle 调度,
|
|
880
|
+
* 到期 abortRun time_limited)。release 时清理,避免 abort/replaceRuntime
|
|
881
|
+
* 后孤儿计时器仍触发(rebuildRuntime 会重排一个全新的计时器,旧的不应残留)。 */
|
|
882
|
+
timeBudgetTimer;
|
|
883
|
+
/**
|
|
884
|
+
* 本 runtime 代际是否已收到 worker 的终态消息(return / error)。
|
|
885
|
+
*
|
|
886
|
+
* [F1] worker exit(0) 且本标记为 false = worker 静默退出、未交付任何终态——最常见根因
|
|
887
|
+
* 是 execute() 返回值不可克隆,worker 侧 _safePost 吞掉 DataCloneError 后 return 消息
|
|
888
|
+
* 根本没发出。旧实现 handleWorkerExit 对 code===0 no-op → run 永久 running、runAndWait
|
|
889
|
+
* 悬挂。handleWorkerExit 据此判定转 done,failed。
|
|
890
|
+
*
|
|
891
|
+
* 按代际归零:字段挂在 RunRuntime(每代际 new 一个实例)而非 run.meta——script-error
|
|
892
|
+
* 重试退避窗口内(error 消息已收到、run 仍 running、旧 worker exit(0))必须 no-op 等
|
|
893
|
+
* rebuild;若挂 meta 则 rebuild 后新 worker 再静默退出时会被旧标记误放行,重新悬挂。
|
|
894
|
+
*
|
|
895
|
+
* 写点:① handleWorkerMessage 的 return/error 分支(WorkerHandle.isCurrent 守卫保证
|
|
896
|
+
* 消息必来自当前代际);② handleWorkerError 进入处理前([R4-F1] 同代际幂等守卫——
|
|
897
|
+
* worker 崩溃时 error + exit(1) 双事件各派发一次 handleWorkerError,第一个事件标记
|
|
898
|
+
* 本代际已处理,第二个事件命中标志跳过,消除单次崩溃计数 +2 / 双 rebuild 交错)。
|
|
899
|
+
* rebuildRuntime 构造新 RunRuntime 自然重置。
|
|
900
|
+
*/
|
|
901
|
+
receivedTerminalMessage = false;
|
|
902
|
+
/** 防止 release 重复执行(幂等)。 */
|
|
903
|
+
released = false;
|
|
904
|
+
constructor(worker, controller, timeBudgetTimer) {
|
|
905
|
+
this.worker = worker;
|
|
906
|
+
this.controller = controller;
|
|
907
|
+
this.timeBudgetTimer = timeBudgetTimer;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* 释放所有资源:terminate worker + abort controller。
|
|
911
|
+
*
|
|
912
|
+
* 幂等——重复调用安全(第二次起 no-op,released flag 守卫)。
|
|
913
|
+
* 调用后此 RunRuntime 应被调用方丢弃(WorkflowRun.runtime = undefined),
|
|
914
|
+
* 崩溃重试时由 replaceRuntime 注入新实例(G3-001)。
|
|
915
|
+
*
|
|
916
|
+
* worker.terminate 本身幂等,controller.abort 本身幂等
|
|
917
|
+
* (重复 abort 无副作用),但 released flag 让本方法语义更明确:
|
|
918
|
+
* 「释放过一次的 runtime 不再释放第二次」。
|
|
919
|
+
*
|
|
920
|
+
* @param mode terminal —— 终局释放(唯一值,保留参数为调用方语义显式化)
|
|
921
|
+
*/
|
|
922
|
+
release(_mode) {
|
|
923
|
+
if (this.released) return;
|
|
924
|
+
this.released = true;
|
|
925
|
+
if (this.timeBudgetTimer) clearTimeout(this.timeBudgetTimer);
|
|
926
|
+
void this.worker.terminate();
|
|
927
|
+
this.controller.abort();
|
|
928
|
+
}
|
|
929
|
+
/** 是否已 release(测试 + 诊断用)。 */
|
|
930
|
+
get isReleased() {
|
|
931
|
+
return this.released;
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
// src/orchestration/error-recovery.ts
|
|
936
|
+
var logger2 = getLogger("subagents");
|
|
937
|
+
var MAX_WORKER_RETRIES = 3;
|
|
938
|
+
var RETRY_BACKOFF_BASE_MS = 1e3;
|
|
939
|
+
var EXPONENTIAL_BACKOFF_BASE = 2;
|
|
940
|
+
var MAX_ERROR_LOGS = 500;
|
|
941
|
+
var MALFORMED_MSG_LOG_PREVIEW_CHARS = 200;
|
|
942
|
+
var WORKER_EXITED_WITHOUT_RESULT_MSG = "worker exited before delivering a result (return value may not be structured-cloneable)";
|
|
943
|
+
function isTerminal(run) {
|
|
944
|
+
return run.state.status === "done";
|
|
945
|
+
}
|
|
946
|
+
function isOrphanedCall(run, callId, call) {
|
|
947
|
+
return run.state.calls.get(callId) !== call;
|
|
948
|
+
}
|
|
949
|
+
function backoffDelay2(retryIndex) {
|
|
950
|
+
return RETRY_BACKOFF_BASE_MS * Math.pow(EXPONENTIAL_BACKOFF_BASE, retryIndex - 1);
|
|
951
|
+
}
|
|
952
|
+
async function saveRunBestEffort(run, deps, context) {
|
|
953
|
+
try {
|
|
954
|
+
await deps.store.save(run);
|
|
955
|
+
} catch (err) {
|
|
956
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
957
|
+
logger2.error(
|
|
958
|
+
`[workflow] store.save failed (${context}, runId=${run.runId}): ${m}. Continuing state-machine finalization (in-memory state already terminal).`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
function delay2(ms) {
|
|
963
|
+
return new Promise((resolve2) => {
|
|
964
|
+
const timer = setTimeout(resolve2, ms);
|
|
965
|
+
timer.unref();
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
function discardInFlightCalls(run) {
|
|
969
|
+
const inFlight = [];
|
|
970
|
+
for (const [callId, call] of run.state.calls) {
|
|
971
|
+
if (call.status !== "done") inFlight.push(callId);
|
|
972
|
+
}
|
|
973
|
+
for (const callId of inFlight) {
|
|
974
|
+
run.state.calls.delete(callId);
|
|
975
|
+
run.state.trace.removeByStepIndex(callId);
|
|
976
|
+
}
|
|
977
|
+
return inFlight.sort((a, b) => a - b);
|
|
978
|
+
}
|
|
979
|
+
function remainingTimeBudgetMs(run) {
|
|
980
|
+
const budget = run.spec.budgetTimeMs;
|
|
981
|
+
if (!budget || budget <= 0) return void 0;
|
|
982
|
+
const startedMs = Date.parse(run.meta.startedAt);
|
|
983
|
+
const elapsed = Number.isFinite(startedMs) ? Math.max(0, Date.now() - startedMs) : 0;
|
|
984
|
+
return Math.max(0, budget - elapsed);
|
|
985
|
+
}
|
|
986
|
+
async function finalizeTimeBudgetExhausted(run, deps) {
|
|
987
|
+
deps.log?.("debug", "workflow:error-recovery", "time budget exhausted on rebuild, transition done", {
|
|
988
|
+
runId: run.runId,
|
|
989
|
+
budgetTimeMs: run.spec.budgetTimeMs
|
|
990
|
+
});
|
|
991
|
+
run.state.error = run.state.error ?? `Time budget exhausted (${run.spec.budgetTimeMs} ms wall clock) before retry rebuild`;
|
|
992
|
+
let transitioned = false;
|
|
993
|
+
try {
|
|
994
|
+
run.transition("done", "time_limited");
|
|
995
|
+
transitioned = true;
|
|
996
|
+
} catch (te) {
|
|
997
|
+
void te;
|
|
998
|
+
}
|
|
999
|
+
if (!transitioned) return;
|
|
1000
|
+
await deps.store.save(run).catch((e) => {
|
|
1001
|
+
const m = e instanceof Error ? e.message : String(e);
|
|
1002
|
+
logger2.error(`[workflow] store.save failed (time budget exhausted): ${m}`);
|
|
1003
|
+
});
|
|
1004
|
+
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "time_limited" });
|
|
1005
|
+
deps.onRunDone?.(run);
|
|
1006
|
+
}
|
|
1007
|
+
function rebuildRuntime(run, deps, handlers) {
|
|
1008
|
+
deps.log?.("debug", "workflow:error-recovery", "runtime rebuild start", {
|
|
1009
|
+
runId: run.runId,
|
|
1010
|
+
budgetTimeMs: run.spec.budgetTimeMs
|
|
1011
|
+
});
|
|
1012
|
+
const controller = new AbortController();
|
|
1013
|
+
const worker = deps.workerHost.start(run.spec, run.spec.args, handlers);
|
|
1014
|
+
let timeBudgetTimer;
|
|
1015
|
+
const remainingBudgetMs = remainingTimeBudgetMs(run);
|
|
1016
|
+
if (remainingBudgetMs !== void 0 && remainingBudgetMs > 0 && deps.scheduleTimeBudget) {
|
|
1017
|
+
timeBudgetTimer = deps.scheduleTimeBudget(run.runId, remainingBudgetMs);
|
|
1018
|
+
deps.log?.("debug", "workflow:error-recovery", "time budget rescheduled", {
|
|
1019
|
+
runId: run.runId,
|
|
1020
|
+
budgetTimeMs: remainingBudgetMs
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
run.replaceRuntime(new RunRuntime(worker, controller, timeBudgetTimer));
|
|
1024
|
+
const discardedCallIds = discardInFlightCalls(run);
|
|
1025
|
+
deps.log?.("debug", "workflow:error-recovery", "in-flight calls discarded", {
|
|
1026
|
+
runId: run.runId,
|
|
1027
|
+
callIds: discardedCallIds,
|
|
1028
|
+
count: discardedCallIds.length
|
|
1029
|
+
});
|
|
1030
|
+
deps.log?.("debug", "workflow:error-recovery", "runtime rebuild complete", {
|
|
1031
|
+
runId: run.runId
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
async function handleWorkerMessage(run, raw, deps, handlers) {
|
|
1035
|
+
if (isTerminal(run)) return;
|
|
1036
|
+
if (typeof raw !== "object" || raw === null) return;
|
|
1037
|
+
const msg = raw;
|
|
1038
|
+
switch (msg.type) {
|
|
1039
|
+
case "agent-call":
|
|
1040
|
+
dispatchAgentCall(run, msg, deps);
|
|
1041
|
+
return;
|
|
1042
|
+
case "workflow-call":
|
|
1043
|
+
dispatchWorkflowCall(run, msg, deps);
|
|
1044
|
+
return;
|
|
1045
|
+
case "return":
|
|
1046
|
+
if (run.runtime) run.runtime.receivedTerminalMessage = true;
|
|
1047
|
+
await handleReturn(run, msg, deps);
|
|
1048
|
+
return;
|
|
1049
|
+
case "error":
|
|
1050
|
+
if (run.runtime) run.runtime.receivedTerminalMessage = true;
|
|
1051
|
+
await handleScriptError(
|
|
1052
|
+
run,
|
|
1053
|
+
msg.error,
|
|
1054
|
+
msg.workerLogs ?? [],
|
|
1055
|
+
deps,
|
|
1056
|
+
handlers
|
|
1057
|
+
);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
function dispatchAgentCall(run, msg, deps) {
|
|
1062
|
+
if (typeof msg.callId !== "number" || !Number.isFinite(msg.callId) || typeof msg.opts !== "object" || msg.opts === null || typeof msg.opts.prompt !== "string") {
|
|
1063
|
+
logger2.error(`[workflow] malformed agent-call message: callId=${JSON.stringify(msg.callId)}, opts=${JSON.stringify(msg.opts)?.slice(0, MALFORMED_MSG_LOG_PREVIEW_CHARS)}`);
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
const cached = run.state.calls.get(msg.callId);
|
|
1067
|
+
if (cached && cached.status === "done") {
|
|
1068
|
+
postAgentResult(run, msg.callId, cached.result, true);
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
const agentName = msg.opts.description ?? msg.opts.agent ?? "unknown";
|
|
1072
|
+
const liveSlug = agentName.length > SLUG_MAX_LENGTH ? agentName.slice(0, SLUG_MAX_LENGTH) : agentName;
|
|
1073
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1074
|
+
const liveRecord = createRecord(String(msg.callId), {
|
|
1075
|
+
agent: agentName,
|
|
1076
|
+
model: msg.opts.model ?? "default",
|
|
1077
|
+
mode: "background",
|
|
1078
|
+
task: msg.opts.prompt,
|
|
1079
|
+
slug: liveSlug,
|
|
1080
|
+
startedAt: Date.now()
|
|
1081
|
+
});
|
|
1082
|
+
const node = {
|
|
1083
|
+
stepIndex: msg.callId,
|
|
1084
|
+
agent: agentName,
|
|
1085
|
+
task: msg.opts.prompt,
|
|
1086
|
+
model: msg.opts.model ?? "default",
|
|
1087
|
+
status: "running",
|
|
1088
|
+
phase: msg.phase,
|
|
1089
|
+
startedAt: now,
|
|
1090
|
+
live: liveRecord
|
|
1091
|
+
};
|
|
1092
|
+
run.state.trace.append(node);
|
|
1093
|
+
const rawSchema = msg.opts.schema;
|
|
1094
|
+
const opts = {
|
|
1095
|
+
...msg.opts,
|
|
1096
|
+
schema: typeof rawSchema === "object" && rawSchema !== null ? rawSchema : void 0
|
|
1097
|
+
};
|
|
1098
|
+
const resolved = resolveAgentOpts(opts);
|
|
1099
|
+
if (resolved.error) {
|
|
1100
|
+
const call2 = new AgentCall(msg.callId, opts, node);
|
|
1101
|
+
call2.markRunning();
|
|
1102
|
+
const errorResult = { content: "", error: resolved.error };
|
|
1103
|
+
call2.markDone(errorResult);
|
|
1104
|
+
run.state.calls.set(msg.callId, call2);
|
|
1105
|
+
node.live = void 0;
|
|
1106
|
+
run.state.trace.update(msg.callId, {
|
|
1107
|
+
status: "failed",
|
|
1108
|
+
result: errorResult,
|
|
1109
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1110
|
+
});
|
|
1111
|
+
postAgentResult(run, msg.callId, errorResult, false);
|
|
1112
|
+
deps.store.save(run).catch((e) => {
|
|
1113
|
+
logger2.error(`[workflow] store.save failed (resolveAgentOpts): ${e instanceof Error ? e.message : String(e)}`);
|
|
1114
|
+
});
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
const call = new AgentCall(msg.callId, resolved.opts, node);
|
|
1118
|
+
run.state.calls.set(msg.callId, call);
|
|
1119
|
+
const runtime = run.runtime;
|
|
1120
|
+
const signal = runtime.controller.signal;
|
|
1121
|
+
const onEvent = (event) => {
|
|
1122
|
+
updateFromEvent(liveRecord, event);
|
|
1123
|
+
};
|
|
1124
|
+
const stream = deps.streamSink ? new SubagentStream(`${run.runId}-${msg.callId}`, deps.streamSink) : void 0;
|
|
1125
|
+
const dispatchCall = async () => {
|
|
1126
|
+
if (signal.aborted) {
|
|
1127
|
+
const abortErr = new Error("Operation aborted before start");
|
|
1128
|
+
abortErr.name = "AbortError";
|
|
1129
|
+
throw abortErr;
|
|
1130
|
+
}
|
|
1131
|
+
try {
|
|
1132
|
+
await executeAgentCall(call, deps.runner, run.state.budget, signal, run.state.trace, onEvent, stream, () => isOrphanedCall(run, msg.callId, call));
|
|
1133
|
+
} finally {
|
|
1134
|
+
stream?.dispose();
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
void dispatchCall().then(() => {
|
|
1138
|
+
node.live = void 0;
|
|
1139
|
+
if (run.state.status !== "running") return;
|
|
1140
|
+
if (isOrphanedCall(run, msg.callId, call)) {
|
|
1141
|
+
deps.log?.("debug", "workflow:error-recovery", "orphan agent call completion dropped", { runId: run.runId, callId: msg.callId });
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
if (call.result) postAgentResult(run, msg.callId, call.result, false);
|
|
1145
|
+
postBudgetUpdate(run);
|
|
1146
|
+
deps.store.save(run).catch((e) => {
|
|
1147
|
+
const m = e instanceof Error ? e.message : String(e);
|
|
1148
|
+
logger2.error(`[workflow] store.save failed (agent call ${msg.callId}): ${m}`);
|
|
1149
|
+
});
|
|
1150
|
+
if (run.state.budget.isExceeded()) {
|
|
1151
|
+
run.state.error = run.state.error ?? "Budget exceeded";
|
|
1152
|
+
deps.log?.("debug", "workflow:error-recovery", "budget exceeded, transition done", { runId: run.runId });
|
|
1153
|
+
let transitioned = false;
|
|
1154
|
+
try {
|
|
1155
|
+
run.transition("done", "budget_limited");
|
|
1156
|
+
transitioned = true;
|
|
1157
|
+
} catch (te) {
|
|
1158
|
+
void te;
|
|
1159
|
+
}
|
|
1160
|
+
if (transitioned) {
|
|
1161
|
+
deps.store.save(run).catch((e) => {
|
|
1162
|
+
const m = e instanceof Error ? e.message : String(e);
|
|
1163
|
+
logger2.error(`[workflow] store.save failed (budget done): ${m}`);
|
|
1164
|
+
});
|
|
1165
|
+
deps.log?.("debug", "workflow:error-recovery", "run saved after budget done", { runId: run.runId, reason: run.state.reason });
|
|
1166
|
+
try {
|
|
1167
|
+
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
|
|
1168
|
+
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
|
|
1169
|
+
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
|
|
1170
|
+
deps.onRunDone?.(run);
|
|
1171
|
+
} catch (err) {
|
|
1172
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
1173
|
+
logger2.error(`[workflow] onRunDone/emit failed (budget done): ${m}`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}).catch((err) => {
|
|
1178
|
+
if (err instanceof Error && err.name === "AbortError") return;
|
|
1179
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1180
|
+
logger2.error(`[workflow] agent call ${msg.callId} failed: ${message}`);
|
|
1181
|
+
node.live = void 0;
|
|
1182
|
+
if (isOrphanedCall(run, msg.callId, call)) {
|
|
1183
|
+
deps.log?.("debug", "workflow:error-recovery", "orphan agent call failure dropped", { runId: run.runId, callId: msg.callId });
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
const errorResult = { content: "", error: message };
|
|
1187
|
+
if (call.status !== "done") {
|
|
1188
|
+
if (call.status === "pending") call.markRunning();
|
|
1189
|
+
call.markDone(errorResult);
|
|
1190
|
+
}
|
|
1191
|
+
run.state.trace.update(msg.callId, {
|
|
1192
|
+
status: "failed",
|
|
1193
|
+
result: errorResult,
|
|
1194
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1195
|
+
});
|
|
1196
|
+
postAgentResult(run, msg.callId, errorResult, false);
|
|
1197
|
+
postBudgetUpdate(run);
|
|
1198
|
+
deps.store.save(run).catch((e) => {
|
|
1199
|
+
logger2.error(`[workflow] store.save failed (catch fallback): ${e instanceof Error ? e.message : String(e)}`);
|
|
1200
|
+
});
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
function makeSerializeFailedResult(prefix, errMsg) {
|
|
1204
|
+
return { content: "", error: `${prefix}: ${errMsg}` };
|
|
1205
|
+
}
|
|
1206
|
+
function dispatchWorkflowCall(run, msg, deps) {
|
|
1207
|
+
if (typeof msg.callId !== "number" || !Number.isFinite(msg.callId) || typeof msg.name !== "string" || typeof msg.args !== "object" || msg.args === null) {
|
|
1208
|
+
logger2.error(`[workflow] malformed workflow-call message: callId=${JSON.stringify(msg.callId)}, name=${JSON.stringify(msg.name)}`);
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
const postResult = (result) => {
|
|
1212
|
+
if (run.state.status !== "running") return;
|
|
1213
|
+
try {
|
|
1214
|
+
run.runtime?.worker.postMessage({
|
|
1215
|
+
type: "workflow-result",
|
|
1216
|
+
callId: msg.callId,
|
|
1217
|
+
result
|
|
1218
|
+
});
|
|
1219
|
+
} catch (err) {
|
|
1220
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1221
|
+
logger2.error(`[workflow] postResult (workflow-call callId=${msg.callId}) failed: ${errMsg}. Sending error fallback.`);
|
|
1222
|
+
try {
|
|
1223
|
+
run.runtime?.worker.postMessage({
|
|
1224
|
+
type: "workflow-result",
|
|
1225
|
+
callId: msg.callId,
|
|
1226
|
+
result: makeSerializeFailedResult("Workflow result serialization failed", errMsg)
|
|
1227
|
+
});
|
|
1228
|
+
} catch {
|
|
1229
|
+
logger2.error(`[workflow] postResult fallback also failed (callId=${msg.callId}): worker pending will hang until timeout`);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
if (!deps.onWorkflowCall) {
|
|
1234
|
+
postResult({
|
|
1235
|
+
content: "",
|
|
1236
|
+
error: `workflow() not supported: onWorkflowCall not injected`
|
|
1237
|
+
});
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
void deps.onWorkflowCall(msg.name, msg.args, run).then(postResult).catch((err) => {
|
|
1241
|
+
postResult({
|
|
1242
|
+
content: "",
|
|
1243
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1244
|
+
});
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
function postAgentResult(run, callId, result, cached) {
|
|
1248
|
+
try {
|
|
1249
|
+
run.runtime?.worker.postMessage({ type: "agent-result", callId, result, cached });
|
|
1250
|
+
} catch (err) {
|
|
1251
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1252
|
+
logger2.error(`[workflow] postAgentResult failed (callId=${callId}): ${msg}. Result likely contains non-cloneable value.`);
|
|
1253
|
+
try {
|
|
1254
|
+
run.runtime?.worker.postMessage({
|
|
1255
|
+
type: "agent-result",
|
|
1256
|
+
callId,
|
|
1257
|
+
result: makeSerializeFailedResult("Result serialization failed", msg),
|
|
1258
|
+
// 原 result 不可克隆时 cached 透传原值含义失真(fallback result 非缓存命中)→ 固定 false
|
|
1259
|
+
cached: false
|
|
1260
|
+
});
|
|
1261
|
+
} catch {
|
|
1262
|
+
logger2.error(`[workflow] postAgentResult fallback also failed (callId=${callId}): worker pending will hang until timeout`);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
function postBudgetUpdate(run) {
|
|
1267
|
+
try {
|
|
1268
|
+
run.runtime?.worker.postMessage({
|
|
1269
|
+
type: "budget-update",
|
|
1270
|
+
budget: {
|
|
1271
|
+
usedTokens: run.state.budget.usedTokens,
|
|
1272
|
+
usedCost: run.state.budget.usedCost
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
} catch (err) {
|
|
1276
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1277
|
+
logger2.error(`[workflow] postBudgetUpdate failed: ${msg}. Budget sync to worker skipped (non-critical).`);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
async function handleReturn(run, msg, deps) {
|
|
1281
|
+
deps.log?.("debug", "workflow:error-recovery", "handleReturn", { runId: run.runId, status: run.state.status });
|
|
1282
|
+
if (msg.workerLogs && msg.workerLogs.length > 0) {
|
|
1283
|
+
run.state.errorLogs.push(...msg.workerLogs);
|
|
1284
|
+
if (run.state.errorLogs.length > MAX_ERROR_LOGS) {
|
|
1285
|
+
run.state.errorLogs = run.state.errorLogs.slice(-MAX_ERROR_LOGS);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
run.state.scriptResult = msg.result;
|
|
1289
|
+
run.transition("done", "completed");
|
|
1290
|
+
await saveRunBestEffort(run, deps, "handleReturn (done,completed)");
|
|
1291
|
+
deps.log?.("debug", "workflow:error-recovery", "run saved after return", { runId: run.runId, reason: run.state.reason });
|
|
1292
|
+
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
|
|
1293
|
+
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
|
|
1294
|
+
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
|
|
1295
|
+
deps.onRunDone?.(run);
|
|
1296
|
+
}
|
|
1297
|
+
async function handleWorkerError(run, err, deps, handlers) {
|
|
1298
|
+
if (isTerminal(run)) return;
|
|
1299
|
+
if (run.runtime?.receivedTerminalMessage) return;
|
|
1300
|
+
if (run.runtime) run.runtime.receivedTerminalMessage = true;
|
|
1301
|
+
const count = (run.meta.workerErrorCount ?? 0) + 1;
|
|
1302
|
+
run.meta.workerErrorCount = count;
|
|
1303
|
+
if (count <= MAX_WORKER_RETRIES) {
|
|
1304
|
+
await scheduleRebuild(run, deps, handlers);
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
run.state.error = err.message;
|
|
1308
|
+
deps.log?.("debug", "workflow:error-recovery", "handleWorkerError retries exceeded, transition done", { runId: run.runId, count });
|
|
1309
|
+
run.transition("done", "failed");
|
|
1310
|
+
await saveRunBestEffort(run, deps, "handleWorkerError (done,failed)");
|
|
1311
|
+
deps.log?.("debug", "workflow:error-recovery", "run saved after worker error", { runId: run.runId, reason: run.state.reason });
|
|
1312
|
+
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
|
|
1313
|
+
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
|
|
1314
|
+
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
|
|
1315
|
+
deps.onRunDone?.(run);
|
|
1316
|
+
}
|
|
1317
|
+
async function handleWorkerExit(run, code, handle, deps, handlers) {
|
|
1318
|
+
if (!handle.isCurrent) return;
|
|
1319
|
+
if (isTerminal(run)) return;
|
|
1320
|
+
if (code === 0) {
|
|
1321
|
+
if (run.runtime?.receivedTerminalMessage) return;
|
|
1322
|
+
deps.log?.("debug", "workflow:error-recovery", "worker exited without terminal message, transition done", { runId: run.runId });
|
|
1323
|
+
run.state.error = WORKER_EXITED_WITHOUT_RESULT_MSG;
|
|
1324
|
+
run.transition("done", "failed");
|
|
1325
|
+
await saveRunBestEffort(run, deps, "handleWorkerExit (done,failed, no terminal message)");
|
|
1326
|
+
deps.log?.("debug", "workflow:error-recovery", "run saved after exit without result", { runId: run.runId, reason: run.state.reason });
|
|
1327
|
+
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
|
|
1328
|
+
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
|
|
1329
|
+
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
|
|
1330
|
+
deps.onRunDone?.(run);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
await handleWorkerError(
|
|
1334
|
+
run,
|
|
1335
|
+
new Error(`Worker exited with code ${code}`),
|
|
1336
|
+
deps,
|
|
1337
|
+
handlers
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
async function handleScriptError(run, errorMsg, workerLogs, deps, handlers) {
|
|
1341
|
+
if (isTerminal(run)) return;
|
|
1342
|
+
if (workerLogs.length > 0) {
|
|
1343
|
+
run.state.errorLogs.push(...workerLogs);
|
|
1344
|
+
if (run.state.errorLogs.length > MAX_ERROR_LOGS) {
|
|
1345
|
+
run.state.errorLogs = run.state.errorLogs.slice(-MAX_ERROR_LOGS);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
const count = (run.meta.scriptErrorCount ?? 0) + 1;
|
|
1349
|
+
run.meta.scriptErrorCount = count;
|
|
1350
|
+
if (count <= MAX_WORKER_RETRIES) {
|
|
1351
|
+
await scheduleRebuild(run, deps, handlers);
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
run.state.error = `Workflow failed after ${MAX_WORKER_RETRIES} retries: ${errorMsg}`;
|
|
1355
|
+
deps.log?.("debug", "workflow:error-recovery", "handleScriptError retries exceeded, transition done", { runId: run.runId, count });
|
|
1356
|
+
run.transition("done", "failed");
|
|
1357
|
+
await saveRunBestEffort(run, deps, "handleScriptError (done,failed)");
|
|
1358
|
+
deps.log?.("debug", "workflow:error-recovery", "run saved after script error", { runId: run.runId, reason: run.state.reason });
|
|
1359
|
+
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister", { runId: run.runId, reason: run.state.reason });
|
|
1360
|
+
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
|
|
1361
|
+
deps.log?.("debug", "workflow:error-recovery", "emit pending:unregister done", { runId: run.runId });
|
|
1362
|
+
deps.onRunDone?.(run);
|
|
1363
|
+
}
|
|
1364
|
+
async function scheduleRebuild(run, deps, handlers) {
|
|
1365
|
+
const retryIndex = Math.max(
|
|
1366
|
+
run.meta.workerErrorCount ?? 0,
|
|
1367
|
+
run.meta.scriptErrorCount ?? 0
|
|
1368
|
+
);
|
|
1369
|
+
await delay2(backoffDelay2(retryIndex));
|
|
1370
|
+
if (isTerminal(run)) return;
|
|
1371
|
+
const remainingMs = remainingTimeBudgetMs(run);
|
|
1372
|
+
if (remainingMs !== void 0 && remainingMs <= 0) {
|
|
1373
|
+
await finalizeTimeBudgetExhausted(run, deps);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
rebuildRuntime(run, deps, handlers);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// src/orchestration/models/budget.ts
|
|
1380
|
+
var INPUT_WEIGHT = 1;
|
|
1381
|
+
var CACHE_READ_WEIGHT = 0.02;
|
|
1382
|
+
var CACHE_WRITE_WEIGHT = 0;
|
|
1383
|
+
var OUTPUT_WEIGHT = 2;
|
|
1384
|
+
var Budget = class {
|
|
1385
|
+
maxTokens;
|
|
1386
|
+
maxCost;
|
|
1387
|
+
maxTimeMs;
|
|
1388
|
+
usedTokens = 0;
|
|
1389
|
+
usedCost = 0;
|
|
1390
|
+
/** 总调用计数(持久化/诊断用;execute-agent-call 每次 dispatch 后 increment)。 */
|
|
1391
|
+
totalCallCount = 0;
|
|
1392
|
+
constructor(opts = {}) {
|
|
1393
|
+
this.maxTokens = opts.maxTokens;
|
|
1394
|
+
this.maxCost = opts.maxCost;
|
|
1395
|
+
this.maxTimeMs = opts.maxTimeMs;
|
|
1396
|
+
this.usedTokens = opts.usedTokens ?? 0;
|
|
1397
|
+
this.usedCost = opts.usedCost ?? 0;
|
|
1398
|
+
this.totalCallCount = opts.totalCallCount ?? 0;
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* 累加一次 agent 调用的 usage(加权口径)。
|
|
1402
|
+
*
|
|
1403
|
+
* 四项 token 按各自权重(INPUT/CACHE_READ/CACHE_WRITE/OUTPUT_WEIGHT)折算后求和,
|
|
1404
|
+
* 而非原始 token 数直接相加。retry 间的真实消耗如实记录,避免预算被低估。
|
|
1405
|
+
* 详见上方权重常量的口径说明。
|
|
1406
|
+
*/
|
|
1407
|
+
consume(usage) {
|
|
1408
|
+
const numOrZero = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
1409
|
+
this.usedTokens += numOrZero(usage.input) * INPUT_WEIGHT + numOrZero(usage.output) * OUTPUT_WEIGHT + numOrZero(usage.cacheRead) * CACHE_READ_WEIGHT + numOrZero(usage.cacheWrite) * CACHE_WRITE_WEIGHT;
|
|
1410
|
+
this.usedCost += numOrZero(usage.cost);
|
|
1411
|
+
}
|
|
1412
|
+
/** 累加调用计数(每次 agent dispatch 后调用;持久化快照同步)。 */
|
|
1413
|
+
incrementCallCount() {
|
|
1414
|
+
this.totalCallCount += 1;
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* 是否超 token / cost 预算(FR-3)。
|
|
1418
|
+
*
|
|
1419
|
+
* maxTokens===0 或 undefined 视为不限制(守卫);
|
|
1420
|
+
* maxCost===0 或 undefined 视为不限制。
|
|
1421
|
+
* 时间预算(maxTimeMs)不由本方法判断——它是 wall-clock 约束,需参照 startedAt,
|
|
1422
|
+
* 由 lifecycle 层的 scheduleTimeBudget(runWorkflow 内 setTimeout)
|
|
1423
|
+
* 独立调度,到期 abortRun(doneReason="time_limited")。
|
|
1424
|
+
*/
|
|
1425
|
+
isExceeded() {
|
|
1426
|
+
if (this.maxTokens !== void 0 && this.maxTokens > 0 && this.usedTokens >= this.maxTokens) {
|
|
1427
|
+
return true;
|
|
1428
|
+
}
|
|
1429
|
+
return this.maxCost !== void 0 && this.maxCost > 0 && this.usedCost >= this.maxCost;
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* 剩余 token 预算。maxTokens 未设或 ≤0 时返回 undefined(视为不限制)。
|
|
1433
|
+
*
|
|
1434
|
+
* 嵌套 workflow() 调用时由 executeNestedWorkflow 消费:子 run 的 budgetTokens
|
|
1435
|
+
* 继承父 run 的剩余预算,实现父子预算隔离下的总量约束。
|
|
1436
|
+
*/
|
|
1437
|
+
remaining() {
|
|
1438
|
+
if (this.maxTokens === void 0 || this.maxTokens <= 0) return void 0;
|
|
1439
|
+
return Math.max(0, this.maxTokens - this.usedTokens);
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
|
|
1443
|
+
// src/orchestration/models/trace.ts
|
|
1444
|
+
var TRACE_RESULT_MAX_CHARS = 8e3;
|
|
1445
|
+
var TRIM_HEAD_CHARS = 4e3;
|
|
1446
|
+
var TRIM_TAIL_CHARS = 4e3;
|
|
1447
|
+
function trimTraceResult(result) {
|
|
1448
|
+
if (result === void 0) return result;
|
|
1449
|
+
const { content } = result;
|
|
1450
|
+
if (content.length <= TRACE_RESULT_MAX_CHARS) return result;
|
|
1451
|
+
const trimmed = content.slice(0, TRIM_HEAD_CHARS) + `
|
|
1452
|
+
\u2026[trace result truncated, original ${content.length} chars]\u2026
|
|
1453
|
+
` + content.slice(-TRIM_TAIL_CHARS);
|
|
1454
|
+
return { ...result, content: trimmed };
|
|
1455
|
+
}
|
|
1456
|
+
var Trace = class _Trace {
|
|
1457
|
+
nodes = [];
|
|
1458
|
+
/** stepIndex 倒排索引(查询加速 O(1);值与 nodes 元素引用共享)。 */
|
|
1459
|
+
byIndex = /* @__PURE__ */ new Map();
|
|
1460
|
+
/**
|
|
1461
|
+
* 从已有节点数组重建 Trace(用于 RunStore 反序列化重水合)。
|
|
1462
|
+
*
|
|
1463
|
+
* 防御性拷贝——传入数组不被持有,外部 mutation 不影响 Trace。
|
|
1464
|
+
* 不验证节点顺序/唯一性(调用方保证快照来源可信)。
|
|
1465
|
+
* 不做裁剪——落盘快照已是 write 路径裁剪后形态,旧版本未裁剪长
|
|
1466
|
+
* content 重水合保持原样(read 路径无二次信息损失)。
|
|
1467
|
+
* 重水合后 call.traceNode(来自快照 calls[].traceNode,
|
|
1468
|
+
* jsonl-run-store.ts:156 直接传入)与 Trace.nodes 副本非同引用——
|
|
1469
|
+
* D-10 引用共享仅 live append 路径成立。
|
|
1470
|
+
*/
|
|
1471
|
+
static fromArray(nodes) {
|
|
1472
|
+
const trace = new _Trace();
|
|
1473
|
+
for (const node of nodes) {
|
|
1474
|
+
const copy = { ...node };
|
|
1475
|
+
trace.nodes.push(copy);
|
|
1476
|
+
trace.byIndex.set(copy.stepIndex, copy);
|
|
1477
|
+
}
|
|
1478
|
+
return trace;
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Append a trace node(append-only,不改已有节点)。
|
|
1482
|
+
*
|
|
1483
|
+
* 入口裁剪:超长 result.content 先 mutate 入参节点的 result 字段,
|
|
1484
|
+
* 再 push 原节点引用(禁止 push 副本——保持 AgentCall.traceNode 与
|
|
1485
|
+
* Trace.nodes 共享同一引用的 D-10 不变式)。
|
|
1486
|
+
*/
|
|
1487
|
+
append(node) {
|
|
1488
|
+
node.result = trimTraceResult(node.result);
|
|
1489
|
+
this.nodes.push(node);
|
|
1490
|
+
this.byIndex.set(node.stepIndex, node);
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Update a trace node by stepIndex (callId) with a partial patch.
|
|
1494
|
+
*
|
|
1495
|
+
* 只改 patch 中提供的字段(status/result/error/completedAt/sessionId)。
|
|
1496
|
+
* stepIndex 不存在时 no-op(防御性——agent 完成/失败回调可能晚于 run 终止到达)。
|
|
1497
|
+
*/
|
|
1498
|
+
update(stepIndex, patch) {
|
|
1499
|
+
const node = this.findByStepIndex(stepIndex);
|
|
1500
|
+
if (!node) return;
|
|
1501
|
+
if (patch.status !== void 0) node.status = patch.status;
|
|
1502
|
+
if (patch.result !== void 0) node.result = trimTraceResult(patch.result);
|
|
1503
|
+
if (patch.error !== void 0) node.error = patch.error;
|
|
1504
|
+
if (patch.completedAt !== void 0) node.completedAt = patch.completedAt;
|
|
1505
|
+
if (patch.sessionId !== void 0) node.sessionId = patch.sessionId;
|
|
1506
|
+
if (patch.sessionFile !== void 0) node.sessionFile = patch.sessionFile;
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* 查找指定 stepIndex 的节点(byIndex O(1),trace 中 stepIndex 应唯一)。
|
|
1510
|
+
*
|
|
1511
|
+
* 语义差异声明(旧线性扫 first-match → Map last-wins):仅在破坏
|
|
1512
|
+
* stepIndex 唯一性的违规使用下可见,两组场景——
|
|
1513
|
+
* 1. 重复 append 同 stepIndex 且未 remove:返回最后一个节点(last-wins;
|
|
1514
|
+
* 旧线性扫 first-match 会返回第一个)。
|
|
1515
|
+
* 2. 重复 append 后 removeByStepIndex:remove 的 findIndex 命中首个旧节点
|
|
1516
|
+
* splice,而 byIndex.delete 把整个 stepIndex 键删掉——nodes 残留第二个
|
|
1517
|
+
* 节点成为孤儿(find/update 不可达,length/toArray 仍可见)。
|
|
1518
|
+
* 合法路径无差异:唯一性由 discard 先 remove 再 append 保证(W1TC12 锚定)。
|
|
1519
|
+
*/
|
|
1520
|
+
findByStepIndex(stepIndex) {
|
|
1521
|
+
return this.byIndex.get(stepIndex);
|
|
1522
|
+
}
|
|
1523
|
+
/** 按节点引用删除(仅用于测试或 run 重建场景;正常运行不调用)。 */
|
|
1524
|
+
find(stepIndex) {
|
|
1525
|
+
return this.findByStepIndex(stepIndex);
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* 按 stepIndex 移除节点(崩溃重建清理在飞 call 用)。
|
|
1529
|
+
*
|
|
1530
|
+
* 正常运行不调用(append-only 不变式)。仅 error-recovery 的 discardInFlightCalls
|
|
1531
|
+
* (rebuildRuntime 内,F2)清理被旧 runtime abort 的在飞 call 时用——移除其 trace
|
|
1532
|
+
* 节点,让重跑重发 agent-call 时 append 全新节点走全新执行路径(避免 stale
|
|
1533
|
+
* "running" 节点残留 + trace.update 命中旧节点导致新节点 orphan)。
|
|
1534
|
+
* stepIndex 不存在时 no-op(防御性)。
|
|
1535
|
+
*/
|
|
1536
|
+
removeByStepIndex(stepIndex) {
|
|
1537
|
+
const idx = this.nodes.findIndex((n) => n.stepIndex === stepIndex);
|
|
1538
|
+
if (idx === -1) return;
|
|
1539
|
+
this.nodes.splice(idx, 1);
|
|
1540
|
+
this.byIndex.delete(stepIndex);
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* readonly 视图——返回内部 nodes 数组引用(仅类型级 readonly,运行时无
|
|
1544
|
+
* 防御)。消费方禁止结构化 mutate(push/splice/重排/覆盖元素):byIndex
|
|
1545
|
+
* 引入后外部结构化 mutate 会使 nodes 与倒排索引 desync。字段级变更走 update()。
|
|
1546
|
+
*/
|
|
1547
|
+
toArray() {
|
|
1548
|
+
return this.nodes;
|
|
1549
|
+
}
|
|
1550
|
+
/** 当前节点数。 */
|
|
1551
|
+
get length() {
|
|
1552
|
+
return this.nodes.length;
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1556
|
+
// src/orchestration/models/types.ts
|
|
1557
|
+
var VALID_RUN_TRANSITIONS = {
|
|
1558
|
+
running: ["done"],
|
|
1559
|
+
done: []
|
|
1560
|
+
};
|
|
1561
|
+
function canRunTransition(from, to) {
|
|
1562
|
+
return VALID_RUN_TRANSITIONS[from].includes(to);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// src/orchestration/models/workflow-run.ts
|
|
1566
|
+
var WorkflowRun = class _WorkflowRun {
|
|
1567
|
+
runId;
|
|
1568
|
+
spec;
|
|
1569
|
+
state;
|
|
1570
|
+
runtime;
|
|
1571
|
+
meta;
|
|
1572
|
+
/**
|
|
1573
|
+
* 创建聚合根。初始状态 "running"(一次性生命周期:run 从创建起即在执行,
|
|
1574
|
+
* runtime 由紧随其后的 assignRuntime 注入)。也可传入 done 状态用于重水合
|
|
1575
|
+
* 已完成的 run(loadAll 后的只读聚合)。
|
|
1576
|
+
*
|
|
1577
|
+
* 不变式 I1 构造期跳过——「创建即 running」要求构造瞬间 runtime===undefined
|
|
1578
|
+
* 合法(runtime 必须由 assignRuntime 注入,构造函数无从持有);重水合的
|
|
1579
|
+
* running 快照同样无 worker。I1 的运行时校验在 assignRuntime/transition/
|
|
1580
|
+
* replaceRuntime 末尾的 validateInvariants 处生效。
|
|
1581
|
+
*/
|
|
1582
|
+
constructor(runId, spec, state, meta) {
|
|
1583
|
+
this.runId = runId;
|
|
1584
|
+
this.spec = spec;
|
|
1585
|
+
this.state = state;
|
|
1586
|
+
this.meta = meta;
|
|
1587
|
+
this.runtime = void 0;
|
|
1588
|
+
this.validateInvariantI2();
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* 从持久化快照重水合聚合根。与构造函数同语义(构造期跳过 I1——持久化的
|
|
1592
|
+
* running 状态没有 worker,进程被杀后 worker 不可能还活着)。保留独立工厂
|
|
1593
|
+
* 标注重水合意图;调用方(D-4 kill-9 恢复)负责在 session_start 时把残留
|
|
1594
|
+
* running 转 done,failed,恢复 I1。
|
|
1595
|
+
*
|
|
1596
|
+
* @throws I2 违反(done 快照缺 reason 仍是 bug,不可跳过)
|
|
1597
|
+
*/
|
|
1598
|
+
static reconstruct(runId, spec, state, meta) {
|
|
1599
|
+
return new _WorkflowRun(runId, spec, state, meta);
|
|
1600
|
+
}
|
|
1601
|
+
// ── 不变式校验 ─────────────────────────────────────────────
|
|
1602
|
+
/**
|
|
1603
|
+
* 校验不变式 I1 + I2。违反抛错(聚合根自我保护,fail-fast)。
|
|
1604
|
+
* 在每个 mutation 方法末尾调用(防御式编程 + 测试可断言)。
|
|
1605
|
+
*/
|
|
1606
|
+
validateInvariants() {
|
|
1607
|
+
this.validateInvariantI2();
|
|
1608
|
+
if (this.state.status === "running" && this.runtime === void 0) {
|
|
1609
|
+
throw new Error(
|
|
1610
|
+
`WorkflowRun invariant I1 violated: status==="running" but runtime is undefined (runId=${this.runId})`
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
if (this.state.status !== "running" && this.runtime !== void 0) {
|
|
1614
|
+
throw new Error(
|
|
1615
|
+
`WorkflowRun invariant I1 violated: status!=="running" but runtime is defined (runId=${this.runId})`
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* 仅校验不变式 I2(done ⟹ reason)。构造期用——「创建即 running」与重水合的
|
|
1621
|
+
* running 快照都无 runtime(I1 构造期跳过),但 I2 必须保证(done 缺 reason 是真 bug)。
|
|
1622
|
+
*/
|
|
1623
|
+
validateInvariantI2() {
|
|
1624
|
+
if (this.state.status === "done" && this.state.reason === void 0) {
|
|
1625
|
+
throw new Error(
|
|
1626
|
+
`WorkflowRun invariant I2 violated: status==="done" but reason is undefined (runId=${this.runId})`
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
// ── 状态机转换 ─────────────────────────────────────────────
|
|
1631
|
+
/**
|
|
1632
|
+
* 状态机转换。合法转换:running→done。
|
|
1633
|
+
*
|
|
1634
|
+
* running 的进入不走 transition——构造即 running,replaceRuntime 保持 running。
|
|
1635
|
+
* 调用 transition("running") 抛错,防止绕过 runtime 注入直接改状态。
|
|
1636
|
+
*
|
|
1637
|
+
* 副作用:
|
|
1638
|
+
* - →done: releaseRuntime + 设 state.reason + meta.completedAt
|
|
1639
|
+
*
|
|
1640
|
+
* @param target 目标状态(不允许 "running"——runtime 注入只走 assignRuntime/replaceRuntime)
|
|
1641
|
+
* @param reason →done 时必填(done ⟹ reason,不变式 I2)
|
|
1642
|
+
* @throws 非法转换 / done 缺 reason / target==="running"
|
|
1643
|
+
*/
|
|
1644
|
+
transition(target, reason) {
|
|
1645
|
+
if (target === "running") {
|
|
1646
|
+
throw new Error(
|
|
1647
|
+
`WorkflowRun.transition: cannot transition to "running" directly \u2014 use assignRuntime() (runId=${this.runId})`
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
if (!canRunTransition(this.state.status, target)) {
|
|
1651
|
+
throw new Error(
|
|
1652
|
+
`WorkflowRun.transition: illegal transition ${this.state.status} \u2192 ${target} (runId=${this.runId})`
|
|
1653
|
+
);
|
|
1654
|
+
}
|
|
1655
|
+
if (target === "done" && reason === void 0) {
|
|
1656
|
+
throw new Error(
|
|
1657
|
+
`WorkflowRun.transition: transition to "done" requires a reason (runId=${this.runId})`
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
this.releaseRuntime();
|
|
1661
|
+
this.state.status = target;
|
|
1662
|
+
this.state.reason = reason;
|
|
1663
|
+
this.meta.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1664
|
+
this.validateInvariants();
|
|
1665
|
+
}
|
|
1666
|
+
// ── Runtime 生命周期 ───────────────────────────────────────
|
|
1667
|
+
/**
|
|
1668
|
+
* 绑定 runtime(run 创建后注入执行资源)。
|
|
1669
|
+
*
|
|
1670
|
+
* 前置:status==="running" && runtime===undefined(runWorkflow 创建路径——
|
|
1671
|
+
* 构造即 running 但 runtime 延迟到此处注入)。
|
|
1672
|
+
* 原子地:设 runtime 后末尾 validateInvariants,恢复构造期跳过的 I1
|
|
1673
|
+
* (running ⟺ runtime!==undefined)。
|
|
1674
|
+
*
|
|
1675
|
+
* @throws runtime 已定义 / status 不是 "running"(done 僵尸不可复活)
|
|
1676
|
+
*/
|
|
1677
|
+
assignRuntime(rt) {
|
|
1678
|
+
if (this.runtime !== void 0) {
|
|
1679
|
+
throw new Error(
|
|
1680
|
+
`WorkflowRun.assignRuntime: runtime already defined (runId=${this.runId})`
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
if (this.state.status !== "running") {
|
|
1684
|
+
throw new Error(
|
|
1685
|
+
`WorkflowRun.assignRuntime: requires status==="running" (current: ${this.state.status}, runId=${this.runId})`
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
this.runtime = rt;
|
|
1689
|
+
this.validateInvariants();
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* 解绑 runtime(done 时由 transition 调用,也可独立调用)。
|
|
1693
|
+
*
|
|
1694
|
+
* 前置:无(runtime===undefined 时 no-op,幂等)。
|
|
1695
|
+
* 副作用:调 runtime.release("terminal") 释放 worker/controller,置 runtime=undefined。
|
|
1696
|
+
*/
|
|
1697
|
+
releaseRuntime() {
|
|
1698
|
+
if (this.runtime === void 0) return;
|
|
1699
|
+
this.runtime.release("terminal");
|
|
1700
|
+
this.runtime = void 0;
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* 原地替换 runtime(G5-001:worker-error-retry)。
|
|
1704
|
+
*
|
|
1705
|
+
* 前置:status==="running"(G6-001:终态 run 拒绝重建)。
|
|
1706
|
+
* 原子地:释放旧 runtime(worker.terminate + abort)+ 绑定新 runtime,
|
|
1707
|
+
* 全程 status 保持 "running",不变式 I1 不违反(中间无 runtime===undefined 可见态)。
|
|
1708
|
+
*
|
|
1709
|
+
* 与 release+assign 的区别:replaceRuntime 不改 status,中间同步完成,
|
|
1710
|
+
* 外部观察不到违反不变式的瞬间。
|
|
1711
|
+
*
|
|
1712
|
+
* @throws status!=="running"
|
|
1713
|
+
*/
|
|
1714
|
+
replaceRuntime(rt) {
|
|
1715
|
+
if (this.state.status !== "running") {
|
|
1716
|
+
throw new Error(
|
|
1717
|
+
`WorkflowRun.replaceRuntime: requires status==="running" (current: ${this.state.status}, runId=${this.runId})`
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
if (this.runtime !== void 0) {
|
|
1721
|
+
this.runtime.release("terminal");
|
|
1722
|
+
}
|
|
1723
|
+
this.runtime = rt;
|
|
1724
|
+
this.validateInvariants();
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
// src/orchestration/lifecycle.ts
|
|
1729
|
+
var logger3 = getLogger("subagents");
|
|
1730
|
+
var RUNID_RADIX = 36;
|
|
1731
|
+
var RUNID_SLICE_START = 2;
|
|
1732
|
+
var RUNID_SLICE_END = 8;
|
|
1733
|
+
function generateRunId() {
|
|
1734
|
+
return `wf-${Date.now()}-${Math.random().toString(RUNID_RADIX).slice(RUNID_SLICE_START, RUNID_SLICE_END)}`;
|
|
1735
|
+
}
|
|
1736
|
+
function makeHandlers(run, deps) {
|
|
1737
|
+
const handlers = {
|
|
1738
|
+
async onMessage(raw) {
|
|
1739
|
+
await handleWorkerMessage(run, raw, deps, handlers);
|
|
1740
|
+
},
|
|
1741
|
+
async onError(err) {
|
|
1742
|
+
await handleWorkerError(run, err, deps, handlers);
|
|
1743
|
+
},
|
|
1744
|
+
async onExit(code, handle) {
|
|
1745
|
+
await handleWorkerExit(run, code, handle, deps, handlers);
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1748
|
+
return handlers;
|
|
1749
|
+
}
|
|
1750
|
+
function scheduleTimeBudget(runId, deps, budgetTimeMs) {
|
|
1751
|
+
assertSafeTimerDelay(budgetTimeMs, "budgetTimeMs");
|
|
1752
|
+
const timer = setTimeout(() => {
|
|
1753
|
+
void abortRun(runId, deps, "Time budget exceeded", "time_limited").catch(
|
|
1754
|
+
(err) => {
|
|
1755
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1756
|
+
logger3.error(`[workflow] time budget abort failed: ${msg}`);
|
|
1757
|
+
}
|
|
1758
|
+
);
|
|
1759
|
+
}, budgetTimeMs);
|
|
1760
|
+
timer.unref();
|
|
1761
|
+
return timer;
|
|
1762
|
+
}
|
|
1763
|
+
async function runWorkflow(spec, deps, signal) {
|
|
1764
|
+
validateRunArgs(spec);
|
|
1765
|
+
const runId = generateRunId();
|
|
1766
|
+
if (spec.args && typeof spec.args === "object") {
|
|
1767
|
+
spec.args._runId = runId;
|
|
1768
|
+
}
|
|
1769
|
+
deps.log?.("debug", "workflow:lifecycle", "runWorkflow start", { runId, scriptName: spec.scriptName });
|
|
1770
|
+
if (signal?.aborted) {
|
|
1771
|
+
throw new Error("Workflow run aborted before start");
|
|
1772
|
+
}
|
|
1773
|
+
const run = new WorkflowRun(
|
|
1774
|
+
runId,
|
|
1775
|
+
spec,
|
|
1776
|
+
{
|
|
1777
|
+
status: "running",
|
|
1778
|
+
budget: spec.budgetRef ?? new Budget({
|
|
1779
|
+
maxTokens: spec.budgetTokens,
|
|
1780
|
+
maxTimeMs: spec.budgetTimeMs
|
|
1781
|
+
}),
|
|
1782
|
+
calls: /* @__PURE__ */ new Map(),
|
|
1783
|
+
trace: new Trace(),
|
|
1784
|
+
errorLogs: []
|
|
1785
|
+
},
|
|
1786
|
+
{ startedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
1787
|
+
);
|
|
1788
|
+
if (signal) {
|
|
1789
|
+
signal.addEventListener(
|
|
1790
|
+
"abort",
|
|
1791
|
+
() => {
|
|
1792
|
+
void abortRun(runId, deps, "External signal aborted").catch((err) => {
|
|
1793
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1794
|
+
logger3.error(`[workflow] abortRun on signal failed: ${msg}`);
|
|
1795
|
+
});
|
|
1796
|
+
},
|
|
1797
|
+
{ once: true }
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1800
|
+
const handlers = makeHandlers(run, deps);
|
|
1801
|
+
const controller = new AbortController();
|
|
1802
|
+
const worker = deps.workerHost.start(spec, spec.args, handlers);
|
|
1803
|
+
const timeBudgetTimer = spec.budgetTimeMs && spec.budgetTimeMs > 0 ? scheduleTimeBudget(runId, deps, spec.budgetTimeMs) : void 0;
|
|
1804
|
+
const runtime = new RunRuntime(worker, controller, timeBudgetTimer);
|
|
1805
|
+
run.assignRuntime(runtime);
|
|
1806
|
+
deps.runs.set(runId, run);
|
|
1807
|
+
await deps.store.save(run);
|
|
1808
|
+
deps.log?.("debug", "workflow:lifecycle", "run saved", { runId, status: run.state.status });
|
|
1809
|
+
deps.log?.("debug", "workflow:lifecycle", "emit pending:register", { runId });
|
|
1810
|
+
deps.eventBus?.emit("pending:register", {
|
|
1811
|
+
id: runId,
|
|
1812
|
+
type: "workflow",
|
|
1813
|
+
name: spec.slug || spec.scriptName || runId
|
|
1814
|
+
});
|
|
1815
|
+
deps.log?.("debug", "workflow:lifecycle", "emit pending:register done", { runId });
|
|
1816
|
+
return runId;
|
|
1817
|
+
}
|
|
1818
|
+
async function abortRun(runId, deps, reason, doneReason = "aborted") {
|
|
1819
|
+
const run = deps.runs.get(runId);
|
|
1820
|
+
if (!run) {
|
|
1821
|
+
throw new Error(`Workflow '${runId}' not found`);
|
|
1822
|
+
}
|
|
1823
|
+
deps.log?.("debug", "workflow:lifecycle", "abortRun", { runId, status: run.state.status, reason, doneReason });
|
|
1824
|
+
if (run.state.status === "done") {
|
|
1825
|
+
deps.log?.("debug", "workflow:lifecycle", "abortRun no-op: already done", { runId });
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
if (reason) {
|
|
1829
|
+
run.state.error = reason;
|
|
1830
|
+
}
|
|
1831
|
+
run.transition("done", doneReason);
|
|
1832
|
+
await deps.store.save(run);
|
|
1833
|
+
deps.log?.("debug", "workflow:lifecycle", "abortRun transition done", { runId, reason: run.state.reason });
|
|
1834
|
+
deps.log?.("debug", "workflow:lifecycle", "emit pending:unregister", { runId, reason: run.state.reason });
|
|
1835
|
+
deps.eventBus?.emit("pending:unregister", { id: run.runId, reason: run.state.reason ?? "completed" });
|
|
1836
|
+
deps.log?.("debug", "workflow:lifecycle", "emit pending:unregister done", { runId });
|
|
1837
|
+
deps.onRunDone?.(run);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
// src/index.ts
|
|
1841
|
+
var CORE_PACKAGE_VERSION = "0.2.0";
|
|
1842
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1843
|
+
0 && (module.exports = {
|
|
1844
|
+
CORE_PACKAGE_VERSION,
|
|
1845
|
+
DEFAULT_DATA_ROOT,
|
|
1846
|
+
abortRun,
|
|
1847
|
+
configureCore,
|
|
1848
|
+
configureNotifyDomain,
|
|
1849
|
+
getLogger,
|
|
1850
|
+
routeEngine,
|
|
1851
|
+
runWorkflow
|
|
1852
|
+
});
|