@warlock.js/ai 5.1.0 → 5.2.2
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/CHANGELOG.md +4 -0
- package/README.md +4 -0
- package/cjs/index.cjs +1 -1
- package/cjs/index.cjs.map +1 -1
- package/cjs/{magic-string.es-BoSa5xIt.cjs → magic-string.es-BQeqHJ-a.cjs} +22 -17
- package/cjs/magic-string.es-BQeqHJ-a.cjs.map +1 -0
- package/cjs/matcher-logic-07fFOz7r.cjs.map +1 -1
- package/cjs/{matchers-DnV47KR_.cjs → matchers-CINm4ojZ.cjs} +27 -27
- package/cjs/matchers-CINm4ojZ.cjs.map +1 -0
- package/esm/agent/agent-input-builder.mjs.map +1 -1
- package/esm/agent/agent-stream.d.mts.map +1 -1
- package/esm/agent/agent-stream.mjs.map +1 -1
- package/esm/agent/agent.d.mts.map +1 -1
- package/esm/agent/agent.mjs.map +1 -1
- package/esm/agent/json-stream-guard.mjs.map +1 -1
- package/esm/agent/signature.mjs.map +1 -1
- package/esm/agent/snapshot.mjs.map +1 -1
- package/esm/agent/spawn-sub-agent.d.mts.map +1 -1
- package/esm/batch/batch.d.mts.map +1 -1
- package/esm/batch/batch.mjs.map +1 -1
- package/esm/checkpoint/memory.d.mts.map +1 -1
- package/esm/checkpoint/pg.mjs.map +1 -1
- package/esm/checkpoint/redis.mjs.map +1 -1
- package/esm/config.d.mts.map +1 -1
- package/esm/eval/dataset.d.mts.map +1 -1
- package/esm/eval/dataset.mjs.map +1 -1
- package/esm/eval/eval-runner.d.mts.map +1 -1
- package/esm/eval/eval-runner.mjs.map +1 -1
- package/esm/eval/judge-scorer.d.mts.map +1 -1
- package/esm/eval/regression.d.mts.map +1 -1
- package/esm/eval/regression.mjs.map +1 -1
- package/esm/eval/report-json.d.mts.map +1 -1
- package/esm/eval/report-junit.mjs.map +1 -1
- package/esm/eval/scorers.d.mts.map +1 -1
- package/esm/eval/scorers.mjs.map +1 -1
- package/esm/guard/detectors/injection.mjs.map +1 -1
- package/esm/guard/detectors/moderation.mjs.map +1 -1
- package/esm/guard/detectors/pii.mjs.map +1 -1
- package/esm/guard/detectors/topic.mjs.map +1 -1
- package/esm/human/human-approval.mjs.map +1 -1
- package/esm/human/resume.d.mts.map +1 -1
- package/esm/human/stores/memory.d.mts.map +1 -1
- package/esm/human/stores/pg.mjs.map +1 -1
- package/esm/human/stores/redis.mjs.map +1 -1
- package/esm/image/image.mjs.map +1 -1
- package/esm/memory/derive-id.mjs.map +1 -1
- package/esm/memory/episodic-memory.mjs.map +1 -1
- package/esm/memory/memory.mjs.map +1 -1
- package/esm/memory/procedural-memory.mjs.map +1 -1
- package/esm/memory/semantic-memory.mjs.map +1 -1
- package/esm/memory/working-memory.mjs.map +1 -1
- package/esm/middleware/builtins/budget.mjs.map +1 -1
- package/esm/middleware/builtins/semantic-cache.mjs.map +1 -1
- package/esm/middleware/helpers/compose.d.mts.map +1 -1
- package/esm/middleware/helpers/for-tool.mjs.map +1 -1
- package/esm/middleware/pipeline.d.mts.map +1 -1
- package/esm/middleware/utils/extract-user-text.mjs.map +1 -1
- package/esm/middleware/utils/namespaced-state.d.mts.map +1 -1
- package/esm/mock/mock-agent.d.mts.map +1 -1
- package/esm/mock/mock-agent.mjs.map +1 -1
- package/esm/mock/mock-model.d.mts.map +1 -1
- package/esm/mock/mock-model.mjs.map +1 -1
- package/esm/mock/mock-router.d.mts.map +1 -1
- package/esm/model/fallback-model.mjs.map +1 -1
- package/esm/node_modules/{@jridgewell → .pnpm/@jridgewell_sourcemap-codec@1.6.0/node_modules/@jridgewell}/sourcemap-codec/dist/sourcemap-codec.mjs +19 -14
- package/esm/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_expect@4.1.10/node_modules/@vitest}/expect/dist/index.mjs +8 -8
- package/esm/node_modules/.pnpm/@vitest_expect@4.1.10/node_modules/@vitest/expect/dist/index.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_pretty-format@4.1.10/node_modules/@vitest}/pretty-format/dist/index.mjs +4 -4
- package/esm/node_modules/.pnpm/@vitest_pretty-format@4.1.10/node_modules/@vitest/pretty-format/dist/index.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_runner@4.1.10/node_modules/@vitest}/runner/dist/chunk-artifact.mjs +5 -5
- package/esm/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/chunk-artifact.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_snapshot@4.1.10/node_modules/@vitest}/snapshot/dist/index.mjs +7 -7
- package/esm/node_modules/.pnpm/@vitest_snapshot@4.1.10/node_modules/@vitest/snapshot/dist/index.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_spy@4.1.10/node_modules/@vitest}/spy/dist/index.mjs +1 -1
- package/esm/node_modules/.pnpm/@vitest_spy@4.1.10/node_modules/@vitest/spy/dist/index.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_utils@4.1.10/node_modules/@vitest}/utils/dist/chunk-pathe.M-eThtNZ.mjs +1 -1
- package/esm/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_utils@4.1.10/node_modules/@vitest}/utils/dist/diff.mjs +4 -4
- package/esm/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/diff.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_utils@4.1.10/node_modules/@vitest}/utils/dist/display.mjs +3 -3
- package/esm/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/display.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_utils@4.1.10/node_modules/@vitest}/utils/dist/error.mjs +2 -2
- package/esm/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/error.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_utils@4.1.10/node_modules/@vitest}/utils/dist/helpers.mjs +1 -1
- package/esm/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/helpers.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_utils@4.1.10/node_modules/@vitest}/utils/dist/offset.mjs +1 -1
- package/esm/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/offset.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_utils@4.1.10/node_modules/@vitest}/utils/dist/serialize.mjs +1 -1
- package/esm/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/serialize.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_utils@4.1.10/node_modules/@vitest}/utils/dist/source-map.mjs +1 -1
- package/esm/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/source-map.mjs.map +1 -0
- package/esm/node_modules/{@vitest → .pnpm/@vitest_utils@4.1.10/node_modules/@vitest}/utils/dist/timers.mjs +1 -1
- package/esm/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/timers.mjs.map +1 -0
- package/esm/node_modules/{chai → .pnpm/chai@6.2.2/node_modules/chai}/index.mjs +1 -1
- package/esm/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.mjs.map +1 -0
- package/esm/node_modules/{magic-string → .pnpm/magic-string@0.30.21/node_modules/magic-string}/dist/magic-string.es.mjs +3 -3
- package/esm/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.mjs.map +1 -0
- package/esm/node_modules/{tinyrainbow → .pnpm/tinyrainbow@3.1.1/node_modules/tinyrainbow}/dist/index.mjs +1 -1
- package/esm/node_modules/.pnpm/tinyrainbow@3.1.1/node_modules/tinyrainbow/dist/index.mjs.map +1 -0
- package/esm/node_modules/{vitest → .pnpm/vitest@4.1.10_@opentelemetr_3b60e89b8b51a25e87011ae54ec49250/node_modules/vitest}/dist/chunks/_commonjsHelpers.D26ty3Ew.mjs +1 -1
- package/esm/node_modules/.pnpm/vitest@4.1.10_@opentelemetr_3b60e89b8b51a25e87011ae54ec49250/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.mjs.map +1 -0
- package/esm/node_modules/{vitest → .pnpm/vitest@4.1.10_@opentelemetr_3b60e89b8b51a25e87011ae54ec49250/node_modules/vitest}/dist/chunks/rpc.MzXet3jl.mjs +1 -1
- package/esm/node_modules/.pnpm/vitest@4.1.10_@opentelemetr_3b60e89b8b51a25e87011ae54ec49250/node_modules/vitest/dist/chunks/rpc.MzXet3jl.mjs.map +1 -0
- package/esm/node_modules/{vitest → .pnpm/vitest@4.1.10_@opentelemetr_3b60e89b8b51a25e87011ae54ec49250/node_modules/vitest}/dist/chunks/test.DNmyFkvJ.mjs +11 -11
- package/esm/node_modules/.pnpm/vitest@4.1.10_@opentelemetr_3b60e89b8b51a25e87011ae54ec49250/node_modules/vitest/dist/chunks/test.DNmyFkvJ.mjs.map +1 -0
- package/esm/node_modules/{vitest → .pnpm/vitest@4.1.10_@opentelemetr_3b60e89b8b51a25e87011ae54ec49250/node_modules/vitest}/dist/chunks/utils.BX5Fg8C4.mjs +2 -2
- package/esm/node_modules/.pnpm/vitest@4.1.10_@opentelemetr_3b60e89b8b51a25e87011ae54ec49250/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.mjs.map +1 -0
- package/esm/object-stream/parse-partial-json.mjs.map +1 -1
- package/esm/object-stream/stream-object.d.mts.map +1 -1
- package/esm/object-stream/stream-object.mjs.map +1 -1
- package/esm/observe/observer-registry.d.mts.map +1 -1
- package/esm/orchestrator/as-tool.d.mts.map +1 -1
- package/esm/orchestrator/checkpoint.mjs.map +1 -1
- package/esm/orchestrator/compaction.mjs.map +1 -1
- package/esm/orchestrator/emitter.d.mts.map +1 -1
- package/esm/orchestrator/emitter.mjs.map +1 -1
- package/esm/orchestrator/execution.d.mts.map +1 -1
- package/esm/orchestrator/execution.mjs.map +1 -1
- package/esm/orchestrator/memory.mjs.map +1 -1
- package/esm/orchestrator/orchestrator-stream.d.mts.map +1 -1
- package/esm/orchestrator/orchestrator-stream.mjs.map +1 -1
- package/esm/orchestrator/orchestrator.d.mts.map +1 -1
- package/esm/orchestrator/orchestrator.mjs.map +1 -1
- package/esm/orchestrator/session-lock.d.mts.map +1 -1
- package/esm/orchestrator/signature.mjs.map +1 -1
- package/esm/planner/dag-scheduler.mjs.map +1 -1
- package/esm/planner/plan-prompt.mjs.map +1 -1
- package/esm/planner/planner-run.mjs.map +1 -1
- package/esm/planner/planner.d.mts.map +1 -1
- package/esm/planner/planner.mjs.map +1 -1
- package/esm/planner/signature.mjs.map +1 -1
- package/esm/planner/snapshot.mjs.map +1 -1
- package/esm/prompt/prompt-langfuse-sync.mjs.map +1 -1
- package/esm/prompt/prompt-validate.mjs.map +1 -1
- package/esm/prompt/prompt.mjs.map +1 -1
- package/esm/prompts/prompts-manager.d.mts.map +1 -1
- package/esm/prompts/prompts-manager.mjs.map +1 -1
- package/esm/prompts/prompts-validate.mjs.map +1 -1
- package/esm/rag/chunk/chunk.mjs.map +1 -1
- package/esm/rag/chunk/markdown.mjs.map +1 -1
- package/esm/rag/chunk/sentence.mjs.map +1 -1
- package/esm/rag/hybrid/bm25.mjs.map +1 -1
- package/esm/rag/hybrid/hybrid-rank.mjs.map +1 -1
- package/esm/rag/hybrid/rrf.mjs.map +1 -1
- package/esm/rag/loaders/load-html.mjs.map +1 -1
- package/esm/rag/loaders/load-pdf.d.mts.map +1 -1
- package/esm/rag/loaders/load-pdf.mjs.map +1 -1
- package/esm/rag/loaders/load-text.mjs.map +1 -1
- package/esm/rag/rerank/keyword-reranker.mjs.map +1 -1
- package/esm/rag/rerank/llm-reranker.mjs.map +1 -1
- package/esm/rag/retrieve.mjs.map +1 -1
- package/esm/rag/store/cache-vector-store.mjs.map +1 -1
- package/esm/rag/store/pg-vector-store.mjs.map +1 -1
- package/esm/rag/transforms/multi-query.mjs.map +1 -1
- package/esm/security/outbound-policy.mjs.map +1 -1
- package/esm/security/private-ip.mjs.map +1 -1
- package/esm/security/redact.d.mts.map +1 -1
- package/esm/security/redact.mjs.map +1 -1
- package/esm/serve/serve.d.mts.map +1 -1
- package/esm/serve/serve.mjs.map +1 -1
- package/esm/serve/stream-to-sse.d.mts.map +1 -1
- package/esm/skills/catalog.mjs.map +1 -1
- package/esm/skills/skills.mjs.map +1 -1
- package/esm/skills/sources/directory-source.mjs.map +1 -1
- package/esm/skills/sources/parse-frontmatter.mjs.map +1 -1
- package/esm/skills/sources/url-source.mjs.map +1 -1
- package/esm/skills/store/mock-skills-store.mjs.map +1 -1
- package/esm/skills/store/procedural-skill-store.mjs.map +1 -1
- package/esm/snapshot/memory.d.mts.map +1 -1
- package/esm/snapshot/pg.mjs.map +1 -1
- package/esm/speech/speech.mjs.map +1 -1
- package/esm/supervisor/as-tool.d.mts.map +1 -1
- package/esm/supervisor/cancellation.mjs.map +1 -1
- package/esm/supervisor/emitter.d.mts.map +1 -1
- package/esm/supervisor/emitter.mjs.map +1 -1
- package/esm/supervisor/entries.mjs.map +1 -1
- package/esm/supervisor/execution.d.mts.map +1 -1
- package/esm/supervisor/execution.mjs.map +1 -1
- package/esm/supervisor/fan-out.mjs.map +1 -1
- package/esm/supervisor/router-factory.mjs.map +1 -1
- package/esm/supervisor/router-prompt.mjs.map +1 -1
- package/esm/supervisor/signature.mjs.map +1 -1
- package/esm/supervisor/snapshot.mjs.map +1 -1
- package/esm/supervisor/supervisor-stream.d.mts.map +1 -1
- package/esm/supervisor/supervisor-stream.mjs.map +1 -1
- package/esm/supervisor/supervisor.d.mts.map +1 -1
- package/esm/supervisor/supervisor.mjs.map +1 -1
- package/esm/system-prompt/refined-system-prompt.d.mts.map +1 -1
- package/esm/system-prompt/refined-system-prompt.mjs.map +1 -1
- package/esm/system-prompt/system-prompt.d.mts.map +1 -1
- package/esm/system-prompt/system-prompt.mjs.map +1 -1
- package/esm/team/team.d.mts.map +1 -1
- package/esm/testing/matcher-logic.mjs.map +1 -1
- package/esm/testing/matchers.mjs +1 -1
- package/esm/testing/register-lazy.d.mts.map +1 -1
- package/esm/tool/executable-as-tool.d.mts.map +1 -1
- package/esm/tool/tool.d.mts.map +1 -1
- package/esm/tool/tool.mjs.map +1 -1
- package/esm/transcribe/audio-input.mjs.map +1 -1
- package/esm/transcribe/transcribe.mjs.map +1 -1
- package/esm/utils/extract-json-payload.mjs.map +1 -1
- package/esm/utils/generate-run-id.mjs.map +1 -1
- package/esm/utils/prepare-attachment-part.mjs.map +1 -1
- package/esm/utils/run-context.d.mts.map +1 -1
- package/esm/utils/safe-json-parse.d.mts.map +1 -1
- package/esm/vcr/cassette-io.mjs.map +1 -1
- package/esm/vcr/hash-request.mjs.map +1 -1
- package/esm/vcr/vcr.mjs.map +1 -1
- package/esm/workflow/cancellation.mjs.map +1 -1
- package/esm/workflow/emitter.mjs.map +1 -1
- package/esm/workflow/engine.mjs.map +1 -1
- package/esm/workflow/retry.mjs.map +1 -1
- package/esm/workflow/router.mjs.map +1 -1
- package/esm/workflow/signature.d.mts.map +1 -1
- package/esm/workflow/signature.mjs.map +1 -1
- package/esm/workflow/snapshot.mjs.map +1 -1
- package/esm/workflow/step-runner.mjs.map +1 -1
- package/esm/workflow/step.d.mts.map +1 -1
- package/esm/workflow/step.mjs.map +1 -1
- package/esm/workflow/workflow.d.mts.map +1 -1
- package/esm/workflow/workflow.mjs.map +1 -1
- package/llms-full.txt +2 -0
- package/package.json +7 -4
- package/skills/ai-basics/SKILL.md +2 -0
- package/cjs/magic-string.es-BoSa5xIt.cjs.map +0 -1
- package/cjs/matchers-DnV47KR_.cjs.map +0 -1
- package/esm/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map +0 -1
- package/esm/node_modules/@vitest/expect/dist/index.mjs.map +0 -1
- package/esm/node_modules/@vitest/pretty-format/dist/index.mjs.map +0 -1
- package/esm/node_modules/@vitest/runner/dist/chunk-artifact.mjs.map +0 -1
- package/esm/node_modules/@vitest/snapshot/dist/index.mjs.map +0 -1
- package/esm/node_modules/@vitest/spy/dist/index.mjs.map +0 -1
- package/esm/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.mjs.map +0 -1
- package/esm/node_modules/@vitest/utils/dist/diff.mjs.map +0 -1
- package/esm/node_modules/@vitest/utils/dist/display.mjs.map +0 -1
- package/esm/node_modules/@vitest/utils/dist/error.mjs.map +0 -1
- package/esm/node_modules/@vitest/utils/dist/helpers.mjs.map +0 -1
- package/esm/node_modules/@vitest/utils/dist/offset.mjs.map +0 -1
- package/esm/node_modules/@vitest/utils/dist/serialize.mjs.map +0 -1
- package/esm/node_modules/@vitest/utils/dist/source-map.mjs.map +0 -1
- package/esm/node_modules/@vitest/utils/dist/timers.mjs.map +0 -1
- package/esm/node_modules/chai/index.mjs.map +0 -1
- package/esm/node_modules/magic-string/dist/magic-string.es.mjs.map +0 -1
- package/esm/node_modules/tinyrainbow/dist/index.mjs.map +0 -1
- package/esm/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.mjs.map +0 -1
- package/esm/node_modules/vitest/dist/chunks/rpc.MzXet3jl.mjs.map +0 -1
- package/esm/node_modules/vitest/dist/chunks/test.DNmyFkvJ.mjs.map +0 -1
- package/esm/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.mjs.map +0 -1
- /package/esm/node_modules/{@vitest → .pnpm/@vitest_runner@4.1.10/node_modules/@vitest}/runner/dist/index.mjs +0 -0
- /package/esm/node_modules/{@vitest → .pnpm/@vitest_runner@4.1.10/node_modules/@vitest}/runner/dist/utils.mjs +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signature.mjs","names":[],"sources":["../../../../../../../ai/src/orchestrator/signature.ts"],"sourcesContent":["import type { OrchestratorConfig } from \"../contracts/orchestrator/orchestrator-config.type\";\nimport type { ResolvedIntentEntry } from \"../supervisor/entries\";\n\n/**\n * Shape of the `historyWindow` config a fingerprint records. A number\n * window is recorded as `\"number\"`; a callback window as `\"callback\"`;\n * an absent role as `null`. The window VALUE (the literal `5`, the\n * callback body) is deliberately excluded — only the structural choice\n * of windowing strategy per role drifts the signature (§10.1).\n */\ntype HistoryWindowRoleFingerprint = \"number\" | \"callback\" | null;\n\n/**\n * Deterministic structural fingerprint of an orchestrator definition\n * (orchestrator.md §10.1). Persisted on every checkpoint so Phase 2 can\n * refuse a turn when the live definition no longer matches the saved\n * session shape. Covers exactly the dispatch contract:\n *\n * - `name`.\n * - The `intents` map — each intent key + its resolved description +\n * the underlying unit's stable identity (agent name, workflow name +\n * signature, or a `\"callback\"` marker for dev-callback intents).\n * Reuses the supervisor's resolved-entry fingerprinting verbatim.\n * - `route` callback presence (its body is code, not data).\n * - `router` agent identity when LLM routing is configured.\n * - `evaluate` callback presence.\n * - `initialAgent` when set.\n * - `maxIterations`.\n * - The `iterate` flag — flipping single-dispatch to delegated\n * iteration is a semantic shape change.\n * - The `historyWindow` config SHAPE — which roles window and whether\n * each is a number or a callback (not the window value itself).\n *\n * Does NOT cover (§10.1): `version` (metadata only), `systemPrompt`\n * text, logger config, store identities, event handlers, or callback\n * function bodies (callbacks fingerprint as their presence/`\"callback\"`\n * marker only). The orchestrator signature does NOT aggregate the\n * internal supervisor's signature — that is a per-run concern delegated\n * to `supervisor.resume()`'s own drift check on `iterate: true`.\n *\n * @example\n * const signature = computeOrchestratorSignature(config, resolvedEntries);\n * // \"1a2b3c4d\" — 8-char FNV-1a hex, stable across process restarts.\n */\nexport function computeOrchestratorSignature(\n config: OrchestratorConfig<unknown>,\n entries: Map<string, ResolvedIntentEntry>,\n): string {\n const intentsFingerprint = [...entries.entries()]\n .sort(([first], [second]) => first.localeCompare(second))\n .map(([intent, entry]) => ({\n k: intent,\n d: entry.description,\n u: fingerprintUnit(entry),\n }));\n\n const fingerprint = {\n n: config.name,\n a: intentsFingerprint,\n r: resolveRouterName(config.router),\n rc: config.route ? 1 : 0,\n e: config.evaluate ? 1 : 0,\n i: config.initialAgent ?? null,\n m: config.maxIterations ?? null,\n it: config.iterate ? 1 : 0,\n hw: fingerprintHistoryWindow(config.historyWindow),\n };\n\n return hash(JSON.stringify(fingerprint));\n}\n\n/**\n * Router identity for the fingerprint. Accepts both the bare-agent\n * shorthand and the `{ agent, ... }` entry form, returning the agent's\n * name (or `null` when no router is configured). Mirrors the\n * supervisor's `resolveRouterName`.\n */\nfunction resolveRouterName(router: OrchestratorConfig<unknown>[\"router\"]): string | null {\n if (!router) {\n return null;\n }\n\n if (typeof (router as { execute?: unknown }).execute === \"function\") {\n return (router as { name?: string }).name ?? null;\n }\n\n return (router as { agent?: { name?: string } }).agent?.name ?? null;\n}\n\n/**\n * Structural fingerprint of the `historyWindow` config. Records the\n * windowing strategy per role (`\"number\"` / `\"callback\"` / `null`) so a\n * dev swapping a fixed-size window for a token-counting callback drifts\n * the signature, while tuning the window value (e.g. `5` → `8`) does\n * not. The window value is a runtime knob, not a shape change.\n */\nfunction fingerprintHistoryWindow(\n historyWindow: OrchestratorConfig<unknown>[\"historyWindow\"],\n): { router: HistoryWindowRoleFingerprint; agents: HistoryWindowRoleFingerprint } {\n return {\n router: fingerprintHistoryWindowRole(historyWindow?.router),\n agents: fingerprintHistoryWindowRole(historyWindow?.agents),\n };\n}\n\nfunction fingerprintHistoryWindowRole(\n window: number | ((...args: never[]) => unknown) | undefined,\n): HistoryWindowRoleFingerprint {\n if (window === undefined) {\n return null;\n }\n\n if (typeof window === \"function\") {\n return \"callback\";\n }\n\n return \"number\";\n}\n\n/**\n * Stable identity of one resolved intent's underlying unit. Agents\n * fingerprint by name; workflows by name + their own signature;\n * callbacks by a type marker only (their closure can't be hashed\n * deterministically, so drift covers add/remove/rename, not body\n * edits). Identical to the supervisor's `fingerprintUnit`.\n */\nfunction fingerprintUnit(entry: ResolvedIntentEntry): unknown {\n if (entry.type === \"callback\") {\n return { t: \"callback\" };\n }\n\n if (entry.type === \"workflow\") {\n const workflow = entry.unit;\n return { t: \"workflow\", n: workflow.name, s: workflow.signature };\n }\n\n return { t: \"agent\", n: entry.unit.name };\n}\n\n/**\n * FNV-1a 32-bit — the same hash `supervisor/signature.ts` and\n * `workflow/signature.ts` use. Deterministic, no crypto dependency,\n * cheap; signatures are 8-char hex.\n */\nfunction hash(input: string): string {\n let h = 0x811c9dc5;\n\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;\n }\n\n return h.toString(16).padStart(8, \"0\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,SAAgB,6BACd,QACA,SACQ;CACR,MAAM,qBAAqB,CAAC,GAAG,QAAQ,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"signature.mjs","names":[],"sources":["../../../../../../../ai/src/orchestrator/signature.ts"],"sourcesContent":["import type { OrchestratorConfig } from \"../contracts/orchestrator/orchestrator-config.type\";\nimport type { ResolvedIntentEntry } from \"../supervisor/entries\";\n\n/**\n * Shape of the `historyWindow` config a fingerprint records. A number\n * window is recorded as `\"number\"`; a callback window as `\"callback\"`;\n * an absent role as `null`. The window VALUE (the literal `5`, the\n * callback body) is deliberately excluded — only the structural choice\n * of windowing strategy per role drifts the signature (§10.1).\n */\ntype HistoryWindowRoleFingerprint = \"number\" | \"callback\" | null;\n\n/**\n * Deterministic structural fingerprint of an orchestrator definition\n * (orchestrator.md §10.1). Persisted on every checkpoint so Phase 2 can\n * refuse a turn when the live definition no longer matches the saved\n * session shape. Covers exactly the dispatch contract:\n *\n * - `name`.\n * - The `intents` map — each intent key + its resolved description +\n * the underlying unit's stable identity (agent name, workflow name +\n * signature, or a `\"callback\"` marker for dev-callback intents).\n * Reuses the supervisor's resolved-entry fingerprinting verbatim.\n * - `route` callback presence (its body is code, not data).\n * - `router` agent identity when LLM routing is configured.\n * - `evaluate` callback presence.\n * - `initialAgent` when set.\n * - `maxIterations`.\n * - The `iterate` flag — flipping single-dispatch to delegated\n * iteration is a semantic shape change.\n * - The `historyWindow` config SHAPE — which roles window and whether\n * each is a number or a callback (not the window value itself).\n *\n * Does NOT cover (§10.1): `version` (metadata only), `systemPrompt`\n * text, logger config, store identities, event handlers, or callback\n * function bodies (callbacks fingerprint as their presence/`\"callback\"`\n * marker only). The orchestrator signature does NOT aggregate the\n * internal supervisor's signature — that is a per-run concern delegated\n * to `supervisor.resume()`'s own drift check on `iterate: true`.\n *\n * @example\n * const signature = computeOrchestratorSignature(config, resolvedEntries);\n * // \"1a2b3c4d\" — 8-char FNV-1a hex, stable across process restarts.\n */\nexport function computeOrchestratorSignature(\n config: OrchestratorConfig<unknown>,\n entries: Map<string, ResolvedIntentEntry>,\n): string {\n const intentsFingerprint = [...entries.entries()]\n .sort(([first], [second]) => first.localeCompare(second))\n .map(([intent, entry]) => ({\n k: intent,\n d: entry.description,\n u: fingerprintUnit(entry),\n }));\n\n const fingerprint = {\n n: config.name,\n a: intentsFingerprint,\n r: resolveRouterName(config.router),\n rc: config.route ? 1 : 0,\n e: config.evaluate ? 1 : 0,\n i: config.initialAgent ?? null,\n m: config.maxIterations ?? null,\n it: config.iterate ? 1 : 0,\n hw: fingerprintHistoryWindow(config.historyWindow),\n };\n\n return hash(JSON.stringify(fingerprint));\n}\n\n/**\n * Router identity for the fingerprint. Accepts both the bare-agent\n * shorthand and the `{ agent, ... }` entry form, returning the agent's\n * name (or `null` when no router is configured). Mirrors the\n * supervisor's `resolveRouterName`.\n */\nfunction resolveRouterName(router: OrchestratorConfig<unknown>[\"router\"]): string | null {\n if (!router) {\n return null;\n }\n\n if (typeof (router as { execute?: unknown }).execute === \"function\") {\n return (router as { name?: string }).name ?? null;\n }\n\n return (router as { agent?: { name?: string } }).agent?.name ?? null;\n}\n\n/**\n * Structural fingerprint of the `historyWindow` config. Records the\n * windowing strategy per role (`\"number\"` / `\"callback\"` / `null`) so a\n * dev swapping a fixed-size window for a token-counting callback drifts\n * the signature, while tuning the window value (e.g. `5` → `8`) does\n * not. The window value is a runtime knob, not a shape change.\n */\nfunction fingerprintHistoryWindow(\n historyWindow: OrchestratorConfig<unknown>[\"historyWindow\"],\n): { router: HistoryWindowRoleFingerprint; agents: HistoryWindowRoleFingerprint } {\n return {\n router: fingerprintHistoryWindowRole(historyWindow?.router),\n agents: fingerprintHistoryWindowRole(historyWindow?.agents),\n };\n}\n\nfunction fingerprintHistoryWindowRole(\n window: number | ((...args: never[]) => unknown) | undefined,\n): HistoryWindowRoleFingerprint {\n if (window === undefined) {\n return null;\n }\n\n if (typeof window === \"function\") {\n return \"callback\";\n }\n\n return \"number\";\n}\n\n/**\n * Stable identity of one resolved intent's underlying unit. Agents\n * fingerprint by name; workflows by name + their own signature;\n * callbacks by a type marker only (their closure can't be hashed\n * deterministically, so drift covers add/remove/rename, not body\n * edits). Identical to the supervisor's `fingerprintUnit`.\n */\nfunction fingerprintUnit(entry: ResolvedIntentEntry): unknown {\n if (entry.type === \"callback\") {\n return { t: \"callback\" };\n }\n\n if (entry.type === \"workflow\") {\n const workflow = entry.unit;\n return { t: \"workflow\", n: workflow.name, s: workflow.signature };\n }\n\n return { t: \"agent\", n: entry.unit.name };\n}\n\n/**\n * FNV-1a 32-bit — the same hash `supervisor/signature.ts` and\n * `workflow/signature.ts` use. Deterministic, no crypto dependency,\n * cheap; signatures are 8-char hex.\n */\nfunction hash(input: string): string {\n let h = 0x811c9dc5;\n\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;\n }\n\n return h.toString(16).padStart(8, \"0\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,SAAgB,6BACd,QACA,SACQ;CACR,MAAM,qBAAqB,CAAC,GAAG,QAAQ,QAAQ,CAAC,EAC7C,MAAM,CAAC,QAAQ,CAAC,YAAY,MAAM,cAAc,MAAM,CAAC,EACvD,KAAK,CAAC,QAAQ,YAAY;EACzB,GAAG;EACH,GAAG,MAAM;EACT,GAAG,gBAAgB,KAAK;CAC1B,EAAE;CAEJ,MAAM,cAAc;EAClB,GAAG,OAAO;EACV,GAAG;EACH,GAAG,kBAAkB,OAAO,MAAM;EAClC,IAAI,OAAO,QAAQ,IAAI;EACvB,GAAG,OAAO,WAAW,IAAI;EACzB,GAAG,OAAO,gBAAgB;EAC1B,GAAG,OAAO,iBAAiB;EAC3B,IAAI,OAAO,UAAU,IAAI;EACzB,IAAI,yBAAyB,OAAO,aAAa;CACnD;CAEA,OAAO,KAAK,KAAK,UAAU,WAAW,CAAC;AACzC;;;;;;;AAQA,SAAS,kBAAkB,QAA8D;CACvF,IAAI,CAAC,QACH,OAAO;CAGT,IAAI,OAAQ,OAAiC,YAAY,YACvD,OAAQ,OAA6B,QAAQ;CAG/C,OAAQ,OAAyC,OAAO,QAAQ;AAClE;;;;;;;;AASA,SAAS,yBACP,eACgF;CAChF,OAAO;EACL,QAAQ,6BAA6B,eAAe,MAAM;EAC1D,QAAQ,6BAA6B,eAAe,MAAM;CAC5D;AACF;AAEA,SAAS,6BACP,QAC8B;CAC9B,IAAI,WAAW,QACb,OAAO;CAGT,IAAI,OAAO,WAAW,YACpB,OAAO;CAGT,OAAO;AACT;;;;;;;;AASA,SAAS,gBAAgB,OAAqC;CAC5D,IAAI,MAAM,SAAS,YACjB,OAAO,EAAE,GAAG,WAAW;CAGzB,IAAI,MAAM,SAAS,YAAY;EAC7B,MAAM,WAAW,MAAM;EACvB,OAAO;GAAE,GAAG;GAAY,GAAG,SAAS;GAAM,GAAG,SAAS;EAAU;CAClE;CAEA,OAAO;EAAE,GAAG;EAAS,GAAG,MAAM,KAAK;CAAK;AAC1C;;;;;;AAOA,SAAS,KAAK,OAAuB;CACnC,IAAI,IAAI;CAER,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,KAAK,MAAM,WAAW,CAAC;EACvB,IAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,SAAU;CACxE;CAEA,OAAO,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AACvC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dag-scheduler.mjs","names":[],"sources":["../../../../../../../ai/src/planner/dag-scheduler.ts"],"sourcesContent":["import type { PlannerStep } from \"../contracts/planner/planner-plan.type\";\nimport { PlannerPlanInvalidError } from \"../errors/planner-plan-invalid-error\";\n\n/**\n * One node in the planner's execution DAG — a plan step plus the\n * resolved structural metadata the scheduler needs to order it.\n *\n * `id` is the step's own `id` when present, falling back to the step's\n * array index stringified (exactly as {@link PlannerStep.id} documents).\n * `dependencies` is the resolved set of node ids this step waits on,\n * de-duplicated and self-references dropped.\n */\nexport type DagNode = {\n /** Stable id — the step's own `id`, or its array index as a string. */\n id: string;\n /** 0-based position of the step in the original plan array. */\n index: number;\n /** The plan step this node schedules. */\n step: PlannerStep;\n /** Resolved ids this step depends on (subset of the DAG's node ids). */\n dependencies: string[];\n};\n\n/**\n * The built execution DAG — the ordered node list plus the lookups the\n * scheduler walks. Ordering follows the original plan array so a\n * dependency-free plan executes in author order, level by level.\n */\nexport type PlannerDag = {\n /** Nodes in original plan order. */\n nodes: DagNode[];\n /** id → node, for dependency resolution and sink detection. */\n byId: Map<string, DagNode>;\n /** id → ids of the nodes that depend on it (reverse edges). */\n dependents: Map<string, string[]>;\n};\n\n/**\n * Build the execution DAG from a plan's steps.\n *\n * Each step's `id` (falling back to its array index) and its `dependsOn`\n * become an adjacency list. A `dependsOn` that names a step not in the\n * plan, or any dependency cycle, raises a typed\n * {@link PlannerPlanInvalidError} BEFORE any step runs — the same error\n * class `generatePlan` uses for an unusable plan, with forensic context.\n *\n * @throws PlannerPlanInvalidError on a duplicate id, an unknown\n * `dependsOn` target, or a cycle.\n */\nexport function buildDag(steps: PlannerStep[], plannerName = \"planner\"): PlannerDag {\n const nodes: DagNode[] = [];\n const byId = new Map<string, DagNode>();\n\n // Pass 1 — assign every step a stable id (own id or array index) and\n // index the nodes. Duplicate explicit ids are a malformed plan.\n for (let index = 0; index < steps.length; index++) {\n const step = steps[index] as PlannerStep;\n const id = step.id ?? String(index);\n\n if (byId.has(id)) {\n throw new PlannerPlanInvalidError(\n `ai.planner(\"${plannerName}\"): duplicate step id \"${id}\" in DAG plan`,\n { context: { id } },\n );\n }\n\n const node: DagNode = { id, index, step, dependencies: [] };\n nodes.push(node);\n byId.set(id, node);\n }\n\n // Pass 2 — resolve dependencies against the id set; reject unknowns,\n // dedupe, and drop self-references (a no-op edge, never a cycle).\n const dependents = new Map<string, string[]>();\n\n for (const node of nodes) {\n const seen = new Set<string>();\n\n for (const dependency of node.step.dependsOn ?? []) {\n if (dependency === node.id) {\n continue;\n }\n\n if (!byId.has(dependency)) {\n throw new PlannerPlanInvalidError(\n `ai.planner(\"${plannerName}\"): step \"${node.id}\" depends on unknown step \"${dependency}\"`,\n { context: { id: node.id, dependency } },\n );\n }\n\n if (seen.has(dependency)) {\n continue;\n }\n\n seen.add(dependency);\n node.dependencies.push(dependency);\n\n const reverse = dependents.get(dependency) ?? [];\n reverse.push(node.id);\n dependents.set(dependency, reverse);\n }\n }\n\n assertAcyclic(nodes, byId, plannerName);\n\n return { nodes, byId, dependents };\n}\n\n/**\n * Compute the next ready set: nodes not yet done whose every dependency\n * is in `completed`. Preserves original plan order so a level dispatches\n * deterministically. A node whose dependency is `unreachable` (a failed\n * or skipped ancestor) is NOT ready — it never becomes ready and is\n * recorded skipped by the caller.\n */\nexport function readyNodes(\n dag: PlannerDag,\n completed: ReadonlySet<string>,\n done: ReadonlySet<string>,\n): DagNode[] {\n return dag.nodes.filter(\n (node) =>\n !done.has(node.id) &&\n node.dependencies.every((dependency) => completed.has(dependency)),\n );\n}\n\n/**\n * The topological sink(s) — nodes nothing depends on. Used to define the\n * \"final output\" under parallelism: with an `output` schema set, a\n * single sink is the unambiguous final step; multiple sinks are a\n * convergence error the caller surfaces.\n */\nexport function sinkNodes(dag: PlannerDag): DagNode[] {\n return dag.nodes.filter((node) => (dag.dependents.get(node.id) ?? []).length === 0);\n}\n\n/**\n * Depth-first cycle detection over the dependency edges. A back-edge to\n * a node on the current recursion stack means a cycle — raised as a\n * typed {@link PlannerPlanInvalidError} naming the offending node.\n */\nfunction assertAcyclic(\n nodes: DagNode[],\n byId: Map<string, DagNode>,\n plannerName: string,\n): void {\n const VISITING = 1;\n const DONE = 2;\n const state = new Map<string, number>();\n\n const visit = (node: DagNode): void => {\n const current = state.get(node.id);\n\n if (current === DONE) {\n return;\n }\n\n if (current === VISITING) {\n throw new PlannerPlanInvalidError(\n `ai.planner(\"${plannerName}\"): dependency cycle detected at step \"${node.id}\"`,\n { context: { id: node.id } },\n );\n }\n\n state.set(node.id, VISITING);\n\n for (const dependency of node.dependencies) {\n visit(byId.get(dependency) as DagNode);\n }\n\n state.set(node.id, DONE);\n };\n\n for (const node of nodes) {\n visit(node);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAiDA,SAAgB,SAAS,OAAsB,cAAc,WAAuB;CAClF,MAAM,QAAmB,CAAC;CAC1B,MAAM,uBAAO,IAAI,IAAqB;CAItC,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS;EACjD,MAAM,OAAO,MAAM;EACnB,MAAM,KAAK,KAAK,MAAM,OAAO,KAAK;EAElC,IAAI,KAAK,IAAI,EAAE,GACb,MAAM,IAAI,wBACR,eAAe,YAAY,yBAAyB,GAAG,gBACvD,EAAE,SAAS,EAAE,GAAG,EAAE,CACpB;EAGF,MAAM,OAAgB;GAAE;GAAI;GAAO;GAAM,cAAc,CAAC;EAAE;EAC1D,MAAM,KAAK,IAAI;EACf,KAAK,IAAI,IAAI,IAAI;CACnB;CAIA,MAAM,6BAAa,IAAI,IAAsB;CAE7C,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,uBAAO,IAAI,IAAY;EAE7B,KAAK,MAAM,cAAc,KAAK,KAAK,aAAa,CAAC,GAAG;GAClD,IAAI,eAAe,KAAK,IACtB;GAGF,IAAI,CAAC,KAAK,IAAI,UAAU,GACtB,MAAM,IAAI,wBACR,eAAe,YAAY,YAAY,KAAK,GAAG,6BAA6B,WAAW,IACvF,EAAE,SAAS;IAAE,IAAI,KAAK;IAAI;GAAW,EAAE,CACzC;GAGF,IAAI,KAAK,IAAI,UAAU,GACrB;GAGF,KAAK,IAAI,UAAU;GACnB,KAAK,aAAa,KAAK,UAAU;GAEjC,MAAM,UAAU,WAAW,IAAI,UAAU,KAAK,CAAC;GAC/C,QAAQ,KAAK,KAAK,EAAE;GACpB,WAAW,IAAI,YAAY,OAAO;EACpC;CACF;CAEA,cAAc,OAAO,MAAM,WAAW;CAEtC,OAAO;EAAE;EAAO;EAAM;CAAW;AACnC;;;;;;;;AASA,SAAgB,WACd,KACA,WACA,MACW;CACX,OAAO,IAAI,MAAM,QACd,SACC,CAAC,KAAK,IAAI,KAAK,EAAE,KACjB,KAAK,aAAa,OAAO,eAAe,UAAU,IAAI,UAAU,CAAC,CACrE;AACF;;;;;;;AAQA,SAAgB,UAAU,KAA4B;CACpD,OAAO,IAAI,MAAM,QAAQ,UAAU,IAAI,WAAW,IAAI,KAAK,EAAE,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"dag-scheduler.mjs","names":[],"sources":["../../../../../../../ai/src/planner/dag-scheduler.ts"],"sourcesContent":["import type { PlannerStep } from \"../contracts/planner/planner-plan.type\";\nimport { PlannerPlanInvalidError } from \"../errors/planner-plan-invalid-error\";\n\n/**\n * One node in the planner's execution DAG — a plan step plus the\n * resolved structural metadata the scheduler needs to order it.\n *\n * `id` is the step's own `id` when present, falling back to the step's\n * array index stringified (exactly as {@link PlannerStep.id} documents).\n * `dependencies` is the resolved set of node ids this step waits on,\n * de-duplicated and self-references dropped.\n */\nexport type DagNode = {\n /** Stable id — the step's own `id`, or its array index as a string. */\n id: string;\n /** 0-based position of the step in the original plan array. */\n index: number;\n /** The plan step this node schedules. */\n step: PlannerStep;\n /** Resolved ids this step depends on (subset of the DAG's node ids). */\n dependencies: string[];\n};\n\n/**\n * The built execution DAG — the ordered node list plus the lookups the\n * scheduler walks. Ordering follows the original plan array so a\n * dependency-free plan executes in author order, level by level.\n */\nexport type PlannerDag = {\n /** Nodes in original plan order. */\n nodes: DagNode[];\n /** id → node, for dependency resolution and sink detection. */\n byId: Map<string, DagNode>;\n /** id → ids of the nodes that depend on it (reverse edges). */\n dependents: Map<string, string[]>;\n};\n\n/**\n * Build the execution DAG from a plan's steps.\n *\n * Each step's `id` (falling back to its array index) and its `dependsOn`\n * become an adjacency list. A `dependsOn` that names a step not in the\n * plan, or any dependency cycle, raises a typed\n * {@link PlannerPlanInvalidError} BEFORE any step runs — the same error\n * class `generatePlan` uses for an unusable plan, with forensic context.\n *\n * @throws PlannerPlanInvalidError on a duplicate id, an unknown\n * `dependsOn` target, or a cycle.\n */\nexport function buildDag(steps: PlannerStep[], plannerName = \"planner\"): PlannerDag {\n const nodes: DagNode[] = [];\n const byId = new Map<string, DagNode>();\n\n // Pass 1 — assign every step a stable id (own id or array index) and\n // index the nodes. Duplicate explicit ids are a malformed plan.\n for (let index = 0; index < steps.length; index++) {\n const step = steps[index] as PlannerStep;\n const id = step.id ?? String(index);\n\n if (byId.has(id)) {\n throw new PlannerPlanInvalidError(\n `ai.planner(\"${plannerName}\"): duplicate step id \"${id}\" in DAG plan`,\n { context: { id } },\n );\n }\n\n const node: DagNode = { id, index, step, dependencies: [] };\n nodes.push(node);\n byId.set(id, node);\n }\n\n // Pass 2 — resolve dependencies against the id set; reject unknowns,\n // dedupe, and drop self-references (a no-op edge, never a cycle).\n const dependents = new Map<string, string[]>();\n\n for (const node of nodes) {\n const seen = new Set<string>();\n\n for (const dependency of node.step.dependsOn ?? []) {\n if (dependency === node.id) {\n continue;\n }\n\n if (!byId.has(dependency)) {\n throw new PlannerPlanInvalidError(\n `ai.planner(\"${plannerName}\"): step \"${node.id}\" depends on unknown step \"${dependency}\"`,\n { context: { id: node.id, dependency } },\n );\n }\n\n if (seen.has(dependency)) {\n continue;\n }\n\n seen.add(dependency);\n node.dependencies.push(dependency);\n\n const reverse = dependents.get(dependency) ?? [];\n reverse.push(node.id);\n dependents.set(dependency, reverse);\n }\n }\n\n assertAcyclic(nodes, byId, plannerName);\n\n return { nodes, byId, dependents };\n}\n\n/**\n * Compute the next ready set: nodes not yet done whose every dependency\n * is in `completed`. Preserves original plan order so a level dispatches\n * deterministically. A node whose dependency is `unreachable` (a failed\n * or skipped ancestor) is NOT ready — it never becomes ready and is\n * recorded skipped by the caller.\n */\nexport function readyNodes(\n dag: PlannerDag,\n completed: ReadonlySet<string>,\n done: ReadonlySet<string>,\n): DagNode[] {\n return dag.nodes.filter(\n (node) =>\n !done.has(node.id) &&\n node.dependencies.every((dependency) => completed.has(dependency)),\n );\n}\n\n/**\n * The topological sink(s) — nodes nothing depends on. Used to define the\n * \"final output\" under parallelism: with an `output` schema set, a\n * single sink is the unambiguous final step; multiple sinks are a\n * convergence error the caller surfaces.\n */\nexport function sinkNodes(dag: PlannerDag): DagNode[] {\n return dag.nodes.filter((node) => (dag.dependents.get(node.id) ?? []).length === 0);\n}\n\n/**\n * Depth-first cycle detection over the dependency edges. A back-edge to\n * a node on the current recursion stack means a cycle — raised as a\n * typed {@link PlannerPlanInvalidError} naming the offending node.\n */\nfunction assertAcyclic(\n nodes: DagNode[],\n byId: Map<string, DagNode>,\n plannerName: string,\n): void {\n const VISITING = 1;\n const DONE = 2;\n const state = new Map<string, number>();\n\n const visit = (node: DagNode): void => {\n const current = state.get(node.id);\n\n if (current === DONE) {\n return;\n }\n\n if (current === VISITING) {\n throw new PlannerPlanInvalidError(\n `ai.planner(\"${plannerName}\"): dependency cycle detected at step \"${node.id}\"`,\n { context: { id: node.id } },\n );\n }\n\n state.set(node.id, VISITING);\n\n for (const dependency of node.dependencies) {\n visit(byId.get(dependency) as DagNode);\n }\n\n state.set(node.id, DONE);\n };\n\n for (const node of nodes) {\n visit(node);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAiDA,SAAgB,SAAS,OAAsB,cAAc,WAAuB;CAClF,MAAM,QAAmB,CAAC;CAC1B,MAAM,uBAAO,IAAI,IAAqB;CAItC,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS;EACjD,MAAM,OAAO,MAAM;EACnB,MAAM,KAAK,KAAK,MAAM,OAAO,KAAK;EAElC,IAAI,KAAK,IAAI,EAAE,GACb,MAAM,IAAI,wBACR,eAAe,YAAY,yBAAyB,GAAG,gBACvD,EAAE,SAAS,EAAE,GAAG,EAAE,CACpB;EAGF,MAAM,OAAgB;GAAE;GAAI;GAAO;GAAM,cAAc,CAAC;EAAE;EAC1D,MAAM,KAAK,IAAI;EACf,KAAK,IAAI,IAAI,IAAI;CACnB;CAIA,MAAM,6BAAa,IAAI,IAAsB;CAE7C,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,uBAAO,IAAI,IAAY;EAE7B,KAAK,MAAM,cAAc,KAAK,KAAK,aAAa,CAAC,GAAG;GAClD,IAAI,eAAe,KAAK,IACtB;GAGF,IAAI,CAAC,KAAK,IAAI,UAAU,GACtB,MAAM,IAAI,wBACR,eAAe,YAAY,YAAY,KAAK,GAAG,6BAA6B,WAAW,IACvF,EAAE,SAAS;IAAE,IAAI,KAAK;IAAI;GAAW,EAAE,CACzC;GAGF,IAAI,KAAK,IAAI,UAAU,GACrB;GAGF,KAAK,IAAI,UAAU;GACnB,KAAK,aAAa,KAAK,UAAU;GAEjC,MAAM,UAAU,WAAW,IAAI,UAAU,KAAK,CAAC;GAC/C,QAAQ,KAAK,KAAK,EAAE;GACpB,WAAW,IAAI,YAAY,OAAO;EACpC;CACF;CAEA,cAAc,OAAO,MAAM,WAAW;CAEtC,OAAO;EAAE;EAAO;EAAM;CAAW;AACnC;;;;;;;;AASA,SAAgB,WACd,KACA,WACA,MACW;CACX,OAAO,IAAI,MAAM,QACd,SACC,CAAC,KAAK,IAAI,KAAK,EAAE,KACjB,KAAK,aAAa,OAAO,eAAe,UAAU,IAAI,UAAU,CAAC,CACrE;AACF;;;;;;;AAQA,SAAgB,UAAU,KAA4B;CACpD,OAAO,IAAI,MAAM,QAAQ,UAAU,IAAI,WAAW,IAAI,KAAK,EAAE,KAAK,CAAC,GAAG,WAAW,CAAC;AACpF;;;;;;AAOA,SAAS,cACP,OACA,MACA,aACM;CACN,MAAM,WAAW;CACjB,MAAM,OAAO;CACb,MAAM,wBAAQ,IAAI,IAAoB;CAEtC,MAAM,SAAS,SAAwB;EACrC,MAAM,UAAU,MAAM,IAAI,KAAK,EAAE;EAEjC,IAAI,YAAY,MACd;EAGF,IAAI,YAAY,UACd,MAAM,IAAI,wBACR,eAAe,YAAY,yCAAyC,KAAK,GAAG,IAC5E,EAAE,SAAS,EAAE,IAAI,KAAK,GAAG,EAAE,CAC7B;EAGF,MAAM,IAAI,KAAK,IAAI,QAAQ;EAE3B,KAAK,MAAM,cAAc,KAAK,cAC5B,MAAM,KAAK,IAAI,UAAU,CAAY;EAGvC,MAAM,IAAI,KAAK,IAAI,IAAI;CACzB;CAEA,KAAK,MAAM,QAAQ,OACjB,MAAM,IAAI;AAEd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan-prompt.mjs","names":[],"sources":["../../../../../../../ai/src/planner/plan-prompt.ts"],"sourcesContent":["import type { PlannerCapability } from \"../contracts/planner/planner-capability.type\";\nimport type { SystemPromptContract } from \"../contracts/system-prompt.contract\";\n\n/**\n * Assemble the plan-generation system prompt: optional caller framing on\n * top, then the mechanical block listing every capability + its\n * description and the rules for emitting an ordered plan.\n *\n * Runs once at factory time (the capability set is fixed for the\n * planner's lifetime) — the produced string is baked onto the internal\n * planning agent.\n */\nexport function buildPlanSystemPrompt(\n capabilities: PlannerCapability[],\n maxSteps: number,\n prefix: SystemPromptContract | string | undefined,\n dag = false,\n): string {\n const capabilityLines = capabilities.map(\n (capability) => `- ${capability.name}: ${capability.description}`,\n );\n\n const sections: string[] = [];\n const resolvedPrefix = resolvePrefix(prefix);\n\n if (resolvedPrefix && resolvedPrefix.trim().length > 0) {\n sections.push(resolvedPrefix.trim(), \"\");\n }\n\n sections.push(\n \"You are a planner. Break the user's goal into an ordered sequence of steps,\",\n \"each one dispatching exactly one of the available capabilities below.\",\n \"\",\n \"Available capabilities:\",\n ...capabilityLines,\n \"\",\n \"Rules:\",\n `- Produce at most ${maxSteps} steps.`,\n \"- Each step's `capability` must be exactly one name from the list above.\",\n \"- Each step's `input` is the concrete instruction passed to that capability.\",\n \"- Order the steps so each builds on the outputs of the ones before it.\",\n \"- Never invent a capability name that is not listed.\",\n \"- Keep the plan minimal — only the steps actually needed to satisfy the goal.\",\n );\n\n if (dag) {\n // DAG mode — the runtime schedules independent steps in parallel off\n // `dependsOn`, so the model should declare dependencies explicitly\n // rather than relying purely on array order.\n sections.push(\n \"- Give each step a stable `id` and list the ids it builds on in `dependsOn`.\",\n \"- Steps with no `dependsOn` between them run in PARALLEL — only add a\",\n \" dependency when a step genuinely needs an earlier step's output.\",\n \"- The plan must converge: avoid dependency cycles.\",\n );\n }\n\n return sections.join(\"\\n\");\n}\n\n/**\n * Resolve a caller-supplied `systemPrompt` (string or contract) to plain\n * text. Returns `undefined` when none was supplied.\n */\nfunction resolvePrefix(prompt: SystemPromptContract | string | undefined): string | undefined {\n if (!prompt) {\n return undefined;\n }\n\n return typeof prompt === \"string\" ? prompt : prompt.resolve();\n}\n"],"mappings":";;;;;;;;;;AAYA,SAAgB,sBACd,cACA,UACA,QACA,MAAM,OACE;CACR,MAAM,kBAAkB,aAAa,KAClC,eAAe,KAAK,WAAW,KAAK,IAAI,WAAW,aACtD;CAEA,MAAM,WAAqB,CAAC;CAC5B,MAAM,iBAAiB,cAAc,MAAM;CAE3C,IAAI,kBAAkB,eAAe,KAAK,
|
|
1
|
+
{"version":3,"file":"plan-prompt.mjs","names":[],"sources":["../../../../../../../ai/src/planner/plan-prompt.ts"],"sourcesContent":["import type { PlannerCapability } from \"../contracts/planner/planner-capability.type\";\nimport type { SystemPromptContract } from \"../contracts/system-prompt.contract\";\n\n/**\n * Assemble the plan-generation system prompt: optional caller framing on\n * top, then the mechanical block listing every capability + its\n * description and the rules for emitting an ordered plan.\n *\n * Runs once at factory time (the capability set is fixed for the\n * planner's lifetime) — the produced string is baked onto the internal\n * planning agent.\n */\nexport function buildPlanSystemPrompt(\n capabilities: PlannerCapability[],\n maxSteps: number,\n prefix: SystemPromptContract | string | undefined,\n dag = false,\n): string {\n const capabilityLines = capabilities.map(\n (capability) => `- ${capability.name}: ${capability.description}`,\n );\n\n const sections: string[] = [];\n const resolvedPrefix = resolvePrefix(prefix);\n\n if (resolvedPrefix && resolvedPrefix.trim().length > 0) {\n sections.push(resolvedPrefix.trim(), \"\");\n }\n\n sections.push(\n \"You are a planner. Break the user's goal into an ordered sequence of steps,\",\n \"each one dispatching exactly one of the available capabilities below.\",\n \"\",\n \"Available capabilities:\",\n ...capabilityLines,\n \"\",\n \"Rules:\",\n `- Produce at most ${maxSteps} steps.`,\n \"- Each step's `capability` must be exactly one name from the list above.\",\n \"- Each step's `input` is the concrete instruction passed to that capability.\",\n \"- Order the steps so each builds on the outputs of the ones before it.\",\n \"- Never invent a capability name that is not listed.\",\n \"- Keep the plan minimal — only the steps actually needed to satisfy the goal.\",\n );\n\n if (dag) {\n // DAG mode — the runtime schedules independent steps in parallel off\n // `dependsOn`, so the model should declare dependencies explicitly\n // rather than relying purely on array order.\n sections.push(\n \"- Give each step a stable `id` and list the ids it builds on in `dependsOn`.\",\n \"- Steps with no `dependsOn` between them run in PARALLEL — only add a\",\n \" dependency when a step genuinely needs an earlier step's output.\",\n \"- The plan must converge: avoid dependency cycles.\",\n );\n }\n\n return sections.join(\"\\n\");\n}\n\n/**\n * Resolve a caller-supplied `systemPrompt` (string or contract) to plain\n * text. Returns `undefined` when none was supplied.\n */\nfunction resolvePrefix(prompt: SystemPromptContract | string | undefined): string | undefined {\n if (!prompt) {\n return undefined;\n }\n\n return typeof prompt === \"string\" ? prompt : prompt.resolve();\n}\n"],"mappings":";;;;;;;;;;AAYA,SAAgB,sBACd,cACA,UACA,QACA,MAAM,OACE;CACR,MAAM,kBAAkB,aAAa,KAClC,eAAe,KAAK,WAAW,KAAK,IAAI,WAAW,aACtD;CAEA,MAAM,WAAqB,CAAC;CAC5B,MAAM,iBAAiB,cAAc,MAAM;CAE3C,IAAI,kBAAkB,eAAe,KAAK,EAAE,SAAS,GACnD,SAAS,KAAK,eAAe,KAAK,GAAG,EAAE;CAGzC,SAAS,KACP,+EACA,yEACA,IACA,2BACA,GAAG,iBACH,IACA,UACA,qBAAqB,SAAS,UAC9B,4EACA,gFACA,0EACA,wDACA,+EACF;CAEA,IAAI,KAIF,SAAS,KACP,gFACA,yEACA,sEACA,oDACF;CAGF,OAAO,SAAS,KAAK,IAAI;AAC3B;;;;;AAMA,SAAS,cAAc,QAAuE;CAC5F,IAAI,CAAC,QACH;CAGF,OAAO,OAAO,WAAW,WAAW,SAAS,OAAO,QAAQ;AAC9D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-run.mjs","names":[],"sources":["../../../../../../../ai/src/planner/planner-run.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { log } from \"@warlock.js/logger\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport type { PlannerCapability } from \"../contracts/planner/planner-capability.type\";\nimport type { PlannerConfig } from \"../contracts/planner/planner-config.type\";\nimport type {\n PlannerExecuteOptions,\n PlannerStepDirective,\n} from \"../contracts/planner/planner-execute-options.type\";\nimport type { PlannerPlan, PlannerStep } from \"../contracts/planner/planner-plan.type\";\nimport type {\n PlannerReport,\n PlannerResult,\n PlannerStepSnapshot,\n} from \"../contracts/planner/planner-result.type\";\nimport type {\n PlannerSnapshot,\n PlannerSnapshotStatus,\n} from \"../contracts/planner/planner-snapshot.type\";\nimport type { BaseReport } from \"../contracts/result/base-report.type\";\nimport { REPORT_SCHEMA_VERSION } from \"../contracts/result/base-report.type\";\nimport type { BaseResult } from \"../contracts/result/base-result.type\";\nimport type { Usage } from \"../contracts/result/usage.type\";\nimport { AIError } from \"../errors/ai-error\";\nimport { PlannerCancelledError } from \"../errors/planner-cancelled-error\";\nimport { PlannerFailedError } from \"../errors/planner-failed-error\";\nimport { PlannerPlanInvalidError } from \"../errors/planner-plan-invalid-error\";\nimport { SchemaValidationError } from \"../errors/schema-validation-error\";\nimport { notifyObservers } from \"../observe/resolve-observers\";\nimport { accumulateCost } from \"../utils/compute-cost\";\nimport { generateRunId } from \"../utils/generate-run-id\";\nimport { captureChildReport, withoutRunFrame } from \"../utils/run-context\";\nimport { stampReportLineage } from \"../utils/stamp-report-lineage\";\nimport type { DagNode, PlannerDag } from \"./dag-scheduler\";\nimport { buildDag, readyNodes, sinkNodes } from \"./dag-scheduler\";\nimport { planSchema } from \"./plan-schema\";\nimport {\n deletePlannerSnapshot,\n persistPlannerSnapshot,\n} from \"./snapshot\";\n\n/**\n * Construction args for one {@link PlannerRun}. Carries everything the\n * factory resolved once (config, capability map, signature, planning\n * agent) plus the per-call goal and options.\n */\nexport type PlannerRunArgs<TOutput> = {\n config: PlannerConfig<TOutput>;\n capabilities: Map<string, PlannerCapability>;\n maxSteps: number;\n signature: string;\n planningAgent: AgentContract<unknown>;\n goal: string;\n options?: PlannerExecuteOptions<TOutput>;\n /**\n * Durable resume seed. When present the run re-hydrates the frozen plan\n * + executed-node ledger + usage + child reports + replan budget from a\n * prior crash, skips plan generation, and continues scheduling only the\n * unfinished frontier. Absent ⇒ a normal cold run.\n */\n resumeFrom?: PlannerSnapshot;\n};\n\n/**\n * Per-call orchestration state for one `planner.execute()` invocation.\n *\n * **Role.** Owns the full bounded-v1 planning lifecycle across four\n * phases that share mutable accumulators: (1) ask the LLM to GENERATE a\n * plan, (2) execute each plan step through its capability's `execute()`,\n * (3) optionally validate the final output, (4) assemble the unified\n * {@link PlannerResult}. Instantiated fresh per call inside the factory\n * so the accumulators (`usage`, `children`, `executedSteps`) are never\n * shared across runs. Unexported — callers only ever see the plain\n * {@link PlannerResult}.\n *\n * **Composition, not a fork.** Plan generation runs through a normal\n * `agent.execute()`; each step runs through the capability's own\n * `executable.execute()`. The planner adds the plan-generation brain and\n * the ordered-dispatch loop on top of the existing executable machinery —\n * it does not reimplement agent or step internals.\n */\nexport class PlannerRun<TOutput> {\n private readonly runId: string;\n /**\n * Run start timestamp. A resumed run restores it from the snapshot (in\n * the constructor) so the rebuilt report spans the whole run, not just\n * the resumed tail — hence not `readonly`.\n */\n private startedAt = new Date().toISOString();\n private readonly startPerf = performance.now();\n\n private readonly usage: Usage = { input: 0, output: 0, total: 0 };\n private readonly children: BaseReport[] = [];\n private readonly executedSteps: PlannerStepSnapshot[] = [];\n\n private plan?: PlannerPlan;\n private data?: TOutput;\n private error?: AIError;\n private cancelledAt?: string;\n\n /** Set when `mode: \"plan-only\"` short-circuited before execution. */\n private awaitingApproval = false;\n\n /** How many times the plan has been regenerated mid-run (≤ maxReplans). */\n private replanCount = 0;\n\n /**\n * One-shot guard so the DAG resume re-seed runs only on the first\n * `executeDag` pass — a later replan recursion gets a fresh plan with\n * different node ids and must NOT re-seed against the stale ledger.\n */\n private dagResumeConsumed = false;\n\n public constructor(private readonly args: PlannerRunArgs<TOutput>) {\n // A resumed run reuses the snapshot's key so it writes back to the\n // same record; otherwise a caller-supplied `options.runId` wins, else\n // a fresh id is generated.\n this.runId = args.resumeFrom?.runId ?? args.options?.runId ?? generateRunId(\"planner\");\n\n // Seed the accumulators from the snapshot on resume — re-hydrate the\n // frozen plan, the per-node ledger, the rolled-up usage, the child\n // reports, and the replan budget. `startedAt` restores too so the\n // resumed report spans the whole run. Pushing into the ledger rather\n // than re-running nodes is what keeps completed capabilities from\n // re-dispatching — the sequential guard / DAG re-seed read \"what ran\"\n // straight off `executedSteps`. Absent ⇒ accumulators stay empty and\n // the cold path is byte-for-byte unchanged.\n if (args.resumeFrom) {\n this.plan = args.resumeFrom.plan;\n this.executedSteps.push(...args.resumeFrom.executedSteps);\n this.children.push(...args.resumeFrom.children);\n this.mergeUsage(this.usage, args.resumeFrom.usage);\n this.replanCount = args.resumeFrom.replanCount;\n this.startedAt = args.resumeFrom.startedAt;\n }\n }\n\n /**\n * Run the planner end-to-end. Never throws on runtime failure —\n * generation errors, plan-validity errors, step failures, and\n * cancellation all surface on `result.error` with a narrowing\n * `report.status`.\n */\n public async run(): Promise<PlannerResult<TOutput>> {\n const result = await this.runPlan();\n\n // Route the planner's OWN report — the planning trip plus every\n // capability step already nest under it via `absorb`, so this single\n // call surfaces the whole tree as one trace. Mirrors agent/workflow:\n // `notifyObservers` self-routes a root run under observe-all (skipped\n // when nested, via the run-frame gate), then `captureChildReport`\n // auto-nests the planner under any enclosing orchestration run. Without\n // this, observe-all would only ever see the sub-agents as standalone\n // fragments — the planner itself never appeared.\n await notifyObservers(this.args.config.observe, result.report);\n captureChildReport(result.report);\n\n return result;\n }\n\n /**\n * Drive the planner lifecycle and return the built result WITHOUT\n * routing it — `run()` owns observer routing + auto-nesting so the\n * unified tree is emitted exactly once.\n */\n private async runPlan(): Promise<PlannerResult<TOutput>> {\n // Completed-run short-circuit. A resume of a snapshot whose run\n // already COMPLETED re-runs nothing — the stored ledger IS the\n // result. A `failed` / `cancelled` snapshot is NOT short-circuited:\n // those are the runs a caller resumes to retry the unfinished\n // frontier after fixing the cause, so they re-enter execution below.\n if (this.args.resumeFrom && this.args.resumeFrom.status === \"completed\") {\n this.rebuildResumedTerminal(\"completed\");\n return this.buildResult();\n }\n\n try {\n if (this.isAborted()) {\n this.markCancelled();\n await this.checkpoint(this.resolveSnapshotStatus());\n return this.buildResult();\n }\n\n // Resume fork — the plan is frozen (re-asking the LLM would burn\n // tokens and risk a different plan that no longer matches the\n // executed-node ledger). Skip generation entirely and execute the\n // re-hydrated plan; the sequential guard / DAG re-seed skip the\n // nodes already terminal in `executedSteps`.\n const plan = this.args.resumeFrom\n ? (this.plan as PlannerPlan)\n : (this.args.options?.approvedPlan ?? (await this.generatePlan()));\n\n if (this.error || !plan) {\n await this.checkpoint(this.resolveSnapshotStatus());\n return this.buildResult();\n }\n\n // On a fresh run, validate the plan (a generated / approved plan\n // could name an unknown capability). A resumed plan was already\n // valid when persisted, so skip re-validation unless drift `force`\n // is implied — re-validating a frozen plan against the same live\n // capabilities is redundant.\n if (!this.args.resumeFrom) {\n this.assertPlanValid(plan);\n\n if (this.error) {\n await this.checkpoint(this.resolveSnapshotStatus());\n return this.buildResult();\n }\n }\n\n this.plan = plan;\n\n // Plan-only mode — surface the validated plan for sign-off and execute\n // NOTHING. `approvedPlan` overrides this (execute the supplied plan),\n // mirroring the documented \"approvedPlan wins\" precedence. A resume is\n // always an execution, never a plan-only short-circuit.\n if (\n !this.args.resumeFrom &&\n this.args.options?.mode === \"plan-only\" &&\n !this.args.options?.approvedPlan\n ) {\n this.awaitingApproval = true;\n return this.buildResult();\n }\n\n await this.executePlan(plan);\n\n await this.finalizeOutput();\n } catch (caught) {\n this.error = this.toAIError(caught);\n }\n\n // Terminal checkpoint — persist the final state so a completed-run\n // resume short-circuits, then optionally drop the snapshot when\n // `deleteOnComplete` is set and the run succeeded. No-op when\n // `durable` is absent.\n await this.checkpoint(this.resolveSnapshotStatus());\n\n if (!this.error && this.args.config.durable?.deleteOnComplete) {\n const outcome = await deletePlannerSnapshot({\n durable: this.args.config.durable,\n runId: this.runId,\n });\n\n if (!outcome.ok) {\n this.logDurableFailure(\"snapshot.delete.failed\", outcome.error);\n }\n }\n\n return this.buildResult();\n }\n\n /**\n * Phase 1 — ask the planning agent for a structured plan. The plan\n * schema (built from the live capability names) is supplied as the\n * agent's per-call `output`, so the model is steered to reference only\n * real capabilities. The planning trip's usage + report roll into the\n * planner's totals regardless of outcome.\n *\n * `feedback` is set only on a RE-plan: the regenerated request is\n * seeded with the executed-step digest plus the caller's feedback so\n * the planner revises the remaining work rather than starting cold.\n */\n private async generatePlan(feedback?: string): Promise<PlannerPlan | undefined> {\n const schema = planSchema([...this.args.capabilities.keys()], this.args.maxSteps);\n\n // `withoutRunFrame` suppresses the planning trip's own self-routing:\n // `absorb` already folds its report into `this.children`, so without\n // this the trip would ALSO route as a standalone top-level trace under\n // observe-all. The planner routes the unified tree once, in `run()`.\n const result = await withoutRunFrame(() =>\n this.args.planningAgent.execute(this.buildPlanPrompt(feedback), {\n output: schema as StandardSchemaV1<unknown>,\n placeholders: this.args.options?.placeholders,\n signal: this.args.options?.signal,\n sessionId: this.args.options?.sessionId,\n }),\n );\n\n this.absorb(result.usage, result.report);\n\n if (result.error) {\n // A schema rejection from the planning trip (e.g. an empty\n // `steps` array tripping the plan schema) is really an invalid\n // plan — re-wrap it into the typed planner contract so callers\n // branch on `PlannerPlanInvalidError` rather than the agent's raw\n // `SchemaValidationError`. Any other child error flows through\n // unchanged.\n this.error =\n result.error instanceof SchemaValidationError\n ? new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): the planner produced no usable plan`,\n { cause: result.error, context: { runId: this.runId } },\n )\n : result.error;\n return undefined;\n }\n\n const plan = result.data as PlannerPlan | undefined;\n\n if (!plan || !Array.isArray(plan.steps) || plan.steps.length === 0) {\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): the planner produced no usable plan`,\n { context: { runId: this.runId } },\n );\n return undefined;\n }\n\n this.assertPlanValid(plan);\n\n if (this.error) {\n return undefined;\n }\n\n return plan;\n }\n\n /**\n * Shared plan-validity guard — used both for a freshly generated plan\n * and for a caller-supplied `approvedPlan`. Sets `this.error` to a\n * typed {@link PlannerPlanInvalidError} when the plan is empty or names\n * an unknown capability; a stale `approvedPlan` thus fails the same way\n * a hallucinated capability does, never silently mis-dispatching.\n */\n private assertPlanValid(plan: PlannerPlan): void {\n if (!Array.isArray(plan.steps) || plan.steps.length === 0) {\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): the planner produced no usable plan`,\n { context: { runId: this.runId } },\n );\n return;\n }\n\n const unknownStep = plan.steps.find((step) => !this.args.capabilities.has(step.capability));\n\n if (unknownStep) {\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): plan references unknown capability \"${unknownStep.capability}\"`,\n { context: { runId: this.runId, capability: unknownStep.capability } },\n );\n }\n }\n\n /**\n * Phase 2 — execute the plan. Branches on `config.dag`: the default is\n * the strict array-order sequential loop (byte-for-byte today's\n * behavior when neither `onStep` nor `replan` is configured); `dag:\n * true` schedules independent `dependsOn` branches in parallel.\n */\n private async executePlan(plan: PlannerPlan): Promise<void> {\n if (this.args.config.dag) {\n return this.executeDag(plan);\n }\n\n return this.executeSequential(plan);\n }\n\n /**\n * Sequential executor — the original strict array-order loop, threading\n * each completed step's output into the next step's input context.\n * Stops at the first step failure or when the abort signal fires\n * between steps; steps beyond `maxSteps` are recorded `skipped`.\n *\n * **Additive hooks (inert by default).** After each step settles it\n * fires the `onStep` directive hook; an `abort` directive stops the run\n * like a failure, and a `replan` directive (or, when `config.replan` is\n * set, an unhandled failure) regenerates the REMAINING plan instead of\n * aborting. With no `onStep` and no `replan`, the behavior is identical\n * to before.\n */\n private async executeSequential(plan: PlannerPlan): Promise<void> {\n const previousOutputs: string[] = [];\n let steps = plan.steps;\n let index = 0;\n\n // Resume re-seed (sequential). The frozen plan's already-completed\n // prefix lives in the persisted ledger; thread its outputs forward and\n // jump the cursor past it so completed nodes are never re-dispatched.\n // Stale non-completed entries (the failed node that crashed the run,\n // and any `skipped` tail) are pruned so the re-run repopulates them\n // cleanly instead of duplicating. No-op on a cold run (empty ledger).\n if (this.args.resumeFrom) {\n index = this.rehydrateSequentialState(steps, previousOutputs);\n }\n\n while (index < steps.length) {\n const step = steps[index] as PlannerStep;\n\n if (index >= this.args.maxSteps) {\n this.recordSkipped(index, step);\n index++;\n continue;\n }\n\n if (this.isAborted()) {\n this.markCancelled();\n this.recordSkipped(index, step);\n index++;\n continue;\n }\n\n const completed = await this.executeStep(index, step, previousOutputs);\n\n const snapshot = this.snapshotFor(index);\n const directive = snapshot\n ? await this.resolveDirective(snapshot, plan, completed)\n : undefined;\n\n if (directive?.type === \"replan\") {\n const remaining = await this.regeneratePlan(directive.feedback);\n\n if (this.error || !remaining) {\n this.skipRest(steps, index + 1);\n return;\n }\n\n // Replace the remaining tail with the regenerated plan and restart\n // the cursor against it (executed steps already recorded stay put).\n // Each new step gets the executed-so-far digest as its context.\n steps = remaining.steps;\n index = 0;\n previousOutputs.length = 0;\n previousOutputs.push(...this.executedDigest());\n continue;\n }\n\n if (directive?.type === \"abort\") {\n // The hook (or an unhandled failure) asked to stop — record the\n // remaining steps as skipped so the report still describes the\n // whole intended plan, then stop.\n this.skipRest(steps, index + 1);\n return;\n }\n\n index++;\n }\n }\n\n /**\n * DAG executor — schedule independent `dependsOn` branches in parallel.\n *\n * Builds the DAG (cycle / unknown-id → `PlannerPlanInvalidError`),\n * then repeatedly computes the ready set (steps whose deps all\n * completed), dispatches up to `maxConcurrency` of them with\n * `Promise.all`, and feeds each step ONLY its dependencies' outputs. A\n * failed step blocks just its descendants (recorded `skipped`);\n * independent branches still settle. With an `output` schema set, the\n * final `data` is the topological SINK's output (multiple sinks → a\n * convergence error).\n */\n private async executeDag(plan: PlannerPlan): Promise<void> {\n const dag = buildDag(plan.steps, this.args.config.name);\n const maxConcurrency = Math.max(1, this.args.config.maxConcurrency ?? 4);\n\n const completed = new Set<string>();\n const done = new Set<string>();\n const outputs = new Map<string, string>();\n const rawOutputs = new Map<string, unknown>();\n let executedCount = 0;\n\n // Resume re-seed (DAG). Re-derive the scheduler's working sets from\n // the persisted ledger so `readyNodes` schedules only the unfinished\n // frontier — completed nodes go straight into `completed` + `done`\n // with their outputs restored; stale non-completed entries are pruned\n // so the re-run repopulates them. One-shot: consumed on the first DAG\n // pass so a later replan recursion (fresh plan, different node ids)\n // doesn't re-seed against a stale ledger. No-op on a cold run.\n if (this.args.resumeFrom && !this.dagResumeConsumed) {\n this.dagResumeConsumed = true;\n executedCount = this.rehydrateDagState(dag, completed, done, outputs, rawOutputs);\n }\n\n while (done.size < dag.nodes.length) {\n if (this.isAborted()) {\n this.markCancelled();\n this.skipDagRest(dag, done);\n return;\n }\n\n const ready = readyNodes(dag, completed, done);\n\n if (ready.length === 0) {\n // No node can advance — every remaining node transitively depends\n // on a failed/skipped ancestor. Record them skipped and stop.\n this.skipDagRest(dag, done);\n return;\n }\n\n const batch = ready.slice(0, maxConcurrency);\n\n const settled = await Promise.all(\n batch.map(async (node) => {\n // `maxSteps` truncation applies to the count of DISPATCHED steps.\n if (executedCount >= this.args.maxSteps) {\n this.recordSkipped(node.index, node.step);\n return { node, ran: false, completed: false };\n }\n\n executedCount++;\n // Feed this step ONLY its dependencies' output digests — the DAG\n // fix for the sequential loop's \"all prior outputs into every\n // step\" behavior. `executeStep` pushes into the array it is\n // given, so a fresh array per node keeps branches isolated.\n const previousOutputs = node.dependencies.map(\n (dependency) => outputs.get(dependency) as string,\n );\n const stepCompleted = await this.executeStep(\n node.index,\n node.step,\n previousOutputs,\n );\n\n if (stepCompleted) {\n // Read the raw output off the snapshot (NOT shared `this.data`,\n // which races under Promise.all) for both the dependent digest\n // and the eventual sink output.\n const rawOutput = this.snapshotFor(node.index)?.output;\n rawOutputs.set(node.id, rawOutput);\n outputs.set(node.id, this.stringifyOutput(node.step.capability, rawOutput));\n }\n\n return { node, ran: true, completed: stepCompleted };\n }),\n );\n\n for (const entry of settled) {\n done.add(entry.node.id);\n\n if (entry.completed) {\n completed.add(entry.node.id);\n }\n }\n\n // Fire the per-step hook for each settled step (in dispatch order).\n let replanFeedback: string | undefined;\n let shouldAbort = false;\n\n for (const entry of settled) {\n if (!entry.ran) {\n continue;\n }\n\n const snapshot = this.snapshotFor(entry.node.index);\n const directive = snapshot\n ? await this.resolveDirective(snapshot, plan, entry.completed)\n : undefined;\n\n if (directive?.type === \"replan\") {\n replanFeedback = directive.feedback;\n } else if (directive?.type === \"abort\") {\n shouldAbort = true;\n }\n }\n\n if (shouldAbort) {\n this.skipDagRest(dag, done);\n return;\n }\n\n if (replanFeedback !== undefined) {\n const remaining = await this.regeneratePlan(replanFeedback);\n\n if (this.error || !remaining) {\n this.skipDagRest(dag, done);\n return;\n }\n\n // Re-plan in DAG mode regenerates the remaining work as a fresh\n // (sequential) plan and runs it through the DAG scheduler again.\n return this.executeDag(remaining);\n }\n }\n\n this.finalizeDagOutput(dag, completed, rawOutputs);\n }\n\n /**\n * Dispatch one plan step through its capability's `executable.execute()`\n * and fold the outcome into the accumulators. Returns `true` when the\n * step completed, `false` when it failed (setting the run error).\n */\n private async executeStep(\n index: number,\n step: PlannerStep,\n previousOutputs: string[],\n ): Promise<boolean> {\n const capability = this.args.capabilities.get(step.capability) as PlannerCapability;\n const stepStart = performance.now();\n const startedAt = new Date().toISOString();\n const input = this.composeStepInput(step, previousOutputs);\n\n // `withoutRunFrame` keeps each capability step nested under the planner\n // only — `absorb` folds its report into `this.children`, so suppressing\n // its self-route prevents a duplicate standalone trace under observe-all.\n const result = await withoutRunFrame(() =>\n capability.executable.execute(input, {\n signal: this.args.options?.signal,\n sessionId: this.args.options?.sessionId,\n }),\n );\n\n const childReport = \"report\" in result ? (result.report as BaseReport) : undefined;\n this.absorb(result.usage, childReport);\n\n const output = this.extractOutput(result);\n const failed = result.error !== undefined;\n\n this.executedSteps.push({\n index,\n step,\n status: failed ? \"failed\" : \"completed\",\n output: failed ? undefined : output,\n error: result.error,\n startedAt,\n endedAt: new Date().toISOString(),\n duration: performance.now() - stepStart,\n usage: result.usage,\n childReport,\n });\n\n // Per-node durable checkpoint. Sits AFTER the node's snapshot is\n // pushed and `absorb` has folded its usage + child report — the only\n // point where the ledger + usage + children are mutually consistent.\n // A completed node is never re-dispatched on resume (the sequential\n // guard / DAG re-seed skip it). Swallow-and-log; no-op when `durable`\n // is absent.\n await this.checkpoint(\"running\");\n\n if (failed) {\n this.error = result.error;\n return false;\n }\n\n previousOutputs.push(this.stringifyOutput(step.capability, output));\n this.data = output as TOutput;\n\n return true;\n }\n\n /**\n * Resolve the steering directive for a just-settled step, shared by the\n * sequential and DAG executors. Fires the user's `onStep` hook, then\n * normalizes the result against the `replan` budget:\n *\n * - explicit `replan` directive — honored only when `config.replan` is\n * set and the budget remains; otherwise downgraded to `continue`.\n * - explicit `abort` — honored.\n * - failed step with no overriding directive — auto-`replan` when\n * `config.replan` is set and the budget remains (feedback = the step\n * error message), else `abort` (today's abort-on-first-failure).\n *\n * Returns `undefined` when the run should simply continue. A returned\n * `replan` directive has ALREADY consumed one unit of the replan budget.\n */\n private async resolveDirective(\n snapshot: PlannerStepSnapshot,\n plan: PlannerPlan,\n completed: boolean,\n ): Promise<PlannerStepDirective | undefined> {\n const hook = this.args.options?.onStep;\n const userDirective = hook ? await hook(snapshot, plan) : undefined;\n\n if (userDirective?.type === \"replan\") {\n if (this.canReplan()) {\n this.replanCount++;\n return userDirective;\n }\n\n // Replan requested but unavailable (no config or budget spent) — fall\n // through to the failure/continue defaults below.\n } else if (userDirective?.type === \"abort\") {\n return { type: \"abort\" };\n } else if (userDirective?.type === \"continue\") {\n return undefined;\n }\n\n if (!completed) {\n if (this.canReplan()) {\n this.replanCount++;\n return { type: \"replan\", feedback: snapshot.error?.message ?? \"step failed\" };\n }\n\n return { type: \"abort\" };\n }\n\n return undefined;\n }\n\n /** Whether a re-plan is configured and the budget has room. */\n private canReplan(): boolean {\n const replan = this.args.config.replan;\n\n return replan !== undefined && this.replanCount < replan.maxReplans;\n }\n\n /**\n * Re-ask the planning agent for a plan over the REMAINING work — a\n * second `generatePlan()` seeded with the executed-step digest plus the\n * caller's feedback. Reuses the exact `generatePlan` plumbing (same\n * schema, same `PlannerPlanInvalidError` handling), so a regenerated\n * plan that is empty or names an unknown capability fails identically.\n * The failed step's error is cleared so the regenerated plan runs\n * cleanly; a fresh failure (or exhausted budget) re-sets it.\n */\n private async regeneratePlan(feedback: string): Promise<PlannerPlan | undefined> {\n this.error = undefined;\n return this.generatePlan(feedback);\n }\n\n /**\n * The executed-so-far digest — one context line per completed step, in\n * execution order. Seeds the regenerated plan's first step so it builds\n * on what already ran.\n */\n private executedDigest(): string[] {\n return this.executedSteps\n .filter((snapshot) => snapshot.status === \"completed\")\n .map((snapshot) => this.stringifyOutput(snapshot.step.capability, snapshot.output));\n }\n\n /** The last-pushed snapshot for a given step index, if any. */\n private snapshotFor(index: number): PlannerStepSnapshot | undefined {\n for (let position = this.executedSteps.length - 1; position >= 0; position--) {\n const snapshot = this.executedSteps[position] as PlannerStepSnapshot;\n\n if (snapshot.index === index) {\n return snapshot;\n }\n }\n\n return undefined;\n }\n\n /** Record every step from `from` onward (in a flat array plan) as skipped. */\n private skipRest(steps: PlannerStep[], from: number): void {\n for (let rest = from; rest < steps.length; rest++) {\n this.recordSkipped(rest, steps[rest] as PlannerStep);\n }\n }\n\n /** Record every not-yet-`done` DAG node as skipped, in plan order. */\n private skipDagRest(dag: PlannerDag, done: ReadonlySet<string>): void {\n for (const node of dag.nodes) {\n if (!done.has(node.id)) {\n this.recordSkipped(node.index, node.step);\n }\n }\n }\n\n /**\n * Set `this.data` from the DAG's topological sink for a configured\n * `output` schema. \"Last completed step\" is meaningless under\n * parallelism, so the sink (the step nothing depends on) is the\n * unambiguous final output. Multiple sinks while an `output` schema is\n * set is a convergence error — a typed `PlannerPlanInvalidError`.\n */\n private finalizeDagOutput(\n dag: PlannerDag,\n completed: ReadonlySet<string>,\n rawOutputs: Map<string, unknown>,\n ): void {\n const schema = this.args.options?.output ?? this.args.config.output;\n\n if (!schema || this.error) {\n return;\n }\n\n const sinks = sinkNodes(dag).filter((node) => completed.has(node.id));\n\n if (sinks.length > 1) {\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): DAG has multiple sinks but an \\`output\\` schema is set — the plan must converge to a single final step`,\n { context: { runId: this.runId, sinks: sinks.map((node) => node.id) } },\n );\n this.data = undefined;\n return;\n }\n\n const sink = sinks[0] as DagNode | undefined;\n this.data = (sink ? rawOutputs.get(sink.id) : undefined) as TOutput | undefined;\n }\n\n /**\n * Phase 3 — when an `output` schema is configured (factory or per-call\n * override), validate the final completed step's output into typed\n * `result.data`. A validation failure replaces the run error and flips\n * the status to failed.\n */\n private async finalizeOutput(): Promise<void> {\n const schema = this.args.options?.output ?? this.args.config.output;\n\n if (!schema || this.error) {\n return;\n }\n\n if (this.data === undefined) {\n // An `output` schema is configured but the final completed step\n // produced nothing to validate — returning `{ data: undefined,\n // error: undefined, status: \"completed\" }` would be a silent\n // contract violation. Surface it as an invalid plan instead.\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): plan completed without producing output for the configured \\`output\\` schema`,\n { context: { runId: this.runId } },\n );\n return;\n }\n\n const validation = await schema[\"~standard\"].validate(this.data);\n\n if (validation.issues) {\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): final output failed validation`,\n {\n context: {\n runId: this.runId,\n issues: validation.issues.map((issue) => issue.message),\n },\n },\n );\n this.data = undefined;\n return;\n }\n\n this.data = validation.value as TOutput;\n }\n\n /**\n * Phase 4 — fold the accumulators into the planner's own\n * {@link PlannerReport} node and the final {@link PlannerResult}, then\n * stamp lineage across the whole subtree so every child shares this\n * run's root id.\n */\n private buildResult(): PlannerResult<TOutput> {\n const status = this.resolveStatus();\n\n const report: PlannerReport = {\n runId: this.runId,\n rootRunId: this.runId,\n name: this.args.config.name,\n version: this.args.config.version,\n type: \"planner\",\n status,\n // Stamp the terminal error so the observe path surfaces it on the\n // planner span (no result envelope reaches an observer). Absent on\n // a completed run.\n ...(this.error ? { error: this.error } : {}),\n startedAt: this.startedAt,\n endedAt: new Date().toISOString(),\n duration: performance.now() - this.startPerf,\n usage: this.usage,\n children: this.children,\n signature: this.args.signature,\n plan: this.plan,\n executedSteps: this.executedSteps,\n cancelledAt: this.cancelledAt,\n reportSchemaVersion: REPORT_SCHEMA_VERSION,\n };\n\n stampReportLineage(report, {\n rootRunId: this.runId,\n sessionId: this.args.options?.sessionId,\n });\n\n const result: PlannerResult<TOutput> = {\n type: \"planner\",\n data: this.error ? undefined : this.data,\n error: this.error,\n usage: this.usage,\n report,\n };\n\n // Plan-only mode surfaces the validated plan WITHOUT execution so the\n // caller can sign off and re-run with `approvedPlan`.\n if (this.awaitingApproval) {\n result.plan = this.plan;\n }\n\n return result;\n }\n\n /**\n * Resolve the terminal status from the accumulated outcome.\n * `awaiting-approval` (plan-only short-circuit) wins over everything —\n * nothing executed, so neither cancellation nor error applies.\n * Otherwise cancelled wins over failed (an abort that also produced a\n * step error still reads as cancelled); failed wins over completed.\n */\n private resolveStatus(): PlannerReport[\"status\"] {\n if (this.awaitingApproval) {\n return \"awaiting-approval\";\n }\n\n if (this.cancelledAt !== undefined) {\n return \"cancelled\";\n }\n\n if (this.error) {\n return \"failed\";\n }\n\n return \"completed\";\n }\n\n /**\n * Build the prompt handed to the planning agent. On the first pass this\n * is just the user's goal (byte-for-byte unchanged). On a RE-plan it\n * prepends the executed-step digest and the steering feedback so the\n * planner revises the remaining work.\n */\n private buildPlanPrompt(feedback?: string): string {\n if (feedback === undefined) {\n return this.args.goal;\n }\n\n const digest = this.executedDigest();\n const sections: string[] = [`Goal: ${this.args.goal}`, \"\"];\n\n if (digest.length > 0) {\n sections.push(\"Steps already completed:\", ...digest, \"\");\n }\n\n sections.push(\n `Feedback requiring a revised plan: ${feedback}`,\n \"\",\n \"Produce a plan for the REMAINING work only.\",\n );\n\n return sections.join(\"\\n\");\n }\n\n /**\n * Compose a step's effective input: the step's own `input`, prefixed\n * with a compact digest of every prior step's output so a downstream\n * capability can build on what ran before it. No prior output → the\n * step's raw input.\n */\n private composeStepInput(step: PlannerStep, previousOutputs: string[]): string {\n if (previousOutputs.length === 0) {\n return step.input;\n }\n\n return [\n \"Context from earlier steps:\",\n ...previousOutputs,\n \"\",\n `Task: ${step.input}`,\n ].join(\"\\n\");\n }\n\n /**\n * Pull the usable output off a capability's result. Prefers structured\n * `data` (agents/workflows with an `output` schema, tools), and falls\n * back to an agent's raw `text` when no structured data was produced —\n * the common case for a plain text-producing capability agent.\n */\n private extractOutput(result: BaseResult): unknown {\n const shaped = result as { data?: unknown; text?: unknown };\n\n if (shaped.data !== undefined) {\n return shaped.data;\n }\n\n if (typeof shaped.text === \"string\") {\n return shaped.text;\n }\n\n return undefined;\n }\n\n /** Serialize a capability output into a single context line for the next step. */\n private stringifyOutput(capability: string, output: unknown): string {\n if (output === undefined) {\n return `- ${capability}: (no output)`;\n }\n\n if (typeof output === \"string\") {\n return `- ${capability}: ${output}`;\n }\n\n return `- ${capability}: ${JSON.stringify(output)}`;\n }\n\n /** Push a `skipped` snapshot for a step the planner never dispatched. */\n private recordSkipped(index: number, step: PlannerStep): void {\n const now = new Date().toISOString();\n\n this.executedSteps.push({\n index,\n step,\n status: \"skipped\",\n startedAt: now,\n endedAt: now,\n duration: 0,\n usage: { input: 0, output: 0, total: 0 },\n });\n }\n\n /** Fold a child's usage + report node into the planner's accumulators. */\n private absorb(usage: Usage, report: BaseReport | undefined): void {\n this.mergeUsage(this.usage, usage);\n\n if (report) {\n this.children.push(report);\n }\n }\n\n /**\n * Add a child's usage into the running total. Mirrors the batch\n * primitive's rollup: scalar token channels sum directly, optional\n * sub-channels accumulate only when reported, and the cost breakdown\n * merges via {@link accumulateCost} so one unpriced child can't erase\n * priced siblings.\n */\n private mergeUsage(target: Usage, child: Usage): void {\n target.input += child.input;\n target.output += child.output;\n target.total += child.total;\n\n if (child.cachedTokens !== undefined) {\n target.cachedTokens = (target.cachedTokens ?? 0) + child.cachedTokens;\n }\n\n if (child.reasoningTokens !== undefined) {\n target.reasoningTokens = (target.reasoningTokens ?? 0) + child.reasoningTokens;\n }\n\n if (child.cacheWriteTokens !== undefined) {\n target.cacheWriteTokens = (target.cacheWriteTokens ?? 0) + child.cacheWriteTokens;\n }\n\n const mergedCost = accumulateCost(target.cost, child.cost);\n\n if (mergedCost !== undefined) {\n target.cost = mergedCost;\n }\n }\n\n /**\n * Re-derive the sequential cursor + prior-output context from the\n * persisted ledger on resume. Threads every already-`completed` node's\n * output into `previousOutputs`, returns the first index NOT completed\n * as the resume cursor, and prunes stale non-completed ledger entries\n * (the failed node + any skipped tail) at-or-after that cursor so the\n * re-run repopulates them without duplicating.\n */\n private rehydrateSequentialState(\n steps: PlannerStep[],\n previousOutputs: string[],\n ): number {\n let cursor = 0;\n\n for (let index = 0; index < steps.length; index++) {\n const snapshot = this.snapshotFor(index);\n\n if (snapshot?.status === \"completed\") {\n const step = steps[index] as PlannerStep;\n previousOutputs.push(this.stringifyOutput(step.capability, snapshot.output));\n cursor = index + 1;\n continue;\n }\n\n // First non-completed index — this is where the re-run resumes.\n break;\n }\n\n // Drop any ledger entries at-or-after the cursor (failed / skipped\n // from the crashed run) so the resumed loop's pushes don't duplicate.\n this.pruneLedgerFrom(cursor);\n\n return cursor;\n }\n\n /**\n * Re-derive the DAG scheduler's working sets from the persisted ledger\n * on resume. Completed nodes go into `completed` + `done` with their\n * string + raw outputs restored (so dependents read the right context);\n * stale non-completed entries are pruned so the re-run repopulates them.\n * Returns the count of nodes already dispatched (for the `maxSteps`\n * truncation budget).\n */\n private rehydrateDagState(\n dag: PlannerDag,\n completed: Set<string>,\n done: Set<string>,\n outputs: Map<string, string>,\n rawOutputs: Map<string, unknown>,\n ): number {\n const completedIndices = new Set<number>();\n\n for (const node of dag.nodes) {\n const snapshot = this.snapshotFor(node.index);\n\n if (snapshot?.status !== \"completed\") {\n continue;\n }\n\n completed.add(node.id);\n done.add(node.id);\n completedIndices.add(node.index);\n rawOutputs.set(node.id, snapshot.output);\n outputs.set(node.id, this.stringifyOutput(node.step.capability, snapshot.output));\n }\n\n // Prune every non-completed ledger entry so the re-run's pushes don't\n // duplicate the failed / skipped frontier from the crashed run.\n const retained = this.executedSteps.filter((snapshot) =>\n completedIndices.has(snapshot.index),\n );\n this.executedSteps.length = 0;\n this.executedSteps.push(...retained);\n\n return completedIndices.size;\n }\n\n /**\n * Drop every ledger entry whose index is at or after `from`. Used by\n * the sequential resume re-seed to clear the crashed run's failed /\n * skipped frontier before the re-run repopulates it.\n */\n private pruneLedgerFrom(from: number): void {\n const retained = this.executedSteps.filter((snapshot) => snapshot.index < from);\n this.executedSteps.length = 0;\n this.executedSteps.push(...retained);\n }\n\n /**\n * Map the run's terminal outcome to the persisted snapshot status.\n * `awaiting-approval` (plan-only) never persists a durable snapshot\n * (resume is always an execution), so it folds to `running` here —\n * but the durable + plan-only combination is disallowed at the call\n * site, so this path is effectively unreachable.\n */\n private resolveSnapshotStatus(): PlannerSnapshotStatus {\n if (this.cancelledAt !== undefined) {\n return \"cancelled\";\n }\n\n if (this.error) {\n return \"failed\";\n }\n\n if (this.awaitingApproval) {\n return \"running\";\n }\n\n return \"completed\";\n }\n\n /**\n * Build and persist a {@link PlannerSnapshot} from the current\n * accumulators. The per-node and terminal checkpoints both route\n * through here. No-op when `durable` is absent. A failed persist is\n * logged and swallowed (never aborts the run), matching the supervisor\n * / workflow checkpoint policy.\n */\n private async checkpoint(status: PlannerSnapshotStatus): Promise<void> {\n if (!this.args.config.durable || !this.plan) {\n return;\n }\n\n const outcome = await persistPlannerSnapshot({\n durable: this.args.config.durable,\n runId: this.runId,\n plannerName: this.args.config.name,\n signature: this.args.signature,\n version: this.args.config.version,\n goal: this.args.goal,\n plan: this.plan,\n executedSteps: this.executedSteps,\n usage: this.usage,\n children: this.children,\n replanCount: this.replanCount,\n status,\n startedAt: this.startedAt,\n });\n\n if (!outcome.ok) {\n this.logDurableFailure(\"snapshot.persist.failed\", outcome.error);\n }\n }\n\n /**\n * Re-derive the terminal state when a resume short-circuits a snapshot\n * whose run already COMPLETED. The persisted ledger is the\n * authoritative outcome — `this.data` is restored from the last\n * completed node so the rebuilt result carries the final output.\n *\n * Only reached for a `completed` snapshot — `failed` / `cancelled`\n * snapshots re-enter execution to retry the unfinished frontier instead.\n */\n private rebuildResumedTerminal(_status: PlannerSnapshotStatus): void {\n const lastCompleted = [...this.executedSteps]\n .reverse()\n .find((snapshot) => snapshot.status === \"completed\");\n\n if (lastCompleted) {\n this.data = lastCompleted.output as TOutput;\n }\n }\n\n /** Structured-log a durable persist/delete failure. */\n private logDurableFailure(action: string, error: unknown): void {\n log.warn(\"ai.planner\", action, \"durable snapshot operation failed\", {\n runId: this.runId,\n planner: this.args.config.name,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n /** Whether the caller's abort signal has fired. */\n private isAborted(): boolean {\n return this.args.options?.signal?.aborted === true;\n }\n\n /** Record a cancellation observation, setting the run error once. */\n private markCancelled(): void {\n if (this.cancelledAt !== undefined) {\n return;\n }\n\n this.cancelledAt = new Date().toISOString();\n\n const reason = this.args.options?.signal?.reason;\n\n this.error = new PlannerCancelledError(\n `ai.planner(\"${this.args.config.name}\"): run cancelled`,\n {\n cancelledAt: this.cancelledAt,\n reason: typeof reason === \"string\" ? reason : undefined,\n context: { runId: this.runId },\n },\n );\n }\n\n /** Normalize any thrown value into a typed {@link AIError}. */\n private toAIError(caught: unknown): AIError {\n if (caught instanceof AIError) {\n return caught;\n }\n\n const message = caught instanceof Error ? caught.message : String(caught);\n\n return new PlannerFailedError(`ai.planner(\"${this.args.config.name}\"): ${message}`, {\n cause: caught,\n context: { runId: this.runId },\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"planner-run.mjs","names":[],"sources":["../../../../../../../ai/src/planner/planner-run.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { log } from \"@warlock.js/logger\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport type { PlannerCapability } from \"../contracts/planner/planner-capability.type\";\nimport type { PlannerConfig } from \"../contracts/planner/planner-config.type\";\nimport type {\n PlannerExecuteOptions,\n PlannerStepDirective,\n} from \"../contracts/planner/planner-execute-options.type\";\nimport type { PlannerPlan, PlannerStep } from \"../contracts/planner/planner-plan.type\";\nimport type {\n PlannerReport,\n PlannerResult,\n PlannerStepSnapshot,\n} from \"../contracts/planner/planner-result.type\";\nimport type {\n PlannerSnapshot,\n PlannerSnapshotStatus,\n} from \"../contracts/planner/planner-snapshot.type\";\nimport type { BaseReport } from \"../contracts/result/base-report.type\";\nimport { REPORT_SCHEMA_VERSION } from \"../contracts/result/base-report.type\";\nimport type { BaseResult } from \"../contracts/result/base-result.type\";\nimport type { Usage } from \"../contracts/result/usage.type\";\nimport { AIError } from \"../errors/ai-error\";\nimport { PlannerCancelledError } from \"../errors/planner-cancelled-error\";\nimport { PlannerFailedError } from \"../errors/planner-failed-error\";\nimport { PlannerPlanInvalidError } from \"../errors/planner-plan-invalid-error\";\nimport { SchemaValidationError } from \"../errors/schema-validation-error\";\nimport { notifyObservers } from \"../observe/resolve-observers\";\nimport { accumulateCost } from \"../utils/compute-cost\";\nimport { generateRunId } from \"../utils/generate-run-id\";\nimport { captureChildReport, withoutRunFrame } from \"../utils/run-context\";\nimport { stampReportLineage } from \"../utils/stamp-report-lineage\";\nimport type { DagNode, PlannerDag } from \"./dag-scheduler\";\nimport { buildDag, readyNodes, sinkNodes } from \"./dag-scheduler\";\nimport { planSchema } from \"./plan-schema\";\nimport {\n deletePlannerSnapshot,\n persistPlannerSnapshot,\n} from \"./snapshot\";\n\n/**\n * Construction args for one {@link PlannerRun}. Carries everything the\n * factory resolved once (config, capability map, signature, planning\n * agent) plus the per-call goal and options.\n */\nexport type PlannerRunArgs<TOutput> = {\n config: PlannerConfig<TOutput>;\n capabilities: Map<string, PlannerCapability>;\n maxSteps: number;\n signature: string;\n planningAgent: AgentContract<unknown>;\n goal: string;\n options?: PlannerExecuteOptions<TOutput>;\n /**\n * Durable resume seed. When present the run re-hydrates the frozen plan\n * + executed-node ledger + usage + child reports + replan budget from a\n * prior crash, skips plan generation, and continues scheduling only the\n * unfinished frontier. Absent ⇒ a normal cold run.\n */\n resumeFrom?: PlannerSnapshot;\n};\n\n/**\n * Per-call orchestration state for one `planner.execute()` invocation.\n *\n * **Role.** Owns the full bounded-v1 planning lifecycle across four\n * phases that share mutable accumulators: (1) ask the LLM to GENERATE a\n * plan, (2) execute each plan step through its capability's `execute()`,\n * (3) optionally validate the final output, (4) assemble the unified\n * {@link PlannerResult}. Instantiated fresh per call inside the factory\n * so the accumulators (`usage`, `children`, `executedSteps`) are never\n * shared across runs. Unexported — callers only ever see the plain\n * {@link PlannerResult}.\n *\n * **Composition, not a fork.** Plan generation runs through a normal\n * `agent.execute()`; each step runs through the capability's own\n * `executable.execute()`. The planner adds the plan-generation brain and\n * the ordered-dispatch loop on top of the existing executable machinery —\n * it does not reimplement agent or step internals.\n */\nexport class PlannerRun<TOutput> {\n private readonly runId: string;\n /**\n * Run start timestamp. A resumed run restores it from the snapshot (in\n * the constructor) so the rebuilt report spans the whole run, not just\n * the resumed tail — hence not `readonly`.\n */\n private startedAt = new Date().toISOString();\n private readonly startPerf = performance.now();\n\n private readonly usage: Usage = { input: 0, output: 0, total: 0 };\n private readonly children: BaseReport[] = [];\n private readonly executedSteps: PlannerStepSnapshot[] = [];\n\n private plan?: PlannerPlan;\n private data?: TOutput;\n private error?: AIError;\n private cancelledAt?: string;\n\n /** Set when `mode: \"plan-only\"` short-circuited before execution. */\n private awaitingApproval = false;\n\n /** How many times the plan has been regenerated mid-run (≤ maxReplans). */\n private replanCount = 0;\n\n /**\n * One-shot guard so the DAG resume re-seed runs only on the first\n * `executeDag` pass — a later replan recursion gets a fresh plan with\n * different node ids and must NOT re-seed against the stale ledger.\n */\n private dagResumeConsumed = false;\n\n public constructor(private readonly args: PlannerRunArgs<TOutput>) {\n // A resumed run reuses the snapshot's key so it writes back to the\n // same record; otherwise a caller-supplied `options.runId` wins, else\n // a fresh id is generated.\n this.runId = args.resumeFrom?.runId ?? args.options?.runId ?? generateRunId(\"planner\");\n\n // Seed the accumulators from the snapshot on resume — re-hydrate the\n // frozen plan, the per-node ledger, the rolled-up usage, the child\n // reports, and the replan budget. `startedAt` restores too so the\n // resumed report spans the whole run. Pushing into the ledger rather\n // than re-running nodes is what keeps completed capabilities from\n // re-dispatching — the sequential guard / DAG re-seed read \"what ran\"\n // straight off `executedSteps`. Absent ⇒ accumulators stay empty and\n // the cold path is byte-for-byte unchanged.\n if (args.resumeFrom) {\n this.plan = args.resumeFrom.plan;\n this.executedSteps.push(...args.resumeFrom.executedSteps);\n this.children.push(...args.resumeFrom.children);\n this.mergeUsage(this.usage, args.resumeFrom.usage);\n this.replanCount = args.resumeFrom.replanCount;\n this.startedAt = args.resumeFrom.startedAt;\n }\n }\n\n /**\n * Run the planner end-to-end. Never throws on runtime failure —\n * generation errors, plan-validity errors, step failures, and\n * cancellation all surface on `result.error` with a narrowing\n * `report.status`.\n */\n public async run(): Promise<PlannerResult<TOutput>> {\n const result = await this.runPlan();\n\n // Route the planner's OWN report — the planning trip plus every\n // capability step already nest under it via `absorb`, so this single\n // call surfaces the whole tree as one trace. Mirrors agent/workflow:\n // `notifyObservers` self-routes a root run under observe-all (skipped\n // when nested, via the run-frame gate), then `captureChildReport`\n // auto-nests the planner under any enclosing orchestration run. Without\n // this, observe-all would only ever see the sub-agents as standalone\n // fragments — the planner itself never appeared.\n await notifyObservers(this.args.config.observe, result.report);\n captureChildReport(result.report);\n\n return result;\n }\n\n /**\n * Drive the planner lifecycle and return the built result WITHOUT\n * routing it — `run()` owns observer routing + auto-nesting so the\n * unified tree is emitted exactly once.\n */\n private async runPlan(): Promise<PlannerResult<TOutput>> {\n // Completed-run short-circuit. A resume of a snapshot whose run\n // already COMPLETED re-runs nothing — the stored ledger IS the\n // result. A `failed` / `cancelled` snapshot is NOT short-circuited:\n // those are the runs a caller resumes to retry the unfinished\n // frontier after fixing the cause, so they re-enter execution below.\n if (this.args.resumeFrom && this.args.resumeFrom.status === \"completed\") {\n this.rebuildResumedTerminal(\"completed\");\n return this.buildResult();\n }\n\n try {\n if (this.isAborted()) {\n this.markCancelled();\n await this.checkpoint(this.resolveSnapshotStatus());\n return this.buildResult();\n }\n\n // Resume fork — the plan is frozen (re-asking the LLM would burn\n // tokens and risk a different plan that no longer matches the\n // executed-node ledger). Skip generation entirely and execute the\n // re-hydrated plan; the sequential guard / DAG re-seed skip the\n // nodes already terminal in `executedSteps`.\n const plan = this.args.resumeFrom\n ? (this.plan as PlannerPlan)\n : (this.args.options?.approvedPlan ?? (await this.generatePlan()));\n\n if (this.error || !plan) {\n await this.checkpoint(this.resolveSnapshotStatus());\n return this.buildResult();\n }\n\n // On a fresh run, validate the plan (a generated / approved plan\n // could name an unknown capability). A resumed plan was already\n // valid when persisted, so skip re-validation unless drift `force`\n // is implied — re-validating a frozen plan against the same live\n // capabilities is redundant.\n if (!this.args.resumeFrom) {\n this.assertPlanValid(plan);\n\n if (this.error) {\n await this.checkpoint(this.resolveSnapshotStatus());\n return this.buildResult();\n }\n }\n\n this.plan = plan;\n\n // Plan-only mode — surface the validated plan for sign-off and execute\n // NOTHING. `approvedPlan` overrides this (execute the supplied plan),\n // mirroring the documented \"approvedPlan wins\" precedence. A resume is\n // always an execution, never a plan-only short-circuit.\n if (\n !this.args.resumeFrom &&\n this.args.options?.mode === \"plan-only\" &&\n !this.args.options?.approvedPlan\n ) {\n this.awaitingApproval = true;\n return this.buildResult();\n }\n\n await this.executePlan(plan);\n\n await this.finalizeOutput();\n } catch (caught) {\n this.error = this.toAIError(caught);\n }\n\n // Terminal checkpoint — persist the final state so a completed-run\n // resume short-circuits, then optionally drop the snapshot when\n // `deleteOnComplete` is set and the run succeeded. No-op when\n // `durable` is absent.\n await this.checkpoint(this.resolveSnapshotStatus());\n\n if (!this.error && this.args.config.durable?.deleteOnComplete) {\n const outcome = await deletePlannerSnapshot({\n durable: this.args.config.durable,\n runId: this.runId,\n });\n\n if (!outcome.ok) {\n this.logDurableFailure(\"snapshot.delete.failed\", outcome.error);\n }\n }\n\n return this.buildResult();\n }\n\n /**\n * Phase 1 — ask the planning agent for a structured plan. The plan\n * schema (built from the live capability names) is supplied as the\n * agent's per-call `output`, so the model is steered to reference only\n * real capabilities. The planning trip's usage + report roll into the\n * planner's totals regardless of outcome.\n *\n * `feedback` is set only on a RE-plan: the regenerated request is\n * seeded with the executed-step digest plus the caller's feedback so\n * the planner revises the remaining work rather than starting cold.\n */\n private async generatePlan(feedback?: string): Promise<PlannerPlan | undefined> {\n const schema = planSchema([...this.args.capabilities.keys()], this.args.maxSteps);\n\n // `withoutRunFrame` suppresses the planning trip's own self-routing:\n // `absorb` already folds its report into `this.children`, so without\n // this the trip would ALSO route as a standalone top-level trace under\n // observe-all. The planner routes the unified tree once, in `run()`.\n const result = await withoutRunFrame(() =>\n this.args.planningAgent.execute(this.buildPlanPrompt(feedback), {\n output: schema as StandardSchemaV1<unknown>,\n placeholders: this.args.options?.placeholders,\n signal: this.args.options?.signal,\n sessionId: this.args.options?.sessionId,\n }),\n );\n\n this.absorb(result.usage, result.report);\n\n if (result.error) {\n // A schema rejection from the planning trip (e.g. an empty\n // `steps` array tripping the plan schema) is really an invalid\n // plan — re-wrap it into the typed planner contract so callers\n // branch on `PlannerPlanInvalidError` rather than the agent's raw\n // `SchemaValidationError`. Any other child error flows through\n // unchanged.\n this.error =\n result.error instanceof SchemaValidationError\n ? new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): the planner produced no usable plan`,\n { cause: result.error, context: { runId: this.runId } },\n )\n : result.error;\n return undefined;\n }\n\n const plan = result.data as PlannerPlan | undefined;\n\n if (!plan || !Array.isArray(plan.steps) || plan.steps.length === 0) {\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): the planner produced no usable plan`,\n { context: { runId: this.runId } },\n );\n return undefined;\n }\n\n this.assertPlanValid(plan);\n\n if (this.error) {\n return undefined;\n }\n\n return plan;\n }\n\n /**\n * Shared plan-validity guard — used both for a freshly generated plan\n * and for a caller-supplied `approvedPlan`. Sets `this.error` to a\n * typed {@link PlannerPlanInvalidError} when the plan is empty or names\n * an unknown capability; a stale `approvedPlan` thus fails the same way\n * a hallucinated capability does, never silently mis-dispatching.\n */\n private assertPlanValid(plan: PlannerPlan): void {\n if (!Array.isArray(plan.steps) || plan.steps.length === 0) {\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): the planner produced no usable plan`,\n { context: { runId: this.runId } },\n );\n return;\n }\n\n const unknownStep = plan.steps.find((step) => !this.args.capabilities.has(step.capability));\n\n if (unknownStep) {\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): plan references unknown capability \"${unknownStep.capability}\"`,\n { context: { runId: this.runId, capability: unknownStep.capability } },\n );\n }\n }\n\n /**\n * Phase 2 — execute the plan. Branches on `config.dag`: the default is\n * the strict array-order sequential loop (byte-for-byte today's\n * behavior when neither `onStep` nor `replan` is configured); `dag:\n * true` schedules independent `dependsOn` branches in parallel.\n */\n private async executePlan(plan: PlannerPlan): Promise<void> {\n if (this.args.config.dag) {\n return this.executeDag(plan);\n }\n\n return this.executeSequential(plan);\n }\n\n /**\n * Sequential executor — the original strict array-order loop, threading\n * each completed step's output into the next step's input context.\n * Stops at the first step failure or when the abort signal fires\n * between steps; steps beyond `maxSteps` are recorded `skipped`.\n *\n * **Additive hooks (inert by default).** After each step settles it\n * fires the `onStep` directive hook; an `abort` directive stops the run\n * like a failure, and a `replan` directive (or, when `config.replan` is\n * set, an unhandled failure) regenerates the REMAINING plan instead of\n * aborting. With no `onStep` and no `replan`, the behavior is identical\n * to before.\n */\n private async executeSequential(plan: PlannerPlan): Promise<void> {\n const previousOutputs: string[] = [];\n let steps = plan.steps;\n let index = 0;\n\n // Resume re-seed (sequential). The frozen plan's already-completed\n // prefix lives in the persisted ledger; thread its outputs forward and\n // jump the cursor past it so completed nodes are never re-dispatched.\n // Stale non-completed entries (the failed node that crashed the run,\n // and any `skipped` tail) are pruned so the re-run repopulates them\n // cleanly instead of duplicating. No-op on a cold run (empty ledger).\n if (this.args.resumeFrom) {\n index = this.rehydrateSequentialState(steps, previousOutputs);\n }\n\n while (index < steps.length) {\n const step = steps[index] as PlannerStep;\n\n if (index >= this.args.maxSteps) {\n this.recordSkipped(index, step);\n index++;\n continue;\n }\n\n if (this.isAborted()) {\n this.markCancelled();\n this.recordSkipped(index, step);\n index++;\n continue;\n }\n\n const completed = await this.executeStep(index, step, previousOutputs);\n\n const snapshot = this.snapshotFor(index);\n const directive = snapshot\n ? await this.resolveDirective(snapshot, plan, completed)\n : undefined;\n\n if (directive?.type === \"replan\") {\n const remaining = await this.regeneratePlan(directive.feedback);\n\n if (this.error || !remaining) {\n this.skipRest(steps, index + 1);\n return;\n }\n\n // Replace the remaining tail with the regenerated plan and restart\n // the cursor against it (executed steps already recorded stay put).\n // Each new step gets the executed-so-far digest as its context.\n steps = remaining.steps;\n index = 0;\n previousOutputs.length = 0;\n previousOutputs.push(...this.executedDigest());\n continue;\n }\n\n if (directive?.type === \"abort\") {\n // The hook (or an unhandled failure) asked to stop — record the\n // remaining steps as skipped so the report still describes the\n // whole intended plan, then stop.\n this.skipRest(steps, index + 1);\n return;\n }\n\n index++;\n }\n }\n\n /**\n * DAG executor — schedule independent `dependsOn` branches in parallel.\n *\n * Builds the DAG (cycle / unknown-id → `PlannerPlanInvalidError`),\n * then repeatedly computes the ready set (steps whose deps all\n * completed), dispatches up to `maxConcurrency` of them with\n * `Promise.all`, and feeds each step ONLY its dependencies' outputs. A\n * failed step blocks just its descendants (recorded `skipped`);\n * independent branches still settle. With an `output` schema set, the\n * final `data` is the topological SINK's output (multiple sinks → a\n * convergence error).\n */\n private async executeDag(plan: PlannerPlan): Promise<void> {\n const dag = buildDag(plan.steps, this.args.config.name);\n const maxConcurrency = Math.max(1, this.args.config.maxConcurrency ?? 4);\n\n const completed = new Set<string>();\n const done = new Set<string>();\n const outputs = new Map<string, string>();\n const rawOutputs = new Map<string, unknown>();\n let executedCount = 0;\n\n // Resume re-seed (DAG). Re-derive the scheduler's working sets from\n // the persisted ledger so `readyNodes` schedules only the unfinished\n // frontier — completed nodes go straight into `completed` + `done`\n // with their outputs restored; stale non-completed entries are pruned\n // so the re-run repopulates them. One-shot: consumed on the first DAG\n // pass so a later replan recursion (fresh plan, different node ids)\n // doesn't re-seed against a stale ledger. No-op on a cold run.\n if (this.args.resumeFrom && !this.dagResumeConsumed) {\n this.dagResumeConsumed = true;\n executedCount = this.rehydrateDagState(dag, completed, done, outputs, rawOutputs);\n }\n\n while (done.size < dag.nodes.length) {\n if (this.isAborted()) {\n this.markCancelled();\n this.skipDagRest(dag, done);\n return;\n }\n\n const ready = readyNodes(dag, completed, done);\n\n if (ready.length === 0) {\n // No node can advance — every remaining node transitively depends\n // on a failed/skipped ancestor. Record them skipped and stop.\n this.skipDagRest(dag, done);\n return;\n }\n\n const batch = ready.slice(0, maxConcurrency);\n\n const settled = await Promise.all(\n batch.map(async (node) => {\n // `maxSteps` truncation applies to the count of DISPATCHED steps.\n if (executedCount >= this.args.maxSteps) {\n this.recordSkipped(node.index, node.step);\n return { node, ran: false, completed: false };\n }\n\n executedCount++;\n // Feed this step ONLY its dependencies' output digests — the DAG\n // fix for the sequential loop's \"all prior outputs into every\n // step\" behavior. `executeStep` pushes into the array it is\n // given, so a fresh array per node keeps branches isolated.\n const previousOutputs = node.dependencies.map(\n (dependency) => outputs.get(dependency) as string,\n );\n const stepCompleted = await this.executeStep(\n node.index,\n node.step,\n previousOutputs,\n );\n\n if (stepCompleted) {\n // Read the raw output off the snapshot (NOT shared `this.data`,\n // which races under Promise.all) for both the dependent digest\n // and the eventual sink output.\n const rawOutput = this.snapshotFor(node.index)?.output;\n rawOutputs.set(node.id, rawOutput);\n outputs.set(node.id, this.stringifyOutput(node.step.capability, rawOutput));\n }\n\n return { node, ran: true, completed: stepCompleted };\n }),\n );\n\n for (const entry of settled) {\n done.add(entry.node.id);\n\n if (entry.completed) {\n completed.add(entry.node.id);\n }\n }\n\n // Fire the per-step hook for each settled step (in dispatch order).\n let replanFeedback: string | undefined;\n let shouldAbort = false;\n\n for (const entry of settled) {\n if (!entry.ran) {\n continue;\n }\n\n const snapshot = this.snapshotFor(entry.node.index);\n const directive = snapshot\n ? await this.resolveDirective(snapshot, plan, entry.completed)\n : undefined;\n\n if (directive?.type === \"replan\") {\n replanFeedback = directive.feedback;\n } else if (directive?.type === \"abort\") {\n shouldAbort = true;\n }\n }\n\n if (shouldAbort) {\n this.skipDagRest(dag, done);\n return;\n }\n\n if (replanFeedback !== undefined) {\n const remaining = await this.regeneratePlan(replanFeedback);\n\n if (this.error || !remaining) {\n this.skipDagRest(dag, done);\n return;\n }\n\n // Re-plan in DAG mode regenerates the remaining work as a fresh\n // (sequential) plan and runs it through the DAG scheduler again.\n return this.executeDag(remaining);\n }\n }\n\n this.finalizeDagOutput(dag, completed, rawOutputs);\n }\n\n /**\n * Dispatch one plan step through its capability's `executable.execute()`\n * and fold the outcome into the accumulators. Returns `true` when the\n * step completed, `false` when it failed (setting the run error).\n */\n private async executeStep(\n index: number,\n step: PlannerStep,\n previousOutputs: string[],\n ): Promise<boolean> {\n const capability = this.args.capabilities.get(step.capability) as PlannerCapability;\n const stepStart = performance.now();\n const startedAt = new Date().toISOString();\n const input = this.composeStepInput(step, previousOutputs);\n\n // `withoutRunFrame` keeps each capability step nested under the planner\n // only — `absorb` folds its report into `this.children`, so suppressing\n // its self-route prevents a duplicate standalone trace under observe-all.\n const result = await withoutRunFrame(() =>\n capability.executable.execute(input, {\n signal: this.args.options?.signal,\n sessionId: this.args.options?.sessionId,\n }),\n );\n\n const childReport = \"report\" in result ? (result.report as BaseReport) : undefined;\n this.absorb(result.usage, childReport);\n\n const output = this.extractOutput(result);\n const failed = result.error !== undefined;\n\n this.executedSteps.push({\n index,\n step,\n status: failed ? \"failed\" : \"completed\",\n output: failed ? undefined : output,\n error: result.error,\n startedAt,\n endedAt: new Date().toISOString(),\n duration: performance.now() - stepStart,\n usage: result.usage,\n childReport,\n });\n\n // Per-node durable checkpoint. Sits AFTER the node's snapshot is\n // pushed and `absorb` has folded its usage + child report — the only\n // point where the ledger + usage + children are mutually consistent.\n // A completed node is never re-dispatched on resume (the sequential\n // guard / DAG re-seed skip it). Swallow-and-log; no-op when `durable`\n // is absent.\n await this.checkpoint(\"running\");\n\n if (failed) {\n this.error = result.error;\n return false;\n }\n\n previousOutputs.push(this.stringifyOutput(step.capability, output));\n this.data = output as TOutput;\n\n return true;\n }\n\n /**\n * Resolve the steering directive for a just-settled step, shared by the\n * sequential and DAG executors. Fires the user's `onStep` hook, then\n * normalizes the result against the `replan` budget:\n *\n * - explicit `replan` directive — honored only when `config.replan` is\n * set and the budget remains; otherwise downgraded to `continue`.\n * - explicit `abort` — honored.\n * - failed step with no overriding directive — auto-`replan` when\n * `config.replan` is set and the budget remains (feedback = the step\n * error message), else `abort` (today's abort-on-first-failure).\n *\n * Returns `undefined` when the run should simply continue. A returned\n * `replan` directive has ALREADY consumed one unit of the replan budget.\n */\n private async resolveDirective(\n snapshot: PlannerStepSnapshot,\n plan: PlannerPlan,\n completed: boolean,\n ): Promise<PlannerStepDirective | undefined> {\n const hook = this.args.options?.onStep;\n const userDirective = hook ? await hook(snapshot, plan) : undefined;\n\n if (userDirective?.type === \"replan\") {\n if (this.canReplan()) {\n this.replanCount++;\n return userDirective;\n }\n\n // Replan requested but unavailable (no config or budget spent) — fall\n // through to the failure/continue defaults below.\n } else if (userDirective?.type === \"abort\") {\n return { type: \"abort\" };\n } else if (userDirective?.type === \"continue\") {\n return undefined;\n }\n\n if (!completed) {\n if (this.canReplan()) {\n this.replanCount++;\n return { type: \"replan\", feedback: snapshot.error?.message ?? \"step failed\" };\n }\n\n return { type: \"abort\" };\n }\n\n return undefined;\n }\n\n /** Whether a re-plan is configured and the budget has room. */\n private canReplan(): boolean {\n const replan = this.args.config.replan;\n\n return replan !== undefined && this.replanCount < replan.maxReplans;\n }\n\n /**\n * Re-ask the planning agent for a plan over the REMAINING work — a\n * second `generatePlan()` seeded with the executed-step digest plus the\n * caller's feedback. Reuses the exact `generatePlan` plumbing (same\n * schema, same `PlannerPlanInvalidError` handling), so a regenerated\n * plan that is empty or names an unknown capability fails identically.\n * The failed step's error is cleared so the regenerated plan runs\n * cleanly; a fresh failure (or exhausted budget) re-sets it.\n */\n private async regeneratePlan(feedback: string): Promise<PlannerPlan | undefined> {\n this.error = undefined;\n return this.generatePlan(feedback);\n }\n\n /**\n * The executed-so-far digest — one context line per completed step, in\n * execution order. Seeds the regenerated plan's first step so it builds\n * on what already ran.\n */\n private executedDigest(): string[] {\n return this.executedSteps\n .filter((snapshot) => snapshot.status === \"completed\")\n .map((snapshot) => this.stringifyOutput(snapshot.step.capability, snapshot.output));\n }\n\n /** The last-pushed snapshot for a given step index, if any. */\n private snapshotFor(index: number): PlannerStepSnapshot | undefined {\n for (let position = this.executedSteps.length - 1; position >= 0; position--) {\n const snapshot = this.executedSteps[position] as PlannerStepSnapshot;\n\n if (snapshot.index === index) {\n return snapshot;\n }\n }\n\n return undefined;\n }\n\n /** Record every step from `from` onward (in a flat array plan) as skipped. */\n private skipRest(steps: PlannerStep[], from: number): void {\n for (let rest = from; rest < steps.length; rest++) {\n this.recordSkipped(rest, steps[rest] as PlannerStep);\n }\n }\n\n /** Record every not-yet-`done` DAG node as skipped, in plan order. */\n private skipDagRest(dag: PlannerDag, done: ReadonlySet<string>): void {\n for (const node of dag.nodes) {\n if (!done.has(node.id)) {\n this.recordSkipped(node.index, node.step);\n }\n }\n }\n\n /**\n * Set `this.data` from the DAG's topological sink for a configured\n * `output` schema. \"Last completed step\" is meaningless under\n * parallelism, so the sink (the step nothing depends on) is the\n * unambiguous final output. Multiple sinks while an `output` schema is\n * set is a convergence error — a typed `PlannerPlanInvalidError`.\n */\n private finalizeDagOutput(\n dag: PlannerDag,\n completed: ReadonlySet<string>,\n rawOutputs: Map<string, unknown>,\n ): void {\n const schema = this.args.options?.output ?? this.args.config.output;\n\n if (!schema || this.error) {\n return;\n }\n\n const sinks = sinkNodes(dag).filter((node) => completed.has(node.id));\n\n if (sinks.length > 1) {\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): DAG has multiple sinks but an \\`output\\` schema is set — the plan must converge to a single final step`,\n { context: { runId: this.runId, sinks: sinks.map((node) => node.id) } },\n );\n this.data = undefined;\n return;\n }\n\n const sink = sinks[0] as DagNode | undefined;\n this.data = (sink ? rawOutputs.get(sink.id) : undefined) as TOutput | undefined;\n }\n\n /**\n * Phase 3 — when an `output` schema is configured (factory or per-call\n * override), validate the final completed step's output into typed\n * `result.data`. A validation failure replaces the run error and flips\n * the status to failed.\n */\n private async finalizeOutput(): Promise<void> {\n const schema = this.args.options?.output ?? this.args.config.output;\n\n if (!schema || this.error) {\n return;\n }\n\n if (this.data === undefined) {\n // An `output` schema is configured but the final completed step\n // produced nothing to validate — returning `{ data: undefined,\n // error: undefined, status: \"completed\" }` would be a silent\n // contract violation. Surface it as an invalid plan instead.\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): plan completed without producing output for the configured \\`output\\` schema`,\n { context: { runId: this.runId } },\n );\n return;\n }\n\n const validation = await schema[\"~standard\"].validate(this.data);\n\n if (validation.issues) {\n this.error = new PlannerPlanInvalidError(\n `ai.planner(\"${this.args.config.name}\"): final output failed validation`,\n {\n context: {\n runId: this.runId,\n issues: validation.issues.map((issue) => issue.message),\n },\n },\n );\n this.data = undefined;\n return;\n }\n\n this.data = validation.value as TOutput;\n }\n\n /**\n * Phase 4 — fold the accumulators into the planner's own\n * {@link PlannerReport} node and the final {@link PlannerResult}, then\n * stamp lineage across the whole subtree so every child shares this\n * run's root id.\n */\n private buildResult(): PlannerResult<TOutput> {\n const status = this.resolveStatus();\n\n const report: PlannerReport = {\n runId: this.runId,\n rootRunId: this.runId,\n name: this.args.config.name,\n version: this.args.config.version,\n type: \"planner\",\n status,\n // Stamp the terminal error so the observe path surfaces it on the\n // planner span (no result envelope reaches an observer). Absent on\n // a completed run.\n ...(this.error ? { error: this.error } : {}),\n startedAt: this.startedAt,\n endedAt: new Date().toISOString(),\n duration: performance.now() - this.startPerf,\n usage: this.usage,\n children: this.children,\n signature: this.args.signature,\n plan: this.plan,\n executedSteps: this.executedSteps,\n cancelledAt: this.cancelledAt,\n reportSchemaVersion: REPORT_SCHEMA_VERSION,\n };\n\n stampReportLineage(report, {\n rootRunId: this.runId,\n sessionId: this.args.options?.sessionId,\n });\n\n const result: PlannerResult<TOutput> = {\n type: \"planner\",\n data: this.error ? undefined : this.data,\n error: this.error,\n usage: this.usage,\n report,\n };\n\n // Plan-only mode surfaces the validated plan WITHOUT execution so the\n // caller can sign off and re-run with `approvedPlan`.\n if (this.awaitingApproval) {\n result.plan = this.plan;\n }\n\n return result;\n }\n\n /**\n * Resolve the terminal status from the accumulated outcome.\n * `awaiting-approval` (plan-only short-circuit) wins over everything —\n * nothing executed, so neither cancellation nor error applies.\n * Otherwise cancelled wins over failed (an abort that also produced a\n * step error still reads as cancelled); failed wins over completed.\n */\n private resolveStatus(): PlannerReport[\"status\"] {\n if (this.awaitingApproval) {\n return \"awaiting-approval\";\n }\n\n if (this.cancelledAt !== undefined) {\n return \"cancelled\";\n }\n\n if (this.error) {\n return \"failed\";\n }\n\n return \"completed\";\n }\n\n /**\n * Build the prompt handed to the planning agent. On the first pass this\n * is just the user's goal (byte-for-byte unchanged). On a RE-plan it\n * prepends the executed-step digest and the steering feedback so the\n * planner revises the remaining work.\n */\n private buildPlanPrompt(feedback?: string): string {\n if (feedback === undefined) {\n return this.args.goal;\n }\n\n const digest = this.executedDigest();\n const sections: string[] = [`Goal: ${this.args.goal}`, \"\"];\n\n if (digest.length > 0) {\n sections.push(\"Steps already completed:\", ...digest, \"\");\n }\n\n sections.push(\n `Feedback requiring a revised plan: ${feedback}`,\n \"\",\n \"Produce a plan for the REMAINING work only.\",\n );\n\n return sections.join(\"\\n\");\n }\n\n /**\n * Compose a step's effective input: the step's own `input`, prefixed\n * with a compact digest of every prior step's output so a downstream\n * capability can build on what ran before it. No prior output → the\n * step's raw input.\n */\n private composeStepInput(step: PlannerStep, previousOutputs: string[]): string {\n if (previousOutputs.length === 0) {\n return step.input;\n }\n\n return [\n \"Context from earlier steps:\",\n ...previousOutputs,\n \"\",\n `Task: ${step.input}`,\n ].join(\"\\n\");\n }\n\n /**\n * Pull the usable output off a capability's result. Prefers structured\n * `data` (agents/workflows with an `output` schema, tools), and falls\n * back to an agent's raw `text` when no structured data was produced —\n * the common case for a plain text-producing capability agent.\n */\n private extractOutput(result: BaseResult): unknown {\n const shaped = result as { data?: unknown; text?: unknown };\n\n if (shaped.data !== undefined) {\n return shaped.data;\n }\n\n if (typeof shaped.text === \"string\") {\n return shaped.text;\n }\n\n return undefined;\n }\n\n /** Serialize a capability output into a single context line for the next step. */\n private stringifyOutput(capability: string, output: unknown): string {\n if (output === undefined) {\n return `- ${capability}: (no output)`;\n }\n\n if (typeof output === \"string\") {\n return `- ${capability}: ${output}`;\n }\n\n return `- ${capability}: ${JSON.stringify(output)}`;\n }\n\n /** Push a `skipped` snapshot for a step the planner never dispatched. */\n private recordSkipped(index: number, step: PlannerStep): void {\n const now = new Date().toISOString();\n\n this.executedSteps.push({\n index,\n step,\n status: \"skipped\",\n startedAt: now,\n endedAt: now,\n duration: 0,\n usage: { input: 0, output: 0, total: 0 },\n });\n }\n\n /** Fold a child's usage + report node into the planner's accumulators. */\n private absorb(usage: Usage, report: BaseReport | undefined): void {\n this.mergeUsage(this.usage, usage);\n\n if (report) {\n this.children.push(report);\n }\n }\n\n /**\n * Add a child's usage into the running total. Mirrors the batch\n * primitive's rollup: scalar token channels sum directly, optional\n * sub-channels accumulate only when reported, and the cost breakdown\n * merges via {@link accumulateCost} so one unpriced child can't erase\n * priced siblings.\n */\n private mergeUsage(target: Usage, child: Usage): void {\n target.input += child.input;\n target.output += child.output;\n target.total += child.total;\n\n if (child.cachedTokens !== undefined) {\n target.cachedTokens = (target.cachedTokens ?? 0) + child.cachedTokens;\n }\n\n if (child.reasoningTokens !== undefined) {\n target.reasoningTokens = (target.reasoningTokens ?? 0) + child.reasoningTokens;\n }\n\n if (child.cacheWriteTokens !== undefined) {\n target.cacheWriteTokens = (target.cacheWriteTokens ?? 0) + child.cacheWriteTokens;\n }\n\n const mergedCost = accumulateCost(target.cost, child.cost);\n\n if (mergedCost !== undefined) {\n target.cost = mergedCost;\n }\n }\n\n /**\n * Re-derive the sequential cursor + prior-output context from the\n * persisted ledger on resume. Threads every already-`completed` node's\n * output into `previousOutputs`, returns the first index NOT completed\n * as the resume cursor, and prunes stale non-completed ledger entries\n * (the failed node + any skipped tail) at-or-after that cursor so the\n * re-run repopulates them without duplicating.\n */\n private rehydrateSequentialState(\n steps: PlannerStep[],\n previousOutputs: string[],\n ): number {\n let cursor = 0;\n\n for (let index = 0; index < steps.length; index++) {\n const snapshot = this.snapshotFor(index);\n\n if (snapshot?.status === \"completed\") {\n const step = steps[index] as PlannerStep;\n previousOutputs.push(this.stringifyOutput(step.capability, snapshot.output));\n cursor = index + 1;\n continue;\n }\n\n // First non-completed index — this is where the re-run resumes.\n break;\n }\n\n // Drop any ledger entries at-or-after the cursor (failed / skipped\n // from the crashed run) so the resumed loop's pushes don't duplicate.\n this.pruneLedgerFrom(cursor);\n\n return cursor;\n }\n\n /**\n * Re-derive the DAG scheduler's working sets from the persisted ledger\n * on resume. Completed nodes go into `completed` + `done` with their\n * string + raw outputs restored (so dependents read the right context);\n * stale non-completed entries are pruned so the re-run repopulates them.\n * Returns the count of nodes already dispatched (for the `maxSteps`\n * truncation budget).\n */\n private rehydrateDagState(\n dag: PlannerDag,\n completed: Set<string>,\n done: Set<string>,\n outputs: Map<string, string>,\n rawOutputs: Map<string, unknown>,\n ): number {\n const completedIndices = new Set<number>();\n\n for (const node of dag.nodes) {\n const snapshot = this.snapshotFor(node.index);\n\n if (snapshot?.status !== \"completed\") {\n continue;\n }\n\n completed.add(node.id);\n done.add(node.id);\n completedIndices.add(node.index);\n rawOutputs.set(node.id, snapshot.output);\n outputs.set(node.id, this.stringifyOutput(node.step.capability, snapshot.output));\n }\n\n // Prune every non-completed ledger entry so the re-run's pushes don't\n // duplicate the failed / skipped frontier from the crashed run.\n const retained = this.executedSteps.filter((snapshot) =>\n completedIndices.has(snapshot.index),\n );\n this.executedSteps.length = 0;\n this.executedSteps.push(...retained);\n\n return completedIndices.size;\n }\n\n /**\n * Drop every ledger entry whose index is at or after `from`. Used by\n * the sequential resume re-seed to clear the crashed run's failed /\n * skipped frontier before the re-run repopulates it.\n */\n private pruneLedgerFrom(from: number): void {\n const retained = this.executedSteps.filter((snapshot) => snapshot.index < from);\n this.executedSteps.length = 0;\n this.executedSteps.push(...retained);\n }\n\n /**\n * Map the run's terminal outcome to the persisted snapshot status.\n * `awaiting-approval` (plan-only) never persists a durable snapshot\n * (resume is always an execution), so it folds to `running` here —\n * but the durable + plan-only combination is disallowed at the call\n * site, so this path is effectively unreachable.\n */\n private resolveSnapshotStatus(): PlannerSnapshotStatus {\n if (this.cancelledAt !== undefined) {\n return \"cancelled\";\n }\n\n if (this.error) {\n return \"failed\";\n }\n\n if (this.awaitingApproval) {\n return \"running\";\n }\n\n return \"completed\";\n }\n\n /**\n * Build and persist a {@link PlannerSnapshot} from the current\n * accumulators. The per-node and terminal checkpoints both route\n * through here. No-op when `durable` is absent. A failed persist is\n * logged and swallowed (never aborts the run), matching the supervisor\n * / workflow checkpoint policy.\n */\n private async checkpoint(status: PlannerSnapshotStatus): Promise<void> {\n if (!this.args.config.durable || !this.plan) {\n return;\n }\n\n const outcome = await persistPlannerSnapshot({\n durable: this.args.config.durable,\n runId: this.runId,\n plannerName: this.args.config.name,\n signature: this.args.signature,\n version: this.args.config.version,\n goal: this.args.goal,\n plan: this.plan,\n executedSteps: this.executedSteps,\n usage: this.usage,\n children: this.children,\n replanCount: this.replanCount,\n status,\n startedAt: this.startedAt,\n });\n\n if (!outcome.ok) {\n this.logDurableFailure(\"snapshot.persist.failed\", outcome.error);\n }\n }\n\n /**\n * Re-derive the terminal state when a resume short-circuits a snapshot\n * whose run already COMPLETED. The persisted ledger is the\n * authoritative outcome — `this.data` is restored from the last\n * completed node so the rebuilt result carries the final output.\n *\n * Only reached for a `completed` snapshot — `failed` / `cancelled`\n * snapshots re-enter execution to retry the unfinished frontier instead.\n */\n private rebuildResumedTerminal(_status: PlannerSnapshotStatus): void {\n const lastCompleted = [...this.executedSteps]\n .reverse()\n .find((snapshot) => snapshot.status === \"completed\");\n\n if (lastCompleted) {\n this.data = lastCompleted.output as TOutput;\n }\n }\n\n /** Structured-log a durable persist/delete failure. */\n private logDurableFailure(action: string, error: unknown): void {\n log.warn(\"ai.planner\", action, \"durable snapshot operation failed\", {\n runId: this.runId,\n planner: this.args.config.name,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n /** Whether the caller's abort signal has fired. */\n private isAborted(): boolean {\n return this.args.options?.signal?.aborted === true;\n }\n\n /** Record a cancellation observation, setting the run error once. */\n private markCancelled(): void {\n if (this.cancelledAt !== undefined) {\n return;\n }\n\n this.cancelledAt = new Date().toISOString();\n\n const reason = this.args.options?.signal?.reason;\n\n this.error = new PlannerCancelledError(\n `ai.planner(\"${this.args.config.name}\"): run cancelled`,\n {\n cancelledAt: this.cancelledAt,\n reason: typeof reason === \"string\" ? reason : undefined,\n context: { runId: this.runId },\n },\n );\n }\n\n /** Normalize any thrown value into a typed {@link AIError}. */\n private toAIError(caught: unknown): AIError {\n if (caught instanceof AIError) {\n return caught;\n }\n\n const message = caught instanceof Error ? caught.message : String(caught);\n\n return new PlannerFailedError(`ai.planner(\"${this.args.config.name}\"): ${message}`, {\n cause: caught,\n context: { runId: this.runId },\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner.d.mts","names":[],"sources":["../../../../../../../ai/src/planner/planner.ts"],"mappings":";;;;;;AAgDA;;;;;;;;;;;;;;;;AAE0B;;;;;;;;;;;iBAFV,OAAA,
|
|
1
|
+
{"version":3,"file":"planner.d.mts","names":[],"sources":["../../../../../../../ai/src/planner/planner.ts"],"mappings":";;;;;;AAgDA;;;;;;;;;;;;;;;;AAE0B;;;;;;;;;;;iBAFV,OAAA,mBAAA,CACd,MAAA,EAAQ,aAAA,CAAc,OAAA,IACrB,eAAA,CAAgB,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner.mjs","names":[],"sources":["../../../../../../../ai/src/planner/planner.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport { agent } from \"../agent/agent\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport type { PlannerCapability } from \"../contracts/planner/planner-capability.type\";\nimport type { PlannerConfig } from \"../contracts/planner/planner-config.type\";\nimport type {\n PlannerExecuteOptions,\n PlannerResumeOptions,\n} from \"../contracts/planner/planner-execute-options.type\";\nimport type { PlannerResult } from \"../contracts/planner/planner-result.type\";\nimport type { PlannerContract } from \"../contracts/planner/planner.contract\";\nimport { PlannerFailedError } from \"../errors\";\nimport { buildPlanSystemPrompt } from \"./plan-prompt\";\nimport { PlannerRun } from \"./planner-run\";\nimport { computeSignature } from \"./signature\";\nimport { loadPlannerSnapshotForResume } from \"./snapshot\";\n\nconst LOG_MODULE = \"ai.planner\";\n\n/**\n * `ai.planner(config)` — construct a {@link PlannerContract}.\n *\n * Validates the config at author time (throws {@link PlannerFailedError}\n * on a bad shape), builds (or adopts) the plan-generation agent, computes\n * a stable structural signature, and returns an instance satisfying\n * `ExecutableContract` so the planner composes into supervisors,\n * orchestrators, and outer agents through the same uniform surface.\n *\n * At `execute(goal)` the planner asks its LLM for an ordered plan over\n * the registered `capabilities`, then executes that plan step-by-step\n * through each capability's own `execute()` — reusing the existing\n * executable machinery rather than forking it — and returns the unified\n * `{ data, report, usage, error }` envelope with `report.type ===\n * \"planner\"`.\n *\n * @example\n * const research = ai.planner({\n * name: \"research-assistant\",\n * model: ai.openai.model({ name: \"gpt-4o\" }),\n * capabilities: [\n * { name: \"search\", description: \"Search the web\", executable: searchAgent },\n * { name: \"write\", description: \"Draft a summary\", executable: writerAgent },\n * ],\n * maxSteps: 6,\n * });\n *\n * const { data, report } = await research.execute(\"Compare React vs Vue in 2026\");\n */\nexport function planner<TOutput = unknown>(\n config: PlannerConfig<TOutput>,\n): PlannerContract<TOutput> {\n validateConfig(config);\n\n const maxSteps = config.maxSteps ?? 10;\n const capabilities = new Map<string, PlannerCapability>();\n\n for (const capability of config.capabilities) {\n capabilities.set(capability.name, capability);\n }\n\n const signature = computeSignature(config.name, config.capabilities);\n const planningAgent = resolvePlanningAgent(config, maxSteps);\n\n async function execute(\n goal: string,\n options?: PlannerExecuteOptions<TOutput>,\n ): Promise<PlannerResult<TOutput>> {\n log.debug(LOG_MODULE, \"execute\", \"Planner run starting\", {\n name: config.name,\n capabilities: capabilities.size,\n });\n\n return new PlannerRun<TOutput>({\n config,\n capabilities,\n maxSteps,\n signature,\n planningAgent,\n goal,\n options,\n }).run();\n }\n\n async function resume(\n runId: string,\n options?: PlannerResumeOptions<TOutput>,\n ): Promise<PlannerResult<TOutput>> {\n // Load the persisted snapshot and run the drift check (throws\n // PlannerDriftError on a structural mismatch unless `{ force: true }`).\n const snapshot = await loadPlannerSnapshotForResume({\n durable: config.durable,\n plannerName: config.name,\n signature,\n runId,\n options: options as PlannerResumeOptions<unknown> | undefined,\n });\n\n return new PlannerRun<TOutput>({\n config,\n capabilities,\n maxSteps,\n signature,\n planningAgent,\n goal: snapshot.goal,\n options: { ...options, runId } as PlannerExecuteOptions<TOutput>,\n resumeFrom: snapshot,\n }).run();\n }\n\n return {\n name: config.name,\n signature,\n execute,\n resume,\n };\n}\n\n/**\n * Resolve the plan-generation agent: either adopt the dev's `planner`\n * agent, or build an internal one from `model` with the generated\n * plan-system-prompt baked on. The plan output schema is supplied\n * per-call in {@link PlannerRun}, so it isn't baked here.\n *\n * **`maxSteps` and BYO planners.** In `model` mode the cap is woven\n * into the generated plan-system-prompt *and* the per-call plan schema\n * (`steps.maxItems`). In `planner` (BYO) mode the dev owns the prompt,\n * so the cap is communicated only through that same per-call schema —\n * and, regardless of mode, {@link PlannerRun} truncates any over-long\n * plan to `skipped` at execution time, so the cap is always enforced.\n */\nfunction resolvePlanningAgent<TOutput>(\n config: PlannerConfig<TOutput>,\n maxSteps: number,\n): AgentContract<unknown> {\n if (config.planner) {\n return config.planner;\n }\n\n const systemPrompt = buildPlanSystemPrompt(\n config.capabilities,\n maxSteps,\n config.systemPrompt,\n config.dag === true,\n );\n\n return agent({\n name: `${config.name}-planner`,\n description: \"Generates an ordered execution plan over the planner's capabilities.\",\n model: config.model!,\n systemPrompt,\n maxTrips: 1,\n });\n}\n\n/**\n * Factory-time validation. Surfaces every violation as a typed\n * {@link PlannerFailedError} tagged `authoring: true`, mirroring the\n * supervisor/orchestrator authoring-error convention.\n */\nfunction validateConfig<TOutput>(config: PlannerConfig<TOutput>): void {\n if (!config.name || typeof config.name !== \"string\") {\n throw new PlannerFailedError(\"ai.planner: `name` is required and must be a string\", {\n context: { authoring: true },\n });\n }\n\n const hasModel = config.model !== undefined;\n const hasPlanner = config.planner !== undefined;\n\n if (!hasModel && !hasPlanner) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): one of \\`model\\` or \\`planner\\` is required`,\n { context: { authoring: true } },\n );\n }\n\n if (hasModel && hasPlanner) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): \\`model\\` and \\`planner\\` are mutually exclusive — configure exactly one`,\n { context: { authoring: true } },\n );\n }\n\n if (!Array.isArray(config.capabilities) || config.capabilities.length === 0) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): at least one capability is required`,\n { context: { authoring: true } },\n );\n }\n\n const seen = new Set<string>();\n\n for (const capability of config.capabilities) {\n if (!capability || typeof capability.name !== \"string\" || capability.name.length === 0) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): every capability needs a non-empty \\`name\\``,\n { context: { authoring: true } },\n );\n }\n\n if (typeof capability.description !== \"string\" || capability.description.length === 0) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): capability \"${capability.name}\" needs a \\`description\\``,\n { context: { authoring: true } },\n );\n }\n\n if (!capability.executable || typeof capability.executable.execute !== \"function\") {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): capability \"${capability.name}\" needs an \\`executable\\` with an execute() method`,\n { context: { authoring: true } },\n );\n }\n\n if (seen.has(capability.name)) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): duplicate capability name \"${capability.name}\"`,\n { context: { authoring: true } },\n );\n }\n\n seen.add(capability.name);\n }\n\n if (config.maxSteps !== undefined && config.maxSteps < 1) {\n throw new PlannerFailedError(`ai.planner(\"${config.name}\"): \\`maxSteps\\` must be >= 1`, {\n context: { authoring: true, maxSteps: config.maxSteps },\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"planner.mjs","names":[],"sources":["../../../../../../../ai/src/planner/planner.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport { agent } from \"../agent/agent\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport type { PlannerCapability } from \"../contracts/planner/planner-capability.type\";\nimport type { PlannerConfig } from \"../contracts/planner/planner-config.type\";\nimport type {\n PlannerExecuteOptions,\n PlannerResumeOptions,\n} from \"../contracts/planner/planner-execute-options.type\";\nimport type { PlannerResult } from \"../contracts/planner/planner-result.type\";\nimport type { PlannerContract } from \"../contracts/planner/planner.contract\";\nimport { PlannerFailedError } from \"../errors\";\nimport { buildPlanSystemPrompt } from \"./plan-prompt\";\nimport { PlannerRun } from \"./planner-run\";\nimport { computeSignature } from \"./signature\";\nimport { loadPlannerSnapshotForResume } from \"./snapshot\";\n\nconst LOG_MODULE = \"ai.planner\";\n\n/**\n * `ai.planner(config)` — construct a {@link PlannerContract}.\n *\n * Validates the config at author time (throws {@link PlannerFailedError}\n * on a bad shape), builds (or adopts) the plan-generation agent, computes\n * a stable structural signature, and returns an instance satisfying\n * `ExecutableContract` so the planner composes into supervisors,\n * orchestrators, and outer agents through the same uniform surface.\n *\n * At `execute(goal)` the planner asks its LLM for an ordered plan over\n * the registered `capabilities`, then executes that plan step-by-step\n * through each capability's own `execute()` — reusing the existing\n * executable machinery rather than forking it — and returns the unified\n * `{ data, report, usage, error }` envelope with `report.type ===\n * \"planner\"`.\n *\n * @example\n * const research = ai.planner({\n * name: \"research-assistant\",\n * model: ai.openai.model({ name: \"gpt-4o\" }),\n * capabilities: [\n * { name: \"search\", description: \"Search the web\", executable: searchAgent },\n * { name: \"write\", description: \"Draft a summary\", executable: writerAgent },\n * ],\n * maxSteps: 6,\n * });\n *\n * const { data, report } = await research.execute(\"Compare React vs Vue in 2026\");\n */\nexport function planner<TOutput = unknown>(\n config: PlannerConfig<TOutput>,\n): PlannerContract<TOutput> {\n validateConfig(config);\n\n const maxSteps = config.maxSteps ?? 10;\n const capabilities = new Map<string, PlannerCapability>();\n\n for (const capability of config.capabilities) {\n capabilities.set(capability.name, capability);\n }\n\n const signature = computeSignature(config.name, config.capabilities);\n const planningAgent = resolvePlanningAgent(config, maxSteps);\n\n async function execute(\n goal: string,\n options?: PlannerExecuteOptions<TOutput>,\n ): Promise<PlannerResult<TOutput>> {\n log.debug(LOG_MODULE, \"execute\", \"Planner run starting\", {\n name: config.name,\n capabilities: capabilities.size,\n });\n\n return new PlannerRun<TOutput>({\n config,\n capabilities,\n maxSteps,\n signature,\n planningAgent,\n goal,\n options,\n }).run();\n }\n\n async function resume(\n runId: string,\n options?: PlannerResumeOptions<TOutput>,\n ): Promise<PlannerResult<TOutput>> {\n // Load the persisted snapshot and run the drift check (throws\n // PlannerDriftError on a structural mismatch unless `{ force: true }`).\n const snapshot = await loadPlannerSnapshotForResume({\n durable: config.durable,\n plannerName: config.name,\n signature,\n runId,\n options: options as PlannerResumeOptions<unknown> | undefined,\n });\n\n return new PlannerRun<TOutput>({\n config,\n capabilities,\n maxSteps,\n signature,\n planningAgent,\n goal: snapshot.goal,\n options: { ...options, runId } as PlannerExecuteOptions<TOutput>,\n resumeFrom: snapshot,\n }).run();\n }\n\n return {\n name: config.name,\n signature,\n execute,\n resume,\n };\n}\n\n/**\n * Resolve the plan-generation agent: either adopt the dev's `planner`\n * agent, or build an internal one from `model` with the generated\n * plan-system-prompt baked on. The plan output schema is supplied\n * per-call in {@link PlannerRun}, so it isn't baked here.\n *\n * **`maxSteps` and BYO planners.** In `model` mode the cap is woven\n * into the generated plan-system-prompt *and* the per-call plan schema\n * (`steps.maxItems`). In `planner` (BYO) mode the dev owns the prompt,\n * so the cap is communicated only through that same per-call schema —\n * and, regardless of mode, {@link PlannerRun} truncates any over-long\n * plan to `skipped` at execution time, so the cap is always enforced.\n */\nfunction resolvePlanningAgent<TOutput>(\n config: PlannerConfig<TOutput>,\n maxSteps: number,\n): AgentContract<unknown> {\n if (config.planner) {\n return config.planner;\n }\n\n const systemPrompt = buildPlanSystemPrompt(\n config.capabilities,\n maxSteps,\n config.systemPrompt,\n config.dag === true,\n );\n\n return agent({\n name: `${config.name}-planner`,\n description: \"Generates an ordered execution plan over the planner's capabilities.\",\n model: config.model!,\n systemPrompt,\n maxTrips: 1,\n });\n}\n\n/**\n * Factory-time validation. Surfaces every violation as a typed\n * {@link PlannerFailedError} tagged `authoring: true`, mirroring the\n * supervisor/orchestrator authoring-error convention.\n */\nfunction validateConfig<TOutput>(config: PlannerConfig<TOutput>): void {\n if (!config.name || typeof config.name !== \"string\") {\n throw new PlannerFailedError(\"ai.planner: `name` is required and must be a string\", {\n context: { authoring: true },\n });\n }\n\n const hasModel = config.model !== undefined;\n const hasPlanner = config.planner !== undefined;\n\n if (!hasModel && !hasPlanner) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): one of \\`model\\` or \\`planner\\` is required`,\n { context: { authoring: true } },\n );\n }\n\n if (hasModel && hasPlanner) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): \\`model\\` and \\`planner\\` are mutually exclusive — configure exactly one`,\n { context: { authoring: true } },\n );\n }\n\n if (!Array.isArray(config.capabilities) || config.capabilities.length === 0) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): at least one capability is required`,\n { context: { authoring: true } },\n );\n }\n\n const seen = new Set<string>();\n\n for (const capability of config.capabilities) {\n if (!capability || typeof capability.name !== \"string\" || capability.name.length === 0) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): every capability needs a non-empty \\`name\\``,\n { context: { authoring: true } },\n );\n }\n\n if (typeof capability.description !== \"string\" || capability.description.length === 0) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): capability \"${capability.name}\" needs a \\`description\\``,\n { context: { authoring: true } },\n );\n }\n\n if (!capability.executable || typeof capability.executable.execute !== \"function\") {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): capability \"${capability.name}\" needs an \\`executable\\` with an execute() method`,\n { context: { authoring: true } },\n );\n }\n\n if (seen.has(capability.name)) {\n throw new PlannerFailedError(\n `ai.planner(\"${config.name}\"): duplicate capability name \"${capability.name}\"`,\n { context: { authoring: true } },\n );\n }\n\n seen.add(capability.name);\n }\n\n if (config.maxSteps !== undefined && config.maxSteps < 1) {\n throw new PlannerFailedError(`ai.planner(\"${config.name}\"): \\`maxSteps\\` must be >= 1`, {\n context: { authoring: true, maxSteps: config.maxSteps },\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signature.mjs","names":[],"sources":["../../../../../../../ai/src/planner/signature.ts"],"sourcesContent":["import type { PlannerCapability } from \"../contracts/planner/planner-capability.type\";\n\n/**\n * Delimiter between capability names in a planner signature. A NUL\n * control character can never appear in a real capability name, so it\n * keeps name boundaries unambiguous — a single capability literally\n * named `\"a,b\"` can never collide with the two capabilities\n * `[\"a\", \"b\"]` (a comma delimiter would render both as `caps:a,b`).\n */\nconst CAPABILITY_DELIMITER = String.fromCharCode(0);\n\n/**\n * Compute a stable structural fingerprint for a planner definition —\n * the planner name plus its ordered capability names. Stamped on every\n * report node the planner produces so trace consumers can tell runs of\n * structurally-different planners apart even when they share a name.\n *\n * Deliberately coarse: it captures WHICH capabilities the planner can\n * dispatch (and in what registration order), not their descriptions or\n * the underlying executables' internals — those don't change the set of\n * plans the planner can produce.\n */\nexport function computeSignature(name: string, capabilities: PlannerCapability[]): string {\n const capabilityNames = capabilities\n .map((capability) => capability.name)\n .join(CAPABILITY_DELIMITER);\n\n return `planner:${name}|caps:${capabilityNames}`;\n}\n"],"mappings":";;;;;;;;AASA,MAAM,uBAAuB,OAAO,aAAa,CAAC;;;;;;;;;;;;AAalD,SAAgB,iBAAiB,MAAc,cAA2C;CAKxF,OAAO,WAAW,KAAK,QAJC,aACrB,KAAK,eAAe,WAAW,IAAI,
|
|
1
|
+
{"version":3,"file":"signature.mjs","names":[],"sources":["../../../../../../../ai/src/planner/signature.ts"],"sourcesContent":["import type { PlannerCapability } from \"../contracts/planner/planner-capability.type\";\n\n/**\n * Delimiter between capability names in a planner signature. A NUL\n * control character can never appear in a real capability name, so it\n * keeps name boundaries unambiguous — a single capability literally\n * named `\"a,b\"` can never collide with the two capabilities\n * `[\"a\", \"b\"]` (a comma delimiter would render both as `caps:a,b`).\n */\nconst CAPABILITY_DELIMITER = String.fromCharCode(0);\n\n/**\n * Compute a stable structural fingerprint for a planner definition —\n * the planner name plus its ordered capability names. Stamped on every\n * report node the planner produces so trace consumers can tell runs of\n * structurally-different planners apart even when they share a name.\n *\n * Deliberately coarse: it captures WHICH capabilities the planner can\n * dispatch (and in what registration order), not their descriptions or\n * the underlying executables' internals — those don't change the set of\n * plans the planner can produce.\n */\nexport function computeSignature(name: string, capabilities: PlannerCapability[]): string {\n const capabilityNames = capabilities\n .map((capability) => capability.name)\n .join(CAPABILITY_DELIMITER);\n\n return `planner:${name}|caps:${capabilityNames}`;\n}\n"],"mappings":";;;;;;;;AASA,MAAM,uBAAuB,OAAO,aAAa,CAAC;;;;;;;;;;;;AAalD,SAAgB,iBAAiB,MAAc,cAA2C;CAKxF,OAAO,WAAW,KAAK,QAJC,aACrB,KAAK,eAAe,WAAW,IAAI,EACnC,KAAK,oBAEqC;AAC/C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.mjs","names":[],"sources":["../../../../../../../ai/src/planner/snapshot.ts"],"sourcesContent":["import { resolveDefaultSnapshotStore } from \"../config\";\nimport type { SnapshotStore } from \"../contracts/orchestrator/snapshot-store.contract\";\nimport type { PlannerResumeOptions } from \"../contracts/planner/planner-execute-options.type\";\nimport type { PlannerPlan } from \"../contracts/planner/planner-plan.type\";\nimport type { PlannerStepSnapshot } from \"../contracts/planner/planner-result.type\";\nimport type {\n PlannerSnapshot,\n PlannerSnapshotStatus,\n} from \"../contracts/planner/planner-snapshot.type\";\nimport type { BaseReport } from \"../contracts/result/base-report.type\";\nimport type { Usage } from \"../contracts/result/usage.type\";\nimport { PlannerDriftError, PlannerFailedError } from \"../errors\";\n\n/**\n * The planner's `durable` config, narrowed to the fields the snapshot\n * helpers read.\n */\nexport type PlannerDurableConfig = {\n store?: SnapshotStore<PlannerSnapshot>;\n deleteOnComplete?: boolean;\n};\n\n/**\n * Resolve the effective {@link SnapshotStore}: the planner's own\n * `durable.store` wins; absent that, fall back to the global default set\n * via `ai.config({ defaultSnapshotStore })`.\n *\n * The global default is typed for the supervisor snapshot shape, but\n * every store impl keys purely by `runId` and round-trips whatever\n * envelope it is handed — so it serves a `PlannerSnapshot` just as well.\n * The cast re-tags the shape at this single boundary (Option B); the\n * planner only ever hands it a `PlannerSnapshot`.\n */\nfunction resolveSnapshotStore(\n durable: PlannerDurableConfig | undefined,\n): SnapshotStore<PlannerSnapshot> | undefined {\n return (\n durable?.store ??\n (resolveDefaultSnapshotStore() as SnapshotStore<PlannerSnapshot> | undefined)\n );\n}\n\nexport type PersistPlannerParams = {\n durable: PlannerDurableConfig | undefined;\n runId: string;\n plannerName: string;\n signature: string;\n version?: string;\n goal: string;\n plan: PlannerPlan;\n executedSteps: PlannerStepSnapshot[];\n usage: Usage;\n children: BaseReport[];\n replanCount: number;\n status: PlannerSnapshotStatus;\n startedAt: string;\n};\n\nexport type PersistOutcome = { ok: true } | { ok: false; error: unknown };\n\n/**\n * Write the current run state to the resolved snapshot store. No-op\n * (returns `{ ok: true }`) when neither `durable.store` nor the global\n * `defaultSnapshotStore` is configured — the common non-durable path.\n * Failures are returned as `{ ok: false }` rather than thrown so the\n * engine can surface them via logs without aborting the run.\n */\nexport async function persistPlannerSnapshot(\n params: PersistPlannerParams,\n): Promise<PersistOutcome> {\n const store = resolveSnapshotStore(params.durable);\n\n if (!store) {\n return { ok: true };\n }\n\n const snapshot: PlannerSnapshot = {\n runId: params.runId,\n plannerName: params.plannerName,\n signature: params.signature,\n version: params.version,\n goal: params.goal,\n plan: params.plan,\n executedSteps: params.executedSteps,\n usage: params.usage,\n children: params.children,\n replanCount: params.replanCount,\n status: params.status,\n startedAt: params.startedAt,\n savedAt: new Date().toISOString(),\n };\n\n try {\n await store.save(snapshot);\n\n return { ok: true };\n } catch (error) {\n return { ok: false, error };\n }\n}\n\n/**\n * Delete a persisted snapshot — used after a successful run when\n * `durable.deleteOnComplete` is set. Never throws. No-op (ok) when no\n * store is configured.\n */\nexport async function deletePlannerSnapshot(params: {\n durable: PlannerDurableConfig | undefined;\n runId: string;\n}): Promise<PersistOutcome> {\n const store = resolveSnapshotStore(params.durable);\n\n if (!store) {\n return { ok: true };\n }\n\n try {\n await store.delete(params.runId);\n\n return { ok: true };\n } catch (error) {\n return { ok: false, error };\n }\n}\n\n/**\n * Load a persisted snapshot for `resume()` and run the drift check.\n * Throws `PlannerFailedError` when no store is configured or when the run\n * is missing; throws `PlannerDriftError` when the stored signature\n * doesn't match the current definition (unless `force` is set).\n */\nexport async function loadPlannerSnapshotForResume(params: {\n durable: PlannerDurableConfig | undefined;\n plannerName: string;\n signature: string;\n runId: string;\n options?: PlannerResumeOptions<unknown>;\n}): Promise<PlannerSnapshot> {\n const store = resolveSnapshotStore(params.durable);\n\n if (!store) {\n throw new PlannerFailedError(\n `ai.planner(\"${params.plannerName}\"): no durable store configured — set \\`durable: { store }\\` on the config or call \\`ai.config({ defaultSnapshotStore })\\` at boot before calling resume()`,\n { context: { runId: params.runId } },\n );\n }\n\n const snapshot = (await store.load(params.runId)) ?? null;\n\n if (!snapshot) {\n throw new PlannerFailedError(\n `ai.planner(\"${params.plannerName}\"): no snapshot for runId \"${params.runId}\"`,\n { context: { runId: params.runId } },\n );\n }\n\n if (!params.options?.force && snapshot.signature !== params.signature) {\n throw new PlannerDriftError(\n `ai.planner(\"${params.plannerName}\") signature drift on resume`,\n {\n savedSignature: snapshot.signature,\n currentSignature: params.signature,\n runId: params.runId,\n },\n );\n }\n\n return snapshot;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiCA,SAAS,qBACP,SAC4C;CAC5C,OACE,SAAS,SACR,4BAA4B;AAEjC;;;;;;;;AA2BA,eAAsB,uBACpB,QACyB;CACzB,MAAM,QAAQ,qBAAqB,OAAO,OAAO;CAEjD,IAAI,CAAC,OACH,OAAO,EAAE,IAAI,KAAK;CAGpB,MAAM,WAA4B;EAChC,OAAO,OAAO;EACd,aAAa,OAAO;EACpB,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,MAAM,OAAO;EACb,MAAM,OAAO;EACb,eAAe,OAAO;EACtB,OAAO,OAAO;EACd,UAAU,OAAO;EACjB,aAAa,OAAO;EACpB,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,0BAAS,IAAI,KAAK,
|
|
1
|
+
{"version":3,"file":"snapshot.mjs","names":[],"sources":["../../../../../../../ai/src/planner/snapshot.ts"],"sourcesContent":["import { resolveDefaultSnapshotStore } from \"../config\";\nimport type { SnapshotStore } from \"../contracts/orchestrator/snapshot-store.contract\";\nimport type { PlannerResumeOptions } from \"../contracts/planner/planner-execute-options.type\";\nimport type { PlannerPlan } from \"../contracts/planner/planner-plan.type\";\nimport type { PlannerStepSnapshot } from \"../contracts/planner/planner-result.type\";\nimport type {\n PlannerSnapshot,\n PlannerSnapshotStatus,\n} from \"../contracts/planner/planner-snapshot.type\";\nimport type { BaseReport } from \"../contracts/result/base-report.type\";\nimport type { Usage } from \"../contracts/result/usage.type\";\nimport { PlannerDriftError, PlannerFailedError } from \"../errors\";\n\n/**\n * The planner's `durable` config, narrowed to the fields the snapshot\n * helpers read.\n */\nexport type PlannerDurableConfig = {\n store?: SnapshotStore<PlannerSnapshot>;\n deleteOnComplete?: boolean;\n};\n\n/**\n * Resolve the effective {@link SnapshotStore}: the planner's own\n * `durable.store` wins; absent that, fall back to the global default set\n * via `ai.config({ defaultSnapshotStore })`.\n *\n * The global default is typed for the supervisor snapshot shape, but\n * every store impl keys purely by `runId` and round-trips whatever\n * envelope it is handed — so it serves a `PlannerSnapshot` just as well.\n * The cast re-tags the shape at this single boundary (Option B); the\n * planner only ever hands it a `PlannerSnapshot`.\n */\nfunction resolveSnapshotStore(\n durable: PlannerDurableConfig | undefined,\n): SnapshotStore<PlannerSnapshot> | undefined {\n return (\n durable?.store ??\n (resolveDefaultSnapshotStore() as SnapshotStore<PlannerSnapshot> | undefined)\n );\n}\n\nexport type PersistPlannerParams = {\n durable: PlannerDurableConfig | undefined;\n runId: string;\n plannerName: string;\n signature: string;\n version?: string;\n goal: string;\n plan: PlannerPlan;\n executedSteps: PlannerStepSnapshot[];\n usage: Usage;\n children: BaseReport[];\n replanCount: number;\n status: PlannerSnapshotStatus;\n startedAt: string;\n};\n\nexport type PersistOutcome = { ok: true } | { ok: false; error: unknown };\n\n/**\n * Write the current run state to the resolved snapshot store. No-op\n * (returns `{ ok: true }`) when neither `durable.store` nor the global\n * `defaultSnapshotStore` is configured — the common non-durable path.\n * Failures are returned as `{ ok: false }` rather than thrown so the\n * engine can surface them via logs without aborting the run.\n */\nexport async function persistPlannerSnapshot(\n params: PersistPlannerParams,\n): Promise<PersistOutcome> {\n const store = resolveSnapshotStore(params.durable);\n\n if (!store) {\n return { ok: true };\n }\n\n const snapshot: PlannerSnapshot = {\n runId: params.runId,\n plannerName: params.plannerName,\n signature: params.signature,\n version: params.version,\n goal: params.goal,\n plan: params.plan,\n executedSteps: params.executedSteps,\n usage: params.usage,\n children: params.children,\n replanCount: params.replanCount,\n status: params.status,\n startedAt: params.startedAt,\n savedAt: new Date().toISOString(),\n };\n\n try {\n await store.save(snapshot);\n\n return { ok: true };\n } catch (error) {\n return { ok: false, error };\n }\n}\n\n/**\n * Delete a persisted snapshot — used after a successful run when\n * `durable.deleteOnComplete` is set. Never throws. No-op (ok) when no\n * store is configured.\n */\nexport async function deletePlannerSnapshot(params: {\n durable: PlannerDurableConfig | undefined;\n runId: string;\n}): Promise<PersistOutcome> {\n const store = resolveSnapshotStore(params.durable);\n\n if (!store) {\n return { ok: true };\n }\n\n try {\n await store.delete(params.runId);\n\n return { ok: true };\n } catch (error) {\n return { ok: false, error };\n }\n}\n\n/**\n * Load a persisted snapshot for `resume()` and run the drift check.\n * Throws `PlannerFailedError` when no store is configured or when the run\n * is missing; throws `PlannerDriftError` when the stored signature\n * doesn't match the current definition (unless `force` is set).\n */\nexport async function loadPlannerSnapshotForResume(params: {\n durable: PlannerDurableConfig | undefined;\n plannerName: string;\n signature: string;\n runId: string;\n options?: PlannerResumeOptions<unknown>;\n}): Promise<PlannerSnapshot> {\n const store = resolveSnapshotStore(params.durable);\n\n if (!store) {\n throw new PlannerFailedError(\n `ai.planner(\"${params.plannerName}\"): no durable store configured — set \\`durable: { store }\\` on the config or call \\`ai.config({ defaultSnapshotStore })\\` at boot before calling resume()`,\n { context: { runId: params.runId } },\n );\n }\n\n const snapshot = (await store.load(params.runId)) ?? null;\n\n if (!snapshot) {\n throw new PlannerFailedError(\n `ai.planner(\"${params.plannerName}\"): no snapshot for runId \"${params.runId}\"`,\n { context: { runId: params.runId } },\n );\n }\n\n if (!params.options?.force && snapshot.signature !== params.signature) {\n throw new PlannerDriftError(\n `ai.planner(\"${params.plannerName}\") signature drift on resume`,\n {\n savedSignature: snapshot.signature,\n currentSignature: params.signature,\n runId: params.runId,\n },\n );\n }\n\n return snapshot;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiCA,SAAS,qBACP,SAC4C;CAC5C,OACE,SAAS,SACR,4BAA4B;AAEjC;;;;;;;;AA2BA,eAAsB,uBACpB,QACyB;CACzB,MAAM,QAAQ,qBAAqB,OAAO,OAAO;CAEjD,IAAI,CAAC,OACH,OAAO,EAAE,IAAI,KAAK;CAGpB,MAAM,WAA4B;EAChC,OAAO,OAAO;EACd,aAAa,OAAO;EACpB,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,MAAM,OAAO;EACb,MAAM,OAAO;EACb,eAAe,OAAO;EACtB,OAAO,OAAO;EACd,UAAU,OAAO;EACjB,aAAa,OAAO;EACpB,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,0BAAS,IAAI,KAAK,GAAE,YAAY;CAClC;CAEA,IAAI;EACF,MAAM,MAAM,KAAK,QAAQ;EAEzB,OAAO,EAAE,IAAI,KAAK;CACpB,SAAS,OAAO;EACd,OAAO;GAAE,IAAI;GAAO;EAAM;CAC5B;AACF;;;;;;AAOA,eAAsB,sBAAsB,QAGhB;CAC1B,MAAM,QAAQ,qBAAqB,OAAO,OAAO;CAEjD,IAAI,CAAC,OACH,OAAO,EAAE,IAAI,KAAK;CAGpB,IAAI;EACF,MAAM,MAAM,OAAO,OAAO,KAAK;EAE/B,OAAO,EAAE,IAAI,KAAK;CACpB,SAAS,OAAO;EACd,OAAO;GAAE,IAAI;GAAO;EAAM;CAC5B;AACF;;;;;;;AAQA,eAAsB,6BAA6B,QAMtB;CAC3B,MAAM,QAAQ,qBAAqB,OAAO,OAAO;CAEjD,IAAI,CAAC,OACH,MAAM,IAAI,mBACR,eAAe,OAAO,YAAY,6JAClC,EAAE,SAAS,EAAE,OAAO,OAAO,MAAM,EAAE,CACrC;CAGF,MAAM,WAAY,MAAM,MAAM,KAAK,OAAO,KAAK,KAAM;CAErD,IAAI,CAAC,UACH,MAAM,IAAI,mBACR,eAAe,OAAO,YAAY,6BAA6B,OAAO,MAAM,IAC5E,EAAE,SAAS,EAAE,OAAO,OAAO,MAAM,EAAE,CACrC;CAGF,IAAI,CAAC,OAAO,SAAS,SAAS,SAAS,cAAc,OAAO,WAC1D,MAAM,IAAI,kBACR,eAAe,OAAO,YAAY,+BAClC;EACE,gBAAgB,SAAS;EACzB,kBAAkB,OAAO;EACzB,OAAO,OAAO;CAChB,CACF;CAGF,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-langfuse-sync.mjs","names":[],"sources":["../../../../../../../ai/src/prompt/prompt-langfuse-sync.ts"],"sourcesContent":["import type { PromptEntry, PromptLangfuseSyncOptions } from \"./prompt.type\";\nimport type {\n LangfuseClientLike,\n LangfusePromptLike,\n} from \"./prompt-langfuse-sync.type\";\n\n// ============================================================\n// Lazily-loaded langfuse SDK (OPTIONAL peer)\n// ============================================================\n\nlet LangfuseSdk: typeof import(\"langfuse\");\nlet isModuleExists: boolean | null = null;\nlet loadingPromise: Promise<void> | undefined;\n\nconst PROMPT_LANGFUSE_INSTALL_INSTRUCTIONS = `\nThe prompt registry's Langfuse sync requires the langfuse package.\nInstall it with:\n\n npm install langfuse\n\nOr with your preferred package manager:\n\n pnpm add langfuse\n yarn add langfuse\n`.trim();\n\n/**\n * Settle the lazy import of `langfuse` once, concurrency-safe. Only needed\n * when the caller did not pass a ready `client`. A bare `catch` flips the\n * flag to `false`; the curated install string surfaces at use time, never a\n * raw module-resolution stack trace.\n */\nfunction loadLangfuse(): Promise<void> {\n if (isModuleExists !== null) {\n return Promise.resolve();\n }\n\n if (loadingPromise) {\n return loadingPromise;\n }\n\n loadingPromise = (async () => {\n try {\n LangfuseSdk = await import(\"langfuse\");\n isModuleExists = true;\n } catch {\n isModuleExists = false;\n }\n })();\n\n return loadingPromise;\n}\n\n/**\n * Resolve the Langfuse client — the caller-supplied one when present,\n * otherwise a lazily-constructed client from credentials. Throws the curated\n * install error when the SDK is missing and no client was supplied.\n */\nasync function resolveClient(\n options: PromptLangfuseSyncOptions,\n): Promise<LangfuseClientLike> {\n if (options.client) {\n return options.client;\n }\n\n await loadLangfuse();\n\n if (!isModuleExists) {\n throw new Error(PROMPT_LANGFUSE_INSTALL_INSTRUCTIONS);\n }\n\n return new LangfuseSdk.Langfuse({\n publicKey: options.publicKey,\n secretKey: options.secretKey,\n baseUrl: options.baseUrl,\n }) as unknown as LangfuseClientLike;\n}\n\n/**\n * Map one Langfuse prompt handle onto a {@link PromptEntry} version snapshot.\n * Langfuse versions are numeric; they become the string `version` label.\n */\nfunction toEntry(remote: LangfusePromptLike): PromptEntry {\n return {\n name: remote.name,\n versions: [{ version: String(remote.version), template: remote.prompt }],\n };\n}\n\n/**\n * Warm the lazy `langfuse` import without blocking — call when a registry is\n * constructed with a `langfuse` option but no pre-built client, so the first\n * `.sync()` does not pay the resolution cost. A bare miss is tolerated.\n */\nexport function warmLangfuse(options: PromptLangfuseSyncOptions): void {\n if (!options.client) {\n void loadLangfuse();\n }\n}\n\n/**\n * Run one Langfuse-prompts sync pass.\n *\n * **Pull** (`direction: \"pull\"` | `\"both\"`) fetches each named prompt from\n * Langfuse and hands the mapped {@link PromptEntry} to `upsert`. **Push**\n * (`direction: \"push\"` | `\"both\"`) writes the latest version of each local\n * entry back as a new Langfuse text prompt. Default direction is `\"pull\"`.\n *\n * Lazily imports `langfuse` (unless a `client` was supplied) and throws a\n * curated install error when the peer is missing.\n *\n * @param options - The configured sync options (client / credentials / direction).\n * @param names - The prompt names to pull (ignored for push-only).\n * @param localEntries - Snapshot of the local catalog, for push.\n * @param upsert - Callback receiving each pulled entry to merge into the catalog.\n */\nexport async function syncLangfusePrompts(\n options: PromptLangfuseSyncOptions,\n names: string[],\n localEntries: PromptEntry[],\n upsert: (entry: PromptEntry) => void,\n): Promise<void> {\n const direction = options.direction ?? \"pull\";\n const client = await resolveClient(options);\n\n if (direction === \"pull\" || direction === \"both\") {\n for (const name of names) {\n const remote = await client.getPrompt(name);\n upsert(toEntry(remote));\n }\n }\n\n if (direction === \"push\" || direction === \"both\") {\n for (const entry of localEntries) {\n const latest = entry.versions[entry.versions.length - 1];\n\n if (!latest) {\n continue;\n }\n\n await client.createPrompt({\n name: entry.name,\n prompt: latest.template,\n type: \"text\",\n });\n }\n }\n}\n"],"mappings":";AAUA,IAAI;AACJ,IAAI,iBAAiC;AACrC,IAAI;AAEJ,MAAM,uCAAuC;;;;;;;;;;EAU3C,KAAK;;;;;;;AAQP,SAAS,eAA8B;CACrC,IAAI,mBAAmB,MACrB,OAAO,QAAQ,QAAQ;CAGzB,IAAI,gBACF,OAAO;CAGT,kBAAkB,YAAY;EAC5B,IAAI;GACF,cAAc,MAAM,OAAO;GAC3B,iBAAiB;EACnB,QAAQ;GACN,iBAAiB;EACnB;CACF,
|
|
1
|
+
{"version":3,"file":"prompt-langfuse-sync.mjs","names":[],"sources":["../../../../../../../ai/src/prompt/prompt-langfuse-sync.ts"],"sourcesContent":["import type { PromptEntry, PromptLangfuseSyncOptions } from \"./prompt.type\";\nimport type {\n LangfuseClientLike,\n LangfusePromptLike,\n} from \"./prompt-langfuse-sync.type\";\n\n// ============================================================\n// Lazily-loaded langfuse SDK (OPTIONAL peer)\n// ============================================================\n\nlet LangfuseSdk: typeof import(\"langfuse\");\nlet isModuleExists: boolean | null = null;\nlet loadingPromise: Promise<void> | undefined;\n\nconst PROMPT_LANGFUSE_INSTALL_INSTRUCTIONS = `\nThe prompt registry's Langfuse sync requires the langfuse package.\nInstall it with:\n\n npm install langfuse\n\nOr with your preferred package manager:\n\n pnpm add langfuse\n yarn add langfuse\n`.trim();\n\n/**\n * Settle the lazy import of `langfuse` once, concurrency-safe. Only needed\n * when the caller did not pass a ready `client`. A bare `catch` flips the\n * flag to `false`; the curated install string surfaces at use time, never a\n * raw module-resolution stack trace.\n */\nfunction loadLangfuse(): Promise<void> {\n if (isModuleExists !== null) {\n return Promise.resolve();\n }\n\n if (loadingPromise) {\n return loadingPromise;\n }\n\n loadingPromise = (async () => {\n try {\n LangfuseSdk = await import(\"langfuse\");\n isModuleExists = true;\n } catch {\n isModuleExists = false;\n }\n })();\n\n return loadingPromise;\n}\n\n/**\n * Resolve the Langfuse client — the caller-supplied one when present,\n * otherwise a lazily-constructed client from credentials. Throws the curated\n * install error when the SDK is missing and no client was supplied.\n */\nasync function resolveClient(\n options: PromptLangfuseSyncOptions,\n): Promise<LangfuseClientLike> {\n if (options.client) {\n return options.client;\n }\n\n await loadLangfuse();\n\n if (!isModuleExists) {\n throw new Error(PROMPT_LANGFUSE_INSTALL_INSTRUCTIONS);\n }\n\n return new LangfuseSdk.Langfuse({\n publicKey: options.publicKey,\n secretKey: options.secretKey,\n baseUrl: options.baseUrl,\n }) as unknown as LangfuseClientLike;\n}\n\n/**\n * Map one Langfuse prompt handle onto a {@link PromptEntry} version snapshot.\n * Langfuse versions are numeric; they become the string `version` label.\n */\nfunction toEntry(remote: LangfusePromptLike): PromptEntry {\n return {\n name: remote.name,\n versions: [{ version: String(remote.version), template: remote.prompt }],\n };\n}\n\n/**\n * Warm the lazy `langfuse` import without blocking — call when a registry is\n * constructed with a `langfuse` option but no pre-built client, so the first\n * `.sync()` does not pay the resolution cost. A bare miss is tolerated.\n */\nexport function warmLangfuse(options: PromptLangfuseSyncOptions): void {\n if (!options.client) {\n void loadLangfuse();\n }\n}\n\n/**\n * Run one Langfuse-prompts sync pass.\n *\n * **Pull** (`direction: \"pull\"` | `\"both\"`) fetches each named prompt from\n * Langfuse and hands the mapped {@link PromptEntry} to `upsert`. **Push**\n * (`direction: \"push\"` | `\"both\"`) writes the latest version of each local\n * entry back as a new Langfuse text prompt. Default direction is `\"pull\"`.\n *\n * Lazily imports `langfuse` (unless a `client` was supplied) and throws a\n * curated install error when the peer is missing.\n *\n * @param options - The configured sync options (client / credentials / direction).\n * @param names - The prompt names to pull (ignored for push-only).\n * @param localEntries - Snapshot of the local catalog, for push.\n * @param upsert - Callback receiving each pulled entry to merge into the catalog.\n */\nexport async function syncLangfusePrompts(\n options: PromptLangfuseSyncOptions,\n names: string[],\n localEntries: PromptEntry[],\n upsert: (entry: PromptEntry) => void,\n): Promise<void> {\n const direction = options.direction ?? \"pull\";\n const client = await resolveClient(options);\n\n if (direction === \"pull\" || direction === \"both\") {\n for (const name of names) {\n const remote = await client.getPrompt(name);\n upsert(toEntry(remote));\n }\n }\n\n if (direction === \"push\" || direction === \"both\") {\n for (const entry of localEntries) {\n const latest = entry.versions[entry.versions.length - 1];\n\n if (!latest) {\n continue;\n }\n\n await client.createPrompt({\n name: entry.name,\n prompt: latest.template,\n type: \"text\",\n });\n }\n }\n}\n"],"mappings":";AAUA,IAAI;AACJ,IAAI,iBAAiC;AACrC,IAAI;AAEJ,MAAM,uCAAuC;;;;;;;;;;EAU3C,KAAK;;;;;;;AAQP,SAAS,eAA8B;CACrC,IAAI,mBAAmB,MACrB,OAAO,QAAQ,QAAQ;CAGzB,IAAI,gBACF,OAAO;CAGT,kBAAkB,YAAY;EAC5B,IAAI;GACF,cAAc,MAAM,OAAO;GAC3B,iBAAiB;EACnB,QAAQ;GACN,iBAAiB;EACnB;CACF,GAAG;CAEH,OAAO;AACT;;;;;;AAOA,eAAe,cACb,SAC6B;CAC7B,IAAI,QAAQ,QACV,OAAO,QAAQ;CAGjB,MAAM,aAAa;CAEnB,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,oCAAoC;CAGtD,OAAO,IAAI,YAAY,SAAS;EAC9B,WAAW,QAAQ;EACnB,WAAW,QAAQ;EACnB,SAAS,QAAQ;CACnB,CAAC;AACH;;;;;AAMA,SAAS,QAAQ,QAAyC;CACxD,OAAO;EACL,MAAM,OAAO;EACb,UAAU,CAAC;GAAE,SAAS,OAAO,OAAO,OAAO;GAAG,UAAU,OAAO;EAAO,CAAC;CACzE;AACF;;;;;;AAOA,SAAgB,aAAa,SAA0C;CACrE,IAAI,CAAC,QAAQ,QACX,AAAK,aAAa;AAEtB;;;;;;;;;;;;;;;;;AAkBA,eAAsB,oBACpB,SACA,OACA,cACA,QACe;CACf,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,SAAS,MAAM,cAAc,OAAO;CAE1C,IAAI,cAAc,UAAU,cAAc,QACxC,KAAK,MAAM,QAAQ,OAEjB,OAAO,QAAQ,MADM,OAAO,UAAU,IAAI,CACrB,CAAC;CAI1B,IAAI,cAAc,UAAU,cAAc,QACxC,KAAK,MAAM,SAAS,cAAc;EAChC,MAAM,SAAS,MAAM,SAAS,MAAM,SAAS,SAAS;EAEtD,IAAI,CAAC,QACH;EAGF,MAAM,OAAO,aAAa;GACxB,MAAM,MAAM;GACZ,QAAQ,OAAO;GACf,MAAM;EACR,CAAC;CACH;AAEJ"}
|