@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,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural validation for continuity records (CONTRACTS.md §3).
|
|
3
|
+
*
|
|
4
|
+
* These guards are independent of `isSessionEntry` and check the envelope
|
|
5
|
+
* locally, so `entries.ts` can call them in 02B without this module ever
|
|
6
|
+
* value-importing the session entry union back. They validate payload structure
|
|
7
|
+
* and references, not just the kind string: a record that merely says
|
|
8
|
+
* `kind: "continuityCommit"` and carries nothing usable is rejected here rather
|
|
9
|
+
* than surfacing as an empty-but-present commit later.
|
|
10
|
+
*
|
|
11
|
+
* Everything numeric must be a finite integer. `Number.MAX_SAFE_INTEGER`
|
|
12
|
+
* arithmetic and `Infinity` deadlines are both ways to obtain an unbounded
|
|
13
|
+
* automatic window, so neither is accepted.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
type AcceptedNote,
|
|
18
|
+
type ContinuityCarriedState,
|
|
19
|
+
type ContinuityCarryingSummary,
|
|
20
|
+
type ContinuityCheckpointPayload,
|
|
21
|
+
type ContinuityCommitData,
|
|
22
|
+
type ContinuityCommitEntry,
|
|
23
|
+
type ContinuityOutcome,
|
|
24
|
+
HANDOFF_NOTE_MAX_BYTES,
|
|
25
|
+
HANDOFF_POLICY_LIMITS,
|
|
26
|
+
HANDOFF_SCHEMA_VERSION,
|
|
27
|
+
type HandoffEvent,
|
|
28
|
+
type HandoffFailureReason,
|
|
29
|
+
type HandoffIdentity,
|
|
30
|
+
type HandoffPauseReason,
|
|
31
|
+
type HandoffPolicy,
|
|
32
|
+
type HandoffResumeAuthority,
|
|
33
|
+
type HandoffTransactionEntry,
|
|
34
|
+
type TransitionLink,
|
|
35
|
+
} from "./contract.js";
|
|
36
|
+
|
|
37
|
+
const OUTCOMES: ReadonlySet<string> = new Set<ContinuityOutcome>(["summarized", "evicted", "continuity_only"]);
|
|
38
|
+
const PAUSE_REASONS: ReadonlySet<string> = new Set<HandoffPauseReason>([
|
|
39
|
+
"operator_cancelled",
|
|
40
|
+
"origin_changed",
|
|
41
|
+
"delivery_uncertain",
|
|
42
|
+
]);
|
|
43
|
+
const FAILURE_REASONS: ReadonlySet<string> = new Set<HandoffFailureReason>([
|
|
44
|
+
"invalid_note",
|
|
45
|
+
"note_exceeds_replay_budget",
|
|
46
|
+
"budget_unsafe_no_material",
|
|
47
|
+
"durability_unresolved",
|
|
48
|
+
"state_root_removed",
|
|
49
|
+
"origin_changed",
|
|
50
|
+
"provider_failed",
|
|
51
|
+
"attempts_exhausted",
|
|
52
|
+
"deadline_exceeded",
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
56
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
60
|
+
return typeof value === "string" && value.length > 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isNullableString(value: unknown): value is string | null {
|
|
64
|
+
return value === null || isNonEmptyString(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isFiniteInteger(value: unknown): value is number {
|
|
68
|
+
return typeof value === "number" && Number.isSafeInteger(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isNonNegativeInteger(value: unknown): value is number {
|
|
72
|
+
return isFiniteInteger(value) && value >= 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isPositiveInteger(value: unknown): value is number {
|
|
76
|
+
return isFiniteInteger(value) && value > 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A valid `BaseSessionEntry` envelope, checked without importing the union. */
|
|
80
|
+
function hasEntryEnvelope(value: unknown, kind: string): value is Record<string, unknown> {
|
|
81
|
+
if (!isRecord(value)) return false;
|
|
82
|
+
return (
|
|
83
|
+
value.kind === kind &&
|
|
84
|
+
isNonEmptyString(value.turnId) &&
|
|
85
|
+
isNullableString(value.parentTurnId) &&
|
|
86
|
+
isNonEmptyString(value.timestamp)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isAcceptedNote(value: unknown): value is AcceptedNote {
|
|
91
|
+
if (!isRecord(value)) return false;
|
|
92
|
+
return (
|
|
93
|
+
typeof value.note === "string" &&
|
|
94
|
+
value.note.length > 0 &&
|
|
95
|
+
isPositiveInteger(value.noteBytes) &&
|
|
96
|
+
value.noteBytes <= HANDOFF_NOTE_MAX_BYTES &&
|
|
97
|
+
typeof value.noteSha256 === "string" &&
|
|
98
|
+
/^[0-9a-f]{64}$/.test(value.noteSha256)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isHandoffIdentity(value: unknown): value is HandoffIdentity {
|
|
103
|
+
if (!isRecord(value)) return false;
|
|
104
|
+
return (
|
|
105
|
+
isNonEmptyString(value.handoffId) &&
|
|
106
|
+
isNonEmptyString(value.preparedEntryId) &&
|
|
107
|
+
isNonEmptyString(value.commitId) &&
|
|
108
|
+
isNonEmptyString(value.commitEntryId) &&
|
|
109
|
+
isNonEmptyString(value.originSessionId) &&
|
|
110
|
+
isNullableString(value.branchAnchorTurnId) &&
|
|
111
|
+
isNonEmptyString(value.initiatingTurnId) &&
|
|
112
|
+
isNonEmptyString(value.toolCallId) &&
|
|
113
|
+
typeof value.sourceRevision === "string"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A persisted policy is held to the frozen §2/§7 ceilings, not merely to being
|
|
119
|
+
* positive. A ledger record claiming three attempts, a summary-retry loop, or
|
|
120
|
+
* eight stream invocations would otherwise widen the automatic budget just by
|
|
121
|
+
* surviving a restart.
|
|
122
|
+
*/
|
|
123
|
+
function isHandoffPolicy(value: unknown): value is HandoffPolicy {
|
|
124
|
+
if (!isRecord(value)) return false;
|
|
125
|
+
if (!isNonNegativeInteger(value.preparedAtMs)) return false;
|
|
126
|
+
if (!isPositiveInteger(value.automaticDeadlineAtMs)) return false;
|
|
127
|
+
// A deadline at or before preparation is not a window.
|
|
128
|
+
if (value.automaticDeadlineAtMs <= value.preparedAtMs) return false;
|
|
129
|
+
if (!isPositiveInteger(value.maxAttempts) || value.maxAttempts > HANDOFF_POLICY_LIMITS.maxAttempts) return false;
|
|
130
|
+
if (
|
|
131
|
+
!isPositiveInteger(value.maxSummaryCallsPerAttempt) ||
|
|
132
|
+
value.maxSummaryCallsPerAttempt > HANDOFF_POLICY_LIMITS.maxSummaryCallsPerAttempt
|
|
133
|
+
) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
if (value.additionalSummaryRetriesPerCall !== HANDOFF_POLICY_LIMITS.additionalSummaryRetriesPerCall) return false;
|
|
137
|
+
if (
|
|
138
|
+
!isPositiveInteger(value.maxSummaryStreamInvocations) ||
|
|
139
|
+
value.maxSummaryStreamInvocations > HANDOFF_POLICY_LIMITS.maxSummaryStreamInvocations
|
|
140
|
+
) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
return isNonNegativeInteger(value.flushRetryLimit) && value.flushRetryLimit <= HANDOFF_POLICY_LIMITS.flushRetryLimit;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Stable value identity with normalized key order, used for comparing two
|
|
148
|
+
* copies of one record and for the persistence progress token. `undefined`
|
|
149
|
+
* fields are dropped so an explicitly-absent optional and a missing one are the
|
|
150
|
+
* same record.
|
|
151
|
+
*/
|
|
152
|
+
export function canonicalJson(value: unknown): string {
|
|
153
|
+
if (value === undefined) return "undefined";
|
|
154
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
155
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
156
|
+
const fields = Object.entries(value as Record<string, unknown>)
|
|
157
|
+
.filter(([, field]) => field !== undefined)
|
|
158
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
159
|
+
return `{${fields.map(([key, field]) => `${JSON.stringify(key)}:${canonicalJson(field)}`).join(",")}}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isTransitionLink(value: unknown): value is TransitionLink {
|
|
163
|
+
if (!isRecord(value)) return false;
|
|
164
|
+
return (
|
|
165
|
+
isNonEmptyString(value.entryId) &&
|
|
166
|
+
isNullableString(value.prevEntryId) &&
|
|
167
|
+
isNonNegativeInteger(value.sequence) &&
|
|
168
|
+
isNonNegativeInteger(value.attempt)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isResumeAuthority(value: unknown): value is HandoffResumeAuthority {
|
|
173
|
+
if (!isRecord(value)) return false;
|
|
174
|
+
return (
|
|
175
|
+
isNonEmptyString(value.operatorRequestEntryId) &&
|
|
176
|
+
isNonEmptyString(value.pausedOrFailedEntryId) &&
|
|
177
|
+
(value.action === "reduce" || value.action === "deliver") &&
|
|
178
|
+
isPositiveInteger(value.automaticDeadlineAtMs)
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Validate one event, discriminating on `phase` and requiring that phase's fields. */
|
|
183
|
+
export function isHandoffEvent(value: unknown): value is HandoffEvent {
|
|
184
|
+
if (!isRecord(value)) return false;
|
|
185
|
+
switch (value.phase) {
|
|
186
|
+
case "prepared":
|
|
187
|
+
return isAcceptedNote(value.accepted) && isHandoffPolicy(value.policy);
|
|
188
|
+
case "reducing":
|
|
189
|
+
return value.resumeRef === undefined || isNonEmptyString(value.resumeRef);
|
|
190
|
+
case "delivered":
|
|
191
|
+
return (
|
|
192
|
+
isNonEmptyString(value.deliveryId) &&
|
|
193
|
+
isNonEmptyString(value.continuationTurnId) &&
|
|
194
|
+
(value.resumeRef === undefined || isNonEmptyString(value.resumeRef))
|
|
195
|
+
);
|
|
196
|
+
case "acknowledged":
|
|
197
|
+
return isNonEmptyString(value.deliveryId) && isNonEmptyString(value.terminalResponseEntryId);
|
|
198
|
+
case "paused":
|
|
199
|
+
return typeof value.reason === "string" && PAUSE_REASONS.has(value.reason);
|
|
200
|
+
case "failed":
|
|
201
|
+
return typeof value.reason === "string" && FAILURE_REASONS.has(value.reason);
|
|
202
|
+
case "resumed":
|
|
203
|
+
return isResumeAuthority(value.authority);
|
|
204
|
+
default:
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* A transaction record. Beyond the field shapes this enforces the two identity
|
|
211
|
+
* equalities the chain depends on: the envelope's `turnId` is the transition's
|
|
212
|
+
* `entryId`, and a `prepared` event must occupy the reserved prepared identity
|
|
213
|
+
* at sequence 0 with no predecessor.
|
|
214
|
+
*/
|
|
215
|
+
export function isHandoffTransactionEntry(value: unknown): value is HandoffTransactionEntry {
|
|
216
|
+
if (!hasEntryEnvelope(value, "handoffTransaction")) return false;
|
|
217
|
+
if (value.schemaVersion !== HANDOFF_SCHEMA_VERSION) return false;
|
|
218
|
+
if (!isHandoffIdentity(value.identity)) return false;
|
|
219
|
+
if (!isTransitionLink(value.transition)) return false;
|
|
220
|
+
if (!isHandoffEvent(value.event)) return false;
|
|
221
|
+
if (value.transition.entryId !== value.turnId) return false;
|
|
222
|
+
if (value.event.phase === "prepared") {
|
|
223
|
+
return (
|
|
224
|
+
value.transition.entryId === value.identity.preparedEntryId &&
|
|
225
|
+
value.transition.prevEntryId === null &&
|
|
226
|
+
value.transition.sequence === 0 &&
|
|
227
|
+
value.transition.attempt === 0
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
// Only `prepared` may be the chain root.
|
|
231
|
+
return value.transition.prevEntryId !== null && value.transition.sequence > 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function isCommitData(value: unknown): value is ContinuityCommitData {
|
|
235
|
+
if (!isRecord(value)) return false;
|
|
236
|
+
if (typeof value.outcome !== "string" || !OUTCOMES.has(value.outcome)) return false;
|
|
237
|
+
if (!isRecord(value.entry)) return false;
|
|
238
|
+
if (!isNonEmptyString(value.entry.turnId)) return false;
|
|
239
|
+
if (!isNullableString(value.entry.parentTurnId)) return false;
|
|
240
|
+
if (!isNonEmptyString(value.entry.timestamp)) return false;
|
|
241
|
+
if (!isTransitionLink(value.transition)) return false;
|
|
242
|
+
if (value.transition.entryId !== value.entry.turnId) return false;
|
|
243
|
+
if (!isNonNegativeInteger(value.tokensBefore) || !isNonNegativeInteger(value.tokensAfter)) return false;
|
|
244
|
+
// The ref an outcome names is required for that outcome and forbidden for
|
|
245
|
+
// the others: an eviction-only commit that also claims a summary ref is not
|
|
246
|
+
// a record this core will treat as valid evidence.
|
|
247
|
+
if (value.outcome === "summarized") {
|
|
248
|
+
return isNonEmptyString(value.summaryRef) && value.evictionRef === undefined;
|
|
249
|
+
}
|
|
250
|
+
if (value.outcome === "evicted") {
|
|
251
|
+
return isNonEmptyString(value.evictionRef) && value.summaryRef === undefined;
|
|
252
|
+
}
|
|
253
|
+
return value.summaryRef === undefined && value.evictionRef === undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* A head must be internally consistent, not merely well-shaped. A phase name is
|
|
258
|
+
* not authority: a `resumed` head has to carry the resume it names, and a
|
|
259
|
+
* `delivered` head the delivery intent it describes, or the payload is claiming
|
|
260
|
+
* a state it does not actually record.
|
|
261
|
+
*/
|
|
262
|
+
function isCarriedState(value: unknown): value is ContinuityCarriedState {
|
|
263
|
+
if (!isRecord(value)) return false;
|
|
264
|
+
if (!isTransitionLink(value.transition)) return false;
|
|
265
|
+
if (!isRecord(value.event)) return false;
|
|
266
|
+
if (value.event.phase !== "ready" && !isHandoffEvent(value.event)) return false;
|
|
267
|
+
if (value.event.phase === "ready" && Object.keys(value.event).length !== 1) return false;
|
|
268
|
+
if (value.activeResume !== null) {
|
|
269
|
+
if (!isRecord(value.activeResume)) return false;
|
|
270
|
+
if (!isNonEmptyString(value.activeResume.entryId)) return false;
|
|
271
|
+
if (!isResumeAuthority(value.activeResume.authority)) return false;
|
|
272
|
+
}
|
|
273
|
+
if (value.delivery !== null) {
|
|
274
|
+
if (!isRecord(value.delivery)) return false;
|
|
275
|
+
if (!isNonEmptyString(value.delivery.deliveryId)) return false;
|
|
276
|
+
if (!isNonEmptyString(value.delivery.continuationTurnId)) return false;
|
|
277
|
+
}
|
|
278
|
+
if (value.event.phase === "resumed") {
|
|
279
|
+
const resume = value.activeResume;
|
|
280
|
+
if (!isRecord(resume)) return false;
|
|
281
|
+
if (resume.entryId !== value.transition.entryId) return false;
|
|
282
|
+
if (canonicalJson(resume.authority) !== canonicalJson(value.event.authority)) return false;
|
|
283
|
+
}
|
|
284
|
+
if (value.event.phase === "delivered") {
|
|
285
|
+
const delivery = value.delivery;
|
|
286
|
+
if (!isRecord(delivery)) return false;
|
|
287
|
+
if (delivery.deliveryId !== value.event.deliveryId) return false;
|
|
288
|
+
if (delivery.continuationTurnId !== value.event.continuationTurnId) return false;
|
|
289
|
+
}
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* The shared payload a commit embeds and a later summary carries.
|
|
295
|
+
*
|
|
296
|
+
* The carried `state` must extend the commit's own chain, so its sequence is
|
|
297
|
+
* required to be at least the commit's. A payload whose head sits before its
|
|
298
|
+
* own commit describes no chain this core can fold.
|
|
299
|
+
*/
|
|
300
|
+
export function isContinuityCheckpointPayload(value: unknown): value is ContinuityCheckpointPayload {
|
|
301
|
+
if (!isRecord(value)) return false;
|
|
302
|
+
if (value.schemaVersion !== HANDOFF_SCHEMA_VERSION) return false;
|
|
303
|
+
if (!isHandoffIdentity(value.identity)) return false;
|
|
304
|
+
if (!isAcceptedNote(value.accepted)) return false;
|
|
305
|
+
if (!isHandoffPolicy(value.policy)) return false;
|
|
306
|
+
if (!isCommitData(value.commit)) return false;
|
|
307
|
+
if (!isCarriedState(value.state)) return false;
|
|
308
|
+
if (value.commit.entry.turnId !== value.identity.commitEntryId) return false;
|
|
309
|
+
// A commit exists because a reduction was spent, and no more of them than the
|
|
310
|
+
// policy allows. An attempt of 0 or 99 on a commit describes a history the
|
|
311
|
+
// policy could not have produced, whatever else the payload says.
|
|
312
|
+
if (value.commit.transition.attempt < 1 || value.commit.transition.attempt > value.policy.maxAttempts) return false;
|
|
313
|
+
if (value.state.transition.attempt > value.policy.maxAttempts) return false;
|
|
314
|
+
if (value.state.transition.sequence < value.commit.transition.sequence) return false;
|
|
315
|
+
// `ready` is definitionally the commit's own transition, so a payload
|
|
316
|
+
// claiming ready at some other link is describing a state that never
|
|
317
|
+
// existed. This is what stops a carry from asserting `ready` at an invented
|
|
318
|
+
// sequence the chain never reached.
|
|
319
|
+
if (value.state.event.phase === "ready") {
|
|
320
|
+
return canonicalJson(value.state.transition) === canonicalJson(value.commit.transition);
|
|
321
|
+
}
|
|
322
|
+
// Past the commit, a reduction has necessarily been spent.
|
|
323
|
+
return value.state.transition.attempt >= value.commit.transition.attempt;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* An original commit record. Beyond the shared payload it must be the envelope
|
|
328
|
+
* its payload reserved and must carry the exact original `ready` state: an
|
|
329
|
+
* advanced head belongs on a later summary carry, never on the commit itself.
|
|
330
|
+
*/
|
|
331
|
+
export function isContinuityCommitEntry(value: unknown): value is ContinuityCommitEntry {
|
|
332
|
+
if (!hasEntryEnvelope(value, "continuityCommit")) return false;
|
|
333
|
+
if (!isContinuityCheckpointPayload(value.continuity)) return false;
|
|
334
|
+
const payload = value.continuity;
|
|
335
|
+
if (payload.state.event.phase !== "ready") return false;
|
|
336
|
+
if (payload.state.activeResume !== null || payload.state.delivery !== null) return false;
|
|
337
|
+
return (
|
|
338
|
+
value.turnId === payload.commit.entry.turnId &&
|
|
339
|
+
value.parentTurnId === payload.commit.entry.parentTurnId &&
|
|
340
|
+
value.timestamp === payload.commit.entry.timestamp
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* A compaction summary carrying continuity. Recognized structurally so 02A adds
|
|
346
|
+
* no field to the live `CompactionSummaryEntry`; a summary without the field is
|
|
347
|
+
* an ordinary summary and not this shape.
|
|
348
|
+
*/
|
|
349
|
+
export function isContinuityCarryingSummary(value: unknown): value is ContinuityCarryingSummary {
|
|
350
|
+
if (!hasEntryEnvelope(value, "compactionSummary")) return false;
|
|
351
|
+
return isContinuityCheckpointPayload(value.continuity);
|
|
352
|
+
}
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
* (interactive/editor-bash.ts).
|
|
13
13
|
* - fileEntry is consumed by the renderer, compaction, handoff, and
|
|
14
14
|
* evidence builders but has no in-tree producer.
|
|
15
|
+
* - handoffTransaction and continuityCommit are durable continuity records
|
|
16
|
+
* (CONTRACTS §3), validated by src/domains/session/continuity/validate.ts.
|
|
17
|
+
* Format v5 registers them; packet 03 wires the runtime that writes them.
|
|
18
|
+
*
|
|
19
|
+
* The continuity import is a value import in one direction only: this module
|
|
20
|
+
* calls those validators, and the continuity modules reach back for
|
|
21
|
+
* `BaseSessionEntry` as an erased `import type`, so there is no runtime cycle.
|
|
15
22
|
*/
|
|
16
23
|
|
|
17
24
|
import { createHash } from "node:crypto";
|
|
@@ -19,6 +26,17 @@ import { createHash } from "node:crypto";
|
|
|
19
26
|
import { isSkillActivation, type PendingSkillToolPolicy, type SkillActivation } from "../../core/skill-activation.js";
|
|
20
27
|
import type { ClioTurnRecord } from "../../engine/session.js";
|
|
21
28
|
import { AUTONOMY_EXPOSURES, type AutonomyExposure } from "../safety/autonomy.js";
|
|
29
|
+
import type {
|
|
30
|
+
ContinuityCheckpointPayload,
|
|
31
|
+
ContinuityCommitEntry,
|
|
32
|
+
HandoffTransactionEntry,
|
|
33
|
+
} from "./continuity/contract.js";
|
|
34
|
+
import { HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE, isHandoffRecoveryRequestData } from "./continuity/operator-request.js";
|
|
35
|
+
import {
|
|
36
|
+
isContinuityCheckpointPayload,
|
|
37
|
+
isContinuityCommitEntry,
|
|
38
|
+
isHandoffTransactionEntry,
|
|
39
|
+
} from "./continuity/validate.js";
|
|
22
40
|
|
|
23
41
|
export interface SessionHeader {
|
|
24
42
|
type: "session";
|
|
@@ -288,6 +306,15 @@ export interface CompactionSummaryEntry extends BaseSessionEntry {
|
|
|
288
306
|
messagesSummarized?: number;
|
|
289
307
|
/** True when the cut split a turn (caller may want to render a banner). */
|
|
290
308
|
isSplitTurn?: boolean;
|
|
309
|
+
/**
|
|
310
|
+
* The latest validated continuity fold this summary carries (CONTRACTS §3).
|
|
311
|
+
*
|
|
312
|
+
* Optional because every summary written before v5, and every ordinary
|
|
313
|
+
* compaction with no agent handoff behind it, has none. A present payload is
|
|
314
|
+
* fully validated by `isSessionEntry`: a summary carrying a malformed
|
|
315
|
+
* checkpoint is a malformed summary, not a summary with a field to ignore.
|
|
316
|
+
*/
|
|
317
|
+
continuity?: ContinuityCheckpointPayload;
|
|
291
318
|
}
|
|
292
319
|
|
|
293
320
|
export interface SessionInfoEntry extends BaseSessionEntry {
|
|
@@ -561,7 +588,9 @@ export type SessionEntry =
|
|
|
561
588
|
| DecisionLedgerEntry
|
|
562
589
|
| WorkerRunEntry
|
|
563
590
|
| ContextEvictionEntry
|
|
564
|
-
| ContextRecallEntry
|
|
591
|
+
| ContextRecallEntry
|
|
592
|
+
| HandoffTransactionEntry
|
|
593
|
+
| ContinuityCommitEntry;
|
|
565
594
|
|
|
566
595
|
export type SessionFileEntry = SessionHeader | SessionEntry;
|
|
567
596
|
|
|
@@ -589,6 +618,12 @@ export const SESSION_ENTRY_KINDS = [
|
|
|
589
618
|
"workerRun",
|
|
590
619
|
"contextEviction",
|
|
591
620
|
"contextRecall",
|
|
621
|
+
// Durable continuity (CONTRACTS §3). Both are bookkeeping sidecars: they
|
|
622
|
+
// never become model messages by themselves, never move the message leaf,
|
|
623
|
+
// and never become a cut point. The accepted note reaches the model exactly
|
|
624
|
+
// once, through the continuity replay projection.
|
|
625
|
+
"handoffTransaction",
|
|
626
|
+
"continuityCommit",
|
|
592
627
|
] as const;
|
|
593
628
|
|
|
594
629
|
export type SessionEntryKind = (typeof SESSION_ENTRY_KINDS)[number];
|
|
@@ -793,7 +828,10 @@ export function isSessionEntry(value: unknown): value is SessionEntry {
|
|
|
793
828
|
return (
|
|
794
829
|
isString(v.customType) &&
|
|
795
830
|
isOptionalBoolean(v.display) &&
|
|
796
|
-
(v.customType !== SKILL_CONTEXT_STATE || isSkillContextState(v.data))
|
|
831
|
+
(v.customType !== SKILL_CONTEXT_STATE || isSkillContextState(v.data)) &&
|
|
832
|
+
// A reserved control subtype is strictly validated, because opaque
|
|
833
|
+
// custom data must never become resume authority by claiming the name.
|
|
834
|
+
(v.customType !== HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE || isHandoffRecoveryRequestData(v.data))
|
|
797
835
|
);
|
|
798
836
|
case "modelChange":
|
|
799
837
|
return isString(v.provider) && isString(v.modelId) && isOptionalString(v.target);
|
|
@@ -816,7 +854,8 @@ export function isSessionEntry(value: unknown): value is SessionEntry {
|
|
|
816
854
|
isOptionalNumber(v.messagesSummarized) &&
|
|
817
855
|
isOptionalBoolean(v.isSplitTurn) &&
|
|
818
856
|
(v.skillContext === undefined || isSkillContextCheckpoint(v.skillContext)) &&
|
|
819
|
-
(v.userContext === undefined || isPreservedUserContext(v.userContext))
|
|
857
|
+
(v.userContext === undefined || isPreservedUserContext(v.userContext)) &&
|
|
858
|
+
(v.continuity === undefined || isContinuityCheckpointPayload(v.continuity))
|
|
820
859
|
);
|
|
821
860
|
case "sessionInfo":
|
|
822
861
|
return isOptionalString(v.name) && isOptionalString(v.targetTurnId) && isOptionalString(v.label);
|
|
@@ -889,6 +928,14 @@ export function isSessionEntry(value: unknown): value is SessionEntry {
|
|
|
889
928
|
isNumber(v.tokensReadmitted) &&
|
|
890
929
|
isOptionalString(v.toolCallId)
|
|
891
930
|
);
|
|
931
|
+
// The continuity guards check payload structure and the identity/link
|
|
932
|
+
// equalities the chain depends on, not just the kind string. A record
|
|
933
|
+
// that says `continuityCommit` and carries nothing usable is rejected
|
|
934
|
+
// here rather than surfacing later as an empty-but-present commit.
|
|
935
|
+
case "handoffTransaction":
|
|
936
|
+
return isHandoffTransactionEntry(v);
|
|
937
|
+
case "continuityCommit":
|
|
938
|
+
return isContinuityCommitEntry(v);
|
|
892
939
|
}
|
|
893
940
|
return false;
|
|
894
941
|
}
|
|
@@ -19,6 +19,54 @@ export {
|
|
|
19
19
|
readAuditRows,
|
|
20
20
|
readSessionEntriesForId,
|
|
21
21
|
} from "./archive-readers.js";
|
|
22
|
+
// Durable continuity (CONTRACTS §§2-8). Only the consumer-facing half surfaces
|
|
23
|
+
// here: the record and fold contracts, the reader-side projection, the operator
|
|
24
|
+
// control carrier and the persistence binding. The pure fold, its validators
|
|
25
|
+
// and the group persister stay reachable through their own modules, because the
|
|
26
|
+
// packets that use them are inside this domain or hold an explicit grant.
|
|
27
|
+
export type {
|
|
28
|
+
AcceptedNote,
|
|
29
|
+
ContinuityAuthority,
|
|
30
|
+
ContinuityCarriedState,
|
|
31
|
+
ContinuityCheckpointPayload,
|
|
32
|
+
ContinuityCommitData,
|
|
33
|
+
ContinuityCommitEntry,
|
|
34
|
+
ContinuityFoldResult,
|
|
35
|
+
ContinuityOutcome,
|
|
36
|
+
ContinuityPersistencePorts,
|
|
37
|
+
ContinuityPhase,
|
|
38
|
+
ContinuityRecoveryAction,
|
|
39
|
+
HandoffEvent,
|
|
40
|
+
HandoffIdentity,
|
|
41
|
+
HandoffPolicy,
|
|
42
|
+
HandoffTransactionEntry,
|
|
43
|
+
TransitionLink,
|
|
44
|
+
} from "./continuity/contract.js";
|
|
45
|
+
export type {
|
|
46
|
+
HandoffRecoveryRequestData,
|
|
47
|
+
HandoffRecoveryRequestEntry,
|
|
48
|
+
} from "./continuity/operator-request.js";
|
|
49
|
+
export {
|
|
50
|
+
HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE,
|
|
51
|
+
isHandoffRecoveryRequestData,
|
|
52
|
+
isHandoffRecoveryRequestEntry,
|
|
53
|
+
} from "./continuity/operator-request.js";
|
|
54
|
+
export type { ContinuityPortOrigin, ContinuityPortsInput } from "./continuity/ports.js";
|
|
55
|
+
export { createContinuityPersistencePorts, readContinuityRecordExact } from "./continuity/ports.js";
|
|
56
|
+
export type {
|
|
57
|
+
ContinuityForkBinding,
|
|
58
|
+
ContinuityProjection,
|
|
59
|
+
ContinuityProjectionInput,
|
|
60
|
+
ContinuityRecallNote,
|
|
61
|
+
ProjectedContinuityNote,
|
|
62
|
+
} from "./continuity/projection.js";
|
|
63
|
+
export {
|
|
64
|
+
continuityProjectionTokens,
|
|
65
|
+
continuityReplayBlocks,
|
|
66
|
+
continuityReplayText,
|
|
67
|
+
inheritedContinuityReplayText,
|
|
68
|
+
resolveContinuityProjection,
|
|
69
|
+
} from "./continuity/projection.js";
|
|
22
70
|
export type {
|
|
23
71
|
ClioSessionMetaExtension,
|
|
24
72
|
DeleteSessionOptions,
|
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
* whatever that build understood and this one does not. Downgrading and
|
|
9
9
|
* resuming would then write the truncated reading back over the original.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
* (`contextEviction`, `contextRecall`)
|
|
13
|
-
*
|
|
11
|
+
* Versions 3, 4 and 5 are one additive chain. 4 added the working-set kinds
|
|
12
|
+
* (`contextEviction`, `contextRecall`); 5 adds the continuity kinds
|
|
13
|
+
* (`handoffTransaction`, `continuityCommit`) and the optional continuity
|
|
14
|
+
* payload on a compaction summary. Each step only makes new records legal, so
|
|
15
|
+
* a version-3 or version-4 ledger is read exactly as it stands and only the
|
|
16
|
+
* metadata is restamped. Nothing in the file changes, and an old ledger simply
|
|
17
|
+
* has no handoff in it.
|
|
14
18
|
*/
|
|
15
19
|
|
|
16
20
|
import { CURRENT_SESSION_FORMAT_VERSION } from "../../../engine/session.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readSessionFileEntries, type SessionTreeNode, sessionPaths } from "../../../engine/session.js";
|
|
2
2
|
import { collectSessionEntries } from "../compaction/session-entries.js";
|
|
3
|
+
import { HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE } from "../continuity/operator-request.js";
|
|
3
4
|
import type { SessionMeta } from "../contract.js";
|
|
4
5
|
import { isSessionHeader, type SessionEntry } from "../entries.js";
|
|
5
6
|
import { enrichForkMeta } from "../history.js";
|
|
@@ -87,13 +88,26 @@ function treeFromLinearPath(path: ReadonlyArray<LinkedRecord>): SessionTreeNode[
|
|
|
87
88
|
* happens to be on the path. Live `/tree`-switch replay does not need this
|
|
88
89
|
* extra gate (see the comment on the shared function for why), which is the
|
|
89
90
|
* one place fork and live replay still deliberately disagree.
|
|
91
|
+
*
|
|
92
|
+
* Continuity records need the same gate and for a stronger reason. They anchor
|
|
93
|
+
* to a turn on the path, so the shared verdict would carry a pause, a delivery,
|
|
94
|
+
* an acknowledgement or an operator recovery request written *after* the fork
|
|
95
|
+
* point into a branch that stands before it. That is not just stale display: a
|
|
96
|
+
* fork inheriting a later `resumed` record, or the control request behind it,
|
|
97
|
+
* would inherit the evidence for a state it never reached. §8 requires ancestry
|
|
98
|
+
* **and** file position for both new kinds; the reserved operator control
|
|
99
|
+
* subtype is named explicitly because §3.1 puts it under the same cutoff, and
|
|
100
|
+
* because it is the one `custom` record that is authority rather than display.
|
|
90
101
|
*/
|
|
91
102
|
function sessionEntryBelongsToPath(
|
|
92
103
|
entry: SessionEntry,
|
|
93
104
|
pathIds: ReadonlySet<string>,
|
|
94
105
|
atOrBeforeForkPoint: boolean,
|
|
95
106
|
): boolean {
|
|
96
|
-
if (
|
|
107
|
+
if (atOrBeforeForkPoint) return entryBelongsToPath(entry, pathIds, atOrBeforeForkPoint);
|
|
108
|
+
if (entry.kind === "compactionSummary") return false;
|
|
109
|
+
if (entry.kind === "handoffTransaction" || entry.kind === "continuityCommit") return false;
|
|
110
|
+
if (entry.kind === "custom" && entry.customType === HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE) return false;
|
|
97
111
|
return entryBelongsToPath(entry, pathIds, atOrBeforeForkPoint);
|
|
98
112
|
}
|
|
99
113
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* for each call.
|
|
6
6
|
*
|
|
7
7
|
* This lives in the session domain because the ledger is the only durable
|
|
8
|
-
* record of what a session spent, and three surfaces read it: the `/
|
|
8
|
+
* record of what a session spent, and three surfaces read it: the `/usage`
|
|
9
9
|
* overlay and footer reseed from it on every session change, and `clio-coder usage
|
|
10
10
|
* report` folds it across sessions. It was written for the overlay first and
|
|
11
11
|
* sat under src/interactive; a headless report reaching into the TUI surface's
|
|
@@ -94,7 +94,7 @@ export function ledgerUsageCalls(
|
|
|
94
94
|
// A compaction summarizes history through a real model call, billed like
|
|
95
95
|
// any other. Its usage rides on the compactionSummary entry rather than on
|
|
96
96
|
// an assistant message, because the summary is context machinery and never
|
|
97
|
-
// enters the conversation; folding it here is what puts it on `/
|
|
97
|
+
// enters the conversation; folding it here is what puts it on `/usage` and
|
|
98
98
|
// in `clio-coder usage report`.
|
|
99
99
|
if (entry?.kind === "compactionSummary") {
|
|
100
100
|
const usage = entry.usage;
|
|
@@ -590,7 +590,7 @@ function headlessContext(
|
|
|
590
590
|
listAgents: () => unreachable("listAgents"),
|
|
591
591
|
listDelegationAgents: () => unreachable("listDelegationAgents"),
|
|
592
592
|
shutdown: () => unreachable("shutdown"),
|
|
593
|
-
|
|
593
|
+
openUsage: () => unreachable("openUsage"),
|
|
594
594
|
openSideQuestion: () => unreachable("openSideQuestion"),
|
|
595
595
|
startHandoff: () => unreachable("startHandoff"),
|
|
596
596
|
openContextView: () => unreachable("openContextView"),
|