@iowarp/clio-coder 0.3.7 → 0.3.9
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 +80 -0
- package/README.md +13 -4
- package/dist/{acp-SK4MD6MM.js → acp-7LOELQFP.js} +13 -13
- package/dist/{agents-2FN2K6ME.js → agents-FIBG2SHA.js} +41 -37
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-QIYZWM5I.js → auth-OI4LIH2I.js} +31 -24
- package/dist/builtins-AD25UL3C.js +17 -0
- package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
- package/dist/chunk-3DPEIQKN.js +113 -0
- package/dist/{chunk-EOOQZZDE.js → chunk-3DUR4WUA.js} +19 -19
- package/dist/{chunk-WHJYKASB.js → chunk-3MRC2YSQ.js} +2 -2
- package/dist/{chunk-EBEFWSGL.js → chunk-3UUY7R3Z.js} +14 -10
- package/dist/{chunk-LADCF22A.js → chunk-3V5AYSEQ.js} +113 -54
- package/dist/{chunk-BMWK7ZIZ.js → chunk-465CC7FK.js} +16 -13
- package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
- package/dist/{chunk-CEYBNUGC.js → chunk-4H6ULJ3H.js} +378 -36
- package/dist/{chunk-YTYFXUI3.js → chunk-4LJX2PUC.js} +9 -9
- package/dist/{chunk-DOOEX22V.js → chunk-56KB5IJP.js} +5 -5
- package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
- package/dist/{chunk-TSHXZTOQ.js → chunk-5HFBWUMU.js} +23 -11
- package/dist/{chunk-5UJ6ECTS.js → chunk-5PVQ4SRS.js} +80 -8
- package/dist/{chunk-ZWLZP4ZT.js → chunk-5QKCQQ3E.js} +359 -17
- package/dist/{chunk-6M7VS3J3.js → chunk-5T7RBWN2.js} +111 -5
- package/dist/chunk-774ILSRL.js +172 -0
- package/dist/chunk-7C6RYZGQ.js +391 -0
- package/dist/{chunk-GH5622CP.js → chunk-A2NJGIB3.js} +2 -2
- package/dist/{chunk-C4JBQ5SR.js → chunk-AD7Y7STJ.js} +6 -6
- package/dist/{chunk-GEYXPTRF.js → chunk-AEYBF3TB.js} +33 -12
- package/dist/{chunk-2SFS6XQE.js → chunk-AMKHQW3C.js} +3 -2
- package/dist/{chunk-D4MDIG46.js → chunk-B5CSFE7B.js} +7 -7
- package/dist/{chunk-MXI6J5JF.js → chunk-B5XRQOLB.js} +10 -10
- package/dist/{chunk-X2KV5FXT.js → chunk-BVDVID7E.js} +2 -2
- package/dist/{chunk-JNXPYBB4.js → chunk-CA42X6KT.js} +3 -3
- package/dist/{chunk-VREKEFLL.js → chunk-D73KXYPF.js} +3 -3
- package/dist/{chunk-JTSEDYVQ.js → chunk-DG4M6ZUE.js} +7 -7
- package/dist/{chunk-DQA7QLMD.js → chunk-EBOC7MT3.js} +10 -25
- package/dist/{chunk-KZ2H5X4G.js → chunk-ECUO3KDP.js} +129 -14
- package/dist/{chunk-JRIO5UD2.js → chunk-EQ63NRB7.js} +5 -5
- package/dist/{chunk-YD734TPH.js → chunk-FALJGAWU.js} +2 -2
- package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
- package/dist/{chunk-XEGB6BCN.js → chunk-GAYUJ7LE.js} +68 -14
- package/dist/{chunk-UND3GU2L.js → chunk-H7IXIC72.js} +2 -2
- package/dist/{chunk-IR4CFBFN.js → chunk-HAY4ZE2P.js} +12 -12
- package/dist/{chunk-UVDSQ6LW.js → chunk-HCBCAYZU.js} +74 -147
- package/dist/{chunk-4DWFMQDR.js → chunk-HJB5IUKP.js} +89 -145
- package/dist/{chunk-M4AKACEO.js → chunk-HKO36JWF.js} +33 -5
- package/dist/{chunk-KCMKRQX4.js → chunk-HPCTNZM2.js} +45 -82
- package/dist/{chunk-465YSENW.js → chunk-IFBNV6H6.js} +3 -3
- package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
- package/dist/chunk-JEQQR47K.js +3025 -0
- package/dist/{chunk-FO5ZOVUY.js → chunk-KV2AOLDF.js} +27 -7
- package/dist/chunk-LU7P4LHA.js +33 -0
- package/dist/{chunk-6TUKSZVF.js → chunk-LXPJXFM5.js} +11 -11
- package/dist/{chunk-VQNODYQ4.js → chunk-MIX5N5AC.js} +488 -3668
- package/dist/chunk-MLOK6ZOS.js +2888 -0
- package/dist/{chunk-ZZMN5OM4.js → chunk-MV2VUEJC.js} +2 -2
- package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
- package/dist/{chunk-OBMAI2DP.js → chunk-N3PBVRTZ.js} +12 -388
- package/dist/{chunk-WJHBC77E.js → chunk-N5XKWMDW.js} +17 -7
- package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
- package/dist/{chunk-UFQ3F4FW.js → chunk-NQ6UCCOD.js} +4 -4
- package/dist/chunk-NUGM5KR6.js +165 -0
- package/dist/{chunk-DMD2AGVS.js → chunk-NZU6YDNV.js} +20 -18
- package/dist/{chunk-WHGPSPT5.js → chunk-O6I4CIEU.js} +151 -13
- package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
- package/dist/{chunk-PD3MESLB.js → chunk-P3JGPQFL.js} +4 -4
- package/dist/{chunk-UHXRNZ2J.js → chunk-PNY46YEY.js} +23 -6
- package/dist/{chunk-THKY7CD7.js → chunk-PZ4I4JE2.js} +134 -29
- package/dist/{chunk-SROCI7ZU.js → chunk-QQ7EKM72.js} +5 -5
- package/dist/{chunk-QCTRSGHQ.js → chunk-R7LNVMCS.js} +91 -53
- package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
- package/dist/chunk-RKKLTLYB.js +45 -0
- package/dist/{chunk-OB5HIGJY.js → chunk-RKRLDWD3.js} +4 -1
- package/dist/{chunk-DJNLUABN.js → chunk-S4COXYBG.js} +588 -32
- package/dist/{chunk-3HAPLH5M.js → chunk-T3Z6VAAF.js} +172 -11
- package/dist/{chunk-FOT2FX5J.js → chunk-TD7UE2L5.js} +12 -10
- package/dist/{chunk-UUANF5CR.js → chunk-TEO2TLVN.js} +856 -967
- package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
- package/dist/{chunk-EELBMBT6.js → chunk-VKBMFOYV.js} +74 -15
- package/dist/chunk-VO2LKSTM.js +165 -0
- package/dist/{chunk-5C77SEEY.js → chunk-VPTUJU4P.js} +3 -3
- package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
- package/dist/{chunk-J7PIKKWC.js → chunk-WXCJ7VME.js} +8 -8
- package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
- package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
- package/dist/{chunk-AB4XIIVB.js → chunk-YKOFT37S.js} +6 -6
- package/dist/chunk-YSEHGPCT.js +127 -0
- package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
- package/dist/cli/index.js +32 -32
- package/dist/{clio-WBVQEBKO.js → clio-LT5V7SSZ.js} +9 -9
- package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +5 -5
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-F7OEATSO.js → components-ZFA3SAER.js} +8 -8
- package/dist/{config-TRBL3RCF.js → config-RXS5T3JT.js} +98 -65
- package/dist/{configure-OLCVPHNM.js → configure-2WYWSCSD.js} +26 -22
- package/dist/{context-MJIJ6GOX.js → context-I3BTOTCS.js} +12 -12
- package/dist/{context-XEWE3MOJ.js → context-MVOORGMF.js} +54 -47
- package/dist/{context-WFPKQSM6.js → context-PALKKQYL.js} +28 -28
- package/dist/{context-clear-KNOS2JPB.js → context-clear-N2WOYZ2K.js} +53 -46
- package/dist/{context-index-SSR5ECNE.js → context-index-HNG3MOME.js} +6 -6
- package/dist/{context-working-set-EUXAZI6N.js → context-working-set-MIEVECVZ.js} +17 -18
- package/dist/{dispatch-runner-B7MTOVKL.js → dispatch-runner-VVA4SRRH.js} +90 -61
- package/dist/{docs-FLJTIDSE.js → docs-7LQ23DLM.js} +8 -8
- package/dist/doctor-TWBWFK5V.js +165 -0
- package/dist/eval-IJ5VEZDJ.js +4483 -0
- package/dist/{evidence-JZNBUOQZ.js → evidence-L5APPXNV.js} +68 -61
- package/dist/{evolve-FJVC4KKI.js → evolve-RGNKFJ52.js} +47 -40
- package/dist/{extensions-IQL36S7K.js → extensions-7WYWUX5A.js} +13 -7
- package/dist/{fleet-BDKYJFCP.js → fleet-6CNVBZZP.js} +113 -76
- package/dist/{fleet-commands-ZFIWZSB3.js → fleet-commands-L2SXSYEI.js} +10 -10
- package/dist/{fleet-graph-Y6HPXIVF.js → fleet-graph-2J3OOIPO.js} +17 -15
- package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-CZRJ4JP5.js} +5 -6
- package/dist/{fleet-validate-BIYREGIK.js → fleet-validate-C5RI6DP7.js} +20 -19
- package/dist/{init-LQUB5COQ.js → init-VBN2ACVA.js} +70 -63
- package/dist/{library-NJAHIGG4.js → library-JHGUMLY2.js} +22 -20
- package/dist/{memory-OG6HOYKM.js → memory-K4OQIYWG.js} +49 -42
- package/dist/{models-5ZG5XY7J.js → models-2NCZUWDD.js} +35 -29
- package/dist/{monitor-TJ7AMTGB.js → monitor-MMVTJABD.js} +64 -45
- package/dist/{orchestrator-WZYB54DM.js → orchestrator-ZKBPCHW6.js} +1971 -520
- package/dist/{paths-XUC7GS6E.js → paths-DBXMZMDU.js} +5 -5
- package/dist/registry-LG64LTF4.js +11 -0
- package/dist/{reset-PXQT45IY.js → reset-DD5JGOY3.js} +11 -11
- package/dist/{run-FQ74YF62.js → run-QEGNX7FL.js} +89 -83
- package/dist/{share-FW7SVCL3.js → share-JKD3BQMW.js} +20 -18
- package/dist/{skills-7E7IRB3R.js → skills-LMQIKDOZ.js} +23 -21
- package/dist/{skills-eval-LI75W6OK.js → skills-eval-I7X2774U.js} +59 -52
- package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
- package/dist/support-I7LOJLIF.js +38 -0
- package/dist/{targets-4CIFKCTW.js → targets-RUSR6B5Z.js} +77 -42
- package/dist/{terminal-lease-WUZY7ZV5.js → terminal-lease-QYVORFR4.js} +6 -4
- package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
- package/dist/{uninstall-7FV7IP4E.js → uninstall-ZJF5H5ZN.js} +8 -8
- package/dist/{upgrade-K2HVIVMQ.js → upgrade-XANW3FXB.js} +29 -26
- package/dist/{usage-GTZELZQX.js → usage-4H7ZRXQT.js} +110 -61
- package/dist/{verifiers-RLAHT27O.js → verifiers-UZXNBZEB.js} +13 -13
- package/dist/{verify-BX3BRKH5.js → verify-BVKWTNDL.js} +9 -9
- package/dist/{wiki-generate-ASIFASCN.js → wiki-generate-MY7WV2QI.js} +76 -69
- package/dist/worker/entry.js +69 -66
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/artifact-versions.md +11 -5
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +23 -2
- package/docs/commands-and-modes.md +2 -2
- package/docs/configuration-and-targets.md +37 -5
- package/docs/context-engine.md +63 -4
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +1 -1
- package/docs/environment-variables.md +2 -0
- package/docs/eval-runner.md +262 -11
- package/docs/evals-internal.md +72 -2
- package/docs/evidence-and-memory.md +77 -12
- package/docs/evolution.md +1 -1
- package/docs/extensions-and-sharing.md +3 -1
- package/docs/fleet-dispatch.md +34 -9
- package/docs/glossary.md +21 -1
- package/docs/installation-and-lifecycle.md +1 -1
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +54 -3
- package/docs/proactive-memory.md +127 -14
- package/docs/prompt-envelope-and-tools.md +22 -2
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +60 -41
- package/docs/safety-model.md +1 -1
- package/docs/scientific-validation.md +1 -1
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +1 -1
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +87 -0
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +1 -1
- package/package.json +2 -2
- package/src/cli/agents.ts +1 -1
- package/src/cli/argv.ts +5 -0
- package/src/cli/config-inspect.ts +33 -6
- package/src/cli/config.ts +1 -1
- package/src/cli/configure.ts +107 -23
- package/src/cli/doctor-state-size.ts +82 -0
- package/src/cli/doctor.ts +7 -1
- package/src/cli/eval.ts +80 -16
- package/src/cli/evidence.ts +30 -25
- package/src/cli/extensions.ts +5 -1
- package/src/cli/fleet-preflight.ts +2 -12
- package/src/cli/fleet.ts +32 -3
- package/src/cli/shared.ts +1 -0
- package/src/cli/targets.ts +45 -11
- package/src/cli/trace.ts +63 -4
- package/src/cli/usage.ts +63 -14
- package/src/cli/validate-model.ts +60 -5
- package/src/core/bus-events.ts +54 -1
- package/src/core/cache-telemetry.ts +42 -0
- package/src/core/commit-attribution.ts +4 -4
- package/src/core/config.ts +18 -0
- package/src/core/defaults.ts +36 -6
- package/src/core/endpoint-key.ts +27 -0
- package/src/core/path-boundary.ts +100 -0
- package/src/core/residency-target-key.ts +25 -0
- package/src/core/response-schema.ts +36 -2
- package/src/domains/agents/extension.ts +2 -11
- package/src/domains/agents/fleet-contract.ts +30 -12
- package/src/domains/agents/recipe.ts +7 -1
- package/src/domains/agents/registry.ts +73 -5
- package/src/domains/agents/result-contract.ts +128 -17
- package/src/domains/agents/write-boundary.ts +15 -50
- package/src/domains/config/classify.ts +3 -0
- package/src/domains/context/codewiki/coordinator.ts +12 -4
- package/src/domains/context/project-rules.ts +51 -1
- package/src/domains/dispatch/admission.ts +40 -3
- package/src/domains/dispatch/assignment-reconcile.ts +22 -5
- package/src/domains/dispatch/assignment-store.ts +151 -14
- package/src/domains/dispatch/capacity-lease.ts +98 -9
- package/src/domains/dispatch/contract.ts +26 -1
- package/src/domains/dispatch/delegation-plan.ts +2 -5
- package/src/domains/dispatch/execution-plan.ts +44 -4
- package/src/domains/dispatch/execution-role.ts +9 -1
- package/src/domains/dispatch/extension.ts +309 -96
- package/src/domains/dispatch/fleet-run.ts +78 -4
- package/src/domains/dispatch/gate-role-prompts.ts +38 -0
- package/src/domains/dispatch/heartbeat.ts +32 -8
- package/src/domains/dispatch/index.ts +6 -1
- package/src/domains/dispatch/intent-requirements.ts +40 -0
- package/src/domains/dispatch/intent.ts +84 -8
- package/src/domains/dispatch/orphan-recovery.ts +5 -0
- package/src/domains/dispatch/path-scope.ts +370 -0
- package/src/domains/dispatch/receipt-integrity.ts +2 -1
- package/src/domains/dispatch/reservation-store.ts +116 -8
- package/src/domains/dispatch/state.ts +4 -0
- package/src/domains/dispatch/types.ts +14 -7
- package/src/domains/dispatch/validation.ts +6 -3
- package/src/domains/dispatch/worker-spawn.ts +25 -11
- package/src/domains/dispatch/write-boundary-enforcer.ts +62 -0
- package/src/domains/dispatch/write-boundary.ts +262 -22
- package/src/domains/eval/artifacts/store.ts +62 -0
- package/src/domains/eval/compare/behavioral.ts +224 -0
- package/src/domains/eval/compare/compare.ts +355 -2
- package/src/domains/eval/compare/envelope.ts +128 -0
- package/src/domains/eval/compare/gates.ts +24 -6
- package/src/domains/eval/compare/thresholds.ts +30 -3
- package/src/domains/eval/execution-provenance.ts +240 -0
- package/src/domains/eval/metrics/aggregate.ts +136 -0
- package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
- package/src/domains/eval/metrics/evidence.ts +79 -2
- package/src/domains/eval/metrics/tracked.ts +413 -0
- package/src/domains/eval/provenance.ts +117 -0
- package/src/domains/eval/reports/comparison.ts +128 -0
- package/src/domains/eval/reports/junit.ts +17 -3
- package/src/domains/eval/reports/markdown.ts +3 -3
- package/src/domains/eval/reports/text.ts +14 -0
- package/src/domains/eval/run-compare.ts +20 -0
- package/src/domains/eval/runners/clio-run.ts +139 -2
- package/src/domains/eval/runners/external-command.ts +28 -3
- package/src/domains/eval/schema/adapter.ts +111 -0
- package/src/domains/eval/schema/artifact.ts +20 -0
- package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
- package/src/domains/eval/schema/behavioral.ts +520 -0
- package/src/domains/eval/schema/execution-envelope.ts +194 -0
- package/src/domains/eval/schema/serving.ts +74 -0
- package/src/domains/eval/schema/suite.ts +38 -8
- package/src/domains/eval/schema/validate.ts +58 -3
- package/src/domains/eval/schema/verdict.ts +237 -0
- package/src/domains/eval/suites/resolve.ts +2 -0
- package/src/domains/eval/suites/run.ts +264 -33
- package/src/domains/eval/verifiers/command.ts +2 -1
- package/src/domains/eval/workspaces/temp-copy.ts +145 -13
- package/src/domains/evidence/build.ts +68 -21
- package/src/domains/evidence/eval.ts +2 -12
- package/src/domains/evidence/findings-markdown.ts +33 -0
- package/src/domains/evidence/index.ts +21 -0
- package/src/domains/evidence/provenance.ts +46 -11
- package/src/domains/evidence/run-trust.ts +7 -113
- package/src/domains/evidence/trust-projection.ts +274 -0
- package/src/domains/evidence/trust-status.ts +145 -17
- package/src/domains/evidence/types.ts +4 -0
- package/src/domains/extensions/compatibility.ts +285 -0
- package/src/domains/extensions/discovery.ts +126 -4
- package/src/domains/extensions/resources.ts +21 -9
- package/src/domains/extensions/state.ts +18 -5
- package/src/domains/extensions/types.ts +6 -1
- package/src/domains/lifecycle/doctor.ts +209 -2
- package/src/domains/memory/index.ts +14 -0
- package/src/domains/memory/task-bank-promotion.ts +64 -0
- package/src/domains/memory/task-memory-policy.ts +77 -8
- package/src/domains/memory/task-memory-spend.ts +131 -0
- package/src/domains/memory/task-memory-status.ts +7 -0
- package/src/domains/memory/task-memory-telemetry.ts +2 -0
- package/src/domains/middleware/index.ts +1 -0
- package/src/domains/middleware/memory-intervention.ts +69 -5
- package/src/domains/middleware/memory-step-endpoint.ts +71 -0
- package/src/domains/observability/background-memory-usage.ts +140 -0
- package/src/domains/observability/cost.ts +1 -1
- package/src/domains/observability/index.ts +7 -0
- package/src/domains/observability/out-of-turn-usage.ts +51 -2
- package/src/domains/observability/trace-store.ts +192 -2
- package/src/domains/prompts/compiler.ts +100 -13
- package/src/domains/prompts/contract.ts +3 -5
- package/src/domains/providers/endpoint-capacity.ts +96 -0
- package/src/domains/providers/extension.ts +30 -2
- package/src/domains/providers/index.ts +10 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
- package/src/domains/providers/runtime-resolution.ts +8 -1
- package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
- package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
- package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
- package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
- package/src/domains/providers/types/capability-flags.ts +2 -0
- package/src/domains/providers/types/target-descriptor.ts +2 -0
- package/src/domains/resources/common-loader.ts +3 -0
- package/src/domains/resources/prompts/loader.ts +184 -17
- package/src/domains/safety/call-target.ts +52 -0
- package/src/domains/safety/policy-engine.ts +5 -5
- package/src/domains/safety/run-effects.ts +96 -2
- package/src/domains/safety/scope.ts +7 -12
- package/src/domains/session/context-accounting.ts +52 -1
- package/src/domains/session/context-ledger.ts +37 -13
- package/src/domains/session/index.ts +6 -0
- package/src/domains/session/prompt-cache.ts +140 -0
- package/src/domains/session/prompt-manifest.ts +42 -0
- package/src/engine/acp/adapter.ts +18 -3
- package/src/engine/acp/server.ts +4 -1
- package/src/engine/ai.ts +35 -0
- package/src/engine/apis/llamacpp-residency.ts +55 -3
- package/src/engine/apis/lmstudio.ts +25 -5
- package/src/engine/apis/ollama-native.ts +2 -1
- package/src/engine/apis/openai-completions.ts +80 -17
- package/src/engine/apis/residency-lock.ts +3 -1
- package/src/engine/apis/residency.ts +34 -1
- package/src/engine/prompt-templates.ts +18 -1
- package/src/engine/provider-payload.ts +29 -1
- package/src/engine/worker-runtime.ts +6 -3
- package/src/entry/orchestrator.ts +176 -30
- package/src/interactive/chat-loop-messages.ts +26 -7
- package/src/interactive/chat-loop.ts +318 -41
- package/src/interactive/chat-panel.ts +62 -8
- package/src/interactive/clio-editor.ts +45 -8
- package/src/interactive/context-activity.ts +5 -1
- package/src/interactive/context-meter.ts +1 -1
- package/src/interactive/context-overlay.ts +40 -10
- package/src/interactive/cost-overlay.ts +64 -6
- package/src/interactive/dispatch-board.ts +127 -5
- package/src/interactive/fleet-run-preview.ts +41 -15
- package/src/interactive/handoff-round.ts +41 -2
- package/src/interactive/interactive-application.ts +24 -1
- package/src/interactive/interactive-event-projection.ts +14 -0
- package/src/interactive/interactive-input-runtime.ts +8 -0
- package/src/interactive/interactive-presentation.ts +4 -0
- package/src/interactive/interactive-shell.ts +20 -17
- package/src/interactive/interactive-slash-runtime.ts +27 -4
- package/src/interactive/memory-overlay.ts +8 -0
- package/src/interactive/mutation-preview.ts +295 -0
- package/src/interactive/overlay-general-openers.ts +16 -0
- package/src/interactive/overlay-key-routing.ts +38 -0
- package/src/interactive/overlay-lifecycle.ts +38 -5
- package/src/interactive/overlay-permission-lifecycle.ts +22 -2
- package/src/interactive/overlay-session-lifecycle.ts +73 -9
- package/src/interactive/overlays/ask-user.ts +91 -19
- package/src/interactive/overlays/help-reference.ts +4 -0
- package/src/interactive/overlays/prompts.ts +11 -1
- package/src/interactive/overlays/settings.ts +176 -48
- package/src/interactive/permission-hint.ts +34 -2
- package/src/interactive/permission-overlay.ts +159 -9
- package/src/interactive/prewarm.ts +197 -0
- package/src/interactive/render-trace.ts +162 -15
- package/src/interactive/renderers/tool-execution.ts +4 -0
- package/src/interactive/side-question.ts +58 -1
- package/src/interactive/slash-commands.ts +7 -2
- package/src/interactive/status/controller.ts +11 -0
- package/src/interactive/status/state-machine.ts +54 -2
- package/src/interactive/status/types.ts +7 -0
- package/src/interactive/terminal-lease.ts +2 -0
- package/src/interactive/turn-context.ts +299 -31
- package/src/interactive/turn-persistence.ts +14 -4
- package/src/interactive/turn-prewarm.ts +364 -0
- package/src/interactive/turn-queues.ts +7 -4
- package/src/interactive/turn-runtime.ts +8 -1
- package/src/interactive/turn-state.ts +23 -0
- package/src/interactive/view/artifacts.ts +42 -9
- package/src/interactive/view/view-overlay.ts +43 -6
- package/src/interactive/worker-receipts.ts +14 -2
- package/src/interactive/worker-stream.ts +8 -0
- package/src/tools/ask-user.ts +43 -2
- package/src/tools/dispatch-admission.ts +12 -13
- package/src/tools/dispatch-arguments.ts +27 -0
- package/src/tools/dispatch-plan.ts +46 -9
- package/src/tools/dispatch-runner.ts +48 -13
- package/src/tools/dispatch-scout.ts +1 -1
- package/src/tools/monitor.ts +13 -0
- package/src/tools/registry.ts +16 -0
- package/src/tools/worker-evidence.ts +19 -13
- package/src/worker/spec-contract.ts +2 -1
- package/dist/chunk-AOCYTWAV.js +0 -449
- package/dist/chunk-HWUFFB6L.js +0 -83
- package/dist/chunk-R346GLFC.js +0 -31
- package/dist/chunk-ZGH7FGS5.js +0 -1079
- package/dist/doctor-RN4YKO2X.js +0 -87
- package/dist/eval-RUBJVSNQ.js +0 -2557
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { createHash, randomBytes } from "node:crypto";
|
|
13
13
|
import { type Dirent, existsSync, readdirSync, readFileSync } from "node:fs";
|
|
14
14
|
import { isAbsolute, relative, resolve as resolvePath, sep } from "node:path";
|
|
15
|
+
import { performance } from "node:perf_hooks";
|
|
15
16
|
import { BusChannels, type DispatchCompletedPayload, type DispatchRunIdentity } from "../../core/bus-events.js";
|
|
16
17
|
import { DEFAULT_SETTINGS, type DelegationToolGovernance } from "../../core/defaults.js";
|
|
17
18
|
import type { DomainBundle, DomainContext, DomainExtension } from "../../core/domain-loader.js";
|
|
@@ -76,6 +77,9 @@ import { type EffectivePricing, resolveEffectivePricing } from "../providers/cat
|
|
|
76
77
|
import {
|
|
77
78
|
type CapabilityFlags,
|
|
78
79
|
canonicalizeWireModelId,
|
|
80
|
+
type EndpointCapacity,
|
|
81
|
+
endpointCapacitiesForStatuses,
|
|
82
|
+
endpointCapacityForStatus,
|
|
79
83
|
firstRuntimeResolutionError,
|
|
80
84
|
isDispatchEligibleRuntime,
|
|
81
85
|
type ProvidersContract,
|
|
@@ -123,6 +127,7 @@ import { AssignmentRegistry, applyActiveRouteSelection, asAssignmentId } from ".
|
|
|
123
127
|
import type { AssignmentAttemptStartEvent } from "./assignment-events.js";
|
|
124
128
|
import { reconcileOrphanAssignments } from "./assignment-reconcile.js";
|
|
125
129
|
import {
|
|
130
|
+
assignmentProcessOwnerAlive,
|
|
126
131
|
cancelStoredAssignment,
|
|
127
132
|
failQueuedAssignment,
|
|
128
133
|
getStoredAssignment,
|
|
@@ -165,8 +170,16 @@ import {
|
|
|
165
170
|
} from "./failure-classification.js";
|
|
166
171
|
import { routeFactVerdict } from "./fleet-preflight.js";
|
|
167
172
|
import { competeStanceLiner, isBoundedGateRolePrompt } from "./gate-role-prompts.js";
|
|
168
|
-
import {
|
|
173
|
+
import {
|
|
174
|
+
classifyHeartbeat,
|
|
175
|
+
DEFAULT_HEARTBEAT_SPEC,
|
|
176
|
+
type HeartbeatSpec,
|
|
177
|
+
type HeartbeatStamp,
|
|
178
|
+
type HeartbeatStatus,
|
|
179
|
+
heartbeatMonotonicAt,
|
|
180
|
+
} from "./heartbeat.js";
|
|
169
181
|
import { hostVerificationRejection, runHostVerification } from "./host-verification.js";
|
|
182
|
+
import { renderDispatchIntentRequirements } from "./intent-requirements.js";
|
|
170
183
|
import { adaptJointRouteInput } from "./joint-route-adapter.js";
|
|
171
184
|
import {
|
|
172
185
|
configuredJointNodes,
|
|
@@ -181,6 +194,12 @@ import {
|
|
|
181
194
|
resultContractWasDue,
|
|
182
195
|
runStatusForOutcome,
|
|
183
196
|
} from "./outcome.js";
|
|
197
|
+
import {
|
|
198
|
+
type DispatchPathScope,
|
|
199
|
+
declaredScopeReplacementDiagnostic,
|
|
200
|
+
declaredScopeReplacementNotice,
|
|
201
|
+
resolveDispatchPathScope,
|
|
202
|
+
} from "./path-scope.js";
|
|
184
203
|
import { deriveEnvelopePhaseDurations, deriveRunPhaseDurations, recordRunTimingBestEffort } from "./phase-timing.js";
|
|
185
204
|
import { createFleetPlacementPreviewResolver, createFleetPlacementResolver } from "./placement.js";
|
|
186
205
|
import {
|
|
@@ -197,6 +216,7 @@ import {
|
|
|
197
216
|
planQueueSlot,
|
|
198
217
|
type ReservationCapacitySnapshot,
|
|
199
218
|
rebindDispatchReservationMember,
|
|
219
|
+
releaseDispatchReservation,
|
|
200
220
|
releaseDispatchReservationMember,
|
|
201
221
|
reservedBudgetUsd,
|
|
202
222
|
reservedPlanPeakSlots,
|
|
@@ -278,6 +298,7 @@ import {
|
|
|
278
298
|
type SpawnOptions as WorkerSpawnOptions,
|
|
279
299
|
type WorkerSpec,
|
|
280
300
|
} from "./worker-spawn.js";
|
|
301
|
+
import type { WriteBoundaryAttribution, WriteBoundaryAttributionDowngrade } from "./write-boundary.js";
|
|
281
302
|
|
|
282
303
|
interface RunTokenMeter {
|
|
283
304
|
inputTokens: number;
|
|
@@ -329,7 +350,7 @@ interface ActiveRun {
|
|
|
329
350
|
/** ACP event-inactivity window; null for native runs (heartbeat spec governs those). */
|
|
330
351
|
stallTimeoutMs: number | null;
|
|
331
352
|
lineage: RunLineage;
|
|
332
|
-
heartbeatAt:
|
|
353
|
+
heartbeatAt: HeartbeatStamp | null;
|
|
333
354
|
heartbeatStatus: HeartbeatStatus;
|
|
334
355
|
meter: RunTokenMeter;
|
|
335
356
|
pricing: EffectivePricing["rates"];
|
|
@@ -365,6 +386,8 @@ export interface DispatchBundleOptions {
|
|
|
365
386
|
heartbeatIntervalMs?: number;
|
|
366
387
|
resilienceCooldownMs?: number;
|
|
367
388
|
now?: () => number;
|
|
389
|
+
/** Monotonic clock for heartbeat age and ACP event-inactivity spans. */
|
|
390
|
+
monotonicNow?: () => number;
|
|
368
391
|
/** Immutable per-attempt session settings view; falls back to the shared snapshot. */
|
|
369
392
|
getSettings?: () => EffectiveSettings;
|
|
370
393
|
/** Live hard-block state cloned into each mediated worker spec. */
|
|
@@ -614,6 +637,14 @@ function recordWorkerRunEffect(recorder: RunEffectsRecorder, event: Record<strin
|
|
|
614
637
|
*/
|
|
615
638
|
const DISPATCH_DRAIN_GRACE_MS = 2000;
|
|
616
639
|
|
|
640
|
+
/**
|
|
641
|
+
* How many finalized runs keep their observed write targets in memory. The
|
|
642
|
+
* write boundary reads a run's record while closing the window it ran in, which
|
|
643
|
+
* is the same wave, so a few hundred runs of headroom is far more than the
|
|
644
|
+
* lookup ever reaches back for.
|
|
645
|
+
*/
|
|
646
|
+
const RUN_WRITE_TARGET_HISTORY = 512;
|
|
647
|
+
|
|
617
648
|
async function awaitEventDrain(drained: Promise<void>, graceMs = DISPATCH_DRAIN_GRACE_MS): Promise<boolean> {
|
|
618
649
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
619
650
|
const grace = new Promise<false>((resolve) => {
|
|
@@ -751,9 +782,14 @@ function pickOrchestratorScope(safety: SafetyContract): ScopeSpec {
|
|
|
751
782
|
return safety.scopes.workspace;
|
|
752
783
|
}
|
|
753
784
|
|
|
754
|
-
function pickWorkerScope(
|
|
785
|
+
function pickWorkerScope(
|
|
786
|
+
safety: SafetyContract,
|
|
787
|
+
requestedActions: ReadonlyArray<ActionClass>,
|
|
788
|
+
pathScope: DispatchPathScope,
|
|
789
|
+
): ScopeSpec {
|
|
755
790
|
if (requestedActions.every((action) => action === "read")) return safety.scopes.readonly;
|
|
756
|
-
return safety.scopes.workspace;
|
|
791
|
+
if (pathScope.writeBoundaries.length === 0) return safety.scopes.workspace;
|
|
792
|
+
return { ...safety.scopes.workspace, allowedWriteRoots: pathScope.writeBoundaries };
|
|
757
793
|
}
|
|
758
794
|
|
|
759
795
|
function deriveRequestedActions(tools: ReadonlyArray<ToolName>, safety: SafetyContract): ReadonlyArray<ActionClass> {
|
|
@@ -993,6 +1029,25 @@ function hasPersonaOverride(req: DispatchRequest): boolean {
|
|
|
993
1029
|
return typeof req.systemPrompt === "string" && req.systemPrompt.trim().length > 0;
|
|
994
1030
|
}
|
|
995
1031
|
|
|
1032
|
+
/**
|
|
1033
|
+
* Whether this request's system prompt is a caller-authored persona rather
|
|
1034
|
+
* than one of the coordinator's own bounded gate-role prompts.
|
|
1035
|
+
*
|
|
1036
|
+
* A reviewer, a compete judge, and a council synthesis all carry a system
|
|
1037
|
+
* prompt the coordinator wrote, pinned by {@link isBoundedGateRolePrompt} to
|
|
1038
|
+
* one exact text under one gate role and read-only autonomy. That is the
|
|
1039
|
+
* topology speaking, not an operator reshaping a recipe, so it is not the
|
|
1040
|
+
* thing the shadow and internal audiences are protected from. The ACP
|
|
1041
|
+
* delegation path already draws the line here; without the same line, a
|
|
1042
|
+
* council that seats the builtin read-only `researcher` (a shadow recipe, and
|
|
1043
|
+
* the agent every unnamed council seats) refused `--synthesis judge` outright,
|
|
1044
|
+
* before a single member ran.
|
|
1045
|
+
*/
|
|
1046
|
+
function hasCallerPersonaOverride(req: DispatchRequest): boolean {
|
|
1047
|
+
if (!hasPersonaOverride(req)) return false;
|
|
1048
|
+
return !isBoundedGateRolePrompt({ role: req.gate?.role, autonomy: req.autonomy, systemPrompt: req.systemPrompt });
|
|
1049
|
+
}
|
|
1050
|
+
|
|
996
1051
|
function personaOverrideFor(req: DispatchRequest, staticCompositionHash: string | null): RunPersonaOverride | null {
|
|
997
1052
|
if (!hasPersonaOverride(req) || staticCompositionHash === null) return null;
|
|
998
1053
|
return { promptHash: staticCompositionHash };
|
|
@@ -1003,9 +1058,8 @@ function personaOverrideFor(req: DispatchRequest, staticCompositionHash: string
|
|
|
1003
1058
|
* flows through dynamic messages, never through the system prompt. The system
|
|
1004
1059
|
* prompt itself is stable per recipe and tool surface with one exception: the
|
|
1005
1060
|
* operator-editable layer (`additionalFragments`) folds in path-scoped project
|
|
1006
|
-
* rules selected
|
|
1007
|
-
*
|
|
1008
|
-
* runs of one recipe when a rule's glob matches one task and not the other.
|
|
1061
|
+
* rules selected from the canonical request scope, so `staticCompositionHash`
|
|
1062
|
+
* can differ between two runs of one recipe when their scoped rules differ.
|
|
1009
1063
|
* A local model's worker prefix cache misses on that flip; the trade was made
|
|
1010
1064
|
* deliberately in #96 to keep rules scoped rather than shipped wholesale.
|
|
1011
1065
|
*/
|
|
@@ -1074,35 +1128,6 @@ function workerProjectContextIncludesVerification(body: string): boolean {
|
|
|
1074
1128
|
return body.includes("\nVerification expectations:\n");
|
|
1075
1129
|
}
|
|
1076
1130
|
|
|
1077
|
-
/**
|
|
1078
|
-
* Path-like tokens in free text: a slash-separated segment, or a bare
|
|
1079
|
-
* `name.ext` token. The model-facing dispatch tool has no structured
|
|
1080
|
-
* path/file field, so a worker's task text is the only working-context
|
|
1081
|
-
* signal available at compile time; this is best-effort recall, not a
|
|
1082
|
-
* parser. A false-positive token is harmless because `selectActiveRules`
|
|
1083
|
-
* only activates a rule when the token matches that rule's own glob.
|
|
1084
|
-
*/
|
|
1085
|
-
const PATH_TOKEN_RE = /(?:[\w.-]+\/)+[\w.-]+|\b[\w-]+\.[A-Za-z0-9]{1,8}\b/g;
|
|
1086
|
-
|
|
1087
|
-
/**
|
|
1088
|
-
* Best-effort working-context paths for a dispatched worker: `writeRoots`
|
|
1089
|
-
* when the caller set them (the precise signal, for internal/programmatic
|
|
1090
|
-
* callers), plus path-like tokens recalled from the task and briefing text.
|
|
1091
|
-
* Feeds the same `selectActiveRules` a session uses, so a worker whose task
|
|
1092
|
-
* touches a ruled path reads that rule instead of never hearing about it.
|
|
1093
|
-
*/
|
|
1094
|
-
function workerWorkingContextPaths(req: DispatchRequest): string[] {
|
|
1095
|
-
const out = new Set<string>();
|
|
1096
|
-
for (const root of req.writeRoots ?? []) out.add(root);
|
|
1097
|
-
const text = `${req.task}\n${req.briefing ?? ""}`;
|
|
1098
|
-
for (const match of text.matchAll(PATH_TOKEN_RE)) {
|
|
1099
|
-
const token = match[0];
|
|
1100
|
-
if (token.startsWith("http://") || token.startsWith("https://")) continue;
|
|
1101
|
-
out.add(token);
|
|
1102
|
-
}
|
|
1103
|
-
return [...out];
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
1131
|
export function buildDynamicPromptMessages(
|
|
1107
1132
|
req: DispatchRequest,
|
|
1108
1133
|
dynamicContext: WorkerDynamicContext = {},
|
|
@@ -1126,6 +1151,10 @@ export function buildDynamicPromptMessages(
|
|
|
1126
1151
|
const body = `Safety posture: autonomy ${autonomy}. ${workerSafetyOneLiner(autonomy, permission)} Worker permission routing: ${permission}.`;
|
|
1127
1152
|
messages.push({ id: "dispatch-safety-posture", body, contentHash: sha256(body) });
|
|
1128
1153
|
}
|
|
1154
|
+
const requirements = renderDispatchIntentRequirements(req.intent);
|
|
1155
|
+
if (requirements !== null) {
|
|
1156
|
+
messages.push({ id: "dispatch-intent-requirements", body: requirements, contentHash: sha256(requirements) });
|
|
1157
|
+
}
|
|
1129
1158
|
if (req.competeStance !== undefined) {
|
|
1130
1159
|
const body = competeStanceLiner(req.competeStance);
|
|
1131
1160
|
messages.push({ id: "dispatch-compete-stance", body, contentHash: sha256(body) });
|
|
@@ -1250,6 +1279,7 @@ interface DispatchAdmissionStage {
|
|
|
1250
1279
|
|
|
1251
1280
|
interface DispatchWorkerSpecInput {
|
|
1252
1281
|
req: DispatchRequest;
|
|
1282
|
+
pathScope: DispatchPathScope;
|
|
1253
1283
|
target: ResolvedTarget;
|
|
1254
1284
|
admission: DispatchAdmissionStage;
|
|
1255
1285
|
recipe?: AgentRecipe | null;
|
|
@@ -1269,6 +1299,7 @@ interface DispatchWorkerSpecInput {
|
|
|
1269
1299
|
|
|
1270
1300
|
interface DispatchLifecycleStage {
|
|
1271
1301
|
recipe: AgentRecipe | null;
|
|
1302
|
+
pathScope: DispatchPathScope;
|
|
1272
1303
|
admission: DispatchAdmissionStage;
|
|
1273
1304
|
target: ResolvedTarget;
|
|
1274
1305
|
cwd: string;
|
|
@@ -1304,6 +1335,7 @@ interface DispatchLifecycleStage {
|
|
|
1304
1335
|
|
|
1305
1336
|
interface AcpDelegationLifecycleStage {
|
|
1306
1337
|
admission: DispatchAdmissionStage;
|
|
1338
|
+
pathScope: DispatchPathScope;
|
|
1307
1339
|
agentConfig: ReturnType<ConfigContract["get"]>["delegation"]["agents"][number];
|
|
1308
1340
|
cwd: string;
|
|
1309
1341
|
systemPrompt: string;
|
|
@@ -1488,10 +1520,6 @@ function explicitToolCapability(
|
|
|
1488
1520
|
return typeof fromKnowledgeBase === "boolean" ? fromKnowledgeBase : null;
|
|
1489
1521
|
}
|
|
1490
1522
|
|
|
1491
|
-
function writeConfinedRequest(req: DispatchRequest): boolean {
|
|
1492
|
-
return req.writeRoots !== undefined && req.writeRoots.length > 0;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
1523
|
function effectiveToolNames(
|
|
1496
1524
|
allowedTools: ReadonlyArray<ToolName>,
|
|
1497
1525
|
target: ResolvedTarget,
|
|
@@ -1717,6 +1745,7 @@ function resolveDispatchAdmissionStage(
|
|
|
1717
1745
|
req: DispatchRequest,
|
|
1718
1746
|
recipe: AgentRecipe,
|
|
1719
1747
|
safety: SafetyContract,
|
|
1748
|
+
pathScope: DispatchPathScope,
|
|
1720
1749
|
): DispatchAdmissionStage {
|
|
1721
1750
|
const recipeTools = recipe.tools;
|
|
1722
1751
|
const candidateTools = recipeTools && recipeTools.length > 0 ? (Array.from(recipeTools) as ToolName[]) : [];
|
|
@@ -1730,7 +1759,7 @@ function resolveDispatchAdmissionStage(
|
|
|
1730
1759
|
}
|
|
1731
1760
|
const requestedActions = deriveRequestedActions(allowedTools, safety);
|
|
1732
1761
|
const orchScope = pickOrchestratorScope(safety);
|
|
1733
|
-
const workerScope = pickWorkerScope(safety, requestedActions);
|
|
1762
|
+
const workerScope = pickWorkerScope(safety, requestedActions, pathScope);
|
|
1734
1763
|
const verdict = admit(
|
|
1735
1764
|
{
|
|
1736
1765
|
requestedScope: workerScope,
|
|
@@ -1755,7 +1784,7 @@ function resolveDelegationAdmissionStage(req: DispatchRequest, safety: SafetyCon
|
|
|
1755
1784
|
const allowedTools = applyToolProfile([], req.toolProfile);
|
|
1756
1785
|
const requestedActions = deriveRequestedActions(allowedTools, safety);
|
|
1757
1786
|
const orchScope = pickOrchestratorScope(safety);
|
|
1758
|
-
const workerScope = pickWorkerScope(safety, requestedActions);
|
|
1787
|
+
const workerScope = pickWorkerScope(safety, requestedActions, resolveDispatchPathScope(req));
|
|
1759
1788
|
const verdict = admit(
|
|
1760
1789
|
{
|
|
1761
1790
|
requestedScope: workerScope,
|
|
@@ -1833,9 +1862,17 @@ function buildDispatchWorkerSpec(input: DispatchWorkerSpecInput, config?: Config
|
|
|
1833
1862
|
// The worker repairs against exactly the contract the orchestrator will seal.
|
|
1834
1863
|
// A gate role that overrides the recipe contract gets no worker-side repair,
|
|
1835
1864
|
// for the same reason it gets no recipe validation.
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1865
|
+
//
|
|
1866
|
+
// The request's own override is the contract whether or not the seated
|
|
1867
|
+
// recipe declares one, because that is the resolution the seal already uses.
|
|
1868
|
+
// Requiring a recipe contract here meant a caller-supplied override against a
|
|
1869
|
+
// recipe with none was sealed but never sent, so the worker spent no repair
|
|
1870
|
+
// round on a contract it was never told about and the run failed on a shape
|
|
1871
|
+
// nothing had asked it for.
|
|
1872
|
+
const workerResultContract = appliesRecipeResultContract(input.req.gate?.role)
|
|
1873
|
+
? (input.req.resultContractOverride ?? input.recipe?.resultContract)
|
|
1874
|
+
: undefined;
|
|
1875
|
+
if (workerResultContract) spec.resultContract = workerResultContract;
|
|
1839
1876
|
const product = input.req.product ?? input.recipe?.product;
|
|
1840
1877
|
if (product) spec.product = product;
|
|
1841
1878
|
// The orchestrator's tool decision is the one the run was admitted under and
|
|
@@ -1886,14 +1923,9 @@ function buildDispatchWorkerSpec(input: DispatchWorkerSpecInput, config?: Config
|
|
|
1886
1923
|
if (escalation) spec.escalation = { timeoutMs: escalation.timeoutMs, fallback: escalation.fallback };
|
|
1887
1924
|
}
|
|
1888
1925
|
assertRuntimeCanHonorWorkerPermissionMode(input.target.runtime, spec.onPermission);
|
|
1889
|
-
// Carry write
|
|
1890
|
-
//
|
|
1891
|
-
|
|
1892
|
-
// reaches the worker absolute, matching the validation contract.
|
|
1893
|
-
if (input.req.writeRoots !== undefined && input.req.writeRoots.length > 0) {
|
|
1894
|
-
const jobCwd = input.req.cwd !== undefined && input.req.cwd.length > 0 ? input.req.cwd : process.cwd();
|
|
1895
|
-
spec.writeRoots = input.req.writeRoots.map((root) => resolvePath(jobCwd, root));
|
|
1896
|
-
}
|
|
1926
|
+
// Carry canonical write boundaries to the worker safety seam. Exact files
|
|
1927
|
+
// remain exact and trailing-slash entries remain subtrees after resolution.
|
|
1928
|
+
if (input.pathScope.writeBoundaries.length > 0) spec.writeRoots = [...input.pathScope.writeBoundaries];
|
|
1897
1929
|
assertWriteRootsEnforceable(input.target.runtime, spec.writeRoots);
|
|
1898
1930
|
// Carry the tool profile so external CLI runtimes that cannot mediate
|
|
1899
1931
|
// per-tool calls can refuse a narrowing profile they would otherwise ignore.
|
|
@@ -2380,6 +2412,7 @@ export function createDispatchBundle(
|
|
|
2380
2412
|
return DEFAULT_RESILIENCE_COOLDOWN_MS;
|
|
2381
2413
|
};
|
|
2382
2414
|
const now = options?.now ?? (() => Date.now());
|
|
2415
|
+
const monotonicNow = options?.monotonicNow ?? (() => performance.now());
|
|
2383
2416
|
// Durable evidence owner used by shadow observation and active readiness.
|
|
2384
2417
|
const routeObserver: RouteObserver = options?.routeObserver ?? createRouteObserver({});
|
|
2385
2418
|
|
|
@@ -2388,12 +2421,32 @@ export function createDispatchBundle(
|
|
|
2388
2421
|
const active = new Map<string, ActiveRun>();
|
|
2389
2422
|
const targetCooldowns = new Map<string, { until: number; reason: string }>();
|
|
2390
2423
|
const ownedReservations = new Set<string>();
|
|
2424
|
+
/**
|
|
2425
|
+
* Attribution from each finalized run's successful tool calls, newest last.
|
|
2426
|
+
* An incomplete record retains its paths as a lower bound beside the reasons
|
|
2427
|
+
* it cannot be read as a closed list.
|
|
2428
|
+
*
|
|
2429
|
+
* Held in memory rather than sealed into the receipt: the only reader is the
|
|
2430
|
+
* write boundary closing a window seconds later in this process, and a
|
|
2431
|
+
* per-run path list is run-shaped evidence the receipt schema does not carry.
|
|
2432
|
+
* Bounded because a long session finalizes many runs and none of this is
|
|
2433
|
+
* needed once its window has closed.
|
|
2434
|
+
*/
|
|
2435
|
+
const runWriteAttributions = new Map<string, WriteBoundaryAttribution>();
|
|
2436
|
+
const recordRunWriteAttribution = (runId: string, attribution: WriteBoundaryAttribution): void => {
|
|
2437
|
+
runWriteAttributions.set(runId, attribution);
|
|
2438
|
+
while (runWriteAttributions.size > RUN_WRITE_TARGET_HISTORY) {
|
|
2439
|
+
const oldest = runWriteAttributions.keys().next();
|
|
2440
|
+
if (oldest.done === true) break;
|
|
2441
|
+
runWriteAttributions.delete(oldest.value);
|
|
2442
|
+
}
|
|
2443
|
+
};
|
|
2391
2444
|
const capacityAdmission = createCapacityAdmissionController({
|
|
2392
2445
|
limits: () => {
|
|
2393
2446
|
const settings = getEffectiveSettings();
|
|
2394
2447
|
const nodes: Record<string, number> = { local: configuredGlobalCapacity(settings) };
|
|
2395
2448
|
for (const node of settings?.fleet?.nodes ?? []) nodes[node.id] = node.maxWorkers;
|
|
2396
|
-
return { global: configuredGlobalCapacity(settings), nodes };
|
|
2449
|
+
return { global: configuredGlobalCapacity(settings), nodes, endpoints: configuredEndpointLimits() };
|
|
2397
2450
|
},
|
|
2398
2451
|
now,
|
|
2399
2452
|
reservedPlanPeak: (planId) =>
|
|
@@ -2406,6 +2459,21 @@ export function createDispatchBundle(
|
|
|
2406
2459
|
}),
|
|
2407
2460
|
);
|
|
2408
2461
|
|
|
2462
|
+
function configuredEndpointLimits(): Readonly<Record<string, number>> {
|
|
2463
|
+
return Object.fromEntries(
|
|
2464
|
+
Object.entries(endpointCapacitiesForStatuses(providers.list())).map(([key, value]) => [key, value.limit]),
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
function endpointCapacityForTarget(targetId: string): EndpointCapacity | null {
|
|
2469
|
+
const status = providers.list().find((entry) => entry.target.id === targetId);
|
|
2470
|
+
if (status === undefined) return null;
|
|
2471
|
+
const endpoint = endpointCapacityForStatus(status);
|
|
2472
|
+
if (endpoint === null) return null;
|
|
2473
|
+
const configured = endpointCapacitiesForStatuses(providers.list())[endpoint.key];
|
|
2474
|
+
return configured ?? endpoint;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2409
2477
|
function reservationCapacitySnapshot(settings: EffectiveSettings): ReservationCapacitySnapshot {
|
|
2410
2478
|
const preflight = scheduling.preflight();
|
|
2411
2479
|
// Reservation planning compares against a fresh read, never the display cache.
|
|
@@ -2415,9 +2483,21 @@ export function createDispatchBundle(
|
|
|
2415
2483
|
};
|
|
2416
2484
|
for (const node of settings?.fleet?.nodes ?? [])
|
|
2417
2485
|
nodes[node.id] = { active: usage.nodes[node.id] ?? 0, limit: node.maxWorkers };
|
|
2486
|
+
const endpoints = Object.fromEntries(
|
|
2487
|
+
Object.entries(configuredEndpointLimits()).map(([key, limit]) => [
|
|
2488
|
+
key,
|
|
2489
|
+
{
|
|
2490
|
+
active: usage.endpoints[key] ?? 0,
|
|
2491
|
+
limit,
|
|
2492
|
+
leases: usage.endpointHolders[key]?.leases ?? 0,
|
|
2493
|
+
foregroundStreams: usage.endpointHolders[key]?.foregroundStreams ?? 0,
|
|
2494
|
+
},
|
|
2495
|
+
]),
|
|
2496
|
+
);
|
|
2418
2497
|
return {
|
|
2419
2498
|
global: { active: usage.global, limit: configuredGlobalCapacity(settings) },
|
|
2420
2499
|
nodes,
|
|
2500
|
+
endpoints,
|
|
2421
2501
|
budget: { currentUsd: preflight.currentUsd, ceilingUsd: preflight.ceilingUsd },
|
|
2422
2502
|
};
|
|
2423
2503
|
}
|
|
@@ -2429,6 +2509,7 @@ export function createDispatchBundle(
|
|
|
2429
2509
|
memberId: task.memberId,
|
|
2430
2510
|
wave: task.wave,
|
|
2431
2511
|
nodeId: task.resolution.node.id,
|
|
2512
|
+
...(task.resolution.endpoint !== undefined ? { endpointKey: task.resolution.endpoint.key } : {}),
|
|
2432
2513
|
costUpperBoundUsd: task.resolution.routeApproval?.totalCostUpperBoundUsd ?? task.resolution.costUpperBoundUsd,
|
|
2433
2514
|
})),
|
|
2434
2515
|
capacity: reservationCapacitySnapshot(getEffectiveSettings()),
|
|
@@ -2483,7 +2564,12 @@ export function createDispatchBundle(
|
|
|
2483
2564
|
return scheduling.maxWorkers?.() ?? (configured === "auto" || configured === undefined ? 4 : Math.max(1, configured));
|
|
2484
2565
|
}
|
|
2485
2566
|
|
|
2486
|
-
async function admitAssignmentCapacity(
|
|
2567
|
+
async function admitAssignmentCapacity(
|
|
2568
|
+
req: DispatchRequest,
|
|
2569
|
+
nodeId: string,
|
|
2570
|
+
timing: RunPhaseMarks,
|
|
2571
|
+
endpoint?: EndpointCapacity | null,
|
|
2572
|
+
) {
|
|
2487
2573
|
const queuedAt = now();
|
|
2488
2574
|
timing.queuedAt = new Date(queuedAt).toISOString();
|
|
2489
2575
|
const assignmentId = req.lineage?.rootRunId ?? `pending-${queuedAt.toString(36)}-${randomBytes(6).toString("hex")}`;
|
|
@@ -2501,9 +2587,16 @@ export function createDispatchBundle(
|
|
|
2501
2587
|
const admitted = await capacityAdmission.admit({
|
|
2502
2588
|
assignmentId,
|
|
2503
2589
|
nodeId,
|
|
2590
|
+
...(endpoint !== null && endpoint !== undefined ? { endpointKey: endpoint.key } : {}),
|
|
2504
2591
|
deadlineAt,
|
|
2505
2592
|
...plan,
|
|
2506
|
-
|
|
2593
|
+
// A fleet's first attempt carries lineage and still owns the held
|
|
2594
|
+
// reservation member that admission must transfer into its lease.
|
|
2595
|
+
// Retries carry the same reservation identity after that member was
|
|
2596
|
+
// consumed, so they reacquire/rebind the assignment lease normally.
|
|
2597
|
+
...(req.reservation !== undefined && (req.lineage === undefined || req.lineage.attempt === 0)
|
|
2598
|
+
? { reservation: req.reservation }
|
|
2599
|
+
: {}),
|
|
2507
2600
|
});
|
|
2508
2601
|
timing.admittedAt = new Date(admitted.admittedAt).toISOString();
|
|
2509
2602
|
return admitted.lease;
|
|
@@ -2520,6 +2613,7 @@ export function createDispatchBundle(
|
|
|
2520
2613
|
function rebindReservationSlot(
|
|
2521
2614
|
req: DispatchRequest,
|
|
2522
2615
|
nodeId: string,
|
|
2616
|
+
endpoint: EndpointCapacity | null,
|
|
2523
2617
|
costUsd: number,
|
|
2524
2618
|
settings: EffectiveSettings,
|
|
2525
2619
|
): void {
|
|
@@ -2528,6 +2622,7 @@ export function createDispatchBundle(
|
|
|
2528
2622
|
const rebind = {
|
|
2529
2623
|
...req.reservation,
|
|
2530
2624
|
nodeId,
|
|
2625
|
+
...(endpoint !== null ? { endpointKey: endpoint.key } : {}),
|
|
2531
2626
|
costUpperBoundUsd: req.routeApproval?.totalCostUpperBoundUsd ?? costUsd,
|
|
2532
2627
|
capacity,
|
|
2533
2628
|
nowMs: now(),
|
|
@@ -2635,6 +2730,21 @@ export function createDispatchBundle(
|
|
|
2635
2730
|
|
|
2636
2731
|
type RetryScheduleResult = { scheduled: true } | { scheduled: false; settlementDetail?: string };
|
|
2637
2732
|
|
|
2733
|
+
/** A reserved plan member owns its retry chain independently of the shared fleet root assignment. */
|
|
2734
|
+
function retryChainIsLive(run: ActiveRun): boolean {
|
|
2735
|
+
if (run.aborted) return false;
|
|
2736
|
+
const reservation = run.req.reservation;
|
|
2737
|
+
if (reservation === undefined) return assignments.get(run.lineage.rootRunId)?.status === "running";
|
|
2738
|
+
try {
|
|
2739
|
+
const record = getDispatchReservation(reservation.ownerId);
|
|
2740
|
+
const member = record?.members.find((entry) => entry.memberId === reservation.memberId);
|
|
2741
|
+
return record?.status === "active" && member !== undefined && member.status !== "released";
|
|
2742
|
+
} catch (error) {
|
|
2743
|
+
reportDispatchDiagnostic(`read retry liveness for reservation member ${reservation.memberId}`, error);
|
|
2744
|
+
return false;
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2638
2748
|
function maybeScheduleRetry(
|
|
2639
2749
|
run: ActiveRun,
|
|
2640
2750
|
outcome: RunOutcome,
|
|
@@ -2644,7 +2754,7 @@ export function createDispatchBundle(
|
|
|
2644
2754
|
): RetryScheduleResult {
|
|
2645
2755
|
if (draining) return { scheduled: false };
|
|
2646
2756
|
const rootRunId = run.lineage.rootRunId;
|
|
2647
|
-
if (
|
|
2757
|
+
if (!retryChainIsLive(run)) return { scheduled: false };
|
|
2648
2758
|
if (!RETRYABLE_OUTCOMES.has(outcome)) {
|
|
2649
2759
|
retryBackoff.delete(rootRunId);
|
|
2650
2760
|
return { scheduled: false };
|
|
@@ -2791,7 +2901,7 @@ export function createDispatchBundle(
|
|
|
2791
2901
|
reason = "retry",
|
|
2792
2902
|
decision: RetryDecision = decideRetry("internal", run.lineage.attempt, assignmentPolicyFor(run.req).maxRetries),
|
|
2793
2903
|
): Promise<void> {
|
|
2794
|
-
if (draining ||
|
|
2904
|
+
if (draining || !retryChainIsLive(run)) return;
|
|
2795
2905
|
const excludesNode = decision.excludedRouteParts.includes("node");
|
|
2796
2906
|
// Record the failed node so placement can choose an eligible survivor.
|
|
2797
2907
|
const rerouteHops =
|
|
@@ -2973,8 +3083,8 @@ export function createDispatchBundle(
|
|
|
2973
3083
|
}
|
|
2974
3084
|
}
|
|
2975
3085
|
|
|
2976
|
-
function heartbeatIso(
|
|
2977
|
-
return new Date(
|
|
3086
|
+
function heartbeatIso(heartbeat: HeartbeatStamp): string {
|
|
3087
|
+
return new Date(heartbeat.current).toISOString();
|
|
2978
3088
|
}
|
|
2979
3089
|
|
|
2980
3090
|
function heartbeatRunStatus(status: HeartbeatStatus): RunStatus {
|
|
@@ -2995,7 +3105,7 @@ export function createDispatchBundle(
|
|
|
2995
3105
|
event: {
|
|
2996
3106
|
type: "heartbeat_status",
|
|
2997
3107
|
status,
|
|
2998
|
-
heartbeatAt: run.heartbeatAt ? heartbeatIso(run.heartbeatAt
|
|
3108
|
+
heartbeatAt: run.heartbeatAt ? heartbeatIso(run.heartbeatAt) : null,
|
|
2999
3109
|
},
|
|
3000
3110
|
});
|
|
3001
3111
|
}
|
|
@@ -3013,16 +3123,16 @@ export function createDispatchBundle(
|
|
|
3013
3123
|
*/
|
|
3014
3124
|
function checkActiveHeartbeats(): void {
|
|
3015
3125
|
if (!ledger) return;
|
|
3016
|
-
const
|
|
3126
|
+
const tickMonotonic = monotonicNow();
|
|
3017
3127
|
for (const run of active.values()) {
|
|
3018
3128
|
if (run.aborted || run.stallKilled || !run.heartbeatAt) continue;
|
|
3019
|
-
const
|
|
3020
|
-
if (!Number.isFinite(
|
|
3129
|
+
const heartbeatMonotonic = heartbeatMonotonicAt(run.heartbeatAt);
|
|
3130
|
+
if (!Number.isFinite(heartbeatMonotonic) || !Number.isFinite(run.heartbeatAt.current)) continue;
|
|
3021
3131
|
if (run.runtimeKind === "acp-delegation") {
|
|
3022
|
-
ledger.update(run.runId, { heartbeatAt: heartbeatIso(
|
|
3132
|
+
ledger.update(run.runId, { heartbeatAt: heartbeatIso(run.heartbeatAt) });
|
|
3023
3133
|
const stallMs = run.stallTimeoutMs;
|
|
3024
3134
|
if (stallMs === null || stallMs <= 0) continue;
|
|
3025
|
-
if (
|
|
3135
|
+
if (tickMonotonic - heartbeatMonotonic <= stallMs) continue;
|
|
3026
3136
|
run.stallKilled = true;
|
|
3027
3137
|
run.heartbeatStatus = "dead";
|
|
3028
3138
|
emitHeartbeatStatus(run, "dead");
|
|
@@ -3033,10 +3143,10 @@ export function createDispatchBundle(
|
|
|
3033
3143
|
}
|
|
3034
3144
|
continue;
|
|
3035
3145
|
}
|
|
3036
|
-
const status = classifyHeartbeat(
|
|
3146
|
+
const status = classifyHeartbeat(heartbeatMonotonic, tickMonotonic, heartbeatSpec);
|
|
3037
3147
|
const patch: Partial<RunEnvelope> = {
|
|
3038
3148
|
status: heartbeatRunStatus(status),
|
|
3039
|
-
heartbeatAt: heartbeatIso(
|
|
3149
|
+
heartbeatAt: heartbeatIso(run.heartbeatAt),
|
|
3040
3150
|
};
|
|
3041
3151
|
ledger.update(run.runId, patch);
|
|
3042
3152
|
// Node staleness display feeds off the freshest worker heartbeat.
|
|
@@ -3169,6 +3279,27 @@ export function createDispatchBundle(
|
|
|
3169
3279
|
targetCooldowns.set(key, { until: now() + cooldownMs, reason: failureClass });
|
|
3170
3280
|
}
|
|
3171
3281
|
|
|
3282
|
+
function publishDispatchPathScope(req: DispatchRequest, pathScope: DispatchPathScope): void {
|
|
3283
|
+
const replacementDiagnostic = declaredScopeReplacementDiagnostic(pathScope);
|
|
3284
|
+
if (replacementDiagnostic !== null) {
|
|
3285
|
+
reportDispatchDiagnostic("typed scope replacement", new Error(replacementDiagnostic));
|
|
3286
|
+
}
|
|
3287
|
+
// Only the replacement case reaches the transcript. A dispatch that
|
|
3288
|
+
// declared no intent is the ordinary case rather than an anomaly, and one
|
|
3289
|
+
// receipt in ninety-nine carries an intent key today, so noticing it would
|
|
3290
|
+
// warn on almost every dispatch and teach the operator to skip the channel.
|
|
3291
|
+
// Its provenance is not lost: the receipt seals `pathProvenance`, and the
|
|
3292
|
+
// approval artifact renders every inferred entry in full before a
|
|
3293
|
+
// supervised dispatch runs, which is where an operator can still act on it.
|
|
3294
|
+
const notice = declaredScopeReplacementNotice(pathScope);
|
|
3295
|
+
if (notice !== null) {
|
|
3296
|
+
context.bus.emit(BusChannels.DispatchScopeNotice, {
|
|
3297
|
+
...notice,
|
|
3298
|
+
agentId: req.agentId,
|
|
3299
|
+
});
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3172
3303
|
async function resolveLifecycle(req: DispatchRequest, settings: EffectiveSettings): Promise<DispatchLifecycleStage> {
|
|
3173
3304
|
const recipe = agents.get(req.agentId);
|
|
3174
3305
|
if (!recipe) {
|
|
@@ -3180,7 +3311,7 @@ export function createDispatchBundle(
|
|
|
3180
3311
|
`dispatch: agent '${req.agentId}' is a ${spec.audience} agent reserved for Clio internal orchestration`,
|
|
3181
3312
|
);
|
|
3182
3313
|
}
|
|
3183
|
-
if (
|
|
3314
|
+
if (hasCallerPersonaOverride(req) && (spec.audience === "shadow" || spec.audience === "internal")) {
|
|
3184
3315
|
throw new Error(`dispatch: persona overrides are not allowed for ${spec.audience} agent '${req.agentId}'`);
|
|
3185
3316
|
}
|
|
3186
3317
|
// A read-only recipe pointed at a mutating task is knowable here and costs
|
|
@@ -3202,7 +3333,9 @@ export function createDispatchBundle(
|
|
|
3202
3333
|
specs: agents.listSpecs(),
|
|
3203
3334
|
});
|
|
3204
3335
|
if (capabilityMismatch?.verdict === "refuse") throw new Error(capabilityMismatch.detail);
|
|
3205
|
-
const
|
|
3336
|
+
const pathScope = resolveDispatchPathScope(req);
|
|
3337
|
+
publishDispatchPathScope(req, pathScope);
|
|
3338
|
+
const admission = resolveDispatchAdmissionStage(req, recipe, safety, pathScope);
|
|
3206
3339
|
const targets = readWorkerTargets(settings);
|
|
3207
3340
|
const target = resolveDispatchTarget(
|
|
3208
3341
|
req,
|
|
@@ -3221,7 +3354,7 @@ export function createDispatchBundle(
|
|
|
3221
3354
|
const sessionAutonomy = settings?.autonomy ?? "auto-edit";
|
|
3222
3355
|
const effectiveAutonomy = clampWorkerAutonomy(sessionAutonomy, req.autonomy);
|
|
3223
3356
|
const effectiveTools = withLedgerToolNarrowing(
|
|
3224
|
-
effectiveToolNames(admission.allowedTools, target,
|
|
3357
|
+
effectiveToolNames(admission.allowedTools, target, pathScope.writeBoundaries.length > 0, deniedToolNames(req)),
|
|
3225
3358
|
req,
|
|
3226
3359
|
);
|
|
3227
3360
|
assertPostRuntimeToolCompatibility(req.agentId, spec, effectiveTools, target);
|
|
@@ -3250,7 +3383,7 @@ export function createDispatchBundle(
|
|
|
3250
3383
|
dynamic: false,
|
|
3251
3384
|
},
|
|
3252
3385
|
cwd,
|
|
3253
|
-
workingContextPaths:
|
|
3386
|
+
workingContextPaths: pathScope.workingContextPaths,
|
|
3254
3387
|
});
|
|
3255
3388
|
const systemPrompt = compiledWorkerPrompt.systemPrompt;
|
|
3256
3389
|
const budgetEnvelope = resolveEffectiveWorkerBudget({
|
|
@@ -3294,6 +3427,7 @@ export function createDispatchBundle(
|
|
|
3294
3427
|
const limitations = runtimeLimitations(runtimeKind, target.runtime.id);
|
|
3295
3428
|
return {
|
|
3296
3429
|
recipe,
|
|
3430
|
+
pathScope,
|
|
3297
3431
|
admission: effectiveAdmission,
|
|
3298
3432
|
target,
|
|
3299
3433
|
cwd,
|
|
@@ -3331,12 +3465,7 @@ export function createDispatchBundle(
|
|
|
3331
3465
|
): AcpDelegationLifecycleStage {
|
|
3332
3466
|
const agentId = req.delegationAgentId;
|
|
3333
3467
|
if (!agentId) throw new Error("dispatch: missing delegationAgentId");
|
|
3334
|
-
|
|
3335
|
-
role: req.gate?.role,
|
|
3336
|
-
autonomy: req.autonomy,
|
|
3337
|
-
systemPrompt: req.systemPrompt,
|
|
3338
|
-
});
|
|
3339
|
-
if (hasPersonaOverride(req) && !boundedGateRolePrompt) {
|
|
3468
|
+
if (hasCallerPersonaOverride(req)) {
|
|
3340
3469
|
throw new Error(`dispatch: persona overrides are not allowed for ACP delegation agent '${agentId}'`);
|
|
3341
3470
|
}
|
|
3342
3471
|
if (req.agentId && maybeAgents) {
|
|
@@ -3365,6 +3494,7 @@ export function createDispatchBundle(
|
|
|
3365
3494
|
);
|
|
3366
3495
|
}
|
|
3367
3496
|
const cwd = req.cwd ?? process.cwd();
|
|
3497
|
+
const pathScope = resolveDispatchPathScope(req);
|
|
3368
3498
|
const personaBody = workerPersonaBody(req, null, []);
|
|
3369
3499
|
// ACP owns an unknown external tool inventory, so it receives the raw
|
|
3370
3500
|
// bounded persona rather than a native Clio schema-harness claim.
|
|
@@ -3391,6 +3521,7 @@ export function createDispatchBundle(
|
|
|
3391
3521
|
const personaOverride = personaOverrideFor(req, staticCompositionHash);
|
|
3392
3522
|
return {
|
|
3393
3523
|
admission,
|
|
3524
|
+
pathScope,
|
|
3394
3525
|
agentConfig: configured,
|
|
3395
3526
|
cwd,
|
|
3396
3527
|
systemPrompt,
|
|
@@ -3426,6 +3557,7 @@ export function createDispatchBundle(
|
|
|
3426
3557
|
finalPromise: Promise<RunReceipt>;
|
|
3427
3558
|
}> {
|
|
3428
3559
|
const lifecycle = resolveAcpDelegationLifecycle(req, settings);
|
|
3560
|
+
publishDispatchPathScope(req, lifecycle.pathScope);
|
|
3429
3561
|
timing.decisionCompletedAt = new Date(now()).toISOString();
|
|
3430
3562
|
const targetId = `delegation:${lifecycle.agentConfig.id}`;
|
|
3431
3563
|
const runtimeId = "acp";
|
|
@@ -3434,7 +3566,7 @@ export function createDispatchBundle(
|
|
|
3434
3566
|
|
|
3435
3567
|
assertBudgetAdmitsRoute(req, { rates: null, provenance: "unknown" }, settings);
|
|
3436
3568
|
|
|
3437
|
-
const capacityLease = await admitAssignmentCapacity(req, "local", timing);
|
|
3569
|
+
const capacityLease = await admitAssignmentCapacity(req, "local", timing, null);
|
|
3438
3570
|
const leaseSlot = createLeaseSlotGuard(capacityAdmission, capacityLease.leaseId, req.lineage !== undefined);
|
|
3439
3571
|
|
|
3440
3572
|
// Resolve the ledger before starting the ACP process: an external agent
|
|
@@ -3460,6 +3592,8 @@ export function createDispatchBundle(
|
|
|
3460
3592
|
safety,
|
|
3461
3593
|
autonomy: lifecycle.autonomy,
|
|
3462
3594
|
clientVersion: readClioVersion(),
|
|
3595
|
+
now,
|
|
3596
|
+
monotonicNow,
|
|
3463
3597
|
});
|
|
3464
3598
|
} catch (error) {
|
|
3465
3599
|
leaseSlot.release();
|
|
@@ -3648,7 +3782,7 @@ export function createDispatchBundle(
|
|
|
3648
3782
|
ledgerRef.update(envelope.id, {
|
|
3649
3783
|
status: "running",
|
|
3650
3784
|
pid: acp.pid,
|
|
3651
|
-
heartbeatAt: heartbeatIso(acp.heartbeatAt
|
|
3785
|
+
heartbeatAt: heartbeatIso(acp.heartbeatAt),
|
|
3652
3786
|
lineage,
|
|
3653
3787
|
identity,
|
|
3654
3788
|
node: LOCAL_RUN_NODE,
|
|
@@ -3799,6 +3933,7 @@ export function createDispatchBundle(
|
|
|
3799
3933
|
requestOrigin: lifecycle.requestOrigin,
|
|
3800
3934
|
task: req.task,
|
|
3801
3935
|
...(req.intent !== undefined ? { intent: structuredClone(req.intent) } : {}),
|
|
3936
|
+
pathScope: structuredClone(lifecycle.pathScope.provenance),
|
|
3802
3937
|
targetId,
|
|
3803
3938
|
wireModelId,
|
|
3804
3939
|
runtimeId,
|
|
@@ -4044,7 +4179,7 @@ export function createDispatchBundle(
|
|
|
4044
4179
|
cacheReadTokenCount: receiptDraft.cacheReadTokenCount ?? 0,
|
|
4045
4180
|
cacheWriteTokenCount: receiptDraft.cacheWriteTokenCount ?? 0,
|
|
4046
4181
|
reasoningTokenCount: receiptDraft.reasoningTokenCount ?? 0,
|
|
4047
|
-
heartbeatAt: heartbeatIso(acp.heartbeatAt
|
|
4182
|
+
heartbeatAt: heartbeatIso(acp.heartbeatAt),
|
|
4048
4183
|
};
|
|
4049
4184
|
ledgerRef.update(envelope.id, ledgerPatch);
|
|
4050
4185
|
const receipt = ledgerRef.recordReceipt(envelope.id, sealRouteDecision(receiptDraft, routeDecision));
|
|
@@ -4202,7 +4337,7 @@ export function createDispatchBundle(
|
|
|
4202
4337
|
"dispatch: writeRoots cannot be enforced on an ACP delegation target; the external agent runs its own tool surface. Dispatch to a native or claude-sdk worker.",
|
|
4203
4338
|
);
|
|
4204
4339
|
}
|
|
4205
|
-
rebindReservationSlot(req, "local", UNKNOWN_PRICING_ADMISSION_ESTIMATE_USD, settings);
|
|
4340
|
+
rebindReservationSlot(req, "local", null, UNKNOWN_PRICING_ADMISSION_ESTIMATE_USD, settings);
|
|
4206
4341
|
routeObservation = observeShadowRoute(req, undefined, settings);
|
|
4207
4342
|
return attachRouteObservation({
|
|
4208
4343
|
...(await dispatchAcpDelegation(req, settings, timing, routeObservation.decision, observer)),
|
|
@@ -4236,10 +4371,12 @@ export function createDispatchBundle(
|
|
|
4236
4371
|
model: lifecycle.target.wireModelId,
|
|
4237
4372
|
node: placement?.node?.id ?? "local",
|
|
4238
4373
|
};
|
|
4374
|
+
const endpoint = endpointCapacityForTarget(lifecycle.target.target.id);
|
|
4239
4375
|
assertRouteWithinApprovedEnvelope(req, effectiveRoute);
|
|
4240
4376
|
rebindReservationSlot(
|
|
4241
4377
|
req,
|
|
4242
4378
|
effectiveRoute.node,
|
|
4379
|
+
endpoint,
|
|
4243
4380
|
conservativeRouteAdmissionEstimateUsd(lifecycle.target.effectivePricing, admissionMaxOutputTokens(settings)),
|
|
4244
4381
|
settings,
|
|
4245
4382
|
);
|
|
@@ -4258,7 +4395,7 @@ export function createDispatchBundle(
|
|
|
4258
4395
|
shadow: () => observeShadowRoute(req, placed, settings),
|
|
4259
4396
|
});
|
|
4260
4397
|
|
|
4261
|
-
const capacityLease = await admitAssignmentCapacity(req, placement?.node.id ?? "local", timing);
|
|
4398
|
+
const capacityLease = await admitAssignmentCapacity(req, placement?.node.id ?? "local", timing, endpoint);
|
|
4262
4399
|
const leaseSlot = createLeaseSlotGuard(capacityAdmission, capacityLease.leaseId, req.lineage !== undefined);
|
|
4263
4400
|
|
|
4264
4401
|
const ledgerRef = (() => {
|
|
@@ -4276,6 +4413,7 @@ export function createDispatchBundle(
|
|
|
4276
4413
|
const spec = buildDispatchWorkerSpec(
|
|
4277
4414
|
{
|
|
4278
4415
|
req,
|
|
4416
|
+
pathScope: lifecycle.pathScope,
|
|
4279
4417
|
target: lifecycle.target,
|
|
4280
4418
|
admission: lifecycle.admission,
|
|
4281
4419
|
recipe: lifecycle.recipe,
|
|
@@ -4326,6 +4464,8 @@ export function createDispatchBundle(
|
|
|
4326
4464
|
try {
|
|
4327
4465
|
worker = (placement?.spawn ?? spawnWorker)(spec, {
|
|
4328
4466
|
cwd: lifecycle.cwd,
|
|
4467
|
+
now,
|
|
4468
|
+
monotonicNow,
|
|
4329
4469
|
...(agentLedgerId !== null ? { onLedgerPost } : {}),
|
|
4330
4470
|
});
|
|
4331
4471
|
} catch (error) {
|
|
@@ -4382,10 +4522,21 @@ export function createDispatchBundle(
|
|
|
4382
4522
|
const upstreamResponses: RunReceiptUpstreamResponse[] = [];
|
|
4383
4523
|
const skillActivations: SkillActivation[] = [];
|
|
4384
4524
|
const finishContractEntries: unknown[] = [];
|
|
4525
|
+
let runIdForPermissionAudit: string | null = null;
|
|
4385
4526
|
// The worker's own tool calls, folded into what this run changed and what
|
|
4386
4527
|
// it validated. The sealed mutation-report contract is measured against
|
|
4387
4528
|
// this, so a reported path the run never touched cannot seal as fact.
|
|
4388
|
-
const runEffects = createRunEffectsRecorder(lifecycle.cwd
|
|
4529
|
+
const runEffects = createRunEffectsRecorder(lifecycle.cwd, {
|
|
4530
|
+
onWriteRecordDowngraded(downgrade) {
|
|
4531
|
+
const runId = runIdForPermissionAudit;
|
|
4532
|
+
if (runId === null) return;
|
|
4533
|
+
context.bus.emit(BusChannels.DispatchProgress, {
|
|
4534
|
+
runId,
|
|
4535
|
+
agentId: req.agentId,
|
|
4536
|
+
event: { type: "clio_write_record_downgraded", payload: downgrade },
|
|
4537
|
+
});
|
|
4538
|
+
},
|
|
4539
|
+
});
|
|
4389
4540
|
let finishContractAssistantText = "";
|
|
4390
4541
|
let finishContractAssistantTurnId: string | null = null;
|
|
4391
4542
|
let failureMessage: string | undefined;
|
|
@@ -4395,7 +4546,6 @@ export function createDispatchBundle(
|
|
|
4395
4546
|
const acceptsOutcomeCodeEvents =
|
|
4396
4547
|
lifecycle.runtimeKind === "http" ||
|
|
4397
4548
|
(lifecycle.runtimeKind === "sdk" && lifecycle.target.runtime.id === "claude-sdk");
|
|
4398
|
-
let runIdForPermissionAudit: string | null = null;
|
|
4399
4549
|
let workerPolicyPermissionCounter = 0;
|
|
4400
4550
|
const outputCapture = createWorkerOutputCapture();
|
|
4401
4551
|
const markObservedPhase = (field: "firstModelTokenAt" | "firstToolAt"): void => {
|
|
@@ -4678,7 +4828,7 @@ export function createDispatchBundle(
|
|
|
4678
4828
|
...(req.council !== undefined ? { council: req.council } : {}),
|
|
4679
4829
|
...(req.plan !== undefined ? { plan: req.plan } : {}),
|
|
4680
4830
|
...(lifecycle.personaOverride ? { personaOverride: lifecycle.personaOverride } : {}),
|
|
4681
|
-
...(heartbeatAt ? { heartbeatAt: heartbeatIso(heartbeatAt
|
|
4831
|
+
...(heartbeatAt ? { heartbeatAt: heartbeatIso(heartbeatAt) } : {}),
|
|
4682
4832
|
});
|
|
4683
4833
|
observer?.onAdmitted({
|
|
4684
4834
|
runId: envelope.id,
|
|
@@ -4694,6 +4844,7 @@ export function createDispatchBundle(
|
|
|
4694
4844
|
// per-worker meter. All optional so consumers degrade to local/plain.
|
|
4695
4845
|
const fleetIdentity = {
|
|
4696
4846
|
budget: lifecycle.budgetEnvelope,
|
|
4847
|
+
...(endpoint !== null ? { endpoint: { key: endpoint.key, label: endpoint.label, limit: endpoint.limit } } : {}),
|
|
4697
4848
|
...(placement !== undefined && placement !== null ? { node: placement.node.id } : {}),
|
|
4698
4849
|
...(req.gate !== undefined ? { gate: { role: req.gate.role, cycle: req.gate.cycle } } : {}),
|
|
4699
4850
|
...(req.council !== undefined ? { council: req.council } : {}),
|
|
@@ -4893,6 +5044,7 @@ export function createDispatchBundle(
|
|
|
4893
5044
|
outcomeCode,
|
|
4894
5045
|
lineage,
|
|
4895
5046
|
...(req.intent !== undefined ? { intent: structuredClone(req.intent) } : {}),
|
|
5047
|
+
pathScope: structuredClone(lifecycle.pathScope.provenance),
|
|
4896
5048
|
identity,
|
|
4897
5049
|
node: placement?.node ?? LOCAL_RUN_NODE,
|
|
4898
5050
|
...receiptAttestationFields(worker.attestation?.() ?? null),
|
|
@@ -5269,13 +5421,51 @@ export function createDispatchBundle(
|
|
|
5269
5421
|
...(receiptDraft.cacheWriteTokenCount !== undefined
|
|
5270
5422
|
? { cacheWriteTokenCount: receiptDraft.cacheWriteTokenCount }
|
|
5271
5423
|
: {}),
|
|
5272
|
-
...(activeRun.heartbeatAt ? { heartbeatAt: heartbeatIso(activeRun.heartbeatAt
|
|
5424
|
+
...(activeRun.heartbeatAt ? { heartbeatAt: heartbeatIso(activeRun.heartbeatAt) } : {}),
|
|
5273
5425
|
};
|
|
5274
5426
|
if (receiptDraft.reasoningTokenCount !== undefined) {
|
|
5275
5427
|
ledgerPatch.reasoningTokenCount = receiptDraft.reasoningTokenCount;
|
|
5276
5428
|
}
|
|
5277
5429
|
ledgerRef.update(envelope.id, ledgerPatch);
|
|
5278
5430
|
const receipt = ledgerRef.recordReceipt(envelope.id, sealRouteDecision(receiptDraft, routeObservation.decision));
|
|
5431
|
+
// Two independent ways a run's write record can fail to be a closed
|
|
5432
|
+
// list, and either one files it as `null` rather than as an empty
|
|
5433
|
+
// one. The difference decides whether the write boundary may clear a
|
|
5434
|
+
// path it did not see this run touch.
|
|
5435
|
+
//
|
|
5436
|
+
// The telemetry axis is whether the events were seen at all: a
|
|
5437
|
+
// subprocess runtime publishes no tool events, and a partial trail
|
|
5438
|
+
// has its holes exactly where an unobserved write would sit.
|
|
5439
|
+
//
|
|
5440
|
+
// The opacity axis is whether the events that were seen say enough.
|
|
5441
|
+
// A run that called bash, verify, dispatch, or steer to a clean exit
|
|
5442
|
+
// could have written through a channel no argument names, so nothing
|
|
5443
|
+
// in the checkout can be cleared on the strength of its absence from
|
|
5444
|
+
// the set. That run's window keeps today's blame-the-whole-diff
|
|
5445
|
+
// behaviour; a run that never touched such a tool keeps the
|
|
5446
|
+
// protection.
|
|
5447
|
+
const toolTelemetryComplete = receipt.safety?.toolTelemetry?.coverage === "complete";
|
|
5448
|
+
const downgrades: WriteBoundaryAttributionDowngrade[] = observedRunEffects.writeRecordDowngrades.map((entry) => ({
|
|
5449
|
+
reason: entry.reason,
|
|
5450
|
+
tool: entry.tool,
|
|
5451
|
+
toolCallId: entry.toolCallId,
|
|
5452
|
+
runId: envelope.id,
|
|
5453
|
+
stepId: null,
|
|
5454
|
+
}));
|
|
5455
|
+
if (!toolTelemetryComplete) {
|
|
5456
|
+
downgrades.push({
|
|
5457
|
+
reason: "incomplete_tool_telemetry",
|
|
5458
|
+
tool: null,
|
|
5459
|
+
toolCallId: null,
|
|
5460
|
+
runId: envelope.id,
|
|
5461
|
+
stepId: null,
|
|
5462
|
+
});
|
|
5463
|
+
}
|
|
5464
|
+
recordRunWriteAttribution(envelope.id, {
|
|
5465
|
+
recorded: [...observedRunEffects.mutatedPaths],
|
|
5466
|
+
complete: toolTelemetryComplete && observedRunEffects.writeRecordComplete,
|
|
5467
|
+
downgrades,
|
|
5468
|
+
});
|
|
5279
5469
|
await ledgerRef.persist();
|
|
5280
5470
|
if (worktreeReceipt?.applied === true && req.taskWorktree !== undefined) {
|
|
5281
5471
|
try {
|
|
@@ -5510,10 +5700,11 @@ export function createDispatchBundle(
|
|
|
5510
5700
|
`dispatch: agent '${req.agentId}' is a ${agentSpec.audience} agent reserved for Clio internal orchestration`,
|
|
5511
5701
|
);
|
|
5512
5702
|
}
|
|
5513
|
-
if (
|
|
5703
|
+
if (hasCallerPersonaOverride(req) && (agentSpec.audience === "shadow" || agentSpec.audience === "internal")) {
|
|
5514
5704
|
throw new Error(`dispatch: persona overrides are not allowed for ${agentSpec.audience} agent '${req.agentId}'`);
|
|
5515
5705
|
}
|
|
5516
|
-
const
|
|
5706
|
+
const pathScope = resolveDispatchPathScope(req);
|
|
5707
|
+
const admission = resolveDispatchAdmissionStage(req, recipe, safety, pathScope);
|
|
5517
5708
|
const targets = readWorkerTargets(settings);
|
|
5518
5709
|
const target = resolveDispatchTarget(
|
|
5519
5710
|
req,
|
|
@@ -5526,14 +5717,14 @@ export function createDispatchBundle(
|
|
|
5526
5717
|
);
|
|
5527
5718
|
enforceCapabilityGate(target.target.id, target.modelCapabilities, req.requiredCapabilities);
|
|
5528
5719
|
const effectiveTools = withLedgerToolNarrowing(
|
|
5529
|
-
effectiveToolNames(admission.allowedTools, target,
|
|
5720
|
+
effectiveToolNames(admission.allowedTools, target, pathScope.writeBoundaries.length > 0, deniedToolNames(req)),
|
|
5530
5721
|
req,
|
|
5531
5722
|
);
|
|
5532
5723
|
assertPostRuntimeToolCompatibility(req.agentId, agentSpec, effectiveTools, target);
|
|
5533
5724
|
assertRuntimeCanHonorWorkerPermissionMode(target.runtime, settings?.workers.onPermission ?? "deny");
|
|
5534
5725
|
assertWorkerBudgetEnforceable(target.runtime, agentSpec.budget !== null || req.budget !== undefined);
|
|
5535
5726
|
assertResponseSchemaEnforceable(target.runtime, target.modelCapabilities, req.responseSchema, effectiveTools.length);
|
|
5536
|
-
assertWriteRootsEnforceable(target.runtime,
|
|
5727
|
+
assertWriteRootsEnforceable(target.runtime, pathScope.writeBoundaries);
|
|
5537
5728
|
assertProtectedArtifactsEnforceable(
|
|
5538
5729
|
target.runtime.id,
|
|
5539
5730
|
target.runtime.kind !== "subprocess",
|
|
@@ -5546,12 +5737,14 @@ export function createDispatchBundle(
|
|
|
5546
5737
|
allowedTools: effectiveTools,
|
|
5547
5738
|
settings,
|
|
5548
5739
|
});
|
|
5740
|
+
const endpoint = endpointCapacityForTarget(target.target.id);
|
|
5549
5741
|
return {
|
|
5550
5742
|
agentId: req.agentId,
|
|
5551
5743
|
specFingerprint: agentSpecFingerprint(agentSpec),
|
|
5552
5744
|
targetId: target.target.id,
|
|
5553
5745
|
wireModelId: target.wireModelId,
|
|
5554
5746
|
runtimeId: target.runtime.id,
|
|
5747
|
+
...(endpoint !== null ? { endpoint: { key: endpoint.key, label: endpoint.label, limit: endpoint.limit } } : {}),
|
|
5555
5748
|
node: previewNode(req).node,
|
|
5556
5749
|
thinkingLevel: target.thinkingLevel,
|
|
5557
5750
|
toolSignature: toolSignature(effectiveTools),
|
|
@@ -5833,7 +6026,7 @@ export function createDispatchBundle(
|
|
|
5833
6026
|
async function sealCouncilSynthesis(input: {
|
|
5834
6027
|
group: string;
|
|
5835
6028
|
round: number;
|
|
5836
|
-
kind: "none" | "vote";
|
|
6029
|
+
kind: "none" | "vote" | "judge";
|
|
5837
6030
|
text: string;
|
|
5838
6031
|
subjects: ReadonlyArray<{ runId: string; digest: string | null }>;
|
|
5839
6032
|
template: RunReceipt;
|
|
@@ -5991,6 +6184,7 @@ export function createDispatchBundle(
|
|
|
5991
6184
|
const reconciledLedger = ledger;
|
|
5992
6185
|
const reconciled = await reconcileOrphanAssignments({
|
|
5993
6186
|
listRunning: () => listStoredAssignments().filter((record) => record.status === "running"),
|
|
6187
|
+
ownerAlive: assignmentProcessOwnerAlive,
|
|
5994
6188
|
lookupAttempt: (runId) => {
|
|
5995
6189
|
const envelope = reconciledLedger?.get(runId) ?? null;
|
|
5996
6190
|
if (!envelope) return null;
|
|
@@ -6000,7 +6194,8 @@ export function createDispatchBundle(
|
|
|
6000
6194
|
succeeded: envelope.outcome === "succeeded",
|
|
6001
6195
|
};
|
|
6002
6196
|
},
|
|
6003
|
-
settle: (assignmentId, terminalRunId, status) =>
|
|
6197
|
+
settle: (assignmentId, terminalRunId, status, owner) =>
|
|
6198
|
+
settleStoredAssignment(assignmentId, terminalRunId, status, owner),
|
|
6004
6199
|
});
|
|
6005
6200
|
if ((reconciled.recovered > 0 || reconciled.abandoned > 0) && process.env.CLIO_CODER_INTERACTIVE !== "1") {
|
|
6006
6201
|
process.stderr.write(
|
|
@@ -6049,19 +6244,21 @@ export function createDispatchBundle(
|
|
|
6049
6244
|
*/
|
|
6050
6245
|
function snapshot(): DispatchSnapshot {
|
|
6051
6246
|
const tickNow = now();
|
|
6247
|
+
const tickMonotonic = monotonicNow();
|
|
6052
6248
|
const running: DispatchSnapshot["running"] = [];
|
|
6053
6249
|
const totals = { ...finalizedTotals };
|
|
6054
6250
|
const costAmounts = [...finalizedCosts];
|
|
6055
6251
|
for (const run of active.values()) {
|
|
6056
6252
|
let heartbeat: "alive" | "stale" | "dead" | "n/a" = "n/a";
|
|
6057
|
-
if (run.heartbeatAt && Number.isFinite(run.heartbeatAt
|
|
6253
|
+
if (run.heartbeatAt && Number.isFinite(heartbeatMonotonicAt(run.heartbeatAt))) {
|
|
6254
|
+
const heartbeatMonotonic = heartbeatMonotonicAt(run.heartbeatAt);
|
|
6058
6255
|
if (run.runtimeKind === "acp-delegation") {
|
|
6059
6256
|
const stallMs = run.stallTimeoutMs;
|
|
6060
6257
|
if (stallMs !== null && stallMs > 0) {
|
|
6061
|
-
heartbeat =
|
|
6258
|
+
heartbeat = tickMonotonic - heartbeatMonotonic > stallMs ? "dead" : "alive";
|
|
6062
6259
|
}
|
|
6063
6260
|
} else {
|
|
6064
|
-
heartbeat = classifyHeartbeat(
|
|
6261
|
+
heartbeat = classifyHeartbeat(heartbeatMonotonic, tickMonotonic, heartbeatSpec);
|
|
6065
6262
|
}
|
|
6066
6263
|
}
|
|
6067
6264
|
const meter = run.meter;
|
|
@@ -6187,6 +6384,7 @@ export function createDispatchBundle(
|
|
|
6187
6384
|
routeCandidates,
|
|
6188
6385
|
reservations: {
|
|
6189
6386
|
prepare: prepareReservation,
|
|
6387
|
+
release: (ownerId) => releaseDispatchReservation(ownerId, now()),
|
|
6190
6388
|
rollback: (ownerId) => rollbackDispatchReservation(ownerId, now()),
|
|
6191
6389
|
rollbackUnconsumed: (ownerId) => rollbackUnconsumedDispatchReservation(ownerId, now()),
|
|
6192
6390
|
get: getDispatchReservation,
|
|
@@ -6204,6 +6402,21 @@ export function createDispatchBundle(
|
|
|
6204
6402
|
if (!ledger) return null;
|
|
6205
6403
|
return ledger.get(runId);
|
|
6206
6404
|
},
|
|
6405
|
+
observedRunWrites(runId) {
|
|
6406
|
+
// A run this process never finalized, or finalized long enough ago to
|
|
6407
|
+
// have been evicted, is unknown rather than silent, so both answer null.
|
|
6408
|
+
const attribution = runWriteAttributions.get(runId);
|
|
6409
|
+
return attribution?.complete === true ? [...attribution.recorded] : null;
|
|
6410
|
+
},
|
|
6411
|
+
observedRunWriteAttribution(runId) {
|
|
6412
|
+
const attribution = runWriteAttributions.get(runId);
|
|
6413
|
+
if (attribution === undefined) return null;
|
|
6414
|
+
return {
|
|
6415
|
+
recorded: [...attribution.recorded],
|
|
6416
|
+
complete: attribution.complete,
|
|
6417
|
+
downgrades: attribution.downgrades?.map((entry) => ({ ...entry })) ?? [],
|
|
6418
|
+
};
|
|
6419
|
+
},
|
|
6207
6420
|
assignments: {
|
|
6208
6421
|
get: (id) => assignments.get(id),
|
|
6209
6422
|
getStored: (id) => getStoredAssignment(id),
|