@iowarp/clio-coder 0.3.3 → 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.
Files changed (244) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +3 -3
  4. package/dist/{acp-P2AQILE2.js → acp-S5R4RR5B.js} +7 -6
  5. package/dist/{agents-72W3BI7I.js → agents-P6DMMVZY.js} +24 -21
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-5TWEIYDN.js → auth-2XCZLPKS.js} +12 -8
  8. package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
  9. package/dist/{chunk-2DJ2KNFG.js → chunk-2LZI5CAG.js} +133 -13
  10. package/dist/{chunk-OAO4GE4M.js → chunk-2TZWSW76.js} +2 -2
  11. package/dist/{chunk-OOJYHWRB.js → chunk-34475P3I.js} +2 -2
  12. package/dist/{chunk-4XUGQOHA.js → chunk-35MKKU5R.js} +4 -4
  13. package/dist/{chunk-LM5TQCJZ.js → chunk-3HZ5RWN2.js} +5 -5
  14. package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
  15. package/dist/{chunk-KZWTDYJF.js → chunk-4JUF2NNX.js} +7 -7
  16. package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
  17. package/dist/chunk-5M54SPOL.js +926 -0
  18. package/dist/{chunk-STBPMHSX.js → chunk-7RXG6QRZ.js} +51 -11
  19. package/dist/{chunk-X6IAEBZR.js → chunk-A2GZF7DC.js} +5 -5
  20. package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
  21. package/dist/chunk-AOCYTWAV.js +449 -0
  22. package/dist/chunk-BEY543CS.js +258 -0
  23. package/dist/{chunk-6N5PTWMY.js → chunk-BP4OYD6A.js} +32 -13
  24. package/dist/chunk-BPGS2WCQ.js +612 -0
  25. package/dist/{chunk-V6RTAOC2.js → chunk-BRXQQJFP.js} +8 -8
  26. package/dist/chunk-CFGTUFWB.js +67 -0
  27. package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
  28. package/dist/{chunk-DUYJ5IO6.js → chunk-EDRHSCIE.js} +4 -4
  29. package/dist/{chunk-XBXAASKX.js → chunk-EFADSJET.js} +2 -2
  30. package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
  31. package/dist/chunk-FYYLNIL5.js +313 -0
  32. package/dist/{chunk-FNTMWMX5.js → chunk-HV5X7OR2.js} +14 -12
  33. package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
  34. package/dist/{chunk-5UFT4SUX.js → chunk-K6WL7QZT.js} +3 -3
  35. package/dist/chunk-K7VKOLQQ.js +15 -0
  36. package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
  37. package/dist/{chunk-OC7FIQPC.js → chunk-KRPY7NTG.js} +10 -7
  38. package/dist/chunk-LL4KHSZI.js +22 -0
  39. package/dist/{chunk-DSELYM6W.js → chunk-MEQ45TQ4.js} +15 -9
  40. package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
  41. package/dist/{chunk-6SGHMWE3.js → chunk-N4CZJQRK.js} +5 -5
  42. package/dist/{chunk-TZK7PACC.js → chunk-NILBFAPG.js} +14 -8
  43. package/dist/chunk-OZNBF4L3.js +23 -0
  44. package/dist/{verify-375KUB3Y.js → chunk-PCZJO5TI.js} +127 -42
  45. package/dist/chunk-QQK64KLB.js +1360 -0
  46. package/dist/{chunk-SRDMMSEP.js → chunk-QQL5RT5M.js} +979 -1619
  47. package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
  48. package/dist/{chunk-2TLUCQVG.js → chunk-RD5U66HV.js} +3 -3
  49. package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
  50. package/dist/{chunk-OQ33BKR3.js → chunk-TTNYS3EA.js} +3 -60
  51. package/dist/chunk-TW3WDMVS.js +677 -0
  52. package/dist/chunk-TZSKNMZG.js +434 -0
  53. package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
  54. package/dist/{chunk-PIWWS5BL.js → chunk-UZHIZC5S.js} +7 -7
  55. package/dist/{chunk-UFIIWP2H.js → chunk-VAWWTKDP.js} +8 -8
  56. package/dist/{chunk-COU2UHX6.js → chunk-VEZEGCGW.js} +170 -2
  57. package/dist/{chunk-LW6DSM3M.js → chunk-VMNQ6OZA.js} +98 -202
  58. package/dist/chunk-VSNATDE6.js +122 -0
  59. package/dist/chunk-W6GROXXM.js +69 -0
  60. package/dist/chunk-WPQLXFOZ.js +375 -0
  61. package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
  62. package/dist/{chunk-PAJK6MAQ.js → chunk-X6COSD2O.js} +5 -5
  63. package/dist/chunk-ZGVHUX3M.js +66 -0
  64. package/dist/{chunk-LWLEKMDQ.js → chunk-ZYKPLLNQ.js} +510 -547
  65. package/dist/cli/index.js +27 -23
  66. package/dist/{clio-JOU4FXVA.js → clio-J5JIOIDS.js} +7 -6
  67. package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
  68. package/dist/{config-XCDVKR23.js → config-OEBMIN2U.js} +37 -27
  69. package/dist/{configure-4GAP54ZW.js → configure-PUQOSIXQ.js} +16 -13
  70. package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
  71. package/dist/{context-4UOGGLQ5.js → context-MGSE4Z2T.js} +33 -23
  72. package/dist/{context-77FM5DV5.js → context-URSXPBCK.js} +17 -9
  73. package/dist/{context-clear-XXJRLCJJ.js → context-clear-KDAJRNUK.js} +33 -23
  74. package/dist/context-working-set-SBKMPPI2.js +1552 -0
  75. package/dist/{dispatch-runner-QPRDDBDX.js → dispatch-runner-MSWN72NK.js} +43 -29
  76. package/dist/{doctor-HR46URBJ.js → doctor-7BSE27PJ.js} +10 -10
  77. package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
  78. package/dist/{evidence-6HG2PY2B.js → evidence-SR7WXB5B.js} +51 -23
  79. package/dist/{evolve-K7YU3NCY.js → evolve-K7VE2CBX.js} +30 -20
  80. package/dist/{fleet-VY3HHKN6.js → fleet-7XMJNQNF.js} +48 -38
  81. package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
  82. package/dist/{init-JYGXI3FK.js → init-JGNPAYXT.js} +41 -31
  83. package/dist/{memory-WFZMGYHX.js → memory-4ALKDJ4Q.js} +32 -22
  84. package/dist/{models-I5QWSEOM.js → models-ZMMLFJNN.js} +22 -19
  85. package/dist/{monitor-GE4ID3IA.js → monitor-2F3T5KHP.js} +55 -43
  86. package/dist/{orchestrator-EM5MC3HM.js → orchestrator-ORHT43JB.js} +572 -388
  87. package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
  88. package/dist/{run-ZU3QMZPZ.js → run-RF4WJGMT.js} +51 -41
  89. package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
  90. package/dist/{skills-X5VXCRNQ.js → skills-PSACKC5Q.js} +2 -2
  91. package/dist/{skills-eval-WKIHWTHR.js → skills-eval-WJSI55RZ.js} +34 -24
  92. package/dist/{targets-SNCPI2NR.js → targets-PIIRAOYS.js} +23 -20
  93. package/dist/{terminal-lease-BNAHVHBS.js → terminal-lease-ULWXWNVY.js} +4 -3
  94. package/dist/{upgrade-JQHHPQ4K.js → upgrade-346TZ6AV.js} +18 -17
  95. package/dist/{usage-OR4O5SMZ.js → usage-6KKXR32N.js} +34 -24
  96. package/dist/verifiers-4UUM6TEE.js +1214 -0
  97. package/dist/verify-X5HDROLA.js +25 -0
  98. package/dist/{wiki-generate-UEXP2ARI.js → wiki-generate-7STOCIFZ.js} +42 -31
  99. package/dist/worker/entry.js +33 -24
  100. package/docs/README.md +7 -6
  101. package/docs/acp.md +1 -1
  102. package/docs/alcf-provider.md +1 -1
  103. package/docs/architecture.md +2 -2
  104. package/docs/artifact-versions.md +1 -1
  105. package/docs/built-in-agents.md +1 -1
  106. package/docs/capacity-and-scheduling.md +1 -1
  107. package/docs/commands-and-modes.md +53 -21
  108. package/docs/config-knobs-audit.md +1 -2
  109. package/docs/configuration-and-targets.md +15 -1
  110. package/docs/context-engine.md +64 -12
  111. package/docs/context-working-set.md +194 -0
  112. package/docs/development-pipeline.md +1 -1
  113. package/docs/documentation-coverage.md +5 -5
  114. package/docs/documentation-guide.md +6 -5
  115. package/docs/environment-variables.md +2 -1
  116. package/docs/eval-runner.md +1 -1
  117. package/docs/evals-internal.md +14 -1
  118. package/docs/evidence-and-memory.md +74 -2
  119. package/docs/evolution.md +1 -1
  120. package/docs/exit-codes-and-output.md +1 -1
  121. package/docs/extensions-and-sharing.md +2 -2
  122. package/docs/fleet-dispatch.md +22 -7
  123. package/docs/glossary.md +21 -1
  124. package/docs/installation-and-lifecycle.md +2 -2
  125. package/docs/middleware-and-components.md +1 -1
  126. package/docs/model-catalog.md +7 -9
  127. package/docs/observability.md +4 -4
  128. package/docs/proactive-memory.md +1 -1
  129. package/docs/prompt-envelope-and-tools.md +4 -4
  130. package/docs/provider-adapter-cookbook.md +1 -1
  131. package/docs/release-cut-checklist.md +31 -31
  132. package/docs/safety-model.md +23 -4
  133. package/docs/scientific-validation.md +21 -3
  134. package/docs/session-lifecycle.md +3 -3
  135. package/docs/skills-marketplace.md +1 -1
  136. package/docs/tool-usage.md +79 -12
  137. package/docs/trace-store.md +1 -1
  138. package/docs/troubleshooting.md +1 -1
  139. package/docs/tui-design.md +1 -1
  140. package/docs/worker-dispatch-mechanics.md +11 -1
  141. package/package.json +8 -11
  142. package/skills/meta/clio-test/SKILL.md +20 -17
  143. package/skills/meta/clio-test/evals.md +3 -3
  144. package/skills/meta/clio-test/references/harness.md +35 -6
  145. package/skills/meta/clio-test/references/test-map.md +20 -10
  146. package/skills/registry.yaml +2 -2
  147. package/skills/skill-marketplace.json +1 -1
  148. package/src/cli/context-working-set.ts +513 -0
  149. package/src/cli/context.ts +8 -0
  150. package/src/cli/evidence.ts +20 -2
  151. package/src/cli/index.ts +4 -0
  152. package/src/cli/verifiers.ts +325 -0
  153. package/src/core/bash-exec.ts +39 -14
  154. package/src/core/bus-events.ts +19 -4
  155. package/src/core/config.ts +54 -0
  156. package/src/core/defaults.ts +50 -3
  157. package/src/core/verification-scripts.ts +6 -0
  158. package/src/domains/agents/builtins/verifier.md +3 -0
  159. package/src/domains/config/classify.ts +1 -0
  160. package/src/domains/context/working-set/contract.ts +161 -0
  161. package/src/domains/context/working-set/defaults.ts +28 -0
  162. package/src/domains/context/working-set/engine.ts +203 -0
  163. package/src/domains/context/working-set/fold.ts +62 -0
  164. package/src/domains/context/working-set/horizon.ts +38 -0
  165. package/src/domains/context/working-set/marker.ts +103 -0
  166. package/src/domains/context/working-set/path-index.ts +436 -0
  167. package/src/domains/context/working-set/payload.ts +152 -0
  168. package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
  169. package/src/domains/context/working-set/policies/index.ts +21 -0
  170. package/src/domains/context/working-set/policies/structural.ts +160 -0
  171. package/src/domains/context/working-set/project.ts +132 -0
  172. package/src/domains/context/working-set/protect.ts +109 -0
  173. package/src/domains/context/working-set/recall.ts +177 -0
  174. package/src/domains/context/working-set/replay/controls.ts +112 -0
  175. package/src/domains/context/working-set/replay/load-clio.ts +199 -0
  176. package/src/domains/context/working-set/replay/metrics.ts +185 -0
  177. package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
  178. package/src/domains/context/working-set/replay/report.ts +139 -0
  179. package/src/domains/context/working-set/replay/runner.ts +325 -0
  180. package/src/domains/context/working-set/replay/synthetic.ts +422 -0
  181. package/src/domains/context/working-set/replay/trace.ts +21 -0
  182. package/src/domains/context/working-set/visible.ts +54 -0
  183. package/src/domains/evidence/build.ts +112 -45
  184. package/src/domains/evidence/eval.ts +24 -7
  185. package/src/domains/evidence/index.ts +53 -0
  186. package/src/domains/evidence/ordering.ts +12 -0
  187. package/src/domains/evidence/run-trust.ts +221 -0
  188. package/src/domains/evidence/store.ts +46 -6
  189. package/src/domains/evidence/trust-status.ts +854 -0
  190. package/src/domains/evidence/types.ts +26 -0
  191. package/src/domains/middleware/memory-intervention.ts +3 -0
  192. package/src/domains/middleware/stalled-turn.ts +165 -4
  193. package/src/domains/safety/autonomy.ts +1 -1
  194. package/src/domains/safety/default-path-policy.ts +8 -0
  195. package/src/domains/safety/finish-contract.ts +4 -3
  196. package/src/domains/safety/policy-engine.ts +48 -6
  197. package/src/domains/session/compaction/compact.ts +23 -1
  198. package/src/domains/session/compaction/cut-point.ts +2 -0
  199. package/src/domains/session/compaction/tokens.ts +16 -1
  200. package/src/domains/session/context-ledger.ts +2 -0
  201. package/src/domains/session/entries.ts +107 -1
  202. package/src/domains/session/manager.ts +9 -2
  203. package/src/domains/session/migrations/index.ts +22 -3
  204. package/src/engine/acp/server.ts +3 -0
  205. package/src/engine/agent.ts +18 -1
  206. package/src/engine/session.ts +9 -3
  207. package/src/entry/orchestrator.ts +16 -4
  208. package/src/interactive/chat-loop-messages.ts +18 -6
  209. package/src/interactive/chat-panel.ts +17 -1
  210. package/src/interactive/chat-renderer.ts +30 -21
  211. package/src/interactive/context-meter.ts +10 -0
  212. package/src/interactive/context-overlay.ts +81 -6
  213. package/src/interactive/context-recall-command.ts +110 -0
  214. package/src/interactive/interactive-slash-runtime.ts +37 -1
  215. package/src/interactive/model-session-replay.ts +21 -0
  216. package/src/interactive/overlay-general-openers.ts +6 -0
  217. package/src/interactive/overlay-session-lifecycle.ts +8 -4
  218. package/src/interactive/renderers/tool-execution.ts +18 -2
  219. package/src/interactive/session-transcript.ts +2 -2
  220. package/src/interactive/slash-commands.ts +29 -2
  221. package/src/interactive/turn-context.ts +238 -88
  222. package/src/interactive/turn-middleware.ts +6 -6
  223. package/src/tools/agent-tools.ts +11 -4
  224. package/src/tools/bash.ts +144 -82
  225. package/src/tools/builtin-tool-catalog.ts +11 -5
  226. package/src/tools/context/index.ts +105 -3
  227. package/src/tools/context/surface.ts +3 -2
  228. package/src/tools/core-bootstrap.ts +21 -0
  229. package/src/tools/dispatch-runner.ts +9 -7
  230. package/src/tools/monitor.ts +28 -20
  231. package/src/tools/registry.ts +59 -7
  232. package/src/tools/result-disposition.ts +550 -0
  233. package/src/tools/result-shaping.ts +262 -19
  234. package/src/tools/safe-exec.ts +2 -0
  235. package/src/tools/verify/authoring.ts +1119 -0
  236. package/src/tools/verify/catalog.ts +346 -0
  237. package/src/tools/verify/index.ts +13 -3
  238. package/src/tools/verify/scripts.ts +135 -37
  239. package/src/tools/verify/surface.ts +9 -5
  240. package/src/tools/worker-evidence.ts +35 -12
  241. package/dist/chunk-J7CWMCQD.js +0 -255
  242. package/dist/chunk-T6YILFSB.js +0 -80
  243. package/dist/chunk-VAKQQHWR.js +0 -434
  244. package/dist/chunk-VPAYEGVX.js +0 -184
@@ -0,0 +1,110 @@
1
+ /**
2
+ * `/context recall <ref>`: the operator's half of working-set recall.
3
+ *
4
+ * The model recalls through `context(scope="recall", ref=...)`, which puts the
5
+ * body back in front of the model at the tail of the working set. This command
6
+ * answers a different question, asked by a person: what was in the result the
7
+ * marker replaced. So the body goes to the transcript and nowhere else. It is
8
+ * never submitted as a turn, never replayed, and never counted against the
9
+ * context window, which is why an operator can read a 40k-line build log back
10
+ * without paying for it.
11
+ *
12
+ * Everything else is identical to the tool path, deliberately: the same
13
+ * `resolveRecall` over the same fold at the same live leaf, the same
14
+ * `contextRecall` ledger entry (with `trigger: "operator"`), and the same
15
+ * `BusChannels.ContextRecalled` publication. A recall is a churn signal
16
+ * whoever asked for it, and a policy that keeps evicting something a human
17
+ * keeps reading back is a policy worth changing.
18
+ */
19
+
20
+ import type { ContextRecalledPayload } from "../core/bus-events.js";
21
+ import type { EvictedState } from "../domains/context/working-set/contract.js";
22
+ import { foldWorkingSet } from "../domains/context/working-set/fold.js";
23
+ import {
24
+ buildRecallFields,
25
+ recallErrorMessage,
26
+ recallParentTurnId,
27
+ resolveRecall,
28
+ } from "../domains/context/working-set/recall.js";
29
+ import type { SessionEntryInput } from "../domains/session/contract.js";
30
+ import type { SessionEntry } from "../domains/session/entries.js";
31
+
32
+ /** Ledger access the command needs, mirroring the context tool's `ContextSessionDeps`. */
33
+ export interface OperatorRecallDeps {
34
+ hasSession(): boolean;
35
+ readEntries(): ReadonlyArray<SessionEntry>;
36
+ /** The live append point (`/tree` pin or tree leaf); undefined lets the fold infer it. */
37
+ activeLeafTurnId(): string | undefined;
38
+ appendEntry(entry: SessionEntryInput): SessionEntry;
39
+ /** Publisher for `BusChannels.ContextRecalled`; the runtime supplies the bus. */
40
+ onRecalled?: (payload: ContextRecalledPayload) => void;
41
+ now?: () => number;
42
+ }
43
+
44
+ export type OperatorRecallOutcome =
45
+ | {
46
+ ok: true;
47
+ /** One-line summary for the notice bar. */
48
+ headline: string;
49
+ /** The original body, byte-exact, for the transcript. */
50
+ body: string;
51
+ }
52
+ | { ok: false; message: string };
53
+
54
+ function formatTokens(tokens: number): string {
55
+ return tokens.toLocaleString("en-US");
56
+ }
57
+
58
+ /**
59
+ * Ref, why it left, what it costs to read, and where the full artifact lives
60
+ * when the original result was offloaded. Nothing else: the body is on the next
61
+ * line and the operator is already looking at it.
62
+ */
63
+ function headlineFor(ref: string, tokens: number, state: EvictedState | undefined, offloadPath?: string): string {
64
+ const parts = [ref];
65
+ if (state !== undefined) {
66
+ parts.push(state.by === undefined ? `evicted: ${state.reason}` : `evicted: ${state.reason} by ${state.by}`);
67
+ }
68
+ parts.push(`${formatTokens(tokens)} tokens`);
69
+ if (offloadPath !== undefined) parts.push(`offload: ${offloadPath}`);
70
+ return `[/context recall] ${parts.join(" · ")}`;
71
+ }
72
+
73
+ export function runOperatorRecall(ref: string, deps: OperatorRecallDeps): OperatorRecallOutcome {
74
+ if (!deps.hasSession()) {
75
+ return { ok: false, message: "[/context recall] no active session; start one with /new or /resume first" };
76
+ }
77
+ const trimmed = ref.trim();
78
+ if (trimmed.length === 0) {
79
+ return { ok: false, message: "[/context recall] needs a ref: the turnId named in an [evicted ...] marker" };
80
+ }
81
+ const entries = deps.readEntries();
82
+ const leaf = deps.activeLeafTurnId();
83
+ const view = foldWorkingSet(entries, leaf);
84
+ const resolved = resolveRecall(entries, view, trimmed, leaf);
85
+ // A ref that resolves to nothing is usually a typo or a stale marker; the
86
+ // shared message ends with the refs the operator could have typed instead.
87
+ if (!resolved.ok)
88
+ return { ok: false, message: `[/context recall] ${recallErrorMessage(resolved.error, entries, view)}` };
89
+ const { result } = resolved;
90
+ const fields = buildRecallFields(result, { trigger: "operator" });
91
+ try {
92
+ deps.appendEntry({ ...fields, parentTurnId: recallParentTurnId(entries, leaf) });
93
+ } catch (err) {
94
+ return {
95
+ ok: false,
96
+ message: `[/context recall] recall of ${result.ref.entry} could not be recorded: ${err instanceof Error ? err.message : String(err)}`,
97
+ };
98
+ }
99
+ deps.onRecalled?.({
100
+ ref: result.ref.entry,
101
+ trigger: "operator",
102
+ tokensReadmitted: result.tokens,
103
+ at: deps.now?.() ?? Date.now(),
104
+ });
105
+ return {
106
+ ok: true,
107
+ headline: headlineFor(result.ref.entry, result.tokens, view.evicted.get(result.ref.entry), result.offloadPath),
108
+ body: result.body,
109
+ };
110
+ }
@@ -1,5 +1,6 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
+ import { BusChannels } from "../core/bus-events.js";
3
4
  import type { ClioSettings } from "../core/config.js";
4
5
  import type { SafeEventBus } from "../core/event-bus.js";
5
6
  import type { PendingSkillRequest } from "../core/skill-activation.js";
@@ -28,6 +29,7 @@ import { type ChatPanel, createChatPanel } from "./chat-panel.js";
28
29
  import { rehydrateChatPanelFromTurns } from "./chat-renderer.js";
29
30
  import { runCompactWithNotice } from "./command-fallbacks.js";
30
31
  import { appendNotice, appendOperatorCommand } from "./command-output.js";
32
+ import { runOperatorRecall } from "./context-recall-command.js";
31
33
  import { renderSessionHtml } from "./export-html/index.js";
32
34
  import { dateLocal } from "./format-time.js";
33
35
  import type { SettingsCenterRowId, SettingsSectionId } from "./overlays/settings.js";
@@ -100,8 +102,10 @@ export interface InteractiveSlashRuntimeDeps {
100
102
  * Leaf lookup for `/export`, so the export follows the branch the session
101
103
  * is actually on. current.jsonl still holds the abandoned turns after a
102
104
  * `/tree` pin, and an unscoped rehydrate reproduced them (issue #109).
105
+ * `/context recall` uses the same leaf for its fold, plus `appendEntry` for
106
+ * the `contextRecall` record.
103
107
  */
104
- session?: Pick<SessionContract, "tree">;
108
+ session?: Pick<SessionContract, "tree" | "current" | "appendEntry">;
105
109
  expandSubmit: (text: string) => Promise<InteractiveSlashSubmitExpansion>;
106
110
  openAskUser: AskUserHandler;
107
111
  openSkillsHub: () => void;
@@ -512,6 +516,38 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
512
516
  }
513
517
  deps.openContextReset();
514
518
  },
519
+ ...(deps.session
520
+ ? {
521
+ runContextRecall: (ref: string) => {
522
+ const session = deps.session;
523
+ if (!session) return;
524
+ const outcome = runOperatorRecall(ref, {
525
+ hasSession: () => session.current() !== null,
526
+ readEntries: () => {
527
+ const sessionId = deps.chat.getSessionId();
528
+ return sessionId ? deps.readStructuredEntries(sessionId) : [];
529
+ },
530
+ activeLeafTurnId: () => {
531
+ const meta = session.current();
532
+ return meta ? (session.tree(meta.id).leafId ?? undefined) : undefined;
533
+ },
534
+ appendEntry: (entry) => session.appendEntry(entry),
535
+ onRecalled: (payload) => deps.bus.emit(BusChannels.ContextRecalled, payload),
536
+ ...(deps.now ? { now: () => (deps.now?.() ?? new Date()).getTime() } : {}),
537
+ });
538
+ if (!outcome.ok) {
539
+ appendCommandNotice("error", outcome.message);
540
+ return;
541
+ }
542
+ appendCommandNotice("success", outcome.headline);
543
+ // The body is transcript output, not a notice: notices collapse
544
+ // newlines, and a recalled build log is worth nothing on one line.
545
+ // It is a replay block either way, so it never enters model context.
546
+ deps.io.stdout(`${outcome.body}\n`);
547
+ deps.requestRender();
548
+ },
549
+ }
550
+ : {}),
515
551
  runContextRefresh: () => {
516
552
  if (!deps.onContextRefresh) {
517
553
  deps.io.stderr("[/context refresh] context refresh not wired; pass onContextRefresh to startInteractive\n");
@@ -0,0 +1,21 @@
1
+ import { foldWorkingSet } from "../domains/context/working-set/fold.js";
2
+ import { projectWorkingSet } from "../domains/context/working-set/project.js";
3
+ import type { SessionEntry } from "../domains/session/entries.js";
4
+ import type { AgentMessage } from "../engine/types.js";
5
+ import { buildReplayAgentMessagesFromTurns, type RehydrateChatPanelOptions } from "./chat-renderer.js";
6
+
7
+ /**
8
+ * Build provider-facing replay messages from the durable session ledger.
9
+ * Projection always honors existing eviction and recall entries; the enabled
10
+ * setting gates creation of new evictions, not replay of durable state.
11
+ * Visible transcript and export callers intentionally keep using the raw
12
+ * rehydration helpers so eviction remains a model projection, not data loss.
13
+ */
14
+ export function buildModelReplayAgentMessagesFromTurns(
15
+ entries: ReadonlyArray<SessionEntry>,
16
+ options: RehydrateChatPanelOptions = {},
17
+ ): AgentMessage[] {
18
+ const activeLeafTurnId = options.activeLeafTurnId ?? options.uptoTurnId;
19
+ const projected = projectWorkingSet(entries, foldWorkingSet(entries, activeLeafTurnId));
20
+ return buildReplayAgentMessagesFromTurns(projected, options);
21
+ }
@@ -1,4 +1,5 @@
1
1
  import type { SafeEventBus } from "../core/event-bus.js";
2
+ import { foldWorkingSet } from "../domains/context/working-set/fold.js";
2
3
  import type { DispatchContract } from "../domains/dispatch/index.js";
3
4
  import { loadMemoryRecordsSync, type MemoryRecord } from "../domains/memory/index.js";
4
5
  import type { ObservabilityContract } from "../domains/observability/index.js";
@@ -107,6 +108,11 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
107
108
  deps.transitions.handle = openContextOverlayFactory(deps.tui, deps.getContextLedger, {
108
109
  bus: deps.bus,
109
110
  chat: deps.contextChat,
111
+ getWorkingSet: () => {
112
+ const readSessionEntries = deps.readSessionEntries;
113
+ if (!readSessionEntries) return null;
114
+ return foldWorkingSet(readSessionEntries(), deps.getSessionMeta()?.pinnedLeafTurnId ?? undefined);
115
+ },
110
116
  });
111
117
  deps.requestRender();
112
118
  };
@@ -3,9 +3,10 @@ import type { SessionContract, SessionEntry } from "../domains/session/index.js"
3
3
  import type { TUI } from "../engine/tui.js";
4
4
  import type { ChatLoop } from "./chat-loop.js";
5
5
  import type { ChatPanel } from "./chat-panel.js";
6
- import { buildReplayAgentMessagesFromTurns, rehydrateChatPanelFromTurns } from "./chat-renderer.js";
6
+ import { rehydrateChatPanelFromTurns } from "./chat-renderer.js";
7
7
  import { emitCommandNotice } from "./command-fallbacks.js";
8
8
  import type { InteractiveNoticeLevel } from "./interactive-subscriptions.js";
9
+ import { buildModelReplayAgentMessagesFromTurns } from "./model-session-replay.js";
9
10
  import type { OverlayTransitions } from "./overlay-transitions.js";
10
11
  import { openCwdFallbackOverlay } from "./overlays/cwd-fallback.js";
11
12
  import { openMessagePickerOverlay } from "./overlays/message-picker.js";
@@ -159,7 +160,7 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
159
160
  const replayOptions = leafTurnId ? { activeLeafTurnId: leafTurnId } : {};
160
161
  deps.resetTranscript();
161
162
  rehydrateChatPanelFromTurns(deps.chatPanel, turns, replayOptions);
162
- const replayMessages = buildReplayAgentMessagesFromTurns(turns, replayOptions);
163
+ const replayMessages = buildModelReplayAgentMessagesFromTurns(turns, replayOptions);
163
164
  deps.chat.resetForSession(leafTurnId, replayMessages);
164
165
  rescopeToBranch(session, turns, leafTurnId);
165
166
  } catch (error) {
@@ -209,7 +210,7 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
209
210
  const turns = deps.readStructuredEntries(sessionId);
210
211
  deps.resetTranscript();
211
212
  rehydrateChatPanelFromTurns(deps.chatPanel, turns, { uptoTurnId: turnId });
212
- const replayMessages = buildReplayAgentMessagesFromTurns(turns, { uptoTurnId: turnId });
213
+ const replayMessages = buildModelReplayAgentMessagesFromTurns(turns, { uptoTurnId: turnId });
213
214
  deps.chat.resetForSession(turnId, replayMessages);
214
215
  // The same branch the transcript above was just scoped to. Without the
215
216
  // leaf, /cost, the footer Σ, and the last-turn line kept reporting the
@@ -287,8 +288,11 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
287
288
  try {
288
289
  const turns = deps.readStructuredEntries(forkedSessionId);
289
290
  rehydrateChatPanelFromTurns(deps.chatPanel, turns);
290
- const replayMessages = buildReplayAgentMessagesFromTurns(turns);
291
291
  const leafTurnId = session.tree(forkedSessionId).leafId ?? parentTurnId;
292
+ const replayMessages = buildModelReplayAgentMessagesFromTurns(
293
+ turns,
294
+ leafTurnId ? { activeLeafTurnId: leafTurnId } : {},
295
+ );
292
296
  deps.chat.resetForSession(leafTurnId, replayMessages);
293
297
  rescopeToBranch(session, turns, leafTurnId);
294
298
  } catch (error) {
@@ -16,6 +16,7 @@ import { sanitizeCallTargetText } from "../../domains/safety/call-target.js";
16
16
  import { formatSize } from "../../engine/truncate.js";
17
17
  import { visibleWidth, wrapTextWithAnsi } from "../../engine/tui.js";
18
18
  import { classifyResourceRead, toolPresentationPolicy } from "../../tools/presentation.js";
19
+ import { toolResultPresentationPolicy, toolResultPresentationText } from "../../tools/result-disposition.js";
19
20
  import type { ApprovalRequestView } from "../permission-overlay.js";
20
21
  import { clioTheme, formatCompactMs, GLYPH } from "../theme/index.js";
21
22
  import { renderDiffLines } from "./diff.js";
@@ -81,6 +82,13 @@ export interface ToolExecutionFinished {
81
82
  * model reading the same transcript, to guess at the rule.
82
83
  */
83
84
  blockReason?: string | undefined;
85
+ /**
86
+ * Working-set eviction reason, when the projection has replaced this
87
+ * result's body for the model. The transcript still renders the full body:
88
+ * the ledger is what the operator scrolls, the projection is only what the
89
+ * next request carries. The tag says the two now differ here.
90
+ */
91
+ evictedReason?: string | undefined;
84
92
  /** Structured exit status when the caller has one; text parsing is legacy fallback only. */
85
93
  exitCode?: number | string | null | undefined;
86
94
  /** Local `!!` bash output is visible to the operator but excluded from model context. */
@@ -456,6 +464,7 @@ function ledgerTail(finished: ToolExecutionFinished): { facts: string; offload:
456
464
  if (details?.outputCapped === true) parts.push("output capped");
457
465
  }
458
466
  if (finished.excludeFromContext === true) parts.push("excluded from context");
467
+ if (finished.evictedReason !== undefined) parts.push("evicted", finished.evictedReason);
459
468
  const offloadPath = executed ? offloadPathOf(finished) : null;
460
469
  return {
461
470
  facts: parts.length > 0 ? dim(` · ${parts.join(" · ")}`) : "",
@@ -473,7 +482,9 @@ const FAILURE_EXCERPT_LIMIT = 80;
473
482
  * its failures carry one; the renderer never names a tool here.
474
483
  */
475
484
  function failureExcerpt(finished: ToolExecutionFinished, width: number): string {
476
- if (!finished.isError || !toolPresentationPolicy(finished.toolName, finished.args).failureExcerpt) return "";
485
+ const presentation =
486
+ toolResultPresentationPolicy(finished.result) ?? toolPresentationPolicy(finished.toolName, finished.args);
487
+ if (!finished.isError || !presentation.failureExcerpt) return "";
477
488
  if (isNonExecutedOutcome(finished.outcome)) return "";
478
489
  const text = unwrapResultEnvelope(finished.result);
479
490
  if (typeof text !== "string") return "";
@@ -871,6 +882,8 @@ function renderArgsBody(toolName: string, args: unknown, width: number, isError:
871
882
  */
872
883
  function unwrapResultEnvelope(result: unknown): unknown {
873
884
  if (typeof result === "string" || result === null || result === undefined) return result;
885
+ const presentationText = toolResultPresentationText(result);
886
+ if (presentationText !== null) return presentationText;
874
887
  const blocks = Array.isArray(result)
875
888
  ? result
876
889
  : isPlainObject(result) && Array.isArray(result.content)
@@ -1041,6 +1054,7 @@ function outputFacts(finished: ToolExecutionFinished): string[] {
1041
1054
  }
1042
1055
  if (isPlainObject(finished.result) && finished.result.terminate === true) parts.push("terminal result");
1043
1056
  if (finished.excludeFromContext === true) parts.push("excluded from context");
1057
+ if (finished.evictedReason !== undefined) parts.push("evicted", finished.evictedReason);
1044
1058
  return parts;
1045
1059
  }
1046
1060
 
@@ -1174,7 +1188,9 @@ export interface ToolSublineRenderOptions {
1174
1188
  function foldedDiffRows(finished: ToolExecutionFinished, width: number, opts: ToolSublineRenderOptions): string[] {
1175
1189
  if (opts.foldedExtras === "none") return [];
1176
1190
  if (finished.isError || isNonExecutedOutcome(finished.outcome)) return [];
1177
- if (!toolPresentationPolicy(finished.toolName, finished.args).showDiffWhenFolded) return [];
1191
+ const presentation =
1192
+ toolResultPresentationPolicy(finished.result) ?? toolPresentationPolicy(finished.toolName, finished.args);
1193
+ if (!presentation.showDiffWhenFolded) return [];
1178
1194
  const diff = resultDiff(finished.result);
1179
1195
  if (diff === null) return [];
1180
1196
  return truncateRowsMiddle(
@@ -3,7 +3,7 @@ import { collectSessionEntries } from "../domains/session/compaction/session-ent
3
3
  import type { SessionContract, SessionEntry } from "../domains/session/index.js";
4
4
  import { openSession, sessionPaths } from "../engine/session.js";
5
5
  import type { ChatLoop } from "./chat-loop.js";
6
- import { buildReplayAgentMessagesFromTurns } from "./chat-renderer.js";
6
+ import { buildModelReplayAgentMessagesFromTurns } from "./model-session-replay.js";
7
7
 
8
8
  type SessionOwner = Pick<SessionContract, "create" | "current">;
9
9
  type SessionChat = Pick<ChatLoop, "resetForSession">;
@@ -87,7 +87,7 @@ export function createSessionTranscript(deps: SessionTranscriptDeps): SessionTra
87
87
  const turns = deps.readSessionEntries();
88
88
  deps.chat.resetForSession(
89
89
  leafTurnId,
90
- buildReplayAgentMessagesFromTurns(turns, {
90
+ buildModelReplayAgentMessagesFromTurns(turns, {
91
91
  ...(leafTurnId ? { activeLeafTurnId: leafTurnId } : {}),
92
92
  }),
93
93
  );
@@ -44,6 +44,8 @@ type SlashCommandVariant =
44
44
  | { kind: "init"; options: InitCommandOptions }
45
45
  | { kind: "context-clear"; options: ContextClearCommandOptions }
46
46
  | { kind: "context-refresh" }
47
+ /** `ref` is the turnId an `[evicted ...]` marker names. */
48
+ | { kind: "context-recall"; ref: string }
47
49
  | { kind: "skill-selector" }
48
50
  | { kind: "skill-invocation"; text: string }
49
51
  | { kind: "prompts" }
@@ -343,6 +345,13 @@ export interface SlashCommandContext {
343
345
  * Optional until the host wires onContextRefresh.
344
346
  */
345
347
  runContextRefresh?: () => void;
348
+ /**
349
+ * Read an evicted tool-result body back into the transcript by ref, and
350
+ * record the `contextRecall` entry. Transcript-only: an operator recall
351
+ * answers the person, so the body never becomes model context. Optional
352
+ * until the host wires a session.
353
+ */
354
+ runContextRecall?: (ref: string) => void;
346
355
  openSkillsHub?: () => void;
347
356
  listPrompts: () => ResourceList<PromptTemplate>;
348
357
  /**
@@ -481,6 +490,13 @@ const COMPACT_POSITIONALS: ReadonlyArray<CommandPositionalSpec> = [
481
490
  { name: "instructions", required: false, rest: true },
482
491
  ];
483
492
 
493
+ /**
494
+ * `/context recall <ref>` takes exactly one ref, not a rest tail: a ref is a
495
+ * single turnId, and a second token is a mistake worth reporting rather than
496
+ * text to fold into the first.
497
+ */
498
+ const RECALL_POSITIONALS: ReadonlyArray<CommandPositionalSpec> = [{ name: "ref", required: true }];
499
+
484
500
  /**
485
501
  * A registered command whose arguments do not parse is a mistake to report,
486
502
  * not a chat message to send. Falling through to `unknown` submitted the raw
@@ -859,11 +875,12 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
859
875
  },
860
876
  {
861
877
  name: "context",
862
- description: "Context hub: window overlay plus compact, init, refresh, and reset",
878
+ description: "Context hub: window overlay plus compact, recall, init, refresh, and reset",
863
879
  group: "Inspect",
864
- kinds: ["context-view", "compact", "init", "context-clear", "context-refresh"],
880
+ kinds: ["context-view", "compact", "context-recall", "init", "context-clear", "context-refresh"],
865
881
  subcommandDescriptions: {
866
882
  compact: "Compact session context",
883
+ recall: "Recall an evicted result",
867
884
  init: "Initialize project context",
868
885
  refresh: "Refresh project context",
869
886
  reset: "Reset project context",
@@ -871,6 +888,7 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
871
888
  args: {
872
889
  subcommands: {
873
890
  compact: { positionals: [...COMPACT_POSITIONALS] },
891
+ recall: { positionals: [...RECALL_POSITIONALS] },
874
892
  init: {},
875
893
  refresh: {},
876
894
  reset: {},
@@ -881,6 +899,8 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
881
899
  switch (parsed.subcommand) {
882
900
  case "compact":
883
901
  return { kind: "compact", instructions: parsed.rest };
902
+ case "recall":
903
+ return { kind: "context-recall", ref: parsed.positionals[0] ?? "" };
884
904
  case "init":
885
905
  return { kind: "init", options: {} };
886
906
  case "refresh":
@@ -899,6 +919,13 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
899
919
  case "compact":
900
920
  ctx.runCompact(command.instructions);
901
921
  return;
922
+ case "context-recall":
923
+ if (ctx.runContextRecall) {
924
+ ctx.runContextRecall(command.ref);
925
+ } else {
926
+ ctx.notice("error", "context recall is not wired; no session is bound to this process");
927
+ }
928
+ return;
902
929
  case "init":
903
930
  ctx.runInit(command.options);
904
931
  return;