@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { endpointLabel } from "../providers/endpoint-capacity.js";
|
|
2
3
|
import type { ActionClass } from "../safety/action-classifier.js";
|
|
3
4
|
import type { ScopeSpec } from "../safety/scope.js";
|
|
4
5
|
import { createAdmissionQueue } from "./admission-queue.js";
|
|
@@ -7,6 +8,8 @@ import {
|
|
|
7
8
|
type CapacityLease,
|
|
8
9
|
type CapacityLimits,
|
|
9
10
|
capacityLeaseUsage,
|
|
11
|
+
describeEndpointCapacityHolders,
|
|
12
|
+
type EndpointCapacityHolders,
|
|
10
13
|
heartbeatCapacityLease,
|
|
11
14
|
releaseCapacityLease,
|
|
12
15
|
renameCapacityLeaseAssignment,
|
|
@@ -41,6 +44,7 @@ export interface CapacityAdmissionController {
|
|
|
41
44
|
admit(input: {
|
|
42
45
|
assignmentId: string;
|
|
43
46
|
nodeId: string;
|
|
47
|
+
endpointKey?: string;
|
|
44
48
|
deadlineAt: number;
|
|
45
49
|
priority?: number;
|
|
46
50
|
planId?: string;
|
|
@@ -105,13 +109,31 @@ const PUMP_MAX_MS = 500;
|
|
|
105
109
|
function describeAdmissionFailure(input: {
|
|
106
110
|
state: "canceled" | "timed_out";
|
|
107
111
|
nodeId: string;
|
|
112
|
+
endpointKey?: string;
|
|
108
113
|
waitedMs: number;
|
|
109
114
|
overdueAtQueueMs: number;
|
|
110
115
|
queueDepth: number;
|
|
111
116
|
limits: CapacityLimits;
|
|
112
|
-
usage: {
|
|
117
|
+
usage: {
|
|
118
|
+
global: number;
|
|
119
|
+
nodes: Readonly<Record<string, number>>;
|
|
120
|
+
endpoints: Readonly<Record<string, number>>;
|
|
121
|
+
endpointHolders?: Readonly<Record<string, EndpointCapacityHolders>>;
|
|
122
|
+
};
|
|
113
123
|
}): string {
|
|
114
124
|
if (input.state === "canceled") return "dispatch: admission canceled before a capacity slot opened";
|
|
125
|
+
if (input.endpointKey !== undefined) {
|
|
126
|
+
const endpointLimit = input.limits.endpoints[input.endpointKey];
|
|
127
|
+
const endpointActive = input.usage.endpoints[input.endpointKey] ?? 0;
|
|
128
|
+
if (endpointLimit !== undefined && endpointActive >= endpointLimit) {
|
|
129
|
+
const holders = input.usage.endpointHolders?.[input.endpointKey];
|
|
130
|
+
const occupancy =
|
|
131
|
+
holders === undefined
|
|
132
|
+
? "the supplied capacity snapshot does not attribute the occupied slots"
|
|
133
|
+
: describeEndpointCapacityHolders(holders);
|
|
134
|
+
return `dispatch: admission denied: endpoint '${endpointLabel(input.endpointKey)}' capacity reached (${endpointActive}/${endpointLimit} slots): ${occupancy}; collect in-flight runs or point workers at a second server`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
115
137
|
const nodeLimit = input.limits.nodes[input.nodeId];
|
|
116
138
|
const nodeActive = input.usage.nodes[input.nodeId] ?? 0;
|
|
117
139
|
const where =
|
|
@@ -142,7 +164,12 @@ function describeAdmissionFailure(input: {
|
|
|
142
164
|
export function createCapacityAdmissionController(options: {
|
|
143
165
|
limits: () => CapacityLimits;
|
|
144
166
|
/** Live lease counts for the timeout message; defaults to the durable state file. */
|
|
145
|
-
usage?: () => {
|
|
167
|
+
usage?: () => {
|
|
168
|
+
global: number;
|
|
169
|
+
nodes: Readonly<Record<string, number>>;
|
|
170
|
+
endpoints: Readonly<Record<string, number>>;
|
|
171
|
+
endpointHolders?: Readonly<Record<string, EndpointCapacityHolders>>;
|
|
172
|
+
};
|
|
146
173
|
now?: () => number;
|
|
147
174
|
maxQueueSize?: number;
|
|
148
175
|
queueCeilingMs?: number;
|
|
@@ -175,7 +202,14 @@ export function createCapacityAdmissionController(options: {
|
|
|
175
202
|
admittedLease = request.value();
|
|
176
203
|
return true;
|
|
177
204
|
} catch (error) {
|
|
178
|
-
//
|
|
205
|
+
// Endpoint saturation is a named refusal because waiting would keep a
|
|
206
|
+
// model request queued behind the same inference scheduler. Existing
|
|
207
|
+
// node and global admission retain their bounded wait behavior.
|
|
208
|
+
if (error instanceof Error && /endpoint '.*' capacity reached/u.test(error.message)) {
|
|
209
|
+
queue.fail(request.requestId, error);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
// Node and global capacity can wait. Anything else fails the request.
|
|
179
213
|
if (error instanceof Error && /capacity reached/.test(error.message)) return false;
|
|
180
214
|
queue.fail(request.requestId, error instanceof Error ? error : new Error(String(error)));
|
|
181
215
|
return false;
|
|
@@ -229,12 +263,14 @@ export function createCapacityAdmissionController(options: {
|
|
|
229
263
|
memberId: input.reservation.memberId,
|
|
230
264
|
assignmentId: input.assignmentId,
|
|
231
265
|
nodeId: input.nodeId,
|
|
266
|
+
...(input.endpointKey !== undefined ? { endpointKey: input.endpointKey } : {}),
|
|
232
267
|
limits: options.limits(),
|
|
233
268
|
nowMs: now(),
|
|
234
269
|
})
|
|
235
270
|
: acquireCapacityLease({
|
|
236
271
|
assignmentId: input.assignmentId,
|
|
237
272
|
nodeId: input.nodeId,
|
|
273
|
+
...(input.endpointKey !== undefined ? { endpointKey: input.endpointKey } : {}),
|
|
238
274
|
limits: options.limits(),
|
|
239
275
|
nowMs: now(),
|
|
240
276
|
}),
|
|
@@ -246,6 +282,7 @@ export function createCapacityAdmissionController(options: {
|
|
|
246
282
|
describeAdmissionFailure({
|
|
247
283
|
state: outcome.state,
|
|
248
284
|
nodeId: input.nodeId,
|
|
285
|
+
...(input.endpointKey !== undefined ? { endpointKey: input.endpointKey } : {}),
|
|
249
286
|
waitedMs: now() - queuedAt,
|
|
250
287
|
overdueAtQueueMs: queuedAt - input.deadlineAt,
|
|
251
288
|
queueDepth: queue.size(),
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Startup reconciliation for durable assignments. The in-memory retry queue and
|
|
3
|
-
* assignment registry do not survive a restart (Symphony §14.3), so
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* assignment registry do not survive a restart (Symphony §14.3), so a durable
|
|
4
|
+
* record whose process owner is gone is orphaned: no active attempt, no queued
|
|
5
|
+
* retry, and no attached terminal promise can ever settle it. Running records
|
|
6
|
+
* owned by a live sibling process are deliberately left alone.
|
|
6
7
|
*
|
|
7
8
|
* This pass resolves each orphan deterministically against durable ledger
|
|
8
9
|
* evidence so a later `wait`/`collect` can never observe a stuck `running`
|
|
@@ -11,7 +12,7 @@
|
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import type { AssignmentStatus } from "./assignment.js";
|
|
14
|
-
import type { DurableAssignmentRecord } from "./assignment-store.js";
|
|
15
|
+
import type { AssignmentVerdictOwner, DurableAssignmentRecord } from "./assignment-store.js";
|
|
15
16
|
|
|
16
17
|
/** Minimal terminal view of one attempt's durable ledger row. */
|
|
17
18
|
export interface ReconcileAttemptView {
|
|
@@ -25,13 +26,16 @@ export interface ReconcileAttemptView {
|
|
|
25
26
|
export interface AssignmentReconcileDeps {
|
|
26
27
|
/** Durable records currently marked `running`. */
|
|
27
28
|
listRunning: () => ReadonlyArray<DurableAssignmentRecord>;
|
|
29
|
+
/** Decide liveness from the record's durable pid and process birth token. */
|
|
30
|
+
ownerAlive: (record: DurableAssignmentRecord) => boolean;
|
|
28
31
|
/** Resolve one attempt's terminal view from the ledger, or null if pruned. */
|
|
29
32
|
lookupAttempt: (runId: string) => ReconcileAttemptView | null;
|
|
30
|
-
/** Persist the reconciled terminal status. */
|
|
33
|
+
/** Persist the reconciled terminal status, as the record's verdict owner when it has one. */
|
|
31
34
|
settle: (
|
|
32
35
|
assignmentId: string,
|
|
33
36
|
terminalRunId: string,
|
|
34
37
|
status: Exclude<AssignmentStatus, "running">,
|
|
38
|
+
owner?: AssignmentVerdictOwner,
|
|
35
39
|
) => Promise<unknown>;
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -44,6 +48,10 @@ export interface AssignmentReconcileSummary {
|
|
|
44
48
|
|
|
45
49
|
/**
|
|
46
50
|
* Deterministic rule, in priority order:
|
|
51
|
+
* -1. a record owned by a live process is not an orphan and remains running;
|
|
52
|
+
* 0. a record whose verdict belongs to its opener is failed (abandoned): the
|
|
53
|
+
* opener died before reaching a verdict, and a green attempt under it is
|
|
54
|
+
* one step of a run, never the run's answer;
|
|
47
55
|
* 1. a recovered succeeded terminal attempt wins (`succeeded`);
|
|
48
56
|
* 2. otherwise the last terminal attempt marks the assignment `failed`;
|
|
49
57
|
* 3. otherwise no attempt is recoverable and the assignment is failed
|
|
@@ -53,6 +61,15 @@ export async function reconcileOrphanAssignments(deps: AssignmentReconcileDeps):
|
|
|
53
61
|
const summary: AssignmentReconcileSummary = { recovered: 0, abandoned: 0 };
|
|
54
62
|
for (const record of deps.listRunning()) {
|
|
55
63
|
if (record.status !== "running") continue;
|
|
64
|
+
if (deps.ownerAlive(record)) continue;
|
|
65
|
+
if (record.verdictOwner !== undefined) {
|
|
66
|
+
// Attempts under a claimed row are fleet steps, not the fleet verdict.
|
|
67
|
+
// Filing a green step as the terminal id of an abandoned fleet would
|
|
68
|
+
// contradict that step's immutable ledger result.
|
|
69
|
+
await deps.settle(record.assignmentId, record.assignmentId, "failed", record.verdictOwner);
|
|
70
|
+
summary.abandoned += 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
56
73
|
const views = record.attempts
|
|
57
74
|
.map((runId) => deps.lookupAttempt(runId))
|
|
58
75
|
.filter((view): view is ReconcileAttemptView => view !== null);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
+
import { BIRTH_TOKEN_SOURCE_AVAILABLE, processAlive, processBirthToken } from "../../core/process-identity.js";
|
|
8
9
|
import { withStateFileLock } from "../../core/state-file-lock.js";
|
|
9
10
|
import { clioStateDir } from "../../core/xdg.js";
|
|
10
11
|
import { atomicWrite } from "../../engine/session.js";
|
|
@@ -12,11 +13,54 @@ import type { AssignmentStatus } from "./assignment.js";
|
|
|
12
13
|
|
|
13
14
|
const MAX_ASSIGNMENT_RECORDS = 1_000;
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Who decides a record's status.
|
|
18
|
+
*
|
|
19
|
+
* Absent is the ordinary case: the record is one logical dispatch, so the
|
|
20
|
+
* attempts filed under it are the only thing that can settle it. A fleet claims
|
|
21
|
+
* the verdict instead, because a fleet root gathers every step of a run under
|
|
22
|
+
* one id and no single step is the run's answer. Without the claim the last
|
|
23
|
+
* step to settle writes the row, which reports a green step of an aborted run
|
|
24
|
+
* as the run's outcome.
|
|
25
|
+
*/
|
|
26
|
+
export type AssignmentVerdictOwner = "fleet";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Durable identity of the process executing a running assignment. A pid alone
|
|
30
|
+
* is not enough: the OS may recycle it after a crash, so the process birth
|
|
31
|
+
* token must still match before another extension treats the owner as live.
|
|
32
|
+
*/
|
|
33
|
+
export interface AssignmentProcessOwner {
|
|
34
|
+
pid: number;
|
|
35
|
+
processBirthToken: string;
|
|
36
|
+
acquiredAt: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AssignmentProcessOwnerProbe {
|
|
40
|
+
birthToken(pid: number): string | null;
|
|
41
|
+
/** True when a missing birth token proves that the process is gone. */
|
|
42
|
+
tokenProvesDeath?: boolean;
|
|
43
|
+
/** Liveness fallback for platforms whose birth token is synthetic. */
|
|
44
|
+
alive?(pid: number): boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One durable logical-dispatch row.
|
|
49
|
+
*
|
|
50
|
+
* `attempts` holds terminal run ids: an agent attempt's receipt id, and for a
|
|
51
|
+
* fleet also each deterministic step's `code-*` run id, whose evidence lives
|
|
52
|
+
* under `code-steps/<assignmentId>/`. `status` answers for the whole record,
|
|
53
|
+
* so for a fleet it answers for the whole run rather than for its last step.
|
|
54
|
+
*/
|
|
15
55
|
export interface DurableAssignmentRecord {
|
|
16
56
|
assignmentId: string;
|
|
17
57
|
attempts: string[];
|
|
18
58
|
terminalRunId: string | null;
|
|
19
59
|
status: AssignmentStatus;
|
|
60
|
+
/** Set when something other than the attempts decides `status`. */
|
|
61
|
+
verdictOwner?: AssignmentVerdictOwner;
|
|
62
|
+
/** Process lease held only while this record is running. */
|
|
63
|
+
processOwner?: AssignmentProcessOwner;
|
|
20
64
|
}
|
|
21
65
|
|
|
22
66
|
interface AssignmentStoreFile {
|
|
@@ -28,6 +72,18 @@ function storePath(): string {
|
|
|
28
72
|
return join(clioStateDir(), "assignments.json");
|
|
29
73
|
}
|
|
30
74
|
|
|
75
|
+
function validProcessOwner(value: unknown): value is AssignmentProcessOwner {
|
|
76
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
77
|
+
const owner = value as Partial<AssignmentProcessOwner>;
|
|
78
|
+
return (
|
|
79
|
+
Number.isSafeInteger(owner.pid) &&
|
|
80
|
+
(owner.pid ?? 0) > 0 &&
|
|
81
|
+
typeof owner.processBirthToken === "string" &&
|
|
82
|
+
typeof owner.acquiredAt === "string" &&
|
|
83
|
+
Number.isFinite(Date.parse(owner.acquiredAt))
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
31
87
|
function validRecord(value: unknown): value is DurableAssignmentRecord {
|
|
32
88
|
if (typeof value !== "object" || value === null) return false;
|
|
33
89
|
const record = value as Partial<DurableAssignmentRecord>;
|
|
@@ -36,6 +92,8 @@ function validRecord(value: unknown): value is DurableAssignmentRecord {
|
|
|
36
92
|
Array.isArray(record.attempts) &&
|
|
37
93
|
record.attempts.every((runId) => typeof runId === "string") &&
|
|
38
94
|
(record.terminalRunId === null || typeof record.terminalRunId === "string") &&
|
|
95
|
+
(record.verdictOwner === undefined || record.verdictOwner === "fleet") &&
|
|
96
|
+
(record.processOwner === undefined || validProcessOwner(record.processOwner)) &&
|
|
39
97
|
(record.status === "running" ||
|
|
40
98
|
record.status === "succeeded" ||
|
|
41
99
|
record.status === "failed" ||
|
|
@@ -57,7 +115,32 @@ function readStore(): DurableAssignmentRecord[] {
|
|
|
57
115
|
}
|
|
58
116
|
|
|
59
117
|
function copyRecord(record: DurableAssignmentRecord): DurableAssignmentRecord {
|
|
60
|
-
return {
|
|
118
|
+
return {
|
|
119
|
+
...record,
|
|
120
|
+
attempts: [...record.attempts],
|
|
121
|
+
...(record.processOwner === undefined ? {} : { processOwner: { ...record.processOwner } }),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const defaultProcessOwnerProbe: AssignmentProcessOwnerProbe = {
|
|
126
|
+
birthToken: processBirthToken,
|
|
127
|
+
tokenProvesDeath: BIRTH_TOKEN_SOURCE_AVAILABLE,
|
|
128
|
+
alive: processAlive,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** True only when the durable identity still names the same live process. */
|
|
132
|
+
export function assignmentProcessOwnerAlive(
|
|
133
|
+
record: Pick<DurableAssignmentRecord, "processOwner">,
|
|
134
|
+
probe: AssignmentProcessOwnerProbe = defaultProcessOwnerProbe,
|
|
135
|
+
): boolean {
|
|
136
|
+
const owner = record.processOwner;
|
|
137
|
+
if (owner === undefined) return false;
|
|
138
|
+
const current = probe.birthToken(owner.pid);
|
|
139
|
+
if (current === null || current !== owner.processBirthToken) return false;
|
|
140
|
+
// A synthetic `pid-N` token cannot distinguish pid reuse, so require an
|
|
141
|
+
// independent liveness signal on platforms without a real birth-token source.
|
|
142
|
+
if (probe.tokenProvesDeath === false) return probe.alive?.(owner.pid) ?? false;
|
|
143
|
+
return true;
|
|
61
144
|
}
|
|
62
145
|
|
|
63
146
|
function writeStore(assignments: ReadonlyArray<DurableAssignmentRecord>): void {
|
|
@@ -114,49 +197,103 @@ export async function renameStoredAssignment(
|
|
|
114
197
|
return copyRecord(result);
|
|
115
198
|
}
|
|
116
199
|
|
|
117
|
-
|
|
118
|
-
|
|
200
|
+
function currentProcessOwner(): AssignmentProcessOwner {
|
|
201
|
+
const token = processBirthToken();
|
|
202
|
+
if (token === null) throw new Error("dispatch: cannot establish assignment owner process birth token");
|
|
203
|
+
return {
|
|
204
|
+
pid: process.pid,
|
|
205
|
+
processBirthToken: token,
|
|
206
|
+
acquiredAt: new Date().toISOString(),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function openRecord(assignmentId: string): DurableAssignmentRecord {
|
|
211
|
+
return {
|
|
119
212
|
assignmentId,
|
|
120
|
-
|
|
121
|
-
|
|
213
|
+
attempts: [],
|
|
214
|
+
terminalRunId: null,
|
|
215
|
+
status: "running",
|
|
216
|
+
processOwner: currentProcessOwner(),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function ensureRunningProcessOwner(record: DurableAssignmentRecord): DurableAssignmentRecord {
|
|
221
|
+
if (record.status !== "running" || record.processOwner !== undefined) return record;
|
|
222
|
+
return { ...record, processOwner: currentProcessOwner() };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function clearProcessOwner(record: DurableAssignmentRecord): DurableAssignmentRecord {
|
|
226
|
+
const result = { ...record };
|
|
227
|
+
delete result.processOwner;
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function registerAssignment(assignmentId: string): Promise<DurableAssignmentRecord> {
|
|
232
|
+
return updateRecord(assignmentId, (current) => ensureRunningProcessOwner(current ?? openRecord(assignmentId)));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Reserve this record's status for `owner`, opening the record if it is new.
|
|
237
|
+
* Attempts filed by anyone are still recorded; only the verdict is reserved,
|
|
238
|
+
* and until the owner settles it the record stays `running`.
|
|
239
|
+
*/
|
|
240
|
+
export function claimAssignmentVerdict(
|
|
241
|
+
assignmentId: string,
|
|
242
|
+
owner: AssignmentVerdictOwner,
|
|
243
|
+
): Promise<DurableAssignmentRecord> {
|
|
244
|
+
return updateRecord(assignmentId, (current) => ({
|
|
245
|
+
...ensureRunningProcessOwner(current ?? openRecord(assignmentId)),
|
|
246
|
+
verdictOwner: owner,
|
|
247
|
+
}));
|
|
122
248
|
}
|
|
123
249
|
|
|
124
250
|
export function recordAssignmentAttempt(assignmentId: string, runId: string): Promise<DurableAssignmentRecord> {
|
|
125
251
|
return updateRecord(assignmentId, (current) => {
|
|
126
|
-
const base = current ??
|
|
252
|
+
const base = ensureRunningProcessOwner(current ?? openRecord(assignmentId));
|
|
127
253
|
return base.attempts.includes(runId) ? base : { ...base, attempts: [...base.attempts, runId] };
|
|
128
254
|
});
|
|
129
255
|
}
|
|
130
256
|
|
|
131
257
|
export function failQueuedAssignment(assignmentId: string): Promise<DurableAssignmentRecord> {
|
|
132
258
|
return updateRecord(assignmentId, (current) => {
|
|
133
|
-
const base = current ??
|
|
134
|
-
|
|
259
|
+
const base = current ?? openRecord(assignmentId);
|
|
260
|
+
if (base.verdictOwner !== undefined) return base;
|
|
261
|
+
return base.status === "running" ? clearProcessOwner({ ...base, status: "failed" }) : base;
|
|
135
262
|
});
|
|
136
263
|
}
|
|
137
264
|
|
|
138
265
|
export function timeoutStoredAssignment(assignmentId: string): Promise<DurableAssignmentRecord> {
|
|
139
266
|
return updateRecord(assignmentId, (current) => {
|
|
140
|
-
const base = current ??
|
|
141
|
-
|
|
267
|
+
const base = current ?? openRecord(assignmentId);
|
|
268
|
+
if (base.verdictOwner !== undefined) return base;
|
|
269
|
+
return base.status === "running" ? clearProcessOwner({ ...base, status: "timed_out" }) : base;
|
|
142
270
|
});
|
|
143
271
|
}
|
|
144
272
|
|
|
145
273
|
export function cancelStoredAssignment(assignmentId: string): Promise<DurableAssignmentRecord> {
|
|
146
274
|
return updateRecord(assignmentId, (current) => {
|
|
147
|
-
const base = current ??
|
|
148
|
-
|
|
275
|
+
const base = current ?? openRecord(assignmentId);
|
|
276
|
+
if (base.verdictOwner !== undefined) return base;
|
|
277
|
+
return base.status === "running" ? clearProcessOwner({ ...base, status: "canceled" }) : base;
|
|
149
278
|
});
|
|
150
279
|
}
|
|
151
280
|
|
|
281
|
+
/**
|
|
282
|
+
* File `terminalRunId` as an attempt and, when the caller owns the verdict, set
|
|
283
|
+
* the record's status. An attempt settling under a claimed record settles
|
|
284
|
+
* itself and not the record: one fleet step reaching a green receipt says
|
|
285
|
+
* nothing about the six steps behind it that never ran.
|
|
286
|
+
*/
|
|
152
287
|
export function settleStoredAssignment(
|
|
153
288
|
assignmentId: string,
|
|
154
289
|
terminalRunId: string,
|
|
155
290
|
status: Exclude<AssignmentStatus, "running">,
|
|
291
|
+
owner?: AssignmentVerdictOwner,
|
|
156
292
|
): Promise<DurableAssignmentRecord> {
|
|
157
293
|
return updateRecord(assignmentId, (current) => {
|
|
158
|
-
const base = current ??
|
|
294
|
+
const base = current ?? openRecord(assignmentId);
|
|
159
295
|
const attempts = base.attempts.includes(terminalRunId) ? base.attempts : [...base.attempts, terminalRunId];
|
|
160
|
-
return { ...base, attempts
|
|
296
|
+
if (base.verdictOwner !== owner) return { ...base, attempts };
|
|
297
|
+
return clearProcessOwner({ ...base, attempts, terminalRunId, status });
|
|
161
298
|
});
|
|
162
299
|
}
|
|
@@ -6,6 +6,7 @@ import { BIRTH_TOKEN_SOURCE_AVAILABLE, processAlive, processBirthToken } from ".
|
|
|
6
6
|
import { FILE_LOCK_ACQUIRE_TIMEOUT_MS, withStateFileLockSync } from "../../core/state-file-lock.js";
|
|
7
7
|
import { clioStateDir } from "../../core/xdg.js";
|
|
8
8
|
import { atomicWrite } from "../../engine/session.js";
|
|
9
|
+
import { endpointLabel, foregroundStreamUsage } from "../providers/endpoint-capacity.js";
|
|
9
10
|
|
|
10
11
|
export { processBirthToken } from "../../core/process-identity.js";
|
|
11
12
|
|
|
@@ -30,6 +31,8 @@ export interface CapacityLease {
|
|
|
30
31
|
leaseId: string;
|
|
31
32
|
assignmentId: string;
|
|
32
33
|
nodeId: string;
|
|
34
|
+
/** Canonical inference endpoint. Absent only on leases written before endpoint capacity existed. */
|
|
35
|
+
endpointKey?: string;
|
|
33
36
|
/** Owner host. Absent only on records written before the field existed; those are read as this host. */
|
|
34
37
|
host?: string;
|
|
35
38
|
ownerPid: number;
|
|
@@ -43,6 +46,7 @@ export interface CapacityLease {
|
|
|
43
46
|
export interface CapacityLimits {
|
|
44
47
|
global: number;
|
|
45
48
|
nodes: Readonly<Record<string, number>>;
|
|
49
|
+
endpoints: Readonly<Record<string, number>>;
|
|
46
50
|
}
|
|
47
51
|
export interface CapacityDrain {
|
|
48
52
|
requestedByPid: number;
|
|
@@ -81,6 +85,7 @@ function valid(value: unknown): value is CapacityLease {
|
|
|
81
85
|
typeof lease.leaseId === "string" &&
|
|
82
86
|
typeof lease.assignmentId === "string" &&
|
|
83
87
|
typeof lease.nodeId === "string" &&
|
|
88
|
+
(lease.endpointKey === undefined || typeof lease.endpointKey === "string") &&
|
|
84
89
|
(lease.host === undefined || typeof lease.host === "string") &&
|
|
85
90
|
Number.isInteger(lease.ownerPid) &&
|
|
86
91
|
typeof lease.processBirthToken === "string" &&
|
|
@@ -182,9 +187,14 @@ const MEMBER_STATUSES = new Set(["held", "consumed", "released"]);
|
|
|
182
187
|
* rejected here as well: silently skipping an unreadable record would under-count
|
|
183
188
|
* capacity and admit past the cap.
|
|
184
189
|
*/
|
|
185
|
-
function heldReservationUsage(values: ReadonlyArray<unknown>): {
|
|
190
|
+
function heldReservationUsage(values: ReadonlyArray<unknown>): {
|
|
191
|
+
global: number;
|
|
192
|
+
nodes: Record<string, number>;
|
|
193
|
+
endpoints: Record<string, number>;
|
|
194
|
+
} {
|
|
186
195
|
let global = 0;
|
|
187
196
|
const nodes: Record<string, number> = {};
|
|
197
|
+
const endpoints: Record<string, number> = {};
|
|
188
198
|
for (const value of values) {
|
|
189
199
|
if (typeof value !== "object" || value === null)
|
|
190
200
|
throw new Error("dispatch capacity store has invalid reservation records");
|
|
@@ -194,10 +204,11 @@ function heldReservationUsage(values: ReadonlyArray<unknown>): { global: number;
|
|
|
194
204
|
if (record.status !== "active") continue;
|
|
195
205
|
const byWave = new Map<number, number>();
|
|
196
206
|
const byNodeWave = new Map<string, Map<number, number>>();
|
|
207
|
+
const byEndpointWave = new Map<string, Map<number, number>>();
|
|
197
208
|
for (const raw of record.members) {
|
|
198
209
|
if (typeof raw !== "object" || raw === null)
|
|
199
210
|
throw new Error("dispatch capacity store has invalid reservation records");
|
|
200
|
-
const member = raw as { status?: unknown; wave?: unknown; nodeId?: unknown };
|
|
211
|
+
const member = raw as { status?: unknown; wave?: unknown; nodeId?: unknown; endpointKey?: unknown };
|
|
201
212
|
if (
|
|
202
213
|
typeof member.status !== "string" ||
|
|
203
214
|
!MEMBER_STATUSES.has(member.status) ||
|
|
@@ -211,13 +222,53 @@ function heldReservationUsage(values: ReadonlyArray<unknown>): { global: number;
|
|
|
211
222
|
const nodeWaves = byNodeWave.get(member.nodeId) ?? new Map<number, number>();
|
|
212
223
|
nodeWaves.set(wave, (nodeWaves.get(wave) ?? 0) + 1);
|
|
213
224
|
byNodeWave.set(member.nodeId, nodeWaves);
|
|
225
|
+
if (member.endpointKey !== undefined && typeof member.endpointKey !== "string")
|
|
226
|
+
throw new Error("dispatch capacity store has invalid reservation records");
|
|
227
|
+
if (typeof member.endpointKey === "string") {
|
|
228
|
+
const endpointWaves = byEndpointWave.get(member.endpointKey) ?? new Map<number, number>();
|
|
229
|
+
endpointWaves.set(wave, (endpointWaves.get(wave) ?? 0) + 1);
|
|
230
|
+
byEndpointWave.set(member.endpointKey, endpointWaves);
|
|
231
|
+
}
|
|
214
232
|
}
|
|
215
233
|
global += Math.max(0, ...byWave.values());
|
|
216
234
|
for (const [id, waves] of byNodeWave) nodes[id] = (nodes[id] ?? 0) + Math.max(0, ...waves.values());
|
|
235
|
+
for (const [key, waves] of byEndpointWave) endpoints[key] = (endpoints[key] ?? 0) + Math.max(0, ...waves.values());
|
|
217
236
|
}
|
|
218
|
-
return { global, nodes };
|
|
237
|
+
return { global, nodes, endpoints };
|
|
219
238
|
}
|
|
220
|
-
|
|
239
|
+
|
|
240
|
+
export interface EndpointCapacityHolders {
|
|
241
|
+
leases: number;
|
|
242
|
+
reservations: number;
|
|
243
|
+
foregroundStreams: number;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Name the endpoint-capacity addends that are actually nonzero. */
|
|
247
|
+
export function describeEndpointCapacityHolders(holders: EndpointCapacityHolders): string {
|
|
248
|
+
const parts: string[] = [];
|
|
249
|
+
const add = (count: number, singular: string, plural: string): void => {
|
|
250
|
+
if (count > 0) parts.push(`${count} ${count === 1 ? singular : plural}`);
|
|
251
|
+
};
|
|
252
|
+
add(holders.leases, "active lease", "active leases");
|
|
253
|
+
add(holders.reservations, "held reservation", "held reservations");
|
|
254
|
+
add(holders.foregroundStreams, "foreground stream", "foreground streams");
|
|
255
|
+
if (parts.length === 0) return "no active lease, held reservation, or foreground stream currently holds a slot";
|
|
256
|
+
const named =
|
|
257
|
+
parts.length === 1
|
|
258
|
+
? parts[0]
|
|
259
|
+
: parts.length === 2
|
|
260
|
+
? `${parts[0]} and ${parts[1]}`
|
|
261
|
+
: `${parts.slice(0, -1).join(", ")}, and ${parts.at(-1)}`;
|
|
262
|
+
const total = holders.leases + holders.reservations + holders.foregroundStreams;
|
|
263
|
+
return `${named} ${total === 1 ? "holds" : "hold"} ${total === 1 ? "the slot" : "the slots"}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function assertCapacity(
|
|
267
|
+
file: CapacityStateFile,
|
|
268
|
+
nodeId: string,
|
|
269
|
+
endpointKey: string | undefined,
|
|
270
|
+
limits: CapacityLimits,
|
|
271
|
+
): void {
|
|
221
272
|
const held = heldReservationUsage(file.reservations);
|
|
222
273
|
const globalUsed = file.leases.length + held.global;
|
|
223
274
|
// The denial names the compliant next move, not just the gate. A model told
|
|
@@ -235,10 +286,28 @@ function assertCapacity(file: CapacityStateFile, nodeId: string, limits: Capacit
|
|
|
235
286
|
if (used >= nodeLimit)
|
|
236
287
|
throw new Error(`dispatch: admission denied: node '${nodeId}' capacity reached (${used}/${nodeLimit})`);
|
|
237
288
|
}
|
|
289
|
+
if (endpointKey !== undefined) {
|
|
290
|
+
const endpointLimit = limits.endpoints[endpointKey];
|
|
291
|
+
if (endpointLimit !== undefined) {
|
|
292
|
+
if (endpointLimit < 1)
|
|
293
|
+
throw new Error(`dispatch: admission denied: endpoint '${endpointLabel(endpointKey)}' capacity unavailable`);
|
|
294
|
+
const holders: EndpointCapacityHolders = {
|
|
295
|
+
leases: file.leases.filter((lease) => lease.endpointKey === endpointKey).length,
|
|
296
|
+
reservations: held.endpoints[endpointKey] ?? 0,
|
|
297
|
+
foregroundStreams: foregroundStreamUsage()[endpointKey] ?? 0,
|
|
298
|
+
};
|
|
299
|
+
const used = holders.leases + holders.reservations + holders.foregroundStreams;
|
|
300
|
+
if (used >= endpointLimit)
|
|
301
|
+
throw new Error(
|
|
302
|
+
`dispatch: admission denied: endpoint '${endpointLabel(endpointKey)}' capacity reached (${used}/${endpointLimit} slots): ${describeEndpointCapacityHolders(holders)}; collect in-flight runs or point workers at a second server`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
238
306
|
}
|
|
239
307
|
export function acquireCapacityLease(input: {
|
|
240
308
|
assignmentId: string;
|
|
241
309
|
nodeId: string;
|
|
310
|
+
endpointKey?: string;
|
|
242
311
|
limits: CapacityLimits;
|
|
243
312
|
nowMs?: number;
|
|
244
313
|
ttlMs?: number;
|
|
@@ -259,10 +328,12 @@ export function acquireCapacityLease(input: {
|
|
|
259
328
|
);
|
|
260
329
|
const existing = file.leases.find((lease) => lease.assignmentId === input.assignmentId);
|
|
261
330
|
if (existing) {
|
|
262
|
-
if (existing.nodeId !== input.nodeId) {
|
|
331
|
+
if (existing.nodeId !== input.nodeId || existing.endpointKey !== input.endpointKey) {
|
|
263
332
|
const without = { ...file, leases: file.leases.filter((lease) => lease !== existing) };
|
|
264
|
-
assertCapacity(without, input.nodeId, input.limits);
|
|
333
|
+
assertCapacity(without, input.nodeId, input.endpointKey, input.limits);
|
|
265
334
|
existing.nodeId = input.nodeId;
|
|
335
|
+
if (input.endpointKey === undefined) delete existing.endpointKey;
|
|
336
|
+
else existing.endpointKey = input.endpointKey;
|
|
266
337
|
}
|
|
267
338
|
existing.heartbeatAt = new Date(nowMs).toISOString();
|
|
268
339
|
existing.expiresAt = new Date(nowMs + (input.ttlMs ?? DEFAULT_CAPACITY_LEASE_TTL_MS)).toISOString();
|
|
@@ -270,7 +341,7 @@ export function acquireCapacityLease(input: {
|
|
|
270
341
|
return clone(existing);
|
|
271
342
|
}
|
|
272
343
|
input.onAcquiredUnderLock?.(file);
|
|
273
|
-
assertCapacity(file, input.nodeId, input.limits);
|
|
344
|
+
assertCapacity(file, input.nodeId, input.endpointKey, input.limits);
|
|
274
345
|
const token = input.processBirthToken ?? processBirthToken(input.ownerPid ?? process.pid);
|
|
275
346
|
if (!token) throw new Error("dispatch: cannot establish process birth token");
|
|
276
347
|
const at = new Date(nowMs).toISOString();
|
|
@@ -278,6 +349,7 @@ export function acquireCapacityLease(input: {
|
|
|
278
349
|
leaseId: `lease-${nowMs.toString(36)}-${randomBytes(6).toString("hex")}`,
|
|
279
350
|
assignmentId: input.assignmentId,
|
|
280
351
|
nodeId: input.nodeId,
|
|
352
|
+
...(input.endpointKey !== undefined ? { endpointKey: input.endpointKey } : {}),
|
|
281
353
|
host: hostname(),
|
|
282
354
|
ownerPid: input.ownerPid ?? process.pid,
|
|
283
355
|
processBirthToken: token,
|
|
@@ -365,11 +437,28 @@ export function capacityDrain(nowMs = Date.now()): CapacityDrain | null {
|
|
|
365
437
|
export function capacityLeaseUsage(options?: { nowMs?: number; probe?: LeaseOwnerProbe }): {
|
|
366
438
|
global: number;
|
|
367
439
|
nodes: Readonly<Record<string, number>>;
|
|
440
|
+
endpoints: Readonly<Record<string, number>>;
|
|
441
|
+
endpointHolders: Readonly<Record<string, EndpointCapacityHolders>>;
|
|
368
442
|
} {
|
|
369
443
|
const leases = listCapacityLeases(options);
|
|
370
444
|
const nodes: Record<string, number> = {};
|
|
371
|
-
|
|
372
|
-
|
|
445
|
+
const foreground = foregroundStreamUsage();
|
|
446
|
+
const endpoints: Record<string, number> = { ...foreground };
|
|
447
|
+
const endpointHolders: Record<string, EndpointCapacityHolders> = Object.fromEntries(
|
|
448
|
+
Object.entries(foreground).map(([endpointKey, foregroundStreams]) => [
|
|
449
|
+
endpointKey,
|
|
450
|
+
{ leases: 0, reservations: 0, foregroundStreams },
|
|
451
|
+
]),
|
|
452
|
+
);
|
|
453
|
+
for (const lease of leases) {
|
|
454
|
+
nodes[lease.nodeId] = (nodes[lease.nodeId] ?? 0) + 1;
|
|
455
|
+
if (lease.endpointKey !== undefined) {
|
|
456
|
+
endpoints[lease.endpointKey] = (endpoints[lease.endpointKey] ?? 0) + 1;
|
|
457
|
+
const holders = endpointHolders[lease.endpointKey] ?? { leases: 0, reservations: 0, foregroundStreams: 0 };
|
|
458
|
+
endpointHolders[lease.endpointKey] = { ...holders, leases: holders.leases + 1 };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return { global: leases.length, nodes, endpoints, endpointHolders };
|
|
373
462
|
}
|
|
374
463
|
|
|
375
464
|
/**
|