@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,25 +1,37 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { ActionClass } from "../domains/safety/action-classifier.js";
|
|
2
|
+
import type { AutonomyExposure } from "../domains/safety/autonomy.js";
|
|
3
|
+
import {
|
|
4
|
+
classifyDecisionPresentation,
|
|
5
|
+
type DecisionPresentation,
|
|
6
|
+
decisionFactsForPermission,
|
|
7
|
+
} from "../domains/safety/decision-presentation.js";
|
|
8
|
+
import type { Component, OverlayOptions, TUI } from "../engine/tui.js";
|
|
9
|
+
import { fitHintEntries } from "./overlay-frame.js";
|
|
10
|
+
import { permissionHintEntries } from "./permission-hint.js";
|
|
11
|
+
import type { ClioToken } from "./theme/index.js";
|
|
3
12
|
|
|
4
13
|
export { type AskAxis, askAxis } from "../domains/safety/approval-axis.js";
|
|
5
14
|
export { describeCallTarget, sanitizeCallTargetText } from "../domains/safety/call-target.js";
|
|
15
|
+
export { permissionHintEntries } from "./permission-hint.js";
|
|
6
16
|
|
|
7
17
|
export interface ApprovalRequestView {
|
|
8
18
|
requestId: string;
|
|
9
19
|
tool: string;
|
|
10
|
-
actionClass:
|
|
20
|
+
actionClass: ActionClass;
|
|
11
21
|
axis: { kind: "net"; ruleId: string } | { kind: "autonomy"; level: string };
|
|
12
22
|
origin: { kind: "main" } | { kind: "worker"; agentId: string; runId: string };
|
|
23
|
+
/** Admission-normalized exposure. Caller prose never supplies presentation fields. */
|
|
24
|
+
exposure?: AutonomyExposure;
|
|
13
25
|
reason: string;
|
|
14
26
|
/** Typed, sanitized multi-line artifact that this one approval authorizes. */
|
|
15
27
|
artifact?: { kind: "dispatch-plan"; text: string };
|
|
16
28
|
/**
|
|
17
|
-
* One-line preview of the call's object
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
29
|
+
* One-line preview of the call's allowlisted object fields. The operator is
|
|
30
|
+
* deciding whether to allow this exact call, so the overlay must show what
|
|
31
|
+
* the call will touch, not just the tool name. Unlisted fields appear only as
|
|
32
|
+
* type-and-size summaries. Main-agent asks derive it from the parked call's
|
|
33
|
+
* args; worker escalations carry it in the escalation payload. Absent only
|
|
34
|
+
* when nothing meaningful is derivable.
|
|
23
35
|
*/
|
|
24
36
|
target?: string;
|
|
25
37
|
queueDepth?: number;
|
|
@@ -29,6 +41,44 @@ const PERMISSION_OVERLAY_CONTENT_WIDTH = 78;
|
|
|
29
41
|
|
|
30
42
|
export const PERMISSION_OVERLAY_WIDTH = PERMISSION_OVERLAY_CONTENT_WIDTH + 4;
|
|
31
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Place a permission frame immediately above the live composer in both TUI
|
|
46
|
+
* modes. The editor grows with wrapped and multiline drafts, while the footer
|
|
47
|
+
* can add notice rows, so their rendered heights are the bottom dock rather
|
|
48
|
+
* than a fixed clearance. Regular mode preserves terminal scrollback and may
|
|
49
|
+
* leave that dock above the viewport bottom when the transcript is short.
|
|
50
|
+
* Recompute its viewport row on every frame so draft changes and terminal
|
|
51
|
+
* resizes move the dialog with the composer.
|
|
52
|
+
*/
|
|
53
|
+
export function permissionOverlayPlacement(
|
|
54
|
+
tui: Pick<TUI, "mode" | "render">,
|
|
55
|
+
editor: Pick<Component, "render">,
|
|
56
|
+
footer: Pick<Component, "render">,
|
|
57
|
+
): OverlayOptions {
|
|
58
|
+
const margin = { bottom: 0 };
|
|
59
|
+
return {
|
|
60
|
+
anchor: "bottom-center",
|
|
61
|
+
margin,
|
|
62
|
+
visible: (termWidth, termHeight) => {
|
|
63
|
+
const dockHeight = editor.render(termWidth).length + footer.render(termWidth).length;
|
|
64
|
+
if (tui.mode === "fullscreen") {
|
|
65
|
+
margin.bottom = dockHeight;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const baseHeight = tui.render(termWidth).length;
|
|
70
|
+
const composerTop = Math.max(0, baseHeight - dockHeight);
|
|
71
|
+
const viewportStart = Math.max(0, baseHeight - termHeight);
|
|
72
|
+
const composerViewportRow = composerTop - viewportStart;
|
|
73
|
+
margin.bottom =
|
|
74
|
+
composerViewportRow >= 0 && composerViewportRow < termHeight
|
|
75
|
+
? Math.max(0, termHeight - composerViewportRow)
|
|
76
|
+
: dockHeight;
|
|
77
|
+
return true;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
32
82
|
class PermissionOverlayBody implements Component {
|
|
33
83
|
constructor(private readonly view: ApprovalRequestView) {}
|
|
34
84
|
|
|
@@ -89,46 +139,41 @@ function wrapArtifactLine(value: string, max: number): string[] {
|
|
|
89
139
|
return lines;
|
|
90
140
|
}
|
|
91
141
|
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
142
|
+
export function permissionDecisionPresentation(view: ApprovalRequestView): DecisionPresentation {
|
|
143
|
+
return classifyDecisionPresentation(
|
|
144
|
+
decisionFactsForPermission({
|
|
145
|
+
tool: view.tool,
|
|
146
|
+
actionClass: view.actionClass,
|
|
147
|
+
axis:
|
|
148
|
+
view.axis.kind === "net"
|
|
149
|
+
? { kind: "safety-net", ruleId: view.axis.ruleId }
|
|
150
|
+
: { kind: "autonomy", level: view.axis.level },
|
|
151
|
+
origin: view.origin,
|
|
152
|
+
...(view.exposure !== undefined ? { exposure: view.exposure } : {}),
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
95
155
|
}
|
|
96
156
|
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
return `worker ${view.origin.agentId} (run ${view.origin.runId}), ${axisLabel(view.axis, "worker")}`;
|
|
100
|
-
}
|
|
101
|
-
return axisLabel(view.axis, "main");
|
|
157
|
+
export function permissionOverlayTitle(view: ApprovalRequestView): string {
|
|
158
|
+
return permissionDecisionPresentation(view).title;
|
|
102
159
|
}
|
|
103
160
|
|
|
104
|
-
export function
|
|
105
|
-
return
|
|
161
|
+
export function permissionOverlayTone(view: ApprovalRequestView): ClioToken {
|
|
162
|
+
return permissionDecisionPresentation(view).semanticToken;
|
|
106
163
|
}
|
|
107
164
|
|
|
108
165
|
/**
|
|
109
|
-
* The footer for a box `innerWidth` columns wide inside its borders.
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* "allow once" and an ambiguous "close" in front of an operator trying to
|
|
113
|
-
* refuse. The key kept working, so the layout was hiding a live safety action.
|
|
114
|
-
* This surface used to answer that with three hand-written width tiers; it now
|
|
115
|
-
* states the same policy as data. Allow and stop are marked critical, Esc is
|
|
116
|
-
* marked droppable, and `fitHintEntries` shortens every label before it drops
|
|
117
|
-
* anything, which reproduces the three tiers exactly.
|
|
166
|
+
* The footer for a box `innerWidth` columns wide inside its borders. The
|
|
167
|
+
* three columns the bottom border spends on `─ ` and the trailing space come
|
|
168
|
+
* off the top, which is what `buildResponsiveHint` does for every other frame.
|
|
118
169
|
*/
|
|
119
|
-
export
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const SAFETY_SENTENCES: ReadonlyArray<string> = [
|
|
128
|
-
"Parked until you decide; allow or deny applies to this call only.",
|
|
129
|
-
"Stopping the turn denies it and ends the run, so nothing asks again.",
|
|
130
|
-
"Hard-blocked actions remain blocked.",
|
|
131
|
-
];
|
|
170
|
+
export function permissionOverlayHint(innerWidth: number, composerHasDraft = false): string {
|
|
171
|
+
return fitHintEntries(permissionHintEntries(composerHasDraft), innerWidth - 3);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function actionConsequence(presentation: DecisionPresentation, id: "deny" | "stop"): string {
|
|
175
|
+
return presentation.requiredActions.find((action) => action.id === id)?.consequence ?? "";
|
|
176
|
+
}
|
|
132
177
|
|
|
133
178
|
/**
|
|
134
179
|
* The overlay's body, laid out for the width the frame gives it.
|
|
@@ -140,15 +185,15 @@ const SAFETY_SENTENCES: ReadonlyArray<string> = [
|
|
|
140
185
|
*/
|
|
141
186
|
export function permissionOverlayLines(view: ApprovalRequestView, width: number): string[] {
|
|
142
187
|
const content = Math.max(8, Math.floor(width));
|
|
188
|
+
const presentation = permissionDecisionPresentation(view);
|
|
143
189
|
// The parked call is awaiting a decision, not blocked: the raw rejection
|
|
144
190
|
// short ("<tool> blocked: <class>") is never rendered here because its
|
|
145
191
|
// wording contradicts the ask. Tool, Target, Action, and the asking axis
|
|
146
192
|
// carry everything the operator needs to decide.
|
|
147
193
|
const lines = [
|
|
148
|
-
field("Tool: ", view.tool
|
|
194
|
+
field("Tool: ", `${view.tool} · Action: ${view.actionClass}`, content),
|
|
149
195
|
...(view.target !== undefined && view.target.length > 0 ? [field("Target: ", view.target, content)] : []),
|
|
150
|
-
|
|
151
|
-
field("Asked by: ", askedBy(view), content),
|
|
196
|
+
...wrapSentence(`Requested by: ${presentation.requestedByCopy}`, content),
|
|
152
197
|
...(view.artifact !== undefined
|
|
153
198
|
? [
|
|
154
199
|
"",
|
|
@@ -157,7 +202,12 @@ export function permissionOverlayLines(view: ApprovalRequestView, width: number)
|
|
|
157
202
|
]
|
|
158
203
|
: []),
|
|
159
204
|
"",
|
|
160
|
-
...
|
|
205
|
+
...wrapSentence(`Approval: ${presentation.authorizationCopy}`, content),
|
|
206
|
+
...wrapSentence(`Consequence: ${presentation.consequenceCopy}`, content),
|
|
207
|
+
...wrapSentence(presentation.reversibilityCopy, content),
|
|
208
|
+
...wrapSentence(`Deny: ${actionConsequence(presentation, "deny")}`, content),
|
|
209
|
+
...wrapSentence(`Stop: ${actionConsequence(presentation, "stop")}`, content),
|
|
210
|
+
...wrapSentence("Hard-blocked actions remain blocked.", content),
|
|
161
211
|
];
|
|
162
212
|
if (view.queueDepth !== undefined && view.queueDepth > 1) {
|
|
163
213
|
lines.splice(lines.indexOf(""), 0, `1 of ${view.queueDepth} parked`);
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { sanitizeCallTargetText } from "../../domains/safety/call-target.js";
|
|
16
|
+
import { redactSecretString, redactToolArgs } from "../../domains/safety/redaction.js";
|
|
16
17
|
import { formatSize } from "../../engine/truncate.js";
|
|
17
18
|
import { visibleWidth, wrapTextWithAnsi } from "../../engine/tui.js";
|
|
18
19
|
import { classifyResourceRead, toolPresentationPolicy } from "../../tools/presentation.js";
|
|
@@ -22,7 +23,10 @@ import { clioTheme, formatCompactMs, GLYPH } from "../theme/index.js";
|
|
|
22
23
|
import { renderDiffLines } from "./diff.js";
|
|
23
24
|
import { tryRenderJson, tryRenderXml } from "./structured.js";
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
// The argument projection lives in the safety domain so the worker tool seam
|
|
27
|
+
// scrubs by the same rules; re-exported here because this renderer is where
|
|
28
|
+
// callers and tests have always reached for it.
|
|
29
|
+
export { classifyResourceRead, redactToolArgs };
|
|
26
30
|
|
|
27
31
|
const theme = clioTheme();
|
|
28
32
|
const dim = (text: string): string => theme.fg("dim", text);
|
|
@@ -127,52 +131,6 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
|
127
131
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
const SECRET_KEY_RE =
|
|
131
|
-
/(?:api[-_]?key|access[-_]?key|auth(?:orization)?|credential|password|private[-_]?key|secret|token)/iu;
|
|
132
|
-
const ENV_KEY_RE = /^env(?:ironment)?$/iu;
|
|
133
|
-
const SECRET_STRING_RE = /(https?:\/\/[^\s/@]+:)[^\s/@]+@/giu;
|
|
134
|
-
const SECRET_URL_PARAM_RE =
|
|
135
|
-
/([?&](?:api[-_]?key|access[-_]?token|auth(?:orization)?|credential|password|secret|token)[^=\s]*=)[^&#\s]+/giu;
|
|
136
|
-
const SECRET_ASSIGNMENT_RE =
|
|
137
|
-
/(?<![A-Z0-9_])((?:[A-Z0-9_]*(?:ACCESS|API|AUTH|CREDENTIAL|KEY|PASS(?:WORD)?|PRIVATE|SECRET|TOKEN)[A-Z0-9_]*)=)[^\s;&|]+/giu;
|
|
138
|
-
const SECRET_FLAG_RE =
|
|
139
|
-
/(--?(?:[a-z0-9-]*(?:api[-_]?key|access[-_]?token|auth(?:orization)?|credential|password|secret|token)[a-z0-9-]*))(=|\s+)([^\s;&|]+)/giu;
|
|
140
|
-
|
|
141
|
-
function redactSecretString(value: string): string {
|
|
142
|
-
return value
|
|
143
|
-
.replace(SECRET_STRING_RE, "$1[redacted]@")
|
|
144
|
-
.replace(SECRET_URL_PARAM_RE, "$1[redacted]")
|
|
145
|
-
.replace(SECRET_ASSIGNMENT_RE, "$1[redacted]")
|
|
146
|
-
.replace(SECRET_FLAG_RE, "$1$2[redacted]");
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function redactEnvironmentValue(value: unknown, depth: number): unknown {
|
|
150
|
-
if (depth > 8) return "[redacted nested values]";
|
|
151
|
-
if (typeof value === "string") return "[redacted]";
|
|
152
|
-
if (Array.isArray(value)) return value.map((item) => redactEnvironmentValue(item, depth + 1));
|
|
153
|
-
if (!isPlainObject(value)) return value;
|
|
154
|
-
const out: Record<string, unknown> = {};
|
|
155
|
-
for (const [childKey, childValue] of Object.entries(value)) {
|
|
156
|
-
out[childKey] = redactEnvironmentValue(childValue, depth + 1);
|
|
157
|
-
}
|
|
158
|
-
return out;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Project tool arguments for display only; execution and receipts retain the original values. */
|
|
162
|
-
export function redactToolArgs(value: unknown, key = "", depth = 0): unknown {
|
|
163
|
-
if (SECRET_KEY_RE.test(key)) return "[redacted]";
|
|
164
|
-
if (ENV_KEY_RE.test(key)) return redactEnvironmentValue(value, depth);
|
|
165
|
-
if (typeof value === "string") return redactSecretString(value);
|
|
166
|
-
if (depth > 8) return "[redacted nested values]";
|
|
167
|
-
if (Array.isArray(value)) return value.map((item) => redactToolArgs(item, key, depth + 1));
|
|
168
|
-
if (!isPlainObject(value)) return value;
|
|
169
|
-
const out: Record<string, unknown> = {};
|
|
170
|
-
for (const [childKey, childValue] of Object.entries(value)) {
|
|
171
|
-
out[childKey] = redactToolArgs(childValue, childKey, depth + 1);
|
|
172
|
-
}
|
|
173
|
-
return out;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
134
|
function readStringField(args: unknown, key: string): string | null {
|
|
177
135
|
if (!isPlainObject(args)) return null;
|
|
178
136
|
const value = args[key];
|
|
@@ -389,6 +347,10 @@ function offloadPathOf(finished: ToolExecutionFinished): string | null {
|
|
|
389
347
|
return isPlainObject(resultSize) ? stringField(resultSize, "offloadPath") : null;
|
|
390
348
|
}
|
|
391
349
|
|
|
350
|
+
function offloadFileMissing(finished: ToolExecutionFinished): boolean {
|
|
351
|
+
return finished.resultSummary?.offloadFileMissing === true;
|
|
352
|
+
}
|
|
353
|
+
|
|
392
354
|
function shownBytesOf(finished: ToolExecutionFinished): number | null {
|
|
393
355
|
const observation = observationOf(finished);
|
|
394
356
|
const fromObservation = numberField(observation, "shownBytes");
|
|
@@ -468,7 +430,12 @@ function ledgerTail(finished: ToolExecutionFinished): { facts: string; offload:
|
|
|
468
430
|
const offloadPath = executed ? offloadPathOf(finished) : null;
|
|
469
431
|
return {
|
|
470
432
|
facts: parts.length > 0 ? dim(` · ${parts.join(" · ")}`) : "",
|
|
471
|
-
offload:
|
|
433
|
+
offload:
|
|
434
|
+
offloadPath === null
|
|
435
|
+
? ""
|
|
436
|
+
: offloadFileMissing(finished)
|
|
437
|
+
? dim(" · full: gone after the 14-day retention sweep")
|
|
438
|
+
: dim(` · full: ${offloadPath}`),
|
|
472
439
|
};
|
|
473
440
|
}
|
|
474
441
|
|
|
@@ -1072,7 +1039,10 @@ function renderOutputMeta(
|
|
|
1072
1039
|
function renderOutputFooter(finished: ToolExecutionFinished, width: number, isError: boolean): string[] {
|
|
1073
1040
|
const out: string[] = [];
|
|
1074
1041
|
const offloadPath = isNonExecutedOutcome(finished.outcome) ? null : offloadPathOf(finished);
|
|
1075
|
-
if (offloadPath !== null)
|
|
1042
|
+
if (offloadPath !== null) {
|
|
1043
|
+
const pointer = offloadFileMissing(finished) ? "gone after the 14-day retention sweep" : offloadPath;
|
|
1044
|
+
out.push(...indentAndWrap(`${yellow("full output")} ${pointer}`, width, isError));
|
|
1045
|
+
}
|
|
1076
1046
|
const hint =
|
|
1077
1047
|
stringField(resultSizeOf(finished), "followUpHint") ?? stringField(finished.resultSummary ?? null, "followUpHint");
|
|
1078
1048
|
if (hint !== null) out.push(...indentAndWrap(`${dim("next")} ${hint}`, width, isError));
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* back off replayed thinking text.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import { type ResponseModelIdObservation, responseModelIdObservationFromRecord } from "../core/response-model-id.js";
|
|
20
21
|
import { rawDurationMs } from "../core/timers.js";
|
|
21
22
|
import { extractReasoningTokens } from "../domains/session/context-accounting.js";
|
|
22
23
|
import type { MessageEntry, SessionEntry, SessionUsageDefaults } from "../domains/session/index.js";
|
|
@@ -133,6 +134,10 @@ export function lastTurnSummaryFromLedger(
|
|
|
133
134
|
let toolErrorCount = 0;
|
|
134
135
|
let targetId = turn.targetId;
|
|
135
136
|
let modelId = turn.modelId;
|
|
137
|
+
let responseModelIdObservation: ResponseModelIdObservation = {
|
|
138
|
+
state: "legacy-difference-only",
|
|
139
|
+
differingModelId: null,
|
|
140
|
+
};
|
|
136
141
|
|
|
137
142
|
for (const row of turn.rows) {
|
|
138
143
|
const record = payloadRecord(row.payload);
|
|
@@ -148,7 +153,8 @@ export function lastTurnSummaryFromLedger(
|
|
|
148
153
|
const reason = stopReasonOf(record.stopReason);
|
|
149
154
|
if (reason && reason !== "stop") stopReason = reason;
|
|
150
155
|
targetId = targetId ?? nonEmptyString(record, "provider", "api");
|
|
151
|
-
modelId = modelId ?? nonEmptyString(record, "
|
|
156
|
+
modelId = modelId ?? nonEmptyString(record, "model");
|
|
157
|
+
responseModelIdObservation = responseModelIdObservationFromRecord(record, "legacy-difference-only");
|
|
152
158
|
const usage = payloadRecord(record.usage);
|
|
153
159
|
if (!usage) continue;
|
|
154
160
|
inputTokens += positiveNumber(usage.input);
|
|
@@ -177,6 +183,7 @@ export function lastTurnSummaryFromLedger(
|
|
|
177
183
|
stopReason,
|
|
178
184
|
watchdogPeak: 0,
|
|
179
185
|
truncated: false,
|
|
186
|
+
responseModelIdObservation,
|
|
180
187
|
};
|
|
181
188
|
if (sawReasoning) {
|
|
182
189
|
summary.reasoningTokens = reasoningTokens;
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
* only the sink wiring between that fold and the observability projection.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import {
|
|
18
|
+
addResponseModelIdObservationCount,
|
|
19
|
+
emptyResponseModelIdObservationCounts,
|
|
20
|
+
type ResponseModelIdObservationCounts,
|
|
21
|
+
} from "../core/response-model-id.js";
|
|
17
22
|
import { ledgerUsageCalls, type SessionEntry, type SessionUsageDefaults } from "../domains/session/index.js";
|
|
18
23
|
import { filterEntriesToActivePath } from "../domains/session/tree/active-path.js";
|
|
19
24
|
|
|
@@ -22,7 +27,7 @@ export interface SessionUsageSink {
|
|
|
22
27
|
resetSession(): void;
|
|
23
28
|
recordTokens(
|
|
24
29
|
providerId: string,
|
|
25
|
-
|
|
30
|
+
attributedModelId: string,
|
|
26
31
|
tokens: number,
|
|
27
32
|
costUsd?: number,
|
|
28
33
|
breakdown?: {
|
|
@@ -35,6 +40,10 @@ export interface SessionUsageSink {
|
|
|
35
40
|
apiCalls?: number;
|
|
36
41
|
},
|
|
37
42
|
costProvenance?: never,
|
|
43
|
+
modelIdFacts?: {
|
|
44
|
+
requestedModelIds: ReadonlyArray<string>;
|
|
45
|
+
responseModelIdObservationCounts: Readonly<ResponseModelIdObservationCounts>;
|
|
46
|
+
},
|
|
38
47
|
): void;
|
|
39
48
|
}
|
|
40
49
|
|
|
@@ -61,14 +70,31 @@ export function reseedSessionUsageFromLedger(
|
|
|
61
70
|
sink.resetSession();
|
|
62
71
|
const scoped = filterEntriesToActivePath(entries, activeLeafTurnId ?? undefined);
|
|
63
72
|
for (const call of ledgerUsageCalls(scoped, defaults)) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
const responseModelIdObservationCounts = emptyResponseModelIdObservationCounts();
|
|
74
|
+
addResponseModelIdObservationCount(
|
|
75
|
+
responseModelIdObservationCounts,
|
|
76
|
+
call.responseModelIdObservation,
|
|
77
|
+
call.apiCalls ?? 1,
|
|
78
|
+
);
|
|
79
|
+
sink.recordTokens(
|
|
80
|
+
call.providerId,
|
|
81
|
+
call.attributedModelId,
|
|
82
|
+
call.totalTokens,
|
|
83
|
+
call.costUsd,
|
|
84
|
+
{
|
|
85
|
+
input: call.input,
|
|
86
|
+
output: call.output,
|
|
87
|
+
cacheRead: call.cacheRead,
|
|
88
|
+
cacheWrite: call.cacheWrite,
|
|
89
|
+
reasoningTokens: call.reasoningTokens,
|
|
90
|
+
totalTokens: call.totalTokens,
|
|
91
|
+
apiCalls: call.apiCalls ?? 1,
|
|
92
|
+
},
|
|
93
|
+
undefined,
|
|
94
|
+
{
|
|
95
|
+
requestedModelIds: [call.requestedModelId],
|
|
96
|
+
responseModelIdObservationCounts,
|
|
97
|
+
},
|
|
98
|
+
);
|
|
73
99
|
}
|
|
74
100
|
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/btw`: one model round beside the session, never inside it.
|
|
3
|
+
*
|
|
4
|
+
* A fleet run briefs its workers from the transcript, so every question the
|
|
5
|
+
* operator asks mid-run to orient themselves ("which file did that land in
|
|
6
|
+
* again?") becomes context the workers inherit. This round reads the compiled
|
|
7
|
+
* history the next turn would send and answers into an overlay; nothing it
|
|
8
|
+
* produces reaches the session JSONL, the transcript panel, the context ledger,
|
|
9
|
+
* or the task board, and it never becomes a turn.
|
|
10
|
+
*
|
|
11
|
+
* The call goes through the same `src/engine/ai.ts` stream seam compaction uses
|
|
12
|
+
* for its summarization call, which is what keeps this outside the turn state
|
|
13
|
+
* machine without adding an engine seam of its own. Tools are never sent.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { stream } from "../engine/ai.js";
|
|
17
|
+
import type { AgentMessage, EngineModel, Usage } from "../engine/types.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The whole system prompt for the round. The session's own compiled prompt is
|
|
21
|
+
* deliberately not reused: it carries the working agreement for a turn that
|
|
22
|
+
* edits files and calls tools, and this round does neither.
|
|
23
|
+
*/
|
|
24
|
+
export const SIDE_QUESTION_SYSTEM_PROMPT = [
|
|
25
|
+
"You are answering a side question from the operator of a coding session.",
|
|
26
|
+
"The conversation above is read-only context. Answer the question directly and briefly.",
|
|
27
|
+
"Do not continue the conversation, do not propose edits, and do not call tools.",
|
|
28
|
+
].join(" ");
|
|
29
|
+
|
|
30
|
+
/** Default output budget for the round. A side question wants an answer, not an essay. */
|
|
31
|
+
export const SIDE_QUESTION_MAX_TOKENS = 2048;
|
|
32
|
+
|
|
33
|
+
export interface SideQuestionUsage {
|
|
34
|
+
input: number;
|
|
35
|
+
output: number;
|
|
36
|
+
cacheRead: number;
|
|
37
|
+
cacheWrite: number;
|
|
38
|
+
reasoning: number;
|
|
39
|
+
totalTokens: number;
|
|
40
|
+
costUsd: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SideQuestionInput {
|
|
44
|
+
/** Live model the next turn would use, taken from the session's active runtime. */
|
|
45
|
+
model: EngineModel;
|
|
46
|
+
/**
|
|
47
|
+
* The compiled message history the next turn would send. Treated as
|
|
48
|
+
* read-only: the round copies it and appends its own user message to the
|
|
49
|
+
* copy, so the live agent's message list is never mutated.
|
|
50
|
+
*/
|
|
51
|
+
messages: ReadonlyArray<AgentMessage>;
|
|
52
|
+
question: string;
|
|
53
|
+
apiKey?: string;
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
maxTokens?: number;
|
|
56
|
+
/** Streamed answer text, delivered as the provider produces it. */
|
|
57
|
+
onDelta?: (partialText: string) => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* One out-of-turn round against the session's live runtime. `/btw` and
|
|
62
|
+
* `/handoff` are both this: read the compiled history the next turn would see,
|
|
63
|
+
* append one instruction, send no tools, and never touch the turn state
|
|
64
|
+
* machine. Only the system prompt and the appended message differ.
|
|
65
|
+
*/
|
|
66
|
+
export interface OutOfTurnRoundInput extends Omit<SideQuestionInput, "question"> {
|
|
67
|
+
systemPrompt: string;
|
|
68
|
+
/** The one message appended to the read-only copy of the history. */
|
|
69
|
+
userText: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface SideQuestionResult {
|
|
73
|
+
text: string;
|
|
74
|
+
usage: SideQuestionUsage | null;
|
|
75
|
+
/** True when the operator cancelled the round with Esc or Ctrl+C. */
|
|
76
|
+
aborted: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function positive(value: unknown): number {
|
|
80
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sideQuestionUsage(raw: unknown): SideQuestionUsage | null {
|
|
84
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
85
|
+
const usage = raw as Partial<Usage> & { reasoning?: number };
|
|
86
|
+
const input = positive(usage.input);
|
|
87
|
+
const output = positive(usage.output);
|
|
88
|
+
const cacheRead = positive(usage.cacheRead);
|
|
89
|
+
const cacheWrite = positive(usage.cacheWrite);
|
|
90
|
+
const totalTokens = positive(usage.totalTokens) || input + output + cacheRead + cacheWrite;
|
|
91
|
+
if (totalTokens === 0) return null;
|
|
92
|
+
return {
|
|
93
|
+
input,
|
|
94
|
+
output,
|
|
95
|
+
cacheRead,
|
|
96
|
+
cacheWrite,
|
|
97
|
+
reasoning: positive(usage.reasoning),
|
|
98
|
+
totalTokens,
|
|
99
|
+
costUsd: positive(usage.cost?.total),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function textFromMessage(message: unknown): string {
|
|
104
|
+
if (!message || typeof message !== "object" || !("content" in message)) return "";
|
|
105
|
+
const content = (message as { content: unknown }).content;
|
|
106
|
+
if (typeof content === "string") return content;
|
|
107
|
+
if (!Array.isArray(content)) return "";
|
|
108
|
+
return content
|
|
109
|
+
.filter(
|
|
110
|
+
(block): block is { type: "text"; text: string } =>
|
|
111
|
+
!!block &&
|
|
112
|
+
typeof block === "object" &&
|
|
113
|
+
(block as { type?: unknown }).type === "text" &&
|
|
114
|
+
typeof (block as { text?: unknown }).text === "string",
|
|
115
|
+
)
|
|
116
|
+
.map((block) => block.text)
|
|
117
|
+
.join("");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Run the side-question round. Resolves with the answer, the provider usage
|
|
122
|
+
* (which is still real money and is reported to `/cost`), and whether the
|
|
123
|
+
* operator aborted it. Provider failures reject; the caller renders the reason
|
|
124
|
+
* in the overlay.
|
|
125
|
+
*/
|
|
126
|
+
export async function runSideQuestion(input: SideQuestionInput): Promise<SideQuestionResult> {
|
|
127
|
+
const { question, ...rest } = input;
|
|
128
|
+
return runOutOfTurnRound({ ...rest, systemPrompt: SIDE_QUESTION_SYSTEM_PROMPT, userText: question });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The shared round. See {@link OutOfTurnRoundInput}. */
|
|
132
|
+
export async function runOutOfTurnRound(input: OutOfTurnRoundInput): Promise<SideQuestionResult> {
|
|
133
|
+
const options: Record<string, unknown> = { maxTokens: input.maxTokens ?? SIDE_QUESTION_MAX_TOKENS };
|
|
134
|
+
if (input.apiKey !== undefined) options.apiKey = input.apiKey;
|
|
135
|
+
if (input.signal !== undefined) options.signal = input.signal;
|
|
136
|
+
|
|
137
|
+
const context = {
|
|
138
|
+
systemPrompt: input.systemPrompt,
|
|
139
|
+
messages: [
|
|
140
|
+
...input.messages,
|
|
141
|
+
{ role: "user", content: [{ type: "text", text: input.userText }], timestamp: Date.now() },
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const events = stream(
|
|
146
|
+
input.model,
|
|
147
|
+
context as unknown as Parameters<typeof stream>[1],
|
|
148
|
+
options as unknown as Parameters<typeof stream>[2],
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
let text = "";
|
|
152
|
+
for await (const event of events) {
|
|
153
|
+
if (event.type === "text_delta") {
|
|
154
|
+
text = textFromMessage(event.partial);
|
|
155
|
+
input.onDelta?.(text);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (event.type === "done") {
|
|
159
|
+
const answer = textFromMessage(event.message).trim();
|
|
160
|
+
return { text: answer, usage: sideQuestionUsage((event.message as { usage?: unknown }).usage), aborted: false };
|
|
161
|
+
}
|
|
162
|
+
if (event.type === "error") {
|
|
163
|
+
const failed = event.error as { stopReason?: unknown; usage?: unknown; errorMessage?: unknown };
|
|
164
|
+
if (event.reason === "aborted" || failed.stopReason === "aborted" || input.signal?.aborted === true) {
|
|
165
|
+
return { text: text.trim(), usage: sideQuestionUsage(failed.usage), aborted: true };
|
|
166
|
+
}
|
|
167
|
+
throw new Error(typeof failed.errorMessage === "string" ? failed.errorMessage : "side question failed");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { text: text.trim(), usage: null, aborted: input.signal?.aborted === true };
|
|
171
|
+
}
|