@iowarp/clio-coder 0.3.4 → 0.3.6

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 (265) hide show
  1. package/CHANGELOG.md +37 -2
  2. package/CONTRIBUTING.md +6 -6
  3. package/README.md +2 -2
  4. package/dist/{acp-S5R4RR5B.js → acp-2BEHC4DL.js} +4 -4
  5. package/dist/{agents-P6DMMVZY.js → agents-LNNFTM53.js} +13 -11
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-2XCZLPKS.js → auth-KXXFI2VS.js} +6 -6
  8. package/dist/{chunk-YCWGATWI.js → chunk-24I7BN55.js} +2 -2
  9. package/dist/{chunk-EKMEHE4H.js → chunk-33YXPOE3.js} +2 -3
  10. package/dist/chunk-3BPUFZDL.js +37 -0
  11. package/dist/{chunk-WPQLXFOZ.js → chunk-43AOLP7E.js} +2 -2
  12. package/dist/{chunk-N4CZJQRK.js → chunk-5JGRAMKL.js} +4 -4
  13. package/dist/{chunk-BRXQQJFP.js → chunk-6US73PDB.js} +568 -47
  14. package/dist/{chunk-K6WL7QZT.js → chunk-6XXKFVSN.js} +2 -2
  15. package/dist/{chunk-QQK64KLB.js → chunk-CJUB2JJ2.js} +138 -20
  16. package/dist/{chunk-HV5X7OR2.js → chunk-CKXWIANG.js} +12 -12
  17. package/dist/{chunk-UZHIZC5S.js → chunk-CYQKWTG3.js} +61 -76
  18. package/dist/{chunk-QWU7ZBO7.js → chunk-DJVECN66.js} +204 -45
  19. package/dist/{chunk-ZWMF7253.js → chunk-E2ER4LJF.js} +304 -9
  20. package/dist/{chunk-7RXG6QRZ.js → chunk-EKY57CSP.js} +2 -75
  21. package/dist/{chunk-EDRHSCIE.js → chunk-EYPA3EGJ.js} +10 -2
  22. package/dist/{chunk-TTNYS3EA.js → chunk-G7MUEIGA.js} +1 -1
  23. package/dist/{chunk-BPGS2WCQ.js → chunk-GEYXPTRF.js} +2 -1
  24. package/dist/{chunk-BEY543CS.js → chunk-GOXNB3AO.js} +5 -2
  25. package/dist/{chunk-G4BMMOKF.js → chunk-HVDIIIQW.js} +2 -2
  26. package/dist/chunk-HWUFFB6L.js +83 -0
  27. package/dist/{chunk-35MKKU5R.js → chunk-K7T3E2SR.js} +15 -8
  28. package/dist/{chunk-VAWWTKDP.js → chunk-KHSFENX2.js} +2 -2
  29. package/dist/chunk-LCGCVYZ4.js +57 -0
  30. package/dist/{chunk-X6COSD2O.js → chunk-LYF7OHWH.js} +41 -14
  31. package/dist/{chunk-POHLU5DW.js → chunk-M6L6IDJG.js} +3 -3
  32. package/dist/{chunk-X4RCMKVQ.js → chunk-NDINPTJ4.js} +2 -2
  33. package/dist/{chunk-5M54SPOL.js → chunk-ODFEOB4F.js} +161 -5
  34. package/dist/{chunk-3JLKSKD7.js → chunk-OH3TOQTB.js} +5 -1
  35. package/dist/{chunk-MEQ45TQ4.js → chunk-PBTHKCPN.js} +18 -4
  36. package/dist/{chunk-ED4KHGC3.js → chunk-PPAMZ32Z.js} +9 -2
  37. package/dist/{chunk-QQL5RT5M.js → chunk-QM3F2GKX.js} +94 -36
  38. package/dist/{chunk-A2GZF7DC.js → chunk-QNQHSOLF.js} +4 -4
  39. package/dist/{chunk-KRPY7NTG.js → chunk-R46L2BIR.js} +3 -3
  40. package/dist/{chunk-BP4OYD6A.js → chunk-RY3LY4J5.js} +20 -2
  41. package/dist/{chunk-34475P3I.js → chunk-TSHXZTOQ.js} +5 -4
  42. package/dist/{chunk-VJWL6YS5.js → chunk-UUVG37B4.js} +2 -2
  43. package/dist/{chunk-2TZWSW76.js → chunk-WHGPSPT5.js} +2 -2
  44. package/dist/{chunk-TW3WDMVS.js → chunk-WHJYKASB.js} +2 -2
  45. package/dist/{chunk-YHZX5GEU.js → chunk-XAKHZX5N.js} +2 -2
  46. package/dist/{chunk-HXG4IURW.js → chunk-XE2VEJHX.js} +2 -2
  47. package/dist/{chunk-3HZ5RWN2.js → chunk-XF5N4U5A.js} +7 -6
  48. package/dist/{chunk-ZYKPLLNQ.js → chunk-XXQNGV4M.js} +590 -32
  49. package/dist/{chunk-4JUF2NNX.js → chunk-XYDYPRZI.js} +4 -4
  50. package/dist/{chunk-VMNQ6OZA.js → chunk-ZRGEBJ4T.js} +971 -794
  51. package/dist/{chunk-2LZI5CAG.js → chunk-ZXF4XRKW.js} +75 -33
  52. package/dist/{chunk-VSNATDE6.js → chunk-ZZMN5OM4.js} +2 -2
  53. package/dist/cli/index.js +31 -31
  54. package/dist/{clio-J5JIOIDS.js → clio-M2KGYUFZ.js} +2 -2
  55. package/dist/{code-nav-AXCXSBHX.js → code-nav-GQNL7XA6.js} +5 -5
  56. package/dist/codewiki/build-worker.js +4 -4
  57. package/dist/{components-KELWS457.js → components-5TTYYX6G.js} +3 -3
  58. package/dist/{config-OEBMIN2U.js → config-XUUYQIWO.js} +27 -25
  59. package/dist/{configure-PUQOSIXQ.js → configure-IHJ7YOMV.js} +7 -7
  60. package/dist/{context-URSXPBCK.js → context-74JLXAWD.js} +12 -12
  61. package/dist/{context-MGSE4Z2T.js → context-75MIWW3U.js} +24 -22
  62. package/dist/{context-EKDCKUUZ.js → context-ZQ7SIFJV.js} +8 -7
  63. package/dist/{context-clear-KDAJRNUK.js → context-clear-GYKWNUML.js} +24 -22
  64. package/dist/{context-index-BZ4UYMTC.js → context-index-SSR5ECNE.js} +3 -3
  65. package/dist/{context-working-set-SBKMPPI2.js → context-working-set-UX5KEP4J.js} +11 -10
  66. package/dist/{dispatch-runner-MSWN72NK.js → dispatch-runner-GIJBHNFL.js} +21 -20
  67. package/dist/{docs-2C2LTVT2.js → docs-6FZSCG5B.js} +3 -3
  68. package/dist/{doctor-7BSE27PJ.js → doctor-SVJ5BZCW.js} +4 -4
  69. package/dist/{eval-IZGDOO4H.js → eval-CG6LLBLD.js} +47 -232
  70. package/dist/{evidence-SR7WXB5B.js → evidence-ZYFIEN42.js} +19 -18
  71. package/dist/{evolve-K7VE2CBX.js → evolve-QGEXEMDW.js} +19 -18
  72. package/dist/{extensions-QVDOHDGJ.js → extensions-ADGNCJJD.js} +3 -3
  73. package/dist/{fleet-7XMJNQNF.js → fleet-S5R4ZOQY.js} +49 -30
  74. package/dist/{fleet-preflight-AQNAH644.js → fleet-preflight-BHSNPBMH.js} +2 -2
  75. package/dist/{init-JGNPAYXT.js → init-5DRU55YR.js} +31 -29
  76. package/dist/memory-7YKKR6UC.js +467 -0
  77. package/dist/{models-ZMMLFJNN.js → models-ZPOLRU2C.js} +10 -10
  78. package/dist/{monitor-2F3T5KHP.js → monitor-US5F5YGZ.js} +33 -18
  79. package/dist/{orchestrator-ORHT43JB.js → orchestrator-E2AL4T5N.js} +1092 -659
  80. package/dist/{paths-UXLN5YYZ.js → paths-E7KYAQWE.js} +3 -3
  81. package/dist/{reset-NXGTYNUO.js → reset-KZ652EK6.js} +3 -3
  82. package/dist/{run-RF4WJGMT.js → run-SRNBKDWD.js} +52 -40
  83. package/dist/{share-UT3W6E4M.js → share-CGZE33UP.js} +3 -3
  84. package/dist/{skills-PSACKC5Q.js → skills-S2X4DLY5.js} +4 -4
  85. package/dist/{skills-eval-WJSI55RZ.js → skills-eval-W2GGIC4R.js} +19 -18
  86. package/dist/{targets-PIIRAOYS.js → targets-54SWINWB.js} +14 -12
  87. package/dist/{terminal-lease-ULWXWNVY.js → terminal-lease-SAIF2OGY.js} +5 -4
  88. package/dist/{uninstall-FZCQCDKC.js → uninstall-BVLWXKBT.js} +3 -3
  89. package/dist/{upgrade-346TZ6AV.js → upgrade-JKAR27XC.js} +8 -8
  90. package/dist/{usage-6KKXR32N.js → usage-MSAWCLX4.js} +60 -27
  91. package/dist/{verifiers-4UUM6TEE.js → verifiers-NCBTHHN2.js} +60 -54
  92. package/dist/{wiki-generate-7STOCIFZ.js → wiki-generate-GUSOQ6ZP.js} +30 -28
  93. package/dist/worker/entry.js +69 -58
  94. package/dist/{workspace-G4ZWUIPR.js → workspace-ZJ6BFM3Q.js} +4 -4
  95. package/docs/README.md +3 -3
  96. package/docs/acp.md +1 -1
  97. package/docs/alcf-provider.md +1 -1
  98. package/docs/architecture.md +2 -2
  99. package/docs/artifact-placement.md +1 -2
  100. package/docs/artifact-versions.md +1 -1
  101. package/docs/built-in-agents.md +1 -1
  102. package/docs/capacity-and-scheduling.md +1 -1
  103. package/docs/commands-and-modes.md +9 -7
  104. package/docs/configuration-and-targets.md +12 -1
  105. package/docs/context-engine.md +4 -2
  106. package/docs/context-working-set.md +4 -4
  107. package/docs/development-pipeline.md +1 -1
  108. package/docs/documentation-coverage.md +3 -3
  109. package/docs/documentation-guide.md +2 -2
  110. package/docs/eval-runner.md +1 -1
  111. package/docs/evals-internal.md +4 -45
  112. package/docs/evidence-and-memory.md +67 -7
  113. package/docs/evolution.md +1 -1
  114. package/docs/exit-codes-and-output.md +1 -1
  115. package/docs/extensions-and-sharing.md +2 -2
  116. package/docs/fleet-dispatch.md +28 -2
  117. package/docs/installation-and-lifecycle.md +2 -2
  118. package/docs/middleware-and-components.md +19 -2
  119. package/docs/model-catalog.md +1 -1
  120. package/docs/observability.md +3 -3
  121. package/docs/proactive-memory.md +26 -16
  122. package/docs/prompt-envelope-and-tools.md +4 -2
  123. package/docs/provider-adapter-cookbook.md +1 -1
  124. package/docs/release-cut-checklist.md +38 -35
  125. package/docs/safety-model.md +29 -7
  126. package/docs/scientific-validation.md +3 -3
  127. package/docs/session-lifecycle.md +1 -1
  128. package/docs/skills-marketplace.md +1 -1
  129. package/docs/tool-usage.md +2 -2
  130. package/docs/trace-store.md +1 -1
  131. package/docs/troubleshooting.md +1 -1
  132. package/docs/tui-design.md +38 -4
  133. package/docs/worker-dispatch-mechanics.md +1 -1
  134. package/package.json +7 -4
  135. package/src/cli/agents.ts +2 -3
  136. package/src/cli/argv.ts +14 -1
  137. package/src/cli/fleet.ts +15 -0
  138. package/src/cli/index.ts +1 -1
  139. package/src/cli/memory.ts +272 -10
  140. package/src/cli/modes/json-stream.ts +2 -2
  141. package/src/cli/modes/print.ts +12 -1
  142. package/src/cli/run.ts +22 -2
  143. package/src/cli/targets.ts +12 -3
  144. package/src/cli/usage.ts +55 -7
  145. package/src/core/bus-events.ts +3 -0
  146. package/src/core/response-model-id.ts +134 -0
  147. package/src/core/toml.ts +62 -0
  148. package/src/core/workspace-files.ts +0 -1
  149. package/src/domains/agents/builtins/architect.md +1 -1
  150. package/src/domains/agents/catalog.ts +5 -4
  151. package/src/domains/agents/recipe.ts +54 -14
  152. package/src/domains/agents/result-contract.ts +7 -4
  153. package/src/domains/context/bootstrap.ts +36 -27
  154. package/src/domains/context/project-metadata.ts +19 -63
  155. package/src/domains/context/prompt-context.ts +8 -0
  156. package/src/domains/context/working-set/policies/index.ts +3 -4
  157. package/src/domains/dispatch/budget-envelope.ts +396 -0
  158. package/src/domains/dispatch/contract.ts +2 -0
  159. package/src/domains/dispatch/extension.ts +81 -27
  160. package/src/domains/dispatch/orphan-recovery.ts +1 -0
  161. package/src/domains/dispatch/receipt-integrity.ts +4 -0
  162. package/src/domains/dispatch/state.ts +1 -0
  163. package/src/domains/dispatch/types.ts +10 -3
  164. package/src/domains/dispatch/validation.ts +14 -0
  165. package/src/domains/dispatch/worker-spawn.ts +14 -3
  166. package/src/domains/eval/metrics/evidence.ts +0 -116
  167. package/src/domains/eval/metrics/invariants.ts +1 -1
  168. package/src/domains/eval/runners/clio-run.ts +1 -10
  169. package/src/domains/eval/runners/external-command.ts +2 -29
  170. package/src/domains/eval/schema/suite.ts +0 -7
  171. package/src/domains/eval/suites/run.ts +1 -7
  172. package/src/domains/memory/index.ts +22 -0
  173. package/src/domains/memory/operations.ts +58 -1
  174. package/src/domains/memory/promotion.ts +281 -0
  175. package/src/domains/memory/prompt-section.ts +25 -5
  176. package/src/domains/memory/proposal.ts +51 -7
  177. package/src/domains/memory/task-bank.ts +3 -2
  178. package/src/domains/memory/task-memory-handoff.ts +181 -24
  179. package/src/domains/memory/task-memory-policy.ts +3 -1
  180. package/src/domains/memory/types.ts +37 -0
  181. package/src/domains/memory/validate.ts +178 -0
  182. package/src/domains/middleware/memory-intervention.ts +35 -25
  183. package/src/domains/middleware/runtime.ts +6 -0
  184. package/src/domains/middleware/skills-reminder.ts +19 -4
  185. package/src/domains/middleware/stalled-turn.ts +43 -1
  186. package/src/domains/middleware/types.ts +10 -0
  187. package/src/domains/observability/contract.ts +6 -1
  188. package/src/domains/observability/cost.ts +20 -4
  189. package/src/domains/observability/extension.ts +2 -2
  190. package/src/domains/providers/index.ts +3 -0
  191. package/src/domains/providers/model-discovery.ts +9 -0
  192. package/src/domains/providers/runtime-resolution.ts +38 -1
  193. package/src/domains/providers/runtimes/common/probe-helpers.ts +97 -16
  194. package/src/domains/providers/types/context-window-slots.ts +18 -0
  195. package/src/domains/providers/types/runtime-descriptor.ts +3 -1
  196. package/src/domains/safety/call-target.ts +211 -14
  197. package/src/domains/safety/decision-presentation.ts +268 -0
  198. package/src/domains/safety/redaction.ts +73 -0
  199. package/src/domains/session/context-ledger.ts +10 -1
  200. package/src/domains/session/decision-board.ts +4 -0
  201. package/src/domains/session/entries.ts +3 -0
  202. package/src/domains/session/history.ts +68 -19
  203. package/src/domains/session/usage.ts +24 -7
  204. package/src/engine/acp/event-mapper.ts +7 -0
  205. package/src/engine/acp/server.ts +29 -2
  206. package/src/engine/apis/lmstudio.ts +25 -4
  207. package/src/engine/apis/openai-completions.ts +147 -22
  208. package/src/engine/claude/sdk-runtime.ts +8 -2
  209. package/src/engine/claude/tool-safety.ts +13 -0
  210. package/src/engine/loop-guard.ts +27 -3
  211. package/src/engine/worker-events.ts +4 -3
  212. package/src/engine/worker-runtime.ts +59 -54
  213. package/src/entry/orchestrator.ts +18 -1
  214. package/src/interactive/chat-loop-messages.ts +22 -0
  215. package/src/interactive/chat-loop.ts +13 -0
  216. package/src/interactive/chat-renderer.ts +19 -3
  217. package/src/interactive/clio-editor.ts +44 -7
  218. package/src/interactive/context-overlay.ts +43 -5
  219. package/src/interactive/cost-overlay.ts +39 -8
  220. package/src/interactive/dispatch-board.ts +212 -35
  221. package/src/interactive/footer/widgets.ts +13 -0
  222. package/src/interactive/interactive-application.ts +6 -1
  223. package/src/interactive/interactive-input-runtime.ts +11 -1
  224. package/src/interactive/interactive-presentation.ts +11 -1
  225. package/src/interactive/memory-overlay.ts +89 -4
  226. package/src/interactive/overlay-ask-user-lifecycle.ts +1 -1
  227. package/src/interactive/overlay-frame.ts +5 -2
  228. package/src/interactive/overlay-general-openers.ts +40 -1
  229. package/src/interactive/overlay-key-routing.ts +41 -1
  230. package/src/interactive/overlay-lifecycle.ts +11 -4
  231. package/src/interactive/overlay-permission-lifecycle.ts +23 -8
  232. package/src/interactive/overlay-transitions.ts +11 -0
  233. package/src/interactive/overlays/ask-user.ts +74 -30
  234. package/src/interactive/overlays/decisions.ts +3 -1
  235. package/src/interactive/permission-hint.ts +35 -0
  236. package/src/interactive/permission-overlay.ts +95 -45
  237. package/src/interactive/renderers/tool-execution.ts +19 -49
  238. package/src/interactive/session-last-turn.ts +8 -1
  239. package/src/interactive/session-usage-reseed.ts +36 -10
  240. package/src/interactive/slash-commands.ts +2 -2
  241. package/src/interactive/status/summary.ts +5 -0
  242. package/src/interactive/status/types.ts +5 -0
  243. package/src/interactive/terminal-lease.ts +1 -0
  244. package/src/interactive/turn-context.ts +96 -23
  245. package/src/interactive/turn-middleware.ts +1 -0
  246. package/src/interactive/turn-runtime.ts +37 -8
  247. package/src/interactive/turn-state.ts +3 -0
  248. package/src/interactive/worker-progress.ts +440 -0
  249. package/src/interactive/worker-stream.ts +51 -110
  250. package/src/tools/agent-tools.ts +28 -3
  251. package/src/tools/ask-user.ts +21 -1
  252. package/src/tools/context/index.ts +2 -2
  253. package/src/tools/dispatch-arguments.ts +8 -0
  254. package/src/tools/dispatch-event-text.ts +19 -0
  255. package/src/tools/dispatch.ts +24 -1
  256. package/src/tools/monitor.ts +15 -0
  257. package/src/tools/registry.ts +15 -5
  258. package/src/tools/result-disposition.ts +156 -0
  259. package/src/tools/result-shaping.ts +59 -1
  260. package/src/tools/verify/authoring.ts +55 -54
  261. package/src/tools/worker-evidence.ts +19 -0
  262. package/src/worker/spec-contract.ts +43 -3
  263. package/dist/chunk-EFADSJET.js +0 -18
  264. package/dist/memory-4ALKDJ4Q.js +0 -246
  265. package/src/domains/eval/metrics/chaos-stream.ts +0 -93
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  describeTaskMemoryActivity,
3
+ type MemoryProposalResult,
3
4
  type MemoryRecord,
4
5
  type TaskMemoryActivityEvent,
5
6
  type TaskMemoryEntry,
@@ -182,6 +183,7 @@ function memorySignature(status: TaskMemoryOperatorStatus, records: ReadonlyArra
182
183
 
183
184
  interface OpenMemoryOverlayOptions {
184
185
  onClose?: () => void;
186
+ onPromote?: (entry: TaskMemoryEntry, scope: "repo" | "global") => Promise<MemoryProposalResult>;
185
187
  }
186
188
 
187
189
  /** Master-detail memory view: status header, grouped list, scrollable detail pane. */
@@ -189,12 +191,18 @@ export class MemoryOverlayView implements Component {
189
191
  private readonly list: ListOverlayView;
190
192
  private signature: string | null = null;
191
193
  private renderMemo: { width: number; status: string; listLines: string[]; lines: string[] } | null = null;
194
+ private pendingGlobalEntryId: string | null = null;
195
+ private promotionMessage: { token: "muted" | "warning" | "success" | "error"; text: string } | null = null;
196
+ private promotionInFlight = false;
192
197
 
193
198
  constructor(
194
199
  private readonly getStatus: () => TaskMemoryOperatorStatus,
195
200
  private readonly getRecords: () => ReadonlyArray<MemoryRecord>,
196
201
  onClose: () => void,
197
202
  onChange: () => void,
203
+ private readonly promotion?: {
204
+ onPromote: (entry: TaskMemoryEntry, scope: "repo" | "global") => Promise<MemoryProposalResult>;
205
+ },
198
206
  ) {
199
207
  this.list = new ListOverlayView(
200
208
  {
@@ -203,6 +211,18 @@ export class MemoryOverlayView implements Component {
203
211
  filterable: true,
204
212
  layout: "split",
205
213
  emptyMessage: EMPTY_MESSAGE,
214
+ ...(promotion
215
+ ? {
216
+ hints: [
217
+ { key: "p", verb: "propose repo" },
218
+ { key: "g", verb: "propose global" },
219
+ ],
220
+ actions: {
221
+ p: (item: ListOverlayItem) => this.promote(item, "repo", onChange),
222
+ g: (item: ListOverlayItem) => this.promote(item, "global", onChange),
223
+ },
224
+ }
225
+ : {}),
206
226
  onClose,
207
227
  },
208
228
  onChange,
@@ -217,11 +237,21 @@ export class MemoryOverlayView implements Component {
217
237
  render(width: number): string[] {
218
238
  const status = this.sync();
219
239
  const statusLine = formatMemoryStatusLine(status, width);
240
+ const promotionLine =
241
+ this.promotionMessage === null
242
+ ? null
243
+ : fitUnits(clioTheme(), "", [clioTheme().fg(this.promotionMessage.token, this.promotionMessage.text)], width);
244
+ const statusKey = promotionLine === null ? statusLine : `${statusLine}\n${promotionLine}`;
220
245
  const listLines = this.list.render(width);
221
246
  const memo = this.renderMemo;
222
- if (memo && memo.width === width && memo.status === statusLine && memo.listLines === listLines) return memo.lines;
223
- const lines = [statusLine, rule(clioTheme(), width), ...listLines];
224
- this.renderMemo = { width, status: statusLine, listLines, lines };
247
+ if (memo && memo.width === width && memo.status === statusKey && memo.listLines === listLines) return memo.lines;
248
+ const lines = [
249
+ statusLine,
250
+ ...(promotionLine === null ? [] : [promotionLine]),
251
+ rule(clioTheme(), width),
252
+ ...listLines,
253
+ ];
254
+ this.renderMemo = { width, status: statusKey, listLines, lines };
225
255
  return lines;
226
256
  }
227
257
 
@@ -247,9 +277,63 @@ export class MemoryOverlayView implements Component {
247
277
  }
248
278
  return status;
249
279
  }
280
+
281
+ private promote(item: ListOverlayItem, scope: "repo" | "global", onChange: () => void): void {
282
+ if (this.promotion === undefined || this.promotionInFlight) return;
283
+ const entry = this.selectedBankEntry(item);
284
+ if (entry === null) {
285
+ this.pendingGlobalEntryId = null;
286
+ this.promotionMessage = { token: "warning", text: "select a knowledge or procedural task-bank entry" };
287
+ this.invalidate();
288
+ onChange();
289
+ return;
290
+ }
291
+ if (scope === "global" && this.pendingGlobalEntryId !== entry.id) {
292
+ this.pendingGlobalEntryId = entry.id;
293
+ this.promotionMessage = {
294
+ token: "warning",
295
+ text: `global scope broadens applicability for ${entry.id}; press g again to acknowledge`,
296
+ };
297
+ this.invalidate();
298
+ onChange();
299
+ return;
300
+ }
301
+ this.pendingGlobalEntryId = null;
302
+ this.promotionInFlight = true;
303
+ this.promotionMessage = { token: "muted", text: `proposing ${entry.id} with ${scope} scope` };
304
+ this.invalidate();
305
+ onChange();
306
+ void this.promotion
307
+ .onPromote(entry, scope)
308
+ .then((result) => {
309
+ this.promotionMessage = {
310
+ token: "success",
311
+ text: `${result.created ? "proposed" : "found existing"} ${result.record.id}; review, then run clio-coder memory approve ${result.record.id}`,
312
+ };
313
+ })
314
+ .catch((error: unknown) => {
315
+ this.promotionMessage = {
316
+ token: "error",
317
+ text: `promotion failed: ${error instanceof Error ? error.message : String(error)}`,
318
+ };
319
+ })
320
+ .finally(() => {
321
+ this.promotionInFlight = false;
322
+ this.invalidate();
323
+ onChange();
324
+ });
325
+ }
326
+
327
+ private selectedBankEntry(item: ListOverlayItem): TaskMemoryEntry | null {
328
+ if (!item.id.startsWith("bank:")) return null;
329
+ const entryId = item.id.slice("bank:".length);
330
+ const entry = bankEntries(this.getStatus().bank).find((candidate) => candidate.id === entryId);
331
+ if (entry === undefined || entry.kind === "status") return null;
332
+ return entry;
333
+ }
250
334
  }
251
335
 
252
- /** Mount the read-only durable-lessons and live task-bank view. */
336
+ /** Mount durable lessons and the live task bank with reviewed promotion actions. */
253
337
  export function openMemoryOverlay(
254
338
  tui: TUI,
255
339
  getStatus: () => TaskMemoryOperatorStatus,
@@ -261,6 +345,7 @@ export function openMemoryOverlay(
261
345
  getRecords,
262
346
  () => options.onClose?.(),
263
347
  () => tui.requestRender(),
348
+ options.onPromote ? { onPromote: options.onPromote } : undefined,
264
349
  );
265
350
  const handle = showClioOverlayFrame(tui, view, {
266
351
  anchor: "center",
@@ -77,7 +77,7 @@ export function createOverlayAskUserLifecycle(deps: OverlayAskUserLifecycleDeps)
77
77
  const activeSession = ensureSession();
78
78
  if (!activeSession) return cancelledAskUserResult();
79
79
  pendingCancel = cancel;
80
- const result = await activeSession.ask(questions);
80
+ const result = await activeSession.ask(questions, invokeOptions?.decisionPresentation);
81
81
  if (result.cancelled === true || !toolBacked) {
82
82
  if (result.cancelled === true) cancelledForTurn = true;
83
83
  close();
@@ -478,16 +478,19 @@ export function showClioOverlayFrame(
478
478
  const { title, footerHint, tone, width, visible, maxHeight, margin, ...overlayOptions } = options;
479
479
  const boxWidth = typeof width === "number" ? width : 0;
480
480
  const frame = new ClioOverlayFrame(child, title, footerHint, boxWidth, frameAlignForAnchor(options.anchor), tone);
481
- const marginRows = typeof margin === "number" ? margin * 2 : (margin?.top ?? 0) + (margin?.bottom ?? 0);
482
481
  return tui.showOverlay(frame, {
483
482
  ...overlayOptions,
484
483
  ...(margin !== undefined ? { margin } : {}),
485
484
  width: "100%",
486
485
  visible: (termWidth, termHeight) => {
486
+ const shown = visible ? visible(termWidth, termHeight) : true;
487
+ // A caller may update an object margin in its visibility callback to
488
+ // follow live content geometry. Read it after that callback, every frame.
489
+ const marginRows = typeof margin === "number" ? margin * 2 : (margin?.top ?? 0) + (margin?.bottom ?? 0);
487
490
  const available = Math.max(1, termHeight - marginRows);
488
491
  const requested = resolveRowSize(maxHeight, termHeight);
489
492
  frame.setRowBudget(requested === null ? available : Math.min(requested, available));
490
- return visible ? visible(termWidth, termHeight) : true;
493
+ return shown;
491
494
  },
492
495
  });
493
496
  }
@@ -1,7 +1,13 @@
1
+ import type { ClioSettings } from "../core/config.js";
1
2
  import type { SafeEventBus } from "../core/event-bus.js";
2
3
  import { foldWorkingSet } from "../domains/context/working-set/fold.js";
3
4
  import type { DispatchContract } from "../domains/dispatch/index.js";
4
- import { loadMemoryRecordsSync, type MemoryRecord } from "../domains/memory/index.js";
5
+ import {
6
+ canonicalMemoryRepositoryIdentity,
7
+ loadMemoryRecordsSync,
8
+ type MemoryRecord,
9
+ proposeMemoryPromotion,
10
+ } from "../domains/memory/index.js";
5
11
  import type { ObservabilityContract } from "../domains/observability/index.js";
6
12
  import type { ContextLedger } from "../domains/session/context-ledger.js";
7
13
  import type { SessionMeta } from "../domains/session/index.js";
@@ -57,6 +63,8 @@ export interface OverlayGeneralOpenersDeps {
57
63
  stateDir: string;
58
64
  getSessionMeta: () => SessionMeta | null;
59
65
  readSessionEntries?: ArtifactProviderDeps["readSessionEntries"];
66
+ /** Live settings, so `/context` can state the configured working-set policy. */
67
+ getSettings?: () => Readonly<ClioSettings>;
60
68
  terminal: { columns: number };
61
69
  dispatchBoard: ReturnType<typeof createDispatchBoardView>;
62
70
  startDispatchBoardTicker: () => void;
@@ -113,6 +121,10 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
113
121
  if (!readSessionEntries) return null;
114
122
  return foldWorkingSet(readSessionEntries(), deps.getSessionMeta()?.pinnedLeafTurnId ?? undefined);
115
123
  },
124
+ getWorkingSetConfig: () => {
125
+ const workingSet = deps.getSettings?.().context.workingSet;
126
+ return workingSet ? { enabled: workingSet.enabled, policy: workingSet.policy } : null;
127
+ },
116
128
  });
117
129
  deps.requestRender();
118
130
  };
@@ -218,6 +230,32 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
218
230
  deps.transitions.state = "memory";
219
231
  deps.transitions.handle = openMemoryOverlayFactory(deps.tui, deps.getTaskMemoryStatus, () => records, {
220
232
  onClose: deps.closeOverlay,
233
+ onPromote: async (entry, scope) => {
234
+ const meta = deps.getSessionMeta();
235
+ if (!meta) throw new Error("memory promotion requires an active session");
236
+ const selection =
237
+ scope === "global"
238
+ ? ({ scope: "global", acknowledgeGlobal: true } as const)
239
+ : (() => {
240
+ const repository = canonicalMemoryRepositoryIdentity(meta.cwd);
241
+ if (repository === null) {
242
+ throw new Error("repo promotion requires a canonical active repository identity");
243
+ }
244
+ return { scope: "repo", repository } as const;
245
+ })();
246
+ const result = await proposeMemoryPromotion(
247
+ deps.dataDir,
248
+ {
249
+ kind: "task-bank-entry",
250
+ sessionId: meta.id,
251
+ evidenceRefs: [`session-${meta.id}`],
252
+ entry,
253
+ },
254
+ selection,
255
+ );
256
+ records = loadMemoryRecordsSync(deps.dataDir);
257
+ return result;
258
+ },
221
259
  });
222
260
  deps.requestRender();
223
261
  };
@@ -261,6 +299,7 @@ export function createOverlayGeneralOpeners(deps: OverlayGeneralOpenersDeps): Ov
261
299
  const dispatchBoardHint = (): string => {
262
300
  const row = deps.dispatchBoard.selectedRow();
263
301
  const entries = [{ key: "↑↓", verb: "select" }];
302
+ if (row) entries.push({ key: "Enter", verb: deps.dispatchBoard.detailExpanded() ? "hide detail" : "detail" });
264
303
  if (row && isDispatchBoardRowSteerable(row)) entries.push({ key: "s", verb: "steer" });
265
304
  if (row && isDispatchBoardRowCancellable(row)) entries.push({ key: "x", verb: "cancel" });
266
305
  return buildHint(entries);
@@ -31,6 +31,19 @@ export interface PermissionOverlayKeyDeps {
31
31
  cancelPermission: () => void;
32
32
  confirmPermission: () => void;
33
33
  stopTurnFromPermission: () => void;
34
+ /**
35
+ * Whether the composer holds text. Enter must not allow a parked call while
36
+ * it does (issue #186): an operator who typed a message and pressed the
37
+ * habitual send key meant to send, and on a safety rail the ambiguous key
38
+ * resolves away from "allow". Absent means an empty composer.
39
+ */
40
+ composerHasDraft?: () => boolean;
41
+ /**
42
+ * Deliver a draft-editing key to the composer while the prompt owns input,
43
+ * so the draft that makes Enter inert can be cleared without first denying
44
+ * the call. Only the deletion keys in `isDraftEditKey` arrive here.
45
+ */
46
+ editDraft?: (data: string) => void;
34
47
  }
35
48
 
36
49
  export interface DispatchBoardOverlayKeyDeps {
@@ -39,6 +52,8 @@ export interface DispatchBoardOverlayKeyDeps {
39
52
  selectNextDispatch: () => void;
40
53
  steerSelectedDispatch: () => void;
41
54
  cancelSelectedDispatch: () => void;
55
+ /** Open or close the selected run's worker-progress detail. */
56
+ toggleSelectedDispatchDetail: () => void;
42
57
  }
43
58
 
44
59
  interface CloseOverlayKeyDeps {
@@ -55,10 +70,29 @@ export function isEscapeKey(data: string): boolean {
55
70
  return matchesKey(data, "escape") && !isKeyRelease(data);
56
71
  }
57
72
 
73
+ const DRAFT_EDIT_KEYS = ["backspace", "delete", "ctrl+u", "ctrl+w", "alt+backspace", "ctrl+k"] as const;
74
+
75
+ /**
76
+ * The keys that only ever remove text from the composer. They are the one
77
+ * class of input a pending permission prompt lets through, because they
78
+ * cannot type a command, cannot submit, and are the way out of the inert-Enter
79
+ * state a draft puts the prompt in.
80
+ */
81
+ function isDraftEditKey(data: string): boolean {
82
+ return !isKeyRelease(data) && DRAFT_EDIT_KEYS.some((key) => matchesKey(data, key));
83
+ }
84
+
58
85
  /** Pure permission overlay key router: returns true when the input was consumed. */
59
86
  export function routePermissionOverlayKey(data: string, deps: PermissionOverlayKeyDeps): boolean {
60
87
  if (matchesKey(data, "enter") && !isKeyRelease(data)) {
61
- deps.confirmPermission();
88
+ // Consumed either way: with a draft the press changes nothing, and the
89
+ // composer rail says what clears it. Letting it fall through would hand
90
+ // Enter to the editor, which is the send path this guard exists to block.
91
+ if (!(deps.composerHasDraft?.() ?? false)) deps.confirmPermission();
92
+ return true;
93
+ }
94
+ if (deps.editDraft && isDraftEditKey(data)) {
95
+ deps.editDraft(data);
62
96
  return true;
63
97
  }
64
98
  if (isEscapeKey(data)) {
@@ -98,6 +132,12 @@ export function routeDispatchBoardOverlayKey(data: string, deps: DispatchBoardOv
98
132
  deps.cancelSelectedDispatch();
99
133
  return true;
100
134
  }
135
+ // Worker progress is expanded detail the operator asks for. The default list
136
+ // stays compact so a fan-out of scouts costs one card each.
137
+ if (matchesKey(data, "enter")) {
138
+ deps.toggleSelectedDispatchDetail();
139
+ return true;
140
+ }
101
141
  return false;
102
142
  }
103
143
 
@@ -12,7 +12,9 @@ import {
12
12
  createPermissionOverlayBody,
13
13
  PERMISSION_OVERLAY_WIDTH,
14
14
  permissionOverlayHint,
15
+ permissionOverlayPlacement,
15
16
  permissionOverlayTitle,
17
+ permissionOverlayTone,
16
18
  } from "./permission-overlay.js";
17
19
 
18
20
  export * from "./overlay-key-routing.js";
@@ -82,7 +84,7 @@ export interface OverlayLifecycleRuntimeDeps {
82
84
  /** Rescopes the footer's last-turn line when a session overlay changes the branch. */
83
85
  setLastTurnSummary?: (summary: import("./status/index.js").TurnSummary | null) => void;
84
86
  keybindings: ReturnType<typeof import("./keybinding-manager.js").createKeybindingManager>;
85
- editor: Pick<import("./clio-editor.js").ClioEditor, "getText" | "setText">;
87
+ editor: Pick<import("./clio-editor.js").ClioEditor, "getText" | "render" | "setText">;
86
88
  getSlashContext: () => import("./slash-commands.js").SlashCommandContext;
87
89
  showOverlayFrame?: typeof showClioOverlayFrame;
88
90
  openAuthDialog?: typeof import("./overlays/auth-dialog.js").openAuthDialog;
@@ -194,6 +196,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
194
196
  cancelPendingAskUser: () => overlayAskUser?.cancelPending() ?? false,
195
197
  finishAuth: (dismiss) => overlayAuth.finish(dismiss),
196
198
  onPermissionOverlayClosed: () => overlayPermission?.onPermissionOverlayClosed(),
199
+ onOverlayClosed: () => overlayPermission?.retryPending(),
197
200
  });
198
201
  const closeOverlay = overlayTransitions.close;
199
202
 
@@ -227,10 +230,13 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
227
230
  if (overlayTransitions.state !== "closed") return false;
228
231
  overlayTransitions.state = "permission-confirm";
229
232
  overlayTransitions.handle = showOverlayFrame(tui, createPermissionOverlayBody(view), {
230
- anchor: "center",
233
+ ...permissionOverlayPlacement(tui, editor, footer.view),
231
234
  width: PERMISSION_OVERLAY_WIDTH,
232
- title: permissionOverlayTitle(),
233
- footerHint: (innerWidth) => permissionOverlayHint(innerWidth),
235
+ title: permissionOverlayTitle(view),
236
+ tone: permissionOverlayTone(view),
237
+ // Read per frame: the footer names what Enter does right now, and
238
+ // that depends on whether the composer holds a draft.
239
+ footerHint: (innerWidth) => permissionOverlayHint(innerWidth, editor.getText().length > 0),
234
240
  });
235
241
  tui.requestRender();
236
242
  return true;
@@ -351,6 +357,7 @@ export function createOverlayLifecycle(deps: OverlayLifecycleRuntimeDeps): Overl
351
357
  stateDir: deps.app.stateDir,
352
358
  getSessionMeta: () => deps.app.session?.current() ?? null,
353
359
  ...(deps.app.readSessionEntries ? { readSessionEntries: deps.app.readSessionEntries } : {}),
360
+ ...(deps.app.getSettings ? { getSettings: deps.app.getSettings } : {}),
354
361
  terminal,
355
362
  dispatchBoard,
356
363
  startDispatchBoardTicker: () => interactiveTickers.startDispatchBoardTicker(),
@@ -2,9 +2,11 @@ import { BusChannels, type PermissionRequestedPayload } from "../core/bus-events
2
2
  import type { SafeEventBus } from "../core/event-bus.js";
3
3
  import { ToolNames } from "../core/tool-names.js";
4
4
  import type { DispatchContract } from "../domains/dispatch/contract.js";
5
- import type { ClassifierCall } from "../domains/safety/action-classifier.js";
5
+ import type { ActionClass, ClassifierCall } from "../domains/safety/action-classifier.js";
6
6
  import { sanitizeCallTargetText } from "../domains/safety/call-target.js";
7
7
  import type { SafetyDecision } from "../domains/safety/contract.js";
8
+ import { decisionActionClass } from "../domains/safety/decision-presentation.js";
9
+ import { askUserExposure } from "../tools/ask-user.js";
8
10
  import type { PermissionRequiredMeta, ToolRegistry } from "../tools/registry.js";
9
11
  import { approvalParkedNotice, autonomyDeniedNotice, workerEscalationNotice } from "./bus-notices.js";
10
12
  import type { ToolApprovalStateEvent } from "./chat-loop.js";
@@ -29,7 +31,7 @@ interface WorkerEscalationEntry {
29
31
  requestId: string;
30
32
  agentId: string;
31
33
  tool: string;
32
- actionClass: string;
34
+ actionClass: ActionClass;
33
35
  axis: ApprovalRequestView["axis"];
34
36
  reason: string;
35
37
  target?: string;
@@ -60,6 +62,14 @@ export interface OverlayPermissionLifecycle {
60
62
  */
61
63
  stopTurn(): void;
62
64
  onPermissionOverlayClosed(): void;
65
+ /**
66
+ * Re-present whatever is still parked once no overlay holds the screen. A
67
+ * request that arrived while `/context` or a picker was open could only be
68
+ * announced, and nothing re-attempted the dialog when that overlay closed:
69
+ * the transcript said awaiting approval, the footer said confirm, and the
70
+ * operator had no key to press (issue #186).
71
+ */
72
+ retryPending(): void;
63
73
  dispose(): void;
64
74
  }
65
75
 
@@ -114,7 +124,7 @@ function mainApprovalRequestView(
114
124
  (axisFromDecision.kind === "net"
115
125
  ? { kind: "net" as const, ruleId: axisFromDecision.ruleId }
116
126
  : { kind: "autonomy" as const, level: autonomy });
117
- const target = describeCallTarget(call.args);
127
+ const target = describeCallTarget(call.tool, call.args);
118
128
  return {
119
129
  requestId: meta?.requestId ?? "permission-pending",
120
130
  tool: call.tool,
@@ -126,6 +136,7 @@ function mainApprovalRequestView(
126
136
  ...(call.tool === ToolNames.Dispatch && decision.kind === "ask"
127
137
  ? { artifact: { kind: "dispatch-plan" as const, text: decision.rejection.detail } }
128
138
  : {}),
139
+ ...(call.tool === ToolNames.AskUser ? { exposure: askUserExposure(call.args) } : {}),
129
140
  ...(target.length > 0 ? { target } : {}),
130
141
  ...(queueDepth !== undefined && queueDepth > 1 ? { queueDepth } : {}),
131
142
  };
@@ -141,7 +152,7 @@ function workerEscalationEntry(payload: PermissionRequestedPayload, autonomy: st
141
152
  requestId: payload.requestId,
142
153
  agentId: typeof payload.agentId === "string" ? payload.agentId : "worker",
143
154
  tool: typeof payload.tool === "string" ? payload.tool : "unknown",
144
- actionClass: typeof payload.actionClass === "string" ? payload.actionClass : "unknown",
155
+ actionClass: decisionActionClass(payload.actionClass),
145
156
  axis:
146
157
  axisViewFromId(typeof payload.axis === "string" ? payload.axis : undefined, autonomy) ??
147
158
  (typeof payload.ruleId === "string"
@@ -188,6 +199,12 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
188
199
  const next = workerQueue.shift();
189
200
  if (next) openWorker(next);
190
201
  };
202
+ const retryPending = (): void => {
203
+ maybeOpenWorker();
204
+ if (deps.getOverlayState() === "closed" && deps.toolRegistry?.hasParkedCalls()) {
205
+ deps.toolRegistry.renotifyHead();
206
+ }
207
+ };
191
208
 
192
209
  const unsubscribePermission =
193
210
  deps.toolRegistry?.onPermissionRequired((call, decision, meta) => {
@@ -310,10 +327,7 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
310
327
  workerQueue.length = 0;
311
328
  return;
312
329
  }
313
- maybeOpenWorker();
314
- if (deps.getOverlayState() === "closed" && deps.toolRegistry?.hasParkedCalls()) {
315
- deps.toolRegistry.renotifyHead();
316
- }
330
+ retryPending();
317
331
  };
318
332
 
319
333
  return {
@@ -336,6 +350,7 @@ export function createOverlayPermissionLifecycle(deps: OverlayPermissionLifecycl
336
350
  );
337
351
  },
338
352
  onPermissionOverlayClosed,
353
+ retryPending,
339
354
  dispose: () => {
340
355
  unsubscribePermission();
341
356
  unsubscribeWorker();
@@ -9,6 +9,12 @@ export interface OverlayTransitionsDeps {
9
9
  cancelPendingAskUser: () => boolean;
10
10
  finishAuth: (dismiss: boolean) => void;
11
11
  onPermissionOverlayClosed: () => void;
12
+ /**
13
+ * Runs after any overlay has closed and the state is back to `closed`. The
14
+ * permission lifecycle uses it to re-present a call that parked while this
15
+ * overlay held the screen; nothing else re-attempts that dialog.
16
+ */
17
+ onOverlayClosed?: () => void;
12
18
  }
13
19
 
14
20
  export interface OverlayTransitions {
@@ -26,7 +32,11 @@ export function createOverlayTransitions(deps: OverlayTransitionsDeps): OverlayT
26
32
  return state;
27
33
  },
28
34
  set state(next) {
35
+ const closing = state !== "closed" && next === "closed";
29
36
  state = next;
37
+ // The ask-user and auth lifecycles leave by assigning the state rather
38
+ // than through `close`, so the hook fires here for them.
39
+ if (closing) deps.onOverlayClosed?.();
30
40
  },
31
41
  get handle() {
32
42
  return handle;
@@ -50,6 +60,7 @@ export function createOverlayTransitions(deps: OverlayTransitionsDeps): OverlayT
50
60
  handle?.hide();
51
61
  handle = null;
52
62
  if (leaving === "permission-confirm") deps.onPermissionOverlayClosed();
63
+ else deps.onOverlayClosed?.();
53
64
  deps.renderContextIsland();
54
65
  deps.renderTaskIsland();
55
66
  deps.requestRender();