@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,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
classifyDecisionPresentation,
|
|
3
|
+
type DecisionPresentation,
|
|
4
|
+
decisionFactsForAnswer,
|
|
5
|
+
} from "../../domains/safety/decision-presentation.js";
|
|
1
6
|
import {
|
|
2
7
|
type Component,
|
|
3
8
|
Input,
|
|
@@ -18,16 +23,14 @@ import { type ClioToken, clioTheme, dotSep, GLYPH, screenTitle } from "../theme/
|
|
|
18
23
|
import { nextContentScrollOffset, type ViewScrollAction } from "../view/view-overlay.js";
|
|
19
24
|
|
|
20
25
|
/**
|
|
21
|
-
* The
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* keyboard is the moment the operator has to act with it. Everything
|
|
25
|
-
* informational keeps the teal frame, so the one border that is not teal is the
|
|
26
|
-
* one asking for an answer. All three surfaces carry it: what changes between
|
|
27
|
-
* them is the size and the place, never the signal.
|
|
26
|
+
* The default title and border token for a local conversational answer.
|
|
27
|
+
* Outward and other consequence tiers replace both values with their typed
|
|
28
|
+
* presentation. All ask_user surfaces carry the same classified signal.
|
|
28
29
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
const DEFAULT_ASK_USER_PRESENTATION = classifyDecisionPresentation(decisionFactsForAnswer("local"));
|
|
31
|
+
|
|
32
|
+
export const ASK_USER_DECISION_TONE: ClioToken = DEFAULT_ASK_USER_PRESENTATION.semanticToken;
|
|
33
|
+
export const ASK_USER_DECISION_TITLE = DEFAULT_ASK_USER_PRESENTATION.title;
|
|
31
34
|
export const ASK_USER_WAITING_TITLE = "Ask User";
|
|
32
35
|
|
|
33
36
|
/**
|
|
@@ -73,7 +76,7 @@ interface AskUserSurfaceGeometry {
|
|
|
73
76
|
* `ASK_USER_FRAME_AND_MARGIN_ROWS` counts alongside the two border rows.
|
|
74
77
|
*/
|
|
75
78
|
export const ASK_USER_SURFACE_GEOMETRY: Record<AskUserSurface, AskUserSurfaceGeometry> = {
|
|
76
|
-
compact: { width: 68, maxInnerRows:
|
|
79
|
+
compact: { width: 68, maxInnerRows: 14, anchor: "bottom-center", margin: { top: 1, right: 2, bottom: 1, left: 2 } },
|
|
77
80
|
panel: { width: 88, maxInnerRows: 18, anchor: "center", margin: 1 },
|
|
78
81
|
// The interview is a workspace, not a modal. A cap of 42 turned a 66-row
|
|
79
82
|
// terminal into a centered box with twenty blank rows around it, so the
|
|
@@ -111,7 +114,7 @@ export interface OpenAskUserOverlayDeps {
|
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
export interface AskUserOverlaySession extends OverlayHandle {
|
|
114
|
-
ask(questions: ReadonlyArray<AskUserQuestion
|
|
117
|
+
ask(questions: ReadonlyArray<AskUserQuestion>, presentation?: DecisionPresentation): Promise<AskUserResult>;
|
|
115
118
|
cancel(): void;
|
|
116
119
|
close(): void;
|
|
117
120
|
isWaiting(): boolean;
|
|
@@ -242,16 +245,21 @@ class AskUserOverlayView implements Component {
|
|
|
242
245
|
private contentRegionHeight = 1;
|
|
243
246
|
/** Rows the scroll region wanted on the last render. */
|
|
244
247
|
private contentTotalRows = 0;
|
|
248
|
+
private presentation: DecisionPresentation = DEFAULT_ASK_USER_PRESENTATION;
|
|
245
249
|
|
|
246
250
|
constructor(private readonly deps: AskUserOverlayViewDeps) {}
|
|
247
251
|
|
|
248
|
-
begin(
|
|
252
|
+
begin(
|
|
253
|
+
questions: ReadonlyArray<AskUserQuestion>,
|
|
254
|
+
presentation: DecisionPresentation = DEFAULT_ASK_USER_PRESENTATION,
|
|
255
|
+
): Promise<AskUserResult> {
|
|
249
256
|
if (this.phase === "closed") return Promise.resolve(cancelledAskUserResult());
|
|
250
257
|
if (this.resolveCurrent) return Promise.resolve(cancelledAskUserResult());
|
|
251
258
|
this.phase = "asking";
|
|
252
259
|
this.index = 0;
|
|
253
260
|
this.status = "";
|
|
254
261
|
this.questions = [...questions];
|
|
262
|
+
this.presentation = presentation;
|
|
255
263
|
this.states = this.questions.map((question) => createQuestionState(question));
|
|
256
264
|
this.list = null;
|
|
257
265
|
this.input = null;
|
|
@@ -288,6 +296,14 @@ class AskUserOverlayView implements Component {
|
|
|
288
296
|
return this.surface;
|
|
289
297
|
}
|
|
290
298
|
|
|
299
|
+
decisionTitle(): string {
|
|
300
|
+
return this.presentation.title;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
decisionTone(): ClioToken {
|
|
304
|
+
return this.presentation.semanticToken;
|
|
305
|
+
}
|
|
306
|
+
|
|
291
307
|
invalidate(): void {
|
|
292
308
|
this.list?.invalidate();
|
|
293
309
|
this.input?.invalidate();
|
|
@@ -358,10 +374,17 @@ class AskUserOverlayView implements Component {
|
|
|
358
374
|
const statusLines = this.status.length > 0 ? ["", fitLine(clioTheme().fg("dim", this.status), safeWidth)] : [];
|
|
359
375
|
const headerLine = this.renderQuestionHeader(question, safeWidth);
|
|
360
376
|
const headerLines = headerLine.length > 0 ? [headerLine] : [];
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
377
|
+
const bottom = [...statusLines, "", ...controlLines];
|
|
378
|
+
const topBudget = Math.max(1, maxRows - bottom.length);
|
|
379
|
+
const contextLines = this.renderDecisionContext(safeWidth);
|
|
380
|
+
const top = [
|
|
381
|
+
contextLines[0] ?? "",
|
|
382
|
+
...headerLines,
|
|
383
|
+
...wrapTextWithAnsi(question.question, safeWidth),
|
|
384
|
+
...contextLines.slice(1),
|
|
385
|
+
].slice(0, topBudget);
|
|
386
|
+
|
|
387
|
+
return [...top, ...bottom].slice(0, maxRows);
|
|
365
388
|
}
|
|
366
389
|
|
|
367
390
|
/**
|
|
@@ -380,7 +403,12 @@ class AskUserOverlayView implements Component {
|
|
|
380
403
|
const headerLine = this.renderQuestionHeader(question, width);
|
|
381
404
|
const headerLines = headerLine.length > 0 ? [headerLine] : [];
|
|
382
405
|
const stripLines = [this.renderQuestionStrip(width), ""];
|
|
383
|
-
const content = [
|
|
406
|
+
const content = [
|
|
407
|
+
...wrapTextWithAnsi(question.question, width),
|
|
408
|
+
"",
|
|
409
|
+
...this.renderDecisionContext(width),
|
|
410
|
+
...this.renderAnswerLedger(width),
|
|
411
|
+
];
|
|
384
412
|
|
|
385
413
|
// Chrome sheds from the outside in when the terminal is too short to carry
|
|
386
414
|
// all of it: the round strip first, then the header, then the status, and
|
|
@@ -460,36 +488,52 @@ class AskUserOverlayView implements Component {
|
|
|
460
488
|
const question = this.currentQuestion();
|
|
461
489
|
const state = this.currentState();
|
|
462
490
|
if (!question || !state) return buildHint([]);
|
|
491
|
+
const recordAnswer =
|
|
492
|
+
this.presentation.requiredActions.find((action) => action.id === "record-answer")?.label.toLowerCase() ??
|
|
493
|
+
"record answer";
|
|
463
494
|
if (state.mode === "text") {
|
|
464
495
|
return this.questions.length > 1
|
|
465
496
|
? this.withScrollHint([
|
|
466
|
-
{ key: "Enter", verb:
|
|
497
|
+
{ key: "Enter", verb: recordAnswer },
|
|
467
498
|
{ key: "Alt+Left/Right", verb: "question" },
|
|
468
499
|
])
|
|
469
|
-
: this.withScrollHint([{ key: "Enter", verb:
|
|
500
|
+
: this.withScrollHint([{ key: "Enter", verb: recordAnswer }]);
|
|
470
501
|
}
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
// interview together.
|
|
502
|
+
// The classified action label distinguishes recording an answer from
|
|
503
|
+
// granting authority. Esc keeps the product's one word for the way out,
|
|
504
|
+
// since it closes the prompt and the interview together.
|
|
475
505
|
if (question.multi_select === true) {
|
|
476
506
|
return this.questions.length > 1
|
|
477
507
|
? this.withScrollHint([
|
|
478
508
|
{ key: "Left/Right", verb: "question" },
|
|
479
509
|
{ key: "Space", verb: "toggle" },
|
|
480
|
-
{ key: "Enter", verb:
|
|
510
|
+
{ key: "Enter", verb: recordAnswer },
|
|
481
511
|
])
|
|
482
512
|
: this.withScrollHint([
|
|
483
513
|
{ key: "Space", verb: "toggle" },
|
|
484
|
-
{ key: "Enter", verb:
|
|
514
|
+
{ key: "Enter", verb: recordAnswer },
|
|
485
515
|
]);
|
|
486
516
|
}
|
|
487
517
|
return this.questions.length > 1
|
|
488
518
|
? this.withScrollHint([
|
|
489
519
|
{ key: "Left/Right", verb: "question" },
|
|
490
|
-
{ key: "Enter", verb:
|
|
520
|
+
{ key: "Enter", verb: recordAnswer },
|
|
491
521
|
])
|
|
492
|
-
: this.withScrollHint([{ key: "Enter", verb:
|
|
522
|
+
: this.withScrollHint([{ key: "Enter", verb: recordAnswer }]);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private renderDecisionContext(width: number): string[] {
|
|
526
|
+
const theme = clioTheme();
|
|
527
|
+
const tier = theme.style(this.presentation.semanticToken, this.presentation.tierLabel, { bold: true });
|
|
528
|
+
const requested = theme.fg("dim", `requested by ${this.presentation.requestedByCopy}`);
|
|
529
|
+
const effect = `${theme.fg("dim", "Effect:")} ${theme.fg("muted", this.presentation.authorizationCopy)}`;
|
|
530
|
+
return [
|
|
531
|
+
fitLine(`${tier}${dotSep(theme)}${requested}`, width),
|
|
532
|
+
...wrapTextWithAnsi(effect, width).map((line) => fitLine(line, width)),
|
|
533
|
+
...wrapTextWithAnsi(theme.fg("muted", this.presentation.reversibilityCopy), width).map((line) =>
|
|
534
|
+
fitLine(line, width),
|
|
535
|
+
),
|
|
536
|
+
];
|
|
493
537
|
}
|
|
494
538
|
|
|
495
539
|
/**
|
|
@@ -1012,8 +1056,8 @@ export function openAskUserOverlay(tui: TUI, deps: OpenAskUserOverlayDeps): AskU
|
|
|
1012
1056
|
// the terminal has left after the margins.
|
|
1013
1057
|
...(geometry.maxInnerRows > 0 ? { maxHeight: geometry.maxInnerRows + ASK_USER_FRAME_ROWS } : {}),
|
|
1014
1058
|
margin: geometry.margin,
|
|
1015
|
-
title: () => (view.isDecisionPending() ?
|
|
1016
|
-
tone: () => (view.isDecisionPending() ?
|
|
1059
|
+
title: () => (view.isDecisionPending() ? view.decisionTitle() : ASK_USER_WAITING_TITLE),
|
|
1060
|
+
tone: () => (view.isDecisionPending() ? view.decisionTone() : undefined),
|
|
1017
1061
|
footerHint: () => view.footerHint(),
|
|
1018
1062
|
});
|
|
1019
1063
|
mounted = surface;
|
|
@@ -1034,7 +1078,7 @@ export function openAskUserOverlay(tui: TUI, deps: OpenAskUserOverlayDeps): AskU
|
|
|
1034
1078
|
focus: () => handle?.focus(),
|
|
1035
1079
|
unfocus: (options) => (options ? handle?.unfocus(options) : handle?.unfocus()),
|
|
1036
1080
|
isFocused: () => handle?.isFocused() ?? false,
|
|
1037
|
-
ask: (questions) => view.begin(questions),
|
|
1081
|
+
ask: (questions, presentation) => view.begin(questions, presentation),
|
|
1038
1082
|
cancel: () => view.cancel(),
|
|
1039
1083
|
close,
|
|
1040
1084
|
hide: close,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { classifyDecisionPresentation, decisionFactsForAnswer } from "../../domains/safety/decision-presentation.js";
|
|
1
2
|
import type { DecisionLedgerEntry, DecisionRecord } from "../../domains/session/entries.js";
|
|
2
3
|
import {
|
|
3
4
|
type Component,
|
|
@@ -73,10 +74,11 @@ function selectableRows(interviews: ReadonlyArray<DecisionLedgerEntry>): Selecta
|
|
|
73
74
|
|
|
74
75
|
function interviewHeader(interview: DecisionLedgerEntry, width: number, now: number): string[] {
|
|
75
76
|
const theme = clioTheme();
|
|
77
|
+
const presentation = classifyDecisionPresentation(decisionFactsForAnswer(interview.exposure ?? "local"));
|
|
76
78
|
const rounds = `${interview.roundCount} round${interview.roundCount === 1 ? "" : "s"}`;
|
|
77
79
|
const status =
|
|
78
80
|
interview.interviewStatus === "complete" ? theme.fg("success", "complete") : theme.fg("warning", "cancelled");
|
|
79
|
-
const heading = `${theme.fg("accent", relativeTime(interview.endedAt, now))}${theme.fg("dim", " · ")}${theme.fg("muted", rounds)}${theme.fg("dim", " · ")}${status}`;
|
|
81
|
+
const heading = `${theme.style(presentation.semanticToken, presentation.tierLabel, { bold: true })}${theme.fg("dim", " · ")}${theme.fg("accent", relativeTime(interview.endedAt, now))}${theme.fg("dim", " · ")}${theme.fg("muted", rounds)}${theme.fg("dim", " · ")}${status}`;
|
|
80
82
|
const lines = [fitLine(heading, width)];
|
|
81
83
|
if (interview.summary) {
|
|
82
84
|
lines.push(...wrapTextWithAnsi(theme.fg("dim", interview.summary), width).map((line) => fitLine(line, width)));
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/fleet run <name>` approval: the whole plan, before anything dispatches.
|
|
3
|
+
*
|
|
4
|
+
* The operator reads the compiled plan here and decides. Enter accepts and the
|
|
5
|
+
* run starts; Esc cancels and nothing is dispatched, nothing is written, and no
|
|
6
|
+
* ledger row exists. A contract that failed preflight opens the same overlay
|
|
7
|
+
* with its diagnostics and no accept key, so the only way out of a broken plan
|
|
8
|
+
* is to leave it.
|
|
9
|
+
*
|
|
10
|
+
* The body is a pure function of the projection so the layout is testable
|
|
11
|
+
* without a TUI.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
type Component,
|
|
16
|
+
isKeyRelease,
|
|
17
|
+
matchesKey,
|
|
18
|
+
type OverlayHandle,
|
|
19
|
+
type TUI,
|
|
20
|
+
truncateToWidth,
|
|
21
|
+
wrapTextWithAnsi,
|
|
22
|
+
} from "../../engine/tui.js";
|
|
23
|
+
import type { FleetRunPreview, FleetRunPreviewStep } from "../fleet-run-preview.js";
|
|
24
|
+
import { buildResponsiveHint, FocusBox, showClioOverlayFrame } from "../overlay-frame.js";
|
|
25
|
+
import { clioTheme, rule } from "../theme/index.js";
|
|
26
|
+
|
|
27
|
+
export const FLEET_RUN_APPROVAL_OVERLAY_TITLE = "Fleet run approval";
|
|
28
|
+
|
|
29
|
+
const FLEET_RUN_APPROVAL_MIN_WIDTH = 48;
|
|
30
|
+
const FLEET_RUN_APPROVAL_MAX_WIDTH = 110;
|
|
31
|
+
/** Rows of plan shown at once. Anything longer scrolls. */
|
|
32
|
+
export const FLEET_RUN_APPROVAL_VISIBLE_ROWS = 22;
|
|
33
|
+
|
|
34
|
+
function fleetRunApprovalOverlayWidth(columns: number): number {
|
|
35
|
+
return Math.max(FLEET_RUN_APPROVAL_MIN_WIDTH, Math.min(FLEET_RUN_APPROVAL_MAX_WIDTH, columns - 4));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** What the overlay renders: an accepted-shaped plan or the reasons there is none. */
|
|
39
|
+
export type FleetRunApprovalSubject =
|
|
40
|
+
| { ok: true; preview: FleetRunPreview }
|
|
41
|
+
| { ok: false; name: string; diagnostics: ReadonlyArray<string> };
|
|
42
|
+
|
|
43
|
+
export interface OpenFleetRunApprovalOverlayOptions {
|
|
44
|
+
subject: FleetRunApprovalSubject;
|
|
45
|
+
/** Live terminal width, so the box tracks the window it opened in. */
|
|
46
|
+
columns: number;
|
|
47
|
+
/** Enter: dispatch this exact plan. Never called for a failed preflight. */
|
|
48
|
+
onAccept: () => void;
|
|
49
|
+
/** Esc: nothing is dispatched and nothing is written. */
|
|
50
|
+
onCancel: () => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The declared boundary as one phrase: an empty allowlist is a claim, not an absence. */
|
|
54
|
+
export function formatWriteBoundary(writes: ReadonlyArray<string> | undefined): string {
|
|
55
|
+
if (writes === undefined) return "writes unenforced";
|
|
56
|
+
if (writes.length === 0) return "writes nothing";
|
|
57
|
+
return `writes ${writes.join(", ")}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** One step as its facts: kind, who runs it, where, its scope, and its boundary. */
|
|
61
|
+
export function formatFleetRunPreviewStep(step: FleetRunPreviewStep): string {
|
|
62
|
+
const loop = step.loop === undefined ? "" : ` (${step.loop.loopId} ${step.loop.role} ${step.loop.attempt})`;
|
|
63
|
+
if (step.kind === "code") {
|
|
64
|
+
const gate = step.gate === undefined ? "" : ` · gate path ${step.gate.path}`;
|
|
65
|
+
return `code ${step.stepId}${loop} · command ${step.commandId ?? "?"}${gate} · ${step.scope} · ${formatWriteBoundary(step.writes)}`;
|
|
66
|
+
}
|
|
67
|
+
const route = step.route === undefined ? "route unresolved" : `${step.route.targetId} ▸ ${step.route.wireModelId}`;
|
|
68
|
+
const node = step.route === undefined ? "" : ` · node ${step.route.nodeId}`;
|
|
69
|
+
const declaredRoute =
|
|
70
|
+
step.target !== undefined
|
|
71
|
+
? ` · target ${step.target}`
|
|
72
|
+
: step.profile !== undefined
|
|
73
|
+
? ` · profile ${step.profile}`
|
|
74
|
+
: "";
|
|
75
|
+
const gate =
|
|
76
|
+
step.gate !== undefined
|
|
77
|
+
? ` · gate path ${step.gate.path} · run ${"commandId" in step.gate ? step.gate.commandId : "?"} · baseline ${"commandId" in step.gate ? step.gate.commandId : "?"}`
|
|
78
|
+
: "";
|
|
79
|
+
const dynamicPlan =
|
|
80
|
+
step.plan === undefined
|
|
81
|
+
? ""
|
|
82
|
+
: ` · roster ${step.plan.roster.join(", ")} · maxTasks ${step.plan.maxTasks}${step.plan.proposals ? " · proposals" : ""}`;
|
|
83
|
+
return `agent ${step.stepId}${loop} · ${step.agentId ?? "?"} · ${route}${node}${declaredRoute}${gate}${dynamicPlan} · ${step.scope} · ${formatWriteBoundary(step.writes)}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function formatBudgetLine(preview: FleetRunPreview): string {
|
|
87
|
+
const contract =
|
|
88
|
+
preview.budget.contractUsd === null
|
|
89
|
+
? "contract declares no ceiling"
|
|
90
|
+
: `contract ceiling $${preview.budget.contractUsd.toFixed(2)}`;
|
|
91
|
+
return `budget: admitted under $${preview.budget.ceilingUsd.toFixed(2)} session ceiling, $${preview.budget.currentUsd.toFixed(4)} spent, ${contract}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Render the body. Waves in order, each step with its facts, then the argv a
|
|
96
|
+
* code step will actually run, and last the budget the run is admitted under.
|
|
97
|
+
*/
|
|
98
|
+
export function formatFleetRunApprovalBody(subject: FleetRunApprovalSubject, width: number, scroll: number): string[] {
|
|
99
|
+
const theme = clioTheme();
|
|
100
|
+
const contentWidth = Math.max(1, Math.floor(width));
|
|
101
|
+
const rows: string[] = [];
|
|
102
|
+
|
|
103
|
+
if (!subject.ok) {
|
|
104
|
+
rows.push(theme.fg("error", `${subject.name}: preflight failed; nothing will be dispatched`));
|
|
105
|
+
rows.push(rule(theme, contentWidth));
|
|
106
|
+
for (const diagnostic of subject.diagnostics) {
|
|
107
|
+
for (const line of diagnostic.split("\n")) {
|
|
108
|
+
for (const wrapped of wrapTextWithAnsi(theme.fg("muted", line), contentWidth)) rows.push(wrapped);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
const { preview } = subject;
|
|
113
|
+
rows.push(theme.fg("dim", `${preview.name} · plan ${preview.planHash.slice(0, 12)}`));
|
|
114
|
+
rows.push(rule(theme, contentWidth));
|
|
115
|
+
for (const wave of preview.waves) {
|
|
116
|
+
rows.push(theme.fg("accent", `wave ${wave.index}`));
|
|
117
|
+
for (const step of wave.steps) {
|
|
118
|
+
for (const line of wrapTextWithAnsi(theme.fg("muted", ` ${formatFleetRunPreviewStep(step)}`), contentWidth)) {
|
|
119
|
+
rows.push(line);
|
|
120
|
+
}
|
|
121
|
+
if (step.argv !== undefined) {
|
|
122
|
+
rows.push(theme.fg("dim", truncateToWidth(` argv ${step.argv.join(" ")}`, contentWidth, "…", false)));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
rows.push(rule(theme, contentWidth));
|
|
127
|
+
for (const line of wrapTextWithAnsi(theme.fg("info", formatBudgetLine(preview)), contentWidth)) rows.push(line);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const maxScroll = Math.max(0, rows.length - FLEET_RUN_APPROVAL_VISIBLE_ROWS);
|
|
131
|
+
const start = Math.max(0, Math.min(scroll, maxScroll));
|
|
132
|
+
const shown = rows.slice(start, start + FLEET_RUN_APPROVAL_VISIBLE_ROWS);
|
|
133
|
+
if (rows.length > FLEET_RUN_APPROVAL_VISIBLE_ROWS) {
|
|
134
|
+
shown.push(theme.fg("dim", `(${start + 1}-${start + shown.length} of ${rows.length} lines)`));
|
|
135
|
+
}
|
|
136
|
+
return shown;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
class FleetRunApprovalBody implements Component {
|
|
140
|
+
scroll = 0;
|
|
141
|
+
|
|
142
|
+
constructor(private readonly subject: FleetRunApprovalSubject) {}
|
|
143
|
+
|
|
144
|
+
render(width: number): string[] {
|
|
145
|
+
return formatFleetRunApprovalBody(this.subject, width, this.scroll);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
invalidate(): void {}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function openFleetRunApprovalOverlay(tui: TUI, options: OpenFleetRunApprovalOverlayOptions): OverlayHandle {
|
|
152
|
+
const body = new FleetRunApprovalBody(options.subject);
|
|
153
|
+
const acceptable = options.subject.ok;
|
|
154
|
+
let settled = false;
|
|
155
|
+
|
|
156
|
+
const accept = (): void => {
|
|
157
|
+
if (settled || !acceptable) return;
|
|
158
|
+
settled = true;
|
|
159
|
+
options.onAccept();
|
|
160
|
+
};
|
|
161
|
+
const cancel = (): void => {
|
|
162
|
+
if (settled) return;
|
|
163
|
+
settled = true;
|
|
164
|
+
options.onCancel();
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const focus = new FocusBox(body, {
|
|
168
|
+
// Keys are matched by name, never by raw bytes: under the kitty keyboard
|
|
169
|
+
// protocol Esc arrives as CSI 27 u, and a byte comparison against "\x1b"
|
|
170
|
+
// left the overlay unanswerable. Everything unmatched is swallowed.
|
|
171
|
+
onInput: (data: string): void => {
|
|
172
|
+
if (settled || isKeyRelease(data)) return;
|
|
173
|
+
if (matchesKey(data, "up")) {
|
|
174
|
+
body.scroll = Math.max(0, body.scroll - 1);
|
|
175
|
+
tui.requestRender();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (matchesKey(data, "down")) {
|
|
179
|
+
body.scroll += 1;
|
|
180
|
+
tui.requestRender();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (matchesKey(data, "enter")) {
|
|
184
|
+
accept();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (matchesKey(data, "escape")) cancel();
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const handle = showClioOverlayFrame(tui, focus, {
|
|
192
|
+
anchor: "center",
|
|
193
|
+
width: fleetRunApprovalOverlayWidth(options.columns),
|
|
194
|
+
title: FLEET_RUN_APPROVAL_OVERLAY_TITLE,
|
|
195
|
+
footerHint: buildResponsiveHint(
|
|
196
|
+
[...(acceptable ? [{ key: "Enter", verb: "dispatch" }] : []), { key: "↑↓", verb: "scroll" }],
|
|
197
|
+
{ key: "Esc", verb: "cancel" },
|
|
198
|
+
),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
...handle,
|
|
203
|
+
hide(): void {
|
|
204
|
+
cancel();
|
|
205
|
+
handle.hide();
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/handoff` review: the document, before anything is written anywhere.
|
|
3
|
+
*
|
|
4
|
+
* The operator reads the rendered Markdown here and chooses. Enter accepts the
|
|
5
|
+
* document as it stands, `e` hands it to `$EDITOR` through the same external
|
|
6
|
+
* editor flow the composer uses and comes back with whatever was saved, and Esc
|
|
7
|
+
* cancels the whole handoff. Cancel is the important one: nothing has been
|
|
8
|
+
* written at the point this overlay opens, so leaving it costs the extraction
|
|
9
|
+
* round and nothing else.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
type Component,
|
|
14
|
+
isKeyRelease,
|
|
15
|
+
matchesKey,
|
|
16
|
+
type OverlayHandle,
|
|
17
|
+
type TUI,
|
|
18
|
+
wrapTextWithAnsi,
|
|
19
|
+
} from "../../engine/tui.js";
|
|
20
|
+
import { buildResponsiveHint, FocusBox, showClioOverlayFrame } from "../overlay-frame.js";
|
|
21
|
+
import { clioTheme, rule } from "../theme/index.js";
|
|
22
|
+
|
|
23
|
+
export const HANDOFF_REVIEW_OVERLAY_TITLE = "Handoff review";
|
|
24
|
+
|
|
25
|
+
const HANDOFF_REVIEW_MIN_WIDTH = 44;
|
|
26
|
+
const HANDOFF_REVIEW_MAX_WIDTH = 100;
|
|
27
|
+
/** Rows of document shown at once. Anything longer scrolls. */
|
|
28
|
+
export const HANDOFF_REVIEW_VISIBLE_ROWS = 20;
|
|
29
|
+
|
|
30
|
+
export function handoffReviewOverlayWidth(columns: number): number {
|
|
31
|
+
return Math.max(HANDOFF_REVIEW_MIN_WIDTH, Math.min(HANDOFF_REVIEW_MAX_WIDTH, columns - 4));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface OpenHandoffReviewOverlayOptions {
|
|
35
|
+
document: string;
|
|
36
|
+
goal: string;
|
|
37
|
+
/** Live terminal width, so the box tracks the window it opened in. */
|
|
38
|
+
columns: number;
|
|
39
|
+
/**
|
|
40
|
+
* Hand the current document to `$EDITOR` and return what came back, or null
|
|
41
|
+
* when the edit was cancelled or no editor is configured. The caller owns
|
|
42
|
+
* stopping and restarting the TUI around the child process.
|
|
43
|
+
*/
|
|
44
|
+
onEdit: (current: string) => string | null;
|
|
45
|
+
/** Enter: the operator accepts this exact text. */
|
|
46
|
+
onAccept: (document: string) => void;
|
|
47
|
+
/** Esc: the whole handoff is abandoned and nothing is written. */
|
|
48
|
+
onCancel: () => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface HandoffReviewOverlaySession extends OverlayHandle {
|
|
52
|
+
/** The document as it currently stands, including any external edit. */
|
|
53
|
+
document(): string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Render the body. Pure so the layout is testable without a TUI: the goal, a
|
|
58
|
+
* rule, then the scrolled document window.
|
|
59
|
+
*/
|
|
60
|
+
export function formatHandoffReviewBody(goal: string, document: string, width: number, scroll: number): string[] {
|
|
61
|
+
const theme = clioTheme();
|
|
62
|
+
const contentWidth = Math.max(1, Math.floor(width));
|
|
63
|
+
const lines: string[] = [];
|
|
64
|
+
for (const line of wrapTextWithAnsi(theme.fg("dim", `goal: ${goal}`), contentWidth)) lines.push(line);
|
|
65
|
+
lines.push(rule(theme, contentWidth));
|
|
66
|
+
|
|
67
|
+
const wrapped: string[] = [];
|
|
68
|
+
for (const raw of document.split("\n")) {
|
|
69
|
+
if (raw.length === 0) {
|
|
70
|
+
wrapped.push("");
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
for (const line of wrapTextWithAnsi(theme.fg("muted", raw), contentWidth)) wrapped.push(line);
|
|
74
|
+
}
|
|
75
|
+
const maxScroll = Math.max(0, wrapped.length - HANDOFF_REVIEW_VISIBLE_ROWS);
|
|
76
|
+
const start = Math.max(0, Math.min(scroll, maxScroll));
|
|
77
|
+
const window = wrapped.slice(start, start + HANDOFF_REVIEW_VISIBLE_ROWS);
|
|
78
|
+
for (const line of window) lines.push(line);
|
|
79
|
+
if (wrapped.length > HANDOFF_REVIEW_VISIBLE_ROWS) {
|
|
80
|
+
lines.push(theme.fg("dim", `(${start + 1}-${start + window.length} of ${wrapped.length} lines)`));
|
|
81
|
+
}
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
class HandoffReviewBody implements Component {
|
|
86
|
+
scroll = 0;
|
|
87
|
+
|
|
88
|
+
constructor(
|
|
89
|
+
private readonly goal: string,
|
|
90
|
+
private text: string,
|
|
91
|
+
) {}
|
|
92
|
+
|
|
93
|
+
setDocument(next: string): void {
|
|
94
|
+
this.text = next;
|
|
95
|
+
this.scroll = 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
document(): string {
|
|
99
|
+
return this.text;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
lineCount(): number {
|
|
103
|
+
return this.text.split("\n").length;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
render(width: number): string[] {
|
|
107
|
+
return formatHandoffReviewBody(this.goal, this.text, width, this.scroll);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
invalidate(): void {}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function openHandoffReviewOverlay(
|
|
114
|
+
tui: TUI,
|
|
115
|
+
options: OpenHandoffReviewOverlayOptions,
|
|
116
|
+
): HandoffReviewOverlaySession {
|
|
117
|
+
const body = new HandoffReviewBody(options.goal, options.document);
|
|
118
|
+
let settled = false;
|
|
119
|
+
|
|
120
|
+
const accept = (): void => {
|
|
121
|
+
if (settled) return;
|
|
122
|
+
settled = true;
|
|
123
|
+
options.onAccept(body.document());
|
|
124
|
+
};
|
|
125
|
+
const cancel = (): void => {
|
|
126
|
+
if (settled) return;
|
|
127
|
+
settled = true;
|
|
128
|
+
options.onCancel();
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const focus = new FocusBox(body, {
|
|
132
|
+
// Keys are matched by name, never by raw bytes: under the kitty keyboard
|
|
133
|
+
// protocol Esc arrives as CSI 27 u, and a byte comparison against "\x1b"
|
|
134
|
+
// left the overlay unanswerable. Everything unmatched is swallowed.
|
|
135
|
+
onInput: (data: string): void => {
|
|
136
|
+
if (settled || isKeyRelease(data)) return;
|
|
137
|
+
if (matchesKey(data, "up")) {
|
|
138
|
+
body.scroll = Math.max(0, body.scroll - 1);
|
|
139
|
+
tui.requestRender();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (matchesKey(data, "down")) {
|
|
143
|
+
body.scroll = Math.min(body.lineCount(), body.scroll + 1);
|
|
144
|
+
tui.requestRender();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (matchesKey(data, "enter")) {
|
|
148
|
+
accept();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (matchesKey(data, "escape")) {
|
|
152
|
+
cancel();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (matchesKey(data, "e") || matchesKey(data, "shift+e")) {
|
|
156
|
+
const edited = options.onEdit(body.document());
|
|
157
|
+
if (edited !== null) body.setDocument(edited);
|
|
158
|
+
tui.requestRender(true);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const handle = showClioOverlayFrame(tui, focus, {
|
|
164
|
+
anchor: "center",
|
|
165
|
+
width: handoffReviewOverlayWidth(options.columns),
|
|
166
|
+
title: HANDOFF_REVIEW_OVERLAY_TITLE,
|
|
167
|
+
footerHint: buildResponsiveHint(
|
|
168
|
+
[
|
|
169
|
+
{ key: "Enter", verb: "accept" },
|
|
170
|
+
{ key: "e", verb: "edit" },
|
|
171
|
+
{ key: "↑↓", verb: "scroll" },
|
|
172
|
+
],
|
|
173
|
+
{ key: "Esc", verb: "cancel" },
|
|
174
|
+
),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
...handle,
|
|
179
|
+
document: () => body.document(),
|
|
180
|
+
hide(): void {
|
|
181
|
+
cancel();
|
|
182
|
+
handle.hide();
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|