@iowarp/clio-coder 0.3.2 → 0.3.4
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/CHANGELOG.md +269 -458
- package/CONTRIBUTING.md +1 -1
- package/README.md +3 -3
- package/dist/{acp-BIYHVZIM.js → acp-S5R4RR5B.js} +7 -6
- package/dist/{agents-YT6SSRIT.js → agents-P6DMMVZY.js} +24 -21
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-5TWEIYDN.js → auth-2XCZLPKS.js} +12 -8
- package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
- package/dist/{chunk-WMSVI4G2.js → chunk-2LZI5CAG.js} +133 -13
- package/dist/{chunk-OAO4GE4M.js → chunk-2TZWSW76.js} +2 -2
- package/dist/{chunk-OOJYHWRB.js → chunk-34475P3I.js} +2 -2
- package/dist/{chunk-WVO7V2QY.js → chunk-35MKKU5R.js} +4 -4
- package/dist/{chunk-LBNRH5WM.js → chunk-3HZ5RWN2.js} +5 -5
- package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
- package/dist/{chunk-MBS4V7ZP.js → chunk-4JUF2NNX.js} +7 -7
- package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
- package/dist/chunk-5M54SPOL.js +926 -0
- package/dist/{chunk-STBPMHSX.js → chunk-7RXG6QRZ.js} +51 -11
- package/dist/{chunk-77VKQEHF.js → chunk-A2GZF7DC.js} +5 -5
- package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
- package/dist/chunk-AOCYTWAV.js +449 -0
- package/dist/chunk-BEY543CS.js +258 -0
- package/dist/{chunk-6N5PTWMY.js → chunk-BP4OYD6A.js} +32 -13
- package/dist/chunk-BPGS2WCQ.js +612 -0
- package/dist/{chunk-J5HN4RYU.js → chunk-BRXQQJFP.js} +8 -8
- package/dist/chunk-CFGTUFWB.js +67 -0
- package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
- package/dist/{chunk-G2DE3C7R.js → chunk-EDRHSCIE.js} +4 -4
- package/dist/{chunk-4KLWL3UC.js → chunk-EFADSJET.js} +2 -2
- package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
- package/dist/chunk-FYYLNIL5.js +313 -0
- package/dist/{chunk-EPVUXGXG.js → chunk-HV5X7OR2.js} +14 -12
- package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
- package/dist/{chunk-IGLFWIYI.js → chunk-K6WL7QZT.js} +3 -3
- package/dist/chunk-K7VKOLQQ.js +15 -0
- package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
- package/dist/{chunk-V4RXGQ5Q.js → chunk-KRPY7NTG.js} +10 -7
- package/dist/chunk-LL4KHSZI.js +22 -0
- package/dist/{chunk-KJ5LWLOE.js → chunk-MEQ45TQ4.js} +15 -9
- package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
- package/dist/{chunk-AO4RKG4M.js → chunk-N4CZJQRK.js} +5 -5
- package/dist/{chunk-ARBGF5F7.js → chunk-NILBFAPG.js} +14 -8
- package/dist/chunk-OZNBF4L3.js +23 -0
- package/dist/{verify-G6V4D2G7.js → chunk-PCZJO5TI.js} +127 -42
- package/dist/chunk-QQK64KLB.js +1360 -0
- package/dist/{chunk-6EJV5X2W.js → chunk-QQL5RT5M.js} +979 -1619
- package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
- package/dist/{chunk-2EHAIA3X.js → chunk-RD5U66HV.js} +3 -3
- package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
- package/dist/{chunk-OQ33BKR3.js → chunk-TTNYS3EA.js} +3 -60
- package/dist/chunk-TW3WDMVS.js +677 -0
- package/dist/chunk-TZSKNMZG.js +434 -0
- package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
- package/dist/{chunk-7EYHLWU7.js → chunk-UZHIZC5S.js} +7 -7
- package/dist/{chunk-QTYWRVRA.js → chunk-VAWWTKDP.js} +8 -8
- package/dist/{chunk-X75S7HFS.js → chunk-VEZEGCGW.js} +214 -20
- package/dist/{chunk-OHHN2SO4.js → chunk-VMNQ6OZA.js} +98 -202
- package/dist/chunk-VSNATDE6.js +122 -0
- package/dist/chunk-W6GROXXM.js +69 -0
- package/dist/chunk-WPQLXFOZ.js +375 -0
- package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
- package/dist/{chunk-3ZXDFGR5.js → chunk-X6COSD2O.js} +5 -5
- package/dist/chunk-ZGVHUX3M.js +66 -0
- package/dist/{chunk-MAW544W2.js → chunk-ZWMF7253.js} +4 -4
- package/dist/{chunk-MQSRRFWA.js → chunk-ZYKPLLNQ.js} +563 -546
- package/dist/cli/index.js +27 -23
- package/dist/{clio-4LY5K2AC.js → clio-J5JIOIDS.js} +7 -6
- package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
- package/dist/{config-GTLUW2PR.js → config-OEBMIN2U.js} +37 -27
- package/dist/{configure-R6A64DHX.js → configure-PUQOSIXQ.js} +16 -13
- package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
- package/dist/{context-RW5HC47S.js → context-MGSE4Z2T.js} +33 -23
- package/dist/{context-JFZEJ7W5.js → context-URSXPBCK.js} +17 -9
- package/dist/{context-clear-6ZHBAZZT.js → context-clear-KDAJRNUK.js} +33 -23
- package/dist/context-working-set-SBKMPPI2.js +1552 -0
- package/dist/{dispatch-runner-VKBRCWQC.js → dispatch-runner-MSWN72NK.js} +43 -29
- package/dist/{doctor-KI767GSN.js → doctor-7BSE27PJ.js} +10 -10
- package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
- package/dist/{evidence-UA6AWDQQ.js → evidence-SR7WXB5B.js} +51 -23
- package/dist/{evolve-QNTFGV6Z.js → evolve-K7VE2CBX.js} +30 -20
- package/dist/{fleet-Q7UOMUSG.js → fleet-7XMJNQNF.js} +48 -38
- package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
- package/dist/{init-WBB65ZHQ.js → init-JGNPAYXT.js} +41 -31
- package/dist/{memory-MD3O64RI.js → memory-4ALKDJ4Q.js} +32 -22
- package/dist/{models-BZU34YWD.js → models-ZMMLFJNN.js} +22 -19
- package/dist/{monitor-MEQA5C3I.js → monitor-2F3T5KHP.js} +55 -43
- package/dist/{orchestrator-CGFKEP27.js → orchestrator-ORHT43JB.js} +2507 -1896
- package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
- package/dist/{run-IV4Q6RLN.js → run-RF4WJGMT.js} +51 -41
- package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
- package/dist/{skills-LQEKRDTN.js → skills-PSACKC5Q.js} +2 -2
- package/dist/{skills-eval-3DC4HEWS.js → skills-eval-WJSI55RZ.js} +34 -24
- package/dist/{targets-C4SSGQOB.js → targets-PIIRAOYS.js} +23 -20
- package/dist/{terminal-lease-IT5JW2NR.js → terminal-lease-ULWXWNVY.js} +5 -4
- package/dist/{upgrade-7TT7SQ3G.js → upgrade-346TZ6AV.js} +18 -17
- package/dist/{usage-GV4PKT3M.js → usage-6KKXR32N.js} +34 -24
- package/dist/verifiers-4UUM6TEE.js +1214 -0
- package/dist/verify-X5HDROLA.js +25 -0
- package/dist/{wiki-generate-DQF6Z66B.js → wiki-generate-7STOCIFZ.js} +42 -31
- package/dist/worker/entry.js +33 -24
- package/docs/README.md +8 -7
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +53 -21
- package/docs/config-knobs-audit.md +1 -2
- package/docs/configuration-and-targets.md +15 -1
- package/docs/context-engine.md +64 -12
- package/docs/context-working-set.md +194 -0
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +5 -5
- package/docs/documentation-guide.md +6 -5
- package/docs/environment-variables.md +2 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +14 -1
- package/docs/evidence-and-memory.md +74 -2
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +2 -2
- package/docs/fleet-dispatch.md +22 -7
- package/docs/glossary.md +21 -1
- package/docs/installation-and-lifecycle.md +6 -6
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +7 -9
- package/docs/observability.md +4 -4
- package/docs/performance-methodology.md +2 -2
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +4 -4
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +35 -35
- package/docs/safety-model.md +23 -4
- package/docs/scientific-validation.md +21 -3
- package/docs/session-lifecycle.md +3 -3
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +79 -12
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +2 -2
- package/docs/worker-dispatch-mechanics.md +11 -1
- package/package.json +8 -11
- package/skills/meta/clio-test/SKILL.md +20 -17
- package/skills/meta/clio-test/evals.md +3 -3
- package/skills/meta/clio-test/references/harness.md +35 -6
- package/skills/meta/clio-test/references/test-map.md +20 -10
- package/skills/registry.yaml +2 -2
- package/skills/skill-marketplace.json +1 -1
- package/src/cli/context-working-set.ts +513 -0
- package/src/cli/context.ts +8 -0
- package/src/cli/evidence.ts +20 -2
- package/src/cli/index.ts +4 -0
- package/src/cli/verifiers.ts +325 -0
- package/src/core/bash-exec.ts +39 -14
- package/src/core/bus-events.ts +19 -4
- package/src/core/config.ts +54 -0
- package/src/core/defaults.ts +50 -3
- package/src/core/git-commit-attribution.ts +46 -21
- package/src/core/verification-scripts.ts +6 -0
- package/src/domains/agents/builtins/verifier.md +3 -0
- package/src/domains/config/classify.ts +1 -0
- package/src/domains/config/keybindings.ts +3 -3
- package/src/domains/context/working-set/contract.ts +161 -0
- package/src/domains/context/working-set/defaults.ts +28 -0
- package/src/domains/context/working-set/engine.ts +203 -0
- package/src/domains/context/working-set/fold.ts +62 -0
- package/src/domains/context/working-set/horizon.ts +38 -0
- package/src/domains/context/working-set/marker.ts +103 -0
- package/src/domains/context/working-set/path-index.ts +436 -0
- package/src/domains/context/working-set/payload.ts +152 -0
- package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
- package/src/domains/context/working-set/policies/index.ts +21 -0
- package/src/domains/context/working-set/policies/structural.ts +160 -0
- package/src/domains/context/working-set/project.ts +132 -0
- package/src/domains/context/working-set/protect.ts +109 -0
- package/src/domains/context/working-set/recall.ts +177 -0
- package/src/domains/context/working-set/replay/controls.ts +112 -0
- package/src/domains/context/working-set/replay/load-clio.ts +199 -0
- package/src/domains/context/working-set/replay/metrics.ts +185 -0
- package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
- package/src/domains/context/working-set/replay/report.ts +139 -0
- package/src/domains/context/working-set/replay/runner.ts +325 -0
- package/src/domains/context/working-set/replay/synthetic.ts +422 -0
- package/src/domains/context/working-set/replay/trace.ts +21 -0
- package/src/domains/context/working-set/visible.ts +54 -0
- package/src/domains/evidence/build.ts +112 -45
- package/src/domains/evidence/eval.ts +24 -7
- package/src/domains/evidence/index.ts +53 -0
- package/src/domains/evidence/ordering.ts +12 -0
- package/src/domains/evidence/run-trust.ts +221 -0
- package/src/domains/evidence/store.ts +46 -6
- package/src/domains/evidence/trust-status.ts +854 -0
- package/src/domains/evidence/types.ts +26 -0
- package/src/domains/middleware/memory-intervention.ts +3 -0
- package/src/domains/middleware/stalled-turn.ts +165 -4
- package/src/domains/safety/autonomy.ts +1 -1
- package/src/domains/safety/default-path-policy.ts +8 -0
- package/src/domains/safety/finish-contract.ts +4 -3
- package/src/domains/safety/policy-engine.ts +48 -6
- package/src/domains/session/compaction/compact.ts +23 -1
- package/src/domains/session/compaction/cut-point.ts +2 -0
- package/src/domains/session/compaction/tokens.ts +16 -1
- package/src/domains/session/context-ledger.ts +2 -0
- package/src/domains/session/entries.ts +107 -1
- package/src/domains/session/manager.ts +9 -2
- package/src/domains/session/migrations/index.ts +22 -3
- package/src/engine/acp/server.ts +3 -0
- package/src/engine/agent.ts +18 -1
- package/src/engine/session.ts +9 -3
- package/src/entry/orchestrator.ts +16 -4
- package/src/interactive/chat-loop-messages.ts +18 -6
- package/src/interactive/chat-panel.ts +571 -244
- package/src/interactive/chat-renderer.ts +79 -39
- package/src/interactive/context-meter.ts +10 -0
- package/src/interactive/context-overlay.ts +81 -6
- package/src/interactive/context-recall-command.ts +110 -0
- package/src/interactive/editor-submit.ts +26 -1
- package/src/interactive/footer/widgets.ts +22 -20
- package/src/interactive/footer-panel.ts +6 -1
- package/src/interactive/interactive-application.ts +2 -0
- package/src/interactive/interactive-event-projection.ts +12 -0
- package/src/interactive/interactive-slash-runtime.ts +49 -8
- package/src/interactive/model-session-replay.ts +21 -0
- package/src/interactive/overlay-general-openers.ts +6 -0
- package/src/interactive/overlay-session-lifecycle.ts +8 -4
- package/src/interactive/overlays/ask-user.ts +146 -24
- package/src/interactive/renderers/tool-execution.ts +167 -56
- package/src/interactive/session-transcript.ts +2 -2
- package/src/interactive/slash-commands.ts +29 -2
- package/src/interactive/status/index.ts +12 -1
- package/src/interactive/status/reasoning.ts +87 -0
- package/src/interactive/status/summary.ts +13 -2
- package/src/interactive/transcript-detail.ts +120 -0
- package/src/interactive/turn-context.ts +238 -88
- package/src/interactive/turn-middleware.ts +6 -6
- package/src/tools/agent-tools.ts +11 -4
- package/src/tools/bash.ts +144 -82
- package/src/tools/builtin-tool-catalog.ts +18 -6
- package/src/tools/context/index.ts +105 -3
- package/src/tools/context/surface.ts +3 -2
- package/src/tools/core-bootstrap.ts +21 -0
- package/src/tools/dispatch-runner.ts +9 -7
- package/src/tools/monitor.ts +28 -20
- package/src/tools/presentation.ts +107 -0
- package/src/tools/registry.ts +65 -7
- package/src/tools/result-disposition.ts +550 -0
- package/src/tools/result-shaping.ts +262 -19
- package/src/tools/safe-exec.ts +2 -0
- package/src/tools/verify/authoring.ts +1119 -0
- package/src/tools/verify/catalog.ts +346 -0
- package/src/tools/verify/index.ts +13 -3
- package/src/tools/verify/scripts.ts +135 -37
- package/src/tools/verify/surface.ts +9 -5
- package/src/tools/worker-evidence.ts +35 -12
- package/dist/chunk-MNA4JGU4.js +0 -255
- package/dist/chunk-SRF2PJNW.js +0 -184
- package/dist/chunk-T6YILFSB.js +0 -80
- package/dist/chunk-VAKQQHWR.js +0 -434
|
@@ -3,6 +3,7 @@ import type { RunPersonaOverride, RunPipelineProvenance, RunReceiptAutonomyEnfor
|
|
|
3
3
|
import type { EvalCommandPhase, EvalFailureClass, EvalRunRecord } from "../eval/index.js";
|
|
4
4
|
import type { ProtectedArtifact } from "../safety/protected-artifacts.js";
|
|
5
5
|
import type { RunEscalationCounts } from "./provenance.js";
|
|
6
|
+
import type { CanonicalTrustStatus } from "./trust-status.js";
|
|
6
7
|
|
|
7
8
|
export const EVIDENCE_VERSION = 1;
|
|
8
9
|
|
|
@@ -131,13 +132,38 @@ export interface EvidenceBuildResult {
|
|
|
131
132
|
directory: string;
|
|
132
133
|
overview: EvidenceOverview;
|
|
133
134
|
findings: EvidenceFinding[];
|
|
135
|
+
trustStatus: EvidenceTrustStatusFile;
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
export interface EvidenceInspectable {
|
|
137
139
|
overview: EvidenceOverview;
|
|
138
140
|
findings: EvidenceFinding[];
|
|
141
|
+
trustStatus: EvidenceTrustStatusView;
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
export interface EvidenceRunTrustStatus {
|
|
145
|
+
runId: string;
|
|
146
|
+
status: CanonicalTrustStatus;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Canonical per-run projection persisted by current evidence builders. */
|
|
150
|
+
export interface EvidenceTrustStatusFile {
|
|
151
|
+
version: 1;
|
|
152
|
+
evidenceId: string;
|
|
153
|
+
projection: "canonical";
|
|
154
|
+
runs: EvidenceRunTrustStatus[];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Explicit compatibility result for a bundle built before the projection existed. */
|
|
158
|
+
export interface HistoricalEvidenceTrustStatus {
|
|
159
|
+
version: 1;
|
|
160
|
+
evidenceId: string;
|
|
161
|
+
projection: "historical_format";
|
|
162
|
+
runs: [];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type EvidenceTrustStatusView = EvidenceTrustStatusFile | HistoricalEvidenceTrustStatus;
|
|
166
|
+
|
|
141
167
|
export interface EvidenceRunSource {
|
|
142
168
|
envelope: RunEnvelope;
|
|
143
169
|
receipt: RunReceipt | null;
|
|
@@ -202,6 +202,9 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
202
202
|
return effects;
|
|
203
203
|
}
|
|
204
204
|
case "on_compaction":
|
|
205
|
+
// Recall grows the working set; it is an observability point, not
|
|
206
|
+
// context loss that should reactivate compacted task memory.
|
|
207
|
+
if (input.metadata?.stage === "working_set_recall") return NO_EFFECTS;
|
|
205
208
|
reactivateAfterCompaction = true;
|
|
206
209
|
return NO_EFFECTS;
|
|
207
210
|
case "turn_start": {
|
|
@@ -7,7 +7,116 @@ export const STALLED_TURN_REQUEST_CONTINUATION_MESSAGE =
|
|
|
7
7
|
"You ended your turn after announcing an action without executing it: no tools were called. Continue now and perform the announced action, or state plainly that you are finished and waiting for the user.";
|
|
8
8
|
|
|
9
9
|
const INTENT_PATTERN = /\b(let me|i['’]ll|i will|i am going to|i['’]m going to|now i|next i|let's|let us)\b/i;
|
|
10
|
+
|
|
11
|
+
// Bare stems only. Inflected forms are derived below so that "running",
|
|
12
|
+
// "checking", and "verifies" count as the same announced action as "run".
|
|
13
|
+
const CONCRETE_ACTION_STEMS = [
|
|
14
|
+
"add",
|
|
15
|
+
"analyse",
|
|
16
|
+
"analyze",
|
|
17
|
+
"apply",
|
|
18
|
+
"audit",
|
|
19
|
+
"build",
|
|
20
|
+
"change",
|
|
21
|
+
"check",
|
|
22
|
+
"compare",
|
|
23
|
+
"compile",
|
|
24
|
+
"create",
|
|
25
|
+
"debug",
|
|
26
|
+
"delete",
|
|
27
|
+
"edit",
|
|
28
|
+
"execute",
|
|
29
|
+
"explore",
|
|
30
|
+
"find",
|
|
31
|
+
"fix",
|
|
32
|
+
"format",
|
|
33
|
+
"grep",
|
|
34
|
+
"implement",
|
|
35
|
+
"inspect",
|
|
36
|
+
"install",
|
|
37
|
+
"investigate",
|
|
38
|
+
"list",
|
|
39
|
+
"load",
|
|
40
|
+
"look",
|
|
41
|
+
"modify",
|
|
42
|
+
"open",
|
|
43
|
+
"patch",
|
|
44
|
+
"query",
|
|
45
|
+
"read",
|
|
46
|
+
"refactor",
|
|
47
|
+
"remove",
|
|
48
|
+
"rename",
|
|
49
|
+
"replace",
|
|
50
|
+
"review",
|
|
51
|
+
"run",
|
|
52
|
+
"scan",
|
|
53
|
+
"search",
|
|
54
|
+
"test",
|
|
55
|
+
"trace",
|
|
56
|
+
"update",
|
|
57
|
+
"validate",
|
|
58
|
+
"verify",
|
|
59
|
+
"write",
|
|
60
|
+
] as const;
|
|
61
|
+
|
|
62
|
+
// Stems whose final consonant doubles before a vowel suffix.
|
|
63
|
+
const DOUBLED_CONSONANT_STEMS = new Set(["debug", "format", "run", "scan"]);
|
|
64
|
+
|
|
65
|
+
function concreteActionForms(stem: string): string[] {
|
|
66
|
+
const forms = new Set([stem, `${stem}s`, `${stem}ed`, `${stem}ing`]);
|
|
67
|
+
if (/(?:ch|sh|s|x|z)$/.test(stem)) forms.add(`${stem}es`);
|
|
68
|
+
if (stem.endsWith("e")) {
|
|
69
|
+
forms.add(`${stem}d`);
|
|
70
|
+
forms.add(`${stem.slice(0, -1)}ing`);
|
|
71
|
+
}
|
|
72
|
+
if (stem.endsWith("y")) {
|
|
73
|
+
forms.add(`${stem.slice(0, -1)}ies`);
|
|
74
|
+
forms.add(`${stem.slice(0, -1)}ied`);
|
|
75
|
+
}
|
|
76
|
+
if (DOUBLED_CONSONANT_STEMS.has(stem)) {
|
|
77
|
+
const doubled = `${stem}${stem.slice(-1)}`;
|
|
78
|
+
forms.add(`${doubled}ed`);
|
|
79
|
+
forms.add(`${doubled}ing`);
|
|
80
|
+
}
|
|
81
|
+
return [...forms];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const CONCRETE_ACTION_PATTERN = new RegExp(
|
|
85
|
+
`\\b(?:${CONCRETE_ACTION_STEMS.flatMap(concreteActionForms).join("|")})\\b`,
|
|
86
|
+
"giu",
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Extensions a model actually names when announcing work on a file. Requiring
|
|
90
|
+
// one (or an explicit ./, ../, ~/ prefix) keeps "and/or" and "I/O" from
|
|
91
|
+
// reading as paths.
|
|
92
|
+
const NAMED_PATH_EXTENSIONS =
|
|
93
|
+
"c|cc|cfg|cjs|cmake|conf|cpp|cs|css|csv|env|go|h|hpp|html|ini|java|jl|js|json|jsx|lock|log|lua|md|mjs|nc|php|py|rb|rs|rst|sh|sql|toml|ts|tsx|txt|yaml|yml";
|
|
94
|
+
const NAMED_PATH_PATTERN = new RegExp(
|
|
95
|
+
`(?:^|[\\s\`"'(\\[])(?:[\\w@.\\-/]*[\\w@\\-]\\.(?:${NAMED_PATH_EXTENSIONS})\\b|(?:\\.{1,2}|~)\\/[\\w@.\\-/]+)`,
|
|
96
|
+
"iu",
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// A named executable followed by at least one argument token. Words that are
|
|
100
|
+
// ordinary English on their own ("go", "make", "test") are deliberately absent.
|
|
101
|
+
const NAMED_COMMAND_PATTERN =
|
|
102
|
+
/\b(?:npm|npx|pnpm|yarn|bun|git|cargo|rustc|cmake|ctest|ninja|meson|bazel|gradle|mvn|pytest|pip|python3?|node|deno|tsc|biome|eslint|prettier|jest|vitest|ruff|mypy|docker|kubectl|mpirun|srun|sbatch|gcc|clang)\s+[-\w./]/iu;
|
|
103
|
+
|
|
104
|
+
const ACTION_OBJECT_CONNECTOR_PATTERN = /^(?:at|for|in|into|on|through|to)\s+/i;
|
|
105
|
+
const ACTION_OBJECT_DETERMINER_PATTERN = /^(?:a|an|my|our|that|the|their|these|this|those|your)\s+/i;
|
|
106
|
+
const ACTION_OBJECT_PATTERN = /^(?!(?:again|everyone|later|next|now|soon|then|you)\b)[`"'./~]*[\p{L}\p{N}_-]/iu;
|
|
107
|
+
const CONDITIONAL_LEAD_IN_PATTERN = /\b(?:if|once|unless|until|when|whenever)\b/i;
|
|
108
|
+
const CONDITIONAL_INVITATION_PATTERN = /\b(?:ask|give|point|say|send|share|tell)\b[\s\S]*\b(?:and|then)\s*$/i;
|
|
109
|
+
// "if needed", and any conditional whose condition is about the user: "if you
|
|
110
|
+
// want", "if that is what you need". Conditions about the work ("if the config
|
|
111
|
+
// is valid") stay announcements.
|
|
112
|
+
const CONDITIONAL_OFFER_SUFFIX_PATTERN =
|
|
113
|
+
/\b(?:if\s+(?:needed|necessary|requested)\b|if\b[^.!?]*\byou\b|when(?:ever)?\s+you\b)/i;
|
|
10
114
|
const LET_ME_KNOW_PATTERN = /\blet me know\b/i;
|
|
115
|
+
// "I'll wait for your go-ahead before I touch src/cli/index.ts" names a path
|
|
116
|
+
// and promises nothing now. A clause that opens by deferring is a wait
|
|
117
|
+
// statement whatever it goes on to name, so it is read before any verb or
|
|
118
|
+
// path inside it can count as an announcement.
|
|
119
|
+
const DEFERRAL_LEAD_PATTERN = /^\s*(?:just\s+|simply\s+|now\s+)?(?:wait|hold\s+off|hold|stand\s+by|pause|defer)\b/i;
|
|
11
120
|
const COMPLETION_PATTERN =
|
|
12
121
|
/^\s*(?:all\s+done|done|complete|completed|all\s+set)\b|^\s*(?:here(?:'s| is)\s+)?(?:a\s+)?summary\b|^\s*summary\s*:?$/i;
|
|
13
122
|
|
|
@@ -38,7 +147,47 @@ export function shouldRequestStalledTurnContinuation(input: MiddlewareHookInput)
|
|
|
38
147
|
if (LET_ME_KNOW_PATTERN.test(finalSentence)) return false;
|
|
39
148
|
if (COMPLETION_PATTERN.test(lastLine) || COMPLETION_PATTERN.test(finalSentence)) return false;
|
|
40
149
|
|
|
41
|
-
return
|
|
150
|
+
return announcesConcreteAction(finalSentence);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function announcesConcreteAction(sentence: string): boolean {
|
|
154
|
+
const intent = INTENT_PATTERN.exec(sentence);
|
|
155
|
+
if (intent) {
|
|
156
|
+
const leadIn = sentence.slice(0, intent.index);
|
|
157
|
+
if (
|
|
158
|
+
CONDITIONAL_LEAD_IN_PATTERN.test(leadIn) ||
|
|
159
|
+
CONDITIONAL_INVITATION_PATTERN.test(leadIn) ||
|
|
160
|
+
CONDITIONAL_OFFER_SUFFIX_PATTERN.test(sentence)
|
|
161
|
+
) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
const clause = sentence.slice(intent.index + intent[0].length);
|
|
165
|
+
if (DEFERRAL_LEAD_PATTERN.test(clause)) return false;
|
|
166
|
+
if (hasActionWithObject(clause)) return true;
|
|
167
|
+
// A path or command is evidence of an immediate action only when the
|
|
168
|
+
// announcement itself is unconditional; "once package.json is updated"
|
|
169
|
+
// names a file but promises nothing now.
|
|
170
|
+
if (CONDITIONAL_LEAD_IN_PATTERN.test(clause)) return false;
|
|
171
|
+
return namesToolTarget(clause);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// A trailing colon can still signal a cut-off action lead-in, but only when
|
|
175
|
+
// the lead-in names work and its target instead of relying on punctuation.
|
|
176
|
+
return sentence.endsWith(":") && hasActionWithObject(sentence);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function namesToolTarget(clause: string): boolean {
|
|
180
|
+
return NAMED_PATH_PATTERN.test(clause) || NAMED_COMMAND_PATTERN.test(clause);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function hasActionWithObject(clause: string): boolean {
|
|
184
|
+
for (const action of clause.matchAll(CONCRETE_ACTION_PATTERN)) {
|
|
185
|
+
const remainder = clause.slice((action.index ?? 0) + action[0].length).trimStart();
|
|
186
|
+
const withoutConnector = remainder.replace(ACTION_OBJECT_CONNECTOR_PATTERN, "");
|
|
187
|
+
const object = withoutConnector.replace(ACTION_OBJECT_DETERMINER_PATTERN, "");
|
|
188
|
+
if (ACTION_OBJECT_PATTERN.test(object)) return true;
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
42
191
|
}
|
|
43
192
|
|
|
44
193
|
function isNormalStopReason(value: unknown): boolean {
|
|
@@ -60,10 +209,22 @@ function lastNonEmptyLine(text: string): string | null {
|
|
|
60
209
|
return null;
|
|
61
210
|
}
|
|
62
211
|
|
|
212
|
+
// A period only terminates a sentence when it ends a token: "bash.ts" and
|
|
213
|
+
// "package.json" must survive extraction intact.
|
|
214
|
+
const SENTENCE_TERMINATOR_PATTERN = /[.!?]+["'’)\]]*(?=\s|$)/gu;
|
|
215
|
+
|
|
63
216
|
function lastSentence(line: string): string {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
217
|
+
const trimmed = line.trimEnd();
|
|
218
|
+
let start = 0;
|
|
219
|
+
for (const terminator of trimmed.matchAll(SENTENCE_TERMINATOR_PATTERN)) {
|
|
220
|
+
const end = (terminator.index ?? 0) + terminator[0].length;
|
|
221
|
+
if (end >= trimmed.length) break;
|
|
222
|
+
start = end;
|
|
223
|
+
}
|
|
224
|
+
return trimmed
|
|
225
|
+
.slice(start)
|
|
226
|
+
.replace(/^[\s"'([{-]+/, "")
|
|
227
|
+
.trim();
|
|
67
228
|
}
|
|
68
229
|
|
|
69
230
|
function isQuestion(sentence: string): boolean {
|
|
@@ -165,7 +165,7 @@ export function autonomyAskRejection(
|
|
|
165
165
|
// directory listings.
|
|
166
166
|
...(actionClass === "execute"
|
|
167
167
|
? [
|
|
168
|
-
'A declared package
|
|
168
|
+
'A declared package or project-catalog check runs without approval through the verify tool: verify(check="<id>").',
|
|
169
169
|
"Read-only inspection runs without approval through the ls, read, grep, and find tools.",
|
|
170
170
|
]
|
|
171
171
|
: []),
|
|
@@ -61,6 +61,14 @@ export const DEFAULT_DAMAGE_CONTROL_PATH_POLICY: PathPolicyInput = {
|
|
|
61
61
|
"*.dump",
|
|
62
62
|
],
|
|
63
63
|
readOnlyPaths: [
|
|
64
|
+
// Clio's own operator-authority files in the project. `safety.yaml` is the
|
|
65
|
+
// policy that gates the model's tools, and `verifiers.yaml` is an argv
|
|
66
|
+
// catalog that verify() executes; a model that can author either one can
|
|
67
|
+
// widen its own permissions in two tool calls. Both are authored by the
|
|
68
|
+
// operator through the CLI (`clio-coder verifiers author` lands nothing
|
|
69
|
+
// before `--yes`), so model-side writes and deletes are refused.
|
|
70
|
+
".clio-coder/safety.yaml",
|
|
71
|
+
".clio-coder/verifiers.yaml",
|
|
64
72
|
"/etc/",
|
|
65
73
|
"/usr/",
|
|
66
74
|
"/bin/",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ToolNames } from "../../core/tool-names.js";
|
|
2
|
-
import { isVerificationScriptName } from "../../core/verification-scripts.js";
|
|
2
|
+
import { isProjectVerifierCheckId, isVerificationScriptName } from "../../core/verification-scripts.js";
|
|
3
3
|
import {
|
|
4
4
|
detectValidationCommand,
|
|
5
5
|
extractCommandDeleteTargets,
|
|
@@ -399,8 +399,9 @@ export function typedValidationSummary(toolName: string, payload: Record<string,
|
|
|
399
399
|
const path = typeof args?.path === "string" && args.path.trim().length > 0 ? args.path.trim() : "artifact";
|
|
400
400
|
return `verify frontend ${path}`;
|
|
401
401
|
}
|
|
402
|
-
if (
|
|
403
|
-
return `
|
|
402
|
+
if (isVerificationScriptName(check)) return `npm run ${check}`;
|
|
403
|
+
if (isProjectVerifierCheckId(check)) return `verify ${check}`;
|
|
404
|
+
return null;
|
|
404
405
|
}
|
|
405
406
|
return null;
|
|
406
407
|
}
|
|
@@ -4,6 +4,7 @@ import { artifactDefaultPath } from "../../core/artifact-paths.js";
|
|
|
4
4
|
import { canonicalizeExistingPath } from "../../core/path-canonical.js";
|
|
5
5
|
import { ToolNames } from "../../core/tool-names.js";
|
|
6
6
|
import { clioConfigDir } from "../../core/xdg.js";
|
|
7
|
+
import { type DeclaredCheck, loadProjectVerifierCatalog } from "../../tools/verify/catalog.js";
|
|
7
8
|
import { type ActionClass, type Classification, type ClassifierCall, classify } from "./action-classifier.js";
|
|
8
9
|
import type { DamageControlMatch, DamageControlRule } from "./damage-control.js";
|
|
9
10
|
import { DEFAULT_DAMAGE_CONTROL_PATH_POLICY, mergePathPolicyInputs } from "./default-path-policy.js";
|
|
@@ -225,7 +226,9 @@ export function createSafetyPolicyEngine(options: SafetyPolicyEngineOptions = {}
|
|
|
225
226
|
const rawClassification = classify(call);
|
|
226
227
|
const command = commandArg(call.args);
|
|
227
228
|
const callCwd = cwdArg(call.args, cwd);
|
|
228
|
-
const
|
|
229
|
+
const catalogCheck = resolveVerifyCatalogCheck(call, cwd);
|
|
230
|
+
const catalogCommand = catalogCheck === null ? null : catalogCheck.command.join(" ");
|
|
231
|
+
const scan = catalogCommand ?? damageControlScan(call);
|
|
229
232
|
const hit = scan ? matchSourcedRule(scan, sourcedRules) : null;
|
|
230
233
|
const classification = effectiveClassification(rawClassification, hit?.match);
|
|
231
234
|
|
|
@@ -335,8 +338,9 @@ export function createSafetyPolicyEngine(options: SafetyPolicyEngineOptions = {}
|
|
|
335
338
|
// command; the one carve-out is the exit-code-only presence check
|
|
336
339
|
// (`grep -q`/`grep -sq` with a ^NAME= pattern), which is the safe
|
|
337
340
|
// protocol the credentials skill teaches.
|
|
338
|
-
|
|
339
|
-
|
|
341
|
+
const scannedCommand = call.tool === ToolNames.Bash ? command : catalogCommand;
|
|
342
|
+
if (scannedCommand !== null) {
|
|
343
|
+
const secretRead = evaluateBashZeroAccessRead(zeroAccessPolicy, scannedCommand, callCwd);
|
|
340
344
|
if (secretRead !== null) {
|
|
341
345
|
const blockInput: Omit<
|
|
342
346
|
SafetyPolicyDecision,
|
|
@@ -407,9 +411,22 @@ export function createSafetyPolicyEngine(options: SafetyPolicyEngineOptions = {}
|
|
|
407
411
|
};
|
|
408
412
|
if (hit?.match.ruleId !== undefined) allowInput.ruleId = hit.match.ruleId;
|
|
409
413
|
if (hit?.match !== undefined) allowInput.match = hit.match;
|
|
410
|
-
// The typed execution tool (verify)
|
|
411
|
-
//
|
|
412
|
-
|
|
414
|
+
// The typed execution tool (verify) sits in the no-prompt set when the
|
|
415
|
+
// check is a package script or the frontend validator: both are bounded
|
|
416
|
+
// by the verification-script family and run a fixed argv shape. A
|
|
417
|
+
// project-catalog check runs whatever argv the workspace file declares,
|
|
418
|
+
// so it is unrecognized: auto-edit parks it for one confirmation that
|
|
419
|
+
// shows the argv, full-auto runs it, and the damage-control match and
|
|
420
|
+
// zero-access scan above have already seen the declared vector.
|
|
421
|
+
if (classification.actionClass === "execute") {
|
|
422
|
+
allowInput.execRecognition = catalogCheck === null ? "recognized" : "unrecognized";
|
|
423
|
+
if (catalogCheck !== null) {
|
|
424
|
+
allowInput.reasons = [
|
|
425
|
+
...allowInput.reasons,
|
|
426
|
+
`project verifier '${catalogCheck.id}' runs declared argv: ${catalogCommand ?? ""}`,
|
|
427
|
+
];
|
|
428
|
+
}
|
|
429
|
+
}
|
|
413
430
|
return allowDecision(base, allowInput);
|
|
414
431
|
},
|
|
415
432
|
metadata() {
|
|
@@ -958,6 +975,31 @@ function damageControlScan(call: ClassifierCall): string {
|
|
|
958
975
|
return typeof pathArg === "string" ? pathArg : "";
|
|
959
976
|
}
|
|
960
977
|
|
|
978
|
+
/**
|
|
979
|
+
* The project-catalog check a verify call resolves to, or null for a listing,
|
|
980
|
+
* the frontend validator, a package script, or an id the catalog does not
|
|
981
|
+
* declare. The catalog is an argv vector the operator authored, but the file
|
|
982
|
+
* sits in the workspace, so the engine reads it fresh on every verify call and
|
|
983
|
+
* treats the declared argv exactly like a bash command string: it is what the
|
|
984
|
+
* damage-control rules and the zero-access read guard scan, and it is not in
|
|
985
|
+
* the no-prompt set. An unreadable or invalid catalog resolves to null here and
|
|
986
|
+
* fails closed in the tool itself.
|
|
987
|
+
*/
|
|
988
|
+
function resolveVerifyCatalogCheck(call: ClassifierCall, workspaceRoot: string): DeclaredCheck | null {
|
|
989
|
+
if (call.tool !== ToolNames.Verify) return null;
|
|
990
|
+
const check = call.args?.check;
|
|
991
|
+
if (typeof check !== "string") return null;
|
|
992
|
+
const id = check.trim();
|
|
993
|
+
if (id.length === 0 || id === "frontend") return null;
|
|
994
|
+
try {
|
|
995
|
+
const catalog = loadProjectVerifierCatalog(workspaceRoot);
|
|
996
|
+
if (!catalog.ok || catalog.source === null) return null;
|
|
997
|
+
return catalog.source.checks.find((candidate) => candidate.id === id) ?? null;
|
|
998
|
+
} catch {
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
961
1003
|
function serializeArgs(args?: Record<string, unknown>): string {
|
|
962
1004
|
if (!args) return "";
|
|
963
1005
|
const parts: string[] = [];
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
import { stream } from "../../../engine/ai.js";
|
|
14
14
|
import type { EngineModel, Usage } from "../../../engine/types.js";
|
|
15
|
+
import { foldWorkingSet } from "../../context/working-set/fold.js";
|
|
16
|
+
import { recallableRefListing } from "../../context/working-set/recall.js";
|
|
15
17
|
import type { CompactionUsage, SessionEntry } from "../entries.js";
|
|
16
18
|
import { serializeConversation } from "./branch-summary.js";
|
|
17
19
|
import { findCutPoint } from "./cut-point.js";
|
|
@@ -307,6 +309,23 @@ function formatFileOperations(fileOps: FileOperations): string {
|
|
|
307
309
|
return sections.length > 0 ? `\n\n${sections.join("\n\n")}` : "";
|
|
308
310
|
}
|
|
309
311
|
|
|
312
|
+
function formatRecallableRefs(entries: ReadonlyArray<SessionEntry>, firstKeptEntryIndex: number): string {
|
|
313
|
+
const entryIndexes = new Map<string, number>();
|
|
314
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
315
|
+
const entry = entries[index];
|
|
316
|
+
if (entry) entryIndexes.set(entry.turnId, index);
|
|
317
|
+
}
|
|
318
|
+
const view = foldWorkingSet(entries);
|
|
319
|
+
const evictedBeforeCut = new Map(
|
|
320
|
+
[...view.evicted].filter(([ref]) => (entryIndexes.get(ref) ?? Number.POSITIVE_INFINITY) < firstKeptEntryIndex),
|
|
321
|
+
);
|
|
322
|
+
const listing = recallableRefListing(entries, { ...view, evicted: evictedBeforeCut });
|
|
323
|
+
if (listing.refs.length === 0) return "";
|
|
324
|
+
const rows = [...listing.refs];
|
|
325
|
+
if (listing.remaining > 0) rows.push(`and ${listing.remaining} more`);
|
|
326
|
+
return `\n\n<recallable-refs>\n${rows.join("\n")}\n</recallable-refs>`;
|
|
327
|
+
}
|
|
328
|
+
|
|
310
329
|
function numberOrZero(value: unknown): number {
|
|
311
330
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
312
331
|
}
|
|
@@ -457,7 +476,10 @@ export async function compact(input: CompactInput): Promise<CompactResult> {
|
|
|
457
476
|
}
|
|
458
477
|
}
|
|
459
478
|
|
|
460
|
-
const summary = `${summaryParts.join("\n\n---\n\n").trim()}${formatFileOperations(fileOps)}
|
|
479
|
+
const summary = `${summaryParts.join("\n\n---\n\n").trim()}${formatFileOperations(fileOps)}${formatRecallableRefs(
|
|
480
|
+
input.entries,
|
|
481
|
+
cut.firstKeptEntryIndex,
|
|
482
|
+
)}`.trim();
|
|
461
483
|
|
|
462
484
|
return {
|
|
463
485
|
summary,
|
|
@@ -34,7 +34,18 @@ export interface TokenEstimator {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function estimateMessage(entry: MessageEntry): number {
|
|
37
|
-
|
|
37
|
+
// Usage invalidation is bookkeeping for token-anchor selection. The replay
|
|
38
|
+
// builder carries it as AgentMessage metadata, but providers never see it in
|
|
39
|
+
// message content, so it cannot change a projected entry's prompt cost. Drop
|
|
40
|
+
// it here so plan-time pricing and the post-event projection use one byte
|
|
41
|
+
// definition even though only the latter has applied the stamp.
|
|
42
|
+
if (entry.role !== "assistant" || !entry.payload || typeof entry.payload !== "object") {
|
|
43
|
+
return estimateAgentMessageTokens(entry);
|
|
44
|
+
}
|
|
45
|
+
const payload = entry.payload as Record<string, unknown>;
|
|
46
|
+
if (!("contextUsageInvalidated" in payload)) return estimateAgentMessageTokens(entry);
|
|
47
|
+
const { contextUsageInvalidated: _stamp, ...promptPayload } = payload;
|
|
48
|
+
return estimateAgentMessageTokens({ ...entry, payload: promptPayload });
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
function estimateBashExecution(entry: BashExecutionEntry): number {
|
|
@@ -78,6 +89,10 @@ export function estimateTokens(entry: SessionEntry): number {
|
|
|
78
89
|
case "taskLedger":
|
|
79
90
|
case "decisionLedger":
|
|
80
91
|
case "workerRun":
|
|
92
|
+
// Working-set bookkeeping: refs and markers, never bodies. The
|
|
93
|
+
// projection accounts for marker cost on the projected messages.
|
|
94
|
+
case "contextEviction":
|
|
95
|
+
case "contextRecall":
|
|
81
96
|
return 0;
|
|
82
97
|
}
|
|
83
98
|
}
|
|
@@ -122,6 +122,8 @@ export interface PromptCacheStats {
|
|
|
122
122
|
* combination the overlay renders as a warning.
|
|
123
123
|
*/
|
|
124
124
|
backendVerdict: "hot" | "partial" | "cold" | "small" | null;
|
|
125
|
+
/** Cache disturbances Clio expected before the last settled run. */
|
|
126
|
+
expectedColdReasons?: ReadonlyArray<string>;
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
export interface ContextLedgerGroup {
|
|
@@ -276,6 +276,75 @@ export interface WorkerRunEntry extends BaseSessionEntry {
|
|
|
276
276
|
parentToolCallId?: string;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Working-set layer (context domain) ledger records. Eviction is a projection:
|
|
281
|
+
* these entries say what left the model's working set and what came back; the
|
|
282
|
+
* original bodies stay in the ledger untouched. The context domain owns the
|
|
283
|
+
* semantics (`src/domains/context/working-set/contract.ts`); the session
|
|
284
|
+
* domain owns the wire shape because it owns the ledger format.
|
|
285
|
+
*/
|
|
286
|
+
export const EVICTION_REASONS = [
|
|
287
|
+
"superseded_read",
|
|
288
|
+
"stale_after_mutation",
|
|
289
|
+
"listing_consumed",
|
|
290
|
+
"failure_resolved",
|
|
291
|
+
"thinking_turn_closed",
|
|
292
|
+
"age_horizon",
|
|
293
|
+
"operator",
|
|
294
|
+
] as const;
|
|
295
|
+
export type EvictionReason = (typeof EVICTION_REASONS)[number];
|
|
296
|
+
|
|
297
|
+
export const EVICTION_TRIGGERS = ["pressure", "operator"] as const;
|
|
298
|
+
export type EvictionTrigger = (typeof EVICTION_TRIGGERS)[number];
|
|
299
|
+
|
|
300
|
+
export const RECALL_TRIGGERS = ["tool", "operator"] as const;
|
|
301
|
+
export type RecallTrigger = (typeof RECALL_TRIGGERS)[number];
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Identity of an evictable unit: the `turnId` of a ledger entry. For a
|
|
305
|
+
* `tool_result` message the unit is the result body; for an `assistant`
|
|
306
|
+
* message the unit is every thinking block it carries. Partial (per-block)
|
|
307
|
+
* eviction is deliberately not modelled; add a `block` field here when it is.
|
|
308
|
+
*/
|
|
309
|
+
export interface WorkingSetRef {
|
|
310
|
+
entry: string;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export interface EvictedItem {
|
|
314
|
+
ref: WorkingSetRef;
|
|
315
|
+
reason: EvictionReason;
|
|
316
|
+
/** Estimated tokens the projection removes for this item (marker cost already subtracted). */
|
|
317
|
+
tokensFreed: number;
|
|
318
|
+
/**
|
|
319
|
+
* Byte-stable one-line stub the projection renders in place of the body.
|
|
320
|
+
* Empty for thinking-block eviction, which removes without a marker.
|
|
321
|
+
*/
|
|
322
|
+
marker: string;
|
|
323
|
+
/** Ref key of the entry that superseded or resolved this one, when the reason names one. */
|
|
324
|
+
by?: string;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export interface ContextEvictionEntry extends BaseSessionEntry {
|
|
328
|
+
kind: "contextEviction";
|
|
329
|
+
policyId: string;
|
|
330
|
+
trigger: EvictionTrigger;
|
|
331
|
+
evicted: ReadonlyArray<EvictedItem>;
|
|
332
|
+
tokensBefore: number;
|
|
333
|
+
tokensAfter: number;
|
|
334
|
+
/** Used/window ratio that fired the event; null for operator-triggered events. */
|
|
335
|
+
pressureBefore: number | null;
|
|
336
|
+
snapshotIdBefore: string | null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export interface ContextRecallEntry extends BaseSessionEntry {
|
|
340
|
+
kind: "contextRecall";
|
|
341
|
+
ref: WorkingSetRef;
|
|
342
|
+
trigger: RecallTrigger;
|
|
343
|
+
tokensReadmitted: number;
|
|
344
|
+
/** The tool call that performed the recall, when `trigger` is `tool`. */
|
|
345
|
+
toolCallId?: string;
|
|
346
|
+
}
|
|
347
|
+
|
|
279
348
|
export type SessionEntry =
|
|
280
349
|
| MessageEntry
|
|
281
350
|
| BashExecutionEntry
|
|
@@ -291,7 +360,9 @@ export type SessionEntry =
|
|
|
291
360
|
| SkillActivationEntry
|
|
292
361
|
| TaskLedgerEntry
|
|
293
362
|
| DecisionLedgerEntry
|
|
294
|
-
| WorkerRunEntry
|
|
363
|
+
| WorkerRunEntry
|
|
364
|
+
| ContextEvictionEntry
|
|
365
|
+
| ContextRecallEntry;
|
|
295
366
|
|
|
296
367
|
export type SessionFileEntry = SessionHeader | SessionEntry;
|
|
297
368
|
|
|
@@ -317,6 +388,8 @@ export const SESSION_ENTRY_KINDS = [
|
|
|
317
388
|
"taskLedger",
|
|
318
389
|
"decisionLedger",
|
|
319
390
|
"workerRun",
|
|
391
|
+
"contextEviction",
|
|
392
|
+
"contextRecall",
|
|
320
393
|
] as const;
|
|
321
394
|
|
|
322
395
|
export type SessionEntryKind = (typeof SESSION_ENTRY_KINDS)[number];
|
|
@@ -477,6 +550,21 @@ export function isSessionHeader(value: unknown): value is SessionHeader {
|
|
|
477
550
|
);
|
|
478
551
|
}
|
|
479
552
|
|
|
553
|
+
function isWorkingSetRef(value: unknown): value is WorkingSetRef {
|
|
554
|
+
return isRecord(value) && isString(value.entry);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function isEvictedItem(value: unknown): value is EvictedItem {
|
|
558
|
+
return (
|
|
559
|
+
isRecord(value) &&
|
|
560
|
+
isWorkingSetRef(value.ref) &&
|
|
561
|
+
isOneOf(value.reason, EVICTION_REASONS) &&
|
|
562
|
+
isNumber(value.tokensFreed) &&
|
|
563
|
+
isString(value.marker) &&
|
|
564
|
+
isOptionalString(value.by)
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
480
568
|
export function isSessionEntry(value: unknown): value is SessionEntry {
|
|
481
569
|
if (!value || typeof value !== "object") return false;
|
|
482
570
|
const v = value as Record<string, unknown>;
|
|
@@ -567,6 +655,24 @@ export function isSessionEntry(value: unknown): value is SessionEntry {
|
|
|
567
655
|
isWorkerRunRuntime(v.runtime) &&
|
|
568
656
|
isOptionalString(v.parentToolCallId)
|
|
569
657
|
);
|
|
658
|
+
case "contextEviction":
|
|
659
|
+
return (
|
|
660
|
+
isString(v.policyId) &&
|
|
661
|
+
isOneOf(v.trigger, EVICTION_TRIGGERS) &&
|
|
662
|
+
Array.isArray(v.evicted) &&
|
|
663
|
+
v.evicted.every(isEvictedItem) &&
|
|
664
|
+
isNumber(v.tokensBefore) &&
|
|
665
|
+
isNumber(v.tokensAfter) &&
|
|
666
|
+
(v.pressureBefore === null || isNumber(v.pressureBefore)) &&
|
|
667
|
+
(v.snapshotIdBefore === null || isString(v.snapshotIdBefore))
|
|
668
|
+
);
|
|
669
|
+
case "contextRecall":
|
|
670
|
+
return (
|
|
671
|
+
isWorkingSetRef(v.ref) &&
|
|
672
|
+
isOneOf(v.trigger, RECALL_TRIGGERS) &&
|
|
673
|
+
isNumber(v.tokensReadmitted) &&
|
|
674
|
+
isOptionalString(v.toolCallId)
|
|
675
|
+
);
|
|
570
676
|
}
|
|
571
677
|
return false;
|
|
572
678
|
}
|
|
@@ -70,9 +70,16 @@ export function resumeSessionState(sessionId: string): {
|
|
|
70
70
|
} {
|
|
71
71
|
const persistedMeta = readSessionMeta(sessionId) as SessionMeta;
|
|
72
72
|
const paths = sessionPaths(persistedMeta);
|
|
73
|
-
runMigrations(persistedMeta, paths.meta);
|
|
73
|
+
const migration = runMigrations(persistedMeta, paths.meta);
|
|
74
74
|
const { meta, writer, tree } = engineResumeSession(sessionId);
|
|
75
|
-
|
|
75
|
+
const state: SessionManagerState = { meta: meta as SessionMeta, writer };
|
|
76
|
+
if (migration.migrated) {
|
|
77
|
+
// The ledger needed no transformation; only the stamp moves, so the next
|
|
78
|
+
// reader does not re-run the same no-op.
|
|
79
|
+
state.meta.sessionFormatVersion = migration.to;
|
|
80
|
+
persistSessionMeta(state);
|
|
81
|
+
}
|
|
82
|
+
return { state, nodes: tree };
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
export function appendTurn(state: SessionManagerState, input: TurnInput): ClioTurnRecord {
|