@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,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local lexical matching over skill text. No model call, no network, and no
|
|
3
|
+
* request text ever leaves the process.
|
|
4
|
+
*
|
|
5
|
+
* Two callers with two different error costs share this vocabulary.
|
|
6
|
+
* `promotion.ts` scores an *unsolicited* install offer, where a false positive
|
|
7
|
+
* costs operator trust, so it keeps its own conservative thresholds on top of
|
|
8
|
+
* these helpers. The catalog view filters a listing the model explicitly asked
|
|
9
|
+
* to narrow, where the expensive error is the opposite one: a skill the
|
|
10
|
+
* operator named that the filter silently hides. {@link lexicalMatches} is
|
|
11
|
+
* tuned for that second case and is not a promotion matcher.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Words too common in a coding request to distinguish one skill from another.
|
|
16
|
+
* Shared so both matchers agree about what carries no signal.
|
|
17
|
+
*/
|
|
18
|
+
export const TOKEN_STOPWORDS = new Set([
|
|
19
|
+
"the",
|
|
20
|
+
"a",
|
|
21
|
+
"an",
|
|
22
|
+
"and",
|
|
23
|
+
"or",
|
|
24
|
+
"for",
|
|
25
|
+
"with",
|
|
26
|
+
"this",
|
|
27
|
+
"that",
|
|
28
|
+
"when",
|
|
29
|
+
"what",
|
|
30
|
+
"how",
|
|
31
|
+
"into",
|
|
32
|
+
"from",
|
|
33
|
+
"use",
|
|
34
|
+
"used",
|
|
35
|
+
"using",
|
|
36
|
+
"one",
|
|
37
|
+
"not",
|
|
38
|
+
"skill",
|
|
39
|
+
"skills",
|
|
40
|
+
"clio",
|
|
41
|
+
"please",
|
|
42
|
+
"can",
|
|
43
|
+
"you",
|
|
44
|
+
"should",
|
|
45
|
+
"would",
|
|
46
|
+
"about",
|
|
47
|
+
"need",
|
|
48
|
+
"want",
|
|
49
|
+
"help",
|
|
50
|
+
"make",
|
|
51
|
+
"file",
|
|
52
|
+
"files",
|
|
53
|
+
"code",
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
/** Lowercase, punctuation to spaces, whitespace collapsed. */
|
|
57
|
+
export function normalize(text: string): string {
|
|
58
|
+
return text
|
|
59
|
+
.toLowerCase()
|
|
60
|
+
.replace(/[^\p{L}\p{N}\s]/gu, " ")
|
|
61
|
+
.replace(/\s+/g, " ")
|
|
62
|
+
.trim();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Distinctive tokens: 4+ chars and outside the stopword set.
|
|
67
|
+
*
|
|
68
|
+
* The 4-character floor belongs to the promotion matcher, which fires an offer
|
|
69
|
+
* nobody asked for and must not do so on `tdd` appearing in a sentence. The
|
|
70
|
+
* catalog filter deliberately does not use this.
|
|
71
|
+
*/
|
|
72
|
+
export function distinctiveTokens(text: string): Set<string> {
|
|
73
|
+
return new Set(
|
|
74
|
+
normalize(text)
|
|
75
|
+
.split(" ")
|
|
76
|
+
.filter((token) => token.length >= 4 && !TOKEN_STOPWORDS.has(token)),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** How many of a multi-word query's tokens a row has to carry. */
|
|
81
|
+
export type LexicalMatchMode = "all" | "any";
|
|
82
|
+
|
|
83
|
+
/** Query tokens worth matching on: 2+ chars, outside the stopword set. */
|
|
84
|
+
function queryTokens(normalizedQuery: string): string[] {
|
|
85
|
+
return normalizedQuery.split(" ").filter((token) => token.length >= 2 && !TOKEN_STOPWORDS.has(token));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A token matches a word outright, or as its prefix once it is 3+ characters.
|
|
90
|
+
*
|
|
91
|
+
* Prefix matching is what makes `conflict` find `resolve-merge-conflicts` and
|
|
92
|
+
* `worktree` find `worktree-create`. It is floored at 3 characters so a
|
|
93
|
+
* two-letter token cannot match most of the catalog.
|
|
94
|
+
*/
|
|
95
|
+
function wordMatches(words: ReadonlySet<string>, token: string): boolean {
|
|
96
|
+
if (words.has(token)) return true;
|
|
97
|
+
if (token.length < 3) return false;
|
|
98
|
+
for (const word of words) {
|
|
99
|
+
if (word.startsWith(token)) return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Does `haystack` match `query`?
|
|
106
|
+
*
|
|
107
|
+
* An empty query matches everything, so a caller can pass the operator's
|
|
108
|
+
* argument through without branching.
|
|
109
|
+
*
|
|
110
|
+
* The whole query matching as a word sequence always wins, which is how a name
|
|
111
|
+
* shorter than the token floor (`tdd`, `prd`) stays findable by typing it. The
|
|
112
|
+
* sequence is matched with word boundaries on both ends, so `git` does not hit
|
|
113
|
+
* `digital`.
|
|
114
|
+
*
|
|
115
|
+
* Failing that, the query is tokenized and matched per token. `mode` decides
|
|
116
|
+
* whether every token must land or any one of them may: the catalog view tries
|
|
117
|
+
* `all` first and only falls back to `any` when that returned nothing, so a
|
|
118
|
+
* precise query narrows and a conversational one still finds something instead
|
|
119
|
+
* of an empty page.
|
|
120
|
+
*/
|
|
121
|
+
export function lexicalMatches(query: string, haystack: string, mode: LexicalMatchMode = "all"): boolean {
|
|
122
|
+
// Canonical composition on both sides, so a decomposed `café` typed by
|
|
123
|
+
// one editor matches a composed `café` written by another. Applied here
|
|
124
|
+
// rather than inside `normalize`, because `normalize` is also the promotion
|
|
125
|
+
// matcher's and changing what that fires on is a separate decision.
|
|
126
|
+
const normalizedQuery = normalize(query.normalize("NFC"));
|
|
127
|
+
if (normalizedQuery.length === 0) return true;
|
|
128
|
+
const normalizedHaystack = normalize(haystack.normalize("NFC"));
|
|
129
|
+
if (` ${normalizedHaystack} `.includes(` ${normalizedQuery} `)) return true;
|
|
130
|
+
const tokens = queryTokens(normalizedQuery);
|
|
131
|
+
if (tokens.length === 0) return false;
|
|
132
|
+
const words = new Set(normalizedHaystack.split(" ").filter((word) => word.length > 0));
|
|
133
|
+
return mode === "all"
|
|
134
|
+
? tokens.every((token) => wordMatches(words, token))
|
|
135
|
+
: tokens.some((token) => wordMatches(words, token));
|
|
136
|
+
}
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { type Dirent, existsSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { CLIO_SELF_DEVELOPMENT_SKILLS, detectClioCoderRepo } from "../../../core/clio-repo.js";
|
|
5
6
|
import { warnLegacyNaming } from "../../../core/naming-compat.js";
|
|
6
7
|
import type { PendingSkillRequest } from "../../../core/skill-activation.js";
|
|
7
8
|
import { type ToolName, ToolNames } from "../../../core/tool-names.js";
|
|
@@ -145,6 +146,8 @@ export interface SkillRoot {
|
|
|
145
146
|
* package roots use the package directory as their containment boundary.
|
|
146
147
|
*/
|
|
147
148
|
containment?: string;
|
|
149
|
+
/** Repository-provided roots cannot relocate their containment through a symlink. */
|
|
150
|
+
strictContainment?: boolean;
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
export interface SkillList {
|
|
@@ -242,6 +245,40 @@ export function defaultSkillRoots(input: LoadSkillsInput = {}): SkillRoot[] {
|
|
|
242
245
|
const trustProject = projectCompatTrusted(input.trustProjectCompatRoots);
|
|
243
246
|
const roots: SkillRoot[] = [];
|
|
244
247
|
|
|
248
|
+
// Source-checkout guidance is a narrow native discovery tier, not a package
|
|
249
|
+
// installation. An installed package owns its name even when disabled,
|
|
250
|
+
// damaged, or awaiting reload; never resurrect it through a source fallback.
|
|
251
|
+
const selfRepo = detectClioCoderRepo(cwd).repoRoot;
|
|
252
|
+
if (selfRepo) {
|
|
253
|
+
const ownedNames = installedSkillNames([], cwd);
|
|
254
|
+
if (path.resolve(cwd) !== selfRepo) {
|
|
255
|
+
for (const name of installedSkillNames([], selfRepo)) ownedNames.add(name);
|
|
256
|
+
// Native skills can be flat Markdown files or directories whose
|
|
257
|
+
// frontmatter name differs from the path. Use the loader's names so
|
|
258
|
+
// a descendant launch cannot resurrect a hidden root override.
|
|
259
|
+
const rootOverrides = loadSkillRoot(
|
|
260
|
+
{ path: path.join(selfRepo, ".clio-coder", "skills"), scope: "project", containment: selfRepo },
|
|
261
|
+
[],
|
|
262
|
+
);
|
|
263
|
+
for (const candidate of rootOverrides) ownedNames.add(candidate.skill.name);
|
|
264
|
+
}
|
|
265
|
+
for (const name of CLIO_SELF_DEVELOPMENT_SKILLS) {
|
|
266
|
+
if (ownedNames.has(name)) continue;
|
|
267
|
+
const skillPath = path.join(selfRepo, "library", "skills", "meta", name);
|
|
268
|
+
if (!existsSync(path.join(skillPath, "SKILL.md"))) continue;
|
|
269
|
+
roots.push({
|
|
270
|
+
path: skillPath,
|
|
271
|
+
scope: "project",
|
|
272
|
+
source: "clio-coder",
|
|
273
|
+
origin: "self-development",
|
|
274
|
+
precedence: SKILL_PRECEDENCE.package - 1,
|
|
275
|
+
trusted: true,
|
|
276
|
+
containment: selfRepo,
|
|
277
|
+
strictContainment: true,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
245
282
|
for (const root of enabledPluginResourceRoots("skills", cwd)) {
|
|
246
283
|
roots.push({
|
|
247
284
|
path: root.path,
|
|
@@ -560,6 +597,7 @@ function pathIsInside(candidate: string, anchor: string): boolean {
|
|
|
560
597
|
function containmentAnchor(root: SkillRoot): string | null {
|
|
561
598
|
if (root.containment === undefined) return null;
|
|
562
599
|
const anchor = canonicalizePath(root.containment);
|
|
600
|
+
if (root.strictContainment) return anchor;
|
|
563
601
|
const rootReal = canonicalizePath(root.path);
|
|
564
602
|
return pathIsInside(rootReal, anchor) ? anchor : rootReal;
|
|
565
603
|
}
|
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { safeResourceWrite } from "../../../core/safe-resource-write.js";
|
|
4
4
|
import { clioConfigDir } from "../../../core/xdg.js";
|
|
5
|
+
import { distinctiveTokens, normalize } from "./lexical-match.js";
|
|
5
6
|
import type { MarketplaceSkill } from "./marketplace.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -26,61 +27,6 @@ export interface PromotionMatch {
|
|
|
26
27
|
matchedTrigger?: string;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
const TOKEN_STOPWORDS = new Set([
|
|
30
|
-
"the",
|
|
31
|
-
"a",
|
|
32
|
-
"an",
|
|
33
|
-
"and",
|
|
34
|
-
"or",
|
|
35
|
-
"for",
|
|
36
|
-
"with",
|
|
37
|
-
"this",
|
|
38
|
-
"that",
|
|
39
|
-
"when",
|
|
40
|
-
"what",
|
|
41
|
-
"how",
|
|
42
|
-
"into",
|
|
43
|
-
"from",
|
|
44
|
-
"use",
|
|
45
|
-
"used",
|
|
46
|
-
"using",
|
|
47
|
-
"one",
|
|
48
|
-
"not",
|
|
49
|
-
"skill",
|
|
50
|
-
"skills",
|
|
51
|
-
"clio",
|
|
52
|
-
"please",
|
|
53
|
-
"can",
|
|
54
|
-
"you",
|
|
55
|
-
"should",
|
|
56
|
-
"would",
|
|
57
|
-
"about",
|
|
58
|
-
"need",
|
|
59
|
-
"want",
|
|
60
|
-
"help",
|
|
61
|
-
"make",
|
|
62
|
-
"file",
|
|
63
|
-
"files",
|
|
64
|
-
"code",
|
|
65
|
-
]);
|
|
66
|
-
|
|
67
|
-
function normalize(text: string): string {
|
|
68
|
-
return text
|
|
69
|
-
.toLowerCase()
|
|
70
|
-
.replace(/[^\p{L}\p{N}\s]/gu, " ")
|
|
71
|
-
.replace(/\s+/g, " ")
|
|
72
|
-
.trim();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Distinctive tokens: 4+ chars and outside the stopword set. */
|
|
76
|
-
function distinctiveTokens(text: string): Set<string> {
|
|
77
|
-
return new Set(
|
|
78
|
-
normalize(text)
|
|
79
|
-
.split(" ")
|
|
80
|
-
.filter((token) => token.length >= 4 && !TOKEN_STOPWORDS.has(token)),
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
30
|
const TRIGGER_SCORE = 4;
|
|
85
31
|
/** Distinct description/name token overlaps required to fire without a trigger. */
|
|
86
32
|
const TOKEN_FIRE_THRESHOLD = 3;
|
|
@@ -101,28 +101,58 @@ export interface SkillDriftReport {
|
|
|
101
101
|
* means nothing recorded a hash for this skill; drift evidence is opt-in and
|
|
102
102
|
* never a gate, and skills still pass the normal tool safety gates either way.
|
|
103
103
|
*/
|
|
104
|
-
export function checkSkillDrift(
|
|
105
|
-
skill
|
|
104
|
+
export function checkSkillDrift(skill: SkillDriftSubject, cwd: string): SkillDriftReport | null {
|
|
105
|
+
return checkSkillDriftBatch([skill], cwd).get(skill.name) ?? null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The fields a drift comparison reads. Structural, so any loaded skill satisfies it. */
|
|
109
|
+
export interface SkillDriftSubject {
|
|
110
|
+
name: string;
|
|
111
|
+
normalizedHash: string;
|
|
112
|
+
provenance?: { installedHash?: string };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The same comparison for many skills, resolving and parsing the pin manifest
|
|
117
|
+
* once.
|
|
118
|
+
*
|
|
119
|
+
* Activation checks one skill and can afford to re-read the manifest each time.
|
|
120
|
+
* A listing checks every row, and doing that through the single-skill entry
|
|
121
|
+
* point would re-stat up to four candidate paths and re-parse the YAML once per
|
|
122
|
+
* skill. {@link checkSkillDrift} is the single-skill case of this function
|
|
123
|
+
* rather than a parallel implementation, so the listing and the activation can
|
|
124
|
+
* never disagree about a verdict.
|
|
125
|
+
*
|
|
126
|
+
* Skills with nothing recorded for them are absent from the map rather than
|
|
127
|
+
* present with a null: an entry means a comparison happened.
|
|
128
|
+
*/
|
|
129
|
+
export function checkSkillDriftBatch(
|
|
130
|
+
skills: ReadonlyArray<SkillDriftSubject>,
|
|
106
131
|
cwd: string,
|
|
107
|
-
): SkillDriftReport
|
|
108
|
-
const
|
|
132
|
+
): Map<string, SkillDriftReport> {
|
|
133
|
+
const reports = new Map<string, SkillDriftReport>();
|
|
134
|
+
if (skills.length === 0) return reports;
|
|
109
135
|
const manifestPath = resolveSkillPinManifestPath(cwd);
|
|
110
136
|
const manifest = manifestPath === null ? null : loadSkillPinManifest(manifestPath);
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
137
|
+
for (const skill of skills) {
|
|
138
|
+
const actual = skill.normalizedHash.toLowerCase();
|
|
139
|
+
const pinned = manifest?.get(skill.name);
|
|
140
|
+
if (pinned !== undefined) {
|
|
141
|
+
reports.set(skill.name, {
|
|
142
|
+
verdict: pinned.sha256 === actual ? "match" : "mismatch",
|
|
143
|
+
authority: "pinned-manifest",
|
|
144
|
+
expected: pinned.sha256,
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const recorded = skill.provenance?.installedHash?.trim().toLowerCase();
|
|
149
|
+
if (recorded !== undefined && /^[0-9a-f]{64}$/.test(recorded)) {
|
|
150
|
+
reports.set(skill.name, {
|
|
151
|
+
verdict: recorded === actual ? "match" : "mismatch",
|
|
152
|
+
authority: "install-record",
|
|
153
|
+
expected: recorded,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
118
156
|
}
|
|
119
|
-
|
|
120
|
-
if (recorded !== undefined && /^[0-9a-f]{64}$/.test(recorded)) {
|
|
121
|
-
return {
|
|
122
|
-
verdict: recorded === actual ? "match" : "mismatch",
|
|
123
|
-
authority: "install-record",
|
|
124
|
-
expected: recorded,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
return null;
|
|
157
|
+
return reports;
|
|
128
158
|
}
|
|
@@ -98,6 +98,7 @@ function baseClassify(tool: string): ActionClass | null {
|
|
|
98
98
|
case ToolNames.WebFetch:
|
|
99
99
|
case ToolNames.Git:
|
|
100
100
|
case ToolNames.CodeNav:
|
|
101
|
+
case ToolNames.SelfCompact:
|
|
101
102
|
case ToolNames.Context:
|
|
102
103
|
// clio_docs and clio_library read Clio's bundled documentation and the
|
|
103
104
|
// recipe catalog; data streams structured files. None writes.
|
|
@@ -185,7 +185,10 @@ export function serializeConversation(entries: ReadonlyArray<SessionEntry>): str
|
|
|
185
185
|
}
|
|
186
186
|
// custom, modelChange, thinkingLevelChange, fileEntry, sessionInfo,
|
|
187
187
|
// protectedArtifact, taskLedger, decisionLedger, and workerRun do not
|
|
188
|
-
// project into the serialized conversation.
|
|
188
|
+
// project into the serialized conversation. Neither do handoffTransaction
|
|
189
|
+
// and continuityCommit: this is an allowlist, so transaction JSON can
|
|
190
|
+
// never reach the summarization prompt as if it were conversation, and
|
|
191
|
+
// the accepted note is never regenerated from summarizer prose.
|
|
189
192
|
}
|
|
190
193
|
return parts.join("\n\n");
|
|
191
194
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ContinuityCheckpointPayload } from "../continuity/contract.js";
|
|
1
2
|
/**
|
|
2
3
|
* Compaction orchestration.
|
|
3
4
|
*
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
import { serializeConversation } from "./branch-summary.js";
|
|
31
32
|
import { findCutPoint } from "./cut-point.js";
|
|
32
33
|
import { DEFAULT_KEEP_RECENT_TOKENS, DEFAULT_RESERVE_TOKENS } from "./defaults.js";
|
|
33
|
-
import { calculateContextTokens, getLastAssistantUsage } from "./tokens.js";
|
|
34
|
+
import { type ContinuityNoteCost, calculateContextTokens, getLastAssistantUsage } from "./tokens.js";
|
|
34
35
|
|
|
35
36
|
interface FileOperations {
|
|
36
37
|
read: Set<string>;
|
|
@@ -144,6 +145,11 @@ export interface CompactionCallObservation {
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
export interface CompactInput {
|
|
148
|
+
/** Synchronous admission after exact request-fit, before each provider call. */
|
|
149
|
+
beforeSummaryCall?: (() => void) | undefined;
|
|
150
|
+
checkpointForSummary?:
|
|
151
|
+
| ((summaryRef: string, tokensBefore: number, tokensAfter: number) => ContinuityCheckpointPayload)
|
|
152
|
+
| undefined;
|
|
147
153
|
/** Ordered active-path session entries to compact; returned cut indexes address this same array. */
|
|
148
154
|
entries: ReadonlyArray<SessionEntry>;
|
|
149
155
|
/** Authoritative main-agent selection; omitted for unknown legacy state. */
|
|
@@ -175,6 +181,15 @@ export interface CompactInput {
|
|
|
175
181
|
preserveUserTurnId?: string;
|
|
176
182
|
/** Accounting observer; called once per invoked stream, including failures. */
|
|
177
183
|
onCall?: (call: CompactionCallObservation) => void;
|
|
184
|
+
/**
|
|
185
|
+
* The continuity note replay projects, from `continuityProjectionTokens` and
|
|
186
|
+
* the projection's `noteAnchorTurnId`. The note survives the reduction, so it
|
|
187
|
+
* is on both sides of the before/after comparison; counting it needs the
|
|
188
|
+
* session and fork facts the caller has and this module does not, and the
|
|
189
|
+
* anchor id is what keeps it from being counted twice once a provider usage
|
|
190
|
+
* anchor already includes it.
|
|
191
|
+
*/
|
|
192
|
+
continuityNote?: ContinuityNoteCost;
|
|
178
193
|
}
|
|
179
194
|
|
|
180
195
|
export interface CompactResult {
|
|
@@ -675,6 +690,7 @@ async function runSummaryStream(
|
|
|
675
690
|
}
|
|
676
691
|
|
|
677
692
|
input.signal?.throwIfAborted();
|
|
693
|
+
input.beforeSummaryCall?.();
|
|
678
694
|
const timestamp = new Date().toISOString();
|
|
679
695
|
const started = performance.now();
|
|
680
696
|
let usage: unknown;
|
|
@@ -804,7 +820,7 @@ export async function compact(input: CompactInput): Promise<CompactResult> {
|
|
|
804
820
|
const usageStart = prevCompactionIndex >= 0 ? prevCompactionIndex : 0;
|
|
805
821
|
const usageEntries = entries.slice(usageStart);
|
|
806
822
|
const lastUsage = getLastAssistantUsage(usageEntries);
|
|
807
|
-
const tokensBefore = calculateContextTokens(usageEntries, lastUsage);
|
|
823
|
+
const tokensBefore = calculateContextTokens(usageEntries, lastUsage, input.continuityNote);
|
|
808
824
|
const rawCut = findCutPoint(entries, keepRecentTokens, { startIndex: boundaryStart });
|
|
809
825
|
const cut =
|
|
810
826
|
protectedStart !== null && rawCut.firstKeptEntryIndex > protectedStart
|
|
@@ -74,6 +74,12 @@ function isValidCutPoint(entry: SessionEntry): boolean {
|
|
|
74
74
|
case "workerRun":
|
|
75
75
|
case "contextEviction":
|
|
76
76
|
case "contextRecall":
|
|
77
|
+
// Transaction bookkeeping is not a place the conversation can be sliced.
|
|
78
|
+
// A cut landing on a handoff record would put the summary boundary in the
|
|
79
|
+
// middle of a durable transaction, and the records carry no conversation
|
|
80
|
+
// text for the retained suffix to be about.
|
|
81
|
+
case "handoffTransaction":
|
|
82
|
+
case "continuityCommit":
|
|
77
83
|
return false;
|
|
78
84
|
}
|
|
79
85
|
}
|
|
@@ -193,7 +199,20 @@ export function findCutPoint(
|
|
|
193
199
|
prev.kind === "bashExecution" ||
|
|
194
200
|
prev.kind === "custom" ||
|
|
195
201
|
prev.kind === "skillActivation" ||
|
|
196
|
-
prev.kind === "branchSummary"
|
|
202
|
+
prev.kind === "branchSummary" ||
|
|
203
|
+
// Stop before a continuity record rather than widening onto it. The
|
|
204
|
+
// widening exists so bookkeeping rides with the retained suffix, but
|
|
205
|
+
// the cut index also names `firstKeptTurnId`, which becomes the new
|
|
206
|
+
// summary's structural parent. Parking that on a transaction record
|
|
207
|
+
// would anchor the summary to a non-message, and `entryBelongsToPath`
|
|
208
|
+
// resolves a summary through its anchor: on a branched session the
|
|
209
|
+
// summary would then be filtered off its own active path. Continuity
|
|
210
|
+
// records ahead of the cut lose nothing by being on the summarized
|
|
211
|
+
// side, because `serializeConversation` never projects them into the
|
|
212
|
+
// prompt and the fold reads the full pre-cut ledger rather than the
|
|
213
|
+
// replay slice.
|
|
214
|
+
prev.kind === "handoffTransaction" ||
|
|
215
|
+
prev.kind === "continuityCommit"
|
|
197
216
|
) {
|
|
198
217
|
break;
|
|
199
218
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type { Usage } from "../../../engine/types.js";
|
|
15
15
|
import { ceilChars, contentChars, estimateAgentMessageTokens } from "../context-accounting.js";
|
|
16
|
+
import { HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE } from "../continuity/operator-request.js";
|
|
16
17
|
import type {
|
|
17
18
|
BashExecutionEntry,
|
|
18
19
|
BranchSummaryEntry,
|
|
@@ -53,6 +54,11 @@ function estimateBashExecution(entry: BashExecutionEntry): number {
|
|
|
53
54
|
|
|
54
55
|
function estimateCustom(entry: CustomEntry): number {
|
|
55
56
|
if (entry.data === undefined) return 0;
|
|
57
|
+
// The reserved operator recovery request is a control record, not model text.
|
|
58
|
+
// Model replay skips it by design (it is never turned into a user turn), so
|
|
59
|
+
// charging its data to the context budget would claim the model can see a
|
|
60
|
+
// record it is never shown.
|
|
61
|
+
if (entry.customType === HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE) return 0;
|
|
56
62
|
return ceilChars(contentChars(entry.data));
|
|
57
63
|
}
|
|
58
64
|
|
|
@@ -100,6 +106,14 @@ export function estimateTokens(entry: SessionEntry): number {
|
|
|
100
106
|
// projection accounts for marker cost on the projected messages.
|
|
101
107
|
case "contextEviction":
|
|
102
108
|
case "contextRecall":
|
|
109
|
+
// Continuity bookkeeping. The transaction chain and the commit payload
|
|
110
|
+
// are never model text, and a carried payload is a copy of facts the
|
|
111
|
+
// projection already holds: charging every copy would price one accepted
|
|
112
|
+
// note three times over three summary cycles. The single rendered note
|
|
113
|
+
// the projection does emit is priced once, by the caller that resolved
|
|
114
|
+
// it, through `continuityNoteTokens` on `calculateContextTokens`.
|
|
115
|
+
case "handoffTransaction":
|
|
116
|
+
case "continuityCommit":
|
|
103
117
|
return 0;
|
|
104
118
|
}
|
|
105
119
|
}
|
|
@@ -152,8 +166,27 @@ function findLastAssistantUsageIndex(entries: ReadonlyArray<SessionEntry>): numb
|
|
|
152
166
|
* accurate for sessions whose most recent assistant turn carried real
|
|
153
167
|
* provider usage data.
|
|
154
168
|
*/
|
|
155
|
-
|
|
169
|
+
/**
|
|
170
|
+
* The continuity note a replay projects, for callers pricing a whole prompt.
|
|
171
|
+
*
|
|
172
|
+
* `tokens` comes from `continuityProjectionTokens`, and `anchorTurnId` is the
|
|
173
|
+
* ledger record that introduced the transaction. Selecting which note gets
|
|
174
|
+
* projected needs session and fork facts a token estimator does not have, so
|
|
175
|
+
* the caller resolves it; a second selection rule here would be free to drift
|
|
176
|
+
* from the fold's.
|
|
177
|
+
*/
|
|
178
|
+
export interface ContinuityNoteCost {
|
|
179
|
+
tokens: number;
|
|
180
|
+
anchorTurnId: string | null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function calculateContextTokens(
|
|
184
|
+
entries: ReadonlyArray<SessionEntry>,
|
|
185
|
+
lastUsage?: Usage,
|
|
186
|
+
continuityNote?: ContinuityNoteCost,
|
|
187
|
+
): number {
|
|
156
188
|
const usage = lastUsage ?? getLastAssistantUsage(entries);
|
|
189
|
+
const noteTokens = continuityNote?.tokens ?? 0;
|
|
157
190
|
if (usage) {
|
|
158
191
|
const anchorTokens = usageTotalTokens(usage);
|
|
159
192
|
const anchorIndex = findLastAssistantUsageIndex(entries);
|
|
@@ -162,9 +195,21 @@ export function calculateContextTokens(entries: ReadonlyArray<SessionEntry>, las
|
|
|
162
195
|
const entry = entries[i];
|
|
163
196
|
if (entry) trailing += estimateTokens(entry);
|
|
164
197
|
}
|
|
165
|
-
|
|
198
|
+
// The anchor is a real provider prompt count. Once a continuation has run
|
|
199
|
+
// with the note in front of it, that measurement already contains the note,
|
|
200
|
+
// and adding it again would inflate `tokensBefore` a little more on every
|
|
201
|
+
// compaction cycle. Only a note introduced *after* the anchored call is new
|
|
202
|
+
// material. An unlocatable anchor record is treated as new, which
|
|
203
|
+
// over-counts by one note rather than hiding it.
|
|
204
|
+
const introducedAt =
|
|
205
|
+
continuityNote?.anchorTurnId == null
|
|
206
|
+
? -1
|
|
207
|
+
: entries.findIndex((entry) => entry.turnId === continuityNote.anchorTurnId);
|
|
208
|
+
const alreadyMeasured = introducedAt >= 0 && introducedAt <= anchorIndex;
|
|
209
|
+
return anchorTokens + trailing + (alreadyMeasured ? 0 : noteTokens);
|
|
166
210
|
}
|
|
167
|
-
|
|
211
|
+
// With no anchor everything is estimated, so the note is always new material.
|
|
212
|
+
let total = noteTokens;
|
|
168
213
|
for (const entry of entries) total += estimateTokens(entry);
|
|
169
214
|
return total;
|
|
170
215
|
}
|
|
@@ -14,6 +14,7 @@ export interface AgentContextEstimateInput {
|
|
|
14
14
|
messages: ReadonlyArray<AgentMessage>;
|
|
15
15
|
systemPrompt?: string;
|
|
16
16
|
pendingUserText?: string;
|
|
17
|
+
pendingUserImages?: ReadonlyArray<unknown>;
|
|
17
18
|
tools?: ReadonlyArray<unknown>;
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -253,7 +254,13 @@ export function estimateAgentContextBreakdown(input: AgentContextEstimateInput):
|
|
|
253
254
|
(sum, message) => sum + (message.role === "system" ? 0 : estimateAgentMessageTokens(message)),
|
|
254
255
|
0,
|
|
255
256
|
),
|
|
256
|
-
pendingUserTokens:
|
|
257
|
+
pendingUserTokens:
|
|
258
|
+
input.pendingUserText !== undefined
|
|
259
|
+
? estimateAgentMessageTokens({
|
|
260
|
+
role: "user",
|
|
261
|
+
content: [{ type: "text", text: input.pendingUserText }, ...(input.pendingUserImages ?? [])],
|
|
262
|
+
})
|
|
263
|
+
: 0,
|
|
257
264
|
toolSchemaTokens: (input.tools ?? []).reduce<number>((sum, tool) => sum + ceilChars(toolSchemaChars(tool)), 0),
|
|
258
265
|
};
|
|
259
266
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Summary carry and missing-commit reconstruction (CONTRACTS.md §3.5, §6).
|
|
3
|
+
*
|
|
4
|
+
* A later summary embeds the current validated folded payload: the same
|
|
5
|
+
* identity, the same agent-authored note, the original policy, the original
|
|
6
|
+
* commit envelope/outcome/refs and spent attempts, with `state` advanced to the
|
|
7
|
+
* latest delivery, acknowledgement, pause or explicit resume. It never
|
|
8
|
+
* manufactures a commit and never turns an eviction-only outcome into a
|
|
9
|
+
* summarized one.
|
|
10
|
+
*
|
|
11
|
+
* Both functions are projections of a validated fold. Neither reads a clock,
|
|
12
|
+
* mints an id, or writes anything.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ContinuityCheckpointPayload, ContinuityFoldResult, MissingCommitReconstruction } from "./contract.js";
|
|
16
|
+
import { HANDOFF_SCHEMA_VERSION } from "./contract.js";
|
|
17
|
+
import { isContinuityCheckpointPayload } from "./validate.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The payload a summary written now should carry, or null when this fold has
|
|
21
|
+
* nothing durable to carry.
|
|
22
|
+
*
|
|
23
|
+
* A carry requires a commit: before one exists there is no checkpoint to
|
|
24
|
+
* project, only an in-flight transaction whose own records are the evidence.
|
|
25
|
+
* The result is validated before being returned, so a fold that produced a
|
|
26
|
+
* shape this core would refuse to read back cannot be written out.
|
|
27
|
+
*/
|
|
28
|
+
export function continuityPayloadFromFold(fold: ContinuityFoldResult): ContinuityCheckpointPayload | null {
|
|
29
|
+
// Only a validated fold may be published. This is not the same as execution
|
|
30
|
+
// authority: a paused or inherited historical transaction is recall-only and
|
|
31
|
+
// still carries perfectly well, while a chain with conflicting copies or a
|
|
32
|
+
// malformed record has nothing trustworthy to write forward.
|
|
33
|
+
if (!fold.validated) return null;
|
|
34
|
+
if (!fold.identity || !fold.accepted || !fold.policy || !fold.commit || !fold.state) return null;
|
|
35
|
+
const payload: ContinuityCheckpointPayload = {
|
|
36
|
+
schemaVersion: HANDOFF_SCHEMA_VERSION,
|
|
37
|
+
identity: fold.identity,
|
|
38
|
+
accepted: fold.accepted,
|
|
39
|
+
policy: fold.policy,
|
|
40
|
+
commit: fold.commit,
|
|
41
|
+
state: fold.state,
|
|
42
|
+
};
|
|
43
|
+
return isContinuityCheckpointPayload(payload) ? payload : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The commit a validated carry proves existed, when this projection does not
|
|
48
|
+
* contain it.
|
|
49
|
+
*
|
|
50
|
+
* The fold has already refused to conclude absence while unreadable records
|
|
51
|
+
* could hide the commit, so this is a projection of that decision rather than a
|
|
52
|
+
* second search. The reconstruction reuses the reserved entry id, parent and
|
|
53
|
+
* timestamp, which is what keeps recovery from minting a second commit
|
|
54
|
+
* identity, and returns the later carried head separately so restoring it after
|
|
55
|
+
* the append cannot regress an acknowledgement or a pause to `ready`.
|
|
56
|
+
*/
|
|
57
|
+
export function missingCommitFromCarry(fold: ContinuityFoldResult): MissingCommitReconstruction | null {
|
|
58
|
+
return fold.validated ? fold.missingCommit : null;
|
|
59
|
+
}
|