@iowarp/clio-coder 0.3.6 → 0.3.8
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 +59 -0
- package/README.md +22 -6
- package/dist/{acp-2BEHC4DL.js → acp-U67UHUK2.js} +14 -14
- package/dist/{agents-LNNFTM53.js → agents-YU6SGALZ.js} +42 -35
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-KXXFI2VS.js → auth-5ZPJOIVG.js} +32 -24
- package/dist/builtins-C6JMZVV6.js +17 -0
- package/dist/{chunk-E25LMLRW.js → chunk-26LEYJZH.js} +2 -2
- package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
- package/dist/{chunk-PBTHKCPN.js → chunk-2HFZQUHL.js} +7 -7
- package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
- package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
- package/dist/{chunk-CKXWIANG.js → chunk-4SPRNWDE.js} +18 -16
- package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
- package/dist/{chunk-XF5N4U5A.js → chunk-5DHKRSMQ.js} +9 -8
- package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
- package/dist/{chunk-EKY57CSP.js → chunk-5H3GB5BO.js} +68 -772
- package/dist/{chunk-6XXKFVSN.js → chunk-5Q2VVUKB.js} +4 -4
- package/dist/{chunk-DJVECN66.js → chunk-7RFXX52T.js} +295 -3705
- package/dist/{chunk-ZXF4XRKW.js → chunk-7RGZWPB6.js} +158 -8
- package/dist/{chunk-FYYLNIL5.js → chunk-A2NJGIB3.js} +2 -2
- package/dist/{chunk-XXQNGV4M.js → chunk-A3WNZD3P.js} +548 -72
- package/dist/{chunk-VEZEGCGW.js → chunk-B5CSFE7B.js} +23 -21
- package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
- package/dist/chunk-DR52UMZW.js +21 -0
- package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
- package/dist/{chunk-QKMUKYO7.js → chunk-E77JEWSD.js} +270 -125
- package/dist/{chunk-LYF7OHWH.js → chunk-EMYUUSFG.js} +12 -467
- package/dist/{chunk-4VP4KH3K.js → chunk-EQ63NRB7.js} +8 -8
- package/dist/chunk-FBVTI2TJ.js +518 -0
- package/dist/{chunk-R46L2BIR.js → chunk-FHJEP5SW.js} +19 -25
- package/dist/{chunk-WR67VIZY.js → chunk-GN57SG4G.js} +66 -8
- package/dist/{chunk-RD5U66HV.js → chunk-GPIEI3LY.js} +9 -9
- package/dist/{chunk-PCZJO5TI.js → chunk-GU2UIAFZ.js} +13 -178
- package/dist/chunk-GWS3VEIW.js +195 -0
- package/dist/chunk-H7IXIC72.js +103 -0
- package/dist/{chunk-3BPUFZDL.js → chunk-HLE42MG7.js} +3 -3
- package/dist/{chunk-24I7BN55.js → chunk-IFBNV6H6.js} +3 -3
- package/dist/{chunk-QNQHSOLF.js → chunk-IGWKHNIQ.js} +114 -55
- package/dist/chunk-IIZWH4XA.js +172 -0
- package/dist/{chunk-AD2SYQYC.js → chunk-IJ7RPIYJ.js} +124 -6
- package/dist/chunk-J3YUBZWY.js +382 -0
- package/dist/{chunk-5JGRAMKL.js → chunk-JOZYP4GM.js} +8 -6
- package/dist/{chunk-OH3TOQTB.js → chunk-K4XHGFR5.js} +751 -15
- package/dist/{chunk-EYPA3EGJ.js → chunk-KTYTFRMB.js} +178 -14
- package/dist/chunk-LU7P4LHA.js +33 -0
- package/dist/chunk-ME6CCNFO.js +108 -0
- package/dist/chunk-MXKJU4JB.js +1100 -0
- package/dist/{chunk-RY3LY4J5.js → chunk-N22QMJKY.js} +21 -18
- package/dist/chunk-NMPKI6XL.js +3006 -0
- package/dist/chunk-NUGM5KR6.js +165 -0
- package/dist/{chunk-22NAGB7X.js → chunk-P43ETTHK.js} +5 -94
- package/dist/{chunk-NILBFAPG.js → chunk-PMDBGQSJ.js} +2 -2
- package/dist/chunk-PT7HYKEM.js +165 -0
- package/dist/chunk-RVG5JXAL.js +41 -0
- package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
- package/dist/{chunk-4BPJXDWC.js → chunk-TANS5ZJS.js} +35 -19
- package/dist/{verifiers-NCBTHHN2.js → chunk-TB5666IT.js} +67 -324
- package/dist/{chunk-QM3F2GKX.js → chunk-TLQJPP24.js} +7927 -8088
- package/dist/{chunk-G7MUEIGA.js → chunk-TT36MB5S.js} +2 -1
- package/dist/chunk-TTHACPOM.js +961 -0
- package/dist/{chunk-6US73PDB.js → chunk-TYPGUK6W.js} +7 -7
- package/dist/{chunk-MFFY33HR.js → chunk-U6MBIEMB.js} +554 -209
- package/dist/chunk-VAWNZU7Z.js +242 -0
- package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
- package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
- package/dist/{chunk-XYDYPRZI.js → chunk-VWZOAB7K.js} +10 -10
- package/dist/{chunk-ZRGEBJ4T.js → chunk-WLFILSD5.js} +48 -48
- package/dist/{chunk-K7T3E2SR.js → chunk-WNIJTQQK.js} +12 -11
- package/dist/{chunk-CYQKWTG3.js → chunk-WSB3FPX7.js} +68 -20
- package/dist/{chunk-KHSFENX2.js → chunk-WWCZ5F23.js} +116 -14
- package/dist/{chunk-XE2VEJHX.js → chunk-WXY7KU3G.js} +2 -2
- package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
- package/dist/{chunk-KOHPCX4K.js → chunk-XWSF374K.js} +5 -5
- package/dist/{chunk-CJUB2JJ2.js → chunk-YS5VLNH5.js} +10 -10
- package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
- package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
- package/dist/cli/index.js +33 -31
- package/dist/{clio-M2KGYUFZ.js → clio-QVTYJ57A.js} +10 -10
- package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +5 -5
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-5TTYYX6G.js → components-ZFA3SAER.js} +9 -9
- package/dist/{config-XUUYQIWO.js → config-LW5IJFQN.js} +65 -55
- package/dist/{configure-IHJ7YOMV.js → configure-7XIZCOU4.js} +29 -24
- package/dist/{context-75MIWW3U.js → context-L3WL3X7K.js} +54 -43
- package/dist/{context-ZQ7SIFJV.js → context-N52ZA626.js} +30 -14
- package/dist/{context-74JLXAWD.js → context-Y6Y7QPR6.js} +12 -12
- package/dist/{context-clear-GYKWNUML.js → context-clear-MBQRLSDQ.js} +54 -43
- package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
- package/dist/{context-working-set-UX5KEP4J.js → context-working-set-GS6DSO7F.js} +18 -18
- package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-22ZCNOM3.js} +367 -79
- package/dist/{docs-6FZSCG5B.js → docs-7LQ23DLM.js} +9 -9
- package/dist/{doctor-SVJ5BZCW.js → doctor-M7YEDGAE.js} +27 -23
- package/dist/{eval-CG6LLBLD.js → eval-BEC2WHDA.js} +73 -20
- package/dist/{evidence-ZYFIEN42.js → evidence-REJUMSKM.js} +70 -59
- package/dist/{evolve-QGEXEMDW.js → evolve-PY5ZBA5K.js} +50 -39
- package/dist/{extensions-ADGNCJJD.js → extensions-HVKU65YU.js} +7 -7
- package/dist/{fleet-S5R4ZOQY.js → fleet-7WZEWRFA.js} +236 -377
- package/dist/fleet-commands-UVHWM76J.js +70 -0
- package/dist/fleet-graph-6ULH7PES.js +125 -0
- package/dist/fleet-new-RDVJLHHH.js +48 -0
- package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
- package/dist/fleet-validate-72PC4SLA.js +79 -0
- package/dist/{init-5DRU55YR.js → init-OG3TPGQG.js} +69 -57
- package/dist/library-CNTMPLRF.js +217 -0
- package/dist/{memory-7YKKR6UC.js → memory-6IS7F275.js} +52 -41
- package/dist/{models-ZPOLRU2C.js → models-ENRJDA5W.js} +37 -31
- package/dist/{monitor-US5F5YGZ.js → monitor-XLDVO7TN.js} +79 -42
- package/dist/{orchestrator-E2AL4T5N.js → orchestrator-6KSPYRHA.js} +4126 -762
- package/dist/{paths-E7KYAQWE.js → paths-DBXMZMDU.js} +6 -6
- package/dist/registry-LG64LTF4.js +11 -0
- package/dist/{reset-KZ652EK6.js → reset-RZ4ER727.js} +12 -12
- package/dist/{run-SRNBKDWD.js → run-Y2CNK5RU.js} +89 -76
- package/dist/{share-CGZE33UP.js → share-A55GYP6Z.js} +37 -13
- package/dist/{skills-S2X4DLY5.js → skills-ALC5J6AT.js} +29 -13
- package/dist/{skills-eval-W2GGIC4R.js → skills-eval-JPBEBYQU.js} +60 -48
- package/dist/support-MIETYA5E.js +38 -0
- package/dist/{targets-54SWINWB.js → targets-VGNXIR3S.js} +46 -36
- package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WOBR64YA.js} +4 -4
- package/dist/{uninstall-BVLWXKBT.js → uninstall-ZJF5H5ZN.js} +9 -9
- package/dist/{upgrade-JKAR27XC.js → upgrade-FUSUAGHR.js} +31 -28
- package/dist/{usage-MSAWCLX4.js → usage-N4MKVHKD.js} +140 -68
- package/dist/verifiers-YAWOJ3H2.js +336 -0
- package/dist/{verify-X5HDROLA.js → verify-LTDHYBGY.js} +10 -9
- package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-6M7GHTBJ.js} +75 -62
- package/dist/worker/entry.js +67 -57
- package/docs/README.md +3 -2
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +11 -6
- package/docs/built-in-agents.md +26 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +83 -3
- package/docs/configuration-and-targets.md +86 -4
- package/docs/context-engine.md +1 -1
- package/docs/development-pipeline.md +1 -1
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +3 -3
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +74 -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 +228 -16
- package/docs/git-commit-provenance.md +2 -2
- package/docs/glossary.md +22 -2
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +2 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +56 -9
- package/docs/proactive-memory.md +1 -1
- 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 +2 -2
- 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 +1 -1
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +1 -2
- package/src/cli/argv.ts +5 -0
- package/src/cli/configure.ts +107 -23
- package/src/cli/doctor.ts +5 -1
- package/src/cli/evidence.ts +30 -25
- 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 +111 -0
- package/src/cli/fleet-validate.ts +30 -0
- package/src/cli/fleet.ts +173 -335
- package/src/cli/index.ts +3 -1
- package/src/cli/library.ts +190 -0
- package/src/cli/share.ts +13 -1
- package/src/cli/shared.ts +1 -0
- package/src/cli/targets.ts +4 -1
- package/src/cli/usage.ts +111 -19
- package/src/cli/validate-model.ts +60 -5
- package/src/core/bus-events.ts +29 -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/path-boundary.ts +100 -0
- package/src/domains/agents/builtins/architect.md +1 -0
- package/src/domains/agents/builtins/oracle.md +33 -0
- package/src/domains/agents/catalog.ts +13 -1
- package/src/domains/agents/extension.ts +2 -11
- package/src/domains/agents/fleet-contract.ts +304 -24
- package/src/domains/agents/index.ts +14 -0
- package/src/domains/agents/recipe.ts +7 -1
- package/src/domains/agents/registry.ts +73 -5
- package/src/domains/agents/result-contract.ts +360 -15
- package/src/domains/agents/write-boundary.ts +15 -50
- package/src/domains/config/classify.ts +4 -0
- package/src/domains/context/project-rules.ts +51 -1
- package/src/domains/dispatch/active-route-planner.ts +14 -0
- package/src/domains/dispatch/assignment-reconcile.ts +22 -5
- package/src/domains/dispatch/assignment-store.ts +151 -14
- package/src/domains/dispatch/backoff.ts +2 -1
- 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 +48 -0
- package/src/domains/dispatch/delegation-plan.ts +164 -0
- package/src/domains/dispatch/execution-plan.ts +76 -5
- package/src/domains/dispatch/execution-role.ts +12 -2
- package/src/domains/dispatch/execution-scheduler.ts +183 -67
- package/src/domains/dispatch/extension.ts +412 -74
- 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 +791 -0
- package/src/domains/dispatch/gate-role-prompts.ts +47 -0
- package/src/domains/dispatch/host-verification.ts +178 -0
- package/src/domains/dispatch/index.ts +41 -1
- package/src/domains/dispatch/intent-requirements.ts +40 -0
- package/src/domains/dispatch/intent.ts +235 -0
- package/src/domains/dispatch/path-scope.ts +370 -0
- package/src/domains/dispatch/receipt-integrity.ts +9 -4
- package/src/domains/dispatch/state.ts +36 -3
- package/src/domains/dispatch/types.ts +64 -12
- package/src/domains/dispatch/validation.ts +69 -6
- package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
- package/src/domains/dispatch/write-boundary.ts +201 -22
- package/src/domains/eval/metrics/evidence.ts +79 -2
- package/src/domains/eval/runners/clio-run.ts +12 -2
- package/src/domains/evidence/build.ts +69 -11
- package/src/domains/evidence/index.ts +21 -0
- package/src/domains/evidence/provenance.ts +46 -11
- package/src/domains/evidence/trust-projection.ts +274 -0
- package/src/domains/evidence/trust-status.ts +155 -18
- package/src/domains/evidence/types.ts +4 -0
- package/src/domains/extensions/discovery.ts +88 -1
- package/src/domains/extensions/resources.ts +20 -8
- package/src/domains/extensions/state.ts +6 -2
- package/src/domains/extensions/types.ts +4 -1
- package/src/domains/lifecycle/doctor.ts +140 -1
- package/src/domains/middleware/index.ts +15 -0
- package/src/domains/middleware/watchdog.ts +281 -0
- package/src/domains/observability/contract.ts +3 -1
- package/src/domains/observability/cost.ts +12 -1
- 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/prompts/contract.ts +3 -5
- package/src/domains/providers/extension.ts +30 -2
- package/src/domains/resources/common-loader.ts +3 -0
- package/src/domains/resources/index.ts +20 -0
- package/src/domains/resources/library.ts +326 -0
- package/src/domains/resources/prompts/loader.ts +119 -14
- package/src/domains/resources/skills/marketplace.ts +37 -12
- package/src/domains/safety/policy-engine.ts +5 -5
- package/src/domains/safety/run-effects.ts +64 -1
- package/src/domains/safety/scope.ts +7 -12
- package/src/domains/session/handoff.ts +629 -0
- package/src/domains/share/archive.ts +67 -2
- package/src/engine/acp/server.ts +4 -1
- package/src/engine/prompt-templates.ts +18 -1
- package/src/engine/worker-runtime.ts +6 -3
- package/src/entry/orchestrator.ts +37 -0
- package/src/interactive/bus-notices.ts +26 -0
- package/src/interactive/chat-loop.ts +235 -1
- package/src/interactive/chat-renderer.ts +22 -0
- package/src/interactive/cost-overlay.ts +31 -3
- 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 +311 -17
- package/src/interactive/fleet-run-preview.ts +307 -0
- package/src/interactive/footer/notifications.ts +219 -0
- package/src/interactive/handoff-round.ts +56 -0
- package/src/interactive/interactive-application.ts +43 -1
- package/src/interactive/interactive-event-projection.ts +23 -1
- package/src/interactive/interactive-slash-runtime.ts +52 -2
- package/src/interactive/interactive-subscriptions.ts +14 -2
- package/src/interactive/oracle.ts +179 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
- package/src/interactive/overlay-general-openers.ts +190 -1
- package/src/interactive/overlay-key-routing.ts +17 -1
- package/src/interactive/overlay-lifecycle.ts +41 -1
- package/src/interactive/overlay-permission-lifecycle.ts +10 -0
- package/src/interactive/overlay-resource-openers.ts +11 -3
- package/src/interactive/overlay-session-lifecycle.ts +234 -2
- 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 +221 -30
- package/src/interactive/overlays/side-question.ts +139 -0
- package/src/interactive/overlays/skills-hub.ts +401 -15
- package/src/interactive/side-question.ts +171 -0
- package/src/interactive/slash-commands.ts +439 -7
- package/src/interactive/slash-spec.ts +19 -6
- package/src/interactive/theme/tokens.ts +30 -0
- package/src/interactive/turn-middleware.ts +15 -1
- package/src/interactive/view/artifacts.ts +42 -9
- package/src/interactive/view/view-overlay.ts +15 -3
- package/src/interactive/watchdog-run.ts +75 -0
- package/src/interactive/worker-receipts.ts +14 -2
- package/src/interactive/worker-share.ts +56 -1
- package/src/interactive/worker-stream.ts +15 -0
- package/src/tools/bootstrap.ts +3 -0
- package/src/tools/compete-worktrees.ts +13 -79
- package/src/tools/dispatch-admission.ts +251 -18
- package/src/tools/dispatch-arguments.ts +84 -1
- package/src/tools/dispatch-plan.ts +165 -6
- package/src/tools/dispatch-runner.ts +364 -23
- package/src/tools/dispatch-types.ts +20 -1
- package/src/tools/dispatch.ts +72 -2
- package/src/tools/monitor.ts +29 -0
- package/src/tools/profiles.ts +18 -4
- package/src/tools/task-worktree.ts +238 -0
- package/src/tools/verify/authoring.ts +61 -1
- package/src/tools/verify/scripts.ts +62 -0
- package/src/tools/worker-evidence.ts +21 -14
- package/src/worker/spec-contract.ts +3 -1
- package/dist/chunk-HC4CLZ2Y.js +0 -68
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable record of the model calls that were billed beside a session rather
|
|
3
|
+
* than inside it: `/btw` side questions and `/handoff` extraction rounds.
|
|
4
|
+
*
|
|
5
|
+
* These rounds deliberately append nothing to the session JSONL. A fleet run
|
|
6
|
+
* briefs its workers from the transcript, so a question the operator asked to
|
|
7
|
+
* orient themselves must not become context those workers inherit, and
|
|
8
|
+
* `src/interactive/side-question.ts` states that promise as a contract. The
|
|
9
|
+
* money was still spent, though, and until this store existed the only record
|
|
10
|
+
* of it was the in-process cost tracker: `/cost` showed it for as long as the
|
|
11
|
+
* process lived, and `clio-coder usage report`, which reads the archive on
|
|
12
|
+
* disk, could not see it at all.
|
|
13
|
+
*
|
|
14
|
+
* So the spend gets its own file. One JSON line per priced out-of-turn call
|
|
15
|
+
* under `<stateDir>/usage/out-of-turn.jsonl`, carrying the label, the session
|
|
16
|
+
* it sat beside, the repo identity the session ledger is filed under (so
|
|
17
|
+
* `usage report --repo` filters these rows the same way it filters ledgers),
|
|
18
|
+
* the target and attributed model, and the provider-reported usage.
|
|
19
|
+
*
|
|
20
|
+
* Writes follow the audit-row conventions: one append-mode `writeSync` per
|
|
21
|
+
* row, so a line lands whole even with concurrent writers, and a failure is
|
|
22
|
+
* logged to stderr rather than thrown back into the interactive path. The file
|
|
23
|
+
* is a bounded ring: once it grows past {@link MAX_OUT_OF_TURN_USAGE_ROWS} the
|
|
24
|
+
* newest rows are rewritten atomically under the shared state-file lock and
|
|
25
|
+
* the oldest are dropped. Reads are tolerant; a malformed line is reported and
|
|
26
|
+
* skipped, never fatal.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { closeSync, mkdirSync, openSync, readFileSync, writeSync } from "node:fs";
|
|
30
|
+
import { dirname, join } from "node:path";
|
|
31
|
+
import { safeResourceWrite } from "../../core/safe-resource-write.js";
|
|
32
|
+
import { withStateFileLockSync } from "../../core/state-file-lock.js";
|
|
33
|
+
import type { CostProvenance } from "../providers/index.js";
|
|
34
|
+
import type { CostEntryLabel } from "./cost.js";
|
|
35
|
+
|
|
36
|
+
/** Directory under the state dir that holds cross-session usage side-cars. */
|
|
37
|
+
export const OUT_OF_TURN_USAGE_DIR = "usage";
|
|
38
|
+
|
|
39
|
+
/** Filename of the out-of-turn usage ledger inside {@link OUT_OF_TURN_USAGE_DIR}. */
|
|
40
|
+
export const OUT_OF_TURN_USAGE_FILE = "out-of-turn.jsonl";
|
|
41
|
+
|
|
42
|
+
/** Cap on retained rows. Matches the dispatch runs ledger and evidence index default. */
|
|
43
|
+
export const MAX_OUT_OF_TURN_USAGE_ROWS = 1000;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* How many appends this process makes between bound checks. Counting rows means
|
|
47
|
+
* reading the file, and an out-of-turn round is rare enough that paying that on
|
|
48
|
+
* the first append of a process and periodically after it keeps the ring bounded
|
|
49
|
+
* without reading a megabyte per side question.
|
|
50
|
+
*/
|
|
51
|
+
const BOUND_CHECK_INTERVAL = 64;
|
|
52
|
+
|
|
53
|
+
/** Provider-reported usage for one out-of-turn call. */
|
|
54
|
+
export interface OutOfTurnUsage {
|
|
55
|
+
input: number;
|
|
56
|
+
output: number;
|
|
57
|
+
cacheRead: number;
|
|
58
|
+
cacheWrite: number;
|
|
59
|
+
reasoning: number;
|
|
60
|
+
totalTokens: number;
|
|
61
|
+
costUsd: number;
|
|
62
|
+
costProvenance: CostProvenance;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** One priced model call that was billed beside a session rather than inside it. */
|
|
66
|
+
export interface OutOfTurnUsageRow {
|
|
67
|
+
label: CostEntryLabel;
|
|
68
|
+
/** Session the round sat beside, or null when no session was current. */
|
|
69
|
+
sessionId: string | null;
|
|
70
|
+
/** The cwd hash the session ledger is filed under, so `--repo` can filter these rows. */
|
|
71
|
+
repoIdentity: string | null;
|
|
72
|
+
/** ISO-8601 timestamp of the call. */
|
|
73
|
+
timestamp: string;
|
|
74
|
+
/** Target id the round ran against. */
|
|
75
|
+
target: string;
|
|
76
|
+
attributedModelId: string;
|
|
77
|
+
usage: OutOfTurnUsage;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface OutOfTurnUsageReadResult {
|
|
81
|
+
rows: OutOfTurnUsageRow[];
|
|
82
|
+
errors: string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function outOfTurnUsagePath(stateDir: string): string {
|
|
86
|
+
return join(stateDir, OUT_OF_TURN_USAGE_DIR, OUT_OF_TURN_USAGE_FILE);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let appendsSinceBoundCheck = BOUND_CHECK_INTERVAL;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Append one priced out-of-turn call. Never throws: this runs on the same path
|
|
93
|
+
* that answers an operator's side question, and a failed bookkeeping write must
|
|
94
|
+
* not take the answer down with it.
|
|
95
|
+
*/
|
|
96
|
+
export function appendOutOfTurnUsageRow(stateDir: string, row: OutOfTurnUsageRow): void {
|
|
97
|
+
const path = outOfTurnUsagePath(stateDir);
|
|
98
|
+
try {
|
|
99
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
100
|
+
const fd = openSync(path, "a");
|
|
101
|
+
try {
|
|
102
|
+
writeSync(fd, `${JSON.stringify(row)}\n`);
|
|
103
|
+
} finally {
|
|
104
|
+
closeSync(fd);
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
process.stderr.write(`[clio:usage] out-of-turn usage row not written: ${messageOf(error)}\n`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
appendsSinceBoundCheck += 1;
|
|
111
|
+
if (appendsSinceBoundCheck < BOUND_CHECK_INTERVAL) return;
|
|
112
|
+
appendsSinceBoundCheck = 0;
|
|
113
|
+
try {
|
|
114
|
+
boundOutOfTurnUsageFile(path);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
process.stderr.write(`[clio:usage] out-of-turn usage ring not bounded: ${messageOf(error)}\n`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Rewrite the file down to the newest {@link MAX_OUT_OF_TURN_USAGE_ROWS} lines
|
|
122
|
+
* when it has grown past the cap. Held under the state-file lock so a concurrent
|
|
123
|
+
* appender cannot lose a row into the rename, and written through
|
|
124
|
+
* `safeResourceWrite` so a crash mid-rewrite leaves the previous file intact.
|
|
125
|
+
*/
|
|
126
|
+
function boundOutOfTurnUsageFile(path: string): void {
|
|
127
|
+
withStateFileLockSync(path, () => {
|
|
128
|
+
let raw: string;
|
|
129
|
+
try {
|
|
130
|
+
raw = readFileSync(path, "utf8");
|
|
131
|
+
} catch {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
135
|
+
if (lines.length <= MAX_OUT_OF_TURN_USAGE_ROWS) return;
|
|
136
|
+
const kept = lines.slice(-MAX_OUT_OF_TURN_USAGE_ROWS);
|
|
137
|
+
safeResourceWrite(path, `${kept.join("\n")}\n`, { encoding: "utf8" });
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Read every recorded out-of-turn call. A missing file is an empty read rather
|
|
143
|
+
* than an error: a machine where nobody has ever run `/btw` has nothing to
|
|
144
|
+
* report, and that is a different thing from a store that failed to open.
|
|
145
|
+
*/
|
|
146
|
+
export function readOutOfTurnUsageRows(stateDir: string): OutOfTurnUsageReadResult {
|
|
147
|
+
const path = outOfTurnUsagePath(stateDir);
|
|
148
|
+
let raw: string;
|
|
149
|
+
try {
|
|
150
|
+
raw = readFileSync(path, "utf8");
|
|
151
|
+
} catch (error) {
|
|
152
|
+
const err = error as NodeJS.ErrnoException;
|
|
153
|
+
if (err.code === "ENOENT" || err.code === "ENOTDIR") return { rows: [], errors: [] };
|
|
154
|
+
return { rows: [], errors: [`out-of-turn usage store unreadable: ${messageOf(error)}`] };
|
|
155
|
+
}
|
|
156
|
+
const rows: OutOfTurnUsageRow[] = [];
|
|
157
|
+
const errors: string[] = [];
|
|
158
|
+
const lines = raw.split("\n");
|
|
159
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
160
|
+
const line = lines[index];
|
|
161
|
+
if (line === undefined || line.trim().length === 0) continue;
|
|
162
|
+
let parsed: unknown;
|
|
163
|
+
try {
|
|
164
|
+
parsed = JSON.parse(line) as unknown;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
errors.push(`${path}:${index + 1}: invalid JSON: ${messageOf(error)}`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const row = asOutOfTurnUsageRow(parsed);
|
|
170
|
+
if (row === null) {
|
|
171
|
+
errors.push(`${path}:${index + 1}: not an out-of-turn usage row`);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
rows.push(row);
|
|
175
|
+
}
|
|
176
|
+
return { rows, errors };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function asOutOfTurnUsageRow(value: unknown): OutOfTurnUsageRow | null {
|
|
180
|
+
if (!isRecord(value)) return null;
|
|
181
|
+
const label = value.label;
|
|
182
|
+
if (label !== "side-question" && label !== "handoff") return null;
|
|
183
|
+
if (typeof value.timestamp !== "string" || value.timestamp.length === 0) return null;
|
|
184
|
+
if (!isRecord(value.usage)) return null;
|
|
185
|
+
const usage = value.usage;
|
|
186
|
+
return {
|
|
187
|
+
label,
|
|
188
|
+
sessionId: typeof value.sessionId === "string" ? value.sessionId : null,
|
|
189
|
+
repoIdentity: typeof value.repoIdentity === "string" ? value.repoIdentity : null,
|
|
190
|
+
timestamp: value.timestamp,
|
|
191
|
+
target: typeof value.target === "string" && value.target.length > 0 ? value.target : "unknown",
|
|
192
|
+
attributedModelId:
|
|
193
|
+
typeof value.attributedModelId === "string" && value.attributedModelId.length > 0
|
|
194
|
+
? value.attributedModelId
|
|
195
|
+
: "unknown",
|
|
196
|
+
usage: {
|
|
197
|
+
input: numberOr0(usage.input),
|
|
198
|
+
output: numberOr0(usage.output),
|
|
199
|
+
cacheRead: numberOr0(usage.cacheRead),
|
|
200
|
+
cacheWrite: numberOr0(usage.cacheWrite),
|
|
201
|
+
reasoning: numberOr0(usage.reasoning),
|
|
202
|
+
totalTokens: numberOr0(usage.totalTokens),
|
|
203
|
+
costUsd: numberOr0(usage.costUsd),
|
|
204
|
+
costProvenance: asCostProvenance(usage.costProvenance),
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function asCostProvenance(value: unknown): CostProvenance {
|
|
210
|
+
return value === "known" || value === "known_free" || value === "estimated" ? value : "unknown";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function numberOr0(value: unknown): number {
|
|
214
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
218
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function messageOf(error: unknown): string {
|
|
222
|
+
return error instanceof Error ? error.message : String(error);
|
|
223
|
+
}
|
|
@@ -15,11 +15,9 @@ export interface CompileWorkerPromptInput extends WorkerPromptInputs {
|
|
|
15
15
|
/** Working directory the rule loader and repo-awareness checks run against; defaults to `process.cwd()`. */
|
|
16
16
|
cwd?: string;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* is no structured path field on the model-facing dispatch tool, so this is
|
|
22
|
-
* inference, not a guarantee: a rule can be missed, never fabricated.
|
|
18
|
+
* Canonical paths for this worker's declared intent. Legacy requests without
|
|
19
|
+
* intent retain path-like task and briefing inference. The prompt compiler
|
|
20
|
+
* consumes the resolved set without deriving another one.
|
|
23
21
|
*/
|
|
24
22
|
workingContextPaths?: ReadonlyArray<string>;
|
|
25
23
|
}
|
|
@@ -16,6 +16,7 @@ import { probeCapabilitiesForModel } from "./model-capabilities.js";
|
|
|
16
16
|
import { loadPluginRuntimes } from "./plugins.js";
|
|
17
17
|
import { getRuntimeRegistry } from "./registry.js";
|
|
18
18
|
import { registerBuiltinRuntimes } from "./runtimes/builtins.js";
|
|
19
|
+
import { listKnownModelsForRuntime } from "./support.js";
|
|
19
20
|
import type { CapabilityFlags } from "./types/capability-flags.js";
|
|
20
21
|
import { EMPTY_CAPABILITIES } from "./types/capability-flags.js";
|
|
21
22
|
import {
|
|
@@ -204,6 +205,32 @@ export function mergeProbeResult(
|
|
|
204
205
|
return merge;
|
|
205
206
|
}
|
|
206
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Whether a reachable target can serve the model it is configured to serve by
|
|
210
|
+
* default. Reachability alone was the whole of `health`, so a target whose
|
|
211
|
+
* `defaultModel` the server had never heard of printed `healthy` until the
|
|
212
|
+
* first turn. This is judged only from a live list on a runtime with no static
|
|
213
|
+
* catalog: the catalog is the authority for cloud runtimes, and a cached or
|
|
214
|
+
* descriptor-supplied list says nothing about the server in front of us.
|
|
215
|
+
* Returns the reason when the default is not served, null when it is or when
|
|
216
|
+
* there is no live evidence either way.
|
|
217
|
+
*/
|
|
218
|
+
export function unservedDefaultModelReason(
|
|
219
|
+
desc: Pick<RuntimeDescriptor, "id">,
|
|
220
|
+
target: Pick<TargetDescriptor, "defaultModel">,
|
|
221
|
+
merge: Pick<ProbeMerge, "discoveredModels" | "discoveredModelsSource" | "discoveredModelStates">,
|
|
222
|
+
): string | null {
|
|
223
|
+
const model = target.defaultModel;
|
|
224
|
+
if (!model) return null;
|
|
225
|
+
if (merge.discoveredModelsSource !== "probe" || merge.discoveredModels.length === 0) return null;
|
|
226
|
+
if (listKnownModelsForRuntime(desc.id).length > 0) return null;
|
|
227
|
+
if (merge.discoveredModels.includes(model)) return null;
|
|
228
|
+
// LM Studio lists a loaded model under its instance id and keeps the model
|
|
229
|
+
// key in the state map; the request path resolves either.
|
|
230
|
+
if (merge.discoveredModelStates && model in merge.discoveredModelStates) return null;
|
|
231
|
+
return `default model '${model}' is not advertised by the target`;
|
|
232
|
+
}
|
|
233
|
+
|
|
207
234
|
export function createProvidersBundle(context: DomainContext): DomainBundle<ProvidersContract> {
|
|
208
235
|
const registry = getRuntimeRegistry();
|
|
209
236
|
const authStore = openAuthStorage();
|
|
@@ -292,13 +319,14 @@ export function createProvidersBundle(context: DomainContext): DomainBundle<Prov
|
|
|
292
319
|
const merge = mergeProbeResult(desc, target, probe, previous);
|
|
293
320
|
const { capabilities, contextWindowProvenance } = capabilitiesFor(desc, target, merge, kb);
|
|
294
321
|
const healthy = probe !== null ? probe.ok : null;
|
|
322
|
+
const unservedDefault = probe?.ok ? unservedDefaultModelReason(desc, target, merge) : null;
|
|
295
323
|
const health: TargetHealth =
|
|
296
324
|
probe === null
|
|
297
325
|
? (previous?.health ?? emptyHealth())
|
|
298
326
|
: {
|
|
299
|
-
status: healthy ? "healthy" : "down",
|
|
327
|
+
status: healthy ? (unservedDefault === null ? "healthy" : "degraded") : "down",
|
|
300
328
|
lastCheckAt: new Date().toISOString(),
|
|
301
|
-
lastError: probe.error ??
|
|
329
|
+
lastError: probe.error ?? unservedDefault,
|
|
302
330
|
latencyMs: probe.latencyMs ?? null,
|
|
303
331
|
};
|
|
304
332
|
const available = availability.available && (probe === null || probe.ok);
|
|
@@ -7,6 +7,8 @@ import type { ResourceDiagnostic, ResourceScope, ResourceSourceInfo } from "./co
|
|
|
7
7
|
|
|
8
8
|
export interface ResourceRoot {
|
|
9
9
|
path: string;
|
|
10
|
+
/** Package root for safely resolving extension-relative prompt references. */
|
|
11
|
+
rootPath?: string;
|
|
10
12
|
scope: ResourceScope;
|
|
11
13
|
source?: string;
|
|
12
14
|
/** Collision rank, higher wins. See {@link ResourceSourceInfo.precedence}. */
|
|
@@ -44,6 +46,7 @@ export function defaultScopedResourceRoots(kind: ExtensionResourceKind, cwd: str
|
|
|
44
46
|
return [
|
|
45
47
|
...enabledExtensionResourceRoots(kind, cwd).map((root) => ({
|
|
46
48
|
path: root.path,
|
|
49
|
+
rootPath: root.rootPath,
|
|
47
50
|
scope: "package" as const,
|
|
48
51
|
source: root.source,
|
|
49
52
|
precedence: COMPAT_RESOURCE_PRECEDENCE.extension,
|
|
@@ -18,6 +18,24 @@ export function createResourcesDomainModule(options: ResourceLoaderOptions = {})
|
|
|
18
18
|
export type { ResourceDiagnostic, ResourceScope, ResourceSourceInfo } from "./collision.js";
|
|
19
19
|
export { resolveResourceCollisions } from "./collision.js";
|
|
20
20
|
export type { ResourceList, ResourcesContract } from "./contract.js";
|
|
21
|
+
export {
|
|
22
|
+
classifyLibraryRequirements,
|
|
23
|
+
confirmLibraryRemote,
|
|
24
|
+
discoverLibrary,
|
|
25
|
+
installLibraryPlan,
|
|
26
|
+
type LibraryCommandRunner,
|
|
27
|
+
type LibraryDiscoveryResult,
|
|
28
|
+
type LibraryEntry,
|
|
29
|
+
type LibraryInstallPlan,
|
|
30
|
+
type LibraryRequirementStatus,
|
|
31
|
+
libraryEntryInstalled,
|
|
32
|
+
libraryEntryPin,
|
|
33
|
+
libraryEntryRef,
|
|
34
|
+
libraryInstallPath,
|
|
35
|
+
planLibraryInstall,
|
|
36
|
+
resolveLibraryRequirements,
|
|
37
|
+
syncLibrary,
|
|
38
|
+
} from "./library.js";
|
|
21
39
|
export { createResourcesLoader, type ResourceLoaderOptions } from "./loader.js";
|
|
22
40
|
export { ResourcesManifest } from "./manifest.js";
|
|
23
41
|
export {
|
|
@@ -61,6 +79,8 @@ export {
|
|
|
61
79
|
discoverMarketplaceSkills,
|
|
62
80
|
getMarketplaceSkills,
|
|
63
81
|
installSkill,
|
|
82
|
+
type LibraryEntryKind,
|
|
83
|
+
type LibraryRequirementRef,
|
|
64
84
|
MARKETPLACE_UNCONFIGURED,
|
|
65
85
|
type MarketplaceDiscoveryResult,
|
|
66
86
|
type MarketplaceSkill,
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
5
|
+
import { readSettings, updateSettings } from "../../core/config.js";
|
|
6
|
+
import { runCommandVector, type SafeCommandResult } from "../../core/safe-exec.js";
|
|
7
|
+
import { safeResourceWrite } from "../../core/safe-resource-write.js";
|
|
8
|
+
import { clioConfigDir } from "../../core/xdg.js";
|
|
9
|
+
import { parseFrontmatter } from "../agents/frontmatter.js";
|
|
10
|
+
import {
|
|
11
|
+
assertAgentSpecPolicy,
|
|
12
|
+
normalizeAgentSpec,
|
|
13
|
+
parseAgentRecipeSchema,
|
|
14
|
+
parseFleetContract,
|
|
15
|
+
} from "../agents/index.js";
|
|
16
|
+
import { loadPromptTemplates } from "./prompts/loader.js";
|
|
17
|
+
import { normalizedSkillHash } from "./skills/install.js";
|
|
18
|
+
import {
|
|
19
|
+
type DiscoverMarketplaceOptions,
|
|
20
|
+
discoverMarketplaceSkills,
|
|
21
|
+
installSkill,
|
|
22
|
+
type LibraryEntryKind,
|
|
23
|
+
type LibraryRequirementRef,
|
|
24
|
+
type MarketplaceSkill,
|
|
25
|
+
} from "./skills/marketplace.js";
|
|
26
|
+
|
|
27
|
+
export type LibraryEntry = MarketplaceSkill;
|
|
28
|
+
|
|
29
|
+
export interface LibraryDiscoveryResult {
|
|
30
|
+
entries: LibraryEntry[];
|
|
31
|
+
diagnostics: string[];
|
|
32
|
+
refusals: Readonly<Record<string, string>>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
36
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function catalogPath(): string {
|
|
40
|
+
const configured = readSettings().library.catalog;
|
|
41
|
+
return path.resolve(configured ?? path.join(clioConfigDir(), "library.yaml"));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseCatalog(filePath: string, diagnostics: string[]): LibraryEntry[] {
|
|
45
|
+
if (!existsSync(filePath)) return [];
|
|
46
|
+
try {
|
|
47
|
+
const raw = readFileSync(filePath, "utf8");
|
|
48
|
+
const parsed = filePath.endsWith(".json") ? JSON.parse(raw) : parseYaml(raw);
|
|
49
|
+
const rows = Array.isArray(parsed) ? parsed : isRecord(parsed) && Array.isArray(parsed.skills) ? parsed.skills : [];
|
|
50
|
+
return rows.flatMap((value): LibraryEntry[] => {
|
|
51
|
+
if (
|
|
52
|
+
!isRecord(value) ||
|
|
53
|
+
typeof value.name !== "string" ||
|
|
54
|
+
typeof value.description !== "string" ||
|
|
55
|
+
typeof value.sourceUrl !== "string"
|
|
56
|
+
) {
|
|
57
|
+
diagnostics.push(`library catalog entry malformed: ${filePath}`);
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
const kind: LibraryEntryKind =
|
|
61
|
+
value.kind === undefined || value.kind === "skill"
|
|
62
|
+
? "skill"
|
|
63
|
+
: value.kind === "agent" || value.kind === "prompt" || value.kind === "fleet"
|
|
64
|
+
? value.kind
|
|
65
|
+
: "skill";
|
|
66
|
+
if (value.kind !== undefined && !["skill", "agent", "prompt", "fleet"].includes(String(value.kind))) {
|
|
67
|
+
diagnostics.push(`library catalog entry has unsupported kind: ${value.name}`);
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
if (
|
|
71
|
+
value.requires !== undefined &&
|
|
72
|
+
(!Array.isArray(value.requires) || value.requires.some((item) => typeof item !== "string"))
|
|
73
|
+
) {
|
|
74
|
+
diagnostics.push(`library_requirement_malformed: ${value.name}`);
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const sourceUrl = /^(?:https?:\/\/|git@)/.test(value.sourceUrl)
|
|
78
|
+
? value.sourceUrl
|
|
79
|
+
: path.resolve(path.dirname(filePath), value.sourceUrl);
|
|
80
|
+
const requires = Array.isArray(value.requires)
|
|
81
|
+
? value.requires.filter((item): item is LibraryRequirementRef => typeof item === "string")
|
|
82
|
+
: undefined;
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
kind,
|
|
86
|
+
name: value.name.trim(),
|
|
87
|
+
description: value.description.trim(),
|
|
88
|
+
sourceUrl,
|
|
89
|
+
origin: "index",
|
|
90
|
+
...(typeof value.version === "string" ? { version: value.version } : {}),
|
|
91
|
+
...(requires ? { requires } : {}),
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
diagnostics.push(`library catalog unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function libraryEntryRef(entry: Pick<LibraryEntry, "kind" | "name">): LibraryRequirementRef {
|
|
102
|
+
return `${entry.kind}:${entry.name}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function discoverLibrary(
|
|
106
|
+
options: { catalog?: string; cwd?: string; marketplace?: DiscoverMarketplaceOptions } = {},
|
|
107
|
+
): LibraryDiscoveryResult {
|
|
108
|
+
const marketplace = discoverMarketplaceSkills({
|
|
109
|
+
...(options.marketplace ?? {}),
|
|
110
|
+
...(options.cwd ? { cwd: options.cwd } : {}),
|
|
111
|
+
});
|
|
112
|
+
const diagnostics = [...marketplace.diagnostics.filter((item) => !item.includes("no local skill marketplace"))];
|
|
113
|
+
const privatePath = catalogPath();
|
|
114
|
+
const primary = options.catalog ? parseCatalog(path.resolve(options.catalog), diagnostics) : [];
|
|
115
|
+
const privateEntries = parseCatalog(privatePath, diagnostics);
|
|
116
|
+
const byRef = new Map<string, LibraryEntry>();
|
|
117
|
+
for (const entry of [...marketplace.skills, ...primary, ...privateEntries]) byRef.set(libraryEntryRef(entry), entry);
|
|
118
|
+
const refusals: Record<string, string> = {};
|
|
119
|
+
const entries = [...byRef.values()].filter((entry) => {
|
|
120
|
+
try {
|
|
121
|
+
resolveLibraryRequirements(entry, [...byRef.values()]);
|
|
122
|
+
return true;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
125
|
+
refusals[libraryEntryRef(entry)] = message;
|
|
126
|
+
if (!diagnostics.includes(message)) diagnostics.push(message);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
entries: entries.sort((a, b) => libraryEntryRef(a).localeCompare(libraryEntryRef(b))),
|
|
132
|
+
diagnostics,
|
|
133
|
+
refusals,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const REQUIREMENT_PATTERN = /^(skill|agent|prompt|fleet):([A-Za-z0-9][A-Za-z0-9._-]*)$/;
|
|
138
|
+
|
|
139
|
+
export function resolveLibraryRequirements(entry: LibraryEntry, catalog: ReadonlyArray<LibraryEntry>): LibraryEntry[] {
|
|
140
|
+
const byRef = new Map(catalog.map((item) => [libraryEntryRef(item), item]));
|
|
141
|
+
const ordered: LibraryEntry[] = [];
|
|
142
|
+
const visiting: string[] = [];
|
|
143
|
+
const visited = new Set<string>();
|
|
144
|
+
const visit = (current: LibraryEntry): void => {
|
|
145
|
+
const currentRef = libraryEntryRef(current);
|
|
146
|
+
if (visited.has(currentRef)) return;
|
|
147
|
+
const cycleAt = visiting.indexOf(currentRef);
|
|
148
|
+
if (cycleAt >= 0)
|
|
149
|
+
throw new Error(`library_requirement_cycle: ${[...visiting.slice(cycleAt), currentRef].join(" -> ")}`);
|
|
150
|
+
visiting.push(currentRef);
|
|
151
|
+
for (const requirement of current.requires ?? []) {
|
|
152
|
+
if (!REQUIREMENT_PATTERN.test(requirement)) throw new Error("library_requirement_malformed");
|
|
153
|
+
const dependency = byRef.get(requirement);
|
|
154
|
+
if (!dependency) throw new Error(`library_requirement_missing: ${requirement}`);
|
|
155
|
+
visit(dependency);
|
|
156
|
+
}
|
|
157
|
+
visiting.pop();
|
|
158
|
+
visited.add(currentRef);
|
|
159
|
+
ordered.push(current);
|
|
160
|
+
};
|
|
161
|
+
visit(entry);
|
|
162
|
+
return ordered;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function sourceFile(entry: LibraryEntry): string {
|
|
166
|
+
const source = path.resolve(entry.sourceUrl);
|
|
167
|
+
if (entry.kind === "skill") return source;
|
|
168
|
+
if (!existsSync(source)) throw new Error(`library source path does not exist: ${source}`);
|
|
169
|
+
return source;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function validateEntry(entry: LibraryEntry, raw: string, filePath: string): void {
|
|
173
|
+
if (entry.kind === "fleet") {
|
|
174
|
+
parseFleetContract(raw, filePath);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (entry.kind === "agent") {
|
|
178
|
+
const parsed = parseFrontmatter(raw, filePath);
|
|
179
|
+
const recipe = parseAgentRecipeSchema({ id: entry.name, source: "user", filepath: filePath, ...parsed });
|
|
180
|
+
assertAgentSpecPolicy(normalizeAgentSpec(recipe));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (entry.kind === "prompt") {
|
|
184
|
+
const loaded = loadPromptTemplates({ roots: [{ path: path.dirname(filePath), scope: "user", source: "library" }] });
|
|
185
|
+
if (!loaded.items.some((item) => item.filePath === filePath))
|
|
186
|
+
throw new Error(`prompt template is malformed: ${filePath}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface LibraryInstallPlan {
|
|
191
|
+
entry: LibraryEntry;
|
|
192
|
+
path: string;
|
|
193
|
+
sha256: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function libraryInstallPath(entry: Pick<LibraryEntry, "kind" | "name">): string {
|
|
197
|
+
if (entry.kind === "skill") return path.join(clioConfigDir(), "skills", entry.name, "SKILL.md");
|
|
198
|
+
const root = entry.kind === "agent" ? "agents" : entry.kind === "fleet" ? "fleets" : "prompts";
|
|
199
|
+
return path.join(clioConfigDir(), root, `${entry.name}.md`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function readLibraryPins(): Record<string, { sha256: string; sourceUrl: string }> {
|
|
203
|
+
const pinPath = path.join(clioConfigDir(), "library-pins.yaml");
|
|
204
|
+
if (!existsSync(pinPath)) return {};
|
|
205
|
+
const parsed = parseYaml(readFileSync(pinPath, "utf8"));
|
|
206
|
+
return isRecord(parsed) ? (parsed as Record<string, { sha256: string; sourceUrl: string }>) : {};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface LibraryRequirementStatus {
|
|
210
|
+
ordered: LibraryEntry[];
|
|
211
|
+
satisfied: LibraryEntry[];
|
|
212
|
+
unsatisfied: LibraryEntry[];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Whether this entry is already on disk for this operator. A kind-qualified pin
|
|
217
|
+
* and a kind-specific destination are each sufficient, which is the same rule
|
|
218
|
+
* the requirement classifier applies, so a surface that draws an installed or
|
|
219
|
+
* available column never disagrees with the gate that refuses an install.
|
|
220
|
+
*/
|
|
221
|
+
export function libraryEntryInstalled(entry: Pick<LibraryEntry, "kind" | "name">): boolean {
|
|
222
|
+
return readLibraryPins()[libraryEntryRef(entry)] !== undefined || existsSync(libraryInstallPath(entry));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* The pin recorded for this entry, or undefined when nothing pinned it. A
|
|
227
|
+
* destination can exist without a pin, which is why this is a separate question
|
|
228
|
+
* from `libraryEntryInstalled`.
|
|
229
|
+
*/
|
|
230
|
+
export function libraryEntryPin(
|
|
231
|
+
entry: Pick<LibraryEntry, "kind" | "name">,
|
|
232
|
+
): { sha256: string; sourceUrl: string } | undefined {
|
|
233
|
+
return readLibraryPins()[libraryEntryRef(entry)];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function classifyLibraryRequirements(
|
|
237
|
+
entry: LibraryEntry,
|
|
238
|
+
catalog: ReadonlyArray<LibraryEntry>,
|
|
239
|
+
): LibraryRequirementStatus {
|
|
240
|
+
const ordered = resolveLibraryRequirements(entry, catalog).slice(0, -1);
|
|
241
|
+
const satisfied = ordered.filter((requirement) => libraryEntryInstalled(requirement));
|
|
242
|
+
const satisfiedRefs = new Set(satisfied.map(libraryEntryRef));
|
|
243
|
+
return {
|
|
244
|
+
ordered,
|
|
245
|
+
satisfied,
|
|
246
|
+
unsatisfied: ordered.filter((requirement) => !satisfiedRefs.has(libraryEntryRef(requirement))),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function planLibraryInstall(entry: LibraryEntry): LibraryInstallPlan {
|
|
251
|
+
if (entry.kind === "skill") {
|
|
252
|
+
const source = sourceFile(entry);
|
|
253
|
+
const file = statSync(source).isDirectory() ? path.join(source, "SKILL.md") : source;
|
|
254
|
+
const raw = readFileSync(file);
|
|
255
|
+
return {
|
|
256
|
+
entry,
|
|
257
|
+
path: libraryInstallPath(entry),
|
|
258
|
+
sha256: normalizedSkillHash(raw.toString("utf8")),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const source = sourceFile(entry);
|
|
262
|
+
const raw = readFileSync(source, "utf8");
|
|
263
|
+
validateEntry(entry, raw, source);
|
|
264
|
+
return {
|
|
265
|
+
entry,
|
|
266
|
+
path: libraryInstallPath(entry),
|
|
267
|
+
sha256: createHash("sha256").update(raw).digest("hex"),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function recordPin(plan: LibraryInstallPlan): void {
|
|
272
|
+
const pinPath = path.join(clioConfigDir(), "library-pins.yaml");
|
|
273
|
+
const pins = readLibraryPins();
|
|
274
|
+
pins[libraryEntryRef(plan.entry)] = { sha256: plan.sha256, sourceUrl: plan.entry.sourceUrl };
|
|
275
|
+
safeResourceWrite(pinPath, stringifyYaml(Object.fromEntries(Object.entries(pins).sort())), { encoding: "utf8" });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function installLibraryPlan(plan: LibraryInstallPlan): void {
|
|
279
|
+
if (existsSync(plan.path)) throw new Error(`library destination already exists: ${plan.path}`);
|
|
280
|
+
if (plan.entry.kind === "skill") installSkill({ source: plan.entry.sourceUrl, scope: "user", name: plan.entry.name });
|
|
281
|
+
else safeResourceWrite(plan.path, readFileSync(sourceFile(plan.entry)));
|
|
282
|
+
recordPin(plan);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function confirmedRemote(): string {
|
|
286
|
+
const settings = readSettings().library;
|
|
287
|
+
if (!settings.remote || settings.remote !== settings.confirmedRemote) throw new Error("library_remote_unconfirmed");
|
|
288
|
+
return settings.remote;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function confirmLibraryRemote(url: string): void {
|
|
292
|
+
const current = readSettings().library.remote;
|
|
293
|
+
if (current !== null && current !== url) throw new Error("library_remote_mismatch");
|
|
294
|
+
updateSettings((settings) => {
|
|
295
|
+
if (settings.library.remote === null) settings.library.remote = url;
|
|
296
|
+
settings.library.confirmedRemote = url;
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export type LibraryCommandRunner = (
|
|
301
|
+
file: string,
|
|
302
|
+
args: ReadonlyArray<string>,
|
|
303
|
+
options: { cwd: string; workspaceRoot: string },
|
|
304
|
+
) => Promise<SafeCommandResult>;
|
|
305
|
+
|
|
306
|
+
export async function syncLibrary(
|
|
307
|
+
direction: "sync" | "push",
|
|
308
|
+
runner: LibraryCommandRunner = runCommandVector,
|
|
309
|
+
): Promise<void> {
|
|
310
|
+
const settings = readSettings().library;
|
|
311
|
+
if (!settings.sync) throw new Error("library_sync_disabled");
|
|
312
|
+
const remote = confirmedRemote();
|
|
313
|
+
const cwd = path.dirname(catalogPath());
|
|
314
|
+
const remoteResult = await runner("git", ["remote", "get-url", "library"], { cwd, workspaceRoot: cwd });
|
|
315
|
+
if (remoteResult.exitCode !== 0 || remoteResult.stdout.trim() !== remote)
|
|
316
|
+
throw new Error("library_remote_unconfirmed");
|
|
317
|
+
if (direction === "sync") {
|
|
318
|
+
const fetched = await runner("git", ["fetch", "library"], { cwd, workspaceRoot: cwd });
|
|
319
|
+
if (fetched.exitCode !== 0) throw new Error(fetched.stderr.trim() || "library sync failed");
|
|
320
|
+
const advanced = await runner("git", ["merge", "--ff-only", "FETCH_HEAD"], { cwd, workspaceRoot: cwd });
|
|
321
|
+
if (advanced.exitCode !== 0) throw new Error(advanced.stderr.trim() || "library sync failed");
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const result = await runner("git", ["push", "library"], { cwd, workspaceRoot: cwd });
|
|
325
|
+
if (result.exitCode !== 0) throw new Error(result.stderr.trim() || `library ${direction} failed`);
|
|
326
|
+
}
|