@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
|
@@ -2,6 +2,7 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { relative } from "node:path";
|
|
3
3
|
import { BusChannels } from "../core/bus-events.js";
|
|
4
4
|
import type { SafeEventBus } from "../core/event-bus.js";
|
|
5
|
+
import type { BudgetInspection } from "../domains/context/budget/inspection.js";
|
|
5
6
|
import type { WorkingSetView } from "../domains/context/working-set/contract.js";
|
|
6
7
|
import { formatContextWindowSlots } from "../domains/providers/index.js";
|
|
7
8
|
import type { ContextLedger, ContextLedgerGroup } from "../domains/session/context-ledger.js";
|
|
@@ -301,6 +302,7 @@ export interface OpenContextOverlayOptions {
|
|
|
301
302
|
chat?: {
|
|
302
303
|
onEvent(handler: (event: { type: string }) => void): () => void;
|
|
303
304
|
isStreaming(): boolean;
|
|
305
|
+
inspectLiveBudget?(): BudgetInspection;
|
|
304
306
|
};
|
|
305
307
|
/** Working-set fold at the live leaf; null or absent hides the section. */
|
|
306
308
|
getWorkingSet?: () => WorkingSetView | null;
|
|
@@ -320,13 +322,32 @@ export function openContextOverlay(
|
|
|
320
322
|
options?: OpenContextOverlayOptions,
|
|
321
323
|
): OverlayHandle {
|
|
322
324
|
let contentWidth = DEFAULT_CONTENT_WIDTH;
|
|
323
|
-
const render = (): string =>
|
|
324
|
-
renderContextLedgerLines(
|
|
325
|
+
const render = (): string => {
|
|
326
|
+
const lines = renderContextLedgerLines(
|
|
325
327
|
getLedger(),
|
|
326
328
|
contentWidth,
|
|
327
329
|
options?.getWorkingSet?.() ?? null,
|
|
328
330
|
options?.getWorkingSetConfig?.() ?? null,
|
|
329
|
-
)
|
|
331
|
+
);
|
|
332
|
+
const inspection = options?.chat?.inspectLiveBudget?.();
|
|
333
|
+
if (inspection?.status === "available") {
|
|
334
|
+
const view = inspection.view;
|
|
335
|
+
const count = (value: number | null) => (value === null ? "unknown" : formatTokens(value));
|
|
336
|
+
lines.push(
|
|
337
|
+
"",
|
|
338
|
+
`Next request: ${count(view.inputTokens)} input + ${count(view.outputReserveTokens)} output`,
|
|
339
|
+
`Headroom: ${count(view.headroomTokens)} · ${view.phase}`,
|
|
340
|
+
);
|
|
341
|
+
if (view.pendingHandoff) {
|
|
342
|
+
lines.push(
|
|
343
|
+
`Handoff: ${view.lastOutcome?.outcome ?? "pending"}`,
|
|
344
|
+
view.pendingHandoff.id,
|
|
345
|
+
`/context recover ${view.pendingHandoff.id} reduce|deliver`,
|
|
346
|
+
);
|
|
347
|
+
} else if (view.lastOutcome) lines.push(`Last handoff: ${view.lastOutcome.outcome}`);
|
|
348
|
+
}
|
|
349
|
+
return lines.join("\n");
|
|
350
|
+
};
|
|
330
351
|
const text = new Text(render(), 0, 0);
|
|
331
352
|
const body = {
|
|
332
353
|
render(width: number): string[] {
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/** Live ownership and execution around the session domain's durable protocol. */
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
type AcceptedNote,
|
|
5
|
+
type ContinuityAppendable,
|
|
6
|
+
type ContinuityCheckpointPayload,
|
|
7
|
+
type ContinuityFoldResult,
|
|
8
|
+
type ContinuityPersistencePorts,
|
|
9
|
+
HANDOFF_MAX_WINDOW_MS,
|
|
10
|
+
HANDOFF_POLICY_LIMITS,
|
|
11
|
+
type HandoffEvent,
|
|
12
|
+
type HandoffIdentity,
|
|
13
|
+
type HandoffPolicy,
|
|
14
|
+
type HandoffTransactionEntry,
|
|
15
|
+
} from "../domains/session/continuity/contract.js";
|
|
16
|
+
import { resolveContinuityEvidence } from "../domains/session/continuity/evidence.js";
|
|
17
|
+
import { validateContinuityNote } from "../domains/session/continuity/note.js";
|
|
18
|
+
import {
|
|
19
|
+
HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE,
|
|
20
|
+
type HandoffRecoveryRequestEntry,
|
|
21
|
+
} from "../domains/session/continuity/operator-request.js";
|
|
22
|
+
import { persistContinuityGroup } from "../domains/session/continuity/persistence.js";
|
|
23
|
+
import { resolveContinuityProjection } from "../domains/session/continuity/projection.js";
|
|
24
|
+
import type { SessionEntry } from "../domains/session/entries.js";
|
|
25
|
+
|
|
26
|
+
export interface ContinuityReductionHooks {
|
|
27
|
+
beforeSummaryCall(): void;
|
|
28
|
+
/** Called before the summary append; the resulting carry reserves the exact commit. */
|
|
29
|
+
checkpointForSummary(summaryRef: string, tokensBefore: number, tokensAfter: number): ContinuityCheckpointPayload;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ContinuityOrigin {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
leafTurnId: string;
|
|
35
|
+
initiatingTurnId: string;
|
|
36
|
+
sourceRevision: string;
|
|
37
|
+
ports: ContinuityPersistencePorts;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ContinuityControllerDeps {
|
|
41
|
+
/** Captures navigation/runtime ownership by value. Never follows a changed session. */
|
|
42
|
+
captureOrigin(): ContinuityOrigin;
|
|
43
|
+
entries(): ReadonlyArray<SessionEntry>;
|
|
44
|
+
leaf(): string | null;
|
|
45
|
+
admitNote(note: AcceptedNote): boolean;
|
|
46
|
+
fits(): boolean;
|
|
47
|
+
inputTokens(): number;
|
|
48
|
+
reduce(hooks: ContinuityReductionHooks, signal: AbortSignal): Promise<void>;
|
|
49
|
+
installReplay(): void;
|
|
50
|
+
onCommit?(commitId: string, outcome: "summarized" | "evicted" | "continuity_only"): void;
|
|
51
|
+
notice(text: string): void;
|
|
52
|
+
now?: () => number;
|
|
53
|
+
id?: () => string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface LiveHandoff {
|
|
57
|
+
origin: ContinuityOrigin;
|
|
58
|
+
identity: HandoffIdentity;
|
|
59
|
+
accepted: AcceptedNote;
|
|
60
|
+
policy: HandoffPolicy;
|
|
61
|
+
deadline: number;
|
|
62
|
+
abort: AbortController;
|
|
63
|
+
deliveryId?: string;
|
|
64
|
+
uncertain: boolean;
|
|
65
|
+
deadlineTimer?: ReturnType<typeof setTimeout>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class ContinuityController {
|
|
69
|
+
private live: LiveHandoff | null = null;
|
|
70
|
+
private busy = false;
|
|
71
|
+
private readonly now: () => number;
|
|
72
|
+
private readonly id: () => string;
|
|
73
|
+
|
|
74
|
+
constructor(private readonly deps: ContinuityControllerDeps) {
|
|
75
|
+
this.now = deps.now ?? Date.now;
|
|
76
|
+
this.id = deps.id ?? randomUUID;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private armDeadline(live: LiveHandoff): void {
|
|
80
|
+
live.deadlineTimer = setTimeout(
|
|
81
|
+
() => live.abort.abort(new Error("Handoff automatic deadline expired.")),
|
|
82
|
+
Math.max(1, live.deadline - this.now()),
|
|
83
|
+
);
|
|
84
|
+
live.deadlineTimer.unref?.();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private fold(live = this.live): ContinuityFoldResult | null {
|
|
88
|
+
if (!live?.origin.ports.isOriginCurrent() || live.origin.ports.isStateRemoved()) return null;
|
|
89
|
+
return resolveContinuityProjection({
|
|
90
|
+
entries: this.deps.entries(),
|
|
91
|
+
sessionId: live.origin.sessionId,
|
|
92
|
+
nowMs: this.now(),
|
|
93
|
+
}).current;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private check(live: LiveHandoff): void {
|
|
97
|
+
live.abort.signal.throwIfAborted();
|
|
98
|
+
if (this.live !== live || !live.origin.ports.isOriginCurrent()) throw new Error("Handoff ownership changed.");
|
|
99
|
+
if (live.uncertain) throw new Error("Handoff persistence is unresolved; inspect before recovery.");
|
|
100
|
+
if (this.now() >= live.deadline) throw new Error("Handoff automatic deadline expired.");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private async persist(
|
|
104
|
+
live: LiveHandoff,
|
|
105
|
+
entries: ReadonlyArray<ContinuityAppendable | SessionEntry>,
|
|
106
|
+
checkpoint = false,
|
|
107
|
+
closing = false,
|
|
108
|
+
): Promise<void> {
|
|
109
|
+
const result = await persistContinuityGroup(
|
|
110
|
+
{
|
|
111
|
+
entries: entries.map((entry) => ({ ...entry })),
|
|
112
|
+
barrier: checkpoint ? { kind: "checkpoint", reason: "context-handoff" } : { kind: "flush" },
|
|
113
|
+
},
|
|
114
|
+
live.origin.ports,
|
|
115
|
+
{
|
|
116
|
+
limit: live.policy.flushRetryLimit,
|
|
117
|
+
deadlineAtMs: closing ? this.now() + 5000 : live.deadline,
|
|
118
|
+
now: this.now,
|
|
119
|
+
wait: async () => {},
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
if (result.status !== "durable" || !result.confirmedWithinDeadline) {
|
|
123
|
+
live.uncertain = true;
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Handoff durability unresolved: ${result.status === "durable" ? "deadline expired" : result.reason}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private async transition(live: LiveHandoff, event: HandoffEvent, closing = false): Promise<void> {
|
|
131
|
+
const fold = this.fold(live);
|
|
132
|
+
if (!fold?.validated || fold.identity?.handoffId !== live.identity.handoffId || !fold.state)
|
|
133
|
+
throw new Error("Handoff head cannot be validated.");
|
|
134
|
+
const entryId = this.id();
|
|
135
|
+
const entry: HandoffTransactionEntry = {
|
|
136
|
+
kind: "handoffTransaction",
|
|
137
|
+
schemaVersion: 1,
|
|
138
|
+
turnId: entryId,
|
|
139
|
+
parentTurnId: live.identity.branchAnchorTurnId,
|
|
140
|
+
timestamp: new Date(this.now()).toISOString(),
|
|
141
|
+
identity: live.identity,
|
|
142
|
+
transition: {
|
|
143
|
+
entryId,
|
|
144
|
+
prevEntryId: fold.state.transition.entryId,
|
|
145
|
+
sequence: fold.state.transition.sequence + 1,
|
|
146
|
+
attempt: fold.attemptsSpent + (event.phase === "reducing" ? 1 : 0),
|
|
147
|
+
},
|
|
148
|
+
event,
|
|
149
|
+
};
|
|
150
|
+
await this.persist(live, [entry], false, closing);
|
|
151
|
+
if (!this.fold(live)?.validated) throw new Error("Persisted handoff transition failed validation.");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async request(note: unknown, toolCallId: string, signal?: AbortSignal): Promise<string> {
|
|
155
|
+
const validation = validateContinuityNote(note);
|
|
156
|
+
if (!validation.ok) throw new Error(`Invalid handoff note: ${validation.reason}`);
|
|
157
|
+
if (!toolCallId || this.busy || this.live)
|
|
158
|
+
throw new Error("A context handoff is already pending or no tool identity was supplied.");
|
|
159
|
+
signal?.throwIfAborted();
|
|
160
|
+
const origin = this.deps.captureOrigin();
|
|
161
|
+
if (origin.ports.isStateRemoved() || !origin.ports.isOriginCurrent())
|
|
162
|
+
throw new Error("The originating session is unavailable.");
|
|
163
|
+
if (!this.deps.admitNote(validation.accepted))
|
|
164
|
+
throw new Error("Exact handoff note and protected context exceed the replay budget.");
|
|
165
|
+
const prior = resolveContinuityProjection({
|
|
166
|
+
entries: this.deps.entries(),
|
|
167
|
+
sessionId: origin.sessionId,
|
|
168
|
+
nowMs: this.now(),
|
|
169
|
+
}).current;
|
|
170
|
+
if (prior && prior.phase !== "absent" && prior.phase !== "acknowledged")
|
|
171
|
+
throw new Error("An earlier handoff requires operator recovery.");
|
|
172
|
+
const preparedAtMs = this.now();
|
|
173
|
+
const policy = {
|
|
174
|
+
...HANDOFF_POLICY_LIMITS,
|
|
175
|
+
preparedAtMs,
|
|
176
|
+
automaticDeadlineAtMs: preparedAtMs + HANDOFF_MAX_WINDOW_MS,
|
|
177
|
+
};
|
|
178
|
+
const identity: HandoffIdentity = {
|
|
179
|
+
handoffId: this.id(),
|
|
180
|
+
preparedEntryId: this.id(),
|
|
181
|
+
commitId: this.id(),
|
|
182
|
+
commitEntryId: this.id(),
|
|
183
|
+
originSessionId: origin.sessionId,
|
|
184
|
+
branchAnchorTurnId: origin.leafTurnId,
|
|
185
|
+
initiatingTurnId: origin.initiatingTurnId,
|
|
186
|
+
toolCallId,
|
|
187
|
+
sourceRevision: origin.sourceRevision,
|
|
188
|
+
};
|
|
189
|
+
const live: LiveHandoff = {
|
|
190
|
+
origin,
|
|
191
|
+
identity,
|
|
192
|
+
accepted: validation.accepted,
|
|
193
|
+
policy,
|
|
194
|
+
deadline: policy.automaticDeadlineAtMs,
|
|
195
|
+
abort: new AbortController(),
|
|
196
|
+
uncertain: false,
|
|
197
|
+
};
|
|
198
|
+
this.live = live;
|
|
199
|
+
this.armDeadline(live);
|
|
200
|
+
const entry: HandoffTransactionEntry = {
|
|
201
|
+
kind: "handoffTransaction",
|
|
202
|
+
schemaVersion: 1,
|
|
203
|
+
turnId: identity.preparedEntryId,
|
|
204
|
+
parentTurnId: identity.branchAnchorTurnId,
|
|
205
|
+
timestamp: new Date(preparedAtMs).toISOString(),
|
|
206
|
+
identity,
|
|
207
|
+
transition: { entryId: identity.preparedEntryId, prevEntryId: null, sequence: 0, attempt: 0 },
|
|
208
|
+
event: { phase: "prepared", accepted: live.accepted, policy },
|
|
209
|
+
};
|
|
210
|
+
try {
|
|
211
|
+
await this.persist(live, [entry]);
|
|
212
|
+
signal?.throwIfAborted();
|
|
213
|
+
this.check(live);
|
|
214
|
+
return `Context handoff ${identity.handoffId} prepared. The exact note is saved. Reduction and continuation follow after this tool receipt is persisted.`;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
await this.pause();
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private readyPayload(
|
|
222
|
+
live: LiveHandoff,
|
|
223
|
+
outcome: "summarized" | "evicted" | "continuity_only",
|
|
224
|
+
before: number,
|
|
225
|
+
after: number,
|
|
226
|
+
ref?: string,
|
|
227
|
+
): ContinuityCheckpointPayload {
|
|
228
|
+
this.check(live);
|
|
229
|
+
const fold = this.fold(live);
|
|
230
|
+
if (!fold?.validated || fold.phase !== "reducing" || !fold.state)
|
|
231
|
+
throw new Error("No durable reduction attempt owns this checkpoint.");
|
|
232
|
+
const transition = {
|
|
233
|
+
entryId: live.identity.commitEntryId,
|
|
234
|
+
prevEntryId: fold.state.transition.entryId,
|
|
235
|
+
sequence: fold.state.transition.sequence + 1,
|
|
236
|
+
attempt: fold.attemptsSpent,
|
|
237
|
+
};
|
|
238
|
+
return {
|
|
239
|
+
schemaVersion: 1,
|
|
240
|
+
identity: live.identity,
|
|
241
|
+
accepted: live.accepted,
|
|
242
|
+
policy: live.policy,
|
|
243
|
+
commit: {
|
|
244
|
+
outcome,
|
|
245
|
+
entry: {
|
|
246
|
+
turnId: live.identity.commitEntryId,
|
|
247
|
+
parentTurnId: live.identity.branchAnchorTurnId,
|
|
248
|
+
timestamp: new Date(this.now()).toISOString(),
|
|
249
|
+
},
|
|
250
|
+
transition,
|
|
251
|
+
tokensBefore: before,
|
|
252
|
+
tokensAfter: after,
|
|
253
|
+
...(outcome === "summarized" ? { summaryRef: ref ?? "" } : outcome === "evicted" ? { evictionRef: ref ?? "" } : {}),
|
|
254
|
+
},
|
|
255
|
+
state: { transition, event: { phase: "ready" }, activeResume: fold.state.activeResume, delivery: null },
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Called only at the settled engine boundary, after awaited result persistence. */
|
|
260
|
+
async settle(signal?: AbortSignal, recovery = false): Promise<boolean> {
|
|
261
|
+
const live = this.live;
|
|
262
|
+
if (!live || this.busy || live.deliveryId) return false;
|
|
263
|
+
this.busy = true;
|
|
264
|
+
const onAbort = () => live.abort.abort(signal?.reason);
|
|
265
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
266
|
+
try {
|
|
267
|
+
if (signal?.aborted) onAbort();
|
|
268
|
+
this.check(live);
|
|
269
|
+
const fold = this.fold(live);
|
|
270
|
+
if (!fold?.validated) throw new Error("Handoff evidence is unresolved.");
|
|
271
|
+
if (!recovery) {
|
|
272
|
+
const entries = this.deps.entries();
|
|
273
|
+
const preparedIndex = entries.findIndex((entry) => entry.turnId === live.identity.preparedEntryId);
|
|
274
|
+
const receipt =
|
|
275
|
+
preparedIndex >= 0 &&
|
|
276
|
+
entries.slice(preparedIndex + 1).some((entry) => {
|
|
277
|
+
if (entry.kind !== "message" || entry.role !== "tool_result") return false;
|
|
278
|
+
const payload = entry.payload as { toolCallId?: string; toolName?: string; isError?: boolean };
|
|
279
|
+
return (
|
|
280
|
+
entry.parentTurnId === live.origin.leafTurnId &&
|
|
281
|
+
payload.toolCallId === live.identity.toolCallId &&
|
|
282
|
+
payload.toolName === "self_compact" &&
|
|
283
|
+
payload.isError === false
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
if (!receipt) throw new Error("Matching successful handoff receipt has not been persisted.");
|
|
287
|
+
}
|
|
288
|
+
await this.persist(live, []);
|
|
289
|
+
this.check(live);
|
|
290
|
+
if (!fold.commit) {
|
|
291
|
+
if (fold.attemptsSpent >= live.policy.maxAttempts) throw new Error("Handoff reduction attempts exhausted.");
|
|
292
|
+
const resumeRef = fold.state?.activeResume?.entryId;
|
|
293
|
+
await this.transition(live, { phase: "reducing", ...(resumeRef ? { resumeRef } : {}) });
|
|
294
|
+
const before = this.deps.inputTokens();
|
|
295
|
+
const priorIds = new Set(this.deps.entries().map((entry) => entry.turnId));
|
|
296
|
+
let payload: ContinuityCheckpointPayload | undefined;
|
|
297
|
+
let calls = 0;
|
|
298
|
+
await this.deps.reduce(
|
|
299
|
+
{
|
|
300
|
+
beforeSummaryCall: () => {
|
|
301
|
+
this.check(live);
|
|
302
|
+
if (++calls > live.policy.maxSummaryCallsPerAttempt)
|
|
303
|
+
throw new Error("Handoff summary invocation limit reached.");
|
|
304
|
+
},
|
|
305
|
+
checkpointForSummary: (ref, tokensBefore, tokensAfter) => {
|
|
306
|
+
payload = this.readyPayload(live, "summarized", tokensBefore, tokensAfter, ref);
|
|
307
|
+
return payload;
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
live.abort.signal,
|
|
311
|
+
);
|
|
312
|
+
this.check(live);
|
|
313
|
+
if (!payload) {
|
|
314
|
+
this.deps.installReplay();
|
|
315
|
+
if (!this.deps.fits()) throw new Error("Context remains unsafe after reduction; the exact note is retained.");
|
|
316
|
+
const eviction = [...this.deps.entries()]
|
|
317
|
+
.reverse()
|
|
318
|
+
.find((entry) => entry.kind === "contextEviction" && !priorIds.has(entry.turnId));
|
|
319
|
+
payload = this.readyPayload(
|
|
320
|
+
live,
|
|
321
|
+
eviction ? "evicted" : "continuity_only",
|
|
322
|
+
before,
|
|
323
|
+
this.deps.inputTokens(),
|
|
324
|
+
eviction?.turnId,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
await this.persist(
|
|
328
|
+
live,
|
|
329
|
+
[
|
|
330
|
+
{
|
|
331
|
+
kind: "continuityCommit",
|
|
332
|
+
...payload.commit.entry,
|
|
333
|
+
continuity: { ...payload, state: { ...payload.state, activeResume: null } },
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
true,
|
|
337
|
+
);
|
|
338
|
+
this.check(live);
|
|
339
|
+
if (!this.fold(live)?.validated) throw new Error("Handoff checkpoint cannot be validated.");
|
|
340
|
+
this.deps.onCommit?.(live.identity.commitId, payload.commit.outcome);
|
|
341
|
+
}
|
|
342
|
+
this.deps.installReplay();
|
|
343
|
+
if (!this.deps.fits()) throw new Error("Compacted continuation exceeds the request budget.");
|
|
344
|
+
this.check(live);
|
|
345
|
+
const continuationTurnId = this.deps.leaf();
|
|
346
|
+
if (!continuationTurnId) throw new Error("Continuation has no persisted message anchor.");
|
|
347
|
+
const deliveryId = this.id();
|
|
348
|
+
const deliveryFold = this.fold(live);
|
|
349
|
+
const resumeRef = deliveryFold?.phase === "resumed" ? deliveryFold.state?.activeResume?.entryId : undefined;
|
|
350
|
+
await this.transition(live, {
|
|
351
|
+
phase: "delivered",
|
|
352
|
+
deliveryId,
|
|
353
|
+
continuationTurnId,
|
|
354
|
+
...(resumeRef ? { resumeRef } : {}),
|
|
355
|
+
});
|
|
356
|
+
this.check(live);
|
|
357
|
+
live.deliveryId = deliveryId;
|
|
358
|
+
return true;
|
|
359
|
+
} catch (error) {
|
|
360
|
+
await this.pause();
|
|
361
|
+
throw error;
|
|
362
|
+
} finally {
|
|
363
|
+
signal?.removeEventListener("abort", onAbort);
|
|
364
|
+
this.busy = false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** Synchronous final provider boundary, including steering drained after prepare. */
|
|
369
|
+
admission(): { block: false; correlationId?: string } | { block: true; reason: string } {
|
|
370
|
+
const live = this.live;
|
|
371
|
+
if (!live) return { block: false };
|
|
372
|
+
try {
|
|
373
|
+
this.check(live);
|
|
374
|
+
if (!live.deliveryId) throw new Error("Context handoff has not durably reached delivery.");
|
|
375
|
+
return { block: false, correlationId: live.deliveryId };
|
|
376
|
+
} catch (error) {
|
|
377
|
+
return { block: true, reason: error instanceof Error ? error.message : String(error) };
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async response(entryId: string | undefined, correlationId: string | undefined): Promise<void> {
|
|
382
|
+
const live = this.live;
|
|
383
|
+
if (
|
|
384
|
+
!live ||
|
|
385
|
+
!entryId ||
|
|
386
|
+
!correlationId ||
|
|
387
|
+
correlationId !== live.deliveryId ||
|
|
388
|
+
live.abort.signal.aborted ||
|
|
389
|
+
!live.origin.ports.isOriginCurrent()
|
|
390
|
+
)
|
|
391
|
+
return;
|
|
392
|
+
this.check(live);
|
|
393
|
+
const evidence = resolveContinuityEvidence({
|
|
394
|
+
entries: this.deps.entries(),
|
|
395
|
+
unreadableRecords: 0,
|
|
396
|
+
}).terminalResponses.find((row) => row.entryId === entryId);
|
|
397
|
+
if (evidence?.status !== "success") return;
|
|
398
|
+
await this.transition(live, { phase: "acknowledged", deliveryId: correlationId, terminalResponseEntryId: entryId });
|
|
399
|
+
if (live.deadlineTimer) clearTimeout(live.deadlineTimer);
|
|
400
|
+
this.live = null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
cancel(): void {
|
|
404
|
+
this.live?.abort.abort(new Error("Operator cancelled the context handoff."));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async pause(): Promise<void> {
|
|
408
|
+
const live = this.live;
|
|
409
|
+
if (!live) return;
|
|
410
|
+
live.abort.abort();
|
|
411
|
+
if (live.deadlineTimer) clearTimeout(live.deadlineTimer);
|
|
412
|
+
try {
|
|
413
|
+
const fold = this.fold(live);
|
|
414
|
+
if (
|
|
415
|
+
!live.uncertain &&
|
|
416
|
+
fold?.validated &&
|
|
417
|
+
["prepared", "reducing", "ready", "delivered", "resumed"].includes(fold.phase)
|
|
418
|
+
) {
|
|
419
|
+
await this.transition(
|
|
420
|
+
live,
|
|
421
|
+
{ phase: "paused", reason: live.deliveryId ? "delivery_uncertain" : "operator_cancelled" },
|
|
422
|
+
true,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
} catch (error) {
|
|
426
|
+
this.deps.notice(`Handoff ${live.identity.handoffId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
427
|
+
} finally {
|
|
428
|
+
if (this.live === live) this.live = null;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Host-only operator action; never reachable through tool arguments. */
|
|
433
|
+
async recover(handoffId: string, action: "reduce" | "deliver"): Promise<void> {
|
|
434
|
+
if (this.live || this.busy) throw new Error("A handoff is already active.");
|
|
435
|
+
const origin = this.deps.captureOrigin();
|
|
436
|
+
let fold = resolveContinuityProjection({
|
|
437
|
+
entries: this.deps.entries(),
|
|
438
|
+
sessionId: origin.sessionId,
|
|
439
|
+
nowMs: this.now(),
|
|
440
|
+
}).current;
|
|
441
|
+
if (!fold?.validated || fold.identity?.handoffId !== handoffId || !fold.accepted || !fold.policy || !fold.state)
|
|
442
|
+
throw new Error("No validated handoff matches that identity on this branch.");
|
|
443
|
+
if (["prepared", "reducing", "ready", "delivered", "resumed"].includes(fold.phase)) {
|
|
444
|
+
// A restarted process has no live delivery lease. Record that fact
|
|
445
|
+
// before accepting this explicit operator request; never auto-redeliver.
|
|
446
|
+
this.live = {
|
|
447
|
+
origin,
|
|
448
|
+
identity: fold.identity,
|
|
449
|
+
accepted: fold.accepted,
|
|
450
|
+
policy: fold.policy,
|
|
451
|
+
deadline: this.now() + HANDOFF_MAX_WINDOW_MS,
|
|
452
|
+
abort: new AbortController(),
|
|
453
|
+
uncertain: false,
|
|
454
|
+
};
|
|
455
|
+
if (fold.missingCommit) {
|
|
456
|
+
const expected = { ...fold.missingCommit.entry };
|
|
457
|
+
const readback = origin.ports.readExact(expected);
|
|
458
|
+
if (readback.status !== "absent" && readback.status !== "matching") {
|
|
459
|
+
this.live = null;
|
|
460
|
+
throw new Error("Commit reconstruction is unresolved.");
|
|
461
|
+
}
|
|
462
|
+
await this.persist(this.live, readback.status === "absent" ? [expected] : [], true);
|
|
463
|
+
}
|
|
464
|
+
await this.pause();
|
|
465
|
+
fold = resolveContinuityProjection({
|
|
466
|
+
entries: this.deps.entries(),
|
|
467
|
+
sessionId: origin.sessionId,
|
|
468
|
+
nowMs: this.now(),
|
|
469
|
+
}).current;
|
|
470
|
+
if (!fold?.validated || !fold.identity || !fold.accepted || !fold.policy || !fold.state)
|
|
471
|
+
throw new Error("Interrupted handoff could not be paused durably.");
|
|
472
|
+
}
|
|
473
|
+
if (!["paused", "failed"].includes(fold.phase))
|
|
474
|
+
throw new Error("Only a paused or failed handoff accepts operator recovery.");
|
|
475
|
+
if (
|
|
476
|
+
action === "reduce" ? fold.commit !== null || fold.attemptsSpent >= fold.policy.maxAttempts : fold.commit === null
|
|
477
|
+
)
|
|
478
|
+
throw new Error("That recovery action is unavailable for this handoff.");
|
|
479
|
+
const now = this.now();
|
|
480
|
+
const live: LiveHandoff = {
|
|
481
|
+
origin,
|
|
482
|
+
identity: fold.identity,
|
|
483
|
+
accepted: fold.accepted,
|
|
484
|
+
policy: fold.policy,
|
|
485
|
+
deadline: now + HANDOFF_MAX_WINDOW_MS,
|
|
486
|
+
abort: new AbortController(),
|
|
487
|
+
uncertain: false,
|
|
488
|
+
};
|
|
489
|
+
this.live = live;
|
|
490
|
+
this.armDeadline(live);
|
|
491
|
+
const request: HandoffRecoveryRequestEntry = {
|
|
492
|
+
kind: "custom",
|
|
493
|
+
customType: HANDOFF_RECOVERY_REQUEST_CUSTOM_TYPE,
|
|
494
|
+
turnId: this.id(),
|
|
495
|
+
parentTurnId: origin.leafTurnId,
|
|
496
|
+
timestamp: new Date(now).toISOString(),
|
|
497
|
+
display: false,
|
|
498
|
+
data: {
|
|
499
|
+
version: 1,
|
|
500
|
+
requestKind: "handoff_recovery",
|
|
501
|
+
handoffId,
|
|
502
|
+
action,
|
|
503
|
+
sessionId: origin.sessionId,
|
|
504
|
+
branchAnchorTurnId: fold.identity.branchAnchorTurnId,
|
|
505
|
+
selectedLeafTurnId: origin.leafTurnId,
|
|
506
|
+
pausedOrFailedEntryId: fold.state.transition.entryId,
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
try {
|
|
510
|
+
await this.persist(live, [request]);
|
|
511
|
+
await this.transition(live, {
|
|
512
|
+
phase: "resumed",
|
|
513
|
+
authority: {
|
|
514
|
+
operatorRequestEntryId: request.turnId,
|
|
515
|
+
pausedOrFailedEntryId: fold.state.transition.entryId,
|
|
516
|
+
action,
|
|
517
|
+
automaticDeadlineAtMs: live.deadline,
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
await this.settle(undefined, true);
|
|
521
|
+
} catch (error) {
|
|
522
|
+
await this.pause();
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
@@ -23,12 +23,14 @@ import {
|
|
|
23
23
|
} from "../domains/observability/index.js";
|
|
24
24
|
import type { WorkerAction, WorkerProgressSnapshot } from "../domains/observability/worker-progress.js";
|
|
25
25
|
import { type CostProvenance, foregroundStreamUsage } from "../domains/providers/index.js";
|
|
26
|
+
import type { UsageSnapshot } from "../domains/quota/types.js";
|
|
26
27
|
import { sanitizeCallTargetText } from "../domains/safety/call-target.js";
|
|
27
28
|
import { type Component, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../engine/tui.js";
|
|
28
29
|
import { formatWorkerContextMeter } from "./context-meter.js";
|
|
29
30
|
import { COUNCIL_SYNTHESIS_LABEL } from "./council.js";
|
|
30
31
|
import { type CouncilGroupView, type CouncilMemberView, councilGroupBody, councilIslandLines } from "./council-grid.js";
|
|
31
32
|
import { formatFooterTokens } from "./footer-panel.js";
|
|
33
|
+
import { routeWeeklyQuota } from "./quota-view.js";
|
|
32
34
|
import { presentWorkerContractAnswer, safeWorkerAnswerText } from "./renderers/worker-answer.js";
|
|
33
35
|
import {
|
|
34
36
|
type ClioTheme,
|
|
@@ -780,8 +782,9 @@ function renderDispatchCard(
|
|
|
780
782
|
return frame(theme, cardTitle, bodyLines, width, { rightMeta: elapsed });
|
|
781
783
|
}
|
|
782
784
|
|
|
783
|
-
function renderTaskIslandRow(row: DispatchBoardRow, width: number): string[] {
|
|
785
|
+
function renderTaskIslandRow(row: DispatchBoardRow, width: number, quota: ReadonlyArray<UsageSnapshot>): string[] {
|
|
784
786
|
const theme = clioTheme();
|
|
787
|
+
const weekly = routeWeeklyQuota(row, quota);
|
|
785
788
|
const agentLabel = agentDisplayLabel(row);
|
|
786
789
|
const elapsed = formatCompactMs(row.elapsedMs);
|
|
787
790
|
const cost = formatCostAggregate(costAggregateForAmount(row.costUsd, row.costProvenance)) ?? COST_NOT_MEASURED;
|
|
@@ -854,6 +857,17 @@ function renderTaskIslandRow(row: DispatchBoardRow, width: number): string[] {
|
|
|
854
857
|
...quality.map((line) => padAnsi(theme.fg("muted", line), width)),
|
|
855
858
|
...task.map((line) => padAnsi(line, width)),
|
|
856
859
|
padAnsi(telemetry, width),
|
|
860
|
+
...(weekly
|
|
861
|
+
? [
|
|
862
|
+
padAnsi(
|
|
863
|
+
theme.fg(
|
|
864
|
+
weekly.severity === "critical" ? "error" : weekly.severity === "normal" ? "dim" : "warning",
|
|
865
|
+
` Shared account · ${weekly.label}`,
|
|
866
|
+
),
|
|
867
|
+
width,
|
|
868
|
+
),
|
|
869
|
+
]
|
|
870
|
+
: []),
|
|
857
871
|
...wrapTextWithAnsi(theme.fg("dim", ` /view dispatch:${row.runId}`), width),
|
|
858
872
|
];
|
|
859
873
|
}
|
|
@@ -1110,7 +1124,11 @@ export function createDispatchBoardView(
|
|
|
1110
1124
|
};
|
|
1111
1125
|
}
|
|
1112
1126
|
|
|
1113
|
-
export function formatTaskIslandLines(
|
|
1127
|
+
export function formatTaskIslandLines(
|
|
1128
|
+
rows: ReadonlyArray<DispatchBoardRow>,
|
|
1129
|
+
maxRows = 4,
|
|
1130
|
+
quota: ReadonlyArray<UsageSnapshot> = [],
|
|
1131
|
+
): string[] {
|
|
1114
1132
|
// Councils are folded before the row cap, so a five-member council costs the
|
|
1115
1133
|
// island one card and never crowds out the runs beside it.
|
|
1116
1134
|
const items = dispatchBoardItems(rows.filter((row) => !isHelperRun(row)));
|
|
@@ -1131,7 +1149,7 @@ export function formatTaskIslandLines(rows: ReadonlyArray<DispatchBoardRow>, max
|
|
|
1131
1149
|
body.push(
|
|
1132
1150
|
...(item.kind === "council"
|
|
1133
1151
|
? councilIslandLines(clioTheme(), item.group, TASK_ISLAND_WIDTH)
|
|
1134
|
-
: renderTaskIslandRow(item.row, TASK_ISLAND_WIDTH)),
|
|
1152
|
+
: renderTaskIslandRow(item.row, TASK_ISLAND_WIDTH, quota)),
|
|
1135
1153
|
);
|
|
1136
1154
|
}
|
|
1137
1155
|
const hidden = items.length - visibleItems.length;
|