@zhushanwen/subagent-core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/chunk-3VOERJPJ.js +22 -0
- package/dist/chunk-A4IVZWVX.js +31 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +54 -0
- package/dist/execution/engine/engines/zcode/constants.d.cts +31 -0
- package/dist/execution/engine/engines/zcode/constants.d.ts +31 -0
- package/dist/execution/engine/engines/zcode/constants.js +22 -0
- package/dist/execution/engine/engines/zcode/reader.cjs +276 -0
- package/dist/execution/engine/engines/zcode/reader.d.cts +20 -0
- package/dist/execution/engine/engines/zcode/reader.d.ts +20 -0
- package/dist/execution/engine/engines/zcode/reader.js +239 -0
- package/dist/execution/engine/paths.cjs +55 -0
- package/dist/execution/engine/paths.d.cts +8 -0
- package/dist/execution/engine/paths.d.ts +8 -0
- package/dist/execution/engine/paths.js +26 -0
- package/dist/execution/relay-env.cjs +62 -0
- package/dist/execution/relay-env.d.cts +33 -0
- package/dist/execution/relay-env.d.ts +33 -0
- package/dist/execution/relay-env.js +20 -0
- package/dist/index.cjs +1852 -0
- package/dist/index.d.cts +1184 -0
- package/dist/index.d.ts +1184 -0
- package/dist/index.js +1810 -0
- package/dist/types-BxyAidGf.d.cts +875 -0
- package/dist/types-BxyAidGf.d.ts +875 -0
- package/package.json +99 -0
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +142 -0
- package/src/__tests__/append-system-prompt-assembly.test.ts +242 -0
- package/src/__tests__/builtin-workflows-structure.test.ts +180 -0
- package/src/__tests__/derive-closed-display-parity.test.ts +93 -0
- package/src/__tests__/fr4-get-state-handshake.test.ts +125 -0
- package/src/__tests__/fr8-orphan-recovery.test.ts +259 -0
- package/src/__tests__/m2-append-content-probe.test.ts +73 -0
- package/src/__tests__/manifest-store.test.ts +368 -0
- package/src/__tests__/record-store-cache.test.ts +335 -0
- package/src/__tests__/record-store-index.test.ts +560 -0
- package/src/__tests__/record-store-last-line.test.ts +129 -0
- package/src/__tests__/review-fix-loop-script.test.ts +288 -0
- package/src/__tests__/review-fix-loop-utils.test.ts +2377 -0
- package/src/__tests__/robustness-low-batch1.test.ts +286 -0
- package/src/__tests__/robustness-medium-batch1.test.ts +69 -0
- package/src/__tests__/robustness-medium-batch2.test.ts +65 -0
- package/src/__tests__/robustness-medium-batch3.test.ts +30 -0
- package/src/__tests__/robustness-medium-batch4.test.ts +358 -0
- package/src/__tests__/session-runner.test.ts +69 -0
- package/src/__tests__/smoke.test.ts +9 -0
- package/src/__tests__/stream-sink.test.ts +203 -0
- package/src/core/__tests__/host-services.test.ts +144 -0
- package/src/core/__tests__/logger.test.ts +78 -0
- package/src/core/__tests__/notify-ports.test.ts +104 -0
- package/src/core/host-services.ts +97 -0
- package/src/core/logger.ts +47 -0
- package/src/core/notify-ports.ts +126 -0
- package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +53 -0
- package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +150 -0
- package/src/execution/__tests__/alive-store.test.ts +147 -0
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +490 -0
- package/src/execution/__tests__/channel-registry-handshake.test.ts +242 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +610 -0
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
- package/src/execution/__tests__/concurrency-pool.test.ts +250 -0
- package/src/execution/__tests__/config.test.ts +302 -0
- package/src/execution/__tests__/delivery-methods.test.ts +424 -0
- package/src/execution/__tests__/dialog-queue.test.ts +299 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +243 -0
- package/src/execution/__tests__/execute-nesting.test.ts +323 -0
- package/src/execution/__tests__/execute-options-mapper.test.ts +200 -0
- package/src/execution/__tests__/execution-record.test.ts +1394 -0
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +171 -0
- package/src/execution/__tests__/finalize-record.test.ts +367 -0
- package/src/execution/__tests__/finalized-marker.test.ts +104 -0
- package/src/execution/__tests__/format-schema-instruction.test.ts +169 -0
- package/src/execution/__tests__/gc-timer.test.ts +184 -0
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
- package/src/execution/__tests__/helpers/mock-extension-api.ts +30 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +242 -0
- package/src/execution/__tests__/host-mode.test.ts +87 -0
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +383 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
- package/src/execution/__tests__/manifest-parentid.test.ts +117 -0
- package/src/execution/__tests__/model-resolver.test.ts +465 -0
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
- package/src/execution/__tests__/nested-visibility.test.ts +325 -0
- package/src/execution/__tests__/output-collector.test.ts +358 -0
- package/src/execution/__tests__/path-encoding.test.ts +104 -0
- package/src/execution/__tests__/pi-invocation.test.ts +134 -0
- package/src/execution/__tests__/record-store.test.ts +1057 -0
- package/src/execution/__tests__/records-cwd-isolation.test.ts +91 -0
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +339 -0
- package/src/execution/__tests__/recursive-visibility-env.test.ts +273 -0
- package/src/execution/__tests__/relay-env.test.ts +42 -0
- package/src/execution/__tests__/resource-policy.test.ts +109 -0
- package/src/execution/__tests__/rpc-mode.test.ts +89 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +687 -0
- package/src/execution/__tests__/run-spawn-integration.test.ts +933 -0
- package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +196 -0
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +199 -0
- package/src/execution/__tests__/session-context-resolver.test.ts +167 -0
- package/src/execution/__tests__/session-file-gc.test.ts +293 -0
- package/src/execution/__tests__/session-reconstructor.test.ts +379 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +350 -0
- package/src/execution/__tests__/spawn-args.test.ts +445 -0
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +189 -0
- package/src/execution/__tests__/spawn-event-adapter.test.ts +167 -0
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +207 -0
- package/src/execution/__tests__/spawned-children.test.ts +92 -0
- package/src/execution/__tests__/start-sync-model-guard.test.ts +150 -0
- package/src/execution/__tests__/stdin-writer.test.ts +462 -0
- package/src/execution/__tests__/stream-sink-retirement.test.ts +261 -0
- package/src/execution/__tests__/subagent-service-abort.test.ts +60 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +629 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
- package/src/execution/__tests__/subagent-service.test.ts +788 -0
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +310 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +612 -0
- package/src/execution/__tests__/temp-prompt.test.ts +53 -0
- package/src/execution/__tests__/timeout-integration.test.ts +615 -0
- package/src/execution/__tests__/tombstone-store.test.ts +73 -0
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
- package/src/execution/__tests__/turn-limiter.test.ts +65 -0
- package/src/execution/__tests__/ui-channels.test.ts +187 -0
- package/src/execution/__tests__/ui-interaction-model.test.ts +67 -0
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +369 -0
- package/src/execution/__tests__/ui-request-handler.test.ts +204 -0
- package/src/execution/__tests__/ui-request-observability.test.ts +113 -0
- package/src/execution/__tests__/ui-request-queue.test.ts +133 -0
- package/src/execution/__tests__/worktree-manager.test.ts +633 -0
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +229 -0
- package/src/execution/__tests__/worktree-reconcile.integration.test.ts +181 -0
- package/src/execution/__tests__/worktree-registry.test.ts +199 -0
- package/src/execution/agent-registry.ts +199 -0
- package/src/execution/agent-result-mapper.ts +90 -0
- package/src/execution/alive-store.ts +119 -0
- package/src/execution/argv-mirror.ts +113 -0
- package/src/execution/best-effort.ts +38 -0
- package/src/execution/channel-registry-access.ts +144 -0
- package/src/execution/concurrency-pool.ts +116 -0
- package/src/execution/config.ts +165 -0
- package/src/execution/dialog-queue.ts +330 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +78 -0
- package/src/execution/engine/__tests__/common/errors.test.ts +132 -0
- package/src/execution/engine/__tests__/common/event-journal.test.ts +177 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +192 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +81 -0
- package/src/execution/engine/__tests__/common/persona-router.test.ts +123 -0
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +154 -0
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +128 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +28 -0
- package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +141 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +109 -0
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +101 -0
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +77 -0
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +104 -0
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +201 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +76 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +79 -0
- package/src/execution/engine/__tests__/engine-discovery.test.ts +87 -0
- package/src/execution/engine/__tests__/model-prompt.test.ts +197 -0
- package/src/execution/engine/__tests__/paths.test.ts +39 -0
- package/src/execution/engine/__tests__/registry.test.ts +120 -0
- package/src/execution/engine/__tests__/routing.test.ts +231 -0
- package/src/execution/engine/common/data-dir.ts +66 -0
- package/src/execution/engine/common/errors.ts +183 -0
- package/src/execution/engine/common/event-journal.ts +254 -0
- package/src/execution/engine/common/journal-replay.ts +62 -0
- package/src/execution/engine/common/kill-chain.ts +221 -0
- package/src/execution/engine/common/nesting-guard.ts +50 -0
- package/src/execution/engine/common/persona-router.ts +108 -0
- package/src/execution/engine/common/pool-manager.ts +226 -0
- package/src/execution/engine/common/schema-emulation.ts +189 -0
- package/src/execution/engine/common/session-view-projection.ts +51 -0
- package/src/execution/engine/engine-discovery.ts +65 -0
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +469 -0
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +155 -0
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +164 -0
- package/src/execution/engine/engines/pi/pi-engine.ts +415 -0
- package/src/execution/engine/engines/pi/reader.ts +48 -0
- package/src/execution/engine/engines/pi/registration.ts +35 -0
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +100 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +39 -0
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +150 -0
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +246 -0
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +228 -0
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +71 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +127 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +580 -0
- package/src/execution/engine/engines/zcode/constants.ts +43 -0
- package/src/execution/engine/engines/zcode/golden-sample.ts +39 -0
- package/src/execution/engine/engines/zcode/launcher.ts +161 -0
- package/src/execution/engine/engines/zcode/parser.ts +436 -0
- package/src/execution/engine/engines/zcode/preparer.ts +363 -0
- package/src/execution/engine/engines/zcode/reader.ts +381 -0
- package/src/execution/engine/engines/zcode/registration.ts +37 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +658 -0
- package/src/execution/engine/host-task-spec.ts +47 -0
- package/src/execution/engine/model-prompt.ts +158 -0
- package/src/execution/engine/paths.ts +42 -0
- package/src/execution/engine/port.ts +153 -0
- package/src/execution/engine/registry.ts +133 -0
- package/src/execution/engine/routing.ts +218 -0
- package/src/execution/engine/types.ts +309 -0
- package/src/execution/execute-options-mapper.ts +115 -0
- package/src/execution/execution-record.ts +984 -0
- package/src/execution/finalize-record.ts +254 -0
- package/src/execution/finalized-marker.ts +70 -0
- package/src/execution/get-state-handshake.ts +104 -0
- package/src/execution/host-mode.ts +56 -0
- package/src/execution/idle-gc.ts +47 -0
- package/src/execution/lifecycle-manager.ts +513 -0
- package/src/execution/lifecycle-predicates.ts +65 -0
- package/src/execution/manifest-store.ts +256 -0
- package/src/execution/model-config-service.ts +250 -0
- package/src/execution/model-resolver.ts +248 -0
- package/src/execution/notifier.ts +372 -0
- package/src/execution/notify-ledger.ts +580 -0
- package/src/execution/output-collector.ts +228 -0
- package/src/execution/path-encoding.ts +60 -0
- package/src/execution/pi-invocation.ts +120 -0
- package/src/execution/record-entry.ts +132 -0
- package/src/execution/record-store.ts +1265 -0
- package/src/execution/relay-env.ts +37 -0
- package/src/execution/session-context-resolver.ts +64 -0
- package/src/execution/session-file-gc.ts +120 -0
- package/src/execution/session-pending.ts +170 -0
- package/src/execution/session-reconstructor.ts +678 -0
- package/src/execution/session-runner.ts +1789 -0
- package/src/execution/sessions-index.ts +304 -0
- package/src/execution/spawn-event-adapter.ts +363 -0
- package/src/execution/stdin-writer.ts +198 -0
- package/src/execution/stream-sink.ts +126 -0
- package/src/execution/subagent-service.ts +2268 -0
- package/src/execution/subprocess-agent-runner.ts +317 -0
- package/src/execution/temp-prompt.ts +62 -0
- package/src/execution/tombstone-store.ts +72 -0
- package/src/execution/turn-limiter.ts +102 -0
- package/src/execution/types.ts +1023 -0
- package/src/execution/ui-channels.ts +216 -0
- package/src/execution/ui-interaction-model.ts +48 -0
- package/src/execution/ui-request-handler-factory.ts +243 -0
- package/src/execution/ui-request-observability.ts +81 -0
- package/src/execution/ui-request-queue.ts +184 -0
- package/src/execution/worktree-manager.ts +688 -0
- package/src/execution/worktree-registry.ts +279 -0
- package/src/index.ts +87 -0
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +349 -0
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +202 -0
- package/src/orchestration/__tests__/agent-call-stream.test.ts +152 -0
- package/src/orchestration/__tests__/args-validator.test.ts +143 -0
- package/src/orchestration/__tests__/config-loader.test.ts +503 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +809 -0
- package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +404 -0
- package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +56 -0
- package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +166 -0
- package/src/orchestration/__tests__/execute-agent-call.test.ts +451 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +600 -0
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +96 -0
- package/src/orchestration/__tests__/lifecycle.test.ts +659 -0
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +95 -0
- package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +183 -0
- package/src/orchestration/__tests__/script-lint.test.ts +831 -0
- package/src/orchestration/__tests__/skill-discovery.test.ts +210 -0
- package/src/orchestration/__tests__/test-mocks.ts +197 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +368 -0
- package/src/orchestration/__tests__/worker-host.test.ts +120 -0
- package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +164 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +563 -0
- package/src/orchestration/__tests__/worker-script-builder.test.ts +309 -0
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +129 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +317 -0
- package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
- package/src/orchestration/agent-opts-resolver.ts +174 -0
- package/src/orchestration/args-validator.ts +127 -0
- package/src/orchestration/config-loader.ts +316 -0
- package/src/orchestration/error-recovery.ts +1018 -0
- package/src/orchestration/execute-agent-call.ts +256 -0
- package/src/orchestration/launcher.ts +455 -0
- package/src/orchestration/lifecycle.ts +399 -0
- package/src/orchestration/models/__tests__/budget.test.ts +307 -0
- package/src/orchestration/models/agent-call.ts +83 -0
- package/src/orchestration/models/budget.ts +118 -0
- package/src/orchestration/models/ports.ts +164 -0
- package/src/orchestration/models/run-runtime.ts +104 -0
- package/src/orchestration/models/run-spec.ts +83 -0
- package/src/orchestration/models/run-state.ts +44 -0
- package/src/orchestration/models/trace.ts +183 -0
- package/src/orchestration/models/types.ts +301 -0
- package/src/orchestration/models/workflow-run.ts +254 -0
- package/src/orchestration/models/workflow-script-registry.ts +35 -0
- package/src/orchestration/models/workflow-script.ts +117 -0
- package/src/orchestration/script-lint.ts +766 -0
- package/src/orchestration/skill-discovery.ts +135 -0
- package/src/orchestration/worker-handle.ts +115 -0
- package/src/orchestration/worker-host.ts +98 -0
- package/src/orchestration/worker-script-builder.ts +421 -0
- package/src/orchestration/workflow-files.ts +85 -0
- package/src/orchestration/workflow-script-registry-impl.ts +133 -0
- package/src/shared/__tests__/agent-ref.test.ts +34 -0
- package/src/shared/__tests__/meta-parser.test.ts +304 -0
- package/src/shared/__tests__/model-ref.test.ts +306 -0
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +288 -0
- package/src/shared/__tests__/resource-discovery.test.ts +749 -0
- package/src/shared/__tests__/resource-meta.test.ts +51 -0
- package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
- package/src/shared/__tests__/timer-delay.test.ts +61 -0
- package/src/shared/agent-event.ts +13 -0
- package/src/shared/agent-ref.ts +57 -0
- package/src/shared/meta-parser.ts +261 -0
- package/src/shared/model-ref.ts +286 -0
- package/src/shared/resource-discovery.ts +835 -0
- package/src/shared/resource-meta.ts +65 -0
- package/src/shared/schema-env.ts +44 -0
- package/src/shared/schema-jsonify.ts +58 -0
- package/src/shared/timer-delay.ts +54 -0
- package/src/shared/xml-injection.ts +35 -0
- package/workflows/README.md +81 -0
- package/workflows/_shared/agent-refs.cjs +40 -0
- package/workflows/chain.js +137 -0
- package/workflows/map-reduce.js +180 -0
- package/workflows/parallel.js +154 -0
- package/workflows/review-fix-loop-utils.cjs +1542 -0
- package/workflows/review-fix-loop.js +1605 -0
- package/workflows/scatter-gather.js +184 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// 测试框架:vitest
|
|
2
|
+
// 运行命令:npx vitest run src/orchestration/models/__tests__/budget.test.ts
|
|
3
|
+
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Budget,
|
|
8
|
+
CACHE_READ_WEIGHT,
|
|
9
|
+
CACHE_WRITE_WEIGHT,
|
|
10
|
+
INPUT_WEIGHT,
|
|
11
|
+
OUTPUT_WEIGHT,
|
|
12
|
+
} from "../budget.js";
|
|
13
|
+
import type { AgentUsage } from "../types.js";
|
|
14
|
+
|
|
15
|
+
/** 构造合法 AgentUsage,未指定字段补 0。 */
|
|
16
|
+
function usage(partial: Partial<AgentUsage>): AgentUsage {
|
|
17
|
+
return {
|
|
18
|
+
input: 0,
|
|
19
|
+
output: 0,
|
|
20
|
+
cacheRead: 0,
|
|
21
|
+
cacheWrite: 0,
|
|
22
|
+
cost: 0,
|
|
23
|
+
contextTokens: 0,
|
|
24
|
+
turns: 0,
|
|
25
|
+
...partial,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 构造"有毒" usage:刻意注入 undefined / NaN / Infinity,
|
|
31
|
+
* 模拟 provider 返回脏数据或 JSON 反序列化缺字段时的运行时场景。
|
|
32
|
+
*
|
|
33
|
+
* 单层 `as AgentUsage`:AgentUsage 全部必填字段(非全可选结构),
|
|
34
|
+
* 不触发 taste/no-unsafe-cast 的 structuralCast;此处刻意绕过类型系统
|
|
35
|
+
* 以验证 consume() 的运行时 NaN 守卫。
|
|
36
|
+
*/
|
|
37
|
+
function poisonedUsage(
|
|
38
|
+
overrides: Partial<Record<keyof AgentUsage, number | undefined>>,
|
|
39
|
+
): AgentUsage {
|
|
40
|
+
return {
|
|
41
|
+
input: 0,
|
|
42
|
+
output: 0,
|
|
43
|
+
cacheRead: 0,
|
|
44
|
+
cacheWrite: 0,
|
|
45
|
+
cost: 0,
|
|
46
|
+
contextTokens: 0,
|
|
47
|
+
turns: 0,
|
|
48
|
+
...overrides,
|
|
49
|
+
} as AgentUsage;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── consume:加权公式 ────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
describe("Budget.consume 加权公式", () => {
|
|
55
|
+
it(`纯 input → usedTokens = input × ${INPUT_WEIGHT}`, () => {
|
|
56
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
57
|
+
b.consume(usage({ input: 100 }));
|
|
58
|
+
expect(b.usedTokens).toBe(100);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it(`纯 output → usedTokens = output × ${OUTPUT_WEIGHT}(自回归开销最高)`, () => {
|
|
62
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
63
|
+
b.consume(usage({ output: 100 }));
|
|
64
|
+
expect(b.usedTokens).toBe(200);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it(`纯 cacheRead → usedTokens = cacheRead × ${CACHE_READ_WEIGHT}(命中缓存,开销极低)`, () => {
|
|
68
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
69
|
+
b.consume(usage({ cacheRead: 100 }));
|
|
70
|
+
expect(b.usedTokens).toBe(2);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it(`纯 cacheWrite → usedTokens = cacheWrite × ${CACHE_WRITE_WEIGHT}(首次写缓存不计 budget)`, () => {
|
|
74
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
75
|
+
b.consume(usage({ cacheWrite: 100 }));
|
|
76
|
+
expect(b.usedTokens).toBe(0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("混合四项加权累加(input×1 + output×2 + cacheRead×0.02 + cacheWrite×0)", () => {
|
|
80
|
+
const b = new Budget({ maxTokens: 10000 });
|
|
81
|
+
b.consume(
|
|
82
|
+
usage({ input: 100, output: 50, cacheRead: 1000, cacheWrite: 500 }),
|
|
83
|
+
);
|
|
84
|
+
// 100×1 + 50×2 + 1000×0.02 + 500×0 = 100 + 100 + 20 + 0 = 220
|
|
85
|
+
expect(b.usedTokens).toBe(220);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("cost 随 consume 累加", () => {
|
|
89
|
+
const b = new Budget();
|
|
90
|
+
b.consume(usage({ cost: 0.3 }));
|
|
91
|
+
expect(b.usedCost).toBe(0.3);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("多次 consume 累加(retry 间如实记录,不减)", () => {
|
|
95
|
+
const b = new Budget();
|
|
96
|
+
b.consume(usage({ input: 100, cost: 0.1 }));
|
|
97
|
+
b.consume(usage({ input: 50, output: 50, cost: 0.2 }));
|
|
98
|
+
// tokens: 100×1 + (50×1 + 50×2) = 250
|
|
99
|
+
expect(b.usedTokens).toBe(250);
|
|
100
|
+
// 0.1 + 0.2 浮点误差,用 toBeCloseTo
|
|
101
|
+
expect(b.usedCost).toBeCloseTo(0.3);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ── consume:NaN 守卫(TDD,预期主 agent 同步补源码守卫)──────
|
|
106
|
+
//
|
|
107
|
+
// 源码 consume() 当前无守卫:undefined/NaN/Infinity 进入加权公式会产出
|
|
108
|
+
// NaN/Infinity 污染 usedTokens,导致后续 isExceeded 永远命中。
|
|
109
|
+
// 以下测试断言「脏字段当 0 处理」——守卫补上前会失败,属预期临时状态。
|
|
110
|
+
|
|
111
|
+
describe("Budget.consume NaN 守卫(undefined/NaN/Infinity 当 0 处理)", () => {
|
|
112
|
+
it("input=undefined → usedTokens 不变(不当 NaN)", () => {
|
|
113
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
114
|
+
b.consume(poisonedUsage({ input: undefined }));
|
|
115
|
+
expect(b.usedTokens).toBe(0);
|
|
116
|
+
expect(Number.isNaN(b.usedTokens)).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("output=undefined → usedTokens 不变", () => {
|
|
120
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
121
|
+
b.consume(poisonedUsage({ output: undefined }));
|
|
122
|
+
expect(b.usedTokens).toBe(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("input=NaN → usedTokens 不变", () => {
|
|
126
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
127
|
+
b.consume(poisonedUsage({ input: Number.NaN }));
|
|
128
|
+
expect(b.usedTokens).toBe(0);
|
|
129
|
+
expect(Number.isNaN(b.usedTokens)).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("output=NaN → usedTokens 不变", () => {
|
|
133
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
134
|
+
b.consume(poisonedUsage({ output: Number.NaN }));
|
|
135
|
+
expect(b.usedTokens).toBe(0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("cacheRead=NaN / cacheWrite=NaN → usedTokens 不变", () => {
|
|
139
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
140
|
+
b.consume(
|
|
141
|
+
poisonedUsage({ cacheRead: Number.NaN, cacheWrite: Number.NaN }),
|
|
142
|
+
);
|
|
143
|
+
expect(b.usedTokens).toBe(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("input=Infinity → usedTokens 不变(Infinity 不烧穿预算)", () => {
|
|
147
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
148
|
+
b.consume(poisonedUsage({ input: Number.POSITIVE_INFINITY }));
|
|
149
|
+
expect(b.usedTokens).toBe(0);
|
|
150
|
+
expect(Number.isFinite(b.usedTokens)).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("合法字段与脏字段混合 → 仅累加合法部分(脏字段当 0)", () => {
|
|
154
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
155
|
+
// input=100(合法)+ output=undefined(脏,当 0)
|
|
156
|
+
b.consume(poisonedUsage({ input: 100, output: undefined }));
|
|
157
|
+
expect(b.usedTokens).toBe(100);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("已有累计后再遇脏数据 → usedTokens 保持原值不退化", () => {
|
|
161
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
162
|
+
b.consume(usage({ input: 50 })); // usedTokens = 50
|
|
163
|
+
b.consume(poisonedUsage({ input: undefined }));
|
|
164
|
+
expect(b.usedTokens).toBe(50);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("cost=undefined → usedCost 不当 NaN", () => {
|
|
168
|
+
const b = new Budget();
|
|
169
|
+
b.consume(poisonedUsage({ cost: undefined }));
|
|
170
|
+
expect(b.usedCost).toBe(0);
|
|
171
|
+
expect(Number.isNaN(b.usedCost)).toBe(false);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// ── incrementCallCount ───────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
describe("Budget.incrementCallCount", () => {
|
|
178
|
+
it("累加调用计数(每次 agent dispatch 后 +1)", () => {
|
|
179
|
+
const b = new Budget();
|
|
180
|
+
expect(b.totalCallCount).toBe(0);
|
|
181
|
+
b.incrementCallCount();
|
|
182
|
+
b.incrementCallCount();
|
|
183
|
+
b.incrementCallCount();
|
|
184
|
+
expect(b.totalCallCount).toBe(3);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// ── isExceeded ───────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
describe("Budget.isExceeded", () => {
|
|
191
|
+
it("maxTokens=1000, usedTokens=999 → 未超", () => {
|
|
192
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
193
|
+
b.consume(usage({ input: 999 }));
|
|
194
|
+
expect(b.isExceeded()).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("maxTokens=1000, usedTokens=1000 → 超限(边界 >=)", () => {
|
|
198
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
199
|
+
b.consume(usage({ input: 1000 }));
|
|
200
|
+
expect(b.isExceeded()).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("maxTokens=1000, usedTokens=1001 → 超限", () => {
|
|
204
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
205
|
+
b.consume(usage({ input: 1001 }));
|
|
206
|
+
expect(b.isExceeded()).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("maxTokens=0 → 视为不限制(守卫,首个 agent 完成不误判)", () => {
|
|
210
|
+
const b = new Budget({ maxTokens: 0 });
|
|
211
|
+
b.consume(usage({ input: 999999 }));
|
|
212
|
+
expect(b.isExceeded()).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("maxTokens undefined → 视为不限制", () => {
|
|
216
|
+
const b = new Budget();
|
|
217
|
+
b.consume(usage({ input: 999999 }));
|
|
218
|
+
expect(b.isExceeded()).toBe(false);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("maxCost 边界:usedCost >= maxCost → 超限", () => {
|
|
222
|
+
const b = new Budget({ maxCost: 1 });
|
|
223
|
+
b.consume(usage({ cost: 1 }));
|
|
224
|
+
expect(b.isExceeded()).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("maxCost=0 → 视为不限制", () => {
|
|
228
|
+
const b = new Budget({ maxCost: 0 });
|
|
229
|
+
b.consume(usage({ cost: 999 }));
|
|
230
|
+
expect(b.isExceeded()).toBe(false);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("maxCost undefined → 视为不限制", () => {
|
|
234
|
+
const b = new Budget();
|
|
235
|
+
b.consume(usage({ cost: 999 }));
|
|
236
|
+
expect(b.isExceeded()).toBe(false);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("token 与 cost 均未达阈值 → 不超限", () => {
|
|
240
|
+
const b = new Budget({ maxTokens: 1000, maxCost: 10 });
|
|
241
|
+
b.consume(usage({ input: 50, cost: 0.5 }));
|
|
242
|
+
expect(b.isExceeded()).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// ── remaining ────────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
describe("Budget.remaining", () => {
|
|
249
|
+
it("正常:maxTokens - usedTokens", () => {
|
|
250
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
251
|
+
b.consume(usage({ input: 300 }));
|
|
252
|
+
expect(b.remaining()).toBe(700);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("usedTokens > maxTokens → clamp 到 0(不返回负数)", () => {
|
|
256
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
257
|
+
b.consume(usage({ input: 1500 }));
|
|
258
|
+
expect(b.remaining()).toBe(0);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("maxTokens=1000, usedTokens=1000(恰好用尽)→ 0", () => {
|
|
262
|
+
const b = new Budget({ maxTokens: 1000 });
|
|
263
|
+
b.consume(usage({ input: 1000 }));
|
|
264
|
+
expect(b.remaining()).toBe(0);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("maxTokens undefined → undefined(视为不限制)", () => {
|
|
268
|
+
const b = new Budget();
|
|
269
|
+
expect(b.remaining()).toBeUndefined();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("maxTokens <= 0 → undefined(视为不限制)", () => {
|
|
273
|
+
const b = new Budget({ maxTokens: 0 });
|
|
274
|
+
expect(b.remaining()).toBeUndefined();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// ── 构造 ─────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
describe("Budget 构造", () => {
|
|
281
|
+
it("默认值全 0 / undefined", () => {
|
|
282
|
+
const b = new Budget();
|
|
283
|
+
expect(b.usedTokens).toBe(0);
|
|
284
|
+
expect(b.usedCost).toBe(0);
|
|
285
|
+
expect(b.totalCallCount).toBe(0);
|
|
286
|
+
expect(b.maxTokens).toBeUndefined();
|
|
287
|
+
expect(b.maxCost).toBeUndefined();
|
|
288
|
+
expect(b.maxTimeMs).toBeUndefined();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("从持久化数据重建(保留全部字段)", () => {
|
|
292
|
+
const b = new Budget({
|
|
293
|
+
maxTokens: 5000,
|
|
294
|
+
maxCost: 2,
|
|
295
|
+
maxTimeMs: 60000,
|
|
296
|
+
usedTokens: 100,
|
|
297
|
+
usedCost: 0.3,
|
|
298
|
+
totalCallCount: 7,
|
|
299
|
+
});
|
|
300
|
+
expect(b.maxTokens).toBe(5000);
|
|
301
|
+
expect(b.maxCost).toBe(2);
|
|
302
|
+
expect(b.maxTimeMs).toBe(60000);
|
|
303
|
+
expect(b.usedTokens).toBe(100);
|
|
304
|
+
expect(b.usedCost).toBe(0.3);
|
|
305
|
+
expect(b.totalCallCount).toBe(7);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Extension — AgentCall 实体
|
|
3
|
+
*
|
|
4
|
+
* 单次 agent 调用的数据 + 不变式守卫(D-12)。纯数据,无 execute 上帝方法——
|
|
5
|
+
* 执行编排(重试+预算+stale 检测)在 execute-agent-call.ts 的 free function。
|
|
6
|
+
*
|
|
7
|
+
* - 状态机:pending → running → done(不可逆)
|
|
8
|
+
* - markRunning 进入 running 并 attempts++(每次 retry 前调用)
|
|
9
|
+
* - markDone(result) 进入 done 并记录结果
|
|
10
|
+
* - traceNode 持有引用,但 AgentCall 不直接改其字段——trace 同步由
|
|
11
|
+
* Trace.update 负责(D-10 单一来源),AgentCall 只持有引用供 executeAgentCall 读取
|
|
12
|
+
*
|
|
13
|
+
* 层归属:Engine。
|
|
14
|
+
*
|
|
15
|
+
* 参考:domain-models.md §5(字段/不变式/设计决策)。
|
|
16
|
+
*/
|
|
17
|
+
import type { AgentCallOpts, AgentResult, ExecutionTraceNode } from "./types.ts";
|
|
18
|
+
|
|
19
|
+
/** AgentCall 生命周期状态。pending→running→done,不可逆。 */
|
|
20
|
+
export type AgentCallStatus = "pending" | "running" | "done";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* AgentCall 实体(在 RunState.calls Map 内)。
|
|
24
|
+
*
|
|
25
|
+
* 不变式:
|
|
26
|
+
* - status 转换严格 pending→running→done,反向抛错
|
|
27
|
+
* - done 时 result 必须已设置(markDone(result) 前置保证)
|
|
28
|
+
* - attempts 反映 dispatch 次数(markRunning 累加,含首次)
|
|
29
|
+
* - **无 execute 方法**(D-12:执行编排由 Engine executeAgentCall 函数承担)
|
|
30
|
+
*/
|
|
31
|
+
export class AgentCall {
|
|
32
|
+
readonly id: number;
|
|
33
|
+
readonly opts: AgentCallOpts;
|
|
34
|
+
status: AgentCallStatus = "pending";
|
|
35
|
+
attempts = 0;
|
|
36
|
+
result?: AgentResult;
|
|
37
|
+
/** Pi subprocess session ID(uuidv7,G-017 归此)。 */
|
|
38
|
+
sessionId?: string;
|
|
39
|
+
/** Session JSONL 绝对路径(finalizeCall 后从 result.sessionFile 填入,对齐 sessionId 模式)。 */
|
|
40
|
+
sessionFile?: string;
|
|
41
|
+
/** 与 Trace 共享的节点引用(D-10 单源)。AgentCall 不直接改其字段。 */
|
|
42
|
+
readonly traceNode: ExecutionTraceNode;
|
|
43
|
+
|
|
44
|
+
constructor(id: number, opts: AgentCallOpts, traceNode: ExecutionTraceNode) {
|
|
45
|
+
this.id = id;
|
|
46
|
+
this.opts = opts;
|
|
47
|
+
this.traceNode = traceNode;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 标记进入 running 状态(dispatch 前)。attempts++(含首次)。
|
|
52
|
+
* @throws 若已 done(不可重启)
|
|
53
|
+
*/
|
|
54
|
+
markRunning(): void {
|
|
55
|
+
if (this.status === "done") {
|
|
56
|
+
throw new Error(`AgentCall ${this.id} already done — cannot mark running`);
|
|
57
|
+
}
|
|
58
|
+
this.status = "running";
|
|
59
|
+
this.attempts += 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 标记完成(成功或失败均调用——result.error 区分)。
|
|
64
|
+
* @throws 若当前非 running(pending 不能直接跳 done,必须先 markRunning)
|
|
65
|
+
*/
|
|
66
|
+
markDone(result: AgentResult): void {
|
|
67
|
+
if (this.status !== "running") {
|
|
68
|
+
throw new Error(`AgentCall ${this.id} must be running to mark done (was ${this.status})`);
|
|
69
|
+
}
|
|
70
|
+
this.result = result;
|
|
71
|
+
this.status = "done";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 记录 pi subprocess session ID(dispatch 成功后)。 */
|
|
75
|
+
setSessionId(sessionId: string): void {
|
|
76
|
+
this.sessionId = sessionId;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 记录 session JSONL 绝对路径(finalizeCall 后,对齐 setSessionId 模式)。 */
|
|
80
|
+
setSessionFile(sessionFile: string): void {
|
|
81
|
+
this.sessionFile = sessionFile;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Extension — Budget 值对象
|
|
3
|
+
*
|
|
4
|
+
* Token / cost 预算值对象(D-12)。纯数据 + 不变式守卫,无副作用。
|
|
5
|
+
*
|
|
6
|
+
* maxTokens===0 视为不限制(守卫,避免首个 agent 完成误判 budget_limited)。
|
|
7
|
+
* (预算语义对齐 2026-08:soft-limit 常量(500 调用数预警)与 90% 阈值预警方法
|
|
8
|
+
* 已删——全库无生产消费方,仅测试锁定。)
|
|
9
|
+
*
|
|
10
|
+
* 层归属:Engine。
|
|
11
|
+
*
|
|
12
|
+
* 参考:domain-models.md §4(字段/不变式/操作)。
|
|
13
|
+
*/
|
|
14
|
+
import type { AgentUsage } from "./types.ts";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Budget 加权系数(token 口径)。
|
|
18
|
+
*
|
|
19
|
+
* usedTokens 不再是四项原始 token 简单求和,而是加权后的「等效消耗」,
|
|
20
|
+
* 反映不同 token 桶的真实计费/处理开销差异:
|
|
21
|
+
* - input(非缓存新增):首次见到、需完整处理的新内容,权重 1(基准)
|
|
22
|
+
* - cacheRead(命中缓存):读取历史,开销极低,按 1/50 折算(权重 0.02)
|
|
23
|
+
* - cacheWrite(首次写入缓存):本版本不计入 budget(权重 0)
|
|
24
|
+
* - output:模型自回归生成,开销最高,权重 2
|
|
25
|
+
*
|
|
26
|
+
* 这避免了长 session 中 cacheRead 随轮次单调累积导致 budget 被快速烧穿的失真
|
|
27
|
+
* (详见讨论:cacheRead 在 N 轮里被报 N 次,去重上下文只有一份)。
|
|
28
|
+
*/
|
|
29
|
+
export const INPUT_WEIGHT = 1;
|
|
30
|
+
export const CACHE_READ_WEIGHT = 0.02;
|
|
31
|
+
export const CACHE_WRITE_WEIGHT = 0;
|
|
32
|
+
export const OUTPUT_WEIGHT = 2;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Budget 值对象。
|
|
36
|
+
*
|
|
37
|
+
* 不变式(domain-models.md §4):
|
|
38
|
+
* - maxTokens > 0 守卫:maxTokens===0 或 undefined 视为不限制
|
|
39
|
+
* - maxCost > 0 守卫:同上
|
|
40
|
+
* - consume 只累加,不减;isExceeded 只读
|
|
41
|
+
* - 无回调字段——所有副作用由调用方在 consume 后查询决定
|
|
42
|
+
*/
|
|
43
|
+
export class Budget {
|
|
44
|
+
readonly maxTokens?: number;
|
|
45
|
+
readonly maxCost?: number;
|
|
46
|
+
readonly maxTimeMs?: number;
|
|
47
|
+
usedTokens = 0;
|
|
48
|
+
usedCost = 0;
|
|
49
|
+
/** 总调用计数(持久化/诊断用;execute-agent-call 每次 dispatch 后 increment)。 */
|
|
50
|
+
totalCallCount = 0;
|
|
51
|
+
|
|
52
|
+
constructor(opts: {
|
|
53
|
+
maxTokens?: number;
|
|
54
|
+
maxCost?: number;
|
|
55
|
+
maxTimeMs?: number;
|
|
56
|
+
usedTokens?: number;
|
|
57
|
+
usedCost?: number;
|
|
58
|
+
totalCallCount?: number;
|
|
59
|
+
} = {}) {
|
|
60
|
+
this.maxTokens = opts.maxTokens;
|
|
61
|
+
this.maxCost = opts.maxCost;
|
|
62
|
+
this.maxTimeMs = opts.maxTimeMs;
|
|
63
|
+
this.usedTokens = opts.usedTokens ?? 0;
|
|
64
|
+
this.usedCost = opts.usedCost ?? 0;
|
|
65
|
+
this.totalCallCount = opts.totalCallCount ?? 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 累加一次 agent 调用的 usage(加权口径)。
|
|
70
|
+
*
|
|
71
|
+
* 四项 token 按各自权重(INPUT/CACHE_READ/CACHE_WRITE/OUTPUT_WEIGHT)折算后求和,
|
|
72
|
+
* 而非原始 token 数直接相加。retry 间的真实消耗如实记录,避免预算被低估。
|
|
73
|
+
* 详见上方权重常量的口径说明。
|
|
74
|
+
*/
|
|
75
|
+
consume(usage: AgentUsage): void {
|
|
76
|
+
// NaN 守卫——非有限值当 0 处理,防 usedTokens 变 NaN 导致 isExceeded() 永远 false(预算限制失效)
|
|
77
|
+
const numOrZero = (v: number | undefined): number =>
|
|
78
|
+
typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
79
|
+
this.usedTokens +=
|
|
80
|
+
numOrZero(usage.input) * INPUT_WEIGHT +
|
|
81
|
+
numOrZero(usage.output) * OUTPUT_WEIGHT +
|
|
82
|
+
numOrZero(usage.cacheRead) * CACHE_READ_WEIGHT +
|
|
83
|
+
numOrZero(usage.cacheWrite) * CACHE_WRITE_WEIGHT;
|
|
84
|
+
this.usedCost += numOrZero(usage.cost);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 累加调用计数(每次 agent dispatch 后调用;持久化快照同步)。 */
|
|
88
|
+
incrementCallCount(): void {
|
|
89
|
+
this.totalCallCount += 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 是否超 token / cost 预算(FR-3)。
|
|
94
|
+
*
|
|
95
|
+
* maxTokens===0 或 undefined 视为不限制(守卫);
|
|
96
|
+
* maxCost===0 或 undefined 视为不限制。
|
|
97
|
+
* 时间预算(maxTimeMs)不由本方法判断——它是 wall-clock 约束,需参照 startedAt,
|
|
98
|
+
* 由 lifecycle 层的 scheduleTimeBudget(runWorkflow 内 setTimeout)
|
|
99
|
+
* 独立调度,到期 abortRun(doneReason="time_limited")。
|
|
100
|
+
*/
|
|
101
|
+
isExceeded(): boolean {
|
|
102
|
+
if (this.maxTokens !== undefined && this.maxTokens > 0 && this.usedTokens >= this.maxTokens) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return this.maxCost !== undefined && this.maxCost > 0 && this.usedCost >= this.maxCost;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 剩余 token 预算。maxTokens 未设或 ≤0 时返回 undefined(视为不限制)。
|
|
110
|
+
*
|
|
111
|
+
* 嵌套 workflow() 调用时由 executeNestedWorkflow 消费:子 run 的 budgetTokens
|
|
112
|
+
* 继承父 run 的剩余预算,实现父子预算隔离下的总量约束。
|
|
113
|
+
*/
|
|
114
|
+
remaining(): number | undefined {
|
|
115
|
+
if (this.maxTokens === undefined || this.maxTokens <= 0) return undefined;
|
|
116
|
+
return Math.max(0, this.maxTokens - this.usedTokens);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Extension — Engine Ports + 编排层共享类型
|
|
3
|
+
*
|
|
4
|
+
* 3 个注入 Port(AgentRunner / RunStore / WorkerHost)——Engine 定义、Infra 实现,
|
|
5
|
+
* 是真需要 mock 测试的依赖(子进程/文件系统/线程)。
|
|
6
|
+
*
|
|
7
|
+
* 编排层共享类型(WorkerHandlers / LifecycleDeps)——打破 lifecycle ↔
|
|
8
|
+
* error-recovery 循环依赖:2 个 engine 函数文件各自独立,共用同一组
|
|
9
|
+
* 依赖签名(D-12)。
|
|
10
|
+
*
|
|
11
|
+
* 层归属:Engine。零 infra 依赖(AC-1)。
|
|
12
|
+
*/
|
|
13
|
+
import type { StreamSink, SubagentStream } from "../../execution/stream-sink.ts";
|
|
14
|
+
import type { AgentEvent } from "../../shared/agent-event.ts";
|
|
15
|
+
import type { WorkerHandle } from "../worker-handle.ts";
|
|
16
|
+
import type { RunSpec } from "./run-spec.ts";
|
|
17
|
+
import type { AgentCallOpts, AgentResult } from "./types.ts";
|
|
18
|
+
import type { WorkflowRun } from "./workflow-run.ts";
|
|
19
|
+
|
|
20
|
+
// ── Port 1: AgentRunner ───────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Agent 子进程执行 port。Infra 实现:SubprocessAgentRunner。
|
|
24
|
+
*
|
|
25
|
+
* run 执行单次 agent 调用(委托 SubagentService.executeAndAwait),返回结构化结果。
|
|
26
|
+
* signal 用于 abort 传播。
|
|
27
|
+
*
|
|
28
|
+
* onEvent(可选):强类型 AgentEvent 回调,供调用方实时更新 live record 供 TUI 展示进度。
|
|
29
|
+
* 不传则不回调(向后兼容;现有调用点不传不受影响)。
|
|
30
|
+
*
|
|
31
|
+
* D-005: onEvent 签名从 raw Record<string,unknown> 升级为 AgentEvent——委托后不再有
|
|
32
|
+
* raw JSONL 中间层(executeAndAwait 直接出 AgentEvent,session-runner handleSdkEvent 出口)。
|
|
33
|
+
*/
|
|
34
|
+
export interface AgentRunner {
|
|
35
|
+
run(opts: AgentCallOpts, signal: AbortSignal, onEvent?: (event: AgentEvent) => void, stream?: SubagentStream): Promise<AgentResult>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Port 2: RunStore ──────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* WorkflowRun 持久化 port。Infra 实现:JsonlRunStore。
|
|
42
|
+
*
|
|
43
|
+
* save 在每次状态变更后持久化整个 WorkflowRun(聚合根);
|
|
44
|
+
* loadAll 在 session_start 时重水合(D-5:JSONL 不向后兼容旧 session,旧格式返回空)。
|
|
45
|
+
* stateFilePath 返回 run 状态文件的绝对路径(供 overlay/GUI 暴露给用户)。
|
|
46
|
+
*/
|
|
47
|
+
export interface RunStore {
|
|
48
|
+
save(run: WorkflowRun): Promise<void>;
|
|
49
|
+
loadAll(): Promise<WorkflowRun[]>;
|
|
50
|
+
/** 返回 run 状态快照文件的绝对路径:<sessionDir>/workflow-state/<runId>.jsonl */
|
|
51
|
+
stateFilePath(runId: string): string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Port 3: WorkerHost ────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Worker 线程启动 port。Infra 实现:WorkerHostImpl。
|
|
58
|
+
*
|
|
59
|
+
* start 创建一个 Worker thread 运行 workflow 脚本,返回 WorkerHandle。
|
|
60
|
+
* handlers 绑定 message/error/exit 回调(见 WorkerHandlers)。
|
|
61
|
+
*/
|
|
62
|
+
export interface WorkerHost {
|
|
63
|
+
start(
|
|
64
|
+
spec: RunSpec,
|
|
65
|
+
args: Record<string, unknown>,
|
|
66
|
+
handlers: WorkerHandlers,
|
|
67
|
+
): WorkerHandle;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── 编排层共享类型 1: WorkerHandlers ───────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Worker 线程事件回调集合——WorkerHost.start 的入参,由 lifecycle
|
|
74
|
+
* 构造并注入。2 个 engine 文件(lifecycle / error-recovery)共用此签名,
|
|
75
|
+
* 避免各自定义形状不一致的 handler bag(打破循环依赖)。
|
|
76
|
+
*
|
|
77
|
+
* 所有回调返回 Promise——允许 engine 层在回调内做 await persistState 等异步操作。
|
|
78
|
+
*/
|
|
79
|
+
export interface WorkerHandlers {
|
|
80
|
+
/** Worker → Main 的业务消息(agent-call / return / error / log)。 */
|
|
81
|
+
onMessage(raw: unknown): Promise<void>;
|
|
82
|
+
/** Worker 线程 uncaught error。 */
|
|
83
|
+
onError(err: Error): Promise<void>;
|
|
84
|
+
/** Worker 线程 exit(含 code,用于区分正常退出 vs 崩溃)。handle 用于竞态防护 G-025。 */
|
|
85
|
+
onExit(code: number, handle: WorkerHandle): Promise<void>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── 编排层共享类型 2: LifecycleDeps ────────────────────────────
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* lifecycle / error-recovery 2 个 engine 函数文件的共同依赖 bag。
|
|
92
|
+
*
|
|
93
|
+
* 取代旧 4 个 Context factory(errorHandlerContext / agentCallContext /
|
|
94
|
+
* budgetCallbacks / 旧 terminate bag,AC-2 目标)。函数签名 `(deps: LifecycleDeps, ...)`
|
|
95
|
+
* 让每个 free function 自包含依赖,无需 God Facade 中介。
|
|
96
|
+
*
|
|
97
|
+
* - store: 持久化(RunStore port)
|
|
98
|
+
* - workerHost: 启动 worker(WorkerHost port)
|
|
99
|
+
* - runner: 执行 agent(AgentRunner port)
|
|
100
|
+
* - runs: 内存中的活动 run 聚合根索引(runId → WorkflowRun),替代旧 6 张并行 map
|
|
101
|
+
* - onRunDone?: run 到达 done 终态时的回调(C-4 修复,可选)。由 Interface 层
|
|
102
|
+
* factory 注入(notifyDone —— 唤醒 parent agent 消费结果)。Engine 层不依赖
|
|
103
|
+
* Pi SDK,通过 callback 把完成信号外推到 Interface 层。所有 transition("done", ...)
|
|
104
|
+
* 路径(handleReturn / handleWorkerError / handleScriptError / abortRun /
|
|
105
|
+
* dispatchAgentCall budget 终止)调完 transition + save 后触发本回调。
|
|
106
|
+
*/
|
|
107
|
+
export interface LifecycleDeps {
|
|
108
|
+
store: RunStore;
|
|
109
|
+
workerHost: WorkerHost;
|
|
110
|
+
runner: AgentRunner;
|
|
111
|
+
runs: Map<string, WorkflowRun>;
|
|
112
|
+
/** run 到达 done 终态时的回调(C-4 修复,可选)。Interface 层注入 notifyDone。 */
|
|
113
|
+
onRunDone?: (run: WorkflowRun) => void;
|
|
114
|
+
/**
|
|
115
|
+
* 跨扩展事件总线(pending-notifications register/unregister 信号灯)。
|
|
116
|
+
*
|
|
117
|
+
* runWorkflow 启动时 emit pending:register;所有 transition("done") 路径 emit
|
|
118
|
+
* pending:unregister。两处均通过本端口(Engine 不直接依赖 Pi SDK)。可选——
|
|
119
|
+
* 无 pending-notifications 扩展时 no-op(向后兼容)。
|
|
120
|
+
*/
|
|
121
|
+
eventBus?: { emit(channel: string, data: unknown): void };
|
|
122
|
+
/**
|
|
123
|
+
* 调试日志端口(Engine 不直接依赖 Pi SDK)。Interface 层注入实现。
|
|
124
|
+
* 关键路径记录 run 启动、保存、pending 注册/注销,便于排查异步操作状态。
|
|
125
|
+
*/
|
|
126
|
+
log?: (level: "debug" | "info" | "warn" | "error", component: string, message: string, data?: unknown) => void;
|
|
127
|
+
/**
|
|
128
|
+
* D-12 regression fix (round-2 #2):rebuildRuntime 重新调度 run 级墙钟预算计时器。
|
|
129
|
+
*
|
|
130
|
+
* worker/script 错误重试走 replaceRuntime,旧 RunRuntime 的 release 会 clearTimeout
|
|
131
|
+
* 旧计时器(run-runtime.release)。新 runtime 必须重排 scheduleTimeBudget,否则带
|
|
132
|
+
* budgetTimeMs 的 run 命中一次错误重试后时间预算静默失效(直到下次 pause/resume 才重排)。
|
|
133
|
+
* 由 Interface 层 factory 注入——闭包捕获 deps,内部调 lifecycle.scheduleTimeBudget。
|
|
134
|
+
*
|
|
135
|
+
* 可选——旧测试 deps 不注入时 rebuildRuntime 不重排计时器(兼容,不影响无时间预算的 run)。
|
|
136
|
+
*/
|
|
137
|
+
scheduleTimeBudget?: (
|
|
138
|
+
runId: string,
|
|
139
|
+
budgetTimeMs: number,
|
|
140
|
+
) => ReturnType<typeof setTimeout> | undefined;
|
|
141
|
+
/**
|
|
142
|
+
* workflow() 嵌套调用回调(可选)。Worker 脚本内调 workflow(name, args) 时触发。
|
|
143
|
+
*
|
|
144
|
+
* 由 Interface 层 makeDeps 注入(闭包捕获 registry + deps)。Engine 层的
|
|
145
|
+
* error-recovery.handleWorkerMessage 收到 workflow-call 消息后调本回调,
|
|
146
|
+
* 拿到子 workflow 执行结果后 postMessage(workflow-result) 回 worker。
|
|
147
|
+
*
|
|
148
|
+
* 不注入时 workflow() 返回 error result(向后兼容,不影响非嵌套场景)。
|
|
149
|
+
*/
|
|
150
|
+
onWorkflowCall?: (
|
|
151
|
+
name: string,
|
|
152
|
+
args: Record<string, unknown>,
|
|
153
|
+
parentRun: WorkflowRun,
|
|
154
|
+
) => Promise<unknown>;
|
|
155
|
+
/**
|
|
156
|
+
* UI streaming sink(ctx.ui.setWidget),workflow agent call 创建 SubagentStream 用。
|
|
157
|
+
*
|
|
158
|
+
* 由 Interface 层 makeDeps 注入(从 SubagentService.getStreamSink() 取)。
|
|
159
|
+
* dispatchAgentCall 用它创建 SubagentStream(widgetKey=subagent-stream-<runId>-<stepIndex>),
|
|
160
|
+
* 使 workflow agent call 的 text_delta 走与 background subagent 相同的 streaming 链路。
|
|
161
|
+
* 可选——无 UI 模式(TUI/RPC 无 setWidget)时为 undefined,dispatchAgentCall 不创建 stream。
|
|
162
|
+
*/
|
|
163
|
+
streamSink?: StreamSink;
|
|
164
|
+
}
|