@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
@@ -2,7 +2,15 @@
2
2
  * Strict session-format version reader.
3
3
  *
4
4
  * Runs before every session resume. Earlier pre-1.0 formats are disposable
5
- * local state and must not be transformed into the current format.
5
+ * local state and must not be transformed into the current format. A version
6
+ * from the future is refused for the opposite reason: the file is not
7
+ * disposable, it belongs to a newer Clio, and this build would silently drop
8
+ * whatever that build understood and this one does not. Downgrading and
9
+ * resuming would then write the truncated reading back over the original.
10
+ *
11
+ * Version 3 to 4 is the one additive step: 4 only adds the working-set kinds
12
+ * (`contextEviction`, `contextRecall`), so a version-3 ledger is read as-is
13
+ * and the metadata is restamped. Nothing in the file changes.
6
14
  */
7
15
 
8
16
  import { CURRENT_SESSION_FORMAT_VERSION } from "../../../engine/session.js";
@@ -10,6 +18,9 @@ import type { SessionMeta } from "../contract.js";
10
18
 
11
19
  export { CURRENT_SESSION_FORMAT_VERSION };
12
20
 
21
+ /** Oldest version this build reads without transforming the ledger. */
22
+ export const OLDEST_READABLE_SESSION_FORMAT_VERSION = 3;
23
+
13
24
  export interface MigrationResult {
14
25
  migrated: boolean;
15
26
  from: number;
@@ -18,10 +29,18 @@ export interface MigrationResult {
18
29
 
19
30
  export function runMigrations(meta: SessionMeta, sessionPath: string): MigrationResult {
20
31
  const from = meta.sessionFormatVersion ?? 1;
21
- if (from < CURRENT_SESSION_FORMAT_VERSION) {
32
+ if (from < OLDEST_READABLE_SESSION_FORMAT_VERSION) {
33
+ throw new Error(
34
+ `session metadata has an unsupported format version (expected version ${CURRENT_SESSION_FORMAT_VERSION}, got ${from}): ${sessionPath}. Remove the session directory to start a new session.`,
35
+ );
36
+ }
37
+ if (from > CURRENT_SESSION_FORMAT_VERSION) {
22
38
  throw new Error(
23
- `session metadata has an unsupported format version (expected version 3, got ${from}): ${sessionPath}. Remove the session directory to start a new session.`,
39
+ `session was written by a newer Clio (format version ${from}, this build reads version ${CURRENT_SESSION_FORMAT_VERSION}): ${sessionPath}. Upgrade clio-coder to resume this session.`,
24
40
  );
25
41
  }
42
+ if (from < CURRENT_SESSION_FORMAT_VERSION) {
43
+ return { migrated: true, from, to: CURRENT_SESSION_FORMAT_VERSION };
44
+ }
26
45
  return { migrated: false, from, to: from };
27
46
  }
@@ -12,6 +12,7 @@ import type { SessionContract, SessionMeta } from "../../domains/session/contrac
12
12
  import type { MessageEntry, SessionEntry } from "../../domains/session/entries.js";
13
13
  import { filterEntriesToActivePath } from "../../domains/session/tree/active-path.js";
14
14
  import type { ToolRegistry } from "../../tools/registry.js";
15
+ import { toolResultPresentationText } from "../../tools/result-disposition.js";
15
16
  import type { AgentMessage } from "../types.js";
16
17
  import { ACP_TURN_FAILED_MESSAGE, AcpRequestError, AcpTimeoutError, acpErrorMessage } from "./errors.js";
17
18
  import type { AcpJsonRpcPeerTransport } from "./transport.js";
@@ -537,6 +538,8 @@ function applyStopReason(active: ActivePrompt, message: unknown): void {
537
538
  function outputText(value: unknown): string {
538
539
  if (typeof value === "string") return value;
539
540
  if (!isRecord(value)) return "";
541
+ const presentationText = toolResultPresentationText(value);
542
+ if (presentationText !== null) return presentationText;
540
543
  if (Array.isArray(value.content)) return contentText(value.content);
541
544
  if (typeof value.output === "string") return value.output;
542
545
  if (typeof value.text === "string") return value.text;
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { Agent, type AgentOptions, type StreamFn } from "@earendil-works/pi-agent-core";
14
+ import { isDispositionedToolResultError } from "../tools/result-disposition.js";
14
15
  import { engineStreamSimple } from "./api-registry.js";
15
16
 
16
17
  export type EngineAgentOptions = Omit<AgentOptions, "streamFn"> & { streamFn?: StreamFn };
@@ -20,8 +21,24 @@ export interface EngineAgentHandle {
20
21
  state(): Agent["state"];
21
22
  }
22
23
 
24
+ function dispositionAwareAfterToolCall(
25
+ delegate: AgentOptions["afterToolCall"],
26
+ ): NonNullable<AgentOptions["afterToolCall"]> {
27
+ return async (context, signal) => {
28
+ const override = await delegate?.(context, signal);
29
+ const effectiveResult =
30
+ override?.details === undefined ? context.result : { ...context.result, details: override.details };
31
+ if (isDispositionedToolResultError(effectiveResult)) return { ...override, isError: true };
32
+ return override;
33
+ };
34
+ }
35
+
23
36
  export function createEngineAgent(options: EngineAgentOptions = {}): EngineAgentHandle {
24
- const agent = new Agent({ streamFn: engineStreamSimple, ...options });
37
+ const agent = new Agent({
38
+ streamFn: engineStreamSimple,
39
+ ...options,
40
+ afterToolCall: dispositionAwareAfterToolCall(options.afterToolCall),
41
+ });
25
42
  return {
26
43
  agent,
27
44
  state: () => agent.state,
@@ -57,13 +57,19 @@ export interface ClioSessionMeta {
57
57
  platform: string;
58
58
  nodeVersion: string;
59
59
  /**
60
- * Version 3 on every new session. Readers reject earlier versions and
61
- * sessions that omit this field.
60
+ * Version 4 on every new session. Readers reject earlier versions, sessions
61
+ * that omit this field, and versions from the future.
62
62
  */
63
63
  sessionFormatVersion?: number;
64
64
  }
65
65
 
66
- export const CURRENT_SESSION_FORMAT_VERSION = 3;
66
+ /**
67
+ * Version 4 adds the working-set ledger kinds (`contextEviction`,
68
+ * `contextRecall`). A version-3 reader would parse those entries as unknown
69
+ * kinds and replay a session as if nothing had been evicted, so the bump is
70
+ * what makes an older Clio refuse the file instead of misreading it.
71
+ */
72
+ export const CURRENT_SESSION_FORMAT_VERSION = 4;
67
73
 
68
74
  export interface ClioSessionJsonlHeader {
69
75
  type: "session";
@@ -161,8 +161,8 @@ import {
161
161
  import { openSession, readSessionTailTurns, sessionCurrentPath, sessionPaths } from "../engine/session.js";
162
162
  import type { EngineModel } from "../engine/types.js";
163
163
  import { createChatLoop } from "../interactive/chat-loop.js";
164
- import { buildReplayAgentMessagesFromTurns } from "../interactive/chat-renderer.js";
165
164
  import { type RunIo, startInteractive } from "../interactive/index.js";
165
+ import { buildModelReplayAgentMessagesFromTurns } from "../interactive/model-session-replay.js";
166
166
  import type { BootOptions } from "./boot-options.js";
167
167
 
168
168
  export type { BootOptions, HeadlessSamplingOverrides } from "./boot-options.js";
@@ -1236,7 +1236,16 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
1236
1236
  // one object the dispatch tool and the keypress both hold.
1237
1237
  const dispatchBackground = createDispatchBackgroundRegistry();
1238
1238
  registerAllTools(toolRegistry, {
1239
- ...(session ? { session } : {}),
1239
+ ...(session
1240
+ ? {
1241
+ session,
1242
+ readSessionEntries: () => {
1243
+ const meta = session.current();
1244
+ return meta ? readSessionEntriesForCompact(meta.id) : [];
1245
+ },
1246
+ onContextRecalled: (payload) => bus.emit(BusChannels.ContextRecalled, payload),
1247
+ }
1248
+ : {}),
1240
1249
  taskBoard,
1241
1250
  userTasks,
1242
1251
  dispatch,
@@ -1612,7 +1621,10 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
1612
1621
  // parent onto.
1613
1622
  chat.resetForSession(
1614
1623
  leafTurnId,
1615
- buildReplayAgentMessagesFromTurns(readCurrentSessionEntries(), leafTurnId ? { activeLeafTurnId: leafTurnId } : {}),
1624
+ buildModelReplayAgentMessagesFromTurns(
1625
+ readCurrentSessionEntries(),
1626
+ leafTurnId ? { activeLeafTurnId: leafTurnId } : {},
1627
+ ),
1616
1628
  );
1617
1629
  } catch (err) {
1618
1630
  chat.resetForSession(leafTurnId);
@@ -1664,7 +1676,7 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
1664
1676
  ? {
1665
1677
  readSessionEntries: readSessionEntriesForCompact,
1666
1678
  buildReplayMessages: (entries: ReadonlyArray<SessionEntry>, leafTurnId: string | null) =>
1667
- buildReplayAgentMessagesFromTurns(entries, leafTurnId === null ? {} : { activeLeafTurnId: leafTurnId }),
1679
+ buildModelReplayAgentMessagesFromTurns(entries, leafTurnId === null ? {} : { activeLeafTurnId: leafTurnId }),
1668
1680
  }
1669
1681
  : {}),
1670
1682
  providers,
@@ -555,21 +555,33 @@ export function toolResultSummary(result: unknown): Record<string, unknown> {
555
555
  const bytes = Buffer.byteLength(text, "utf8");
556
556
  const details = recordValue(obj?.details);
557
557
  const size = recordValue(details?.resultSize);
558
+ const disposition = recordValue(details?.resultDisposition);
558
559
  const truncation = recordValue(details?.truncation);
559
560
  const observation = recordValue(details?.observation);
560
561
  const offloadPath =
561
- typeof observation?.offloadPath === "string"
562
- ? observation.offloadPath
563
- : typeof size?.offloadPath === "string"
564
- ? size.offloadPath
565
- : null;
562
+ typeof disposition?.offloadPath === "string"
563
+ ? disposition.offloadPath
564
+ : typeof observation?.offloadPath === "string"
565
+ ? observation.offloadPath
566
+ : typeof size?.offloadPath === "string"
567
+ ? size.offloadPath
568
+ : null;
569
+ const displayedBytes =
570
+ typeof disposition?.displayedBytes === "number" && Number.isFinite(disposition.displayedBytes)
571
+ ? disposition.displayedBytes
572
+ : bytes;
566
573
  return {
567
- bytes,
574
+ bytes: displayedBytes,
568
575
  truncated:
576
+ disposition?.presentationTruncated === true ||
569
577
  size?.truncated === true ||
570
578
  truncation?.truncated === true ||
571
579
  observation?.truncated === true ||
572
580
  text.includes("[tool result truncated]"),
581
+ ...(typeof disposition?.capturedBytes === "number" ? { capturedBytes: disposition.capturedBytes } : {}),
582
+ ...(typeof disposition?.displayedBytes === "number" ? { displayedBytes: disposition.displayedBytes } : {}),
583
+ ...(typeof disposition?.contextBytes === "number" ? { contextBytes: disposition.contextBytes } : {}),
584
+ ...(disposition?.contextTruncated === true ? { contextTruncated: true } : {}),
573
585
  ...(typeof size?.policy === "string" ? { policy: size.policy } : {}),
574
586
  ...(typeof size?.followUpHint === "string" ? { followUpHint: size.followUpHint } : {}),
575
587
  ...(offloadPath !== null ? { offloadPath } : {}),
@@ -5,6 +5,7 @@ import { rawDurationMs } from "../core/timers.js";
5
5
  import { type Component, Markdown, truncateToWidth, wrapTextWithAnsi } from "../engine/tui.js";
6
6
  import type { AgentMessage } from "../engine/types.js";
7
7
  import { toolPresentationPolicy } from "../tools/presentation.js";
8
+ import { toolResultPresentationPolicy } from "../tools/result-disposition.js";
8
9
  import type { ChatLoopEvent, RetryStatusPayload } from "./chat-loop.js";
9
10
  import { extractText, isSelfExplainingAbort } from "./chat-loop-messages.js";
10
11
  import type { ApprovalRequestView } from "./permission-overlay.js";
@@ -202,6 +203,12 @@ type ToolSegment = {
202
203
  * was refused and leaves the operator no way to learn why.
203
204
  */
204
205
  blockReason?: string | undefined;
206
+ /**
207
+ * Working-set eviction reason for a replayed result whose body the
208
+ * projection has replaced. Set only on rehydrate, from the folded ledger;
209
+ * a live call is never evicted while it is still being rendered.
210
+ */
211
+ evictedReason?: string | undefined;
205
212
  /** View-only marker: historical calls render mutation diffs without live color. */
206
213
  replayed?: true;
207
214
  };
@@ -835,7 +842,7 @@ function styleStatusVerb(text: string, toneHint: VerbRender["toneHint"]): string
835
842
  function policySegmentFold(seg: ToolSegment, detail: TranscriptDetailPolicy): Fold {
836
843
  if (!seg.finished) return policyRunningToolFold(detail);
837
844
  if (seg.isError && detail.errors === "body") return "expanded";
838
- return policyToolFold(detail, toolPresentationPolicy(seg.name, seg.args));
845
+ return policyToolFold(detail, toolResultPresentationPolicy(seg.result) ?? toolPresentationPolicy(seg.name, seg.args));
839
846
  }
840
847
 
841
848
  /** Effective state of a tool segment: the operator's override, else the policy. */
@@ -884,6 +891,7 @@ function renderToolSegmentLines(
884
891
  resultSummary: seg.resultSummary,
885
892
  outcome: seg.settlement,
886
893
  blockReason: seg.blockReason,
894
+ evictedReason: seg.evictedReason,
887
895
  }
888
896
  : { toolCallId: seg.id, toolName: seg.name, args: seg.args, elapsedMs, phase },
889
897
  width,
@@ -916,6 +924,7 @@ function renderToolSegmentLines(
916
924
  resultSummary: seg.resultSummary,
917
925
  outcome: seg.settlement,
918
926
  blockReason: seg.blockReason,
927
+ evictedReason: seg.evictedReason,
919
928
  },
920
929
  width,
921
930
  { unbounded: unboundedToolBodies, diffStyle: seg.replayed === true ? "plain" : "color" },
@@ -1936,7 +1945,14 @@ export function createChatPanel(options: ChatPanelOptions = {}): ChatPanel {
1936
1945
  resultSummary?: unknown;
1937
1946
  outcome?: unknown;
1938
1947
  blockReason?: unknown;
1948
+ evictedReason?: unknown;
1939
1949
  };
1950
+ // Replay-only: the rehydrator reads the working-set fold and tags
1951
+ // the rows whose bodies the projection has replaced for the model.
1952
+ tool.evictedReason =
1953
+ typeof enriched.evictedReason === "string" && enriched.evictedReason.length > 0
1954
+ ? enriched.evictedReason
1955
+ : undefined;
1940
1956
  // Settlement is that verdict, never an inference from result text.
1941
1957
  // The text of a tool result is the tool's own output: `node --test`
1942
1958
  // prints `cancelled 0` on every run and a linter can print
@@ -14,6 +14,8 @@
14
14
  */
15
15
 
16
16
  import { ToolNames } from "../core/tool-names.js";
17
+ import { foldWorkingSet } from "../domains/context/working-set/fold.js";
18
+ import { compactionCut } from "../domains/context/working-set/visible.js";
17
19
  import type {
18
20
  BashExecutionEntry,
19
21
  BranchSummaryEntry,
@@ -40,6 +42,7 @@ import {
40
42
  import { wrapTextWithAnsi } from "../engine/tui.js";
41
43
  import type { AgentMessage } from "../engine/types.js";
42
44
  import { toolPresentationPolicy } from "../tools/presentation.js";
45
+ import { toolResultPresentationText } from "../tools/result-disposition.js";
43
46
  import type { ChatLoopEvent, RetryStatusPayload } from "./chat-loop.js";
44
47
  import { isSelfExplainingAbort } from "./chat-loop-messages.js";
45
48
  import type { ChatPanel } from "./chat-panel.js";
@@ -482,7 +485,11 @@ function toolResultContent(result: unknown, unbounded = false): unknown[] {
482
485
  }
483
486
 
484
487
  function displayReplayToolResult(result: unknown, unbounded = false): unknown {
485
- const content = toolResultContent(result, unbounded);
488
+ const presentationText = toolResultPresentationText(result);
489
+ const content =
490
+ presentationText === null
491
+ ? toolResultContent(result, unbounded)
492
+ : [{ type: "text", text: unbounded ? presentationText : truncateReplayText(presentationText) }];
486
493
  // Preserve the details record (observation envelope, exec records) so the
487
494
  // replayed ledger line carries the same outcome facts as the live one.
488
495
  const details = payloadObject(result)?.details;
@@ -835,13 +842,6 @@ function truncateAtTurn(entries: ReadonlyArray<SessionEntry>, uptoTurnId?: strin
835
842
  return entries.slice(0, index + 1);
836
843
  }
837
844
 
838
- function latestCompactionIndex(entries: ReadonlyArray<SessionEntry>): number {
839
- for (let i = entries.length - 1; i >= 0; i--) {
840
- if (entries[i]?.kind === "compactionSummary") return i;
841
- }
842
- return -1;
843
- }
844
-
845
845
  function toolCallIdsInEntry(entry: SessionEntry): string[] {
846
846
  if (entry.kind !== "message") return [];
847
847
  if (entry.role === "tool_call") return [extractToolCall(entry).id];
@@ -919,19 +919,13 @@ export function selectReplayEntries(
919
919
  ): SessionEntry[] {
920
920
  const active = filterEntriesToActivePath(turns, options.activeLeafTurnId ?? options.uptoTurnId);
921
921
  const entries = truncateAtTurn(active, options.uptoTurnId);
922
- const compactionIndex = latestCompactionIndex(entries);
923
- if (compactionIndex < 0) return dropLegacyToolResultAssistantDuplicates(entries);
924
-
925
- const compaction = entries[compactionIndex] as CompactionSummaryEntry;
926
- const selected: SessionEntry[] = [compaction];
927
- const firstKeptIndex = compaction.firstKeptTurnId
928
- ? entries.findIndex((entry) => entry.turnId === compaction.firstKeptTurnId)
929
- : -1;
930
- if (firstKeptIndex >= 0 && firstKeptIndex < compactionIndex) {
931
- selected.push(...entries.slice(firstKeptIndex, compactionIndex));
932
- }
933
- selected.push(...entries.slice(compactionIndex + 1));
934
- return dropLegacyToolResultAssistantDuplicates(repairToolResultOrphans(entries, selected, compactionIndex));
922
+ // The cut itself is the working-set layer's definition of "what the model
923
+ // can see", shared with the eviction policy input so the two cannot drift.
924
+ const cut = compactionCut(entries);
925
+ if (cut.compactionIndex < 0) return dropLegacyToolResultAssistantDuplicates(entries);
926
+ const compaction = entries[cut.compactionIndex] as CompactionSummaryEntry;
927
+ const selected: SessionEntry[] = [compaction, ...cut.visible];
928
+ return dropLegacyToolResultAssistantDuplicates(repairToolResultOrphans(entries, selected, cut.compactionIndex));
935
929
  }
936
930
 
937
931
  function dropLegacyToolResultAssistantDuplicates(entries: ReadonlyArray<SessionEntry>): SessionEntry[] {
@@ -1043,6 +1037,11 @@ export function buildReplayAgentMessagesFromTurns(
1043
1037
  // It reaches the model only when an operator shares it, and a share
1044
1038
  // is already a user message by the time it lands in the ledger.
1045
1039
  case "workerRun":
1040
+ // Working-set entries are folded into a view by the context domain
1041
+ // and applied as a projection before this builder runs; the
1042
+ // entries themselves never become messages.
1043
+ case "contextEviction":
1044
+ case "contextRecall":
1046
1045
  break;
1047
1046
  }
1048
1047
  }
@@ -1080,6 +1079,11 @@ export function rehydrateChatPanelFromTurns(
1080
1079
  const pendingToolIds: string[] = [];
1081
1080
  let runAssistantMessages: AgentMessage[] = [];
1082
1081
  const selected = selectReplayEntries(turns, options);
1082
+ // The transcript shows the ledger, never the projection: an evicted result
1083
+ // still renders its full body here, tagged with the reason it left the
1084
+ // model's working set. Folded once over the same active path the replay
1085
+ // uses, so a /tree switch cannot tag a row from an abandoned branch.
1086
+ const workingSet = foldWorkingSet(turns, options.activeLeafTurnId ?? options.uptoTurnId);
1083
1087
  // One block per assignment, drawn where its first attempt started. Later
1084
1088
  // attempts of the same assignment fold into that block as `↻` rail lines, so
1085
1089
  // a failover replays as the one run it was rather than as two.
@@ -1127,6 +1131,7 @@ export function rehydrateChatPanelFromTurns(
1127
1131
  }
1128
1132
  if (entry.role === "tool_result") {
1129
1133
  const result = extractToolResult(entry);
1134
+ const evictedReason = workingSet.evicted.get(entry.turnId)?.reason;
1130
1135
  const fallbackId = result.id ?? pendingToolIds.pop() ?? null;
1131
1136
  if (fallbackId) {
1132
1137
  const pendingIndex = pendingToolIds.indexOf(fallbackId);
@@ -1141,6 +1146,7 @@ export function rehydrateChatPanelFromTurns(
1141
1146
  ...(result.resultSummary !== undefined ? { resultSummary: result.resultSummary } : {}),
1142
1147
  ...(result.outcome !== undefined ? { outcome: result.outcome } : {}),
1143
1148
  ...(result.blockReason !== undefined ? { blockReason: result.blockReason } : {}),
1149
+ ...(evictedReason !== undefined ? { evictedReason } : {}),
1144
1150
  } as ChatLoopEvent);
1145
1151
  } else {
1146
1152
  chatPanel.appendReplayBlock((width) =>
@@ -1154,6 +1160,7 @@ export function rehydrateChatPanelFromTurns(
1154
1160
  ...(result.resultSummary !== undefined ? { resultSummary: result.resultSummary } : {}),
1155
1161
  ...(result.outcome === "blocked" ? { outcome: "blocked" as const } : {}),
1156
1162
  ...(result.blockReason !== undefined ? { blockReason: result.blockReason } : {}),
1163
+ ...(evictedReason !== undefined ? { evictedReason } : {}),
1157
1164
  },
1158
1165
  width,
1159
1166
  { unbounded: options.unboundedToolBodies === true },
@@ -1239,6 +1246,8 @@ export function rehydrateChatPanelFromTurns(
1239
1246
  case "label":
1240
1247
  case "taskLedger":
1241
1248
  case "decisionLedger":
1249
+ case "contextEviction":
1250
+ case "contextRecall":
1242
1251
  break;
1243
1252
  }
1244
1253
  }
@@ -156,6 +156,16 @@ export function renderContextMeterGrid(
156
156
  return lines;
157
157
  }
158
158
 
159
+ /**
160
+ * Evicted tokens live outside the window: the working-set projection removed
161
+ * them from what the model sees, so they are not a meter category and must
162
+ * not claim cells or a legend row. One line after the legend names them.
163
+ */
164
+ export function renderEvictedTokensLine(evictedTokens: number, theme: ClioTheme = clioTheme()): string {
165
+ const tokens = Math.round(Math.max(0, evictedTokens)).toLocaleString("en-US");
166
+ return `${theme.fg("dim", GLYPH.contextReserve)} ${theme.fg("muted", "evicted (outside window)")} ${theme.fg("dim", `${tokens} tokens`)}`;
167
+ }
168
+
159
169
  /** A small colored swatch for a category, for inline legends. */
160
170
  export function contextCategorySwatch(category: ContextLedgerCategory, theme: ClioTheme = clioTheme()): string {
161
171
  return theme.fg(CONTEXT_CATEGORY_TOKEN[category], contextCategoryGlyph(category));
@@ -2,9 +2,10 @@ import { homedir } from "node:os";
2
2
  import { relative } from "node:path";
3
3
  import { BusChannels } from "../core/bus-events.js";
4
4
  import type { SafeEventBus } from "../core/event-bus.js";
5
+ import type { WorkingSetView } from "../domains/context/working-set/contract.js";
5
6
  import type { ContextLedger, ContextLedgerGroup } from "../domains/session/context-ledger.js";
6
7
  import { type OverlayHandle, Text, type TUI, visibleWidth } from "../engine/tui.js";
7
- import { contextCategorySwatch, renderContextMeterGrid } from "./context-meter.js";
8
+ import { contextCategorySwatch, renderContextMeterGrid, renderEvictedTokensLine } from "./context-meter.js";
8
9
  import { buildHint, showClioOverlayFrame } from "./overlay-frame.js";
9
10
  import { abbreviateModelId, type ClioToken, clioTheme, formatContextPercent } from "./theme/index.js";
10
11
 
@@ -81,7 +82,62 @@ function legendRow(group: ContextLedgerGroup, contentWidth: number): string {
81
82
  return `${swatch} ${theme.fg(labelToken, labelText)} ${theme.fg("muted", right)}`;
82
83
  }
83
84
 
84
- export function renderContextLedgerLines(ledger: ContextLedger, contentWidth: number): string[] {
85
+ function evictedTokens(view: WorkingSetView): number {
86
+ let total = 0;
87
+ for (const state of view.evicted.values()) total += state.tokensFreed;
88
+ return total;
89
+ }
90
+
91
+ function formatChurn(view: WorkingSetView): string {
92
+ if (view.itemsEvicted === 0) return "n/a";
93
+ return (view.recalls / view.itemsEvicted).toFixed(2);
94
+ }
95
+
96
+ /**
97
+ * Prose for one cache-disturbance reason. The wire values are stamped by
98
+ * `noteColdReason` in turn-context.ts and persisted on the assistant entry's
99
+ * `promptCache.expectedColdReasons`; the overlay reads them back, so an unknown
100
+ * reason renders as itself rather than disappearing.
101
+ */
102
+ function coldReasonLabel(reason: string): string {
103
+ switch (reason) {
104
+ case "working_set_evict":
105
+ return "working-set eviction";
106
+ case "compaction":
107
+ return "compaction";
108
+ case "dispatch":
109
+ return "dispatch traffic";
110
+ default:
111
+ return reason;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * The working-set section: what the projection has taken out of the window
117
+ * and how often the model has asked for it back. Churn is recalls over items
118
+ * evicted; a high number means the policy evicts what is still needed.
119
+ */
120
+ function renderWorkingSetLines(view: WorkingSetView): string[] {
121
+ const theme = clioTheme();
122
+ const items = view.evicted.size;
123
+ const summary = [
124
+ `${items} evicted item${items === 1 ? "" : "s"}`,
125
+ `${formatTokens(evictedTokens(view))} tokens`,
126
+ `${view.evictionEvents} event${view.evictionEvents === 1 ? "" : "s"}`,
127
+ `${view.recalls} recall${view.recalls === 1 ? "" : "s"}`,
128
+ `churn ${formatChurn(view)}`,
129
+ ].join(" · ");
130
+ return [
131
+ `${theme.fg("muted", "working set")} ${theme.fg("dim", "·")} ${theme.fg("accent", `policy ${view.lastPolicyId ?? "none"}`)}`,
132
+ theme.fg("dim", summary),
133
+ ];
134
+ }
135
+
136
+ export function renderContextLedgerLines(
137
+ ledger: ContextLedger,
138
+ contentWidth: number,
139
+ workingSet?: WorkingSetView | null,
140
+ ): string[] {
85
141
  const theme = clioTheme();
86
142
  const lines: string[] = [];
87
143
 
@@ -110,8 +166,13 @@ export function renderContextLedgerLines(ledger: ContextLedger, contentWidth: nu
110
166
  lines.push("");
111
167
 
112
168
  for (const group of ledger.meter) lines.push(legendRow(group, contentWidth));
169
+ if (workingSet && workingSet.evicted.size > 0) lines.push(renderEvictedTokensLine(evictedTokens(workingSet), theme));
113
170
 
114
171
  lines.push("");
172
+ if (workingSet) {
173
+ for (const line of renderWorkingSetLines(workingSet)) lines.push(line);
174
+ lines.push("");
175
+ }
115
176
  if (ledger.projectPreload && ledger.groups.some((group) => group.category === "project")) {
116
177
  lines.push(theme.fg("dim", `project preload: ${ledger.projectPreload}`));
117
178
  }
@@ -141,11 +202,21 @@ export function renderContextLedgerLines(ledger: ContextLedger, contentWidth: nu
141
202
  const uncached =
142
203
  cache.uncachedInputTokens !== null ? `uncached input ${formatTokens(cache.uncachedInputTokens)}` : null;
143
204
  const line = ["prompt cache:", shell, "·", backend, "·", read, ...(uncached ? ["·", uncached] : [])].join(" ");
205
+ // Reasons Clio recorded before the run: working-set eviction, summary
206
+ // compaction, and dispatch traffic all move the byte prefix a local
207
+ // single-slot backend caches, so a cold turn after one of them is the
208
+ // expected outcome rather than a provider surprise.
209
+ const coldReasons = cache.backendVerdict === "cold" ? (cache.expectedColdReasons ?? []) : [];
144
210
  // A reused shell with a cold backend means Clio kept the bytes stable
145
211
  // but the provider re-prefilled anyway; surface that disagreement
146
- // instead of hiding it.
147
- const misleading = cache.shellReused && cache.backendVerdict === "cold";
212
+ // instead of hiding it. An expected reason explains the same numbers, so
213
+ // it is reported on its own line and not as a warning.
214
+ const misleading = cache.shellReused && cache.backendVerdict === "cold" && coldReasons.length === 0;
148
215
  lines.push(theme.fg(misleading ? "warning" : "dim", line));
216
+ if (coldReasons.length > 0) {
217
+ const reasons = coldReasons.map(coldReasonLabel).join(", ");
218
+ lines.push(theme.fg("dim", `last cold turn: ${reasons} (expected)`));
219
+ }
149
220
  }
150
221
 
151
222
  if (ledger.lastCompaction) {
@@ -170,6 +241,8 @@ export interface OpenContextOverlayOptions {
170
241
  onEvent(handler: (event: { type: string }) => void): () => void;
171
242
  isStreaming(): boolean;
172
243
  };
244
+ /** Working-set fold at the live leaf; null or absent hides the section. */
245
+ getWorkingSet?: () => WorkingSetView | null;
173
246
  }
174
247
 
175
248
  /**
@@ -183,7 +256,9 @@ export function openContextOverlay(
183
256
  getLedger: () => ContextLedger,
184
257
  options?: OpenContextOverlayOptions,
185
258
  ): OverlayHandle {
186
- const text = new Text(renderContextLedgerLines(getLedger(), DEFAULT_CONTENT_WIDTH).join("\n"), 0, 0);
259
+ const render = (): string =>
260
+ renderContextLedgerLines(getLedger(), DEFAULT_CONTENT_WIDTH, options?.getWorkingSet?.() ?? null).join("\n");
261
+ const text = new Text(render(), 0, 0);
187
262
  const handle = showClioOverlayFrame(tui, text, {
188
263
  anchor: "center",
189
264
  width: CONTEXT_OVERLAY_WIDTH,
@@ -192,7 +267,7 @@ export function openContextOverlay(
192
267
  });
193
268
 
194
269
  const refresh = (): void => {
195
- text.setText(renderContextLedgerLines(getLedger(), DEFAULT_CONTENT_WIDTH).join("\n"));
270
+ text.setText(render());
196
271
  text.invalidate();
197
272
  tui.requestRender();
198
273
  };