@iowarp/clio-coder 0.5.0 → 0.5.2
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/.claude-plugin/marketplace.json +4 -4
- package/CHANGELOG.md +35 -0
- package/CONTRIBUTING.md +9 -0
- package/README.md +12 -9
- package/ROADMAP.md +30 -5
- package/dist/{acp-KS7ARGV6.js → acp-F6PGOFZ4.js} +2 -2
- package/dist/{agents-YOGJ2YEJ.js → agents-ZHP4R6EX.js} +22 -21
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-CEDCO6O6.js → auth-N75U2H3Z.js} +4 -4
- package/dist/{chunk-D3KWFOUT.js → chunk-23UIXH6Z.js} +3 -3
- package/dist/{chunk-ZR5YA6ZF.js → chunk-36XVK5P5.js} +5 -5
- package/dist/{chunk-AVSR2HKI.js → chunk-3DZ4LPLS.js} +6 -6
- package/dist/{chunk-LYJJ6546.js → chunk-3GSPCWFX.js} +12 -12
- package/dist/{chunk-CRHPCPZP.js → chunk-4KC4CHKJ.js} +2 -2
- package/dist/{chunk-4VDHLCF5.js → chunk-4WJBWKMI.js} +2 -2
- package/dist/{chunk-KBUTSJSR.js → chunk-4XBYHXGK.js} +2 -2
- package/dist/{chunk-6W75D37F.js → chunk-53P775GE.js} +6 -3
- package/dist/{chunk-XZWSP67B.js → chunk-5APYAXCB.js} +2 -2
- package/dist/{chunk-D65E25JG.js → chunk-67HPYNTJ.js} +2 -2
- package/dist/{chunk-5JQR5B4D.js → chunk-6KV4KCQZ.js} +4 -4
- package/dist/{chunk-V4XJWDHR.js → chunk-6UKFRTXI.js} +2 -1
- package/dist/{chunk-NDPF2N2L.js → chunk-7DW7CJW2.js} +2 -2
- package/dist/{chunk-5KQEUIWM.js → chunk-7LUEQODV.js} +4 -4
- package/dist/{chunk-LNOXCGN2.js → chunk-7UFDIOBT.js} +3 -3
- package/dist/{chunk-DXSETN72.js → chunk-7WUGNN43.js} +2 -2
- package/dist/{chunk-V35KZDUZ.js → chunk-AA3GRZWS.js} +2 -2
- package/dist/{chunk-X4CEFOSG.js → chunk-ADO3OQIC.js} +4 -4
- package/dist/{chunk-UX2L5CUD.js → chunk-AI36V3ON.js} +3 -3
- package/dist/{chunk-B7GY6QER.js → chunk-B26OUMJW.js} +2 -2
- package/dist/{chunk-LAR7DX5E.js → chunk-B5TASTCK.js} +153 -62
- package/dist/{chunk-TGWZXJ7L.js → chunk-BEMQK5Q7.js} +92 -84
- package/dist/{chunk-RNDCROSG.js → chunk-CNXU7TR2.js} +3 -3
- package/dist/{chunk-3JLTRPN7.js → chunk-CVMUXT64.js} +35 -66
- package/dist/{chunk-733ZBT5P.js → chunk-E74MJF6G.js} +4 -4
- package/dist/{chunk-T2MDZES3.js → chunk-EBMTBE76.js} +3 -3
- package/dist/{chunk-OZ76TLGU.js → chunk-EEITU7S6.js} +2 -2
- package/dist/{chunk-GRHOXDKB.js → chunk-ELDJWJKM.js} +97 -14
- package/dist/{chunk-GCQFN7YI.js → chunk-EMYEQLWO.js} +2 -2
- package/dist/{chunk-LTWH2ACR.js → chunk-EQPFEDFV.js} +222 -127
- package/dist/{chunk-TDGYTWM6.js → chunk-F4I3H56H.js} +2 -2
- package/dist/chunk-F6KCJO3U.js +523 -0
- package/dist/{chunk-FGSQHUCK.js → chunk-F6VKWTTY.js} +5 -5
- package/dist/{chunk-UJD5MPP6.js → chunk-FLNXQQ5B.js} +2 -2
- package/dist/{chunk-EX57VVWW.js → chunk-FOSRJ2VZ.js} +4 -4
- package/dist/{chunk-ZARZ4POR.js → chunk-G5ZDQQE3.js} +3 -3
- package/dist/{chunk-HQVN7G4F.js → chunk-G62JBPXN.js} +7 -7
- package/dist/{chunk-ZJPORZTC.js → chunk-GIAI7A2K.js} +2 -2
- package/dist/chunk-GPTR6OTU.js +2551 -0
- package/dist/{chunk-EZD2BXGP.js → chunk-GUFQPTQK.js} +2 -2
- package/dist/{chunk-OPD7GL6F.js → chunk-H6FQVNZC.js} +8 -7
- package/dist/chunk-H7REJ7L5.js +80 -0
- package/dist/{chunk-EYTLV3W3.js → chunk-HTWTORF7.js} +2 -2
- package/dist/{chunk-DF6PI6GN.js → chunk-I6UKQPGS.js} +3 -3
- package/dist/{chunk-ISHUS7HC.js → chunk-JRW236JT.js} +3 -3
- package/dist/{chunk-DE2LU267.js → chunk-KKAJGT3B.js} +3 -2
- package/dist/{chunk-OV62D6KO.js → chunk-KQMYL5GR.js} +3 -3
- package/dist/{chunk-QDUUOZ3S.js → chunk-KWMFC7FN.js} +3 -3
- package/dist/{chunk-R7FGRZNF.js → chunk-LXYHCCIZ.js} +9 -9
- package/dist/{chunk-NFSK2VNU.js → chunk-LZMTVVN4.js} +2 -2
- package/dist/{chunk-62UK7AGW.js → chunk-M5PMRZSR.js} +397 -44
- package/dist/chunk-MMZMM6SW.js +131 -0
- package/dist/{chunk-FFYTC2KQ.js → chunk-MNABNEBX.js} +2 -2
- package/dist/{chunk-XMDBTYCH.js → chunk-N3CWOGVF.js} +3 -3
- package/dist/{chunk-VYRQHORQ.js → chunk-NMQSO6Z6.js} +4 -3
- package/dist/{chunk-R2UUNSNQ.js → chunk-O3ZAF7UY.js} +18 -5
- package/dist/{chunk-4SYHGVDE.js → chunk-OQNCLZI4.js} +4 -4
- package/dist/{chunk-EG4ARXPC.js → chunk-OX6QTA4N.js} +3 -3
- package/dist/{chunk-P4GNZPXO.js → chunk-OYUS3UZP.js} +2 -2
- package/dist/{chunk-OQ2EOAHA.js → chunk-PF36KOGI.js} +291 -112
- package/dist/{chunk-55RMDDFM.js → chunk-PGQG7ZMW.js} +2 -2
- package/dist/{chunk-CBAQPTDU.js → chunk-PIU6BXXW.js} +2 -2
- package/dist/{chunk-EKV4UBCM.js → chunk-PZBQUJ2F.js} +2 -2
- package/dist/{chunk-7RET77VB.js → chunk-Q2HKY32Y.js} +2 -2
- package/dist/{chunk-U6YTUVOX.js → chunk-Q4RMNWMZ.js} +4 -4
- package/dist/chunk-Q6FRR3AQ.js +48 -0
- package/dist/{chunk-6RJMYYIE.js → chunk-QJSIAJBY.js} +3 -3
- package/dist/{chunk-DOUISWEE.js → chunk-QOJ4ERMU.js} +5 -5
- package/dist/{chunk-IFPDAD7R.js → chunk-RWSRKXLT.js} +3 -3
- package/dist/{chunk-T33IYTZM.js → chunk-SAASVMIY.js} +3 -3
- package/dist/{chunk-2DTIWSAW.js → chunk-SEQDH6JC.js} +3 -3
- package/dist/{chunk-BRCOSE7O.js → chunk-SZUO5BSE.js} +2 -2
- package/dist/{chunk-HOEDA42N.js → chunk-T7HZ7KJF.js} +2 -2
- package/dist/{chunk-OJXOA6YU.js → chunk-TL6LNERH.js} +2 -2
- package/dist/{chunk-UCINIRHW.js → chunk-TPCZXWLT.js} +3 -3
- package/dist/{chunk-ZUKUCZYZ.js → chunk-TQ2KTH4A.js} +2 -2
- package/dist/{chunk-4WOOTKFD.js → chunk-U3FETNQB.js} +341 -20
- package/dist/{chunk-5B3RTYAK.js → chunk-UGDSB4AN.js} +2 -2
- package/dist/{chunk-VKHLUZNO.js → chunk-UZ7YBL43.js} +3 -3
- package/dist/{chunk-QNUQ7K7D.js → chunk-VKMSEO7Y.js} +4 -4
- package/dist/{chunk-IN7DGBVS.js → chunk-VLX5VZ35.js} +5 -5
- package/dist/{chunk-SJGS3GDI.js → chunk-WAGBMMNX.js} +5 -5
- package/dist/{chunk-BFOSV5EZ.js → chunk-WX2YCH7F.js} +2 -2
- package/dist/{chunk-3LT34CAM.js → chunk-XBIGUILU.js} +32 -17
- package/dist/{chunk-XDHUDE5K.js → chunk-XKYBFRWR.js} +5 -5
- package/dist/{chunk-U5QU5ZOD.js → chunk-XYPWFSU5.js} +3 -3
- package/dist/{chunk-YBUECLAF.js → chunk-YEJQDODA.js} +2 -2
- package/dist/{chunk-YQ6XEFVK.js → chunk-Z74OGONW.js} +39 -7
- package/dist/{chunk-I4ELN5BX.js → chunk-ZTNOEJRI.js} +2 -2
- package/dist/cli/index.js +26 -26
- package/dist/{clio-K2PBVCEM.js → clio-4IKFE25F.js} +2 -2
- package/dist/{clio-context-tools-GR4OHFSO.js → clio-context-tools-OHJXYX4R.js} +25 -23
- package/dist/{code-nav-4X3OI6DK.js → code-nav-XPM3MY5I.js} +7 -7
- package/dist/{config-Q5J7UBAB.js → config-343YS25T.js} +43 -41
- package/dist/{config-graph-P3555574.js → config-graph-WI263KDX.js} +43 -41
- package/dist/{configure-KIJ32ZO6.js → configure-GZDG4HER.js} +21 -21
- package/dist/{context-LFBQOSXT.js → context-4OFO7N7Y.js} +12 -12
- package/dist/{context-WBH3KSVR.js → context-JLK2RLOG.js} +42 -40
- package/dist/{context-NNLBES2Y.js → context-O6DIWCF6.js} +23 -21
- package/dist/{context-clear-YOHMQ6SQ.js → context-clear-MR7KOGQI.js} +42 -40
- package/dist/{context-working-set-MRAEVDNV.js → context-working-set-YZAD3KWO.js} +14 -13
- package/dist/{data-tool-JJPBGMQJ.js → data-tool-EKD7DFHG.js} +7 -7
- package/dist/{detail-RHJOTGN4.js → detail-YZPHFEVJ.js} +43 -41
- package/dist/{dispatch-runner-NC7PYNIO.js → dispatch-runner-VKVXVTWJ.js} +44 -42
- package/dist/{doctor-S5KZ2GTN.js → doctor-53AVQXAO.js} +17 -16
- package/dist/{doctor-deep-2EJQSHIZ.js → doctor-deep-BLRB2NCG.js} +5 -5
- package/dist/{eval-3D6M36N7.js → eval-P3BQSLQE.js} +21 -20
- package/dist/{evidence-XNVPPLVK.js → evidence-6LLI6SZH.js} +44 -42
- package/dist/{evidence-6IYN52ET.js → evidence-HTN3JLUO.js} +42 -40
- package/dist/{evidence-JVECPI3F.js → evidence-NJRE3R5H.js} +42 -40
- package/dist/{evolve-TLPUDJDM.js → evolve-WWZHJVJP.js} +42 -40
- package/dist/{fleet-WKVBF5SZ.js → fleet-GGZ4BVJE.js} +64 -62
- package/dist/{fleet-6RJWIOEO.js → fleet-QG3IIWWM.js} +44 -42
- package/dist/{fleet-commands-L6WFT7KZ.js → fleet-commands-J2ZIKISQ.js} +7 -7
- package/dist/{fleet-decisions-BTQMWU5A.js → fleet-decisions-Y2Q7375Q.js} +8 -8
- package/dist/{fleet-graph-553DZ67Q.js → fleet-graph-TCBCCCBP.js} +11 -11
- package/dist/{fleet-inspect-236FZPRB.js → fleet-inspect-KOLKBC56.js} +44 -42
- package/dist/{fleet-preflight-NZQHABWO.js → fleet-preflight-44UNCC4Y.js} +27 -25
- package/dist/{fleet-validate-7NLAE26R.js → fleet-validate-K2FXRZVA.js} +13 -13
- package/dist/{fleet-verify-E3RO6A5W.js → fleet-verify-AS444WDW.js} +42 -40
- package/dist/{fleet-view-KMZQY7YJ.js → fleet-view-X4JU4J6O.js} +43 -41
- package/dist/gui/ops-worker.js +9 -9
- package/dist/gui/reads-worker.js +9 -8
- package/dist/{init-DV5B6HQG.js → init-VSOWFHVL.js} +54 -52
- package/dist/{interactive-OXGYZYAS.js → interactive-JRCBENLZ.js} +1733 -577
- package/dist/{interop-J7UOBOSF.js → interop-JR6BCY7R.js} +10 -10
- package/dist/{inventory-OEGIL6PY.js → inventory-3JUGLVJI.js} +43 -41
- package/dist/{library-ROZAMUIL.js → library-7BOH3Y7N.js} +15 -15
- package/dist/{library-B3UPGY4V.js → library-7PFONXUI.js} +9 -9
- package/dist/{library-MXQA5DLT.js → library-TUYZMFIA.js} +7 -7
- package/dist/{library-import-2LTWOVBD.js → library-import-TJVXKLDK.js} +10 -10
- package/dist/{library-inventory-LJ3XHLWO.js → library-inventory-MD3AYTP4.js} +9 -9
- package/dist/{library-validation-DARHHUNW.js → library-validation-ZDLITEA3.js} +7 -7
- package/dist/{mcp-2GSPYQ54.js → mcp-EFCWUPUR.js} +3 -3
- package/dist/{memory-J3STUUIK.js → memory-Q2YVOKK5.js} +42 -40
- package/dist/{models-Y3GMFJ2V.js → models-M7BOCW7F.js} +17 -16
- package/dist/{monitor-LHNH577J.js → monitor-2HD3MC67.js} +47 -45
- package/dist/{orchestrator-DTAPXULA.js → orchestrator-OUW3ZN6Y.js} +1894 -226
- package/dist/{panes-A743ONZ5.js → panes-TTPPSYCL.js} +3 -3
- package/dist/{preload-OJBMAIXF.js → preload-A47C2NUF.js} +42 -40
- package/dist/{providers-CMETPZNS.js → providers-T43W7EL4.js} +4 -4
- package/dist/{resources-FY6XYFN2.js → resources-TP6X2V6W.js} +20 -11
- package/dist/{run-H64MEVTA.js → run-LDSBPMCS.js} +61 -59
- package/dist/{share-CAFMMQLM.js → share-URHKKMHR.js} +9 -9
- package/dist/{skills-ERKQUC7L.js → skills-E477MOOO.js} +13 -11
- package/dist/{skills-eval-CQ23MYQK.js → skills-eval-AIBR2L7H.js} +583 -93
- package/dist/{skills-inventory-TVUYJDVN.js → skills-inventory-HLMKY3R5.js} +13 -11
- package/dist/{slash-commands-ROG3KIFJ.js → slash-commands-LIIZKA5Y.js} +28 -26
- package/dist/{startup-background-KDWCQMWT.js → startup-background-JR2KDK6R.js} +43 -41
- package/dist/{steer-KKYXJSH4.js → steer-D7RTAPCM.js} +3 -3
- package/dist/{system-ZDW44ZGX.js → system-X2P5N4IY.js} +14 -13
- package/dist/{targets-AJWA6E7Z.js → targets-VFFAKUFM.js} +25 -24
- package/dist/{tasks-6L2JJRC5.js → tasks-ERAWZA22.js} +6 -6
- package/dist/{terminal-lease-YFUSDPAD.js → terminal-lease-V2N2BIKQ.js} +2 -2
- package/dist/{trace-LA5MVR4Y.js → trace-MA5BT7XY.js} +3 -3
- package/dist/{usage-XSILDHKF.js → usage-WYFY2L6M.js} +50 -48
- package/dist/{verifiers-ECA5GIHL.js → verifiers-TYIW2XKF.js} +7 -7
- package/dist/{verify-R6QQCCEB.js → verify-242UFYTP.js} +6 -6
- package/dist/{web-fetch-PVZNMBOB.js → web-fetch-LRAGYU6Y.js} +3 -3
- package/dist/{wiki-generate-RP22WP5K.js → wiki-generate-OFKOX2T6.js} +53 -51
- package/dist/worker/entry.js +31 -29
- package/docs/architecture/architecture.md +1 -0
- package/docs/architecture/context-engine.md +4 -2
- package/docs/architecture/observability.md +6 -4
- package/docs/architecture/prompt-envelope-and-tools.md +1 -1
- package/docs/architecture/session-lifecycle.md +1 -1
- package/docs/architecture/tui-design.md +12 -9
- package/docs/gui/parity/02-slash-and-surfaces.md +1 -1
- package/docs/guide/commands-and-modes.md +91 -5
- package/docs/guide/configuration-and-targets.md +2 -2
- package/docs/guide/context-continuity.md +49 -0
- package/docs/guide/proactive-memory.md +6 -3
- package/library/registry.yaml +292 -32
- package/library/skills/README.md +27 -2
- package/library/skills/context/context-handoff/SKILL.md +10 -4
- package/library/skills/meta/clio-coder-dev/SKILL.md +92 -88
- package/library/skills/meta/clio-coder-dev/evals.md +51 -46
- package/library/skills/meta/clio-coder-dev/plugin.json +2 -2
- package/library/skills/meta/clio-coder-dev/references/change-map.md +49 -0
- package/library/skills/meta/clio-coder-test/SKILL.md +89 -142
- package/library/skills/meta/clio-coder-test/evals.md +58 -62
- package/library/skills/meta/clio-coder-test/plugin.json +2 -2
- package/library/skills/meta/clio-coder-test/references/harness.md +3 -3
- package/library/skills/meta/clio-coder-test/references/lifecycle-validation.md +30 -0
- package/library/skills/meta/clio-coder-test/references/test-map.md +60 -89
- package/library/skills/registry.yaml +5 -5
- package/library/skills/skill-marketplace.json +12 -14
- package/package.json +1 -1
- package/src/cli/skills-eval.ts +1064 -54
- package/src/cli/usage.ts +2 -2
- package/src/core/clio-repo.ts +3 -0
- package/src/core/tool-names.ts +1 -0
- package/src/domains/context/budget/inspection.ts +9 -0
- package/src/domains/context/budget/live-view.ts +418 -0
- package/src/domains/context/budget/pressure.ts +393 -0
- package/src/domains/context/budget/request-fit.ts +15 -0
- package/src/domains/evidence/build.ts +24 -0
- package/src/domains/gateway/mcp/client.ts +1 -1
- package/src/domains/memory/commit-state.ts +160 -0
- package/src/domains/memory/operations.ts +18 -10
- package/src/domains/memory/prompt-cache.ts +95 -0
- package/src/domains/memory/prompt-section.ts +50 -7
- package/src/domains/memory/relevance.ts +93 -0
- package/src/domains/memory/restoration.ts +78 -0
- package/src/domains/memory/store.ts +39 -1
- package/src/domains/middleware/memory-intervention.ts +116 -24
- package/src/domains/observability/background-memory-usage.ts +1 -1
- package/src/domains/observability/cost.ts +3 -3
- package/src/domains/observability/extension.ts +1 -1
- package/src/domains/observability/metrics.ts +1 -1
- package/src/domains/prompts/compiler.ts +1 -1
- package/src/domains/prompts/extension.ts +38 -1
- package/src/domains/quota/anthropic-max-provider.ts +134 -0
- package/src/domains/quota/anthropic-usage.ts +220 -0
- package/src/domains/quota/antigravity-provider.ts +339 -0
- package/src/domains/quota/cache.ts +87 -0
- package/src/domains/quota/claude-code-provider.ts +169 -0
- package/src/domains/quota/codex-provider.ts +237 -0
- package/src/domains/quota/presentation.ts +240 -0
- package/src/domains/quota/registry.ts +23 -0
- package/src/domains/quota/service.ts +93 -0
- package/src/domains/quota/summary-feed.ts +86 -0
- package/src/domains/quota/types.ts +84 -0
- package/src/domains/resources/index.ts +11 -0
- package/src/domains/resources/skills/catalog-view.ts +571 -0
- package/src/domains/resources/skills/lexical-match.ts +136 -0
- package/src/domains/resources/skills/loader.ts +38 -0
- package/src/domains/resources/skills/promotion.ts +1 -55
- package/src/domains/resources/skills/provenance-pin.ts +50 -20
- package/src/domains/safety/action-classifier.ts +1 -0
- package/src/domains/session/compaction/branch-summary.ts +4 -1
- package/src/domains/session/compaction/compact.ts +18 -2
- package/src/domains/session/compaction/cut-point.ts +20 -1
- package/src/domains/session/compaction/tokens.ts +48 -3
- package/src/domains/session/context-accounting.ts +8 -1
- package/src/domains/session/continuity/carry.ts +59 -0
- package/src/domains/session/continuity/contract.ts +592 -0
- package/src/domains/session/continuity/evidence.ts +290 -0
- package/src/domains/session/continuity/fold.ts +1075 -0
- package/src/domains/session/continuity/note.ts +79 -0
- package/src/domains/session/continuity/operator-request.ts +104 -0
- package/src/domains/session/continuity/persistence.ts +408 -0
- package/src/domains/session/continuity/ports.ts +231 -0
- package/src/domains/session/continuity/projection.ts +538 -0
- package/src/domains/session/continuity/validate.ts +352 -0
- package/src/domains/session/entries.ts +50 -3
- package/src/domains/session/index.ts +48 -0
- package/src/domains/session/migrations/index.ts +7 -3
- package/src/domains/session/tree/fork.ts +15 -1
- package/src/domains/session/usage.ts +2 -2
- package/src/engine/acp/commands.ts +1 -1
- package/src/engine/agent.ts +133 -9
- package/src/engine/session.ts +13 -6
- package/src/entry/orchestrator.ts +116 -33
- package/src/interactive/chat-loop-messages.ts +2 -2
- package/src/interactive/chat-loop.ts +219 -63
- package/src/interactive/chat-renderer.ts +99 -8
- package/src/interactive/context-overlay.ts +24 -3
- package/src/interactive/continuity-controller.ts +526 -0
- package/src/interactive/dispatch-board.ts +21 -3
- package/src/interactive/footer/dashboard.ts +25 -1
- package/src/interactive/footer/key-hints.ts +2 -2
- package/src/interactive/footer/pages.ts +70 -27
- package/src/interactive/footer/widgets.ts +32 -7
- package/src/interactive/footer-panel.ts +1 -1
- package/src/interactive/interactive-application.ts +7 -2
- package/src/interactive/interactive-input-runtime.ts +2 -2
- package/src/interactive/interactive-presentation.ts +15 -0
- package/src/interactive/interactive-slash-runtime.ts +45 -8
- package/src/interactive/interactive-tickers.ts +7 -1
- package/src/interactive/model-session-replay.ts +130 -3
- package/src/interactive/output-reserve.ts +35 -0
- package/src/interactive/overlay-general-openers.ts +12 -8
- package/src/interactive/overlay-key-routing.ts +6 -9
- package/src/interactive/overlay-lifecycle.ts +10 -6
- package/src/interactive/overlay-session-lifecycle.ts +21 -14
- package/src/interactive/quota-view.ts +229 -0
- package/src/interactive/session-last-turn.ts +1 -1
- package/src/interactive/session-transcript.ts +2 -1
- package/src/interactive/session-usage-reseed.ts +2 -2
- package/src/interactive/side-question.ts +2 -2
- package/src/interactive/slash-commands.ts +28 -9
- package/src/interactive/turn-context.ts +686 -63
- package/src/interactive/turn-middleware.ts +39 -6
- package/src/interactive/turn-persistence.ts +15 -2
- package/src/interactive/turn-prewarm.ts +1 -1
- package/src/interactive/turn-runtime.ts +48 -5
- package/src/interactive/{cost-overlay.ts → usage-overlay.ts} +121 -33
- package/src/interactive/welcome-dashboard.ts +26 -3
- package/src/tools/agent-tools.ts +3 -1
- package/src/tools/bootstrap.ts +6 -0
- package/src/tools/builtin-tool-catalog.ts +9 -0
- package/src/tools/context/index.ts +148 -113
- package/src/tools/context/surface.ts +11 -7
- package/src/tools/core-bootstrap.ts +4 -0
- package/src/tools/observation.ts +8 -2
- package/src/tools/policy.ts +3 -0
- package/src/tools/self-compact.ts +31 -0
- package/src/tools/surface.ts +1 -0
- package/src/tools/tasks.ts +1 -1
- package/dist/chunk-CTFPFW3H.js +0 -44
- package/dist/chunk-EOJPDNUP.js +0 -1325
- package/dist/chunk-I4Y4WKZR.js +0 -386
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only data model for provider subscription quota.
|
|
3
|
+
*
|
|
4
|
+
* The shapes mirror QuotaBubble's `providers/base.py` (`ProviderStatus`,
|
|
5
|
+
* `UsageWindow`, `UsageSnapshot`) so that adapters written against either
|
|
6
|
+
* project describe the same usage facts. Timestamps are ISO 8601 strings
|
|
7
|
+
* rather than `Date` objects so a snapshot stays structurally cloneable
|
|
8
|
+
* across the worker and transport boundaries.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Outcome of one usage read, including the reasons a read produced no data. */
|
|
12
|
+
export type QuotaStatus = "ok" | "no_credentials" | "expired" | "error" | "loading";
|
|
13
|
+
|
|
14
|
+
/** One rate-limit window reported by a provider. */
|
|
15
|
+
export interface UsageWindow {
|
|
16
|
+
/** Stable identity for ordering and lookup, for example "session" or "weekly". */
|
|
17
|
+
key: string;
|
|
18
|
+
/** Human label such as "5h" or "Weekly". */
|
|
19
|
+
label: string;
|
|
20
|
+
/** Canonical percentage consumed, 0 to 100. Adapters convert remaining fractions here. */
|
|
21
|
+
usedPct: number;
|
|
22
|
+
/** ISO 8601 instant the window resets, or null when the provider omitted it. */
|
|
23
|
+
resetsAt: string | null;
|
|
24
|
+
/** Compact label for a narrow surface such as a footer indicator. */
|
|
25
|
+
short?: string;
|
|
26
|
+
/** Model or product scope when the window covers less than the whole plan. */
|
|
27
|
+
scope?: string;
|
|
28
|
+
/** Provider-supplied severity word; Clio does not invent one. */
|
|
29
|
+
severity?: string;
|
|
30
|
+
/** True when the provider marks this window as the currently binding limit. */
|
|
31
|
+
active?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Prepaid credit balance for providers that bill credits instead of windows. */
|
|
35
|
+
export interface UsageCredits {
|
|
36
|
+
display: string;
|
|
37
|
+
usedPct: number | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Everything one provider reported about quota at a single point in time. */
|
|
41
|
+
export interface UsageSnapshot {
|
|
42
|
+
providerId: string;
|
|
43
|
+
displayName: string;
|
|
44
|
+
status: QuotaStatus;
|
|
45
|
+
windows: UsageWindow[];
|
|
46
|
+
credits?: UsageCredits | null;
|
|
47
|
+
plan?: string | null;
|
|
48
|
+
message?: string | null;
|
|
49
|
+
/** Seconds to wait, captured from a 429 `Retry-After` header. */
|
|
50
|
+
retryAfterSeconds?: number | null;
|
|
51
|
+
/** True when the snapshot is a cached last-good value rather than a fresh read. */
|
|
52
|
+
stale?: boolean;
|
|
53
|
+
/** ISO 8601 instant of the read that produced this snapshot. */
|
|
54
|
+
fetchedAt: string | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* One runtime's quota adapter. Implementations stay pure data plumbing:
|
|
59
|
+
* no UI, no dispatch admission, no credential writes.
|
|
60
|
+
*/
|
|
61
|
+
export interface QuotaProvider {
|
|
62
|
+
id: string;
|
|
63
|
+
displayName: string;
|
|
64
|
+
/** True when usable credentials are present, without spending a network call. */
|
|
65
|
+
detect(): Promise<boolean>;
|
|
66
|
+
/** Read current usage, reporting failure as a snapshot status rather than throwing. */
|
|
67
|
+
fetch(): Promise<UsageSnapshot>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Parse a numeric `Retry-After` value into non-negative seconds. */
|
|
71
|
+
export function parseRetryAfterSeconds(value: string | null | undefined): number | null {
|
|
72
|
+
if (!value) return null;
|
|
73
|
+
const seconds = Number.parseFloat(value.trim());
|
|
74
|
+
if (!Number.isFinite(seconds)) return null;
|
|
75
|
+
return Math.max(0, seconds);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Turn a raw plan identifier such as "max_5x" into a display plan such as "Max 5X". */
|
|
79
|
+
export function formatPlan(value: string | null | undefined): string | null {
|
|
80
|
+
if (!value) return null;
|
|
81
|
+
const words = value.replace(/_/g, " ").trim();
|
|
82
|
+
if (!words) return null;
|
|
83
|
+
return words.replace(/\S+/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
|
|
84
|
+
}
|
|
@@ -115,6 +115,14 @@ export {
|
|
|
115
115
|
} from "./prompts/loader.js";
|
|
116
116
|
export { parseCommandArgs, substituteArgs } from "./prompts/substitute.js";
|
|
117
117
|
export { installedSkillNames, installedSkillPackages } from "./skills/availability.js";
|
|
118
|
+
export {
|
|
119
|
+
buildSkillCatalogView,
|
|
120
|
+
type SkillCatalogPackage,
|
|
121
|
+
type SkillCatalogRow,
|
|
122
|
+
type SkillCatalogRowKind,
|
|
123
|
+
type SkillCatalogView,
|
|
124
|
+
type SkillCatalogViewInput,
|
|
125
|
+
} from "./skills/catalog-view.js";
|
|
118
126
|
export { parseSkillEvals, type SkillEvalParseResult, type SkillEvalScenario } from "./skills/evals.js";
|
|
119
127
|
export {
|
|
120
128
|
type InstallSkillInput,
|
|
@@ -127,6 +135,7 @@ export {
|
|
|
127
135
|
type UpdateSkillsInput,
|
|
128
136
|
updateSkills,
|
|
129
137
|
} from "./skills/install.js";
|
|
138
|
+
export { type LexicalMatchMode, lexicalMatches } from "./skills/lexical-match.js";
|
|
130
139
|
export {
|
|
131
140
|
defaultSkillRoots,
|
|
132
141
|
expandSkillInvocationInput,
|
|
@@ -163,6 +172,8 @@ export {
|
|
|
163
172
|
} from "./skills/marketplace.js";
|
|
164
173
|
export {
|
|
165
174
|
checkSkillDrift,
|
|
175
|
+
checkSkillDriftBatch,
|
|
176
|
+
type SkillDriftSubject,
|
|
166
177
|
type SkillDriftVerdict,
|
|
167
178
|
type SkillPinEntry,
|
|
168
179
|
} from "./skills/provenance-pin.js";
|
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SKILL_INSTALL_OFFER_OPTION_NEVER,
|
|
3
|
+
SKILL_INSTALL_OFFER_OPTION_NOT_NOW,
|
|
4
|
+
SKILL_INSTALL_OFFER_OPTION_PROJECT,
|
|
5
|
+
SKILL_INSTALL_OFFER_OPTION_USER,
|
|
6
|
+
SKILL_SUGGESTION_ANCHOR,
|
|
7
|
+
} from "../../../core/skill-activation.js";
|
|
8
|
+
import { type LexicalMatchMode, lexicalMatches, normalize } from "./lexical-match.js";
|
|
9
|
+
import type { Skill } from "./loader.js";
|
|
10
|
+
import type { MarketplaceSkill } from "./marketplace.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The model-facing skills listing: which rows exist, which of them the caller
|
|
14
|
+
* asked for, and how many of those fit the byte budget.
|
|
15
|
+
*
|
|
16
|
+
* It lives here rather than in the context tool for two reasons. The row model
|
|
17
|
+
* and the query filter are resource concerns, and the tool should hold only the
|
|
18
|
+
* call site. And the budget arithmetic wants to be testable without standing up
|
|
19
|
+
* an observation reservation.
|
|
20
|
+
*
|
|
21
|
+
* Two properties this module exists to guarantee.
|
|
22
|
+
*
|
|
23
|
+
* A listing that is neither filtered nor cut renders byte-identical to what the
|
|
24
|
+
* tool emitted before any of this existed. Narrowing is something the caller
|
|
25
|
+
* opts into; the default view still shows every skill, because a ranked or
|
|
26
|
+
* clipped default would decide for the model which skills it is allowed to
|
|
27
|
+
* know about.
|
|
28
|
+
*
|
|
29
|
+
* The footer always survives. The listing used to be rendered whole and then
|
|
30
|
+
* head-truncated against the per-call cap, which keeps the head and drops the
|
|
31
|
+
* tail. The tail is the reply protocol, the one line a model actually acts on.
|
|
32
|
+
* Here the footer is measured and reserved first and rows are fitted into what
|
|
33
|
+
* is left, so an over-budget catalog loses catalog rows and says so, rather
|
|
34
|
+
* than losing the instruction that tells the model what to do with them.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/** Ordered lowest-value-last, which is also the order overflow drops them in. */
|
|
38
|
+
export type SkillCatalogRowKind = "ready" | "session" | "package" | "marketplace";
|
|
39
|
+
|
|
40
|
+
export interface SkillCatalogRow {
|
|
41
|
+
kind: SkillCatalogRowKind;
|
|
42
|
+
name: string;
|
|
43
|
+
/** The rendered bullet, exactly as it appears in the listing. */
|
|
44
|
+
line: string;
|
|
45
|
+
/** Typed drift state. Never re-derived by reading the rendered line back. */
|
|
46
|
+
drifted: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Stable identity for this row, unique across the whole listing.
|
|
49
|
+
*
|
|
50
|
+
* A name is not an identity here. One installed standalone skill produces a
|
|
51
|
+
* ready row and a package row under the same name, and the same package id
|
|
52
|
+
* can be installed at both user and project scope. Callers that reconcile a
|
|
53
|
+
* page against their own inventories have to match on this, not on the name.
|
|
54
|
+
*/
|
|
55
|
+
key: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Disk-install state for one package, as {@link installedSkillPackages} reports it. */
|
|
59
|
+
export interface SkillCatalogPackage {
|
|
60
|
+
name: string;
|
|
61
|
+
names: string[];
|
|
62
|
+
scope: string;
|
|
63
|
+
state: string;
|
|
64
|
+
origin: string;
|
|
65
|
+
path: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SkillCatalogViewInput {
|
|
69
|
+
/** Model-visible skills, already filtered by trust and disable-model-invocation. */
|
|
70
|
+
skills: ReadonlyArray<Skill>;
|
|
71
|
+
packages: ReadonlyArray<SkillCatalogPackage>;
|
|
72
|
+
marketplace: ReadonlyArray<MarketplaceSkill>;
|
|
73
|
+
/** Names of ready skills whose content no longer matches its recorded hash. */
|
|
74
|
+
drifted?: ReadonlySet<string>;
|
|
75
|
+
/** False on a registry that never offers the marketplace, such as a worker. */
|
|
76
|
+
marketplaceOffered: boolean;
|
|
77
|
+
modelActivation: boolean;
|
|
78
|
+
/** Operator/model query; empty means no filtering. */
|
|
79
|
+
query?: string;
|
|
80
|
+
/** Maximum rows on this page; undefined means every row the budget allows. */
|
|
81
|
+
limit?: number | undefined;
|
|
82
|
+
offset?: number;
|
|
83
|
+
/** Per-call observation cap in bytes. */
|
|
84
|
+
capBytes: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface SkillCatalogView {
|
|
88
|
+
text: string;
|
|
89
|
+
/** The rows actually rendered. */
|
|
90
|
+
rows: SkillCatalogRow[];
|
|
91
|
+
/** Rows matching the query across every kind, before paging. */
|
|
92
|
+
total: number;
|
|
93
|
+
shown: number;
|
|
94
|
+
/** Offset to continue from, when rows remain. */
|
|
95
|
+
nextOffset: number | undefined;
|
|
96
|
+
/** True when a query was applied, whatever it matched. */
|
|
97
|
+
filtered: boolean;
|
|
98
|
+
/** Which token rule produced the matches; null when no query was applied. */
|
|
99
|
+
matchMode: LexicalMatchMode | null;
|
|
100
|
+
/** True when the byte budget, rather than the query or the limit, cut the page. */
|
|
101
|
+
budgetLimited: boolean;
|
|
102
|
+
/** Ready-row names reported as drifted in this result set. */
|
|
103
|
+
driftedNames: string[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const LISTING_HEADER = "Available skills.";
|
|
107
|
+
const SESSION_NOTE =
|
|
108
|
+
"These skills were explicitly supplied for this session. Preserve their source; session availability does not mean Clio installed or copied them.";
|
|
109
|
+
const INTEROP_NOTE =
|
|
110
|
+
"Other-agent skill folders are discovery-only. Explicitly import into Clio before use; the trust-imports setting does not install or activate loose files.";
|
|
111
|
+
const MARKETPLACE_HEADER = "Marketplace (additional skills available to install; /skill <name> offers to install):";
|
|
112
|
+
const SESSION_HEADER = "Explicitly supplied session skills (not installed packages):";
|
|
113
|
+
const EMPTY_WITH_MARKETPLACE = "No skills are available in Clio and no additional marketplace skills were found.";
|
|
114
|
+
const EMPTY_WITHOUT_MARKETPLACE = "No skills are available in Clio.";
|
|
115
|
+
|
|
116
|
+
/** Marks a ready row whose content no longer matches the hash recorded for it. */
|
|
117
|
+
const DRIFT_MARKER = " [drifted]";
|
|
118
|
+
|
|
119
|
+
/** Ceiling on an explicit `limit`; the byte budget is the real bound. */
|
|
120
|
+
const MAX_LIMIT = 200;
|
|
121
|
+
|
|
122
|
+
/** Code points of a caller-supplied query echoed back in the note. */
|
|
123
|
+
const MAX_QUERY_ECHO = 48;
|
|
124
|
+
/** Code points of a name echoed back in the note. */
|
|
125
|
+
const MAX_NAME_ECHO = 64;
|
|
126
|
+
/** Drifted skills named individually before the notice reports a remainder. */
|
|
127
|
+
const MAX_DRIFT_NAMES = 6;
|
|
128
|
+
|
|
129
|
+
function byteLength(text: string): number {
|
|
130
|
+
return Buffer.byteLength(text, "utf8");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Clip by code point, not by UTF-16 unit, so a surrogate pair is never split
|
|
135
|
+
* into a lone half and a multi-byte script is bounded by what it reads as
|
|
136
|
+
* rather than by how it happens to encode.
|
|
137
|
+
*/
|
|
138
|
+
function boundText(text: string, maxCodePoints: number): string {
|
|
139
|
+
const points = [...text];
|
|
140
|
+
if (points.length <= maxCodePoints) return text;
|
|
141
|
+
return `${points.slice(0, maxCodePoints - 1).join("")}…`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Echoing the caller's query must not let one argument size the response. */
|
|
145
|
+
function boundQuery(query: string): string {
|
|
146
|
+
return boundText(query.replace(/\s+/g, " ").trim(), MAX_QUERY_ECHO);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function boundName(name: string): string {
|
|
150
|
+
return boundText(name, MAX_NAME_ECHO);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function activationProtocolLine(modelActivation: boolean): string {
|
|
154
|
+
return modelActivation
|
|
155
|
+
? `If one skill above matches the current task, load it now with context(scope="skills", name="<name>") and continue in the same turn; at this autonomy level you activate installed skills yourself and do not wait for the operator. Marketplace additions still require operator approval. If none match, do not mention skills.`
|
|
156
|
+
: `If one skill above matches the current task, begin your reply with the line \`${SKILL_SUGGESTION_ANCHOR}\` (a comma-separated sequence, in order, when several compose), then continue the task in the same turn without it; only the operator can run it. If none match, do not mention skills.`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function marketplaceOfferClause(): string {
|
|
160
|
+
return `When no installed skill serves the task but a marketplace skill above genuinely does, you may instead ask the operator with ask_user (mode=single_question, header "Install skill") whether to install it, offering exactly: "${SKILL_INSTALL_OFFER_OPTION_PROJECT}", "${SKILL_INSTALL_OFFER_OPTION_USER}", "${SKILL_INSTALL_OFFER_OPTION_NOT_NOW}", "${SKILL_INSTALL_OFFER_OPTION_NEVER}". The harness handles those exact offer options. An explicit operator request or approval also authorizes the documented library install CLI. After installation, refresh the inventory; distinguish installed from ready and report /library reload when required.`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* One sentence, only when something actually drifted.
|
|
165
|
+
*
|
|
166
|
+
* Visibility and nothing else. The skill stays listed, stays ready and stays
|
|
167
|
+
* loadable; the operator owns the installed copy and this does not reinstall,
|
|
168
|
+
* repair, suppress or reorder anything. It exists because the drift warning
|
|
169
|
+
* used to arrive only after the model had already spent a turn loading the
|
|
170
|
+
* skill, which is too late to choose a different one.
|
|
171
|
+
*/
|
|
172
|
+
function driftNotice(names: ReadonlyArray<string>): string {
|
|
173
|
+
if (names.length === 0) return "";
|
|
174
|
+
// Bounded: a catalog where everything drifted would otherwise put every name
|
|
175
|
+
// in one sentence and size the response by how much is wrong.
|
|
176
|
+
const shown = names.slice(0, MAX_DRIFT_NAMES).map(boundName);
|
|
177
|
+
const omitted = names.length - shown.length;
|
|
178
|
+
const list = omitted > 0 ? `${shown.join(", ")} and ${omitted} more` : shown.join(", ");
|
|
179
|
+
const subject = names.length === 1 ? `${list} no longer matches` : `${list} no longer match`;
|
|
180
|
+
return `Marked [drifted]: ${subject} the content hash recorded for it; the skill still loads unchanged, and /library shows the installed copy.`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function readySkills(skills: ReadonlyArray<Skill>): Skill[] {
|
|
184
|
+
return skills.filter((skill) => skill.source === "clio-coder" || skill.source === "plugin");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function sessionSkills(skills: ReadonlyArray<Skill>): Skill[] {
|
|
188
|
+
return skills.filter((skill) => skill.source !== "clio-coder" && skill.source !== "plugin");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** The text a query is matched against, per row kind. */
|
|
192
|
+
function haystackForSkill(skill: Skill): string {
|
|
193
|
+
const triggers = skill.metadata.triggers;
|
|
194
|
+
const triggerText = Array.isArray(triggers)
|
|
195
|
+
? triggers.filter((entry): entry is string => typeof entry === "string").join(" ")
|
|
196
|
+
: "";
|
|
197
|
+
return `${skill.name.replace(/-/g, " ")} ${skill.description} ${triggerText}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function haystackForMarketplace(entry: MarketplaceSkill): string {
|
|
201
|
+
return `${entry.name.replace(/-/g, " ")} ${entry.description} ${entry.category ?? ""} ${(entry.triggers ?? []).join(" ")}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function haystackForPackage(record: SkillCatalogPackage): string {
|
|
205
|
+
return `${record.name.replace(/-/g, " ")} ${record.names.join(" ").replace(/-/g, " ")}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
interface CandidateRow extends SkillCatalogRow {
|
|
209
|
+
haystack: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function buildRows(input: SkillCatalogViewInput): CandidateRow[] {
|
|
213
|
+
const drifted = input.drifted ?? new Set<string>();
|
|
214
|
+
const rows: CandidateRow[] = [];
|
|
215
|
+
for (const skill of readySkills(input.skills)) {
|
|
216
|
+
const isDrifted = drifted.has(skill.name);
|
|
217
|
+
rows.push({
|
|
218
|
+
kind: "ready",
|
|
219
|
+
name: skill.name,
|
|
220
|
+
key: `ready:${skill.filePath}`,
|
|
221
|
+
drifted: isDrifted,
|
|
222
|
+
line: `- ${skill.name}${isDrifted ? DRIFT_MARKER : ""} (source: ${skill.source}; scope: ${skill.scope}): ${skill.description}`,
|
|
223
|
+
haystack: haystackForSkill(skill),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
for (const skill of sessionSkills(input.skills)) {
|
|
227
|
+
rows.push({
|
|
228
|
+
kind: "session",
|
|
229
|
+
name: skill.name,
|
|
230
|
+
key: `session:${skill.filePath}`,
|
|
231
|
+
drifted: false,
|
|
232
|
+
line: `- ${skill.name} (source: ${skill.source}; scope: ${skill.scope}; file: ${skill.filePath}): ${skill.description}`,
|
|
233
|
+
haystack: haystackForSkill(skill),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
for (const record of input.packages) {
|
|
237
|
+
rows.push({
|
|
238
|
+
kind: "package",
|
|
239
|
+
name: record.name,
|
|
240
|
+
key: `package:${record.scope}:${record.path}`,
|
|
241
|
+
drifted: false,
|
|
242
|
+
line: `- ${record.name} (scope: ${record.scope}; origin: ${record.origin === "catalog" ? "marketplace catalog" : record.origin}; state: ${record.state}; path: ${record.path})`,
|
|
243
|
+
haystack: haystackForPackage(record),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
for (const entry of input.marketplace) {
|
|
247
|
+
const category = entry.category ? ` [${entry.category}]` : "";
|
|
248
|
+
rows.push({
|
|
249
|
+
kind: "marketplace",
|
|
250
|
+
name: entry.name,
|
|
251
|
+
key: `marketplace:${entry.sourceUrl}`,
|
|
252
|
+
drifted: false,
|
|
253
|
+
line: `- ${entry.name}${category}: ${entry.description}`,
|
|
254
|
+
haystack: haystackForMarketplace(entry),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return rows;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Narrow to the rows the query asked for.
|
|
262
|
+
*
|
|
263
|
+
* `all` first, then `any`. A precise query ("worktree merge") should narrow to
|
|
264
|
+
* the rows carrying both words, but a query written as a sentence would then
|
|
265
|
+
* match nothing, and an empty page is a worse answer than a broad one. The
|
|
266
|
+
* chosen mode is reported so the payload can say which happened.
|
|
267
|
+
*/
|
|
268
|
+
function selectRows(
|
|
269
|
+
rows: ReadonlyArray<CandidateRow>,
|
|
270
|
+
query: string,
|
|
271
|
+
): { rows: CandidateRow[]; mode: LexicalMatchMode } {
|
|
272
|
+
const strict = rows.filter((row) => lexicalMatches(query, row.haystack, "all"));
|
|
273
|
+
if (strict.length > 0) return { rows: strict, mode: "all" };
|
|
274
|
+
return { rows: rows.filter((row) => lexicalMatches(query, row.haystack, "any")), mode: "any" };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function renderSections(rows: ReadonlyArray<SkillCatalogRow>, input: SkillCatalogViewInput, drift: string): string {
|
|
278
|
+
const ready = rows.filter((row) => row.kind === "ready");
|
|
279
|
+
const session = rows.filter((row) => row.kind === "session");
|
|
280
|
+
const packages = rows.filter((row) => row.kind === "package");
|
|
281
|
+
const marketplace = rows.filter((row) => row.kind === "marketplace");
|
|
282
|
+
|
|
283
|
+
const lines = [LISTING_HEADER, ""];
|
|
284
|
+
lines.push(`Ready skills in Clio (${ready.length}):`);
|
|
285
|
+
if (ready.length === 0) lines.push("- none");
|
|
286
|
+
for (const row of ready) lines.push(row.line);
|
|
287
|
+
if (session.length > 0) {
|
|
288
|
+
lines.push("", SESSION_HEADER);
|
|
289
|
+
for (const row of session) lines.push(row.line);
|
|
290
|
+
lines.push(SESSION_NOTE);
|
|
291
|
+
}
|
|
292
|
+
if (packages.length > 0) {
|
|
293
|
+
lines.push("", `Installed packages providing skills (${packages.length}):`);
|
|
294
|
+
for (const row of packages) lines.push(row.line);
|
|
295
|
+
}
|
|
296
|
+
lines.push("", INTEROP_NOTE);
|
|
297
|
+
if (marketplace.length > 0) {
|
|
298
|
+
lines.push("", MARKETPLACE_HEADER);
|
|
299
|
+
for (const row of marketplace) lines.push(row.line);
|
|
300
|
+
}
|
|
301
|
+
if (drift.length > 0) lines.push("", drift);
|
|
302
|
+
lines.push("", activationProtocolLine(input.modelActivation));
|
|
303
|
+
if (marketplace.length > 0) lines.push(marketplaceOfferClause());
|
|
304
|
+
return lines.join("\n");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function clampLimit(limit: number | undefined, fallback: number): number {
|
|
308
|
+
if (limit === undefined || !Number.isFinite(limit)) return fallback;
|
|
309
|
+
return Math.max(1, Math.min(MAX_LIMIT, Math.floor(limit)));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function clampOffset(offset: number | undefined): number {
|
|
313
|
+
if (offset === undefined || !Number.isFinite(offset) || offset <= 0) return 0;
|
|
314
|
+
return Math.floor(offset);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* The listing, narrowed and fitted.
|
|
319
|
+
*
|
|
320
|
+
* A catalog with nothing in it keeps the exact sentence the tool emitted
|
|
321
|
+
* before, including the distinction between "no marketplace was offered" and
|
|
322
|
+
* "the marketplace was offered and had nothing", because a worker registry must
|
|
323
|
+
* not claim a marketplace is unconfigured when it simply has none of its own.
|
|
324
|
+
*/
|
|
325
|
+
export function buildSkillCatalogView(input: SkillCatalogViewInput): SkillCatalogView {
|
|
326
|
+
const rawQuery = (input.query ?? "").trim();
|
|
327
|
+
// A query of punctuation or emoji normalizes to nothing searchable. Filtering
|
|
328
|
+
// on it would return an empty page for a request that carried no terms, so it
|
|
329
|
+
// is treated as no query and the note says the terms were dropped.
|
|
330
|
+
const query = normalize(rawQuery).length > 0 ? rawQuery : "";
|
|
331
|
+
const emptyTerms = rawQuery.length > 0 && query.length === 0;
|
|
332
|
+
const filtered = query.length > 0;
|
|
333
|
+
const all = buildRows(input);
|
|
334
|
+
|
|
335
|
+
if (all.length === 0 && !filtered) {
|
|
336
|
+
return {
|
|
337
|
+
text: input.marketplaceOffered ? EMPTY_WITH_MARKETPLACE : EMPTY_WITHOUT_MARKETPLACE,
|
|
338
|
+
rows: [],
|
|
339
|
+
total: 0,
|
|
340
|
+
shown: 0,
|
|
341
|
+
nextOffset: undefined,
|
|
342
|
+
filtered: false,
|
|
343
|
+
matchMode: null,
|
|
344
|
+
budgetLimited: false,
|
|
345
|
+
driftedNames: [],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const selected = filtered ? selectRows(all, query) : { rows: [...all], mode: null as LexicalMatchMode | null };
|
|
350
|
+
const matching = selected.rows;
|
|
351
|
+
const total = matching.length;
|
|
352
|
+
const offset = clampOffset(input.offset);
|
|
353
|
+
const limit = clampLimit(input.limit, total);
|
|
354
|
+
const window = matching.slice(offset, offset + limit);
|
|
355
|
+
|
|
356
|
+
// The drift notice describes the matched result set, not the page: a drifted
|
|
357
|
+
// skill the caller filtered to is worth naming even when paging pushed its
|
|
358
|
+
// row to the next offset. Read from the typed flag, never from the rendered
|
|
359
|
+
// line, so a description that happens to contain the marker text cannot give
|
|
360
|
+
// a healthy skill a drift footer.
|
|
361
|
+
const driftedNames = matching.filter((row) => row.drifted).map((row) => row.name);
|
|
362
|
+
|
|
363
|
+
const context: RenderContext = {
|
|
364
|
+
input,
|
|
365
|
+
drift: driftNotice(driftedNames),
|
|
366
|
+
filtered,
|
|
367
|
+
emptyTerms,
|
|
368
|
+
query,
|
|
369
|
+
matchMode: selected.mode,
|
|
370
|
+
total,
|
|
371
|
+
offset,
|
|
372
|
+
catalogTotal: all.length,
|
|
373
|
+
window,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// The whole window first, so an inventory that exactly fits is never cut. The
|
|
377
|
+
// previous version reserved scaffolding for sections that did not exist and
|
|
378
|
+
// dropped complete listings that fitted with room to spare.
|
|
379
|
+
const whole = renderCandidate(context, window.length, false);
|
|
380
|
+
if (byteLength(whole) <= input.capBytes) {
|
|
381
|
+
return finishView(context, window, whole, false);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Bisection is valid only over row counts of one or more.
|
|
385
|
+
//
|
|
386
|
+
// For k >= 1 the rendered size is non-decreasing in k: each extra row adds
|
|
387
|
+
// its line and, at a section's first row, that section's heading, while the
|
|
388
|
+
// note only grows by the digits of a larger count. A zero-row page is NOT on
|
|
389
|
+
// that curve, because it alone carries the "this row is too large, open it
|
|
390
|
+
// directly" sentence, which a one-row page does not. Including 0 in the
|
|
391
|
+
// search let a midpoint of 0 test larger than a one-row page and report that
|
|
392
|
+
// nothing fits while the first row would have fitted comfortably.
|
|
393
|
+
let low = 1;
|
|
394
|
+
let high = window.length - 1;
|
|
395
|
+
let best = -1;
|
|
396
|
+
let bestText = "";
|
|
397
|
+
while (low <= high) {
|
|
398
|
+
const mid = (low + high) >> 1;
|
|
399
|
+
const text = renderCandidate(context, mid, true);
|
|
400
|
+
if (byteLength(text) <= input.capBytes) {
|
|
401
|
+
best = mid;
|
|
402
|
+
bestText = text;
|
|
403
|
+
low = mid + 1;
|
|
404
|
+
} else {
|
|
405
|
+
high = mid - 1;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (best >= 1) return finishView(context, window.slice(0, best), bestText, true);
|
|
409
|
+
|
|
410
|
+
// No row fits. The zero-row page is measured on its own, off the monotonic
|
|
411
|
+
// curve, because it says something the others do not.
|
|
412
|
+
const blocked = renderCandidate(context, 0, true);
|
|
413
|
+
if (byteLength(blocked) <= input.capBytes) {
|
|
414
|
+
return finishView(context, [], blocked, true);
|
|
415
|
+
}
|
|
416
|
+
// Not even the scaffolding fits. Say what is there and how to reach it, in a
|
|
417
|
+
// line short enough to survive any usable reservation.
|
|
418
|
+
return {
|
|
419
|
+
text: compactFallback(context),
|
|
420
|
+
rows: [],
|
|
421
|
+
total,
|
|
422
|
+
shown: 0,
|
|
423
|
+
nextOffset: undefined,
|
|
424
|
+
filtered,
|
|
425
|
+
matchMode: selected.mode,
|
|
426
|
+
budgetLimited: true,
|
|
427
|
+
driftedNames,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
interface RenderContext {
|
|
432
|
+
input: SkillCatalogViewInput;
|
|
433
|
+
drift: string;
|
|
434
|
+
filtered: boolean;
|
|
435
|
+
emptyTerms: boolean;
|
|
436
|
+
query: string;
|
|
437
|
+
matchMode: LexicalMatchMode | null;
|
|
438
|
+
total: number;
|
|
439
|
+
offset: number;
|
|
440
|
+
catalogTotal: number;
|
|
441
|
+
window: ReadonlyArray<SkillCatalogRow>;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Where paging resumes, and whether a row too large to carry was stepped over. */
|
|
445
|
+
function continuation(context: RenderContext, shown: number): { nextOffset: number | undefined; blocked: boolean } {
|
|
446
|
+
const consumed = context.offset + shown;
|
|
447
|
+
if (consumed >= context.total) return { nextOffset: undefined, blocked: false };
|
|
448
|
+
// A page that carried nothing must not hand back the offset it was given:
|
|
449
|
+
// that is a loop, not a continuation. Stepping over the oversized row keeps
|
|
450
|
+
// the rows behind it reachable, and the note names what was stepped over.
|
|
451
|
+
if (shown === 0) return { nextOffset: context.offset + 1, blocked: true };
|
|
452
|
+
return { nextOffset: consumed, blocked: false };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function renderCandidate(context: RenderContext, shown: number, cut: boolean): string {
|
|
456
|
+
const page = context.window.slice(0, shown);
|
|
457
|
+
const body = renderSections(page, context.input, context.drift);
|
|
458
|
+
const { nextOffset, blocked } = continuation(context, shown);
|
|
459
|
+
const note = pageNote({
|
|
460
|
+
filtered: context.filtered,
|
|
461
|
+
emptyTerms: context.emptyTerms,
|
|
462
|
+
query: context.query,
|
|
463
|
+
matchMode: context.matchMode,
|
|
464
|
+
total: context.total,
|
|
465
|
+
shown,
|
|
466
|
+
nextOffset,
|
|
467
|
+
budgetLimited: cut,
|
|
468
|
+
blockedRow: blocked ? (context.window[0] ?? null) : null,
|
|
469
|
+
catalogTotal: context.catalogTotal,
|
|
470
|
+
});
|
|
471
|
+
return note.length > 0 ? `${body}\n${note}` : body;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function finishView(
|
|
475
|
+
context: RenderContext,
|
|
476
|
+
page: ReadonlyArray<SkillCatalogRow>,
|
|
477
|
+
text: string,
|
|
478
|
+
budgetLimited: boolean,
|
|
479
|
+
): SkillCatalogView {
|
|
480
|
+
const { nextOffset } = continuation(context, page.length);
|
|
481
|
+
return {
|
|
482
|
+
text,
|
|
483
|
+
rows: page.map((row) => ({ ...row })),
|
|
484
|
+
total: context.total,
|
|
485
|
+
shown: page.length,
|
|
486
|
+
nextOffset,
|
|
487
|
+
filtered: context.filtered,
|
|
488
|
+
matchMode: context.matchMode,
|
|
489
|
+
budgetLimited,
|
|
490
|
+
driftedNames: context.window.filter((row) => row.drifted).map((row) => row.name),
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* What to say when the reservation cannot carry the listing's own scaffolding.
|
|
496
|
+
*
|
|
497
|
+
* One short line naming the size of the thing and the one argument that makes
|
|
498
|
+
* it smaller. Deliberately free of the reply protocol: a page that shows no
|
|
499
|
+
* skills has nothing for the protocol to point at, and repeating it here would
|
|
500
|
+
* be the same overrun in a smaller font.
|
|
501
|
+
*/
|
|
502
|
+
function compactFallback(context: RenderContext): string {
|
|
503
|
+
const scope = context.filtered ? `${context.total} matching` : `${context.total}`;
|
|
504
|
+
return `Available skills: ${scope}. This call's budget cannot carry the listing; narrow it with context(scope="skills", query="<terms>") or raise the budget in a fresh turn.`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
interface PageNoteInput {
|
|
508
|
+
filtered: boolean;
|
|
509
|
+
emptyTerms: boolean;
|
|
510
|
+
query: string;
|
|
511
|
+
matchMode: LexicalMatchMode | null;
|
|
512
|
+
total: number;
|
|
513
|
+
shown: number;
|
|
514
|
+
nextOffset: number | undefined;
|
|
515
|
+
budgetLimited: boolean;
|
|
516
|
+
blockedRow: SkillCatalogRow | null;
|
|
517
|
+
catalogTotal: number;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Emitted only when the view is narrowed or cut.
|
|
522
|
+
*
|
|
523
|
+
* A complete unfiltered listing gains no new text at all, which is what keeps
|
|
524
|
+
* the default view byte-identical to the one this module replaced. Everything
|
|
525
|
+
* here is a statement about what the caller is *not* seeing, so a view that
|
|
526
|
+
* hides nothing has nothing to say.
|
|
527
|
+
*
|
|
528
|
+
* Every value interpolated here is bounded. The query is a caller-supplied
|
|
529
|
+
* string with no length limit in the schema, and the drift notice can name a
|
|
530
|
+
* whole catalog, so echoing either whole would let one argument decide the size
|
|
531
|
+
* of a response this module exists to keep inside its budget.
|
|
532
|
+
*/
|
|
533
|
+
function pageNote(input: PageNoteInput): string {
|
|
534
|
+
const clauses: string[] = [];
|
|
535
|
+
if (input.emptyTerms) {
|
|
536
|
+
clauses.push("The query carried no searchable terms, so the full list is shown.");
|
|
537
|
+
}
|
|
538
|
+
if (input.filtered) {
|
|
539
|
+
const broadened = input.matchMode === "any" ? ", matched on any query word after no row matched them all" : "";
|
|
540
|
+
clauses.push(
|
|
541
|
+
input.total === 0
|
|
542
|
+
? `No skill matches "${boundQuery(input.query)}"; ${input.catalogTotal} are available unfiltered, so drop query to list them.`
|
|
543
|
+
: `Filtered by "${boundQuery(input.query)}"${broadened}: ${input.total} of ${input.catalogTotal} rows match.`,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
if (input.shown < input.total) {
|
|
547
|
+
clauses.push(
|
|
548
|
+
input.budgetLimited
|
|
549
|
+
? `Showing ${input.shown} of ${input.total} matching rows; the rest did not fit this call's budget.`
|
|
550
|
+
: `Showing ${input.shown} of ${input.total} matching rows.`,
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
if (input.blockedRow !== null) {
|
|
554
|
+
clauses.push(
|
|
555
|
+
`"${boundName(input.blockedRow.name)}" is too large to carry in this call's budget and was stepped over; open it directly with context(scope="skills", name="${boundName(input.blockedRow.name)}").`,
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
if (input.nextOffset !== undefined) {
|
|
559
|
+
clauses.push(
|
|
560
|
+
input.filtered
|
|
561
|
+
? // Offsets index the FILTERED result set. A continuation that dropped
|
|
562
|
+
// the query would apply this cursor to the unfiltered catalog, which
|
|
563
|
+
// repeats rows and skips matches.
|
|
564
|
+
`Continue with the same query at offset=${input.nextOffset}; the offset indexes the filtered rows, so dropping query changes which rows it selects.`
|
|
565
|
+
: `Continue with context(scope="skills", offset=${input.nextOffset}) for the remainder.`,
|
|
566
|
+
);
|
|
567
|
+
} else if (input.shown < input.total) {
|
|
568
|
+
clauses.push("The remainder cannot be carried this turn; narrow with query or continue in a follow-up turn.");
|
|
569
|
+
}
|
|
570
|
+
return clauses.join(" ");
|
|
571
|
+
}
|