@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
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Editor, stripTerminalSequences, type TUI, truncateToWidth, visibleWidth } from "../engine/tui.js";
|
|
2
|
+
import { fitHintEntries } from "./overlay-frame.js";
|
|
3
|
+
import { permissionHintEntries } from "./permission-hint.js";
|
|
2
4
|
import type { ClioTheme } from "./theme/index.js";
|
|
3
5
|
import { clioTheme, editorTheme, GLYPH, rule } from "./theme/index.js";
|
|
4
6
|
|
|
5
7
|
const REVERSE_VIDEO_BLANK = `${String.fromCharCode(27)}[7m ${String.fromCharCode(27)}[0m`;
|
|
6
8
|
const EMPTY_PROMPT = "Ask Clio… / for commands";
|
|
9
|
+
const CONFIRM_PROMPT = "A parked call is waiting for your decision";
|
|
7
10
|
const MIN_HINT_WIDTH = 60;
|
|
8
11
|
|
|
9
12
|
function hasScrollIndicator(line: string): boolean {
|
|
@@ -18,6 +21,13 @@ export interface EditorChrome {
|
|
|
18
21
|
getThinkingLabel: () => string;
|
|
19
22
|
/** Whether Enter currently targets the active Clio response. */
|
|
20
23
|
isStreaming?: () => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether a permission prompt owns the keyboard. The dialog once sat at the
|
|
26
|
+
* vertical center of a tall viewport, far from the composer. It now anchors
|
|
27
|
+
* above that composer, while the rail still says CONFIRM and carries the
|
|
28
|
+
* dialog's keys whenever the prompt owns input (issues #186 and #194).
|
|
29
|
+
*/
|
|
30
|
+
isAwaitingApproval?: () => boolean;
|
|
21
31
|
/** Whether the current draft will actually steer Clio or live dispatch work on Enter. */
|
|
22
32
|
willEnterSteer?: (text: string) => boolean;
|
|
23
33
|
/** Resolved submit binding, formatted for display. */
|
|
@@ -26,7 +36,7 @@ export interface EditorChrome {
|
|
|
26
36
|
getNewlineKeyLabel?: () => string;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
type ComposerMode = "MESSAGE" | "FOLLOW-UP" | "STEER";
|
|
39
|
+
type ComposerMode = "MESSAGE" | "FOLLOW-UP" | "STEER" | "CONFIRM";
|
|
30
40
|
|
|
31
41
|
function normalizeThinkingHint(value: string): string {
|
|
32
42
|
return value
|
|
@@ -61,6 +71,7 @@ function styledRailLabel(theme: ClioTheme, chrome: EditorChrome): string {
|
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
function composerMode(chrome: EditorChrome, text: string): ComposerMode {
|
|
74
|
+
if (chrome.isAwaitingApproval?.() ?? false) return "CONFIRM";
|
|
64
75
|
if (!(chrome.isStreaming?.() ?? false)) return "MESSAGE";
|
|
65
76
|
const willSteer = chrome.willEnterSteer?.(text) ?? text.trim().length > 0;
|
|
66
77
|
return willSteer ? "STEER" : "FOLLOW-UP";
|
|
@@ -73,12 +84,27 @@ function lowerRailHint(theme: ClioTheme, chrome: EditorChrome): string {
|
|
|
73
84
|
);
|
|
74
85
|
}
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
/**
|
|
88
|
+
* The permission keys on the composer rail, fitted like the dialog footer so
|
|
89
|
+
* both surfaces narrow in the same order and never drop allow or stop first.
|
|
90
|
+
* The rule spends three columns around a right label, hence the subtraction.
|
|
91
|
+
*/
|
|
92
|
+
function confirmRailHint(theme: ClioTheme, width: number, hasDraft: boolean): string {
|
|
93
|
+
return theme.fg("warning", fitHintEntries(permissionHintEntries(hasDraft), Math.max(1, width - 3)));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function modeToken(mode: ComposerMode): "action" | "accentDeep" | "warning" {
|
|
97
|
+
if (mode === "STEER") return "action";
|
|
98
|
+
if (mode === "CONFIRM") return "warning";
|
|
99
|
+
return "accentDeep";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function renderEmptyPrompt(line: string, width: number, theme: ClioTheme, text = EMPTY_PROMPT): string {
|
|
77
103
|
const cursorAt = line.indexOf(REVERSE_VIDEO_BLANK);
|
|
78
104
|
if (cursorAt < 0) return line;
|
|
79
105
|
const afterCursorAt = cursorAt + REVERSE_VIDEO_BLANK.length;
|
|
80
106
|
const available = Math.max(0, width - 1);
|
|
81
|
-
const prompt = truncateToWidth(theme.fg("dim",
|
|
107
|
+
const prompt = truncateToWidth(theme.fg("dim", text), available, "…", false);
|
|
82
108
|
const consumed = visibleWidth(prompt);
|
|
83
109
|
return `${line.slice(0, afterCursorAt)}${prompt}${line.slice(afterCursorAt + consumed)}`;
|
|
84
110
|
}
|
|
@@ -111,12 +137,12 @@ export class ClioEditor extends Editor {
|
|
|
111
137
|
// The base editor has already fitted the scroll indicator to this width.
|
|
112
138
|
// Prefix the mode and trim only the indicator rail's trailing fill, keeping
|
|
113
139
|
// its direction/count text and its own narrow-width fallback intact.
|
|
114
|
-
const modeLabel = theme.style(mode
|
|
140
|
+
const modeLabel = theme.style(modeToken(mode), mode, { bold: true });
|
|
115
141
|
lines[0] = truncateToWidth(`${modeLabel} ${lines[0] ?? ""}`, safeWidth, "", true);
|
|
116
142
|
} else {
|
|
117
143
|
lines[0] = rule(theme, safeWidth, {
|
|
118
144
|
left: mode,
|
|
119
|
-
leftToken: mode
|
|
145
|
+
leftToken: modeToken(mode),
|
|
120
146
|
right: styledRailLabel(theme, this.chrome),
|
|
121
147
|
fillToken: "frameStrong",
|
|
122
148
|
rightRaw: true,
|
|
@@ -124,10 +150,21 @@ export class ClioEditor extends Editor {
|
|
|
124
150
|
});
|
|
125
151
|
}
|
|
126
152
|
|
|
127
|
-
if (text.length === 0 && lines[1])
|
|
153
|
+
if (text.length === 0 && lines[1]) {
|
|
154
|
+
lines[1] = renderEmptyPrompt(lines[1], safeWidth, theme, mode === "CONFIRM" ? CONFIRM_PROMPT : EMPTY_PROMPT);
|
|
155
|
+
}
|
|
128
156
|
|
|
129
157
|
const bottomRail = findBottomRail(lines, safeWidth);
|
|
130
|
-
|
|
158
|
+
// The confirm keys render at every width: the send hint is a convenience
|
|
159
|
+
// that a narrow composer can drop, the allow and deny keys are not.
|
|
160
|
+
if (bottomRail >= 0 && mode === "CONFIRM") {
|
|
161
|
+
lines[bottomRail] = rule(theme, safeWidth, {
|
|
162
|
+
right: confirmRailHint(theme, safeWidth, text.length > 0),
|
|
163
|
+
fillToken: "frameStrong",
|
|
164
|
+
rightRaw: true,
|
|
165
|
+
rightTail: theme.style("frameStrong", "─", { bold: true }),
|
|
166
|
+
});
|
|
167
|
+
} else if (bottomRail >= 0 && safeWidth >= MIN_HINT_WIDTH) {
|
|
131
168
|
lines[bottomRail] = rule(theme, safeWidth, {
|
|
132
169
|
right: lowerRailHint(theme, this.chrome),
|
|
133
170
|
fillToken: "frameStrong",
|
|
@@ -3,6 +3,7 @@ import { relative } from "node:path";
|
|
|
3
3
|
import { BusChannels } from "../core/bus-events.js";
|
|
4
4
|
import type { SafeEventBus } from "../core/event-bus.js";
|
|
5
5
|
import type { WorkingSetView } from "../domains/context/working-set/contract.js";
|
|
6
|
+
import { formatContextWindowSlots } from "../domains/providers/index.js";
|
|
6
7
|
import type { ContextLedger, ContextLedgerGroup } from "../domains/session/context-ledger.js";
|
|
7
8
|
import { type OverlayHandle, Text, type TUI, visibleWidth } from "../engine/tui.js";
|
|
8
9
|
import { contextCategorySwatch, renderContextMeterGrid, renderEvictedTokensLine } from "./context-meter.js";
|
|
@@ -112,12 +113,34 @@ function coldReasonLabel(reason: string): string {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
/** The operator's working-set configuration, as `/context` states it. */
|
|
117
|
+
export interface WorkingSetConfigView {
|
|
118
|
+
enabled: boolean;
|
|
119
|
+
policy: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
115
122
|
/**
|
|
116
123
|
* The working-set section: what the projection has taken out of the window
|
|
117
124
|
* and how often the model has asked for it back. Churn is recalls over items
|
|
118
125
|
* evicted; a high number means the policy evicts what is still needed.
|
|
119
126
|
*/
|
|
120
|
-
|
|
127
|
+
/**
|
|
128
|
+
* The policy line names what is configured, not what last ran. `lastPolicyId`
|
|
129
|
+
* is stamped by the first applied event, and eviction runs only when pressure
|
|
130
|
+
* crosses `compaction.threshold`, so a whole session at the shipped default
|
|
131
|
+
* read `policy none` (issue #190). With no configuration in hand (older
|
|
132
|
+
* callers) the last applied policy is still the best available claim.
|
|
133
|
+
*/
|
|
134
|
+
function workingSetPolicyLabel(view: WorkingSetView, config: WorkingSetConfigView | null | undefined): string {
|
|
135
|
+
if (config === null || config === undefined) return `policy ${view.lastPolicyId ?? "none"}`;
|
|
136
|
+
if (!config.enabled) return "disabled";
|
|
137
|
+
const state = view.evictionEvents === 0 ? " · no events yet" : "";
|
|
138
|
+
const lastRan =
|
|
139
|
+
view.lastPolicyId !== null && view.lastPolicyId !== config.policy ? ` (last event by ${view.lastPolicyId})` : "";
|
|
140
|
+
return `policy ${config.policy}${lastRan}${state}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function renderWorkingSetLines(view: WorkingSetView, config: WorkingSetConfigView | null | undefined): string[] {
|
|
121
144
|
const theme = clioTheme();
|
|
122
145
|
const items = view.evicted.size;
|
|
123
146
|
const summary = [
|
|
@@ -128,7 +151,7 @@ function renderWorkingSetLines(view: WorkingSetView): string[] {
|
|
|
128
151
|
`churn ${formatChurn(view)}`,
|
|
129
152
|
].join(" · ");
|
|
130
153
|
return [
|
|
131
|
-
`${theme.fg("muted", "working set")} ${theme.fg("dim", "·")} ${theme.fg("accent",
|
|
154
|
+
`${theme.fg("muted", "working set")} ${theme.fg("dim", "·")} ${theme.fg("accent", workingSetPolicyLabel(view, config))}`,
|
|
132
155
|
theme.fg("dim", summary),
|
|
133
156
|
];
|
|
134
157
|
}
|
|
@@ -137,6 +160,7 @@ export function renderContextLedgerLines(
|
|
|
137
160
|
ledger: ContextLedger,
|
|
138
161
|
contentWidth: number,
|
|
139
162
|
workingSet?: WorkingSetView | null,
|
|
163
|
+
workingSetConfig?: WorkingSetConfigView | null,
|
|
140
164
|
): string[] {
|
|
141
165
|
const theme = clioTheme();
|
|
142
166
|
const lines: string[] = [];
|
|
@@ -154,7 +178,14 @@ export function renderContextLedgerLines(
|
|
|
154
178
|
|
|
155
179
|
if (ledger.contextWindow > 0) {
|
|
156
180
|
const source = ledger.measured ? "measured" : "≈ estimated";
|
|
157
|
-
|
|
181
|
+
// `196,608 (786,432 / 4 slots)`: a llama.cpp window that is one slot's
|
|
182
|
+
// share of the server's KV budget names the split, because the bare
|
|
183
|
+
// number reads as the whole server and the meter is measured against
|
|
184
|
+
// the share (issue #187).
|
|
185
|
+
const window = ledger.contextWindowSlots
|
|
186
|
+
? formatContextWindowSlots(ledger.contextWindow, ledger.contextWindowSlots)
|
|
187
|
+
: formatTokens(ledger.contextWindow);
|
|
188
|
+
const summary = `${formatTokens(ledger.usedTokens)} / ${window} tokens (${formatContextPercent(ledger.percent)})`;
|
|
158
189
|
const provenance = contextWindowProvenanceLabel(ledger.contextWindowSource);
|
|
159
190
|
const trailer = provenance ? `${provenance} window · ${source}` : source;
|
|
160
191
|
lines.push(`${theme.fg("title", summary)} ${theme.fg("dim", "·")} ${theme.fg("muted", trailer)}`);
|
|
@@ -170,7 +201,7 @@ export function renderContextLedgerLines(
|
|
|
170
201
|
|
|
171
202
|
lines.push("");
|
|
172
203
|
if (workingSet) {
|
|
173
|
-
for (const line of renderWorkingSetLines(workingSet)) lines.push(line);
|
|
204
|
+
for (const line of renderWorkingSetLines(workingSet, workingSetConfig)) lines.push(line);
|
|
174
205
|
lines.push("");
|
|
175
206
|
}
|
|
176
207
|
if (ledger.projectPreload && ledger.groups.some((group) => group.category === "project")) {
|
|
@@ -243,6 +274,8 @@ export interface OpenContextOverlayOptions {
|
|
|
243
274
|
};
|
|
244
275
|
/** Working-set fold at the live leaf; null or absent hides the section. */
|
|
245
276
|
getWorkingSet?: () => WorkingSetView | null;
|
|
277
|
+
/** The configured `context.workingSet` block, so the policy line states what is set. */
|
|
278
|
+
getWorkingSetConfig?: () => WorkingSetConfigView | null;
|
|
246
279
|
}
|
|
247
280
|
|
|
248
281
|
/**
|
|
@@ -257,7 +290,12 @@ export function openContextOverlay(
|
|
|
257
290
|
options?: OpenContextOverlayOptions,
|
|
258
291
|
): OverlayHandle {
|
|
259
292
|
const render = (): string =>
|
|
260
|
-
renderContextLedgerLines(
|
|
293
|
+
renderContextLedgerLines(
|
|
294
|
+
getLedger(),
|
|
295
|
+
DEFAULT_CONTENT_WIDTH,
|
|
296
|
+
options?.getWorkingSet?.() ?? null,
|
|
297
|
+
options?.getWorkingSetConfig?.() ?? null,
|
|
298
|
+
).join("\n");
|
|
261
299
|
const text = new Text(render(), 0, 0);
|
|
262
300
|
const handle = showClioOverlayFrame(tui, text, {
|
|
263
301
|
anchor: "center",
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addResponseModelIdObservationCounts,
|
|
3
|
+
type ResponseModelIdObservationCounts,
|
|
4
|
+
responseModelIdObservationCountsLabel,
|
|
5
|
+
} from "../core/response-model-id.js";
|
|
1
6
|
import {
|
|
2
7
|
aggregateCostAmounts,
|
|
3
8
|
type CostAggregate,
|
|
@@ -16,7 +21,9 @@ export const COST_OVERLAY_WIDTH = DEFAULT_CONTENT_WIDTH + 4;
|
|
|
16
21
|
|
|
17
22
|
export interface CostRow {
|
|
18
23
|
providerId: string;
|
|
19
|
-
|
|
24
|
+
attributedModelId: string;
|
|
25
|
+
requestedModelIds: string[];
|
|
26
|
+
responseModelIdObservationCounts: ResponseModelIdObservationCounts;
|
|
20
27
|
runs: number;
|
|
21
28
|
tokens: number;
|
|
22
29
|
input: number;
|
|
@@ -25,6 +32,10 @@ export interface CostRow {
|
|
|
25
32
|
cacheWrite: number;
|
|
26
33
|
reasoningTokens: number;
|
|
27
34
|
apiCalls: number;
|
|
35
|
+
/** Calls in this row that were `/btw` side questions rather than turns. */
|
|
36
|
+
sideQuestions: number;
|
|
37
|
+
/** Calls in this row that were `/handoff` extraction rounds rather than turns. */
|
|
38
|
+
handoffs: number;
|
|
28
39
|
cost: CostAggregate;
|
|
29
40
|
}
|
|
30
41
|
|
|
@@ -33,9 +44,17 @@ function formatTokens(n: number): string {
|
|
|
33
44
|
}
|
|
34
45
|
|
|
35
46
|
export function aggregateCostEntries(entries: ReadonlyArray<CostEntry>): CostRow[] {
|
|
36
|
-
const grouped = new Map<
|
|
47
|
+
const grouped = new Map<
|
|
48
|
+
string,
|
|
49
|
+
{
|
|
50
|
+
row: Omit<CostRow, "cost" | "requestedModelIds" | "responseModelIdObservationCounts">;
|
|
51
|
+
requestedModelIds: Set<string>;
|
|
52
|
+
responseModelIdObservationCounts: ResponseModelIdObservationCounts;
|
|
53
|
+
entries: CostEntry[];
|
|
54
|
+
}
|
|
55
|
+
>();
|
|
37
56
|
for (const entry of entries) {
|
|
38
|
-
const key = `${entry.providerId}::${entry.
|
|
57
|
+
const key = `${entry.providerId}::${entry.attributedModelId}`;
|
|
39
58
|
const existing = grouped.get(key);
|
|
40
59
|
if (existing) {
|
|
41
60
|
existing.row.runs += 1;
|
|
@@ -46,13 +65,20 @@ export function aggregateCostEntries(entries: ReadonlyArray<CostEntry>): CostRow
|
|
|
46
65
|
existing.row.cacheWrite += entry.cacheWrite;
|
|
47
66
|
existing.row.reasoningTokens += entry.reasoningTokens;
|
|
48
67
|
existing.row.apiCalls += entry.apiCalls ?? 1;
|
|
68
|
+
if (entry.label === "side-question") existing.row.sideQuestions += 1;
|
|
69
|
+
if (entry.label === "handoff") existing.row.handoffs += 1;
|
|
70
|
+
for (const requestedModelId of entry.requestedModelIds) existing.requestedModelIds.add(requestedModelId);
|
|
71
|
+
addResponseModelIdObservationCounts(
|
|
72
|
+
existing.responseModelIdObservationCounts,
|
|
73
|
+
entry.responseModelIdObservationCounts,
|
|
74
|
+
);
|
|
49
75
|
existing.entries.push(entry);
|
|
50
76
|
continue;
|
|
51
77
|
}
|
|
52
78
|
grouped.set(key, {
|
|
53
79
|
row: {
|
|
54
80
|
providerId: entry.providerId,
|
|
55
|
-
|
|
81
|
+
attributedModelId: entry.attributedModelId,
|
|
56
82
|
runs: 1,
|
|
57
83
|
tokens: entry.tokens,
|
|
58
84
|
input: entry.input,
|
|
@@ -61,23 +87,34 @@ export function aggregateCostEntries(entries: ReadonlyArray<CostEntry>): CostRow
|
|
|
61
87
|
cacheWrite: entry.cacheWrite,
|
|
62
88
|
reasoningTokens: entry.reasoningTokens,
|
|
63
89
|
apiCalls: entry.apiCalls ?? 1,
|
|
90
|
+
sideQuestions: entry.label === "side-question" ? 1 : 0,
|
|
91
|
+
handoffs: entry.label === "handoff" ? 1 : 0,
|
|
64
92
|
},
|
|
93
|
+
requestedModelIds: new Set(entry.requestedModelIds),
|
|
94
|
+
responseModelIdObservationCounts: { ...entry.responseModelIdObservationCounts },
|
|
65
95
|
entries: [entry],
|
|
66
96
|
});
|
|
67
97
|
}
|
|
68
|
-
const rows = Array.from(grouped.values(), ({ row, entries }) => ({
|
|
98
|
+
const rows = Array.from(grouped.values(), ({ row, entries, requestedModelIds, responseModelIdObservationCounts }) => ({
|
|
69
99
|
...row,
|
|
100
|
+
requestedModelIds: [...requestedModelIds].sort(),
|
|
101
|
+
responseModelIdObservationCounts,
|
|
70
102
|
cost: aggregateCostAmounts(entries.map((entry) => ({ usd: entry.usd, provenance: entry.provenance }))),
|
|
71
103
|
}));
|
|
72
104
|
rows.sort((a, b) => {
|
|
73
105
|
if (a.providerId !== b.providerId) return a.providerId < b.providerId ? -1 : 1;
|
|
74
|
-
if (a.
|
|
106
|
+
if (a.attributedModelId !== b.attributedModelId) return a.attributedModelId < b.attributedModelId ? -1 : 1;
|
|
75
107
|
return 0;
|
|
76
108
|
});
|
|
77
109
|
return rows;
|
|
78
110
|
}
|
|
79
111
|
|
|
80
|
-
function sumRows(
|
|
112
|
+
function sumRows(
|
|
113
|
+
rows: ReadonlyArray<CostRow>,
|
|
114
|
+
): Omit<
|
|
115
|
+
CostRow,
|
|
116
|
+
"providerId" | "attributedModelId" | "requestedModelIds" | "responseModelIdObservationCounts" | "cost"
|
|
117
|
+
> {
|
|
81
118
|
return rows.reduce(
|
|
82
119
|
(acc, row) => ({
|
|
83
120
|
runs: acc.runs + row.runs,
|
|
@@ -88,8 +125,21 @@ function sumRows(rows: ReadonlyArray<CostRow>): Omit<CostRow, "providerId" | "mo
|
|
|
88
125
|
cacheWrite: acc.cacheWrite + row.cacheWrite,
|
|
89
126
|
reasoningTokens: acc.reasoningTokens + row.reasoningTokens,
|
|
90
127
|
apiCalls: acc.apiCalls + row.apiCalls,
|
|
128
|
+
sideQuestions: acc.sideQuestions + row.sideQuestions,
|
|
129
|
+
handoffs: acc.handoffs + row.handoffs,
|
|
91
130
|
}),
|
|
92
|
-
{
|
|
131
|
+
{
|
|
132
|
+
runs: 0,
|
|
133
|
+
tokens: 0,
|
|
134
|
+
input: 0,
|
|
135
|
+
output: 0,
|
|
136
|
+
cacheRead: 0,
|
|
137
|
+
cacheWrite: 0,
|
|
138
|
+
reasoningTokens: 0,
|
|
139
|
+
apiCalls: 0,
|
|
140
|
+
sideQuestions: 0,
|
|
141
|
+
handoffs: 0,
|
|
142
|
+
},
|
|
93
143
|
);
|
|
94
144
|
}
|
|
95
145
|
|
|
@@ -137,9 +187,14 @@ function summaryBlock(totalCost: CostAggregate, totalTokens: number, rows: Reado
|
|
|
137
187
|
// unknown` it printed for a target that reports no pricing. The token rows
|
|
138
188
|
// below are measured and stay either way. See formatCostAggregate.
|
|
139
189
|
const cost = formatCostAggregate(totalCost);
|
|
190
|
+
// A side question is billed like anything else and is counted here, but it is
|
|
191
|
+
// deliberately not a turn: it never entered the session, so `turns` above
|
|
192
|
+
// excludes it and this row says how much of the spend sat beside the session.
|
|
140
193
|
return kvBlock([
|
|
141
|
-
["turns", formatTokens(totals.runs)],
|
|
194
|
+
["turns", formatTokens(totals.runs - totals.sideQuestions - totals.handoffs)],
|
|
142
195
|
["model calls", formatTokens(totals.apiCalls)],
|
|
196
|
+
...(totals.sideQuestions > 0 ? [["side questions", formatTokens(totals.sideQuestions)] as const] : []),
|
|
197
|
+
...(totals.handoffs > 0 ? [["handoffs", formatTokens(totals.handoffs)] as const] : []),
|
|
143
198
|
...(cost === null ? [] : [["cost", cost] as const]),
|
|
144
199
|
["input", formatTokens(totals.input)],
|
|
145
200
|
["output", formatTokens(totals.output)],
|
|
@@ -156,8 +211,12 @@ function modelBlock(row: CostRow): string[] {
|
|
|
156
211
|
// block with token rows and no cost row.
|
|
157
212
|
const cost = formatCostAggregate(row.cost);
|
|
158
213
|
return kvBlock([
|
|
159
|
-
["
|
|
214
|
+
["requested model ids", row.requestedModelIds.join(", ")],
|
|
215
|
+
["response model id observation", responseModelIdObservationCountsLabel(row.responseModelIdObservationCounts)],
|
|
216
|
+
["turns", formatTokens(row.runs - row.sideQuestions - row.handoffs)],
|
|
160
217
|
["model calls", formatTokens(row.apiCalls)],
|
|
218
|
+
...(row.sideQuestions > 0 ? [["side questions", formatTokens(row.sideQuestions)] as const] : []),
|
|
219
|
+
...(row.handoffs > 0 ? [["handoffs", formatTokens(row.handoffs)] as const] : []),
|
|
161
220
|
...(cost === null ? [] : [["cost", cost] as const]),
|
|
162
221
|
["input", formatTokens(row.input)],
|
|
163
222
|
["output", formatTokens(row.output)],
|
|
@@ -185,7 +244,7 @@ export function formatCostOverlayBodyLines(
|
|
|
185
244
|
} else {
|
|
186
245
|
for (const [index, row] of rows.entries()) {
|
|
187
246
|
if (index > 0) lines.push("");
|
|
188
|
-
lines.push(theme.style("accent", `${row.providerId} · ${row.
|
|
247
|
+
lines.push(theme.style("accent", `${row.providerId} · attributed model ${row.attributedModelId}`, { bold: true }));
|
|
189
248
|
for (const line of modelBlock(row)) {
|
|
190
249
|
lines.push(line);
|
|
191
250
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one path `/council` takes into execution: the dispatch tool, admitted by
|
|
3
|
+
* the ordinary tool registry.
|
|
4
|
+
*
|
|
5
|
+
* The registry is where classification, safety, autonomy, middleware, and the
|
|
6
|
+
* operator approval park all live. A council started from the composer is a
|
|
7
|
+
* plan-scale dispatch exactly as a council the model asks for is, so it is
|
|
8
|
+
* admitted the same way and parks the same approval overlay. Nothing here
|
|
9
|
+
* builds a request, resolves a route, or touches the dispatch domain.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { ToolNames } from "../core/tool-names.js";
|
|
13
|
+
import type { ToolRegistry } from "../tools/registry.js";
|
|
14
|
+
import type { CouncilDispatchOutcome } from "./slash-commands.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Invoke the dispatch tool with prepared council arguments and report the
|
|
18
|
+
* verdict in the command's own vocabulary. A tool that is not visible in this
|
|
19
|
+
* posture and a call the operator declined are both reported as what they are,
|
|
20
|
+
* because a council that never ran must never read as one that did.
|
|
21
|
+
*/
|
|
22
|
+
export async function dispatchCouncilThroughRegistry(
|
|
23
|
+
registry: Pick<ToolRegistry, "invoke">,
|
|
24
|
+
args: Readonly<Record<string, unknown>>,
|
|
25
|
+
): Promise<CouncilDispatchOutcome> {
|
|
26
|
+
const verdict = await registry.invoke({ tool: ToolNames.Dispatch, args: { ...args } });
|
|
27
|
+
if (verdict.kind === "blocked") return { status: "blocked", reason: verdict.reason };
|
|
28
|
+
if (verdict.kind === "not_visible") return { status: "error", message: verdict.reason };
|
|
29
|
+
return verdict.result.kind === "error" ? { status: "error", message: verdict.result.message } : { status: "ok" };
|
|
30
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a council reads on the Fleet Runs board.
|
|
3
|
+
*
|
|
4
|
+
* A council is one question asked of several members at once, so its runs are
|
|
5
|
+
* one thing on the screen rather than three to five unrelated cards. Members
|
|
6
|
+
* sit side by side while every column keeps enough width to read an answer in,
|
|
7
|
+
* and stack one under another when they do not. The synthesis run is the whole
|
|
8
|
+
* council's answer, so it takes the full width under the members rather than a
|
|
9
|
+
* column of its own.
|
|
10
|
+
*
|
|
11
|
+
* Pure presentation: the caller projects board rows into these views, and this
|
|
12
|
+
* module owns only layout, color, and text. It knows nothing about the board
|
|
13
|
+
* store, the bus, or run status semantics.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { truncateToWidth, wrapTextWithAnsi } from "../engine/tui.js";
|
|
17
|
+
import {
|
|
18
|
+
type ClioTheme,
|
|
19
|
+
type ClioToken,
|
|
20
|
+
dotSep,
|
|
21
|
+
GLYPH,
|
|
22
|
+
innerDivider,
|
|
23
|
+
isClioToken,
|
|
24
|
+
padAnsi,
|
|
25
|
+
paintHex,
|
|
26
|
+
} from "./theme/index.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The narrowest column an answer is still worth reading in. Below it the grid
|
|
30
|
+
* becomes a set of one-word columns, so the group stacks instead: a member's
|
|
31
|
+
* answer is the reason the operator opened the board.
|
|
32
|
+
*/
|
|
33
|
+
export const COUNCIL_COLUMN_MIN_WIDTH = 34;
|
|
34
|
+
|
|
35
|
+
/** Blank columns between two side-by-side member columns. */
|
|
36
|
+
export const COUNCIL_COLUMN_GUTTER = 2;
|
|
37
|
+
|
|
38
|
+
/** Rows of a member's bounded answer tail a column shows before it defers to `/view`. */
|
|
39
|
+
export const COUNCIL_ANSWER_ROWS = 4;
|
|
40
|
+
|
|
41
|
+
/** The label the coordinator gives the synthesis run of every council group. */
|
|
42
|
+
export const COUNCIL_SYNTHESIS_LABEL = "synthesis";
|
|
43
|
+
|
|
44
|
+
/** One council run as the board draws it: the roster facts plus the run's presentation. */
|
|
45
|
+
export interface CouncilMemberView {
|
|
46
|
+
runId: string;
|
|
47
|
+
label: string;
|
|
48
|
+
/** Roster color: a theme token name or a `#rrggbb` value. Absent members take the accent. */
|
|
49
|
+
color?: string;
|
|
50
|
+
round: number;
|
|
51
|
+
/** Route as the card names it, such as `local/example-model`. */
|
|
52
|
+
route: string;
|
|
53
|
+
status: { glyph: string; label: string; token: ClioToken };
|
|
54
|
+
/** Bounded answer tail the board already keeps for this run. */
|
|
55
|
+
tailText: string;
|
|
56
|
+
droppedLines: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface CouncilGroupView {
|
|
60
|
+
group: string;
|
|
61
|
+
/** Final-round member runs, in roster order. */
|
|
62
|
+
members: ReadonlyArray<CouncilMemberView>;
|
|
63
|
+
/** The council's own answer, or null while the members are still running. */
|
|
64
|
+
synthesis: CouncilMemberView | null;
|
|
65
|
+
/** Aggregate status of the group, used by the compact card. */
|
|
66
|
+
status: { glyph: string; label: string; token: ClioToken };
|
|
67
|
+
/** Highest round any member has reached. */
|
|
68
|
+
round: number;
|
|
69
|
+
/** Elapsed time of the longest-running member, formatted by the caller. */
|
|
70
|
+
elapsed: string;
|
|
71
|
+
/** Run id of the selected row when the selection sits inside this group. */
|
|
72
|
+
selectedRunId?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type CouncilLayout = { mode: "grid"; columnWidth: number } | { mode: "stack"; columnWidth: number };
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Grid or stack for this many members at this content width. Columns divide the
|
|
79
|
+
* width evenly after the gutters are taken out, and the whole group stacks the
|
|
80
|
+
* moment one column would fall under {@link COUNCIL_COLUMN_MIN_WIDTH}: a grid
|
|
81
|
+
* where only some columns are readable is worse than no grid at all.
|
|
82
|
+
*/
|
|
83
|
+
export function councilGridLayout(memberCount: number, contentWidth: number): CouncilLayout {
|
|
84
|
+
const count = Math.max(1, memberCount);
|
|
85
|
+
const usable = contentWidth - COUNCIL_COLUMN_GUTTER * (count - 1);
|
|
86
|
+
const columnWidth = Math.floor(usable / count);
|
|
87
|
+
if (count > 1 && columnWidth >= COUNCIL_COLUMN_MIN_WIDTH) return { mode: "grid", columnWidth };
|
|
88
|
+
return { mode: "stack", columnWidth: Math.max(1, contentWidth) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A member's label in its roster color. A theme token name is painted as that
|
|
93
|
+
* token; a `#rrggbb` value is painted literally; anything else, including a
|
|
94
|
+
* member with no color at all, takes the accent, which is what the board paints
|
|
95
|
+
* the operator's own work in.
|
|
96
|
+
*/
|
|
97
|
+
function councilLabelText(theme: ClioTheme, label: string, color?: string): string {
|
|
98
|
+
if (color !== undefined && isClioToken(color)) return theme.style(color, label, { bold: true });
|
|
99
|
+
if (color !== undefined) {
|
|
100
|
+
const painted = paintHex(label, color);
|
|
101
|
+
if (painted !== label) return painted;
|
|
102
|
+
}
|
|
103
|
+
return theme.style("accent", label, { bold: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The answer tail on a rail, wrapped to the column and bounded to {@link COUNCIL_ANSWER_ROWS} rows. */
|
|
107
|
+
function answerRows(theme: ClioTheme, member: CouncilMemberView, width: number, maxRows: number): string[] {
|
|
108
|
+
if (member.tailText.trim().length === 0) return [];
|
|
109
|
+
const railWidth = Math.max(1, width - 2);
|
|
110
|
+
const wrapped: string[] = [];
|
|
111
|
+
for (const line of member.tailText.split("\n")) {
|
|
112
|
+
for (const row of wrapTextWithAnsi(line, railWidth)) wrapped.push(row);
|
|
113
|
+
}
|
|
114
|
+
const shown = wrapped.slice(Math.max(0, wrapped.length - maxRows));
|
|
115
|
+
const hidden = wrapped.length - shown.length + member.droppedLines;
|
|
116
|
+
const rail = theme.fg("dim", `${GLYPH.rail} `);
|
|
117
|
+
const rows = shown.map((row) => `${rail}${theme.fg("muted", row)}`);
|
|
118
|
+
if (hidden > 0) {
|
|
119
|
+
rows.push(`${rail}${theme.fg("dim", truncateToWidth(`${hidden} more`, railWidth, "…", false))}`);
|
|
120
|
+
}
|
|
121
|
+
return rows;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* One member column: the label, the route, the round and status, then the
|
|
126
|
+
* answer. Every line is clipped to the column so a long model id or a long
|
|
127
|
+
* answer line can never push a neighbouring column out of position.
|
|
128
|
+
*/
|
|
129
|
+
export function councilMemberLines(
|
|
130
|
+
theme: ClioTheme,
|
|
131
|
+
member: CouncilMemberView,
|
|
132
|
+
width: number,
|
|
133
|
+
options: { selected?: boolean; maxAnswerRows?: number } = {},
|
|
134
|
+
): string[] {
|
|
135
|
+
const dot = dotSep(theme);
|
|
136
|
+
const cursor = options.selected === true ? `${theme.fg("accent", GLYPH.cursor)} ` : "";
|
|
137
|
+
const status = theme.fg(member.status.token, `${member.status.glyph} ${member.status.label}`);
|
|
138
|
+
const head = `${cursor}${councilLabelText(theme, member.label, member.color)}`;
|
|
139
|
+
const lines = [
|
|
140
|
+
truncateToWidth(head, width, "…", false),
|
|
141
|
+
truncateToWidth(theme.fg("muted", member.route), width, "…", false),
|
|
142
|
+
truncateToWidth(`${status}${dot}${theme.fg("dim", `r${member.round}`)}`, width, "…", false),
|
|
143
|
+
...answerRows(theme, member, width, options.maxAnswerRows ?? COUNCIL_ANSWER_ROWS),
|
|
144
|
+
];
|
|
145
|
+
return lines.map((line) => padAnsi(line, width));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Zip member columns into rows, padding short columns so the grid keeps its shape. */
|
|
149
|
+
function zipColumns(columns: ReadonlyArray<ReadonlyArray<string>>, columnWidth: number): string[] {
|
|
150
|
+
const height = columns.reduce((tallest, column) => Math.max(tallest, column.length), 0);
|
|
151
|
+
const gutter = " ".repeat(COUNCIL_COLUMN_GUTTER);
|
|
152
|
+
const rows: string[] = [];
|
|
153
|
+
for (let index = 0; index < height; index += 1) {
|
|
154
|
+
rows.push(columns.map((column) => column[index] ?? " ".repeat(columnWidth)).join(gutter));
|
|
155
|
+
}
|
|
156
|
+
return rows;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The council group's body: the member grid or stack, then the synthesis run
|
|
161
|
+
* full width under it. The caller frames the result.
|
|
162
|
+
*/
|
|
163
|
+
export function councilGroupBody(theme: ClioTheme, group: CouncilGroupView, contentWidth: number): string[] {
|
|
164
|
+
const layout = councilGridLayout(group.members.length, contentWidth);
|
|
165
|
+
const body: string[] = [];
|
|
166
|
+
if (layout.mode === "grid") {
|
|
167
|
+
const columns = group.members.map((member) =>
|
|
168
|
+
councilMemberLines(theme, member, layout.columnWidth, {
|
|
169
|
+
selected: member.runId === group.selectedRunId,
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
body.push(...zipColumns(columns, layout.columnWidth));
|
|
173
|
+
} else {
|
|
174
|
+
for (const member of group.members) {
|
|
175
|
+
if (body.length > 0) body.push(innerDivider(theme, contentWidth));
|
|
176
|
+
body.push(
|
|
177
|
+
...councilMemberLines(theme, member, layout.columnWidth, {
|
|
178
|
+
selected: member.runId === group.selectedRunId,
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (group.synthesis !== null) {
|
|
184
|
+
body.push(innerDivider(theme, contentWidth));
|
|
185
|
+
body.push(
|
|
186
|
+
...councilMemberLines(theme, group.synthesis, contentWidth, {
|
|
187
|
+
selected: group.synthesis.runId === group.selectedRunId,
|
|
188
|
+
maxAnswerRows: COUNCIL_ANSWER_ROWS + 2,
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
return body;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The compact Fleet Runs island card for a council: one row for the whole
|
|
197
|
+
* group, naming how many members are seated and which round they are on. The
|
|
198
|
+
* grid belongs to the expanded board, where there is width to read it in.
|
|
199
|
+
*/
|
|
200
|
+
export function councilIslandLines(theme: ClioTheme, group: CouncilGroupView, width: number): string[] {
|
|
201
|
+
const dot = dotSep(theme);
|
|
202
|
+
const glyph = theme.fg(group.status.token, group.status.glyph);
|
|
203
|
+
const status = theme.fg(group.status.token, group.status.label);
|
|
204
|
+
const title = theme.paint(`council ${group.group}`, { bold: true });
|
|
205
|
+
const head = `${glyph} ${title}${dot}${status}${dot}${theme.fg("muted", group.elapsed)}`;
|
|
206
|
+
const seats = `${group.members.length} member${group.members.length === 1 ? "" : "s"}`;
|
|
207
|
+
const facts = [
|
|
208
|
+
theme.fg("muted", seats),
|
|
209
|
+
theme.fg("dim", `r${group.round}`),
|
|
210
|
+
...(group.synthesis !== null ? [theme.fg("info", COUNCIL_SYNTHESIS_LABEL)] : []),
|
|
211
|
+
].join(dot);
|
|
212
|
+
return [padAnsi(truncateToWidth(head, width, "…", false), width), padAnsi(` ${facts}`, width)];
|
|
213
|
+
}
|