@iowarp/clio-coder 0.5.1 → 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 +24 -0
- package/CONTRIBUTING.md +9 -0
- package/README.md +3 -3
- package/ROADMAP.md +30 -5
- package/dist/{acp-X3N7JDGS.js → acp-F6PGOFZ4.js} +2 -2
- package/dist/{agents-UYGSMAWR.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-DGMY2DWE.js → chunk-23UIXH6Z.js} +3 -3
- package/dist/{chunk-QTYRMBW6.js → chunk-36XVK5P5.js} +5 -5
- package/dist/{chunk-BIQLF3NZ.js → chunk-3DZ4LPLS.js} +6 -6
- package/dist/{chunk-7VNLPEUG.js → chunk-3GSPCWFX.js} +12 -12
- package/dist/{chunk-CRHPCPZP.js → chunk-4KC4CHKJ.js} +2 -2
- package/dist/{chunk-M5FPIZQW.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-A7G7OUND.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-GW2KVAVI.js → chunk-AA3GRZWS.js} +2 -2
- package/dist/{chunk-6B76U65H.js → chunk-ADO3OQIC.js} +3 -3
- 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-AKQ3N3L4.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-7KI4ZSEB.js → chunk-ELDJWJKM.js} +97 -14
- package/dist/{chunk-GCQFN7YI.js → chunk-EMYEQLWO.js} +2 -2
- package/dist/{chunk-7FHSA7VB.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-GGEEG7CD.js → chunk-FOSRJ2VZ.js} +4 -4
- package/dist/{chunk-ZARZ4POR.js → chunk-G5ZDQQE3.js} +3 -3
- package/dist/{chunk-QM7HFPQQ.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-WAISUJNQ.js → chunk-KWMFC7FN.js} +3 -3
- package/dist/{chunk-4D7QM7S5.js → chunk-LXYHCCIZ.js} +9 -9
- package/dist/{chunk-NFSK2VNU.js → chunk-LZMTVVN4.js} +2 -2
- package/dist/{chunk-ZNTGRDFA.js → chunk-M5PMRZSR.js} +123 -37
- 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-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-VWKR2NN5.js → chunk-PF36KOGI.js} +290 -111
- package/dist/{chunk-LFYA3HYH.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-VMTCZ2GR.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-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-OXWRE2QJ.js → chunk-VKMSEO7Y.js} +4 -4
- package/dist/{chunk-FBXTHPNZ.js → chunk-VLX5VZ35.js} +5 -5
- package/dist/{chunk-KIAUZGKF.js → chunk-WAGBMMNX.js} +5 -5
- package/dist/{chunk-BFOSV5EZ.js → chunk-WX2YCH7F.js} +2 -2
- package/dist/{chunk-SAL2SBP4.js → chunk-XBIGUILU.js} +25 -10
- package/dist/{chunk-XOH3SD7H.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 +25 -25
- package/dist/{clio-Z3JTEL2I.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-QYIRZHI7.js → config-343YS25T.js} +42 -40
- package/dist/{config-graph-TS3SGGQW.js → config-graph-WI263KDX.js} +42 -40
- package/dist/{configure-OX7KVJTQ.js → configure-GZDG4HER.js} +21 -21
- package/dist/{context-2BNF6HTP.js → context-4OFO7N7Y.js} +12 -12
- package/dist/{context-GTMUGAR4.js → context-JLK2RLOG.js} +41 -39
- package/dist/{context-NNLBES2Y.js → context-O6DIWCF6.js} +23 -21
- package/dist/{context-clear-FVJQBBL7.js → context-clear-MR7KOGQI.js} +41 -39
- package/dist/{context-working-set-MOE72SD6.js → context-working-set-YZAD3KWO.js} +14 -13
- package/dist/{data-tool-JJPBGMQJ.js → data-tool-EKD7DFHG.js} +7 -7
- package/dist/{detail-QZM3QAAU.js → detail-YZPHFEVJ.js} +42 -40
- package/dist/{dispatch-runner-2KSQO5FU.js → dispatch-runner-VKVXVTWJ.js} +43 -41
- package/dist/{doctor-2U6YR5NH.js → doctor-53AVQXAO.js} +16 -15
- package/dist/{doctor-deep-2EJQSHIZ.js → doctor-deep-BLRB2NCG.js} +5 -5
- package/dist/{eval-ZKK77JWZ.js → eval-P3BQSLQE.js} +21 -20
- package/dist/{evidence-POSEQW4Q.js → evidence-6LLI6SZH.js} +43 -41
- package/dist/{evidence-FMP7KZZ2.js → evidence-HTN3JLUO.js} +41 -39
- package/dist/{evidence-PJTOE7JA.js → evidence-NJRE3R5H.js} +41 -39
- package/dist/{evolve-JT6PAMZE.js → evolve-WWZHJVJP.js} +41 -39
- package/dist/{fleet-4XR2ZEOF.js → fleet-GGZ4BVJE.js} +63 -61
- package/dist/{fleet-6JJKNIMV.js → fleet-QG3IIWWM.js} +43 -41
- 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-SWJMFVDP.js → fleet-inspect-KOLKBC56.js} +43 -41
- package/dist/{fleet-preflight-KSIHWQAU.js → fleet-preflight-44UNCC4Y.js} +26 -24
- package/dist/{fleet-validate-7NLAE26R.js → fleet-validate-K2FXRZVA.js} +13 -13
- package/dist/{fleet-verify-QM6XU2KL.js → fleet-verify-AS444WDW.js} +41 -39
- package/dist/{fleet-view-RLKFHFCM.js → fleet-view-X4JU4J6O.js} +42 -40
- package/dist/gui/ops-worker.js +9 -9
- package/dist/gui/reads-worker.js +9 -8
- package/dist/{init-VBUPHABR.js → init-VSOWFHVL.js} +53 -51
- package/dist/{interactive-JCVBPVYT.js → interactive-JRCBENLZ.js} +154 -92
- package/dist/{interop-J7UOBOSF.js → interop-JR6BCY7R.js} +10 -10
- package/dist/{inventory-54G7FSVY.js → inventory-3JUGLVJI.js} +42 -40
- 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/{memory-5BPQSSOP.js → memory-Q2YVOKK5.js} +41 -39
- package/dist/{models-R6HILX6X.js → models-M7BOCW7F.js} +17 -16
- package/dist/{monitor-HVRKSFEQ.js → monitor-2HD3MC67.js} +46 -44
- package/dist/{orchestrator-OR7V4YWK.js → orchestrator-OUW3ZN6Y.js} +1890 -222
- package/dist/{panes-A743ONZ5.js → panes-TTPPSYCL.js} +3 -3
- package/dist/{preload-KLXFOX2F.js → preload-A47C2NUF.js} +41 -39
- package/dist/{providers-CMETPZNS.js → providers-T43W7EL4.js} +4 -4
- package/dist/{resources-FY6XYFN2.js → resources-TP6X2V6W.js} +20 -11
- package/dist/{run-UIDRUYAY.js → run-LDSBPMCS.js} +60 -58
- package/dist/{share-CAFMMQLM.js → share-URHKKMHR.js} +9 -9
- package/dist/{skills-ERKQUC7L.js → skills-E477MOOO.js} +13 -11
- package/dist/{skills-eval-JDAXOPWC.js → skills-eval-AIBR2L7H.js} +582 -92
- package/dist/{skills-inventory-TVUYJDVN.js → skills-inventory-HLMKY3R5.js} +13 -11
- package/dist/{slash-commands-MNKQ5QP3.js → slash-commands-LIIZKA5Y.js} +26 -24
- package/dist/{startup-background-PJMWTKVO.js → startup-background-JR2KDK6R.js} +42 -40
- package/dist/{steer-KKYXJSH4.js → steer-D7RTAPCM.js} +3 -3
- package/dist/{system-IVVZRKGI.js → system-X2P5N4IY.js} +14 -13
- package/dist/{targets-RFRUMCQ5.js → targets-VFFAKUFM.js} +25 -24
- package/dist/{tasks-6L2JJRC5.js → tasks-ERAWZA22.js} +6 -6
- package/dist/{trace-LA5MVR4Y.js → trace-MA5BT7XY.js} +3 -3
- package/dist/{usage-LA33SG3R.js → usage-WYFY2L6M.js} +49 -47
- 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-HL73TICA.js → wiki-generate-OFKOX2T6.js} +52 -50
- package/dist/worker/entry.js +30 -28
- package/docs/architecture/context-engine.md +2 -0
- package/docs/guide/context-continuity.md +49 -0
- package/docs/guide/proactive-memory.md +3 -0
- 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/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/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/prompts/compiler.ts +1 -1
- package/src/domains/prompts/extension.ts +38 -1
- 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/engine/agent.ts +133 -9
- package/src/engine/session.ts +13 -6
- package/src/entry/orchestrator.ts +114 -31
- package/src/interactive/chat-loop.ts +216 -60
- 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/footer/dashboard.ts +12 -1
- package/src/interactive/footer/pages.ts +15 -7
- package/src/interactive/footer/widgets.ts +32 -7
- package/src/interactive/interactive-application.ts +2 -0
- package/src/interactive/interactive-slash-runtime.ts +43 -6
- package/src/interactive/model-session-replay.ts +130 -3
- package/src/interactive/output-reserve.ts +35 -0
- package/src/interactive/overlay-session-lifecycle.ts +18 -11
- package/src/interactive/session-transcript.ts +2 -1
- package/src/interactive/slash-commands.ts +20 -1
- package/src/interactive/turn-context.ts +685 -62
- package/src/interactive/turn-middleware.ts +39 -6
- package/src/interactive/turn-persistence.ts +15 -2
- package/src/interactive/turn-runtime.ts +48 -5
- 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-3OODBJ45.js +0 -1325
- package/dist/chunk-CTFPFW3H.js +0 -44
- package/dist/chunk-I4Y4WKZR.js +0 -386
|
@@ -22,6 +22,7 @@ import { extractText, hasStructuredToolCall, toolNamesFromAgentState } from "./c
|
|
|
22
22
|
import type { AgentRuntime, ChatTurnState } from "./turn-state.js";
|
|
23
23
|
|
|
24
24
|
export interface TurnMiddlewareDeps {
|
|
25
|
+
memoryContentGuard?: (() => () => boolean) | undefined;
|
|
25
26
|
state: ChatTurnState;
|
|
26
27
|
middleware?: MiddlewareContract | undefined;
|
|
27
28
|
session?: SessionContract | undefined;
|
|
@@ -50,6 +51,7 @@ export interface TurnMiddleware {
|
|
|
50
51
|
tokensBefore?: number,
|
|
51
52
|
): void;
|
|
52
53
|
flushPendingReminders(): string;
|
|
54
|
+
takePendingReminderProjection(): () => string;
|
|
53
55
|
clearPendingReminders(): void;
|
|
54
56
|
/**
|
|
55
57
|
* Deliver a reminder produced after its own turn boundary already closed.
|
|
@@ -57,7 +59,7 @@ export interface TurnMiddleware {
|
|
|
57
59
|
* value; the reminder joins the same buffer, ledger, and transcript path a
|
|
58
60
|
* turn_end reminder takes.
|
|
59
61
|
*/
|
|
60
|
-
injectDeferredReminder(message: string, severity?: MiddlewareReminderSeverity): void;
|
|
62
|
+
injectDeferredReminder(message: string, severity?: MiddlewareReminderSeverity, isCurrent?: () => boolean): void;
|
|
61
63
|
/**
|
|
62
64
|
* Deliver an operator-facing finding produced after its turn boundary closed.
|
|
63
65
|
* The watchdog seam: its run settles well after the turn it reviewed, and
|
|
@@ -76,11 +78,29 @@ export function createTurnMiddleware(deps: TurnMiddlewareDeps): TurnMiddleware {
|
|
|
76
78
|
// injections). The next accepted prompt flushes them into the model
|
|
77
79
|
// request as one system-reminder block. Completed background reminders
|
|
78
80
|
// can also drain at a native tool-batch boundary; session switches clear all.
|
|
79
|
-
const pendingReminders: Array<{
|
|
81
|
+
const pendingReminders: Array<{
|
|
82
|
+
message: string;
|
|
83
|
+
severity: MiddlewareReminderSeverity;
|
|
84
|
+
deferred: boolean;
|
|
85
|
+
isCurrent?: (() => boolean) | undefined;
|
|
86
|
+
}> = [];
|
|
87
|
+
const dropStale = () => {
|
|
88
|
+
for (let index = pendingReminders.length - 1; index >= 0; index--) {
|
|
89
|
+
if (pendingReminders[index]?.isCurrent?.() === false) pendingReminders.splice(index, 1);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
80
92
|
|
|
81
|
-
const bufferReminder = (
|
|
93
|
+
const bufferReminder = (
|
|
94
|
+
message: string,
|
|
95
|
+
severity: MiddlewareReminderSeverity,
|
|
96
|
+
deferred = false,
|
|
97
|
+
isCurrent?: () => boolean,
|
|
98
|
+
): void => {
|
|
99
|
+
dropStale();
|
|
100
|
+
isCurrent ??= message.startsWith("Memory:") ? deps.memoryContentGuard?.() : undefined;
|
|
101
|
+
if (isCurrent?.() === false) return;
|
|
82
102
|
if (pendingReminders.some((entry) => entry.message === message && entry.severity === severity)) return;
|
|
83
|
-
pendingReminders.push({ message, severity, deferred });
|
|
103
|
+
pendingReminders.push({ message, severity, deferred, isCurrent });
|
|
84
104
|
};
|
|
85
105
|
|
|
86
106
|
const runMiddlewareTurnHook = (input: MiddlewareHookInput): ReadonlyArray<MiddlewareEffect> => {
|
|
@@ -194,6 +214,7 @@ export function createTurnMiddleware(deps: TurnMiddlewareDeps): TurnMiddleware {
|
|
|
194
214
|
[],
|
|
195
215
|
);
|
|
196
216
|
if (!current()) return false;
|
|
217
|
+
dropStale();
|
|
197
218
|
const ready = pendingReminders.filter((entry) => entry.deferred);
|
|
198
219
|
if (ready.length === 0) return false;
|
|
199
220
|
const content = `<system-reminder>\n${[...new Set(ready.map((entry) => entry.message))].join("\n\n")}\n</system-reminder>`;
|
|
@@ -314,7 +335,17 @@ export function createTurnMiddleware(deps: TurnMiddlewareDeps): TurnMiddleware {
|
|
|
314
335
|
});
|
|
315
336
|
},
|
|
316
337
|
|
|
338
|
+
takePendingReminderProjection(): () => string {
|
|
339
|
+
const entries = pendingReminders.splice(0);
|
|
340
|
+
return () => {
|
|
341
|
+
const messages = [
|
|
342
|
+
...new Set(entries.filter((entry) => entry.isCurrent?.() !== false).map((entry) => entry.message)),
|
|
343
|
+
];
|
|
344
|
+
return messages.length ? `<system-reminder>\n${messages.join("\n\n")}\n</system-reminder>` : "";
|
|
345
|
+
};
|
|
346
|
+
},
|
|
317
347
|
flushPendingReminders(): string {
|
|
348
|
+
dropStale();
|
|
318
349
|
if (pendingReminders.length === 0) return "";
|
|
319
350
|
// Severity affects host actions, not the text delivered to the model.
|
|
320
351
|
// A paired continuation/info and reminder/warn still needs one copy.
|
|
@@ -327,14 +358,16 @@ export function createTurnMiddleware(deps: TurnMiddlewareDeps): TurnMiddleware {
|
|
|
327
358
|
pendingReminders.length = 0;
|
|
328
359
|
},
|
|
329
360
|
|
|
330
|
-
injectDeferredReminder(message, severity = "advisory"): void {
|
|
361
|
+
injectDeferredReminder(message, severity = "advisory", isCurrent): void {
|
|
362
|
+
dropStale();
|
|
363
|
+
if (isCurrent?.() === false) return;
|
|
331
364
|
const text = message.trim();
|
|
332
365
|
if (text.length === 0) return;
|
|
333
366
|
// A deferred reminder never carries hard-block authority: the turn it
|
|
334
367
|
// could have interrupted is already over.
|
|
335
368
|
const level: MiddlewareReminderSeverity = severity === "hard-block" ? "advisory" : severity;
|
|
336
369
|
if (pendingReminders.some((entry) => entry.message === text)) return;
|
|
337
|
-
bufferReminder(text, level, true);
|
|
370
|
+
bufferReminder(text, level, true, isCurrent);
|
|
338
371
|
appendMiddlewareReminderEntry(text, level);
|
|
339
372
|
deps.emitNotice(text);
|
|
340
373
|
},
|
|
@@ -60,7 +60,11 @@ export interface TurnPersistenceDeps {
|
|
|
60
60
|
export interface TurnPersistence {
|
|
61
61
|
/** True when this exact assistant message object was already persisted. */
|
|
62
62
|
wasPersisted(message: unknown): boolean;
|
|
63
|
-
appendAssistantTurn(
|
|
63
|
+
appendAssistantTurn(
|
|
64
|
+
message: AgentMessage,
|
|
65
|
+
timing?: AssistantCallTiming | null,
|
|
66
|
+
deliveryId?: string,
|
|
67
|
+
): string | undefined;
|
|
64
68
|
appendQueuedUserTurn(message: AgentMessage): void;
|
|
65
69
|
appendToolCallTurn(event: Extract<AgentEvent, { type: "tool_execution_start" }>): void;
|
|
66
70
|
appendToolResultTurn(
|
|
@@ -226,7 +230,11 @@ export function createTurnPersistence(deps: TurnPersistenceDeps): TurnPersistenc
|
|
|
226
230
|
};
|
|
227
231
|
};
|
|
228
232
|
|
|
229
|
-
const appendAssistantTurn = (
|
|
233
|
+
const appendAssistantTurn = (
|
|
234
|
+
message: AgentMessage,
|
|
235
|
+
timing?: AssistantCallTiming | null,
|
|
236
|
+
deliveryId?: string,
|
|
237
|
+
): string | undefined => {
|
|
230
238
|
if (message?.role !== "assistant") return;
|
|
231
239
|
if (persistedAssistantMessages.has(message)) return;
|
|
232
240
|
// A loop-guard interrupt already persisted a durable closing turn with the
|
|
@@ -237,6 +245,10 @@ export function createTurnPersistence(deps: TurnPersistenceDeps): TurnPersistenc
|
|
|
237
245
|
const failure = terminalFailureFromAssistantMessage(message);
|
|
238
246
|
const payload = assistantSessionPayload(message, failure);
|
|
239
247
|
if (timing) payload.timing = timing;
|
|
248
|
+
if (deliveryId) {
|
|
249
|
+
payload.continuityDeliveryId = deliveryId;
|
|
250
|
+
payload.stopReason = message.stopReason;
|
|
251
|
+
}
|
|
240
252
|
const usage = (message as { usage?: Usage }).usage;
|
|
241
253
|
if (usage && typeof usage === "object") {
|
|
242
254
|
const backendTimings = (message as { backendTimings?: BackendCompletionTimings }).backendTimings;
|
|
@@ -279,6 +291,7 @@ export function createTurnPersistence(deps: TurnPersistenceDeps): TurnPersistenc
|
|
|
279
291
|
tokens: traceUsage?.totalTokens ?? null,
|
|
280
292
|
});
|
|
281
293
|
if (!turnContinues) finishTracedTurn(failure ? "fail" : "success", failure?.errorMessage ?? null);
|
|
294
|
+
return turn.id;
|
|
282
295
|
};
|
|
283
296
|
|
|
284
297
|
return {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { requestFits } from "../domains/context/budget/request-fit.js";
|
|
2
|
+
import { estimateInputTokensFromContext } from "../engine/apis/output-budget.js";
|
|
1
3
|
import { resolvedRequestContext } from "../engine/context.js";
|
|
4
|
+
import type { ContinuityController } from "./continuity-controller.js";
|
|
5
|
+
import { resolveTurnOutputReserve } from "./output-reserve.js";
|
|
2
6
|
/**
|
|
3
7
|
* Turn runtime ownership: orchestrator target resolution, model synthesis,
|
|
4
8
|
* live-agent construction and model hot-swap, thinking-level reconciliation,
|
|
@@ -133,6 +137,8 @@ export interface TurnRuntimeDeps {
|
|
|
133
137
|
middlewareToolChoice: MiddlewareToolChoiceControl;
|
|
134
138
|
persistence: TurnPersistence;
|
|
135
139
|
context: TurnContext;
|
|
140
|
+
continuity?: ContinuityController;
|
|
141
|
+
hasQueuedSteering?: () => boolean;
|
|
136
142
|
middleware: TurnMiddleware;
|
|
137
143
|
retrySettings: () => RetrySettings;
|
|
138
144
|
/** Stable Clio session id forwarded only to gateway runtimes that understand it. */
|
|
@@ -512,6 +518,37 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
|
|
|
512
518
|
},
|
|
513
519
|
maxRetryDelayMs: deps.retrySettings().maxDelayMs,
|
|
514
520
|
...(gatewaySessionId ? { sessionId: gatewaySessionId } : {}),
|
|
521
|
+
beforeToolBatch: ({ assistantMessage }) => {
|
|
522
|
+
const calls = assistantMessage.content.filter((block) => block.type === "toolCall");
|
|
523
|
+
if (calls.some((call) => call.name === "self_compact") && calls.length !== 1) {
|
|
524
|
+
return { reason: "self_compact must be the only call in its batch; no sibling was executed." };
|
|
525
|
+
}
|
|
526
|
+
return undefined;
|
|
527
|
+
},
|
|
528
|
+
beforeStreamRequest: ({ context: request }) => {
|
|
529
|
+
setGlobalDefaultMaxOutputTokens(deps.getSettings().chat.maxOutputTokens);
|
|
530
|
+
if (state.activeInterruptReason !== null || state.runtime !== localRuntime) {
|
|
531
|
+
return { block: true, reason: "Request ownership changed before invocation." };
|
|
532
|
+
}
|
|
533
|
+
if (deps.hasQueuedSteering?.() && deps.continuity?.admission().block === false) {
|
|
534
|
+
const handoff = deps.continuity.admission();
|
|
535
|
+
if (!handoff.block && handoff.correlationId)
|
|
536
|
+
return { block: true, reason: "Operator steering arrived during handoff preparation; handoff paused." };
|
|
537
|
+
}
|
|
538
|
+
const view = context.refreshLiveBudget();
|
|
539
|
+
const resolved = resolvedRequestContext(request);
|
|
540
|
+
const actual = state.synthesisToolLock
|
|
541
|
+
? { ...resolved, systemPrompt: lockedSynthesisSystemPrompt(resolved.systemPrompt ?? "") }
|
|
542
|
+
: request;
|
|
543
|
+
const input = Math.max(estimateInputTokensFromContext(actual), view.inputTokens ?? 0);
|
|
544
|
+
const output = resolveTurnOutputReserve(localRuntime, input);
|
|
545
|
+
return requestFits(input, output, view.effectiveWindow)
|
|
546
|
+
? (deps.continuity?.admission() ?? { block: false })
|
|
547
|
+
: {
|
|
548
|
+
block: true,
|
|
549
|
+
reason: `Context window exceeded: input ${input} + output ${output}, window ${view.effectiveWindow ?? "unknown"}.`,
|
|
550
|
+
};
|
|
551
|
+
},
|
|
515
552
|
onStreamInvocation: () => {
|
|
516
553
|
setGlobalDefaultMaxOutputTokens(deps.getSettings().chat.maxOutputTokens);
|
|
517
554
|
apiCallStartedAt = performance.now();
|
|
@@ -634,19 +671,22 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
|
|
|
634
671
|
stallTimer.unref?.();
|
|
635
672
|
};
|
|
636
673
|
|
|
637
|
-
handle.agent.
|
|
674
|
+
handle.agent.prepareNextTurnWithContext = async (_completed, signal?: AbortSignal) => {
|
|
638
675
|
stallSuspendDepth += 1;
|
|
639
676
|
try {
|
|
640
677
|
if (signal?.aborted || state.activeInterruptReason !== null) return undefined;
|
|
641
|
-
|
|
678
|
+
if (deps.hasQueuedSteering?.()) await deps.continuity?.pause();
|
|
679
|
+
const handoffChanged = (await deps.continuity?.settle(signal)) ?? false;
|
|
680
|
+
const contextChanged = (await middleware.prepareToolContinuation(localRuntime, signal)) || handoffChanged;
|
|
642
681
|
const update = await context.postToolContinuationGuard(localRuntime, signal, contextChanged);
|
|
682
|
+
const restored = context.installMemoryRestoration(localRuntime);
|
|
643
683
|
if (signal?.aborted || state.activeInterruptReason !== null) return undefined;
|
|
644
684
|
// Compaction owns any replacement snapshot. Without one, publish the
|
|
645
685
|
// already-accounted reminder through context, not message events that
|
|
646
686
|
// would manufacture a new operator turn and reset task authority.
|
|
647
687
|
return (
|
|
648
|
-
update ??
|
|
649
|
-
(contextChanged
|
|
688
|
+
(!restored ? update : undefined) ??
|
|
689
|
+
(contextChanged || restored
|
|
650
690
|
? {
|
|
651
691
|
context: {
|
|
652
692
|
messages: [...localRuntime.agent.state.messages],
|
|
@@ -950,7 +990,9 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
|
|
|
950
990
|
apiMs: Math.round(Math.max(0, eventClock - apiCallStartedAt)),
|
|
951
991
|
}
|
|
952
992
|
: null;
|
|
953
|
-
|
|
993
|
+
const correlationId = event.type === "message_end" ? handle.requestCorrelationId(event.message) : undefined;
|
|
994
|
+
const persistedId = persistence.appendAssistantTurn(enrichedEvent.message, timing, correlationId);
|
|
995
|
+
await deps.continuity?.response(persistedId, correlationId);
|
|
954
996
|
if (isAssistant) apiCallStartedAt = null;
|
|
955
997
|
const usage = (enrichedEvent.message as { usage?: Usage }).usage;
|
|
956
998
|
if (isAssistant && usage && typeof usage === "object" && runFirstCallVerdict === null) {
|
|
@@ -974,6 +1016,7 @@ export function createTurnRuntime(deps: TurnRuntimeDeps): TurnRuntime {
|
|
|
974
1016
|
: null;
|
|
975
1017
|
}
|
|
976
1018
|
if (enrichedEvent.type === "agent_end") {
|
|
1019
|
+
await deps.continuity?.pause();
|
|
977
1020
|
clearStallTimer();
|
|
978
1021
|
const terminal = pendingTerminalToolResult;
|
|
979
1022
|
pendingTerminalToolResult = null;
|
package/src/tools/agent-tools.ts
CHANGED
|
@@ -497,5 +497,7 @@ export function resolveSessionTools(
|
|
|
497
497
|
supportsImages: runtime.runtimeResolution.capabilityDecisions.vision === true,
|
|
498
498
|
});
|
|
499
499
|
if (telemetry) input.telemetry = telemetry;
|
|
500
|
-
return resolveAgentTools(input)
|
|
500
|
+
return resolveAgentTools(input).filter(
|
|
501
|
+
(tool) => tool.name !== ToolNames.SelfCompact || !runtime.runtimeResolution.runtime.externalAgentLoop,
|
|
502
|
+
);
|
|
501
503
|
}
|
package/src/tools/bootstrap.ts
CHANGED
|
@@ -22,11 +22,13 @@ import { lazyTool } from "./lazy-tool.js";
|
|
|
22
22
|
import { monitorToolSurface } from "./monitor-surface.js";
|
|
23
23
|
import { panesToolSurface } from "./panes-surface.js";
|
|
24
24
|
import type { ToolRegistry } from "./registry.js";
|
|
25
|
+
import { createSelfCompactTool, type RequestSelfCompact } from "./self-compact.js";
|
|
25
26
|
import { steerToolSurface } from "./steer-surface.js";
|
|
26
27
|
|
|
27
28
|
export { toolPromptHintsForNames };
|
|
28
29
|
|
|
29
30
|
export interface ToolBootstrapDeps extends Omit<CoreToolBootstrapDeps, "mcpCapabilities"> {
|
|
31
|
+
requestSelfCompact?: RequestSelfCompact;
|
|
30
32
|
captureWorkerContext?: () => WorkerContextSnapshot | null;
|
|
31
33
|
dispatch?: DispatchContract;
|
|
32
34
|
bus?: SafeEventBus;
|
|
@@ -77,6 +79,10 @@ export function registerAllTools(registry: ToolRegistry, deps: ToolBootstrapDeps
|
|
|
77
79
|
...coreDeps,
|
|
78
80
|
...(mcpCapabilities ? { mcpCapabilities } : {}),
|
|
79
81
|
});
|
|
82
|
+
if (deps.requestSelfCompact)
|
|
83
|
+
registry.register(
|
|
84
|
+
builtin(createSelfCompactTool(deps.requestSelfCompact), { path: "src/tools/self-compact.ts", scope: "core" }),
|
|
85
|
+
);
|
|
80
86
|
if (deps.dispatch) {
|
|
81
87
|
const dispatch = deps.dispatch;
|
|
82
88
|
// Display tail only. In a composed process the dispatch domain writes the
|
|
@@ -100,6 +100,15 @@ const TOOL_METADATA: Readonly<Record<string, ToolMetadata>> = {
|
|
|
100
100
|
promptHint:
|
|
101
101
|
"Use code_nav with source=workspace (default) for project code and source=clio for Clio's shipped code map; modes: symbol, path, entries, outline, deps, dependents, wiki (workspace only).",
|
|
102
102
|
},
|
|
103
|
+
[ToolNames.SelfCompact]: {
|
|
104
|
+
objective: "Save an exact assistant handoff and reduce native session context.",
|
|
105
|
+
uiLabel: "Compact",
|
|
106
|
+
retrySafety: "not_retry_safe",
|
|
107
|
+
resultSizePolicy: { kind: "exact", maxBytes: 2048 },
|
|
108
|
+
costLatency: "local_fast",
|
|
109
|
+
promptHint:
|
|
110
|
+
'Use self_compact alone at a safe boundary. Preserve decisions, evidence, paths, and next actions in note_to_self. Inspect context(scope="budget") for current pressure; recovery belongs to the operator.',
|
|
111
|
+
},
|
|
103
112
|
[ToolNames.Context]: {
|
|
104
113
|
objective: "Return workspace, effective configuration, skill, or recall context.",
|
|
105
114
|
uiLabel: "Context",
|
|
@@ -3,14 +3,9 @@ import path from "node:path";
|
|
|
3
3
|
import type { ContextRecalledPayload } from "../../core/bus-events.js";
|
|
4
4
|
import type { ClioSettings } from "../../core/config.js";
|
|
5
5
|
import { settingsAwareness } from "../../core/settings-awareness.js";
|
|
6
|
-
import {
|
|
7
|
-
SKILL_INSTALL_OFFER_OPTION_NEVER,
|
|
8
|
-
SKILL_INSTALL_OFFER_OPTION_NOT_NOW,
|
|
9
|
-
SKILL_INSTALL_OFFER_OPTION_PROJECT,
|
|
10
|
-
SKILL_INSTALL_OFFER_OPTION_USER,
|
|
11
|
-
SKILL_SUGGESTION_ANCHOR,
|
|
12
|
-
} from "../../core/skill-activation.js";
|
|
6
|
+
import { SKILL_SUGGESTION_ANCHOR } from "../../core/skill-activation.js";
|
|
13
7
|
import { ToolNames } from "../../core/tool-names.js";
|
|
8
|
+
import type { BudgetProvider } from "../../domains/context/budget/inspection.js";
|
|
14
9
|
import type { WorkerRecall } from "../../domains/context/worker/recall.js";
|
|
15
10
|
import { foldWorkingSet } from "../../domains/context/working-set/fold.js";
|
|
16
11
|
import {
|
|
@@ -21,7 +16,9 @@ import {
|
|
|
21
16
|
resolveRecall,
|
|
22
17
|
} from "../../domains/context/working-set/recall.js";
|
|
23
18
|
import {
|
|
19
|
+
buildSkillCatalogView,
|
|
24
20
|
checkSkillDrift,
|
|
21
|
+
checkSkillDriftBatch,
|
|
25
22
|
discoverMarketplaceSkills,
|
|
26
23
|
installedSkillNames,
|
|
27
24
|
installedSkillPackages,
|
|
@@ -30,6 +27,7 @@ import {
|
|
|
30
27
|
type MarketplaceSkill,
|
|
31
28
|
modelVisibleSkills,
|
|
32
29
|
type Skill,
|
|
30
|
+
type SkillCatalogRowKind,
|
|
33
31
|
} from "../../domains/resources/index.js";
|
|
34
32
|
import type { SessionEntryInput } from "../../domains/session/contract.js";
|
|
35
33
|
import type { SessionEntry } from "../../domains/session/entries.js";
|
|
@@ -78,6 +76,8 @@ export interface ContextSessionDeps {
|
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
export interface ContextToolDeps {
|
|
79
|
+
/** Refreshes this run's native budget; absent for external/worker registries. */
|
|
80
|
+
getContextBudget?: BudgetProvider;
|
|
81
81
|
/** Live session view, including overrides. Omit when no authoritative settings snapshot exists. */
|
|
82
82
|
getSettings?: () => Readonly<ClioSettings>;
|
|
83
83
|
/** Run-scoped evidence port; never reads or appends the parent session. */
|
|
@@ -294,84 +294,29 @@ function marketplaceRowsFor(
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
lines.push(`Ready skills in Clio (${native.length}):`);
|
|
320
|
-
if (native.length === 0) lines.push("- none");
|
|
321
|
-
for (const skill of native) {
|
|
322
|
-
lines.push(`- ${skill.name} (source: ${skill.source}; scope: ${skill.scope}): ${skill.description}`);
|
|
323
|
-
}
|
|
324
|
-
if (discovered.length > 0) {
|
|
325
|
-
lines.push("", "Explicitly supplied session skills (not installed packages):");
|
|
326
|
-
for (const skill of discovered) {
|
|
327
|
-
lines.push(
|
|
328
|
-
`- ${skill.name} (source: ${skill.source}; scope: ${skill.scope}; file: ${skill.filePath}): ${skill.description}`,
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
lines.push(
|
|
332
|
-
"These skills were explicitly supplied for this session. Preserve their source; session availability does not mean Clio installed or copied them.",
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (packages.length > 0) {
|
|
337
|
-
lines.push("", `Installed packages providing skills (${packages.length}):`);
|
|
338
|
-
for (const pkg of packages)
|
|
339
|
-
lines.push(
|
|
340
|
-
`- ${pkg.name} (scope: ${pkg.scope}; origin: ${pkg.origin === "catalog" ? "marketplace catalog" : pkg.origin}; state: ${pkg.state}; path: ${pkg.path})`,
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
lines.push(
|
|
344
|
-
"",
|
|
345
|
-
"Other-agent skill folders are discovery-only. Explicitly import into Clio before use; the trust-imports setting does not install or activate loose files.",
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
if (marketplace.length > 0) {
|
|
349
|
-
// Installable rows are suggested exactly like installed ones: the
|
|
350
|
-
// operator's /skill <name> prompts to install before it runs, so the
|
|
351
|
-
// model's move is the same suggest-and-wait. Only the body is out of
|
|
352
|
-
// reach until then, which is why the description is all that appears.
|
|
353
|
-
lines.push("", "Marketplace (additional skills available to install; /skill <name> offers to install):");
|
|
354
|
-
for (const entry of marketplace) {
|
|
355
|
-
const category = entry.category ? ` [${entry.category}]` : "";
|
|
356
|
-
lines.push(`- ${entry.name}${category}: ${entry.description}`);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
// Recency anchor with an exact reply shape: literal models act on an output
|
|
360
|
-
// template where they skip conditional prose in the header.
|
|
361
|
-
lines.push(
|
|
362
|
-
"",
|
|
363
|
-
modelActivation
|
|
364
|
-
? `If one skill above matches the current task, load it now with context(scope="skills", name="<name>") and continue in the same turn; at this autonomy level you activate installed skills yourself and do not wait for the operator. Marketplace additions still require operator approval. If none match, do not mention skills.`
|
|
365
|
-
: `If one skill above matches the current task, begin your reply with the line \`${SKILL_SUGGESTION_ANCHOR}\` (a comma-separated sequence, in order, when several compose), then continue the task in the same turn without it; only the operator can run it. If none match, do not mention skills.`,
|
|
366
|
-
);
|
|
367
|
-
if (marketplace.length > 0) {
|
|
368
|
-
// The offer protocol mirrors the marketplace-offer middleware: fixed
|
|
369
|
-
// option labels so the harness recognizes and handles the answer.
|
|
370
|
-
lines.push(
|
|
371
|
-
`When no installed skill serves the task but a marketplace skill above genuinely does, you may instead ask the operator with ask_user (mode=single_question, header "Install skill") whether to install it, offering exactly: "${SKILL_INSTALL_OFFER_OPTION_PROJECT}", "${SKILL_INSTALL_OFFER_OPTION_USER}", "${SKILL_INSTALL_OFFER_OPTION_NOT_NOW}", "${SKILL_INSTALL_OFFER_OPTION_NEVER}". The harness handles those exact offer options. An explicit operator request or approval also authorizes the documented library install CLI. After installation, refresh the inventory; distinguish installed from ready and report /library reload when required.`,
|
|
372
|
-
);
|
|
297
|
+
/**
|
|
298
|
+
* Ready rows whose installed content no longer matches the hash recorded for
|
|
299
|
+
* it, resolved for the whole listing in one manifest read.
|
|
300
|
+
*
|
|
301
|
+
* Drift used to be checked only in the activation branch below, so the listing
|
|
302
|
+
* reported a drifted skill as ready with no caveat and the warning arrived only
|
|
303
|
+
* after the model had already spent its turn loading it. This is the same
|
|
304
|
+
* comparison, one step earlier, through the batched entry point so a listing
|
|
305
|
+
* does not re-parse the pin manifest once per row.
|
|
306
|
+
*
|
|
307
|
+
* It annotates and nothing else. The skill stays listed, stays ready and stays
|
|
308
|
+
* loadable, the installed copy keeps its ownership, and the activation warning
|
|
309
|
+
* is unchanged. A skill with no recorded hash is not marked, because absence of
|
|
310
|
+
* evidence is not drift, and a failure here yields an unmarked listing rather
|
|
311
|
+
* than no listing.
|
|
312
|
+
*/
|
|
313
|
+
function driftedSkillNames(skills: ReadonlyArray<Skill>, cwd: string): Set<string> {
|
|
314
|
+
try {
|
|
315
|
+
const reports = checkSkillDriftBatch(skills, cwd);
|
|
316
|
+
return new Set([...reports].filter(([, report]) => report.verdict === "mismatch").map(([name]) => name));
|
|
317
|
+
} catch {
|
|
318
|
+
return new Set<string>();
|
|
373
319
|
}
|
|
374
|
-
return lines.join("\n");
|
|
375
320
|
}
|
|
376
321
|
|
|
377
322
|
function runWorkspaceScope(
|
|
@@ -496,38 +441,71 @@ function runSkillsScope(
|
|
|
496
441
|
cwdFromDeps(deps),
|
|
497
442
|
deps.getSkillLoaderOptions?.().trustProjectCompatRoots === true,
|
|
498
443
|
);
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
444
|
+
// The catalog is bounded but not small, and it has to fit the per-call cap
|
|
445
|
+
// like any observation. It is cut by whole rows with the reply protocol
|
|
446
|
+
// reserved first, rather than by head-truncating the finished string:
|
|
447
|
+
// head truncation keeps the head, and the tail is the one line a model
|
|
448
|
+
// acts on. `query`, `limit` and `offset` are the context tool's existing
|
|
449
|
+
// optional arguments, wired here so a caller can ask for less than all of
|
|
450
|
+
// it; omitting them still lists every row.
|
|
451
|
+
const view = buildSkillCatalogView({
|
|
452
|
+
skills: visible,
|
|
504
453
|
packages,
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
454
|
+
marketplace,
|
|
455
|
+
drifted: driftedSkillNames(visible, cwdFromDeps(deps)),
|
|
456
|
+
marketplaceOffered: deps.skillMarketplace !== false,
|
|
457
|
+
modelActivation: options?.pendingSkillPolicy?.modelActivation === true,
|
|
458
|
+
query: typeof args.query === "string" ? args.query : "",
|
|
459
|
+
limit: typeof args.limit === "number" ? args.limit : undefined,
|
|
460
|
+
offset: typeof args.offset === "number" ? args.offset : 0,
|
|
461
|
+
capBytes: reservation.callCapBytes,
|
|
462
|
+
});
|
|
463
|
+
// Shown rows are selected by the row's own stable key, not by its name. An
|
|
464
|
+
// installed standalone skill has a ready row and a package row under one
|
|
465
|
+
// name, and the same package id can be installed at user and project scope,
|
|
466
|
+
// so a name set puts rows in `details` that the text never carried.
|
|
467
|
+
const shownKeys = new Set(view.rows.map((row) => row.key));
|
|
468
|
+
const shown = (kind: SkillCatalogRowKind, identity: string): boolean => shownKeys.has(`${kind}:${identity}`);
|
|
511
469
|
return finalizeObservation({
|
|
512
470
|
tool: ToolNames.Context,
|
|
513
471
|
unit: "entries",
|
|
514
|
-
output:
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
472
|
+
output: view.text,
|
|
473
|
+
shownCount: view.shown,
|
|
474
|
+
totalCount: view.total,
|
|
475
|
+
truncated: view.nextOffset !== undefined || view.shown < view.total,
|
|
476
|
+
// Offsets index the filtered result set. A continuation carrying only
|
|
477
|
+
// the offset would be applied to the unfiltered catalog, which repeats
|
|
478
|
+
// rows and skips matches, so the envelope says the query is part of it.
|
|
479
|
+
...(view.nextOffset !== undefined
|
|
480
|
+
? {
|
|
481
|
+
next: view.filtered ? `offset=${view.nextOffset} with the same query` : `offset=${view.nextOffset}`,
|
|
482
|
+
}
|
|
483
|
+
: {}),
|
|
519
484
|
details: {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
485
|
+
// Rows on this page, so a caller reading `details` sees the same
|
|
486
|
+
// listing the text carries. The totals beside them are how a
|
|
487
|
+
// filtered or paged view is told apart from a complete one.
|
|
488
|
+
skills: visible
|
|
489
|
+
.filter((skill) => shown("ready", skill.filePath) || shown("session", skill.filePath))
|
|
490
|
+
.map((skill) => ({
|
|
491
|
+
name: skill.name,
|
|
492
|
+
scope: skill.scope,
|
|
493
|
+
source: skill.source,
|
|
494
|
+
path: skill.filePath,
|
|
495
|
+
...(view.driftedNames.includes(skill.name) ? { drift: "mismatch" } : {}),
|
|
496
|
+
})),
|
|
497
|
+
installedPackages: packages.filter((pkg) => shown("package", `${pkg.scope}:${pkg.path}`)),
|
|
498
|
+
marketplace: marketplace
|
|
499
|
+
.filter((entry) => shown("marketplace", entry.sourceUrl))
|
|
500
|
+
.map((entry) => ({
|
|
501
|
+
name: entry.name,
|
|
502
|
+
...(entry.category ? { category: entry.category } : {}),
|
|
503
|
+
})),
|
|
504
|
+
totalSkills: visible.length,
|
|
505
|
+
totalPackages: packages.length,
|
|
506
|
+
totalMarketplace: marketplace.length,
|
|
507
|
+
...(view.filtered ? { query: (args.query as string).trim(), matchMode: view.matchMode } : {}),
|
|
508
|
+
...(view.driftedNames.length > 0 ? { drifted: view.driftedNames } : {}),
|
|
531
509
|
},
|
|
532
510
|
reservation,
|
|
533
511
|
...(options ? { options } : {}),
|
|
@@ -767,6 +745,52 @@ function runRecallScope(
|
|
|
767
745
|
});
|
|
768
746
|
}
|
|
769
747
|
|
|
748
|
+
/** A byte-fitted JSON observation. Inspection never spills to disk. */
|
|
749
|
+
function runBudgetScope(
|
|
750
|
+
deps: ContextToolDeps,
|
|
751
|
+
reservation: ObservationReservation,
|
|
752
|
+
options?: ToolInvokeOptions,
|
|
753
|
+
): ToolResult {
|
|
754
|
+
const inspection = deps.getContextBudget?.() ?? {
|
|
755
|
+
status: "unavailable",
|
|
756
|
+
reason:
|
|
757
|
+
"This run has no native session budget port. Worker and external-agent budgets are not the parent session's budget.",
|
|
758
|
+
};
|
|
759
|
+
const payload =
|
|
760
|
+
inspection.status === "available"
|
|
761
|
+
? {
|
|
762
|
+
...inspection,
|
|
763
|
+
mode: "enforced",
|
|
764
|
+
admissionNote:
|
|
765
|
+
"Submission and continuation enforce input plus reserved output against the effective window. Counts are estimates unless provider-attested; pressure advice remains advisory.",
|
|
766
|
+
}
|
|
767
|
+
: inspection;
|
|
768
|
+
const full = JSON.stringify(payload);
|
|
769
|
+
const fits = Buffer.byteLength(full, "utf8") <= reservation.callCapBytes;
|
|
770
|
+
// Do not pass fullOutput to finalizeObservation: an inspection must not
|
|
771
|
+
// persist its view even when an optional projection contains huge text.
|
|
772
|
+
const output = fits
|
|
773
|
+
? full
|
|
774
|
+
: JSON.stringify({
|
|
775
|
+
status: "unavailable",
|
|
776
|
+
reason: "observation-limit",
|
|
777
|
+
message:
|
|
778
|
+
"The budget view exceeds the available observation bytes. Continue in a follow-up turn; do not retry this turn.",
|
|
779
|
+
});
|
|
780
|
+
return finalizeObservation({
|
|
781
|
+
tool: ToolNames.Context,
|
|
782
|
+
unit: "results",
|
|
783
|
+
format: "json",
|
|
784
|
+
output,
|
|
785
|
+
shownCount: fits ? 1 : 0,
|
|
786
|
+
totalCount: 1,
|
|
787
|
+
totalBytes: Buffer.byteLength(full, "utf8"),
|
|
788
|
+
truncated: !fits,
|
|
789
|
+
reservation,
|
|
790
|
+
...(options ? { options } : {}),
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
|
|
770
794
|
export function createContextTool(deps: ContextToolDeps = {}): ToolSpec {
|
|
771
795
|
return {
|
|
772
796
|
...contextToolSurface,
|
|
@@ -782,12 +806,21 @@ export function createContextTool(deps: ContextToolDeps = {}): ToolSpec {
|
|
|
782
806
|
message: `context: scope "${scope}" is a gateway capability now: call gateway(op="call", capability="${capability}", args={...}) or gateway(op="describe", capability="${capability}") for its arguments.`,
|
|
783
807
|
};
|
|
784
808
|
}
|
|
785
|
-
if (
|
|
809
|
+
if (
|
|
810
|
+
scope !== "workspace" &&
|
|
811
|
+
scope !== "settings" &&
|
|
812
|
+
scope !== "skills" &&
|
|
813
|
+
scope !== "recall" &&
|
|
814
|
+
scope !== "budget"
|
|
815
|
+
) {
|
|
786
816
|
return {
|
|
787
817
|
kind: "error",
|
|
788
|
-
message: `context: scope must be workspace, settings, skills, or
|
|
818
|
+
message: `context: scope must be workspace, settings, skills, recall, or budget; got '${scope}'`,
|
|
789
819
|
};
|
|
790
820
|
}
|
|
821
|
+
if (scope === "budget" && Object.keys(args).some((key) => key !== "scope")) {
|
|
822
|
+
return { kind: "error", message: 'context: scope="budget" accepts only scope; it inspects the current request.' };
|
|
823
|
+
}
|
|
791
824
|
const selfCap = scope === "skills" ? OBSERVE_SELF_CAPS.contextSkills : OBSERVE_SELF_CAPS.contextWorkspace;
|
|
792
825
|
// Reserved before any scope handler runs, so an exhausted pool answers
|
|
793
826
|
// with the notice and no scope does its work for nothing.
|
|
@@ -796,6 +829,7 @@ export function createContextTool(deps: ContextToolDeps = {}): ToolSpec {
|
|
|
796
829
|
return observationBudgetExhausted({
|
|
797
830
|
tool: ToolNames.Context,
|
|
798
831
|
unit: scope === "skills" ? "entries" : "results",
|
|
832
|
+
...(scope === "budget" ? { format: "json" as const } : {}),
|
|
799
833
|
reservation,
|
|
800
834
|
subject: `scope=${scope}`,
|
|
801
835
|
hint: "Continue in a follow-up turn.",
|
|
@@ -838,6 +872,7 @@ export function createContextTool(deps: ContextToolDeps = {}): ToolSpec {
|
|
|
838
872
|
...(options ? { options } : {}),
|
|
839
873
|
});
|
|
840
874
|
}
|
|
875
|
+
if (scope === "budget") return runBudgetScope(deps, reservation, options);
|
|
841
876
|
if (scope === "workspace") return runWorkspaceScope(deps, reservation, options);
|
|
842
877
|
if (scope === "recall") return runRecallScope(deps, args, reservation, options);
|
|
843
878
|
return runSkillsScope(deps, args, reservation, options);
|