@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,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
|
+
}
|
|
@@ -35,6 +35,8 @@ import { loadSkills, type Skill } from "./loader.js";
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
export type MarketplaceSkillOrigin = "catalog" | "index";
|
|
38
|
+
export type LibraryEntryKind = "skill" | "agent" | "prompt" | "fleet";
|
|
39
|
+
export type LibraryRequirementRef = `${LibraryEntryKind}:${string}`;
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* The diagnostic that means "nothing is wrong, nothing is configured". Callers
|
|
@@ -44,6 +46,7 @@ export type MarketplaceSkillOrigin = "catalog" | "index";
|
|
|
44
46
|
export const MARKETPLACE_UNCONFIGURED = "no local skill marketplace catalog or index configured";
|
|
45
47
|
|
|
46
48
|
export interface MarketplaceSkill {
|
|
49
|
+
kind: LibraryEntryKind;
|
|
47
50
|
name: string;
|
|
48
51
|
description: string;
|
|
49
52
|
/** Local path or URL accepted by `clio-coder skills install`. */
|
|
@@ -53,6 +56,7 @@ export interface MarketplaceSkill {
|
|
|
53
56
|
/** Catalog grouping ("git", "research", ...); absent in a flat catalog. */
|
|
54
57
|
category?: string;
|
|
55
58
|
origin: MarketplaceSkillOrigin;
|
|
59
|
+
requires?: LibraryRequirementRef[];
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
export type MarketplaceStatus = "installed" | "installable" | "unavailable";
|
|
@@ -131,7 +135,7 @@ function indexAudit(value: unknown): MarketplaceSkill["audit"] {
|
|
|
131
135
|
return value === "pass" || value === "warn" || value === "fail" || value === "unknown" ? value : undefined;
|
|
132
136
|
}
|
|
133
137
|
|
|
134
|
-
function
|
|
138
|
+
function parseMarketplaceIndex(indexPath: string, diagnostics: string[] = []): MarketplaceSkill[] {
|
|
135
139
|
try {
|
|
136
140
|
const parsed = JSON.parse(readFileSync(indexPath, "utf8")) as unknown;
|
|
137
141
|
const rawSkills = Array.isArray(parsed)
|
|
@@ -139,7 +143,7 @@ function indexSkills(indexPath: string, diagnostics: string[]): MarketplaceSkill
|
|
|
139
143
|
: parsed && typeof parsed === "object" && Array.isArray((parsed as { skills?: unknown }).skills)
|
|
140
144
|
? (parsed as { skills: unknown[] }).skills
|
|
141
145
|
: [];
|
|
142
|
-
return rawSkills.filter(isIndexSkill).
|
|
146
|
+
return rawSkills.filter(isIndexSkill).flatMap((skill): MarketplaceSkill[] => {
|
|
143
147
|
const record = skill as unknown as Record<string, unknown>;
|
|
144
148
|
// Version, audit and category are published by `npm run skills:pin`.
|
|
145
149
|
// They are advisory display metadata; an index entry stays installable
|
|
@@ -147,15 +151,35 @@ function indexSkills(indexPath: string, diagnostics: string[]): MarketplaceSkill
|
|
|
147
151
|
const version = optionalString(record.version);
|
|
148
152
|
const audit = indexAudit(record.audit);
|
|
149
153
|
const category = optionalString(record.category);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
if (record.kind !== undefined && !["skill", "agent", "prompt", "fleet"].includes(String(record.kind))) {
|
|
155
|
+
diagnostics.push(`skill marketplace index entry has unsupported kind: ${skill.name}`);
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
if (
|
|
159
|
+
record.requires !== undefined &&
|
|
160
|
+
(!Array.isArray(record.requires) || record.requires.some((entry) => typeof entry !== "string"))
|
|
161
|
+
) {
|
|
162
|
+
diagnostics.push(`library_requirement_malformed: ${skill.name}`);
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
const kind: LibraryEntryKind =
|
|
166
|
+
record.kind === "agent" || record.kind === "prompt" || record.kind === "fleet" ? record.kind : "skill";
|
|
167
|
+
const requires = Array.isArray(record.requires)
|
|
168
|
+
? record.requires.filter((entry): entry is LibraryRequirementRef => typeof entry === "string")
|
|
169
|
+
: undefined;
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
kind,
|
|
173
|
+
name: skill.name.trim(),
|
|
174
|
+
description: skill.description.trim(),
|
|
175
|
+
sourceUrl: skill.sourceUrl.trim(),
|
|
176
|
+
...(version ? { version } : {}),
|
|
177
|
+
...(audit ? { audit } : {}),
|
|
178
|
+
...(category ? { category } : {}),
|
|
179
|
+
...(requires ? { requires } : {}),
|
|
180
|
+
origin: "index" as const,
|
|
181
|
+
},
|
|
182
|
+
];
|
|
159
183
|
});
|
|
160
184
|
} catch (err) {
|
|
161
185
|
diagnostics.push(`skill marketplace index unreadable: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -226,6 +250,7 @@ function catalogEntry(skill: Skill, catalogDir: string): MarketplaceSkill {
|
|
|
226
250
|
...(skill.provenance?.audit ? { audit: skill.provenance.audit } : {}),
|
|
227
251
|
...(category ? { category } : {}),
|
|
228
252
|
origin: "catalog",
|
|
253
|
+
kind: "skill",
|
|
229
254
|
};
|
|
230
255
|
}
|
|
231
256
|
|
|
@@ -256,7 +281,7 @@ export function discoverMarketplaceSkills(options: DiscoverMarketplaceOptions =
|
|
|
256
281
|
options.indexPath === null ? null : (options.indexPath ?? process.env.CLIO_CODER_SKILL_MARKETPLACE_INDEX ?? null);
|
|
257
282
|
const resolvedIndexPath = indexPath ?? defaultIndexPath();
|
|
258
283
|
if (options.indexPath !== null && resolvedIndexPath && existsSync(resolvedIndexPath)) {
|
|
259
|
-
for (const skill of
|
|
284
|
+
for (const skill of parseMarketplaceIndex(resolvedIndexPath, diagnostics)) {
|
|
260
285
|
if (seen.has(skill.name)) continue;
|
|
261
286
|
seen.add(skill.name);
|
|
262
287
|
skills.push(skill);
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* or spoof the UI that approves it.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { isSecretArgKey, redactSecretString } from "./redaction.js";
|
|
11
|
+
|
|
10
12
|
const ESC_CHAR = String.fromCharCode(27);
|
|
11
13
|
const BEL_CHAR = String.fromCharCode(7);
|
|
12
14
|
// Built through the constructor so no control character appears in a regex
|
|
@@ -39,21 +41,216 @@ export function sanitizeCallTargetText(value: string): string {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
44
|
+
* What a tool call is doing, in the bounded form that may cross the worker
|
|
45
|
+
* stdout seam. The verb comes from a fixed vocabulary and the object from a
|
|
46
|
+
* fixed allowlist of argument fields, so an argument the table does not name
|
|
47
|
+
* cannot reach an operator surface however it is spelled.
|
|
48
|
+
*/
|
|
49
|
+
export interface CallActionDescriptor {
|
|
50
|
+
/** One word from the vocabulary below naming what the call does. */
|
|
51
|
+
verb: string;
|
|
52
|
+
/** The redacted, bounded thing the call acts on. Absent when nothing safe is derivable. */
|
|
53
|
+
object?: string;
|
|
54
|
+
/** Whether the object was cut to {@link CALL_ACTION_OBJECT_MAX_CHARS}. */
|
|
55
|
+
truncated?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Characters of object text a descriptor carries. Bounded here rather than at
|
|
60
|
+
* the renderer: this string crosses a process boundary, so a hostile argument
|
|
61
|
+
* must be small before it is transported, not after.
|
|
46
62
|
*/
|
|
47
|
-
export
|
|
63
|
+
export const CALL_ACTION_OBJECT_MAX_CHARS = 64;
|
|
64
|
+
|
|
65
|
+
/** Maximum characters carried by the approval overlay's one-line call target. */
|
|
66
|
+
export const CALL_TARGET_MAX_CHARS = 120;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The verb and object field for each tool Clio ships, plus the ACP tool kinds
|
|
70
|
+
* a delegated peer reports. A tool absent from this table gets the neutral
|
|
71
|
+
* `calling` verb, never a verb guessed from its name.
|
|
72
|
+
*/
|
|
73
|
+
const CALL_ACTION_VOCABULARY: Readonly<Record<string, { verb: string; field: string }>> = {
|
|
74
|
+
read: { verb: "reading", field: "path" },
|
|
75
|
+
edit: { verb: "editing", field: "path" },
|
|
76
|
+
write: { verb: "writing", field: "path" },
|
|
77
|
+
ls: { verb: "listing", field: "path" },
|
|
78
|
+
bash: { verb: "running", field: "command" },
|
|
79
|
+
grep: { verb: "searching", field: "pattern" },
|
|
80
|
+
find: { verb: "finding", field: "pattern" },
|
|
81
|
+
web_fetch: { verb: "fetching", field: "url" },
|
|
82
|
+
git: { verb: "git", field: "op" },
|
|
83
|
+
verify: { verb: "verifying", field: "check" },
|
|
84
|
+
code_nav: { verb: "navigating", field: "query" },
|
|
85
|
+
context: { verb: "context", field: "scope" },
|
|
86
|
+
artifact: { verb: "writing", field: "kind" },
|
|
87
|
+
monitor: { verb: "monitoring", field: "run_id" },
|
|
88
|
+
steer: { verb: "steering", field: "run_id" },
|
|
89
|
+
tasks: { verb: "tasks", field: "action" },
|
|
90
|
+
dispatch: { verb: "dispatching", field: "agent" },
|
|
91
|
+
// ACP tool kinds. A peer names its own argument fields, so these rely on
|
|
92
|
+
// the shared allowlist below rather than on a field this side can predict.
|
|
93
|
+
execute: { verb: "running", field: "command" },
|
|
94
|
+
search: { verb: "searching", field: "query" },
|
|
95
|
+
fetch: { verb: "fetching", field: "url" },
|
|
96
|
+
delete: { verb: "deleting", field: "path" },
|
|
97
|
+
move: { verb: "moving", field: "path" },
|
|
98
|
+
think: { verb: "thinking", field: "" },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Argument fields any tool may surface as its object. A field outside this
|
|
103
|
+
* list is never read, so a tool that hides a credential in `body`, `headers`,
|
|
104
|
+
* or `env` cannot leak it through a descriptor.
|
|
105
|
+
*/
|
|
106
|
+
const CALL_ACTION_OBJECT_FIELDS = ["path", "file_path", "command", "pattern", "query", "url"] as const;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Argument fields whose values may reach the approval overlay for each tool.
|
|
110
|
+
* Fields are ordered by decision value: the first present field is the plain
|
|
111
|
+
* target, and any later present fields are labeled facts. Everything outside
|
|
112
|
+
* the tool's list is described only by type and size.
|
|
113
|
+
*/
|
|
114
|
+
const CALL_TARGET_FIELDS: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
115
|
+
read: ["path", "offset", "limit", "tail"],
|
|
116
|
+
grep: ["pattern", "path", "mode", "glob", "ignore_case", "literal", "context", "limit", "include_ignored"],
|
|
117
|
+
find: ["pattern", "path", "order", "limit", "include_ignored"],
|
|
118
|
+
ls: ["path", "limit"],
|
|
119
|
+
code_nav: ["mode", "query", "limit"],
|
|
120
|
+
context: ["scope", "query", "name", "limit", "ref", "include_tree"],
|
|
121
|
+
credential_present: ["name", "source", "file"],
|
|
122
|
+
write: ["path"],
|
|
123
|
+
edit: ["path"],
|
|
124
|
+
bash: ["command", "cwd", "timeout_ms", "output_policy"],
|
|
125
|
+
git: ["op", "path", "cached", "stat", "name_only", "limit", "cwd", "timeout_ms", "max_output_bytes"],
|
|
126
|
+
verify: ["check", "path", "browser", "cwd", "timeout_ms", "max_output_bytes"],
|
|
127
|
+
dispatch: [
|
|
128
|
+
"list",
|
|
129
|
+
"mode",
|
|
130
|
+
"agent",
|
|
131
|
+
"target",
|
|
132
|
+
"model",
|
|
133
|
+
"node",
|
|
134
|
+
"autonomy",
|
|
135
|
+
"tool_profile",
|
|
136
|
+
"thinking_level",
|
|
137
|
+
"detach",
|
|
138
|
+
"timeout_ms",
|
|
139
|
+
],
|
|
140
|
+
monitor: ["run_id", "mode"],
|
|
141
|
+
steer: ["run_id", "action"],
|
|
142
|
+
tasks: ["action", "id"],
|
|
143
|
+
ledger: ["action", "kind", "path", "line", "target", "passed", "since"],
|
|
144
|
+
web_fetch: ["url", "method", "timeout_ms", "max_bytes", "format"],
|
|
145
|
+
ask_user: ["action", "mode", "max_rounds", "exposure"],
|
|
146
|
+
artifact: ["kind", "path", "title"],
|
|
147
|
+
// ACP tool kinds use only fields whose meaning the protocol defines.
|
|
148
|
+
execute: ["command", "cwd"],
|
|
149
|
+
search: ["query", "path"],
|
|
150
|
+
fetch: ["url", "method"],
|
|
151
|
+
delete: ["path"],
|
|
152
|
+
move: ["path", "target"],
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/** Sanitize, scrub, and bound one candidate object string. Null when nothing is left. */
|
|
156
|
+
function boundActionObject(value: unknown): { object: string; truncated: boolean } | null {
|
|
157
|
+
if (typeof value !== "string") return null;
|
|
158
|
+
const clean = sanitizeCallTargetText(redactSecretString(value));
|
|
159
|
+
if (clean.length === 0) return null;
|
|
160
|
+
if (clean.length <= CALL_ACTION_OBJECT_MAX_CHARS) return { object: clean, truncated: false };
|
|
161
|
+
return { object: clean.slice(0, CALL_ACTION_OBJECT_MAX_CHARS), truncated: true };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Compose the redacted action descriptor for one tool call. Called at the
|
|
166
|
+
* trusted seams that hold the arguments (the tool registry's admission path,
|
|
167
|
+
* the Claude tool mapper, and the ACP update mapper) so the descriptor, and
|
|
168
|
+
* never the arguments, is what crosses into an operator surface.
|
|
169
|
+
*
|
|
170
|
+
* Returns null when the tool is unknown and no allowlisted field is present:
|
|
171
|
+
* a bare `calling` with nothing after it says less than the tool name already
|
|
172
|
+
* on the row.
|
|
173
|
+
*/
|
|
174
|
+
export function describeCallAction(
|
|
175
|
+
tool: string,
|
|
176
|
+
args: Record<string, unknown> | undefined,
|
|
177
|
+
): CallActionDescriptor | null {
|
|
178
|
+
const known = CALL_ACTION_VOCABULARY[tool];
|
|
179
|
+
const candidates = known?.field ? [known.field, ...CALL_ACTION_OBJECT_FIELDS] : [...CALL_ACTION_OBJECT_FIELDS];
|
|
180
|
+
let bounded: { object: string; truncated: boolean } | null = null;
|
|
181
|
+
if (args) {
|
|
182
|
+
for (const field of candidates) {
|
|
183
|
+
if (isSecretArgKey(field)) continue;
|
|
184
|
+
bounded = boundActionObject(args[field]);
|
|
185
|
+
if (bounded !== null) break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (known === undefined && bounded === null) return null;
|
|
189
|
+
const verb = known?.verb ?? "calling";
|
|
190
|
+
if (bounded === null) return { verb };
|
|
191
|
+
return { verb, object: bounded.object, ...(bounded.truncated ? { truncated: true } : {}) };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function renderAllowedTargetValue(value: unknown): string | null {
|
|
195
|
+
if (typeof value === "string") {
|
|
196
|
+
const rendered = sanitizeCallTargetText(redactSecretString(value));
|
|
197
|
+
return rendered.length > 0 ? rendered : null;
|
|
198
|
+
}
|
|
199
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
200
|
+
if (typeof value === "boolean") return String(value);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function countLabel(count: number, singular: string, plural: string): string {
|
|
205
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Describe an argument without copying any part of its value into display text. */
|
|
209
|
+
function summarizeUnlistedTargetValue(value: unknown): string {
|
|
210
|
+
if (typeof value === "string") {
|
|
211
|
+
return `<string ${countLabel(Buffer.byteLength(value, "utf8"), "byte", "bytes")}>`;
|
|
212
|
+
}
|
|
213
|
+
if (Array.isArray(value)) return `<array ${countLabel(value.length, "item", "items")}>`;
|
|
214
|
+
if (value !== null && typeof value === "object") {
|
|
215
|
+
return `<object ${countLabel(Object.keys(value).length, "field", "fields")}>`;
|
|
216
|
+
}
|
|
217
|
+
if (value === null) return "<null 0 values>";
|
|
218
|
+
if (typeof value === "undefined") return "<undefined 0 values>";
|
|
219
|
+
return `<${typeof value} 1 value>`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function targetFieldName(value: string): string {
|
|
223
|
+
return sanitizeCallTargetText(value).slice(0, 32) || "field";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Derive the operator-facing object of a call for the approval overlay. Only
|
|
228
|
+
* values in the named tool's allowlist may render. Every other argument is
|
|
229
|
+
* summarized by its field name, type, and size, so an unexpected credential
|
|
230
|
+
* or pasted document still informs the decision without disclosing content.
|
|
231
|
+
* Returns an empty string when the call carries no arguments.
|
|
232
|
+
*/
|
|
233
|
+
export function describeCallTarget(tool: string, args: Record<string, unknown> | undefined): string {
|
|
48
234
|
if (!args) return "";
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
235
|
+
const allowedFields = CALL_TARGET_FIELDS[tool] ?? [];
|
|
236
|
+
const allowed = new Set(allowedFields);
|
|
237
|
+
const parts: string[] = [];
|
|
238
|
+
for (const field of allowedFields) {
|
|
239
|
+
if (!(field in args)) continue;
|
|
240
|
+
if (isSecretArgKey(field)) {
|
|
241
|
+
parts.push(`${targetFieldName(field)}=${summarizeUnlistedTargetValue(args[field])}`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const rendered = renderAllowedTargetValue(args[field]);
|
|
245
|
+
if (rendered === null) {
|
|
246
|
+
parts.push(`${targetFieldName(field)}=${summarizeUnlistedTargetValue(args[field])}`);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
parts.push(parts.length === 0 ? rendered : `${targetFieldName(field)}=${rendered}`);
|
|
250
|
+
}
|
|
251
|
+
for (const [field, value] of Object.entries(args)) {
|
|
252
|
+
if (allowed.has(field)) continue;
|
|
253
|
+
parts.push(`${targetFieldName(field)}=${summarizeUnlistedTargetValue(value)}`);
|
|
58
254
|
}
|
|
255
|
+
return sanitizeCallTargetText(parts.join(" · ")).slice(0, CALL_TARGET_MAX_CHARS);
|
|
59
256
|
}
|