@iowarp/clio-coder 0.3.4 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -2
- package/CONTRIBUTING.md +6 -6
- package/README.md +16 -5
- package/dist/{acp-S5R4RR5B.js → acp-SK4MD6MM.js} +11 -11
- package/dist/{agents-P6DMMVZY.js → agents-2FN2K6ME.js} +33 -26
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-2XCZLPKS.js → auth-QIYZWM5I.js} +15 -15
- package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
- package/dist/{chunk-VAWWTKDP.js → chunk-3HAPLH5M.js} +11 -11
- package/dist/{chunk-YCWGATWI.js → chunk-465YSENW.js} +3 -3
- package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
- package/dist/{chunk-UZHIZC5S.js → chunk-4DWFMQDR.js} +61 -76
- package/dist/{chunk-ZWMF7253.js → chunk-5C3AQNDW.js} +328 -9
- package/dist/{chunk-22NAGB7X.js → chunk-5C77SEEY.js} +5 -94
- package/dist/{chunk-WPQLXFOZ.js → chunk-5FR74PWO.js} +3 -2
- package/dist/{chunk-35MKKU5R.js → chunk-5UJ6ECTS.js} +18 -10
- package/dist/{chunk-BRXQQJFP.js → chunk-6M7VS3J3.js} +571 -50
- package/dist/{chunk-QQK64KLB.js → chunk-6TUKSZVF.js} +141 -23
- package/dist/{chunk-N4CZJQRK.js → chunk-AB4XIIVB.js} +8 -6
- package/dist/{chunk-KRPY7NTG.js → chunk-BMWK7ZIZ.js} +14 -20
- package/dist/{chunk-4BPJXDWC.js → chunk-C4JBQ5SR.js} +30 -14
- package/dist/{chunk-ZYKPLLNQ.js → chunk-CEYBNUGC.js} +821 -83
- package/dist/{chunk-VEZEGCGW.js → chunk-D4MDIG46.js} +20 -18
- package/dist/chunk-DJNLUABN.js +843 -0
- package/dist/{chunk-BP4OYD6A.js → chunk-DMD2AGVS.js} +21 -2
- package/dist/{chunk-KOHPCX4K.js → chunk-DOOEX22V.js} +2 -2
- package/dist/chunk-DQA7QLMD.js +123 -0
- package/dist/chunk-DR52UMZW.js +21 -0
- package/dist/{chunk-3HZ5RWN2.js → chunk-EBEFWSGL.js} +9 -7
- package/dist/{chunk-EDRHSCIE.js → chunk-EELBMBT6.js} +128 -13
- package/dist/{chunk-HV5X7OR2.js → chunk-EOOQZZDE.js} +16 -14
- package/dist/{chunk-WR67VIZY.js → chunk-FOT2FX5J.js} +63 -5
- package/dist/{chunk-BPGS2WCQ.js → chunk-GEYXPTRF.js} +2 -1
- package/dist/{chunk-FYYLNIL5.js → chunk-GH5622CP.js} +2 -2
- package/dist/{chunk-BEY543CS.js → chunk-GOXNB3AO.js} +5 -2
- package/dist/chunk-GWS3VEIW.js +195 -0
- package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
- package/dist/chunk-HWUFFB6L.js +83 -0
- package/dist/{chunk-X6COSD2O.js → chunk-J7PIKKWC.js} +8 -436
- package/dist/{chunk-NILBFAPG.js → chunk-JNXPYBB4.js} +2 -2
- package/dist/{chunk-4VP4KH3K.js → chunk-JRIO5UD2.js} +4 -4
- package/dist/{chunk-K6WL7QZT.js → chunk-JTSEDYVQ.js} +7 -7
- package/dist/{chunk-QKMUKYO7.js → chunk-KCMKRQX4.js} +236 -84
- package/dist/chunk-KZ2H5X4G.js +1026 -0
- package/dist/{chunk-A2GZF7DC.js → chunk-LADCF22A.js} +13 -13
- package/dist/chunk-LCGCVYZ4.js +57 -0
- package/dist/chunk-M4AKACEO.js +382 -0
- package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
- package/dist/{chunk-4JUF2NNX.js → chunk-MXI6J5JF.js} +7 -7
- package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
- package/dist/{chunk-TTNYS3EA.js → chunk-OB5HIGJY.js} +1 -1
- package/dist/{chunk-7RXG6QRZ.js → chunk-OBMAI2DP.js} +61 -840
- package/dist/{chunk-5M54SPOL.js → chunk-ODFEOB4F.js} +161 -5
- package/dist/chunk-PD3MESLB.js +242 -0
- package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
- package/dist/{chunk-VMNQ6OZA.js → chunk-QCTRSGHQ.js} +963 -786
- package/dist/chunk-RVG5JXAL.js +41 -0
- package/dist/{chunk-RD5U66HV.js → chunk-SROCI7ZU.js} +7 -7
- package/dist/{chunk-MFFY33HR.js → chunk-THKY7CD7.js} +466 -205
- package/dist/{chunk-34475P3I.js → chunk-TSHXZTOQ.js} +5 -4
- package/dist/{chunk-PCZJO5TI.js → chunk-UFQ3F4FW.js} +13 -178
- package/dist/{chunk-AD2SYQYC.js → chunk-UHXRNZ2J.js} +121 -3
- package/dist/chunk-UND3GU2L.js +103 -0
- package/dist/{chunk-QQL5RT5M.js → chunk-UUANF5CR.js} +2323 -2114
- package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
- package/dist/chunk-UVDSQ6LW.js +472 -0
- package/dist/{chunk-QWU7ZBO7.js → chunk-VQNODYQ4.js} +215 -56
- package/dist/chunk-VREKEFLL.js +37 -0
- package/dist/{chunk-2TZWSW76.js → chunk-WHGPSPT5.js} +2 -2
- package/dist/{chunk-TW3WDMVS.js → chunk-WHJYKASB.js} +2 -2
- package/dist/{chunk-MEQ45TQ4.js → chunk-WJHBC77E.js} +21 -7
- package/dist/{chunk-HXG4IURW.js → chunk-X2KV5FXT.js} +2 -2
- package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
- package/dist/{chunk-2LZI5CAG.js → chunk-XEGB6BCN.js} +228 -36
- package/dist/{chunk-E25LMLRW.js → chunk-YD734TPH.js} +2 -2
- package/dist/{verifiers-4UUM6TEE.js → chunk-YTYFXUI3.js} +121 -372
- package/dist/{chunk-3JLKSKD7.js → chunk-ZGH7FGS5.js} +17 -7
- package/dist/{chunk-VSNATDE6.js → chunk-ZZMN5OM4.js} +2 -2
- package/dist/cli/index.js +34 -32
- package/dist/{clio-J5JIOIDS.js → clio-WBVQEBKO.js} +7 -7
- package/dist/{code-nav-AXCXSBHX.js → code-nav-FGGFIE7L.js} +7 -7
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-KELWS457.js → components-F7OEATSO.js} +5 -5
- package/dist/{config-OEBMIN2U.js → config-TRBL3RCF.js} +48 -41
- package/dist/{configure-PUQOSIXQ.js → configure-OLCVPHNM.js} +17 -17
- package/dist/{context-URSXPBCK.js → context-MJIJ6GOX.js} +12 -12
- package/dist/{context-EKDCKUUZ.js → context-WFPKQSM6.js} +26 -9
- package/dist/{context-MGSE4Z2T.js → context-XEWE3MOJ.js} +44 -37
- package/dist/{context-clear-KDAJRNUK.js → context-clear-KNOS2JPB.js} +44 -37
- package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
- package/dist/{context-working-set-SBKMPPI2.js → context-working-set-EUXAZI6N.js} +14 -13
- package/dist/{dispatch-runner-MSWN72NK.js → dispatch-runner-B7MTOVKL.js} +321 -60
- package/dist/{docs-2C2LTVT2.js → docs-FLJTIDSE.js} +5 -5
- package/dist/{doctor-7BSE27PJ.js → doctor-RN4YKO2X.js} +15 -15
- package/dist/{eval-IZGDOO4H.js → eval-RUBJVSNQ.js} +52 -236
- package/dist/{evidence-SR7WXB5B.js → evidence-JZNBUOQZ.js} +39 -33
- package/dist/{evolve-K7VE2CBX.js → evolve-FJVC4KKI.js} +39 -33
- package/dist/{extensions-QVDOHDGJ.js → extensions-IQL36S7K.js} +5 -5
- package/dist/{fleet-7XMJNQNF.js → fleet-BDKYJFCP.js} +243 -370
- package/dist/fleet-commands-ZFIWZSB3.js +70 -0
- package/dist/fleet-graph-Y6HPXIVF.js +125 -0
- package/dist/fleet-new-RDVJLHHH.js +48 -0
- package/dist/{fleet-preflight-AQNAH644.js → fleet-preflight-BHSNPBMH.js} +2 -2
- package/dist/fleet-validate-BIYREGIK.js +79 -0
- package/dist/{init-JGNPAYXT.js → init-LQUB5COQ.js} +57 -48
- package/dist/library-NJAHIGG4.js +217 -0
- package/dist/memory-OG6HOYKM.js +472 -0
- package/dist/{models-ZMMLFJNN.js → models-5ZG5XY7J.js} +23 -22
- package/dist/{monitor-2F3T5KHP.js → monitor-TJ7AMTGB.js} +69 -35
- package/dist/{orchestrator-ORHT43JB.js → orchestrator-WZYB54DM.js} +4868 -1189
- package/dist/{paths-UXLN5YYZ.js → paths-XUC7GS6E.js} +5 -5
- package/dist/{reset-NXGTYNUO.js → reset-PXQT45IY.js} +8 -8
- package/dist/{run-RF4WJGMT.js → run-FQ74YF62.js} +82 -62
- package/dist/{share-UT3W6E4M.js → share-FW7SVCL3.js} +34 -10
- package/dist/{skills-PSACKC5Q.js → skills-7E7IRB3R.js} +25 -9
- package/dist/{skills-eval-WJSI55RZ.js → skills-eval-LI75W6OK.js} +43 -35
- package/dist/{targets-PIIRAOYS.js → targets-4CIFKCTW.js} +27 -24
- package/dist/{terminal-lease-ULWXWNVY.js → terminal-lease-WUZY7ZV5.js} +5 -4
- package/dist/{uninstall-FZCQCDKC.js → uninstall-7FV7IP4E.js} +5 -5
- package/dist/{upgrade-346TZ6AV.js → upgrade-K2HVIVMQ.js} +21 -20
- package/dist/{usage-6KKXR32N.js → usage-GTZELZQX.js} +159 -59
- package/dist/verifiers-RLAHT27O.js +336 -0
- package/dist/{verify-X5HDROLA.js → verify-BX3BRKH5.js} +7 -6
- package/dist/{wiki-generate-7STOCIFZ.js → wiki-generate-ASIFASCN.js} +58 -48
- package/dist/worker/entry.js +98 -84
- package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
- package/docs/README.md +4 -3
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-placement.md +1 -2
- package/docs/artifact-versions.md +10 -6
- package/docs/built-in-agents.md +26 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +90 -8
- package/docs/configuration-and-targets.md +90 -2
- package/docs/context-engine.md +4 -2
- package/docs/context-working-set.md +4 -4
- package/docs/development-pipeline.md +1 -1
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +4 -4
- package/docs/documentation-guide.md +4 -4
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +4 -45
- package/docs/evidence-and-memory.md +70 -10
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +4 -1
- package/docs/extensions-and-sharing.md +6 -2
- package/docs/fleet-demo-runbook.md +2 -2
- package/docs/fleet-dispatch.md +224 -11
- package/docs/git-commit-provenance.md +2 -2
- package/docs/glossary.md +1 -1
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +20 -2
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +55 -8
- package/docs/proactive-memory.md +26 -16
- package/docs/prompt-envelope-and-tools.md +4 -2
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +83 -65
- package/docs/resource-library.md +59 -0
- package/docs/safety-model.md +29 -7
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +37 -1
- package/docs/skills-marketplace.md +16 -3
- package/docs/tool-usage.md +14 -7
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +38 -4
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +7 -4
- package/src/cli/agents.ts +2 -3
- package/src/cli/argv.ts +14 -1
- package/src/cli/fleet-commands.ts +37 -0
- package/src/cli/fleet-graph.ts +102 -0
- package/src/cli/fleet-new.ts +36 -0
- package/src/cli/fleet-preflight.ts +121 -0
- package/src/cli/fleet-validate.ts +30 -0
- package/src/cli/fleet.ts +188 -335
- package/src/cli/index.ts +4 -2
- package/src/cli/library.ts +190 -0
- package/src/cli/memory.ts +272 -10
- package/src/cli/modes/json-stream.ts +2 -2
- package/src/cli/modes/print.ts +12 -1
- package/src/cli/run.ts +22 -2
- package/src/cli/share.ts +13 -1
- package/src/cli/targets.ts +12 -3
- package/src/cli/usage.ts +160 -20
- package/src/core/bus-events.ts +7 -0
- package/src/core/commit-attribution.ts +4 -4
- package/src/core/config.ts +130 -0
- package/src/core/defaults.ts +81 -0
- package/src/core/response-model-id.ts +134 -0
- package/src/core/toml.ts +62 -0
- package/src/core/workspace-files.ts +0 -1
- package/src/domains/agents/builtins/architect.md +2 -1
- package/src/domains/agents/builtins/oracle.md +33 -0
- package/src/domains/agents/catalog.ts +18 -5
- package/src/domains/agents/fleet-contract.ts +278 -16
- package/src/domains/agents/index.ts +14 -0
- package/src/domains/agents/recipe.ts +54 -14
- package/src/domains/agents/result-contract.ts +242 -5
- package/src/domains/config/classify.ts +4 -0
- package/src/domains/context/bootstrap.ts +36 -27
- package/src/domains/context/project-metadata.ts +19 -63
- package/src/domains/context/prompt-context.ts +8 -0
- package/src/domains/context/working-set/policies/index.ts +3 -4
- package/src/domains/dispatch/active-route-planner.ts +14 -0
- package/src/domains/dispatch/backoff.ts +2 -1
- package/src/domains/dispatch/budget-envelope.ts +396 -0
- package/src/domains/dispatch/capability-match.ts +1 -0
- package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
- package/src/domains/dispatch/contract.ts +36 -0
- package/src/domains/dispatch/delegation-plan.ts +167 -0
- package/src/domains/dispatch/execution-plan.ts +76 -5
- package/src/domains/dispatch/execution-role.ts +3 -1
- package/src/domains/dispatch/execution-scheduler.ts +183 -67
- package/src/domains/dispatch/extension.ts +339 -36
- package/src/domains/dispatch/fleet-gate.ts +14 -0
- package/src/domains/dispatch/fleet-plan.ts +63 -3
- package/src/domains/dispatch/fleet-run.ts +737 -0
- package/src/domains/dispatch/gate-role-prompts.ts +9 -0
- package/src/domains/dispatch/host-verification.ts +178 -0
- package/src/domains/dispatch/index.ts +38 -0
- package/src/domains/dispatch/intent.ts +159 -0
- package/src/domains/dispatch/orphan-recovery.ts +1 -0
- package/src/domains/dispatch/receipt-integrity.ts +12 -4
- package/src/domains/dispatch/state.ts +37 -3
- package/src/domains/dispatch/types.ts +61 -9
- package/src/domains/dispatch/validation.ts +80 -6
- package/src/domains/dispatch/worker-spawn.ts +14 -3
- package/src/domains/eval/metrics/evidence.ts +0 -116
- package/src/domains/eval/metrics/invariants.ts +1 -1
- package/src/domains/eval/runners/clio-run.ts +1 -10
- package/src/domains/eval/runners/external-command.ts +2 -29
- package/src/domains/eval/schema/suite.ts +0 -7
- package/src/domains/eval/suites/run.ts +1 -7
- package/src/domains/evidence/trust-status.ts +10 -1
- package/src/domains/memory/index.ts +22 -0
- package/src/domains/memory/operations.ts +58 -1
- package/src/domains/memory/promotion.ts +281 -0
- package/src/domains/memory/prompt-section.ts +25 -5
- package/src/domains/memory/proposal.ts +51 -7
- package/src/domains/memory/task-bank.ts +3 -2
- package/src/domains/memory/task-memory-handoff.ts +181 -24
- package/src/domains/memory/task-memory-policy.ts +3 -1
- package/src/domains/memory/types.ts +37 -0
- package/src/domains/memory/validate.ts +178 -0
- package/src/domains/middleware/index.ts +15 -0
- package/src/domains/middleware/memory-intervention.ts +35 -25
- package/src/domains/middleware/runtime.ts +6 -0
- package/src/domains/middleware/skills-reminder.ts +19 -4
- package/src/domains/middleware/stalled-turn.ts +43 -1
- package/src/domains/middleware/types.ts +10 -0
- package/src/domains/middleware/watchdog.ts +281 -0
- package/src/domains/observability/contract.ts +9 -2
- package/src/domains/observability/cost.ts +31 -4
- package/src/domains/observability/extension.ts +2 -2
- package/src/domains/observability/index.ts +10 -0
- package/src/domains/observability/out-of-turn-usage.ts +223 -0
- package/src/domains/providers/index.ts +3 -0
- package/src/domains/providers/model-discovery.ts +9 -0
- package/src/domains/providers/runtime-resolution.ts +38 -1
- package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
- package/src/domains/providers/types/context-window-slots.ts +18 -0
- package/src/domains/providers/types/runtime-descriptor.ts +3 -1
- package/src/domains/resources/index.ts +20 -0
- package/src/domains/resources/library.ts +326 -0
- package/src/domains/resources/skills/marketplace.ts +37 -12
- package/src/domains/safety/call-target.ts +211 -14
- package/src/domains/safety/decision-presentation.ts +268 -0
- package/src/domains/safety/redaction.ts +73 -0
- package/src/domains/session/context-ledger.ts +10 -1
- package/src/domains/session/decision-board.ts +4 -0
- package/src/domains/session/entries.ts +3 -0
- package/src/domains/session/handoff.ts +629 -0
- package/src/domains/session/history.ts +68 -19
- package/src/domains/session/usage.ts +24 -7
- package/src/domains/share/archive.ts +67 -2
- package/src/engine/acp/event-mapper.ts +7 -0
- package/src/engine/acp/server.ts +29 -2
- package/src/engine/apis/lmstudio.ts +25 -4
- package/src/engine/apis/openai-completions.ts +147 -22
- package/src/engine/claude/sdk-runtime.ts +8 -2
- package/src/engine/claude/tool-safety.ts +13 -0
- package/src/engine/loop-guard.ts +27 -3
- package/src/engine/worker-events.ts +4 -3
- package/src/engine/worker-runtime.ts +59 -54
- package/src/entry/orchestrator.ts +55 -1
- package/src/interactive/bus-notices.ts +26 -0
- package/src/interactive/chat-loop-messages.ts +22 -0
- package/src/interactive/chat-loop.ts +248 -1
- package/src/interactive/chat-renderer.ts +41 -3
- package/src/interactive/clio-editor.ts +44 -7
- package/src/interactive/context-overlay.ts +43 -5
- package/src/interactive/cost-overlay.ts +70 -11
- package/src/interactive/council-dispatch.ts +30 -0
- package/src/interactive/council-grid.ts +213 -0
- package/src/interactive/council.ts +99 -0
- package/src/interactive/dispatch-board.ts +471 -50
- package/src/interactive/fleet-run-preview.ts +307 -0
- package/src/interactive/footer/notifications.ts +219 -0
- package/src/interactive/footer/widgets.ts +13 -0
- package/src/interactive/handoff-round.ts +56 -0
- package/src/interactive/interactive-application.ts +49 -2
- package/src/interactive/interactive-event-projection.ts +9 -1
- package/src/interactive/interactive-input-runtime.ts +11 -1
- package/src/interactive/interactive-presentation.ts +11 -1
- package/src/interactive/interactive-slash-runtime.ts +52 -2
- package/src/interactive/interactive-subscriptions.ts +14 -2
- package/src/interactive/memory-overlay.ts +89 -4
- package/src/interactive/oracle.ts +179 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +7 -1
- package/src/interactive/overlay-frame.ts +5 -2
- package/src/interactive/overlay-general-openers.ts +230 -2
- package/src/interactive/overlay-key-routing.ts +58 -2
- package/src/interactive/overlay-lifecycle.ts +52 -5
- package/src/interactive/overlay-permission-lifecycle.ts +33 -8
- package/src/interactive/overlay-resource-openers.ts +11 -3
- package/src/interactive/overlay-session-lifecycle.ts +234 -2
- package/src/interactive/overlay-transitions.ts +11 -0
- package/src/interactive/overlays/ask-user.ts +74 -30
- package/src/interactive/overlays/decisions.ts +3 -1
- package/src/interactive/overlays/fleet-run-approval.ts +208 -0
- package/src/interactive/overlays/handoff-review.ts +185 -0
- package/src/interactive/overlays/library-install-confirm.ts +151 -0
- package/src/interactive/overlays/list-overlay.ts +168 -2
- package/src/interactive/overlays/settings.ts +101 -4
- package/src/interactive/overlays/side-question.ts +139 -0
- package/src/interactive/overlays/skills-hub.ts +401 -15
- package/src/interactive/permission-hint.ts +35 -0
- package/src/interactive/permission-overlay.ts +95 -45
- package/src/interactive/renderers/tool-execution.ts +19 -49
- package/src/interactive/session-last-turn.ts +8 -1
- package/src/interactive/session-usage-reseed.ts +36 -10
- package/src/interactive/side-question.ts +171 -0
- package/src/interactive/slash-commands.ts +434 -7
- package/src/interactive/slash-spec.ts +19 -6
- package/src/interactive/status/summary.ts +5 -0
- package/src/interactive/status/types.ts +5 -0
- package/src/interactive/terminal-lease.ts +1 -0
- package/src/interactive/theme/tokens.ts +30 -0
- package/src/interactive/turn-context.ts +96 -23
- package/src/interactive/turn-middleware.ts +16 -1
- package/src/interactive/turn-runtime.ts +37 -8
- package/src/interactive/turn-state.ts +3 -0
- package/src/interactive/watchdog-run.ts +75 -0
- package/src/interactive/worker-progress.ts +440 -0
- package/src/interactive/worker-share.ts +56 -1
- package/src/interactive/worker-stream.ts +58 -110
- package/src/tools/agent-tools.ts +28 -3
- package/src/tools/ask-user.ts +21 -1
- package/src/tools/bootstrap.ts +3 -0
- package/src/tools/compete-worktrees.ts +13 -79
- package/src/tools/context/index.ts +2 -2
- package/src/tools/dispatch-admission.ts +242 -8
- package/src/tools/dispatch-arguments.ts +65 -1
- package/src/tools/dispatch-event-text.ts +19 -0
- package/src/tools/dispatch-plan.ts +136 -6
- package/src/tools/dispatch-runner.ts +319 -13
- package/src/tools/dispatch-types.ts +20 -1
- package/src/tools/dispatch.ts +96 -3
- package/src/tools/monitor.ts +31 -0
- package/src/tools/profiles.ts +18 -4
- package/src/tools/registry.ts +15 -5
- package/src/tools/result-disposition.ts +156 -0
- package/src/tools/result-shaping.ts +59 -1
- package/src/tools/task-worktree.ts +238 -0
- package/src/tools/verify/authoring.ts +116 -55
- package/src/tools/verify/scripts.ts +62 -0
- package/src/tools/worker-evidence.ts +21 -1
- package/src/worker/spec-contract.ts +44 -3
- package/dist/chunk-EFADSJET.js +0 -18
- package/dist/chunk-HC4CLZ2Y.js +0 -68
- package/dist/memory-4ALKDJ4Q.js +0 -246
- package/src/domains/eval/metrics/chaos-stream.ts +0 -93
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/btw` overlay: the question, then the answer as it streams.
|
|
3
|
+
*
|
|
4
|
+
* The overlay is the whole surface for a side question. Nothing it shows is
|
|
5
|
+
* appended to the transcript, so closing it is the end of the exchange: the
|
|
6
|
+
* answer was for the operator, in the moment, and the session never saw it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { type Component, type OverlayHandle, type TUI, wrapTextWithAnsi } from "../../engine/tui.js";
|
|
10
|
+
import { buildResponsiveHint, showClioOverlayFrame } from "../overlay-frame.js";
|
|
11
|
+
import { clioTheme, GLYPH, rule } from "../theme/index.js";
|
|
12
|
+
|
|
13
|
+
export const SIDE_QUESTION_OVERLAY_TITLE = "Side question";
|
|
14
|
+
|
|
15
|
+
/** What the overlay is showing right now. */
|
|
16
|
+
export type SideQuestionOverlayPhase =
|
|
17
|
+
| { kind: "streaming"; text: string }
|
|
18
|
+
| { kind: "answered"; text: string }
|
|
19
|
+
| { kind: "aborted"; text: string }
|
|
20
|
+
| { kind: "error"; reason: string };
|
|
21
|
+
|
|
22
|
+
export interface SideQuestionOverlaySession extends OverlayHandle {
|
|
23
|
+
/** Replace the streamed answer text. */
|
|
24
|
+
setAnswer(text: string): void;
|
|
25
|
+
/** Settle the overlay on a final phase. */
|
|
26
|
+
settle(phase: SideQuestionOverlayPhase): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface OpenSideQuestionOverlayOptions {
|
|
30
|
+
question: string;
|
|
31
|
+
/** Live terminal width, so the box tracks the window it opened in. */
|
|
32
|
+
columns: number;
|
|
33
|
+
/** Esc: abort a streaming round, then close. */
|
|
34
|
+
onClose: () => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const SIDE_QUESTION_OVERLAY_MIN_WIDTH = 44;
|
|
38
|
+
const SIDE_QUESTION_OVERLAY_MAX_WIDTH = 100;
|
|
39
|
+
|
|
40
|
+
export function sideQuestionOverlayWidth(columns: number): number {
|
|
41
|
+
return Math.max(SIDE_QUESTION_OVERLAY_MIN_WIDTH, Math.min(SIDE_QUESTION_OVERLAY_MAX_WIDTH, columns - 4));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Render the overlay body. Pure so the layout is unit-testable without a TUI:
|
|
46
|
+
* the question in dim above a rule, then the answer, then one status line that
|
|
47
|
+
* says whether the round is still running, cancelled, or failed.
|
|
48
|
+
*/
|
|
49
|
+
export function formatSideQuestionBody(
|
|
50
|
+
question: string,
|
|
51
|
+
phase: SideQuestionOverlayPhase,
|
|
52
|
+
width: number,
|
|
53
|
+
spinner: string,
|
|
54
|
+
): string[] {
|
|
55
|
+
const theme = clioTheme();
|
|
56
|
+
const contentWidth = Math.max(1, Math.floor(width));
|
|
57
|
+
const lines: string[] = [];
|
|
58
|
+
for (const line of wrapTextWithAnsi(theme.fg("dim", `${GLYPH.user} ${question}`), contentWidth)) lines.push(line);
|
|
59
|
+
lines.push(rule(theme, contentWidth));
|
|
60
|
+
if (phase.kind === "error") {
|
|
61
|
+
for (const line of wrapTextWithAnsi(theme.fg("error", phase.reason), contentWidth)) lines.push(line);
|
|
62
|
+
return lines;
|
|
63
|
+
}
|
|
64
|
+
const body = phase.text.trim();
|
|
65
|
+
if (body.length > 0) {
|
|
66
|
+
for (const paragraph of body.split("\n")) {
|
|
67
|
+
if (paragraph.length === 0) {
|
|
68
|
+
lines.push("");
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
for (const line of wrapTextWithAnsi(theme.fg("muted", paragraph), contentWidth)) lines.push(line);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (phase.kind === "streaming") {
|
|
75
|
+
lines.push(theme.fg("dim", body.length > 0 ? spinner : `${spinner} asking…`));
|
|
76
|
+
} else if (phase.kind === "aborted") {
|
|
77
|
+
lines.push(theme.fg("warning", "cancelled"));
|
|
78
|
+
} else if (body.length === 0) {
|
|
79
|
+
lines.push(theme.fg("dim", "the model returned no text"));
|
|
80
|
+
}
|
|
81
|
+
return lines;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const SPINNER_FRAMES = ["·", "•", "●", "•"] as const;
|
|
85
|
+
|
|
86
|
+
class SideQuestionOverlayBody implements Component {
|
|
87
|
+
private phase: SideQuestionOverlayPhase = { kind: "streaming", text: "" };
|
|
88
|
+
private frame = 0;
|
|
89
|
+
|
|
90
|
+
constructor(private readonly question: string) {}
|
|
91
|
+
|
|
92
|
+
set(phase: SideQuestionOverlayPhase): void {
|
|
93
|
+
this.phase = phase;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
render(width: number): string[] {
|
|
97
|
+
if (this.phase.kind === "streaming") this.frame = (this.frame + 1) % SPINNER_FRAMES.length;
|
|
98
|
+
return formatSideQuestionBody(this.question, this.phase, width, SPINNER_FRAMES[this.frame] ?? "·");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
invalidate(): void {}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Mount the overlay. `setAnswer` is called from the stream; `settle` closes the
|
|
106
|
+
* round's live state without closing the overlay, so the operator reads the
|
|
107
|
+
* answer and leaves with Esc.
|
|
108
|
+
*/
|
|
109
|
+
export function openSideQuestionOverlay(tui: TUI, options: OpenSideQuestionOverlayOptions): SideQuestionOverlaySession {
|
|
110
|
+
const body = new SideQuestionOverlayBody(options.question);
|
|
111
|
+
let settled = false;
|
|
112
|
+
const handle = showClioOverlayFrame(tui, body, {
|
|
113
|
+
anchor: "center",
|
|
114
|
+
width: sideQuestionOverlayWidth(options.columns),
|
|
115
|
+
title: SIDE_QUESTION_OVERLAY_TITLE,
|
|
116
|
+
// Esc means two things here and the footer names the live one: it aborts a
|
|
117
|
+
// round that is still streaming, and it closes one that has settled.
|
|
118
|
+
footerHint: (innerWidth) => buildResponsiveHint([], { key: "Esc", verb: settled ? "close" : "cancel" })(innerWidth),
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
...handle,
|
|
122
|
+
setAnswer(text: string): void {
|
|
123
|
+
if (settled) return;
|
|
124
|
+
body.set({ kind: "streaming", text });
|
|
125
|
+
body.invalidate();
|
|
126
|
+
tui.requestRender();
|
|
127
|
+
},
|
|
128
|
+
settle(phase: SideQuestionOverlayPhase): void {
|
|
129
|
+
settled = true;
|
|
130
|
+
body.set(phase);
|
|
131
|
+
body.invalidate();
|
|
132
|
+
tui.requestRender();
|
|
133
|
+
},
|
|
134
|
+
hide(): void {
|
|
135
|
+
options.onClose();
|
|
136
|
+
handle.hide();
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -1,28 +1,74 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
LibraryDiscoveryResult,
|
|
3
|
+
LibraryEntry,
|
|
4
|
+
LibraryEntryKind,
|
|
5
|
+
LibraryInstallPlan,
|
|
6
|
+
LibraryRequirementStatus,
|
|
2
7
|
MarketplaceDiscoveryResult,
|
|
3
8
|
MarketplaceSkill,
|
|
4
9
|
ResourceList,
|
|
5
10
|
Skill,
|
|
6
11
|
} from "../../domains/resources/index.js";
|
|
7
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
classifyLibraryRequirements,
|
|
14
|
+
discoverLibrary,
|
|
15
|
+
discoverMarketplaceSkills,
|
|
16
|
+
installLibraryPlan,
|
|
17
|
+
libraryEntryInstalled,
|
|
18
|
+
libraryEntryPin,
|
|
19
|
+
libraryEntryRef,
|
|
20
|
+
libraryInstallPath,
|
|
21
|
+
MARKETPLACE_UNCONFIGURED,
|
|
22
|
+
planLibraryInstall,
|
|
23
|
+
} from "../../domains/resources/index.js";
|
|
8
24
|
import type { OverlayHandle, TUI } from "../../engine/tui.js";
|
|
9
25
|
import type { NoticeLevel } from "../command-output.js";
|
|
10
26
|
import { clioTheme, GLYPH } from "../theme/index.js";
|
|
11
|
-
import
|
|
27
|
+
import type { LibraryInstallConfirmSubject } from "./library-install-confirm.js";
|
|
28
|
+
import { openLibraryInstallConfirmOverlay } from "./library-install-confirm.js";
|
|
29
|
+
import { type ListOverlayItem, type ListOverlayTab, openListOverlay } from "./list-overlay.js";
|
|
12
30
|
|
|
13
31
|
/**
|
|
14
|
-
* The Skills Hub: one multipane surface for every
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
32
|
+
* The Skills Hub: one multipane surface for every resource Clio can reach.
|
|
33
|
+
*
|
|
34
|
+
* The Skills tab is the original view. Installed skills group by scope,
|
|
35
|
+
* marketplace rows come from the same local marketplace lookup the installer
|
|
36
|
+
* and `/skill <name>` resolve through, Enter inserts the invocation into the
|
|
37
|
+
* editor, and `i` installs in place. The hub lists nothing the resolver cannot
|
|
38
|
+
* resolve; when the lookup is empty the hub says so and names the remedy
|
|
39
|
+
* instead of drawing an inventory.
|
|
40
|
+
*
|
|
41
|
+
* The Agents, Prompts, and Fleets tabs are the interactive half of the resource
|
|
42
|
+
* library. Their rows come from the same `discoverLibrary()` the CLI's
|
|
43
|
+
* `library list --kind` reads, their install runs the same plan-then-write pair
|
|
44
|
+
* `library add` runs behind the same confirmation the `--yes` gate expresses,
|
|
45
|
+
* and their requirement gate is the same classifier. Nothing here reaches
|
|
46
|
+
* around the library domain into a private path.
|
|
20
47
|
*/
|
|
21
48
|
|
|
22
49
|
const GROUP_PROJECT = "Project";
|
|
23
50
|
const GROUP_USER = "User";
|
|
24
51
|
const GROUP_MARKETPLACE = "Marketplace";
|
|
25
52
|
const GROUP_DIAGNOSTICS = "Diagnostics";
|
|
53
|
+
const GROUP_INSTALLED = "Installed";
|
|
54
|
+
const GROUP_AVAILABLE = "Available";
|
|
55
|
+
|
|
56
|
+
/** The hub's tabs, in the order ←/→ walks them. `skill` is the original view. */
|
|
57
|
+
export const LIBRARY_TABS: ReadonlyArray<{ id: LibraryEntryKind; label: string }> = [
|
|
58
|
+
{ id: "skill", label: "Skills" },
|
|
59
|
+
{ id: "agent", label: "Agents" },
|
|
60
|
+
{ id: "prompt", label: "Prompts" },
|
|
61
|
+
{ id: "fleet", label: "Fleets" },
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
export const SKILLS_HUB_TITLE = "Skills Hub";
|
|
65
|
+
|
|
66
|
+
/** Row id prefix for a library row, which is how an action tells the tabs apart. */
|
|
67
|
+
export const LIBRARY_ROW_PREFIX = "library:";
|
|
68
|
+
|
|
69
|
+
export function isLibraryTab(value: string): value is LibraryEntryKind {
|
|
70
|
+
return LIBRARY_TABS.some((tab) => tab.id === value);
|
|
71
|
+
}
|
|
26
72
|
|
|
27
73
|
/** @internal exported for contract tests */
|
|
28
74
|
export const SKILLS_HUB_EMPTY =
|
|
@@ -37,6 +83,50 @@ export interface SkillsHubDeps {
|
|
|
37
83
|
onClose: () => void;
|
|
38
84
|
/** Injectable for tests; defaults to the local marketplace the installer uses. */
|
|
39
85
|
discoverMarketplace?: () => MarketplaceDiscoveryResult;
|
|
86
|
+
/** Tab the hub opens on. `/skills` opens on Skills, `/library <kind>` on that kind. */
|
|
87
|
+
initialTab?: LibraryEntryKind;
|
|
88
|
+
/** Injectable for tests; defaults to the discovery `library list` reads. */
|
|
89
|
+
discoverLibrary?: () => LibraryDiscoveryResult;
|
|
90
|
+
/** Injectable for tests; defaults to the classifier `library add` gates on. */
|
|
91
|
+
classifyRequirements?: (entry: LibraryEntry, catalog: ReadonlyArray<LibraryEntry>) => LibraryRequirementStatus;
|
|
92
|
+
/** Injectable for tests; defaults to the planner `library add` prints from. */
|
|
93
|
+
planInstall?: (entry: LibraryEntry) => LibraryInstallPlan;
|
|
94
|
+
/** Injectable for tests; defaults to the writer `library add --yes` runs. */
|
|
95
|
+
installPlan?: (plan: LibraryInstallPlan) => void;
|
|
96
|
+
/** Injectable for tests; defaults to the pin-and-destination check the domain owns. */
|
|
97
|
+
entryInstalled?: (entry: LibraryEntry) => boolean;
|
|
98
|
+
/** Injectable for tests; defaults to the recorded pin for this entry. */
|
|
99
|
+
entryPin?: (entry: LibraryEntry) => { sha256: string; sourceUrl: string } | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* The confirmation gate. Resolves true only when the operator accepted, and
|
|
102
|
+
* nothing is written before it does. Defaults to the framed confirmation
|
|
103
|
+
* overlay, which is the TUI spelling of `library add`'s `--yes`.
|
|
104
|
+
*/
|
|
105
|
+
confirmInstall?: (subject: LibraryInstallConfirmSubject) => Promise<boolean>;
|
|
106
|
+
/** Opens the `/fleet run` approval preview for an installed fleet. */
|
|
107
|
+
openFleetRun?: (name: string) => void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Row and action context for one library tab, resolved once per rebuild. */
|
|
111
|
+
interface LibraryTabContext {
|
|
112
|
+
discovery: LibraryDiscoveryResult;
|
|
113
|
+
installed: (entry: LibraryEntry) => boolean;
|
|
114
|
+
pin: (entry: LibraryEntry) => { sha256: string; sourceUrl: string } | undefined;
|
|
115
|
+
unresolved: (entry: LibraryEntry) => LibraryEntry[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The composer text or the surface a `use` on this entry leads to.
|
|
120
|
+
*
|
|
121
|
+
* A fleet has no composer spelling because its `use` opens the `/fleet run`
|
|
122
|
+
* approval preview instead, so this returns null for one and the caller routes
|
|
123
|
+
* it there.
|
|
124
|
+
*/
|
|
125
|
+
export function libraryUseInvocation(entry: Pick<LibraryEntry, "kind" | "name">): string | null {
|
|
126
|
+
if (entry.kind === "fleet") return null;
|
|
127
|
+
if (entry.kind === "agent") return `/run ${entry.name} `;
|
|
128
|
+
if (entry.kind === "prompt") return `/${entry.name} `;
|
|
129
|
+
return `/skill ${entry.name} `;
|
|
40
130
|
}
|
|
41
131
|
|
|
42
132
|
function groupForScope(scope: string): string {
|
|
@@ -170,8 +260,159 @@ export function buildDiagnosticItems(
|
|
|
170
260
|
return items;
|
|
171
261
|
}
|
|
172
262
|
|
|
263
|
+
/** @internal exported for contract tests */
|
|
264
|
+
export function libraryRowId(entry: Pick<LibraryEntry, "kind" | "name">): string {
|
|
265
|
+
return `${LIBRARY_ROW_PREFIX}${libraryEntryRef(entry)}`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* One kind's rows: the same columns the Skills tab shows where they apply, plus
|
|
270
|
+
* the requirements this entry still needs.
|
|
271
|
+
*
|
|
272
|
+
* The requires column carries the names rather than a count, because the names
|
|
273
|
+
* are what the refusal will say and what the second confirmation will install.
|
|
274
|
+
* It renders in the warning token: an entry with an unmet requirement is
|
|
275
|
+
* listable and readable but not installable on its own.
|
|
276
|
+
*
|
|
277
|
+
* @internal exported for contract tests
|
|
278
|
+
*/
|
|
279
|
+
export function buildLibraryItems(kind: LibraryEntryKind, context: LibraryTabContext): ListOverlayItem[] {
|
|
280
|
+
const theme = clioTheme();
|
|
281
|
+
return context.discovery.entries
|
|
282
|
+
.filter((entry) => entry.kind === kind)
|
|
283
|
+
.map((entry) => {
|
|
284
|
+
const installed = context.installed(entry);
|
|
285
|
+
const pin = context.pin(entry);
|
|
286
|
+
const unresolved = context.unresolved(entry).map(libraryEntryRef);
|
|
287
|
+
const metaParts: string[] = [entry.origin];
|
|
288
|
+
if (entry.version) metaParts.push(`v${entry.version}`);
|
|
289
|
+
metaParts.push(installed ? "installed" : "available");
|
|
290
|
+
metaParts.push(pin ? `pin ${pin.sha256.slice(0, 8)}` : "unpinned");
|
|
291
|
+
if (unresolved.length > 0) metaParts.push(theme.fg("warning", `requires ${unresolved.join(", ")}`));
|
|
292
|
+
return {
|
|
293
|
+
id: libraryRowId(entry),
|
|
294
|
+
label: entry.name,
|
|
295
|
+
meta: metaParts.join(" · "),
|
|
296
|
+
group: installed ? GROUP_INSTALLED : GROUP_AVAILABLE,
|
|
297
|
+
detail: () => {
|
|
298
|
+
const use = libraryUseInvocation(entry);
|
|
299
|
+
const lines = [
|
|
300
|
+
`# ${entry.name}`,
|
|
301
|
+
`**Kind:** ${entry.kind}`,
|
|
302
|
+
`**Use:** ${use === null ? "`Enter` opens the /fleet run approval preview" : `\`${use.trim()}\``}`,
|
|
303
|
+
`**State:** ${installed ? "installed" : "available"}${pin ? ` · pinned ${pin.sha256.slice(0, 12)}` : " · unpinned"}`,
|
|
304
|
+
`**Destination:** \`${libraryInstallPath(entry)}\``,
|
|
305
|
+
`**Source:** \`${entry.sourceUrl}\``,
|
|
306
|
+
`**Origin:** ${entry.origin}`,
|
|
307
|
+
];
|
|
308
|
+
if ((entry.requires ?? []).length > 0) {
|
|
309
|
+
lines.push(`**Requires:** ${(entry.requires ?? []).join(", ")}`);
|
|
310
|
+
}
|
|
311
|
+
if (unresolved.length > 0) {
|
|
312
|
+
lines.push(`**Unresolved:** ${unresolved.join(", ")}`, "", "Press `i` twice to install them with it.");
|
|
313
|
+
} else if (!installed) {
|
|
314
|
+
lines.push("", "**Install now:** press `i`");
|
|
315
|
+
}
|
|
316
|
+
lines.push("", "---", "", entry.description);
|
|
317
|
+
return lines;
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Diagnostic rows for a library tab. `discoverLibrary` drops an entry whose
|
|
325
|
+
* requirements are missing, malformed, or cyclic and records why; a tab that
|
|
326
|
+
* silently omitted those would be the inventory this hub refuses to draw.
|
|
327
|
+
*
|
|
328
|
+
* @internal exported for contract tests
|
|
329
|
+
*/
|
|
330
|
+
export function buildLibraryDiagnosticItems(discovery: LibraryDiscoveryResult): ListOverlayItem[] {
|
|
331
|
+
const theme = clioTheme();
|
|
332
|
+
return discovery.diagnostics
|
|
333
|
+
.filter((message) => message !== MARKETPLACE_UNCONFIGURED)
|
|
334
|
+
.map((message, index) => ({
|
|
335
|
+
id: `library-diagnostic-${index}`,
|
|
336
|
+
label: `${theme.fg("warning", GLYPH.warnInline)} ${message}`,
|
|
337
|
+
meta: "library",
|
|
338
|
+
group: GROUP_DIAGNOSTICS,
|
|
339
|
+
detail: () => ["# Library diagnostic", `**Message:** ${message}`],
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** What one attempted library install did, in the terms the hub reports it. */
|
|
344
|
+
export type LibraryInstallOutcome =
|
|
345
|
+
| { status: "installed"; refs: string[] }
|
|
346
|
+
| { status: "cancelled" }
|
|
347
|
+
| { status: "refused"; unresolved: string[] }
|
|
348
|
+
| { status: "already-installed" }
|
|
349
|
+
| { status: "failed"; message: string };
|
|
350
|
+
|
|
351
|
+
/** The library domain calls one install needs, resolved before it runs. */
|
|
352
|
+
export interface LibraryInstallRunner {
|
|
353
|
+
discovery: LibraryDiscoveryResult;
|
|
354
|
+
classify: (entry: LibraryEntry, catalog: ReadonlyArray<LibraryEntry>) => LibraryRequirementStatus;
|
|
355
|
+
plan: (entry: LibraryEntry) => LibraryInstallPlan;
|
|
356
|
+
write: (plan: LibraryInstallPlan) => void;
|
|
357
|
+
installed: (entry: LibraryEntry) => boolean;
|
|
358
|
+
confirm: (subject: LibraryInstallConfirmSubject) => Promise<boolean>;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Install one library entry through the same sequence `library add` runs.
|
|
363
|
+
*
|
|
364
|
+
* The order is the CLI's order and the refusals are the CLI's refusals: an
|
|
365
|
+
* entry with an unmet requirement is refused by name and nothing is planned, a
|
|
366
|
+
* plan is built and shown before anything is written, and the writes happen
|
|
367
|
+
* only after the confirmation resolves true. A cancelled confirmation is a run
|
|
368
|
+
* in which no file changed.
|
|
369
|
+
*
|
|
370
|
+
* @internal exported for contract tests
|
|
371
|
+
*/
|
|
372
|
+
export async function runLibraryInstall(
|
|
373
|
+
entry: LibraryEntry,
|
|
374
|
+
runner: LibraryInstallRunner,
|
|
375
|
+
options: { withRequirements: boolean },
|
|
376
|
+
): Promise<LibraryInstallOutcome> {
|
|
377
|
+
if (runner.installed(entry)) return { status: "already-installed" };
|
|
378
|
+
let requirements: LibraryRequirementStatus;
|
|
379
|
+
let plans: LibraryInstallPlan[];
|
|
380
|
+
try {
|
|
381
|
+
requirements = runner.classify(entry, runner.discovery.entries);
|
|
382
|
+
if (requirements.unsatisfied.length > 0 && !options.withRequirements) {
|
|
383
|
+
return { status: "refused", unresolved: requirements.unsatisfied.map(libraryEntryRef) };
|
|
384
|
+
}
|
|
385
|
+
plans = [...(options.withRequirements ? requirements.unsatisfied : []), entry].map(runner.plan);
|
|
386
|
+
} catch (error) {
|
|
387
|
+
return { status: "failed", message: error instanceof Error ? error.message : String(error) };
|
|
388
|
+
}
|
|
389
|
+
const accepted = await runner.confirm({
|
|
390
|
+
entryRef: libraryEntryRef(entry),
|
|
391
|
+
writes: plans.map((plan) => ({ ref: libraryEntryRef(plan.entry), path: plan.path, sha256: plan.sha256 })),
|
|
392
|
+
requirements: options.withRequirements ? requirements.unsatisfied.map(libraryEntryRef) : [],
|
|
393
|
+
satisfied: requirements.satisfied.map(libraryEntryRef),
|
|
394
|
+
});
|
|
395
|
+
if (!accepted) return { status: "cancelled" };
|
|
396
|
+
const written: string[] = [];
|
|
397
|
+
try {
|
|
398
|
+
for (const plan of plans) {
|
|
399
|
+
runner.write(plan);
|
|
400
|
+
written.push(libraryEntryRef(plan.entry));
|
|
401
|
+
}
|
|
402
|
+
} catch (error) {
|
|
403
|
+
return { status: "failed", message: error instanceof Error ? error.message : String(error) };
|
|
404
|
+
}
|
|
405
|
+
return { status: "installed", refs: written };
|
|
406
|
+
}
|
|
407
|
+
|
|
173
408
|
export function openSkillsHub(tui: TUI, deps: SkillsHubDeps): OverlayHandle {
|
|
174
409
|
const discoverMarketplace = deps.discoverMarketplace ?? (() => discoverMarketplaceSkills());
|
|
410
|
+
const readLibrary = deps.discoverLibrary ?? (() => discoverLibrary());
|
|
411
|
+
const classify = deps.classifyRequirements ?? classifyLibraryRequirements;
|
|
412
|
+
const plan = deps.planInstall ?? planLibraryInstall;
|
|
413
|
+
const write = deps.installPlan ?? installLibraryPlan;
|
|
414
|
+
const installed = deps.entryInstalled ?? libraryEntryInstalled;
|
|
415
|
+
const pin = deps.entryPin ?? libraryEntryPin;
|
|
175
416
|
|
|
176
417
|
// Rebuilt rather than mutated in place: the view memoizes its frame on the row
|
|
177
418
|
// set, so a spliced array leaves an installed skill drawn as installable.
|
|
@@ -186,27 +427,170 @@ export function openSkillsHub(tui: TUI, deps: SkillsHubDeps): OverlayHandle {
|
|
|
186
427
|
];
|
|
187
428
|
};
|
|
188
429
|
|
|
430
|
+
// One discovery pass feeds all three library tabs. The tab bar asks every tab
|
|
431
|
+
// for its rows on each rebuild, and four independent catalog reads per
|
|
432
|
+
// keystroke would be four answers to the same question.
|
|
433
|
+
let libraryCache: LibraryTabContext | null = null;
|
|
434
|
+
const libraryContext = (): LibraryTabContext => {
|
|
435
|
+
if (libraryCache) return libraryCache;
|
|
436
|
+
const discovery = readLibrary();
|
|
437
|
+
libraryCache = {
|
|
438
|
+
discovery,
|
|
439
|
+
installed,
|
|
440
|
+
pin,
|
|
441
|
+
unresolved: (entry) => {
|
|
442
|
+
try {
|
|
443
|
+
return classify(entry, discovery.entries).unsatisfied;
|
|
444
|
+
} catch {
|
|
445
|
+
// A requirement that will not resolve is already a diagnostic row on
|
|
446
|
+
// this tab; a row that cannot answer the question reports none rather
|
|
447
|
+
// than taking the whole rebuild down.
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
return libraryCache;
|
|
453
|
+
};
|
|
454
|
+
const invalidateLibrary = (): void => {
|
|
455
|
+
libraryCache = null;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const buildLibraryRows = (kind: LibraryEntryKind): ListOverlayItem[] => {
|
|
459
|
+
const context = libraryContext();
|
|
460
|
+
return [...buildLibraryItems(kind, context), ...buildLibraryDiagnosticItems(context.discovery)];
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const entryForRow = (item: ListOverlayItem): LibraryEntry | undefined => {
|
|
464
|
+
if (!item.id.startsWith(LIBRARY_ROW_PREFIX)) return undefined;
|
|
465
|
+
const ref = item.id.slice(LIBRARY_ROW_PREFIX.length);
|
|
466
|
+
return libraryContext().discovery.entries.find((candidate) => libraryEntryRef(candidate) === ref);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const tabs: ListOverlayTab[] = LIBRARY_TABS.map((tab) => ({
|
|
470
|
+
id: tab.id,
|
|
471
|
+
label: tab.label,
|
|
472
|
+
items: tab.id === "skill" ? buildRows : () => buildLibraryRows(tab.id),
|
|
473
|
+
}));
|
|
474
|
+
|
|
189
475
|
let closed = false;
|
|
190
476
|
let installInFlight = false;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
477
|
+
/**
|
|
478
|
+
* The row whose requirements were just refused. A second `i` on that row is
|
|
479
|
+
* the install-with-requirements offer, so the refusal names what is missing
|
|
480
|
+
* before any confirmation lists what would be written.
|
|
481
|
+
*/
|
|
482
|
+
let pendingWithRequirements: string | null = null;
|
|
483
|
+
|
|
484
|
+
const columns = (): number => (typeof process.stdout.columns === "number" ? process.stdout.columns : 100);
|
|
485
|
+
|
|
486
|
+
// eslint-disable-next-line prefer-const -- the confirmation closure needs the handle it is opened from.
|
|
487
|
+
let handle: ReturnType<typeof openListOverlay>;
|
|
488
|
+
|
|
489
|
+
const confirmInstall =
|
|
490
|
+
deps.confirmInstall ??
|
|
491
|
+
((subject: LibraryInstallConfirmSubject): Promise<boolean> =>
|
|
492
|
+
new Promise<boolean>((resolve) => {
|
|
493
|
+
handle.setHidden(true);
|
|
494
|
+
const settle = (accepted: boolean): void => {
|
|
495
|
+
if (!closed) {
|
|
496
|
+
handle.setHidden(false);
|
|
497
|
+
handle.focus();
|
|
498
|
+
}
|
|
499
|
+
resolve(accepted);
|
|
500
|
+
};
|
|
501
|
+
const confirmation = openLibraryInstallConfirmOverlay(tui, {
|
|
502
|
+
subject,
|
|
503
|
+
columns: columns(),
|
|
504
|
+
onAccept: () => {
|
|
505
|
+
settle(true);
|
|
506
|
+
confirmation.hide();
|
|
507
|
+
},
|
|
508
|
+
onCancel: () => {
|
|
509
|
+
settle(false);
|
|
510
|
+
confirmation.hide();
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
}));
|
|
514
|
+
|
|
515
|
+
const useEntry = (entry: LibraryEntry): void => {
|
|
516
|
+
if (!installed(entry)) {
|
|
517
|
+
deps.notice("warn", `${libraryEntryRef(entry)} is not installed; press i to install it`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (entry.kind === "fleet") {
|
|
521
|
+
deps.openFleetRun?.(entry.name);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const invocation = libraryUseInvocation(entry);
|
|
525
|
+
if (invocation === null) return;
|
|
526
|
+
deps.setEditorText(invocation);
|
|
527
|
+
deps.onClose();
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const installEntry = (entry: LibraryEntry): void => {
|
|
531
|
+
const ref = libraryEntryRef(entry);
|
|
532
|
+
const withRequirements = pendingWithRequirements === ref;
|
|
533
|
+
installInFlight = true;
|
|
534
|
+
void (async () => {
|
|
535
|
+
const outcome = await runLibraryInstall(
|
|
536
|
+
entry,
|
|
537
|
+
{ discovery: libraryContext().discovery, classify, plan, write, installed, confirm: confirmInstall },
|
|
538
|
+
{ withRequirements },
|
|
539
|
+
);
|
|
540
|
+
installInFlight = false;
|
|
541
|
+
if (outcome.status === "refused") {
|
|
542
|
+
pendingWithRequirements = ref;
|
|
543
|
+
deps.notice("error", `${ref} needs ${outcome.unresolved.join(", ")}; press i again to install them with it`);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
pendingWithRequirements = null;
|
|
547
|
+
if (outcome.status === "already-installed") deps.notice("info", `${ref} is already installed`);
|
|
548
|
+
else if (outcome.status === "cancelled") deps.notice("info", `${ref}: cancelled; nothing was written`);
|
|
549
|
+
else if (outcome.status === "failed") deps.notice("error", `library install failed: ${outcome.message}`);
|
|
550
|
+
else deps.notice("success", `installed ${outcome.refs.join(", ")}`);
|
|
551
|
+
// An install that lands after the hub closed has nothing to redraw.
|
|
552
|
+
if (!closed && outcome.status === "installed") {
|
|
553
|
+
invalidateLibrary();
|
|
554
|
+
handle.refreshTabs();
|
|
555
|
+
}
|
|
556
|
+
})();
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
handle = openListOverlay(tui, {
|
|
560
|
+
title: SKILLS_HUB_TITLE,
|
|
561
|
+
items: [],
|
|
562
|
+
tabs,
|
|
563
|
+
...(deps.initialTab ? { activeTabId: deps.initialTab } : {}),
|
|
564
|
+
onTabChange: () => {
|
|
565
|
+
pendingWithRequirements = null;
|
|
566
|
+
},
|
|
194
567
|
filterable: true,
|
|
195
568
|
layout: "split",
|
|
196
569
|
emptyMessage: SKILLS_HUB_EMPTY,
|
|
197
570
|
hints: [
|
|
198
|
-
{ key: "Enter", verb: "
|
|
571
|
+
{ key: "Enter", verb: "use" },
|
|
199
572
|
{ key: "i", verb: "install" },
|
|
200
573
|
],
|
|
201
574
|
onSelect: (item) => {
|
|
202
575
|
if (item.group === GROUP_DIAGNOSTICS) return;
|
|
576
|
+
const entry = entryForRow(item);
|
|
577
|
+
if (entry) {
|
|
578
|
+
useEntry(entry);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
203
581
|
const name = item.id.startsWith("marketplace:") ? item.id.slice("marketplace:".length) : item.id;
|
|
204
582
|
deps.setEditorText(`/skill ${name} `);
|
|
205
583
|
deps.onClose();
|
|
206
584
|
},
|
|
207
585
|
actions: {
|
|
208
586
|
i: (item) => {
|
|
209
|
-
if (
|
|
587
|
+
if (installInFlight) return;
|
|
588
|
+
const entry = entryForRow(item);
|
|
589
|
+
if (entry) {
|
|
590
|
+
installEntry(entry);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (!item.id.startsWith("marketplace:")) return;
|
|
210
594
|
const name = item.id.slice("marketplace:".length);
|
|
211
595
|
installInFlight = true;
|
|
212
596
|
void (async () => {
|
|
@@ -218,8 +602,10 @@ export function openSkillsHub(tui: TUI, deps: SkillsHubDeps): OverlayHandle {
|
|
|
218
602
|
deps.notice("error", `skill install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
219
603
|
} finally {
|
|
220
604
|
installInFlight = false;
|
|
221
|
-
|
|
222
|
-
|
|
605
|
+
if (!closed) {
|
|
606
|
+
invalidateLibrary();
|
|
607
|
+
handle.refreshTabs();
|
|
608
|
+
}
|
|
223
609
|
}
|
|
224
610
|
})();
|
|
225
611
|
},
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { HintEntry } from "./overlay-frame.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The keys that answer a parked call, as data, so the dialog footer and the
|
|
5
|
+
* composer rail render the same policy at any width.
|
|
6
|
+
*
|
|
7
|
+
* This is its own leaf because the composer is part of the Stage 0 boot shell
|
|
8
|
+
* and the permission overlay is not: importing the overlay from the editor
|
|
9
|
+
* pulled the safety domain's approval modules into the instant-shell closure.
|
|
10
|
+
*
|
|
11
|
+
* At 40 columns the old positional elider removed `[s] stop turn` and left
|
|
12
|
+
* "allow once" and an ambiguous "close" in front of an operator trying to
|
|
13
|
+
* refuse. The key kept working, so the layout was hiding a live safety action.
|
|
14
|
+
* Allow and stop are marked critical, Esc is marked droppable, and
|
|
15
|
+
* `fitHintEntries` shortens every label before it drops anything.
|
|
16
|
+
*
|
|
17
|
+
* Esc says `deny`, not `close`: closing the dialog denies the call, and on the
|
|
18
|
+
* one surface where a misread is a wrong decision about a tool call the key
|
|
19
|
+
* names its effect.
|
|
20
|
+
*
|
|
21
|
+
* With a draft in the composer, Enter is inert (issue #186): the habitual send
|
|
22
|
+
* key must not resolve toward "allow" on a safety rail. The entry then says so
|
|
23
|
+
* and names the key that clears the draft, which the router passes through.
|
|
24
|
+
*/
|
|
25
|
+
export function permissionHintEntries(composerHasDraft = false): HintEntry[] {
|
|
26
|
+
return [
|
|
27
|
+
composerHasDraft
|
|
28
|
+
? { key: "Backspace", verb: "clear the draft to allow", short: "clear draft", critical: true }
|
|
29
|
+
: { key: "Enter", verb: "allow once", short: "allow", critical: true },
|
|
30
|
+
{ key: "s", verb: "stop turn", short: "stop", critical: true },
|
|
31
|
+
// With a draft, deny and stop are the only immediate answers, so Esc
|
|
32
|
+
// outranks the narrowing that would otherwise drop it first.
|
|
33
|
+
{ key: "Esc", verb: "deny", critical: composerHasDraft },
|
|
34
|
+
];
|
|
35
|
+
}
|