@iowarp/clio-coder 0.3.4 → 0.3.7
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 +65 -2
- package/CONTRIBUTING.md +6 -6
- package/README.md +16 -5
- package/dist/{acp-S5R4RR5B.js → acp-SK4MD6MM.js} +11 -11
- package/dist/{agents-P6DMMVZY.js → agents-2FN2K6ME.js} +33 -26
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-2XCZLPKS.js → auth-QIYZWM5I.js} +15 -15
- package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
- package/dist/{chunk-VAWWTKDP.js → chunk-3HAPLH5M.js} +11 -11
- package/dist/{chunk-YCWGATWI.js → chunk-465YSENW.js} +3 -3
- package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
- package/dist/{chunk-UZHIZC5S.js → chunk-4DWFMQDR.js} +61 -76
- package/dist/{chunk-ZWMF7253.js → chunk-5C3AQNDW.js} +328 -9
- package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
- package/dist/{chunk-WPQLXFOZ.js → chunk-5FR74PWO.js} +3 -2
- package/dist/{chunk-35MKKU5R.js → chunk-5UJ6ECTS.js} +18 -10
- package/dist/{chunk-BRXQQJFP.js → chunk-6M7VS3J3.js} +571 -50
- package/dist/{chunk-QQK64KLB.js → chunk-6TUKSZVF.js} +141 -23
- package/dist/{chunk-N4CZJQRK.js → chunk-AB4XIIVB.js} +8 -6
- package/dist/{chunk-KRPY7NTG.js → chunk-BMWK7ZIZ.js} +14 -20
- package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
- package/dist/{chunk-ZYKPLLNQ.js → chunk-CEYBNUGC.js} +821 -83
- package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
- package/dist/chunk-DJNLUABN.js +843 -0
- package/dist/{chunk-BP4OYD6A.js → chunk-DMD2AGVS.js} +21 -2
- package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
- package/dist/chunk-DQA7QLMD.js +123 -0
- package/dist/chunk-DR52UMZW.js +21 -0
- package/dist/{chunk-3HZ5RWN2.js → chunk-EBEFWSGL.js} +9 -7
- package/dist/{chunk-EDRHSCIE.js → chunk-EELBMBT6.js} +128 -13
- package/dist/{chunk-HV5X7OR2.js → chunk-EOOQZZDE.js} +16 -14
- package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
- package/dist/{chunk-BPGS2WCQ.js → chunk-GEYXPTRF.js} +2 -1
- package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
- package/dist/{chunk-BEY543CS.js → chunk-GOXNB3AO.js} +5 -2
- package/dist/chunk-GWS3VEIW.js +195 -0
- package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
- package/dist/chunk-HWUFFB6L.js +83 -0
- package/dist/{chunk-X6COSD2O.js → chunk-J7PIKKWC.js} +8 -436
- package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
- package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
- package/dist/{chunk-K6WL7QZT.js → chunk-JTSEDYVQ.js} +7 -7
- package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
- package/dist/chunk-KZ2H5X4G.js +1026 -0
- package/dist/{chunk-A2GZF7DC.js → chunk-LADCF22A.js} +13 -13
- package/dist/chunk-LCGCVYZ4.js +57 -0
- package/dist/chunk-M4AKACEO.js +382 -0
- package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
- package/dist/{chunk-4JUF2NNX.js → chunk-MXI6J5JF.js} +7 -7
- package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
- package/dist/{chunk-TTNYS3EA.js → chunk-OB5HIGJY.js} +1 -1
- package/dist/{chunk-7RXG6QRZ.js → chunk-OBMAI2DP.js} +61 -840
- package/dist/{chunk-5M54SPOL.js → chunk-ODFEOB4F.js} +161 -5
- package/dist/chunk-PD3MESLB.js +242 -0
- package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
- package/dist/{chunk-VMNQ6OZA.js → chunk-QCTRSGHQ.js} +963 -786
- package/dist/chunk-RVG5JXAL.js +41 -0
- package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
- package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
- package/dist/{chunk-34475P3I.js → chunk-TSHXZTOQ.js} +5 -4
- package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
- package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
- package/dist/chunk-UND3GU2L.js +103 -0
- package/dist/{chunk-QQL5RT5M.js → chunk-UUANF5CR.js} +2323 -2114
- package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
- package/dist/chunk-UVDSQ6LW.js +472 -0
- package/dist/{chunk-QWU7ZBO7.js → chunk-VQNODYQ4.js} +215 -56
- package/dist/chunk-VREKEFLL.js +37 -0
- package/dist/{chunk-2TZWSW76.js → chunk-WHGPSPT5.js} +2 -2
- package/dist/{chunk-TW3WDMVS.js → chunk-WHJYKASB.js} +2 -2
- package/dist/{chunk-MEQ45TQ4.js → chunk-WJHBC77E.js} +21 -7
- package/dist/{chunk-HXG4IURW.js → chunk-X2KV5FXT.js} +2 -2
- package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
- package/dist/{chunk-2LZI5CAG.js → chunk-XEGB6BCN.js} +228 -36
- package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
- package/dist/{verifiers-4UUM6TEE.js → chunk-YTYFXUI3.js} +121 -372
- package/dist/{chunk-3JLKSKD7.js → chunk-ZGH7FGS5.js} +17 -7
- package/dist/{chunk-VSNATDE6.js → chunk-ZZMN5OM4.js} +2 -2
- package/dist/cli/index.js +34 -32
- package/dist/{clio-J5JIOIDS.js → clio-WBVQEBKO.js} +7 -7
- package/dist/{code-nav-AXCXSBHX.js → code-nav-FGGFIE7L.js} +7 -7
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-KELWS457.js → components-F7OEATSO.js} +5 -5
- package/dist/{config-OEBMIN2U.js → config-TRBL3RCF.js} +48 -41
- package/dist/{configure-PUQOSIXQ.js → configure-OLCVPHNM.js} +17 -17
- package/dist/{context-URSXPBCK.js → context-MJIJ6GOX.js} +12 -12
- package/dist/{context-EKDCKUUZ.js → context-WFPKQSM6.js} +26 -9
- package/dist/{context-MGSE4Z2T.js → context-XEWE3MOJ.js} +44 -37
- package/dist/{context-clear-KDAJRNUK.js → context-clear-KNOS2JPB.js} +44 -37
- package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
- package/dist/{context-working-set-SBKMPPI2.js → context-working-set-EUXAZI6N.js} +14 -13
- package/dist/{dispatch-runner-MSWN72NK.js → dispatch-runner-B7MTOVKL.js} +321 -60
- package/dist/{docs-2C2LTVT2.js → docs-FLJTIDSE.js} +5 -5
- package/dist/{doctor-7BSE27PJ.js → doctor-RN4YKO2X.js} +15 -15
- package/dist/{eval-IZGDOO4H.js → eval-RUBJVSNQ.js} +52 -236
- package/dist/{evidence-SR7WXB5B.js → evidence-JZNBUOQZ.js} +39 -33
- package/dist/{evolve-K7VE2CBX.js → evolve-FJVC4KKI.js} +39 -33
- package/dist/{extensions-QVDOHDGJ.js → extensions-IQL36S7K.js} +5 -5
- package/dist/{fleet-7XMJNQNF.js → fleet-BDKYJFCP.js} +243 -370
- package/dist/fleet-commands-ZFIWZSB3.js +70 -0
- package/dist/fleet-graph-Y6HPXIVF.js +125 -0
- package/dist/fleet-new-RDVJLHHH.js +48 -0
- package/dist/{fleet-preflight-AQNAH644.js → fleet-preflight-BHSNPBMH.js} +2 -2
- package/dist/fleet-validate-BIYREGIK.js +79 -0
- package/dist/{init-JGNPAYXT.js → init-LQUB5COQ.js} +57 -48
- package/dist/library-NJAHIGG4.js +217 -0
- package/dist/memory-OG6HOYKM.js +472 -0
- package/dist/{models-ZMMLFJNN.js → models-5ZG5XY7J.js} +23 -22
- package/dist/{monitor-2F3T5KHP.js → monitor-TJ7AMTGB.js} +69 -35
- package/dist/{orchestrator-ORHT43JB.js → orchestrator-WZYB54DM.js} +4868 -1189
- package/dist/{paths-UXLN5YYZ.js → paths-XUC7GS6E.js} +5 -5
- package/dist/{reset-NXGTYNUO.js → reset-PXQT45IY.js} +8 -8
- package/dist/{run-RF4WJGMT.js → run-FQ74YF62.js} +82 -62
- package/dist/{share-UT3W6E4M.js → share-FW7SVCL3.js} +34 -10
- package/dist/{skills-PSACKC5Q.js → skills-7E7IRB3R.js} +25 -9
- package/dist/{skills-eval-WJSI55RZ.js → skills-eval-LI75W6OK.js} +43 -35
- package/dist/{targets-PIIRAOYS.js → targets-4CIFKCTW.js} +27 -24
- package/dist/{terminal-lease-ULWXWNVY.js → terminal-lease-WUZY7ZV5.js} +5 -4
- package/dist/{uninstall-FZCQCDKC.js → uninstall-7FV7IP4E.js} +5 -5
- package/dist/{upgrade-346TZ6AV.js → upgrade-K2HVIVMQ.js} +21 -20
- package/dist/{usage-6KKXR32N.js → usage-GTZELZQX.js} +159 -59
- package/dist/verifiers-RLAHT27O.js +336 -0
- package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
- package/dist/{wiki-generate-7STOCIFZ.js → wiki-generate-ASIFASCN.js} +58 -48
- package/dist/worker/entry.js +98 -84
- package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
- package/docs/README.md +4 -3
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-placement.md +1 -2
- package/docs/artifact-versions.md +10 -6
- package/docs/built-in-agents.md +26 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +90 -8
- package/docs/configuration-and-targets.md +90 -2
- package/docs/context-engine.md +4 -2
- package/docs/context-working-set.md +4 -4
- package/docs/development-pipeline.md +1 -1
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +4 -4
- package/docs/documentation-guide.md +4 -4
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +4 -45
- package/docs/evidence-and-memory.md +70 -10
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +4 -1
- package/docs/extensions-and-sharing.md +6 -2
- package/docs/fleet-demo-runbook.md +2 -2
- package/docs/fleet-dispatch.md +224 -11
- package/docs/git-commit-provenance.md +2 -2
- package/docs/glossary.md +1 -1
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +20 -2
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +55 -8
- package/docs/proactive-memory.md +26 -16
- package/docs/prompt-envelope-and-tools.md +4 -2
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +83 -65
- package/docs/resource-library.md +59 -0
- package/docs/safety-model.md +29 -7
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +37 -1
- package/docs/skills-marketplace.md +16 -3
- package/docs/tool-usage.md +14 -7
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +38 -4
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +7 -4
- package/src/cli/agents.ts +2 -3
- package/src/cli/argv.ts +14 -1
- package/src/cli/fleet-commands.ts +37 -0
- package/src/cli/fleet-graph.ts +102 -0
- package/src/cli/fleet-new.ts +36 -0
- package/src/cli/fleet-preflight.ts +121 -0
- package/src/cli/fleet-validate.ts +30 -0
- package/src/cli/fleet.ts +188 -335
- package/src/cli/index.ts +4 -2
- package/src/cli/library.ts +190 -0
- package/src/cli/memory.ts +272 -10
- package/src/cli/modes/json-stream.ts +2 -2
- package/src/cli/modes/print.ts +12 -1
- package/src/cli/run.ts +22 -2
- package/src/cli/share.ts +13 -1
- package/src/cli/targets.ts +12 -3
- package/src/cli/usage.ts +160 -20
- package/src/core/bus-events.ts +7 -0
- package/src/core/commit-attribution.ts +4 -4
- package/src/core/config.ts +130 -0
- package/src/core/defaults.ts +81 -0
- package/src/core/response-model-id.ts +134 -0
- package/src/core/toml.ts +62 -0
- package/src/core/workspace-files.ts +0 -1
- package/src/domains/agents/builtins/architect.md +2 -1
- package/src/domains/agents/builtins/oracle.md +33 -0
- package/src/domains/agents/catalog.ts +18 -5
- package/src/domains/agents/fleet-contract.ts +278 -16
- package/src/domains/agents/index.ts +14 -0
- package/src/domains/agents/recipe.ts +54 -14
- package/src/domains/agents/result-contract.ts +242 -5
- package/src/domains/config/classify.ts +4 -0
- package/src/domains/context/bootstrap.ts +36 -27
- package/src/domains/context/project-metadata.ts +19 -63
- package/src/domains/context/prompt-context.ts +8 -0
- package/src/domains/context/working-set/policies/index.ts +3 -4
- package/src/domains/dispatch/active-route-planner.ts +14 -0
- package/src/domains/dispatch/backoff.ts +2 -1
- package/src/domains/dispatch/budget-envelope.ts +396 -0
- package/src/domains/dispatch/capability-match.ts +1 -0
- package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
- package/src/domains/dispatch/contract.ts +36 -0
- package/src/domains/dispatch/delegation-plan.ts +167 -0
- package/src/domains/dispatch/execution-plan.ts +76 -5
- package/src/domains/dispatch/execution-role.ts +3 -1
- package/src/domains/dispatch/execution-scheduler.ts +183 -67
- package/src/domains/dispatch/extension.ts +339 -36
- package/src/domains/dispatch/fleet-gate.ts +14 -0
- package/src/domains/dispatch/fleet-plan.ts +63 -3
- package/src/domains/dispatch/fleet-run.ts +737 -0
- package/src/domains/dispatch/gate-role-prompts.ts +9 -0
- package/src/domains/dispatch/host-verification.ts +178 -0
- package/src/domains/dispatch/index.ts +38 -0
- package/src/domains/dispatch/intent.ts +159 -0
- package/src/domains/dispatch/orphan-recovery.ts +1 -0
- package/src/domains/dispatch/receipt-integrity.ts +12 -4
- package/src/domains/dispatch/state.ts +37 -3
- package/src/domains/dispatch/types.ts +61 -9
- package/src/domains/dispatch/validation.ts +80 -6
- package/src/domains/dispatch/worker-spawn.ts +14 -3
- package/src/domains/eval/metrics/evidence.ts +0 -116
- package/src/domains/eval/metrics/invariants.ts +1 -1
- package/src/domains/eval/runners/clio-run.ts +1 -10
- package/src/domains/eval/runners/external-command.ts +2 -29
- package/src/domains/eval/schema/suite.ts +0 -7
- package/src/domains/eval/suites/run.ts +1 -7
- package/src/domains/evidence/trust-status.ts +10 -1
- package/src/domains/memory/index.ts +22 -0
- package/src/domains/memory/operations.ts +58 -1
- package/src/domains/memory/promotion.ts +281 -0
- package/src/domains/memory/prompt-section.ts +25 -5
- package/src/domains/memory/proposal.ts +51 -7
- package/src/domains/memory/task-bank.ts +3 -2
- package/src/domains/memory/task-memory-handoff.ts +181 -24
- package/src/domains/memory/task-memory-policy.ts +3 -1
- package/src/domains/memory/types.ts +37 -0
- package/src/domains/memory/validate.ts +178 -0
- package/src/domains/middleware/index.ts +15 -0
- package/src/domains/middleware/memory-intervention.ts +35 -25
- package/src/domains/middleware/runtime.ts +6 -0
- package/src/domains/middleware/skills-reminder.ts +19 -4
- package/src/domains/middleware/stalled-turn.ts +43 -1
- package/src/domains/middleware/types.ts +10 -0
- package/src/domains/middleware/watchdog.ts +281 -0
- package/src/domains/observability/contract.ts +9 -2
- package/src/domains/observability/cost.ts +31 -4
- package/src/domains/observability/extension.ts +2 -2
- package/src/domains/observability/index.ts +10 -0
- package/src/domains/observability/out-of-turn-usage.ts +223 -0
- package/src/domains/providers/index.ts +3 -0
- package/src/domains/providers/model-discovery.ts +9 -0
- package/src/domains/providers/runtime-resolution.ts +38 -1
- package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
- package/src/domains/providers/types/context-window-slots.ts +18 -0
- package/src/domains/providers/types/runtime-descriptor.ts +3 -1
- package/src/domains/resources/index.ts +20 -0
- package/src/domains/resources/library.ts +326 -0
- package/src/domains/resources/skills/marketplace.ts +37 -12
- package/src/domains/safety/call-target.ts +211 -14
- package/src/domains/safety/decision-presentation.ts +268 -0
- package/src/domains/safety/redaction.ts +73 -0
- package/src/domains/session/context-ledger.ts +10 -1
- package/src/domains/session/decision-board.ts +4 -0
- package/src/domains/session/entries.ts +3 -0
- package/src/domains/session/handoff.ts +629 -0
- package/src/domains/session/history.ts +68 -19
- package/src/domains/session/usage.ts +24 -7
- package/src/domains/share/archive.ts +67 -2
- package/src/engine/acp/event-mapper.ts +7 -0
- package/src/engine/acp/server.ts +29 -2
- package/src/engine/apis/lmstudio.ts +25 -4
- package/src/engine/apis/openai-completions.ts +147 -22
- package/src/engine/claude/sdk-runtime.ts +8 -2
- package/src/engine/claude/tool-safety.ts +13 -0
- package/src/engine/loop-guard.ts +27 -3
- package/src/engine/worker-events.ts +4 -3
- package/src/engine/worker-runtime.ts +59 -54
- package/src/entry/orchestrator.ts +55 -1
- package/src/interactive/bus-notices.ts +26 -0
- package/src/interactive/chat-loop-messages.ts +22 -0
- package/src/interactive/chat-loop.ts +248 -1
- package/src/interactive/chat-renderer.ts +41 -3
- package/src/interactive/clio-editor.ts +44 -7
- package/src/interactive/context-overlay.ts +43 -5
- package/src/interactive/cost-overlay.ts +70 -11
- package/src/interactive/council-dispatch.ts +30 -0
- package/src/interactive/council-grid.ts +213 -0
- package/src/interactive/council.ts +99 -0
- package/src/interactive/dispatch-board.ts +471 -50
- package/src/interactive/fleet-run-preview.ts +307 -0
- package/src/interactive/footer/notifications.ts +219 -0
- package/src/interactive/footer/widgets.ts +13 -0
- package/src/interactive/handoff-round.ts +56 -0
- package/src/interactive/interactive-application.ts +49 -2
- package/src/interactive/interactive-event-projection.ts +9 -1
- package/src/interactive/interactive-input-runtime.ts +11 -1
- package/src/interactive/interactive-presentation.ts +11 -1
- package/src/interactive/interactive-slash-runtime.ts +52 -2
- package/src/interactive/interactive-subscriptions.ts +14 -2
- package/src/interactive/memory-overlay.ts +89 -4
- package/src/interactive/oracle.ts +179 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +7 -1
- package/src/interactive/overlay-frame.ts +5 -2
- package/src/interactive/overlay-general-openers.ts +230 -2
- package/src/interactive/overlay-key-routing.ts +58 -2
- package/src/interactive/overlay-lifecycle.ts +52 -5
- package/src/interactive/overlay-permission-lifecycle.ts +33 -8
- package/src/interactive/overlay-resource-openers.ts +11 -3
- package/src/interactive/overlay-session-lifecycle.ts +234 -2
- package/src/interactive/overlay-transitions.ts +11 -0
- package/src/interactive/overlays/ask-user.ts +74 -30
- package/src/interactive/overlays/decisions.ts +3 -1
- package/src/interactive/overlays/fleet-run-approval.ts +208 -0
- package/src/interactive/overlays/handoff-review.ts +185 -0
- package/src/interactive/overlays/library-install-confirm.ts +151 -0
- package/src/interactive/overlays/list-overlay.ts +168 -2
- package/src/interactive/overlays/settings.ts +101 -4
- package/src/interactive/overlays/side-question.ts +139 -0
- package/src/interactive/overlays/skills-hub.ts +401 -15
- package/src/interactive/permission-hint.ts +35 -0
- package/src/interactive/permission-overlay.ts +95 -45
- package/src/interactive/renderers/tool-execution.ts +19 -49
- package/src/interactive/session-last-turn.ts +8 -1
- package/src/interactive/session-usage-reseed.ts +36 -10
- package/src/interactive/side-question.ts +171 -0
- package/src/interactive/slash-commands.ts +434 -7
- package/src/interactive/slash-spec.ts +19 -6
- package/src/interactive/status/summary.ts +5 -0
- package/src/interactive/status/types.ts +5 -0
- package/src/interactive/terminal-lease.ts +1 -0
- package/src/interactive/theme/tokens.ts +30 -0
- package/src/interactive/turn-context.ts +96 -23
- package/src/interactive/turn-middleware.ts +16 -1
- package/src/interactive/turn-runtime.ts +37 -8
- package/src/interactive/turn-state.ts +3 -0
- package/src/interactive/watchdog-run.ts +75 -0
- package/src/interactive/worker-progress.ts +440 -0
- package/src/interactive/worker-share.ts +56 -1
- package/src/interactive/worker-stream.ts +58 -110
- package/src/tools/agent-tools.ts +28 -3
- package/src/tools/ask-user.ts +21 -1
- package/src/tools/bootstrap.ts +3 -0
- package/src/tools/compete-worktrees.ts +13 -79
- package/src/tools/context/index.ts +2 -2
- package/src/tools/dispatch-admission.ts +242 -8
- package/src/tools/dispatch-arguments.ts +65 -1
- package/src/tools/dispatch-event-text.ts +19 -0
- package/src/tools/dispatch-plan.ts +136 -6
- package/src/tools/dispatch-runner.ts +319 -13
- package/src/tools/dispatch-types.ts +20 -1
- package/src/tools/dispatch.ts +96 -3
- package/src/tools/monitor.ts +31 -0
- package/src/tools/profiles.ts +18 -4
- package/src/tools/registry.ts +15 -5
- package/src/tools/result-disposition.ts +156 -0
- package/src/tools/result-shaping.ts +59 -1
- package/src/tools/task-worktree.ts +238 -0
- package/src/tools/verify/authoring.ts +116 -55
- package/src/tools/verify/scripts.ts +62 -0
- package/src/tools/worker-evidence.ts +21 -1
- package/src/worker/spec-contract.ts +44 -3
- package/dist/chunk-EFADSJET.js +0 -18
- package/dist/chunk-HC4CLZ2Y.js +0 -68
- package/dist/memory-4ALKDJ4Q.js +0 -246
- package/src/domains/eval/metrics/chaos-stream.ts +0 -93
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The opt-in turn-end watchdog.
|
|
3
|
+
*
|
|
4
|
+
* A long agentic turn can leave the tree in a state nobody looked at: the
|
|
5
|
+
* model reports success, the operator reads the report, and the drift between
|
|
6
|
+
* what the board asked for and what the diff actually did stays invisible
|
|
7
|
+
* until someone reads the diff themselves. When `watchdog.enabled` is on, a
|
|
8
|
+
* turn that changed the tree is handed to one read-only verifier run briefed
|
|
9
|
+
* with the turn's coalesced diff and the board's current scope. Its blockers
|
|
10
|
+
* become one transcript notice and nothing else.
|
|
11
|
+
*
|
|
12
|
+
* What it deliberately does not do: it never auto-follows, never carries the
|
|
13
|
+
* turn onward, never queues a turn, and never mutates. A watchdog that could
|
|
14
|
+
* start work would be a second agent nobody asked for; this one can only tell
|
|
15
|
+
* the operator what it saw.
|
|
16
|
+
*
|
|
17
|
+
* The registration itself emits no middleware effects. It owns the decision of
|
|
18
|
+
* when to fire and the coalesced diff it fires with; the caller owns the
|
|
19
|
+
* dispatch and the notice.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { ToolNames } from "../../core/tool-names.js";
|
|
23
|
+
import type { MiddlewareHookRegistration } from "./runtime.js";
|
|
24
|
+
import type { MiddlewareEffect, MiddlewareHookInput } from "./types.js";
|
|
25
|
+
|
|
26
|
+
export const WATCHDOG_REGISTRATION_ID = "observer.watchdog";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Bound on the coalesced diff a watchdog briefing carries, in UTF-8 bytes. The
|
|
30
|
+
* run is meant to be cheap enough to spend on every mutating turn, so it is
|
|
31
|
+
* given the shape of the change rather than every byte of it.
|
|
32
|
+
*/
|
|
33
|
+
export const WATCHDOG_DIFF_MAX_BYTES = 12 * 1024;
|
|
34
|
+
|
|
35
|
+
/** Bound on how many mutated paths the briefing names before it summarizes the rest. */
|
|
36
|
+
export const WATCHDOG_PATHS_MAX = 40;
|
|
37
|
+
|
|
38
|
+
export const WATCHDOG_TRUNCATION_MARKER = "\n[diff truncated]";
|
|
39
|
+
|
|
40
|
+
/** Tools whose successful results carry a file diff worth coalescing. */
|
|
41
|
+
const MUTATING_TOOL_NAMES = new Set<string>([ToolNames.Write, ToolNames.Edit, ToolNames.Artifact]);
|
|
42
|
+
|
|
43
|
+
const NO_EFFECTS: ReadonlyArray<MiddlewareEffect> = [];
|
|
44
|
+
|
|
45
|
+
export type WatchdogTriggerReason = "turn_end" | "cadence";
|
|
46
|
+
|
|
47
|
+
export interface WatchdogTrigger {
|
|
48
|
+
reason: WatchdogTriggerReason;
|
|
49
|
+
/** The turn's coalesced diff, one block per mutated path in first-touch order. */
|
|
50
|
+
diff: string;
|
|
51
|
+
/** Paths the turn mutated, in first-touch order. */
|
|
52
|
+
paths: ReadonlyArray<string>;
|
|
53
|
+
/** The board's current scope, or null when the session has no task board. */
|
|
54
|
+
scope: string | null;
|
|
55
|
+
/** Tool calls seen in this turn when the trigger fired. */
|
|
56
|
+
toolCalls: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The live settings view the registration reads on every boundary, so a hot reload lands immediately. */
|
|
60
|
+
export interface WatchdogSettingsView {
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
target?: string;
|
|
63
|
+
cadenceToolCalls?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface WatchdogDeps {
|
|
67
|
+
getSettings: () => Readonly<WatchdogSettingsView>;
|
|
68
|
+
/** The board's current scope line, or null when there is no board. */
|
|
69
|
+
getScope?: () => string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Dispatch the watchdog run and deliver whatever it found. Resolves when the
|
|
72
|
+
* run has settled; the registration awaits only to know the in-flight slot is
|
|
73
|
+
* free again, never to hold the turn.
|
|
74
|
+
*/
|
|
75
|
+
run: (trigger: WatchdogTrigger) => Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* False on a surface that must never fire the watchdog whatever the setting
|
|
78
|
+
* says. Headless and ACP runs pass false: neither has an operator watching a
|
|
79
|
+
* transcript, so a notice they cannot read is a worker run spent on nothing.
|
|
80
|
+
*/
|
|
81
|
+
firesOnThisSurface?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface WatchdogRegistration extends MiddlewareHookRegistration {
|
|
85
|
+
/** Triggers dropped because a watchdog run was already in flight. */
|
|
86
|
+
droppedTriggers(): number;
|
|
87
|
+
/** True while a watchdog run is outstanding. */
|
|
88
|
+
runInFlight(): boolean;
|
|
89
|
+
/** Resolves once no watchdog run is outstanding. */
|
|
90
|
+
whenIdle(): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function stringValue(value: unknown): string | null {
|
|
94
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function firstPath(details: Readonly<Record<string, unknown>> | undefined): string | null {
|
|
98
|
+
const paths = details?.paths;
|
|
99
|
+
if (!Array.isArray(paths)) return null;
|
|
100
|
+
for (const entry of paths) {
|
|
101
|
+
const path = stringValue(entry);
|
|
102
|
+
if (path !== null) return path;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Coalesce the per-call diffs of one turn into one diff.
|
|
109
|
+
*
|
|
110
|
+
* Per-path last-write-wins, in first-touch order. A turn that edited one file
|
|
111
|
+
* six times produced six diffs against six different base contents; replaying
|
|
112
|
+
* all of them tells a reviewer the history of the edit rather than its result,
|
|
113
|
+
* and the result is what the reviewer is being asked about.
|
|
114
|
+
*/
|
|
115
|
+
export function coalesceTurnDiff(
|
|
116
|
+
byPath: ReadonlyMap<string, string>,
|
|
117
|
+
maxBytes: number = WATCHDOG_DIFF_MAX_BYTES,
|
|
118
|
+
): string {
|
|
119
|
+
const blocks: string[] = [];
|
|
120
|
+
let index = 0;
|
|
121
|
+
for (const [path, diff] of byPath) {
|
|
122
|
+
if (index >= WATCHDOG_PATHS_MAX) {
|
|
123
|
+
blocks.push(`[${byPath.size - WATCHDOG_PATHS_MAX} more changed path(s) omitted]`);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
blocks.push(`--- ${path}\n${diff}`);
|
|
127
|
+
index += 1;
|
|
128
|
+
}
|
|
129
|
+
const whole = blocks.join("\n\n");
|
|
130
|
+
if (Buffer.byteLength(whole, "utf8") <= maxBytes) return whole;
|
|
131
|
+
const room = Math.max(0, maxBytes - Buffer.byteLength(WATCHDOG_TRUNCATION_MARKER, "utf8"));
|
|
132
|
+
const buffer = Buffer.from(whole, "utf8");
|
|
133
|
+
let cut = Math.min(room, buffer.byteLength);
|
|
134
|
+
while (cut > 0) {
|
|
135
|
+
const next = buffer[cut];
|
|
136
|
+
if (next === undefined || (next & 0xc0) !== 0x80) break;
|
|
137
|
+
cut -= 1;
|
|
138
|
+
}
|
|
139
|
+
return `${buffer.subarray(0, cut).toString("utf8")}${WATCHDOG_TRUNCATION_MARKER}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The bounded, fixed task every watchdog run is given. It never varies with the diff. */
|
|
143
|
+
export const WATCHDOG_TASK =
|
|
144
|
+
"Review the coalesced diff in the briefing against the scope it names. Report one check per concern: " +
|
|
145
|
+
"a change outside the stated scope, a change the scope required that the diff does not make, or a defect " +
|
|
146
|
+
"visible in the diff itself. Run no commands and change nothing.";
|
|
147
|
+
|
|
148
|
+
/** The briefing body a watchdog run receives. Data, never instructions. */
|
|
149
|
+
export function watchdogBriefing(trigger: WatchdogTrigger): string {
|
|
150
|
+
const scope = trigger.scope === null ? "(no task board; scope is unstated)" : trigger.scope;
|
|
151
|
+
const paths = trigger.paths.length === 0 ? "(none)" : trigger.paths.join("\n");
|
|
152
|
+
return [
|
|
153
|
+
"## Current scope",
|
|
154
|
+
scope,
|
|
155
|
+
"",
|
|
156
|
+
"## Changed paths",
|
|
157
|
+
paths,
|
|
158
|
+
"",
|
|
159
|
+
"## Coalesced diff for this turn",
|
|
160
|
+
trigger.diff,
|
|
161
|
+
].join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Build the registration. It is hooked on `after_tool` and `turn_end`:
|
|
166
|
+
* `after_tool` is where the diffs and the tool count come from, and `turn_end`
|
|
167
|
+
* is the boundary a mutating turn fires on.
|
|
168
|
+
*/
|
|
169
|
+
export function createWatchdogRegistration(deps: WatchdogDeps): WatchdogRegistration {
|
|
170
|
+
const diffByPath = new Map<string, string>();
|
|
171
|
+
let toolCalls = 0;
|
|
172
|
+
let dropped = 0;
|
|
173
|
+
let inFlight = false;
|
|
174
|
+
let outstanding: Promise<void> = Promise.resolve();
|
|
175
|
+
|
|
176
|
+
const active = (): WatchdogSettingsView | null => {
|
|
177
|
+
if (deps.firesOnThisSurface === false) return null;
|
|
178
|
+
let settings: Readonly<WatchdogSettingsView>;
|
|
179
|
+
try {
|
|
180
|
+
settings = deps.getSettings();
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return settings.enabled ? settings : null;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const resetTurn = (): void => {
|
|
188
|
+
diffByPath.clear();
|
|
189
|
+
toolCalls = 0;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const fire = (reason: WatchdogTriggerReason): void => {
|
|
193
|
+
if (diffByPath.size === 0) return;
|
|
194
|
+
// One run at a time. A watchdog slower than the turns that trigger it must
|
|
195
|
+
// not queue: a queued review describes a tree that has already moved on,
|
|
196
|
+
// and a backlog of them would outlive the session. The drop is counted so
|
|
197
|
+
// a starved cadence and a quiet one stay distinguishable.
|
|
198
|
+
if (inFlight) {
|
|
199
|
+
dropped += 1;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const trigger: WatchdogTrigger = {
|
|
203
|
+
reason,
|
|
204
|
+
diff: coalesceTurnDiff(diffByPath),
|
|
205
|
+
paths: [...diffByPath.keys()].slice(0, WATCHDOG_PATHS_MAX),
|
|
206
|
+
scope: scopeNow(),
|
|
207
|
+
toolCalls,
|
|
208
|
+
};
|
|
209
|
+
inFlight = true;
|
|
210
|
+
// Started synchronously, so the in-flight slot is genuinely taken before
|
|
211
|
+
// this hook returns. Deferring the start to a microtask would let two
|
|
212
|
+
// triggers in the same tool batch both find the slot free.
|
|
213
|
+
let started: Promise<void>;
|
|
214
|
+
try {
|
|
215
|
+
started = deps.run(trigger);
|
|
216
|
+
} catch {
|
|
217
|
+
inFlight = false;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
outstanding = started
|
|
221
|
+
.catch(() => {
|
|
222
|
+
// A failed watchdog run is not a failed turn. The run's own reporting
|
|
223
|
+
// path already told the operator whatever it could.
|
|
224
|
+
})
|
|
225
|
+
.finally(() => {
|
|
226
|
+
inFlight = false;
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const scopeNow = (): string | null => {
|
|
231
|
+
try {
|
|
232
|
+
return deps.getScope?.() ?? null;
|
|
233
|
+
} catch {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
id: WATCHDOG_REGISTRATION_ID,
|
|
240
|
+
description: "review a mutating turn's coalesced diff against the board scope with a read-only verifier run",
|
|
241
|
+
hooks: ["after_tool", "turn_end"],
|
|
242
|
+
evaluate(input: MiddlewareHookInput): ReadonlyArray<MiddlewareEffect> {
|
|
243
|
+
const settings = active();
|
|
244
|
+
if (settings === null) {
|
|
245
|
+
// Still clear turn state so a mid-session disable cannot leave a diff
|
|
246
|
+
// behind that a later enable would fire on.
|
|
247
|
+
if (input.hook === "turn_end") resetTurn();
|
|
248
|
+
return NO_EFFECTS;
|
|
249
|
+
}
|
|
250
|
+
if (input.hook === "after_tool") {
|
|
251
|
+
toolCalls += 1;
|
|
252
|
+
recordMutation(input);
|
|
253
|
+
const cadence = settings.cadenceToolCalls;
|
|
254
|
+
// Mid-turn cadence keeps accumulating: turn end still reviews the whole
|
|
255
|
+
// turn, not just what landed after the last cadence firing.
|
|
256
|
+
if (cadence !== undefined && cadence >= 1 && toolCalls % cadence === 0) fire("cadence");
|
|
257
|
+
return NO_EFFECTS;
|
|
258
|
+
}
|
|
259
|
+
if (input.hook !== "turn_end") return NO_EFFECTS;
|
|
260
|
+
// A turn with no file mutations never fires. A middleware continuation
|
|
261
|
+
// re-evaluates turn_end with the accumulator already cleared, so it
|
|
262
|
+
// cannot fire a second run for the same change either.
|
|
263
|
+
fire("turn_end");
|
|
264
|
+
resetTurn();
|
|
265
|
+
return NO_EFFECTS;
|
|
266
|
+
},
|
|
267
|
+
droppedTriggers: () => dropped,
|
|
268
|
+
runInFlight: () => inFlight,
|
|
269
|
+
whenIdle: () => outstanding,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
function recordMutation(input: MiddlewareHookInput): void {
|
|
273
|
+
if (input.metadata?.resultKind !== "ok") return;
|
|
274
|
+
if (input.toolName === undefined || !MUTATING_TOOL_NAMES.has(input.toolName)) return;
|
|
275
|
+
const diff = stringValue(input.toolResultDetails?.diff);
|
|
276
|
+
if (diff === null) return;
|
|
277
|
+
// Map.set on an existing key keeps its insertion position, so the newest
|
|
278
|
+
// diff for a path wins while the path keeps its first-touch order.
|
|
279
|
+
diffByPath.set(firstPath(input.toolResultDetails) ?? input.toolName, diff);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { ResponseModelIdObservationCounts } from "../../core/response-model-id.js";
|
|
1
2
|
import type { TargetStatus } from "../providers/contract.js";
|
|
2
3
|
import type { CostProvenance } from "../providers/index.js";
|
|
3
4
|
import type { AccountabilitySummary } from "./accountability.js";
|
|
4
|
-
import type { CostAggregate, CostEntry, UsageBreakdown } from "./cost.js";
|
|
5
|
+
import type { CostAggregate, CostEntry, CostEntryLabel, UsageBreakdown } from "./cost.js";
|
|
5
6
|
import type { MetricsView } from "./metrics.js";
|
|
6
7
|
import type { TelemetrySnapshot } from "./telemetry.js";
|
|
7
8
|
import type { SessionTurnTrace } from "./trace-store.js";
|
|
@@ -132,11 +133,17 @@ export interface ObservabilityContract {
|
|
|
132
133
|
*/
|
|
133
134
|
recordTokens(
|
|
134
135
|
providerId: string,
|
|
135
|
-
|
|
136
|
+
attributedModelId: string,
|
|
136
137
|
tokens: number,
|
|
137
138
|
costUsd?: number,
|
|
138
139
|
breakdown?: Partial<UsageBreakdown>,
|
|
139
140
|
costProvenance?: CostProvenance,
|
|
141
|
+
modelIdFacts?: {
|
|
142
|
+
requestedModelIds: ReadonlyArray<string>;
|
|
143
|
+
responseModelIdObservationCounts: Readonly<ResponseModelIdObservationCounts>;
|
|
144
|
+
},
|
|
145
|
+
/** Marks a priced call that was not an ordinary turn, such as a `/btw` side question. */
|
|
146
|
+
label?: CostEntryLabel,
|
|
140
147
|
): void;
|
|
141
148
|
/** Record final output token throughput for one completed assistant stream. */
|
|
142
149
|
recordTokenThroughput(snapshot: TokenThroughputSnapshot): void;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* footer consumes the session sum through `ObservabilityContract.sessionTokens()`.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import type { ResponseModelIdObservationCounts } from "../../core/response-model-id.js";
|
|
14
15
|
import { type CostProvenance, normalizeCostProvenance } from "../providers/index.js";
|
|
15
16
|
|
|
16
17
|
export interface CostAmount {
|
|
@@ -110,9 +111,18 @@ export interface UsageBreakdown {
|
|
|
110
111
|
apiCalls?: number;
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
/**
|
|
115
|
+
* What produced a priced call, when it was not an ordinary turn. `/cost` and
|
|
116
|
+
* the usage surfaces separate these out so an operator can see that money was
|
|
117
|
+
* spent beside the session rather than inside it.
|
|
118
|
+
*/
|
|
119
|
+
export type CostEntryLabel = "side-question" | "handoff";
|
|
120
|
+
|
|
113
121
|
export interface CostEntry {
|
|
114
122
|
providerId: string;
|
|
115
|
-
|
|
123
|
+
attributedModelId: string;
|
|
124
|
+
requestedModelIds: string[];
|
|
125
|
+
responseModelIdObservationCounts: ResponseModelIdObservationCounts;
|
|
116
126
|
tokens: number;
|
|
117
127
|
usd: number;
|
|
118
128
|
provenance: CostProvenance;
|
|
@@ -122,16 +132,23 @@ export interface CostEntry {
|
|
|
122
132
|
cacheWrite: number;
|
|
123
133
|
reasoningTokens: number;
|
|
124
134
|
apiCalls?: number;
|
|
135
|
+
/** Absent on an ordinary turn's call. */
|
|
136
|
+
label?: CostEntryLabel;
|
|
125
137
|
}
|
|
126
138
|
|
|
127
139
|
export interface CostTracker {
|
|
128
140
|
accumulate(
|
|
129
141
|
providerId: string,
|
|
130
|
-
|
|
142
|
+
attributedModelId: string,
|
|
131
143
|
tokens: number,
|
|
132
144
|
usd?: number,
|
|
133
145
|
breakdown?: Partial<UsageBreakdown>,
|
|
134
146
|
provenance?: CostProvenance,
|
|
147
|
+
modelIdFacts?: {
|
|
148
|
+
requestedModelIds: ReadonlyArray<string>;
|
|
149
|
+
responseModelIdObservationCounts: Readonly<ResponseModelIdObservationCounts>;
|
|
150
|
+
},
|
|
151
|
+
label?: CostEntryLabel,
|
|
135
152
|
): number;
|
|
136
153
|
sessionTotal(): number;
|
|
137
154
|
sessionCost(): CostAggregate;
|
|
@@ -149,7 +166,7 @@ export function createCostTracker(): CostTracker {
|
|
|
149
166
|
let total = 0;
|
|
150
167
|
const totals = emptyBreakdown();
|
|
151
168
|
return {
|
|
152
|
-
accumulate(providerId,
|
|
169
|
+
accumulate(providerId, attributedModelId, tokens, usd, breakdown, costProvenance, modelIdFacts, label) {
|
|
153
170
|
const resolvedUsd = usd ?? 0;
|
|
154
171
|
const provenance = normalizeCostProvenance(costProvenance);
|
|
155
172
|
const input = breakdown?.input ?? 0;
|
|
@@ -160,7 +177,16 @@ export function createCostTracker(): CostTracker {
|
|
|
160
177
|
const apiCalls = breakdown?.apiCalls;
|
|
161
178
|
log.push({
|
|
162
179
|
providerId,
|
|
163
|
-
|
|
180
|
+
attributedModelId,
|
|
181
|
+
requestedModelIds: [...(modelIdFacts?.requestedModelIds ?? [attributedModelId])],
|
|
182
|
+
responseModelIdObservationCounts: modelIdFacts
|
|
183
|
+
? { ...modelIdFacts.responseModelIdObservationCounts }
|
|
184
|
+
: {
|
|
185
|
+
reportedCalls: 0,
|
|
186
|
+
notReportedCalls: 0,
|
|
187
|
+
notObservedCalls: apiCalls ?? 1,
|
|
188
|
+
legacyDifferenceOnlyCalls: 0,
|
|
189
|
+
},
|
|
164
190
|
tokens,
|
|
165
191
|
usd: resolvedUsd,
|
|
166
192
|
provenance,
|
|
@@ -170,6 +196,7 @@ export function createCostTracker(): CostTracker {
|
|
|
170
196
|
cacheWrite,
|
|
171
197
|
reasoningTokens,
|
|
172
198
|
...(apiCalls !== undefined ? { apiCalls } : {}),
|
|
199
|
+
...(label !== undefined ? { label } : {}),
|
|
173
200
|
});
|
|
174
201
|
total += resolvedUsd;
|
|
175
202
|
totals.input += input;
|
|
@@ -259,9 +259,9 @@ export function createObservabilityBundle(
|
|
|
259
259
|
latestThroughput = null;
|
|
260
260
|
projection.refresh();
|
|
261
261
|
},
|
|
262
|
-
recordTokens(providerId,
|
|
262
|
+
recordTokens(providerId, attributedModelId, tokens, costUsd, breakdown, costProvenance, modelIdFacts, label) {
|
|
263
263
|
telemetry.record("counter", "tokens.total", tokens);
|
|
264
|
-
cost.accumulate(providerId,
|
|
264
|
+
cost.accumulate(providerId, attributedModelId, tokens, costUsd, breakdown, costProvenance, modelIdFacts, label);
|
|
265
265
|
projection.refresh();
|
|
266
266
|
},
|
|
267
267
|
recordSessionTurn(sessionTurn) {
|
|
@@ -30,6 +30,7 @@ export {
|
|
|
30
30
|
type CostAggregate,
|
|
31
31
|
type CostAmount,
|
|
32
32
|
type CostEntry,
|
|
33
|
+
type CostEntryLabel,
|
|
33
34
|
costAggregateForAmount,
|
|
34
35
|
costWasMeasured,
|
|
35
36
|
emptyCostAggregate,
|
|
@@ -40,6 +41,15 @@ export type { EvidenceIndexRow } from "./evidence-index.js";
|
|
|
40
41
|
export { EVIDENCE_INDEX_FILE, MAX_EVIDENCE_INDEX_ROWS, readEvidenceIndex } from "./evidence-index.js";
|
|
41
42
|
export { ObservabilityManifest } from "./manifest.js";
|
|
42
43
|
export type { MetricsView } from "./metrics.js";
|
|
44
|
+
export type { OutOfTurnUsage, OutOfTurnUsageReadResult, OutOfTurnUsageRow } from "./out-of-turn-usage.js";
|
|
45
|
+
export {
|
|
46
|
+
appendOutOfTurnUsageRow,
|
|
47
|
+
MAX_OUT_OF_TURN_USAGE_ROWS,
|
|
48
|
+
OUT_OF_TURN_USAGE_DIR,
|
|
49
|
+
OUT_OF_TURN_USAGE_FILE,
|
|
50
|
+
outOfTurnUsagePath,
|
|
51
|
+
readOutOfTurnUsageRows,
|
|
52
|
+
} from "./out-of-turn-usage.js";
|
|
43
53
|
export type { ObservabilityProjection, ProjectionReadModel } from "./projection.js";
|
|
44
54
|
export {
|
|
45
55
|
createObservabilityProjection,
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable record of the model calls that were billed beside a session rather
|
|
3
|
+
* than inside it: `/btw` side questions and `/handoff` extraction rounds.
|
|
4
|
+
*
|
|
5
|
+
* These rounds deliberately append nothing to the session JSONL. A fleet run
|
|
6
|
+
* briefs its workers from the transcript, so a question the operator asked to
|
|
7
|
+
* orient themselves must not become context those workers inherit, and
|
|
8
|
+
* `src/interactive/side-question.ts` states that promise as a contract. The
|
|
9
|
+
* money was still spent, though, and until this store existed the only record
|
|
10
|
+
* of it was the in-process cost tracker: `/cost` showed it for as long as the
|
|
11
|
+
* process lived, and `clio-coder usage report`, which reads the archive on
|
|
12
|
+
* disk, could not see it at all.
|
|
13
|
+
*
|
|
14
|
+
* So the spend gets its own file. One JSON line per priced out-of-turn call
|
|
15
|
+
* under `<stateDir>/usage/out-of-turn.jsonl`, carrying the label, the session
|
|
16
|
+
* it sat beside, the repo identity the session ledger is filed under (so
|
|
17
|
+
* `usage report --repo` filters these rows the same way it filters ledgers),
|
|
18
|
+
* the target and attributed model, and the provider-reported usage.
|
|
19
|
+
*
|
|
20
|
+
* Writes follow the audit-row conventions: one append-mode `writeSync` per
|
|
21
|
+
* row, so a line lands whole even with concurrent writers, and a failure is
|
|
22
|
+
* logged to stderr rather than thrown back into the interactive path. The file
|
|
23
|
+
* is a bounded ring: once it grows past {@link MAX_OUT_OF_TURN_USAGE_ROWS} the
|
|
24
|
+
* newest rows are rewritten atomically under the shared state-file lock and
|
|
25
|
+
* the oldest are dropped. Reads are tolerant; a malformed line is reported and
|
|
26
|
+
* skipped, never fatal.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { closeSync, mkdirSync, openSync, readFileSync, writeSync } from "node:fs";
|
|
30
|
+
import { dirname, join } from "node:path";
|
|
31
|
+
import { safeResourceWrite } from "../../core/safe-resource-write.js";
|
|
32
|
+
import { withStateFileLockSync } from "../../core/state-file-lock.js";
|
|
33
|
+
import type { CostProvenance } from "../providers/index.js";
|
|
34
|
+
import type { CostEntryLabel } from "./cost.js";
|
|
35
|
+
|
|
36
|
+
/** Directory under the state dir that holds cross-session usage side-cars. */
|
|
37
|
+
export const OUT_OF_TURN_USAGE_DIR = "usage";
|
|
38
|
+
|
|
39
|
+
/** Filename of the out-of-turn usage ledger inside {@link OUT_OF_TURN_USAGE_DIR}. */
|
|
40
|
+
export const OUT_OF_TURN_USAGE_FILE = "out-of-turn.jsonl";
|
|
41
|
+
|
|
42
|
+
/** Cap on retained rows. Matches the dispatch runs ledger and evidence index default. */
|
|
43
|
+
export const MAX_OUT_OF_TURN_USAGE_ROWS = 1000;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* How many appends this process makes between bound checks. Counting rows means
|
|
47
|
+
* reading the file, and an out-of-turn round is rare enough that paying that on
|
|
48
|
+
* the first append of a process and periodically after it keeps the ring bounded
|
|
49
|
+
* without reading a megabyte per side question.
|
|
50
|
+
*/
|
|
51
|
+
const BOUND_CHECK_INTERVAL = 64;
|
|
52
|
+
|
|
53
|
+
/** Provider-reported usage for one out-of-turn call. */
|
|
54
|
+
export interface OutOfTurnUsage {
|
|
55
|
+
input: number;
|
|
56
|
+
output: number;
|
|
57
|
+
cacheRead: number;
|
|
58
|
+
cacheWrite: number;
|
|
59
|
+
reasoning: number;
|
|
60
|
+
totalTokens: number;
|
|
61
|
+
costUsd: number;
|
|
62
|
+
costProvenance: CostProvenance;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** One priced model call that was billed beside a session rather than inside it. */
|
|
66
|
+
export interface OutOfTurnUsageRow {
|
|
67
|
+
label: CostEntryLabel;
|
|
68
|
+
/** Session the round sat beside, or null when no session was current. */
|
|
69
|
+
sessionId: string | null;
|
|
70
|
+
/** The cwd hash the session ledger is filed under, so `--repo` can filter these rows. */
|
|
71
|
+
repoIdentity: string | null;
|
|
72
|
+
/** ISO-8601 timestamp of the call. */
|
|
73
|
+
timestamp: string;
|
|
74
|
+
/** Target id the round ran against. */
|
|
75
|
+
target: string;
|
|
76
|
+
attributedModelId: string;
|
|
77
|
+
usage: OutOfTurnUsage;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface OutOfTurnUsageReadResult {
|
|
81
|
+
rows: OutOfTurnUsageRow[];
|
|
82
|
+
errors: string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function outOfTurnUsagePath(stateDir: string): string {
|
|
86
|
+
return join(stateDir, OUT_OF_TURN_USAGE_DIR, OUT_OF_TURN_USAGE_FILE);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let appendsSinceBoundCheck = BOUND_CHECK_INTERVAL;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Append one priced out-of-turn call. Never throws: this runs on the same path
|
|
93
|
+
* that answers an operator's side question, and a failed bookkeeping write must
|
|
94
|
+
* not take the answer down with it.
|
|
95
|
+
*/
|
|
96
|
+
export function appendOutOfTurnUsageRow(stateDir: string, row: OutOfTurnUsageRow): void {
|
|
97
|
+
const path = outOfTurnUsagePath(stateDir);
|
|
98
|
+
try {
|
|
99
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
100
|
+
const fd = openSync(path, "a");
|
|
101
|
+
try {
|
|
102
|
+
writeSync(fd, `${JSON.stringify(row)}\n`);
|
|
103
|
+
} finally {
|
|
104
|
+
closeSync(fd);
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
process.stderr.write(`[clio:usage] out-of-turn usage row not written: ${messageOf(error)}\n`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
appendsSinceBoundCheck += 1;
|
|
111
|
+
if (appendsSinceBoundCheck < BOUND_CHECK_INTERVAL) return;
|
|
112
|
+
appendsSinceBoundCheck = 0;
|
|
113
|
+
try {
|
|
114
|
+
boundOutOfTurnUsageFile(path);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
process.stderr.write(`[clio:usage] out-of-turn usage ring not bounded: ${messageOf(error)}\n`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Rewrite the file down to the newest {@link MAX_OUT_OF_TURN_USAGE_ROWS} lines
|
|
122
|
+
* when it has grown past the cap. Held under the state-file lock so a concurrent
|
|
123
|
+
* appender cannot lose a row into the rename, and written through
|
|
124
|
+
* `safeResourceWrite` so a crash mid-rewrite leaves the previous file intact.
|
|
125
|
+
*/
|
|
126
|
+
function boundOutOfTurnUsageFile(path: string): void {
|
|
127
|
+
withStateFileLockSync(path, () => {
|
|
128
|
+
let raw: string;
|
|
129
|
+
try {
|
|
130
|
+
raw = readFileSync(path, "utf8");
|
|
131
|
+
} catch {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
135
|
+
if (lines.length <= MAX_OUT_OF_TURN_USAGE_ROWS) return;
|
|
136
|
+
const kept = lines.slice(-MAX_OUT_OF_TURN_USAGE_ROWS);
|
|
137
|
+
safeResourceWrite(path, `${kept.join("\n")}\n`, { encoding: "utf8" });
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Read every recorded out-of-turn call. A missing file is an empty read rather
|
|
143
|
+
* than an error: a machine where nobody has ever run `/btw` has nothing to
|
|
144
|
+
* report, and that is a different thing from a store that failed to open.
|
|
145
|
+
*/
|
|
146
|
+
export function readOutOfTurnUsageRows(stateDir: string): OutOfTurnUsageReadResult {
|
|
147
|
+
const path = outOfTurnUsagePath(stateDir);
|
|
148
|
+
let raw: string;
|
|
149
|
+
try {
|
|
150
|
+
raw = readFileSync(path, "utf8");
|
|
151
|
+
} catch (error) {
|
|
152
|
+
const err = error as NodeJS.ErrnoException;
|
|
153
|
+
if (err.code === "ENOENT" || err.code === "ENOTDIR") return { rows: [], errors: [] };
|
|
154
|
+
return { rows: [], errors: [`out-of-turn usage store unreadable: ${messageOf(error)}`] };
|
|
155
|
+
}
|
|
156
|
+
const rows: OutOfTurnUsageRow[] = [];
|
|
157
|
+
const errors: string[] = [];
|
|
158
|
+
const lines = raw.split("\n");
|
|
159
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
160
|
+
const line = lines[index];
|
|
161
|
+
if (line === undefined || line.trim().length === 0) continue;
|
|
162
|
+
let parsed: unknown;
|
|
163
|
+
try {
|
|
164
|
+
parsed = JSON.parse(line) as unknown;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
errors.push(`${path}:${index + 1}: invalid JSON: ${messageOf(error)}`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const row = asOutOfTurnUsageRow(parsed);
|
|
170
|
+
if (row === null) {
|
|
171
|
+
errors.push(`${path}:${index + 1}: not an out-of-turn usage row`);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
rows.push(row);
|
|
175
|
+
}
|
|
176
|
+
return { rows, errors };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function asOutOfTurnUsageRow(value: unknown): OutOfTurnUsageRow | null {
|
|
180
|
+
if (!isRecord(value)) return null;
|
|
181
|
+
const label = value.label;
|
|
182
|
+
if (label !== "side-question" && label !== "handoff") return null;
|
|
183
|
+
if (typeof value.timestamp !== "string" || value.timestamp.length === 0) return null;
|
|
184
|
+
if (!isRecord(value.usage)) return null;
|
|
185
|
+
const usage = value.usage;
|
|
186
|
+
return {
|
|
187
|
+
label,
|
|
188
|
+
sessionId: typeof value.sessionId === "string" ? value.sessionId : null,
|
|
189
|
+
repoIdentity: typeof value.repoIdentity === "string" ? value.repoIdentity : null,
|
|
190
|
+
timestamp: value.timestamp,
|
|
191
|
+
target: typeof value.target === "string" && value.target.length > 0 ? value.target : "unknown",
|
|
192
|
+
attributedModelId:
|
|
193
|
+
typeof value.attributedModelId === "string" && value.attributedModelId.length > 0
|
|
194
|
+
? value.attributedModelId
|
|
195
|
+
: "unknown",
|
|
196
|
+
usage: {
|
|
197
|
+
input: numberOr0(usage.input),
|
|
198
|
+
output: numberOr0(usage.output),
|
|
199
|
+
cacheRead: numberOr0(usage.cacheRead),
|
|
200
|
+
cacheWrite: numberOr0(usage.cacheWrite),
|
|
201
|
+
reasoning: numberOr0(usage.reasoning),
|
|
202
|
+
totalTokens: numberOr0(usage.totalTokens),
|
|
203
|
+
costUsd: numberOr0(usage.costUsd),
|
|
204
|
+
costProvenance: asCostProvenance(usage.costProvenance),
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function asCostProvenance(value: unknown): CostProvenance {
|
|
210
|
+
return value === "known" || value === "known_free" || value === "estimated" ? value : "unknown";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function numberOr0(value: unknown): number {
|
|
214
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
218
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function messageOf(error: unknown): string {
|
|
222
|
+
return error instanceof Error ? error.message : String(error);
|
|
223
|
+
}
|
|
@@ -32,6 +32,7 @@ export type { ModelCapabilityPatchTarget } from "./model-capabilities.js";
|
|
|
32
32
|
export { applyModelCapabilityPatch, resolveModelCapabilities } from "./model-capabilities.js";
|
|
33
33
|
export {
|
|
34
34
|
canonicalizeWireModelId,
|
|
35
|
+
contextSlotsForModel,
|
|
35
36
|
hasLiveModelCatalog,
|
|
36
37
|
loadedContextWindowForModel,
|
|
37
38
|
type ModelResidency,
|
|
@@ -96,6 +97,7 @@ export {
|
|
|
96
97
|
refineRuntimeTargetWithModelHints,
|
|
97
98
|
resolveRuntimeTarget,
|
|
98
99
|
runtimeResolutionWarnings,
|
|
100
|
+
runtimeResolutionWarningsBesideThinkingNotice,
|
|
99
101
|
runtimeTargetSnapshot,
|
|
100
102
|
} from "./runtime-resolution.js";
|
|
101
103
|
export {
|
|
@@ -126,6 +128,7 @@ export {
|
|
|
126
128
|
EMPTY_CAPABILITIES,
|
|
127
129
|
VALID_THINKING_LEVELS,
|
|
128
130
|
} from "./types/capability-flags.js";
|
|
131
|
+
export { type ContextWindowSlots, formatContextWindowSlots } from "./types/context-window-slots.js";
|
|
129
132
|
export { type CostProvenance, normalizeCostProvenance } from "./types/cost-provenance.js";
|
|
130
133
|
export type { KnowledgeBase, KnowledgeBaseEntry, KnowledgeBaseHit } from "./types/knowledge-base.js";
|
|
131
134
|
export type {
|