@iowarp/clio-coder 0.3.2 → 0.3.3

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 (115) hide show
  1. package/CHANGELOG.md +229 -457
  2. package/README.md +2 -2
  3. package/dist/{acp-BIYHVZIM.js → acp-P2AQILE2.js} +2 -2
  4. package/dist/{agents-YT6SSRIT.js → agents-72W3BI7I.js} +8 -8
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{chunk-WMSVI4G2.js → chunk-2DJ2KNFG.js} +3 -3
  7. package/dist/{chunk-2EHAIA3X.js → chunk-2TLUCQVG.js} +2 -2
  8. package/dist/{chunk-WVO7V2QY.js → chunk-4XUGQOHA.js} +2 -2
  9. package/dist/{chunk-IGLFWIYI.js → chunk-5UFT4SUX.js} +2 -2
  10. package/dist/{chunk-AO4RKG4M.js → chunk-6SGHMWE3.js} +3 -3
  11. package/dist/{chunk-X75S7HFS.js → chunk-COU2UHX6.js} +45 -19
  12. package/dist/{chunk-KJ5LWLOE.js → chunk-DSELYM6W.js} +2 -2
  13. package/dist/{chunk-G2DE3C7R.js → chunk-DUYJ5IO6.js} +2 -2
  14. package/dist/{chunk-EPVUXGXG.js → chunk-FNTMWMX5.js} +9 -9
  15. package/dist/{chunk-MNA4JGU4.js → chunk-J7CWMCQD.js} +2 -2
  16. package/dist/{chunk-MBS4V7ZP.js → chunk-KZWTDYJF.js} +4 -4
  17. package/dist/{chunk-LBNRH5WM.js → chunk-LM5TQCJZ.js} +3 -3
  18. package/dist/{chunk-OHHN2SO4.js → chunk-LW6DSM3M.js} +7 -7
  19. package/dist/{chunk-MQSRRFWA.js → chunk-LWLEKMDQ.js} +56 -2
  20. package/dist/{chunk-V4RXGQ5Q.js → chunk-OC7FIQPC.js} +2 -2
  21. package/dist/{chunk-3ZXDFGR5.js → chunk-PAJK6MAQ.js} +2 -2
  22. package/dist/{chunk-7EYHLWU7.js → chunk-PIWWS5BL.js} +3 -3
  23. package/dist/{chunk-6EJV5X2W.js → chunk-SRDMMSEP.js} +3 -3
  24. package/dist/{chunk-ARBGF5F7.js → chunk-TZK7PACC.js} +2 -2
  25. package/dist/{chunk-QTYWRVRA.js → chunk-UFIIWP2H.js} +2 -2
  26. package/dist/{chunk-J5HN4RYU.js → chunk-V6RTAOC2.js} +2 -2
  27. package/dist/{chunk-SRF2PJNW.js → chunk-VPAYEGVX.js} +2 -2
  28. package/dist/{chunk-77VKQEHF.js → chunk-X6IAEBZR.js} +2 -2
  29. package/dist/{chunk-4KLWL3UC.js → chunk-XBXAASKX.js} +2 -2
  30. package/dist/{chunk-MAW544W2.js → chunk-ZWMF7253.js} +4 -4
  31. package/dist/cli/index.js +17 -17
  32. package/dist/{clio-4LY5K2AC.js → clio-JOU4FXVA.js} +2 -2
  33. package/dist/{config-GTLUW2PR.js → config-XCDVKR23.js} +7 -7
  34. package/dist/{configure-R6A64DHX.js → configure-4GAP54ZW.js} +5 -5
  35. package/dist/{context-RW5HC47S.js → context-4UOGGLQ5.js} +6 -6
  36. package/dist/{context-JFZEJ7W5.js → context-77FM5DV5.js} +9 -9
  37. package/dist/{context-clear-6ZHBAZZT.js → context-clear-XXJRLCJJ.js} +6 -6
  38. package/dist/{dispatch-runner-VKBRCWQC.js → dispatch-runner-QPRDDBDX.js} +7 -7
  39. package/dist/{doctor-KI767GSN.js → doctor-HR46URBJ.js} +5 -5
  40. package/dist/{evidence-UA6AWDQQ.js → evidence-6HG2PY2B.js} +4 -4
  41. package/dist/{evolve-QNTFGV6Z.js → evolve-K7YU3NCY.js} +4 -4
  42. package/dist/{fleet-Q7UOMUSG.js → fleet-VY3HHKN6.js} +15 -15
  43. package/dist/{init-WBB65ZHQ.js → init-JYGXI3FK.js} +12 -12
  44. package/dist/{memory-MD3O64RI.js → memory-WFZMGYHX.js} +5 -5
  45. package/dist/{models-BZU34YWD.js → models-I5QWSEOM.js} +7 -7
  46. package/dist/{monitor-MEQA5C3I.js → monitor-GE4ID3IA.js} +5 -5
  47. package/dist/{orchestrator-CGFKEP27.js → orchestrator-EM5MC3HM.js} +1482 -1055
  48. package/dist/{run-IV4Q6RLN.js → run-ZU3QMZPZ.js} +18 -18
  49. package/dist/{skills-LQEKRDTN.js → skills-X5VXCRNQ.js} +2 -2
  50. package/dist/{skills-eval-3DC4HEWS.js → skills-eval-WKIHWTHR.js} +5 -5
  51. package/dist/{targets-C4SSGQOB.js → targets-SNCPI2NR.js} +8 -8
  52. package/dist/{terminal-lease-IT5JW2NR.js → terminal-lease-BNAHVHBS.js} +2 -2
  53. package/dist/{upgrade-7TT7SQ3G.js → upgrade-JQHHPQ4K.js} +7 -7
  54. package/dist/{usage-GV4PKT3M.js → usage-OR4O5SMZ.js} +5 -5
  55. package/dist/{verify-G6V4D2G7.js → verify-375KUB3Y.js} +4 -4
  56. package/dist/{wiki-generate-DQF6Z66B.js → wiki-generate-UEXP2ARI.js} +11 -11
  57. package/dist/worker/entry.js +8 -8
  58. package/docs/README.md +3 -3
  59. package/docs/acp.md +1 -1
  60. package/docs/alcf-provider.md +1 -1
  61. package/docs/architecture.md +2 -2
  62. package/docs/artifact-versions.md +1 -1
  63. package/docs/built-in-agents.md +1 -1
  64. package/docs/capacity-and-scheduling.md +1 -1
  65. package/docs/commands-and-modes.md +1 -1
  66. package/docs/configuration-and-targets.md +1 -1
  67. package/docs/context-engine.md +1 -1
  68. package/docs/development-pipeline.md +1 -1
  69. package/docs/documentation-coverage.md +2 -2
  70. package/docs/documentation-guide.md +1 -1
  71. package/docs/eval-runner.md +1 -1
  72. package/docs/evals-internal.md +1 -1
  73. package/docs/evidence-and-memory.md +2 -2
  74. package/docs/evolution.md +1 -1
  75. package/docs/exit-codes-and-output.md +1 -1
  76. package/docs/extensions-and-sharing.md +2 -2
  77. package/docs/fleet-dispatch.md +1 -1
  78. package/docs/installation-and-lifecycle.md +6 -6
  79. package/docs/middleware-and-components.md +1 -1
  80. package/docs/model-catalog.md +1 -1
  81. package/docs/observability.md +3 -3
  82. package/docs/performance-methodology.md +2 -2
  83. package/docs/proactive-memory.md +1 -1
  84. package/docs/prompt-envelope-and-tools.md +1 -1
  85. package/docs/provider-adapter-cookbook.md +1 -1
  86. package/docs/release-cut-checklist.md +30 -30
  87. package/docs/safety-model.md +2 -2
  88. package/docs/scientific-validation.md +3 -3
  89. package/docs/session-lifecycle.md +2 -2
  90. package/docs/skills-marketplace.md +1 -1
  91. package/docs/tool-usage.md +2 -2
  92. package/docs/trace-store.md +1 -1
  93. package/docs/troubleshooting.md +1 -1
  94. package/docs/tui-design.md +2 -2
  95. package/docs/worker-dispatch-mechanics.md +1 -1
  96. package/package.json +1 -1
  97. package/src/core/git-commit-attribution.ts +46 -21
  98. package/src/domains/config/keybindings.ts +3 -3
  99. package/src/interactive/chat-panel.ts +555 -244
  100. package/src/interactive/chat-renderer.ts +50 -19
  101. package/src/interactive/editor-submit.ts +26 -1
  102. package/src/interactive/footer/widgets.ts +22 -20
  103. package/src/interactive/footer-panel.ts +6 -1
  104. package/src/interactive/interactive-application.ts +2 -0
  105. package/src/interactive/interactive-event-projection.ts +12 -0
  106. package/src/interactive/interactive-slash-runtime.ts +12 -7
  107. package/src/interactive/overlays/ask-user.ts +146 -24
  108. package/src/interactive/renderers/tool-execution.ts +151 -56
  109. package/src/interactive/status/index.ts +12 -1
  110. package/src/interactive/status/reasoning.ts +87 -0
  111. package/src/interactive/status/summary.ts +13 -2
  112. package/src/interactive/transcript-detail.ts +120 -0
  113. package/src/tools/builtin-tool-catalog.ts +7 -1
  114. package/src/tools/presentation.ts +107 -0
  115. package/src/tools/registry.ts +6 -0
@@ -3,7 +3,7 @@ import {
3
3
  bootstrapInputFromInitOptions,
4
4
  modelBootstrapGenerate,
5
5
  resolveBootstrapRoute
6
- } from "./chunk-EPVUXGXG.js";
6
+ } from "./chunk-FNTMWMX5.js";
7
7
  import "./chunk-J5Q24KAG.js";
8
8
  import {
9
9
  assistantSessionPayload,
@@ -37,37 +37,37 @@ import {
37
37
  toolNamesFromAgentState,
38
38
  toolResultSummary,
39
39
  toolSignatureFromState
40
- } from "./chunk-KJ5LWLOE.js";
40
+ } from "./chunk-DSELYM6W.js";
41
41
  import {
42
42
  SchedulingDomainModule,
43
43
  createPromptsDomainModule,
44
44
  renderAgentCatalogSectionsFromSpecs
45
- } from "./chunk-LBNRH5WM.js";
45
+ } from "./chunk-LM5TQCJZ.js";
46
46
  import {
47
47
  ShareDomainModule
48
48
  } from "./chunk-ORBHGJC5.js";
49
49
  import {
50
50
  AgentsDomainModule,
51
51
  SafetyDomainModule
52
- } from "./chunk-3ZXDFGR5.js";
52
+ } from "./chunk-PAJK6MAQ.js";
53
53
  import {
54
54
  LifecycleDomainModule,
55
55
  describeUpgradeNotice
56
- } from "./chunk-IGLFWIYI.js";
56
+ } from "./chunk-5UFT4SUX.js";
57
57
  import {
58
58
  ConfigDomainModule,
59
59
  createConfigDomainModule,
60
60
  loadDomains
61
- } from "./chunk-V4RXGQ5Q.js";
61
+ } from "./chunk-OC7FIQPC.js";
62
62
  import {
63
63
  ensureClioState,
64
64
  takeUpgradeNotice
65
- } from "./chunk-QTYWRVRA.js";
65
+ } from "./chunk-UFIIWP2H.js";
66
66
  import {
67
67
  compileScoutTransition,
68
68
  createDispatchRunEventRegistry
69
- } from "./chunk-AO4RKG4M.js";
70
- import "./chunk-4KLWL3UC.js";
69
+ } from "./chunk-6SGHMWE3.js";
70
+ import "./chunk-XBXAASKX.js";
71
71
  import {
72
72
  monitorToolSurface
73
73
  } from "./chunk-FJ3H4MN5.js";
@@ -78,7 +78,7 @@ import {
78
78
  costAggregateForAmount,
79
79
  formatCostAggregate,
80
80
  readAccountabilitySummary
81
- } from "./chunk-WVO7V2QY.js";
81
+ } from "./chunk-4XUGQOHA.js";
82
82
  import "./chunk-HC4CLZ2Y.js";
83
83
  import "./chunk-HFSBBKSQ.js";
84
84
  import {
@@ -92,7 +92,7 @@ import "./chunk-CBCAPZAA.js";
92
92
  import {
93
93
  createContextDomainModule,
94
94
  loadProjectClioMd
95
- } from "./chunk-7EYHLWU7.js";
95
+ } from "./chunk-PIWWS5BL.js";
96
96
  import {
97
97
  renderCodewikiDigest
98
98
  } from "./chunk-5WIGXA4T.js";
@@ -107,7 +107,7 @@ import {
107
107
  taskMemoryBankSize,
108
108
  taskMemoryHandoffSeedOffer,
109
109
  taskMemoryTracePath
110
- } from "./chunk-J5HN4RYU.js";
110
+ } from "./chunk-V6RTAOC2.js";
111
111
  import {
112
112
  ACP_MAX_CHUNK_BYTES,
113
113
  ACP_MAX_RAW_RECORD_BYTES,
@@ -141,7 +141,7 @@ import {
141
141
  readPendingGateDecisions,
142
142
  recoverCleanupReadyCompeteGroups,
143
143
  requireAgentSteps
144
- } from "./chunk-6EJV5X2W.js";
144
+ } from "./chunk-SRDMMSEP.js";
145
145
  import "./chunk-4BJ5BYCE.js";
146
146
  import {
147
147
  parseRigorOverride,
@@ -209,12 +209,12 @@ import {
209
209
  resolveSessionTools,
210
210
  sanitizeCallTargetText,
211
211
  sanitizeLockedSynthesisMessage
212
- } from "./chunk-OHHN2SO4.js";
212
+ } from "./chunk-LW6DSM3M.js";
213
213
  import {
214
214
  HEADLESS_PERMISSION_DENIED_REASON,
215
215
  combineBashOutput,
216
216
  runBashCommand
217
- } from "./chunk-MNA4JGU4.js";
217
+ } from "./chunk-J7CWMCQD.js";
218
218
  import {
219
219
  MIDDLEWARE_HOOK_TEXT_MAX_CHARS,
220
220
  MiddlewareDomainModule,
@@ -230,7 +230,7 @@ import {
230
230
  installUserHooks,
231
231
  openDetachedBatchViews,
232
232
  writeMiddlewareDiagnosticToStderr
233
- } from "./chunk-WMSVI4G2.js";
233
+ } from "./chunk-2DJ2KNFG.js";
234
234
  import "./chunk-IR4CFBFN.js";
235
235
  import "./chunk-UOV2BYIW.js";
236
236
  import {
@@ -244,7 +244,7 @@ import {
244
244
  stringArg,
245
245
  timeoutMsArg,
246
246
  withResolvedDispatchPlan
247
- } from "./chunk-G2DE3C7R.js";
247
+ } from "./chunk-DUYJ5IO6.js";
248
248
  import {
249
249
  TaskMemoryBank,
250
250
  createTaskMemoryTelemetrySink
@@ -256,12 +256,14 @@ import {
256
256
  TOOL_PROFILE_NAMES,
257
257
  approvedRouteCandidates,
258
258
  builtin,
259
+ classifyResourceRead,
259
260
  createTaskBoardStore,
260
261
  foldSessionTaskHistory,
261
262
  isToolProfileName,
262
263
  runKindSupportsLiveSteering,
263
- taskBoardCounts
264
- } from "./chunk-MQSRRFWA.js";
264
+ taskBoardCounts,
265
+ toolPresentationPolicy
266
+ } from "./chunk-LWLEKMDQ.js";
265
267
  import {
266
268
  DEFAULT_AUTONOMY_LEVEL,
267
269
  DEFAULT_RECENT_ENTRY_LIMIT,
@@ -283,7 +285,7 @@ import {
283
285
  import "./chunk-CMZWFGD2.js";
284
286
  import "./chunk-I4HZDVNP.js";
285
287
  import "./chunk-ZI647VB5.js";
286
- import "./chunk-ARBGF5F7.js";
288
+ import "./chunk-TZK7PACC.js";
287
289
  import {
288
290
  VERIFICATION_SCRIPT_FAMILY_HINT
289
291
  } from "./chunk-7MNJORFF.js";
@@ -330,7 +332,7 @@ import {
330
332
  InteropDomainModule,
331
333
  delegationEntryForKind,
332
334
  renderProposalEntry
333
- } from "./chunk-2EHAIA3X.js";
335
+ } from "./chunk-2TLUCQVG.js";
334
336
  import "./chunk-BMEMKKIT.js";
335
337
  import {
336
338
  source_default
@@ -406,10 +408,10 @@ import {
406
408
  sessionPaths
407
409
  } from "./chunk-M6SHUN7Q.js";
408
410
  import "./chunk-R346GLFC.js";
409
- import "./chunk-SRF2PJNW.js";
411
+ import "./chunk-VPAYEGVX.js";
410
412
  import {
411
413
  setGitCommitAttributionEnabled
412
- } from "./chunk-X75S7HFS.js";
414
+ } from "./chunk-COU2UHX6.js";
413
415
  import {
414
416
  MAX_TIMER_DELAY_MS,
415
417
  rawDurationMs
@@ -498,7 +500,7 @@ import {
498
500
  validateKeybindings,
499
501
  visibleWidth,
500
502
  wrapTextWithAnsi
501
- } from "./chunk-MAW544W2.js";
503
+ } from "./chunk-ZWMF7253.js";
502
504
  import {
503
505
  getTerminationCoordinator
504
506
  } from "./chunk-TZTZS7QK.js";
@@ -4778,6 +4780,7 @@ var FULL_RESULT_PREVIEW_LIMIT = 6e4;
4778
4780
  var FULL_RESULT_ROW_LIMIT = 120;
4779
4781
  var STREAMING_RESULT_ROW_LIMIT = 20;
4780
4782
  var ARGS_BODY_LINE_LIMIT = 24;
4783
+ var FOLDED_DIFF_ROW_LIMIT = 24;
4781
4784
  var STATUS_OK_GLYPH = GLYPH.ok;
4782
4785
  var STATUS_ERROR_GLYPH = GLYPH.error;
4783
4786
  var RAIL_DIM = dim(BODY_INDENT_PLAIN);
@@ -4914,18 +4917,6 @@ function summarizeArgs(toolName2, args) {
4914
4917
  if (primary !== null) return truncate2(displayArg(toolName2, primary), ARG_PREVIEW_LIMIT);
4915
4918
  return truncate2(jsonStringifySafe(safeArgs), ARG_PREVIEW_LIMIT);
4916
4919
  }
4917
- function classifyResourceRead(toolName2, args) {
4918
- if (toolName2 !== "read") return null;
4919
- const path = readStringField(args, "path");
4920
- if (path === null) return null;
4921
- const normalized = path.replace(/\\/g, "/");
4922
- const base = normalized.split("/").pop() ?? "";
4923
- if (base === "SKILL.md") return "skill";
4924
- if (base === "CLIO-CODER.md") return "handbook";
4925
- if (base === "AGENTS.md") return "agents";
4926
- if (/(^|\/)docs\//.test(normalized)) return "docs";
4927
- return null;
4928
- }
4929
4920
  function detailsOf(result) {
4930
4921
  if (!isPlainObject(result)) return null;
4931
4922
  return isPlainObject(result.details) ? result.details : null;
@@ -5047,14 +5038,45 @@ function ledgerTail(finished) {
5047
5038
  const parts = [];
5048
5039
  const outcome = outcomeSummary(finished);
5049
5040
  if (outcome !== null) parts.push(outcome);
5050
- const bytes = isNonExecutedOutcome(finished.outcome) ? null : shownBytesOf(finished);
5051
- if (bytes !== null && bytes > 0) parts.push(formatSize(bytes));
5052
- const offloadPath = offloadPathOf(finished);
5041
+ const executed = !isNonExecutedOutcome(finished.outcome);
5042
+ if (executed && finished.toolName === "bash" && !finished.isError) {
5043
+ parts.push(`exit ${structuredExitCode(finished) ?? "0"}`);
5044
+ }
5045
+ const bytes = executed ? shownBytesOf(finished) : null;
5046
+ if (bytes !== null && bytes > 0) {
5047
+ const total = totalBytesOf(finished);
5048
+ parts.push(
5049
+ total !== null && total > bytes ? `${formatSize(bytes)} shown / ${formatSize(total)} total` : formatSize(bytes)
5050
+ );
5051
+ }
5052
+ if (executed) {
5053
+ if (isTruncatedResult(finished)) parts.push("truncated");
5054
+ const details = detailsOf(finished.result);
5055
+ if (details?.timedOut === true) parts.push("timed out");
5056
+ if (details?.outputCapped === true) parts.push("output capped");
5057
+ }
5058
+ if (finished.excludeFromContext === true) parts.push("excluded from context");
5059
+ const offloadPath = executed ? offloadPathOf(finished) : null;
5053
5060
  return {
5054
5061
  facts: parts.length > 0 ? dim(` \xB7 ${parts.join(" \xB7 ")}`) : "",
5055
5062
  offload: offloadPath !== null ? dim(` \xB7 full: ${offloadPath}`) : ""
5056
5063
  };
5057
5064
  }
5065
+ var FAILURE_EXCERPT_LIMIT = 80;
5066
+ function failureExcerpt(finished, width) {
5067
+ if (!finished.isError || !toolPresentationPolicy(finished.toolName, finished.args).failureExcerpt) return "";
5068
+ if (isNonExecutedOutcome(finished.outcome)) return "";
5069
+ const text = unwrapResultEnvelope(finished.result);
5070
+ if (typeof text !== "string") return "";
5071
+ let excerpt;
5072
+ for (const raw of text.split("\n")) {
5073
+ const line = sanitizeCallTargetText(raw).trim();
5074
+ if (line.length > 0) excerpt = line;
5075
+ }
5076
+ if (excerpt === void 0) return "";
5077
+ const limit = Math.min(FAILURE_EXCERPT_LIMIT, Math.max(20, width - 20));
5078
+ return dim(` \xB7 ${truncate2(excerpt, limit)}`);
5079
+ }
5058
5080
  var AWAITING_APPROVAL_TAIL = ` ${yellow(GLYPH.phaseBlocked)}${dim(" awaiting approval")}`;
5059
5081
  function renderToolAwaitingApproval(call, width, view) {
5060
5082
  const parts = sublineParts({ toolCallId: call.toolCallId, toolName: call.toolName, args: call.args }, void 0, {});
@@ -5090,8 +5112,8 @@ function statusGlyph(status, meta = {}) {
5090
5112
  return ` ${red(STATUS_ERROR_GLYPH)}${outcomeSuffix}${reasonSuffix}${exitSuffix}${durationSuffix}`;
5091
5113
  }
5092
5114
  function headerLine(toolName2, args, status, meta = {}) {
5093
- const summary = summarizeArgs(toolName2, args);
5094
- const head = `${dim(HEADER_PREFIX_PLAIN)}${cyanBold(toolName2)}${dim("(")}${cyan(summary)}${dim(")")}`;
5115
+ const body = styleSublineBody(buildSublineBody(toolName2, args, status, void 0, meta.outcome));
5116
+ const head = `${dim(HEADER_PREFIX_PLAIN)}${body}`;
5095
5117
  return `${head}${statusGlyph(status, meta)}`;
5096
5118
  }
5097
5119
  function sublineLead(token, rest) {
@@ -5102,8 +5124,9 @@ function styleSublineBody(body) {
5102
5124
  if (match?.groups?.lead === void 0 || match.groups.rest === void 0) return body;
5103
5125
  return sublineLead(match.groups.lead, match.groups.rest);
5104
5126
  }
5105
- function buildUnknownToolBody(toolName2, args) {
5106
- return `${toolName2}(${summarizeArgs(toolName2, args)})`;
5127
+ function buildGenericToolBody(toolName2, args) {
5128
+ const summary = summarizeArgs(toolName2, args);
5129
+ return summary.length > 0 ? `tool action ${summary}` : "tool action";
5107
5130
  }
5108
5131
  function buildFieldSublineBody(args, key, lead, options = {}) {
5109
5132
  const value = readStringField(args, key);
@@ -5145,6 +5168,7 @@ var SUBLINE_BODY_BUILDERS = {
5145
5168
  bash: (args) => buildFieldSublineBody(args, "command", "running ", { wrapInBackticks: true }),
5146
5169
  grep: (args) => buildFieldSublineBody(args, "pattern", "searching for ", { wrapInBackticks: true }),
5147
5170
  find: (args) => buildFieldSublineBody(args, "pattern", "finding ", { wrapInBackticks: true }),
5171
+ web_fetch: (args) => buildFieldSublineBody(args, "url", "fetching "),
5148
5172
  git: (args) => buildFieldSublineBody(args, "op", "git "),
5149
5173
  verify: (args) => buildFieldSublineBody(args, "check", "verifying "),
5150
5174
  code_nav: (args) => {
@@ -5194,19 +5218,24 @@ function isNonExecutedOutcome(outcome) {
5194
5218
  return outcome === "blocked";
5195
5219
  }
5196
5220
  function buildSublineBody(toolName2, args, status, result, outcome) {
5197
- if (isNonExecutedOutcome(outcome)) return buildUnknownToolBody(toolName2, args);
5198
- if (status === "forming" || status === "ready") return buildUnknownToolBody(toolName2, args);
5221
+ if (isNonExecutedOutcome(outcome)) {
5222
+ if (toolName2 === "bash") {
5223
+ return buildFieldSublineBody(args, "command", "command ", { wrapInBackticks: true }) ?? buildGenericToolBody(toolName2, args);
5224
+ }
5225
+ return SUBLINE_BODY_BUILDERS[toolName2]?.(args) ?? buildGenericToolBody(toolName2, args);
5226
+ }
5199
5227
  if (toolName2 === "web_fetch") {
5200
5228
  const meta = status === void 0 ? null : webFetchMeta(result);
5201
- return `${buildUnknownToolBody(toolName2, args)}${meta ? dim(` \xB7 ${meta}`) : ""}`;
5229
+ const body2 = SUBLINE_BODY_BUILDERS.web_fetch?.(args) ?? buildGenericToolBody(toolName2, args);
5230
+ return `${body2}${meta ? dim(` \xB7 ${meta}`) : ""}`;
5202
5231
  }
5203
5232
  if (toolName2 === "bash") {
5204
- const lead = status === "running" || status === void 0 ? "running " : "ran ";
5205
- return buildFieldSublineBody(args, "command", lead, { wrapInBackticks: true }) ?? buildUnknownToolBody(toolName2, args);
5233
+ const lead = status === "ok" || status === "error" ? "ran " : "running ";
5234
+ return buildFieldSublineBody(args, "command", lead, { wrapInBackticks: true }) ?? buildGenericToolBody(toolName2, args);
5206
5235
  }
5207
5236
  const body = SUBLINE_BODY_BUILDERS[toolName2]?.(args);
5208
5237
  if (body !== null && body !== void 0) return body;
5209
- return buildUnknownToolBody(toolName2, args);
5238
+ return buildGenericToolBody(toolName2, args);
5210
5239
  }
5211
5240
  function sublineParts(call, status, meta) {
5212
5241
  const finished = "result" in call ? call : null;
@@ -5479,7 +5508,19 @@ function sublineStatus(call) {
5479
5508
  if (!("result" in call)) return call.phase ?? "running";
5480
5509
  return call.isError ? "error" : "ok";
5481
5510
  }
5482
- function renderToolSubline(call, width, expandKey) {
5511
+ function foldedDiffRows(finished, width, opts) {
5512
+ if (opts.foldedExtras === "none") return [];
5513
+ if (finished.isError || isNonExecutedOutcome(finished.outcome)) return [];
5514
+ if (!toolPresentationPolicy(finished.toolName, finished.args).showDiffWhenFolded) return [];
5515
+ const diff = resultDiff(finished.result);
5516
+ if (diff === null) return [];
5517
+ return truncateRowsMiddle(
5518
+ renderMutationDiffBlock(diff, width, opts.diffStyle !== "plain"),
5519
+ FOLDED_DIFF_ROW_LIMIT,
5520
+ false
5521
+ );
5522
+ }
5523
+ function renderToolSubline(call, width, expandKey, opts = {}) {
5483
5524
  const status = sublineStatus(call);
5484
5525
  const meta = "result" in call ? {
5485
5526
  durationMs: call.durationMs,
@@ -5488,9 +5529,12 @@ function renderToolSubline(call, width, expandKey) {
5488
5529
  blockReason: call.blockReason
5489
5530
  } : { elapsedMs: call.elapsedMs };
5490
5531
  const parts = sublineParts(call, status, meta);
5532
+ const excerpt = "result" in call ? failureExcerpt(call, width) : "";
5491
5533
  const showHint = "result" in call && expandKey !== void 0 && expandKey.length > 0;
5492
5534
  const tail = showHint ? `${parts.tail}${dim(` (${expandKey})`)}` : parts.tail;
5493
- return wrapSublineWithTail(parts.lead, tail, width);
5535
+ const lines = wrapSublineWithTail(`${parts.lead}${excerpt}`, tail, width);
5536
+ if ("result" in call) lines.push(...foldedDiffRows(call, width, opts));
5537
+ return lines;
5494
5538
  }
5495
5539
  function renderToolExecution(finished, width, opts = {}) {
5496
5540
  const status = finished.isError ? "error" : "ok";
@@ -5585,7 +5629,7 @@ function renderToolStreamingExecution(call, width, partialResult) {
5585
5629
  }
5586
5630
  return out;
5587
5631
  }
5588
- function renderBashTranscriptExecution(execution, width) {
5632
+ function renderBashTranscriptExecution(execution, width, expandKey, bodyOptions = {}) {
5589
5633
  const shownBytes = Buffer.byteLength(execution.output, "utf8");
5590
5634
  const totalBytes = execution.totalBytes ?? shownBytes;
5591
5635
  const args = { command: execution.command };
@@ -5603,7 +5647,20 @@ function renderBashTranscriptExecution(execution, width) {
5603
5647
  content: [{ type: "text", text: execution.output }],
5604
5648
  details
5605
5649
  };
5650
+ const folded = execution.folded !== false;
5606
5651
  if (execution.running) {
5652
+ if (folded) {
5653
+ return renderToolSubline(
5654
+ {
5655
+ toolCallId: "local-bash",
5656
+ toolName: "bash",
5657
+ args,
5658
+ elapsedMs: execution.elapsedMs,
5659
+ phase: "running"
5660
+ },
5661
+ width
5662
+ );
5663
+ }
5607
5664
  return renderToolStreamingExecution(
5608
5665
  {
5609
5666
  toolCallId: "local-bash",
@@ -5618,24 +5675,22 @@ function renderBashTranscriptExecution(execution, width) {
5618
5675
  }
5619
5676
  const message = execution.error?.trim();
5620
5677
  const output = message ? `${execution.output}${execution.output.length > 0 ? "\n\n" : ""}${message}` : execution.output;
5621
- return renderToolExecution(
5622
- {
5623
- toolCallId: "local-bash",
5624
- toolName: "bash",
5625
- args,
5626
- result: { ...result, content: [{ type: "text", text: output }] },
5627
- isError: execution.cancelled === true || execution.error !== void 0 || (execution.exitCode ?? 0) !== 0,
5628
- exitCode: execution.exitCode,
5629
- ...execution.cancelled === true ? { outcome: "aborted" } : {},
5630
- excludeFromContext: execution.excludeFromContext,
5631
- resultSummary: {
5632
- bytes: shownBytes,
5633
- truncated: execution.truncated === true,
5634
- ...execution.fullOutputPath !== void 0 ? { offloadPath: execution.fullOutputPath } : {}
5635
- }
5636
- },
5637
- width
5638
- );
5678
+ const finished = {
5679
+ toolCallId: "local-bash",
5680
+ toolName: "bash",
5681
+ args,
5682
+ result: { ...result, content: [{ type: "text", text: output }] },
5683
+ isError: execution.cancelled === true || execution.error !== void 0 || (execution.exitCode ?? 0) !== 0,
5684
+ exitCode: execution.exitCode,
5685
+ ...execution.cancelled === true ? { outcome: "aborted" } : {},
5686
+ excludeFromContext: execution.excludeFromContext,
5687
+ resultSummary: {
5688
+ bytes: shownBytes,
5689
+ truncated: execution.truncated === true,
5690
+ ...execution.fullOutputPath !== void 0 ? { offloadPath: execution.fullOutputPath } : {}
5691
+ }
5692
+ };
5693
+ return folded ? renderToolSubline(finished, width, expandKey) : renderToolExecution(finished, width, bodyOptions);
5639
5694
  }
5640
5695
 
5641
5696
  // src/interactive/stream-pacer.ts
@@ -6001,6 +6056,63 @@ function createStreamPacer(options) {
6001
6056
  return api;
6002
6057
  }
6003
6058
 
6059
+ // src/interactive/transcript-detail.ts
6060
+ init_esm_shims();
6061
+ var MINIMAL = {
6062
+ toolBody: "folded",
6063
+ runningTool: "row",
6064
+ thinking: "marker",
6065
+ worker: "folded",
6066
+ receipt: "none",
6067
+ errors: "excerpt"
6068
+ };
6069
+ var DEFAULT = {
6070
+ toolBody: "per-tool",
6071
+ runningTool: "row",
6072
+ thinking: "folded",
6073
+ worker: "origin",
6074
+ receipt: "compact",
6075
+ errors: "excerpt"
6076
+ };
6077
+ var VERBOSE = {
6078
+ toolBody: "expanded",
6079
+ runningTool: "body",
6080
+ thinking: "rail",
6081
+ worker: "expanded",
6082
+ receipt: "full",
6083
+ errors: "body"
6084
+ };
6085
+ function transcriptDetail(verbosity) {
6086
+ switch (verbosity) {
6087
+ case "minimal":
6088
+ return MINIMAL;
6089
+ case "verbose":
6090
+ return VERBOSE;
6091
+ default:
6092
+ return DEFAULT;
6093
+ }
6094
+ }
6095
+ function resolveFold(override, policyFold) {
6096
+ return override ?? policyFold;
6097
+ }
6098
+ function policyToolFold(detail, presentation) {
6099
+ if (detail.toolBody === "per-tool") return presentation.foldDefault;
6100
+ return detail.toolBody;
6101
+ }
6102
+ function policyRunningToolFold(detail) {
6103
+ return detail.runningTool === "body" ? "expanded" : "folded";
6104
+ }
6105
+ function policyThinkingFold(detail) {
6106
+ return detail.thinking === "rail" ? "expanded" : "folded";
6107
+ }
6108
+ function policyWorkerFold(detail, askedByModel) {
6109
+ if (detail.worker === "origin") return askedByModel ? "folded" : "expanded";
6110
+ return detail.worker;
6111
+ }
6112
+ function toggledFold(effective) {
6113
+ return effective === "expanded" ? "folded" : "expanded";
6114
+ }
6115
+
6004
6116
  // src/interactive/worker-receipts.ts
6005
6117
  init_esm_shims();
6006
6118
  import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
@@ -6209,6 +6321,50 @@ import { performance as performance3 } from "node:perf_hooks";
6209
6321
 
6210
6322
  // src/interactive/footer-panel.ts
6211
6323
  init_esm_shims();
6324
+
6325
+ // src/interactive/status/reasoning.ts
6326
+ init_esm_shims();
6327
+ var UNMEASURED_REASONING = { tokens: 0, provenance: "unmeasured" };
6328
+ function provenanceOf(hadProvider, hadEstimated) {
6329
+ if (hadProvider && hadEstimated) return "mixed";
6330
+ if (hadProvider) return "provider";
6331
+ if (hadEstimated) return "estimated";
6332
+ return "unmeasured";
6333
+ }
6334
+ function finiteTokens(value) {
6335
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
6336
+ }
6337
+ function reasoningFromTally(tally) {
6338
+ if (!tally) return UNMEASURED_REASONING;
6339
+ const provenance = provenanceOf(tally.hadProviderReasoning === true, tally.hadEstimatedReasoning === true);
6340
+ if (provenance === "unmeasured") return UNMEASURED_REASONING;
6341
+ return { tokens: finiteTokens(tally.reasoningTokens), provenance };
6342
+ }
6343
+ function reasoningFromSummary(summary) {
6344
+ if (!summary || typeof summary.reasoningTokens !== "number") return UNMEASURED_REASONING;
6345
+ return { tokens: finiteTokens(summary.reasoningTokens), provenance: summary.reasoningTokenProvenance ?? "provider" };
6346
+ }
6347
+ function compactReasoningTokens(value) {
6348
+ if (value < 1e3) return String(Math.round(value));
6349
+ if (value < 1e6) {
6350
+ const scaled2 = (value / 1e3).toFixed(1);
6351
+ return `${scaled2.endsWith(".0") ? scaled2.slice(0, -2) : scaled2}k`;
6352
+ }
6353
+ const scaled = (value / 1e6).toFixed(1);
6354
+ return `${scaled.endsWith(".0") ? scaled.slice(0, -2) : scaled}M`;
6355
+ }
6356
+ function formatReasoningChip(view, format = compactReasoningTokens) {
6357
+ if (view.provenance === "unmeasured" || view.tokens <= 0) return null;
6358
+ return `r${view.provenance === "provider" ? "" : "\u2248"}${format(view.tokens)}`;
6359
+ }
6360
+ function formatReasoningLabel(view) {
6361
+ if (view.provenance === "provider") return "provider-reported";
6362
+ if (view.provenance === "estimated") return "estimated";
6363
+ if (view.provenance === "mixed") return "mixed";
6364
+ return "unmeasured";
6365
+ }
6366
+
6367
+ // src/interactive/footer-panel.ts
6212
6368
  function formatFooterTokens(n) {
6213
6369
  if (!Number.isFinite(n) || n <= 0) return "0";
6214
6370
  const value = Math.round(n);
@@ -6229,7 +6385,8 @@ function tokensSegment(usage) {
6229
6385
  const reasoning = Math.max(0, usage.reasoningTokens ?? 0);
6230
6386
  const total = Math.max(0, usage.totalTokens ?? input + output);
6231
6387
  if (input + output + reasoning + total === 0) return null;
6232
- const reasoningPart = reasoning > 0 ? ` r${formatFooterTokens(reasoning)}` : "";
6388
+ const reasoningChip2 = formatReasoningChip({ tokens: reasoning, provenance: "provider" }, formatFooterTokens);
6389
+ const reasoningPart = reasoningChip2 === null ? "" : ` ${reasoningChip2}`;
6233
6390
  const totalPart = total > 0 ? ` \u03A3${formatFooterTokens(total)}` : "";
6234
6391
  return `${GLYPH.up}${formatFooterTokens(input)} ${GLYPH.down}${formatFooterTokens(output)}${reasoningPart}${totalPart}`;
6235
6392
  }
@@ -7214,7 +7371,7 @@ function foldMessageIntoRunTally(tally, message) {
7214
7371
  cacheReadTokens: tally.cacheReadTokens + (usage ? finite2(usage.cacheRead) : 0),
7215
7372
  cacheWriteTokens: tally.cacheWriteTokens + (usage ? finite2(usage.cacheWrite) : 0)
7216
7373
  };
7217
- const reasoning = usage ? extractReasoningTokens(usage) : null;
7374
+ const reasoning = usage && usage.estimated !== true ? extractReasoningTokens(usage) : null;
7218
7375
  if (reasoning !== null) {
7219
7376
  next.reasoningTokens += reasoning;
7220
7377
  next.hadProviderReasoning = true;
@@ -7222,7 +7379,8 @@ function foldMessageIntoRunTally(tally, message) {
7222
7379
  }
7223
7380
  const estimated = estimateReasoningTextTokens(assistantThinkingText2(message));
7224
7381
  if (estimated !== null) {
7225
- next.reasoningTokens += estimated;
7382
+ const reportedOutput = usage && typeof usage.output === "number" && Number.isFinite(usage.output) ? Math.max(0, usage.output) : null;
7383
+ next.reasoningTokens += reportedOutput === null ? estimated : Math.min(estimated, reportedOutput);
7226
7384
  next.hadEstimatedReasoning = true;
7227
7385
  }
7228
7386
  return next;
@@ -8436,6 +8594,10 @@ function liveTokenValue(theme6, status, throughput, lastTurn, sessionTokens, con
8436
8594
  const joined = joinChips(theme6, parts);
8437
8595
  return joined.length > 0 ? joined : null;
8438
8596
  }
8597
+ function reasoningChip(theme6, lastTurn) {
8598
+ const chip = formatReasoningChip(reasoningFromSummary(lastTurn), formatFooterTokens);
8599
+ return chip === null ? null : theme6.fg("reason", chip);
8600
+ }
8439
8601
  function lastTurnOutcome(theme6, lastTurn) {
8440
8602
  const stop = stopReasonStyle(lastTurn.stopReason);
8441
8603
  return theme6.fg(stop.token, `${stop.glyph} ${formatCompactMs(lastTurn.elapsedMs)}`);
@@ -8446,10 +8608,7 @@ function lastTurnDetails(theme6, lastTurn) {
8446
8608
  "muted",
8447
8609
  `${GLYPH.up}${formatFooterTokens(lastTurn.inputTokens)} ${GLYPH.down}${formatFooterTokens(lastTurn.outputTokens)}`
8448
8610
  ),
8449
- lastTurn.reasoningTokens !== void 0 && lastTurn.reasoningTokens > 0 ? theme6.fg(
8450
- "reason",
8451
- `r${lastTurn.reasoningTokenProvenance === "estimated" || lastTurn.reasoningTokenProvenance === "mixed" ? "\u2248" : ""}${formatFooterTokens(lastTurn.reasoningTokens)}`
8452
- ) : null,
8611
+ reasoningChip(theme6, lastTurn),
8453
8612
  lastTurn.cacheReadTokens > 0 || lastTurn.cacheWriteTokens > 0 ? theme6.fg(
8454
8613
  "dim",
8455
8614
  `cache ${formatFooterTokens(lastTurn.cacheReadTokens)}/${formatFooterTokens(lastTurn.cacheWriteTokens)}`
@@ -8689,12 +8848,7 @@ function buildMetricStrip(theme6, status, throughput, lastTurn, sessionTokens, s
8689
8848
  `${GLYPH.up}${formatFooterTokens(lastTurn.inputTokens)} ${GLYPH.down}${formatFooterTokens(lastTurn.outputTokens)}`
8690
8849
  )
8691
8850
  );
8692
- candidates.push(
8693
- lastTurn.reasoningTokens !== void 0 && lastTurn.reasoningTokens > 0 ? theme6.fg(
8694
- "reason",
8695
- `r${lastTurn.reasoningTokenProvenance === "estimated" || lastTurn.reasoningTokenProvenance === "mixed" ? "\u2248" : ""}${formatFooterTokens(lastTurn.reasoningTokens)}`
8696
- ) : null
8697
- );
8851
+ candidates.push(reasoningChip(theme6, lastTurn));
8698
8852
  deferred.push(
8699
8853
  lastTurn.cacheReadTokens > 0 || lastTurn.cacheWriteTokens > 0 ? theme6.fg(
8700
8854
  "dim",
@@ -10695,17 +10849,17 @@ function toolCallMessageFromEntry(entry) {
10695
10849
  timestamp: timestampMillis(entry.timestamp)
10696
10850
  };
10697
10851
  }
10698
- function toolResultContent(result) {
10852
+ function toolResultContent(result, unbounded = false) {
10699
10853
  const obj = payloadObject3(result);
10700
10854
  if (Array.isArray(obj?.content)) {
10701
- return cloneContentBlocks(obj.content, MAX_REPLAY_TEXT_CHARS) ?? [];
10855
+ return cloneContentBlocks(obj.content, unbounded ? void 0 : MAX_REPLAY_TEXT_CHARS) ?? [];
10702
10856
  }
10703
- if (isTextResult(result)) return [{ type: "text", text: truncateReplayText(result.text) }];
10704
- if (typeof result === "string") return [{ type: "text", text: truncateReplayText(result) }];
10705
- return [{ type: "text", text: stringifyPreview3(result, 1e4) }];
10857
+ if (isTextResult(result)) return [{ type: "text", text: unbounded ? result.text : truncateReplayText(result.text) }];
10858
+ if (typeof result === "string") return [{ type: "text", text: unbounded ? result : truncateReplayText(result) }];
10859
+ return [{ type: "text", text: stringifyPreview3(result, unbounded ? Number.POSITIVE_INFINITY : 1e4) }];
10706
10860
  }
10707
- function displayReplayToolResult(result) {
10708
- const content = toolResultContent(result);
10861
+ function displayReplayToolResult(result, unbounded = false) {
10862
+ const content = toolResultContent(result, unbounded);
10709
10863
  const details = payloadObject3(result)?.details;
10710
10864
  return details !== null && details !== void 0 && typeof details === "object" ? { content, details } : content;
10711
10865
  }
@@ -10846,19 +11000,23 @@ function renderReplayLine(text, width) {
10846
11000
  function appendReplayLine(chatPanel, text) {
10847
11001
  chatPanel.appendReplayBlock((width) => renderReplayLine(truncateReplayText(text), width));
10848
11002
  }
10849
- function renderBashExecutionEntry(entry, width) {
11003
+ function renderBashExecutionEntry(entry, width, folded, unbounded) {
11004
+ const normalizedOutput = entry.output.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\s+$/g, "");
10850
11005
  return renderBashTranscriptExecution(
10851
11006
  {
10852
11007
  command: entry.command,
10853
- output: truncateReplayText(entry.output.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\s+$/g, "")),
11008
+ output: unbounded ? normalizedOutput : truncateReplayText(normalizedOutput),
10854
11009
  running: false,
10855
11010
  exitCode: entry.exitCode,
10856
11011
  cancelled: entry.cancelled,
10857
11012
  truncated: entry.truncated,
10858
11013
  fullOutputPath: entry.fullOutputPath,
10859
- excludeFromContext: entry.excludeFromContext
11014
+ excludeFromContext: entry.excludeFromContext,
11015
+ folded
10860
11016
  },
10861
- width
11017
+ width,
11018
+ void 0,
11019
+ { unbounded, diffStyle: "plain" }
10862
11020
  );
10863
11021
  }
10864
11022
  function renderRetryStatusEntry(entry, width) {
@@ -11102,6 +11260,7 @@ function buildReplayAgentMessagesFromTurns(turns, options = {}) {
11102
11260
  }
11103
11261
  function rehydrateChatPanelFromTurns(chatPanel, turns, options = {}) {
11104
11262
  const pendingToolIds = [];
11263
+ let runAssistantMessages = [];
11105
11264
  const selected = selectReplayEntries(turns, options);
11106
11265
  const workerStates = workerEntriesFromRunEntries(
11107
11266
  selected.filter((entry) => entry.kind === "workerRun"),
@@ -11112,6 +11271,7 @@ function rehydrateChatPanelFromTurns(chatPanel, turns, options = {}) {
11112
11271
  switch (entry.kind) {
11113
11272
  case "message": {
11114
11273
  if (entry.role === "user") {
11274
+ runAssistantMessages = [];
11115
11275
  const text = truncateReplayText(replayedUserText(entry));
11116
11276
  if (text.length > 0) chatPanel.appendUser(text);
11117
11277
  break;
@@ -11127,7 +11287,8 @@ function rehydrateChatPanelFromTurns(chatPanel, turns, options = {}) {
11127
11287
  message.errorMessage = failure.errorMessage;
11128
11288
  }
11129
11289
  chatPanel.applyEvent({ type: "message_end", message });
11130
- chatPanel.applyEvent({ type: "agent_end", messages: [message] });
11290
+ runAssistantMessages.push(message);
11291
+ chatPanel.applyEvent({ type: "agent_end", messages: runAssistantMessages });
11131
11292
  }
11132
11293
  break;
11133
11294
  }
@@ -11153,7 +11314,7 @@ function rehydrateChatPanelFromTurns(chatPanel, turns, options = {}) {
11153
11314
  type: "tool_execution_end",
11154
11315
  toolCallId: fallbackId,
11155
11316
  toolName: result.name,
11156
- result: displayReplayToolResult(result.result),
11317
+ result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
11157
11318
  isError: result.isError,
11158
11319
  ...result.durationMs !== void 0 ? { durationMs: result.durationMs } : {},
11159
11320
  ...result.resultSummary !== void 0 ? { resultSummary: result.resultSummary } : {},
@@ -11166,7 +11327,7 @@ function rehydrateChatPanelFromTurns(chatPanel, turns, options = {}) {
11166
11327
  {
11167
11328
  toolCallId: result.id ?? "",
11168
11329
  toolName: result.name,
11169
- result: displayReplayToolResult(result.result),
11330
+ result: displayReplayToolResult(result.result, options.unboundedToolBodies === true),
11170
11331
  isError: result.isError,
11171
11332
  ...result.durationMs !== void 0 ? { durationMs: result.durationMs } : {},
11172
11333
  ...result.resultSummary !== void 0 ? { resultSummary: result.resultSummary } : {},
@@ -11192,9 +11353,24 @@ function rehydrateChatPanelFromTurns(chatPanel, turns, options = {}) {
11192
11353
  }
11193
11354
  break;
11194
11355
  }
11195
- case "bashExecution":
11196
- chatPanel.appendReplayBlock((width) => renderBashExecutionEntry(entry, width));
11356
+ case "bashExecution": {
11357
+ let fold;
11358
+ const presentation = toolPresentationPolicy(ToolNames.Bash, void 0);
11359
+ const policyFold = (detail) => policyToolFold(detail, presentation);
11360
+ const unbounded = options.unboundedToolBodies === true;
11361
+ chatPanel.appendReplayBlock(
11362
+ (width, detail) => renderBashExecutionEntry(entry, width, resolveFold(fold, policyFold(detail)) === "folded", unbounded),
11363
+ void 0,
11364
+ {
11365
+ policyFold,
11366
+ fold: () => fold,
11367
+ setFold: (next) => {
11368
+ fold = next;
11369
+ }
11370
+ }
11371
+ );
11197
11372
  break;
11373
+ }
11198
11374
  case "custom":
11199
11375
  if (rendersCustomEntry(entry)) chatPanel.appendReplayBlock((width) => renderCustomEntry(entry, width));
11200
11376
  break;
@@ -11253,7 +11429,7 @@ function rehydrateChatPanelFromTurns(chatPanel, turns, options = {}) {
11253
11429
  isError: true
11254
11430
  });
11255
11431
  }
11256
- chatPanel.collapseAllTools();
11432
+ chatPanel.clearFoldOverrides();
11257
11433
  }
11258
11434
 
11259
11435
  // src/interactive/turn-context.ts
@@ -14113,175 +14289,771 @@ function resolveRowSize(value, termHeight) {
14113
14289
  return Math.max(1, Math.floor(termHeight * Number.parseFloat(percent[1]) / 100));
14114
14290
  }
14115
14291
 
14116
- // src/interactive/overlays/ask-user.ts
14117
- var ASK_USER_DECISION_TONE = "action";
14118
- var ASK_USER_DECISION_TITLE = "Decision required";
14119
- var ASK_USER_WAITING_TITLE = "Ask User";
14120
- var ASK_USER_COMPACT_MAX_OPTIONS = 3;
14121
- var ASK_USER_DEFAULT_SURFACE = "panel";
14122
- var ASK_USER_SURFACE_GEOMETRY = {
14123
- compact: { width: 68, maxInnerRows: 10, anchor: "bottom-center", margin: { top: 1, right: 2, bottom: 1, left: 2 } },
14124
- panel: { width: 88, maxInnerRows: 18, anchor: "center", margin: 1 },
14125
- interview: { width: 0, maxInnerRows: 42, anchor: "center", margin: 1 }
14126
- };
14127
- var ASK_USER_FRAME_ROWS = 2;
14128
- var ASK_USER_FRAME_AND_MARGIN_ROWS = 4;
14129
- var MIN_VISIBLE_OPTIONS = 3;
14130
- var MAX_VISIBLE_OPTIONS = 12;
14292
+ // src/interactive/view/view-overlay.ts
14293
+ init_esm_shims();
14294
+ var VIEW_OVERLAY_WIDTH = "100%";
14295
+ var VIEW_OVERLAY_MAX_HEIGHT = "100%";
14296
+ var VIEW_OVERLAY_MARGIN = { top: 0, right: 0, bottom: 0, left: 0 };
14297
+ var LEFT_PANE_TARGET_WIDTH = 38;
14298
+ var LEFT_PANE_MIN_WIDTH = 30;
14299
+ var LEFT_PANE_MAX_WIDTH = 44;
14300
+ var SEPARATOR = " \u2502 ";
14131
14301
  var ELLIPSIS = "\u2026";
14132
- function askUserSurface(questions, priorRounds = 0) {
14133
- if (priorRounds > 0 || questions.length !== 1) return "interview";
14134
- const question = questions[0];
14135
- if (!question) return "interview";
14136
- return (question.options?.length ?? 0) <= ASK_USER_COMPACT_MAX_OPTIONS ? "compact" : "panel";
14302
+ var TWO_PANE_MIN_WIDTH = LEFT_PANE_MIN_WIDTH + 3 + 24;
14303
+ function viewUsesSinglePane(bodyWidth) {
14304
+ return bodyWidth < TWO_PANE_MIN_WIDTH;
14137
14305
  }
14138
- function questionHasOptions(question) {
14139
- return (question.options?.length ?? 0) > 0;
14306
+ function artifactKey(artifact) {
14307
+ return `${artifact.category}:${artifact.id}:${artifact.path ?? ""}`;
14140
14308
  }
14141
- function initialMode(question) {
14142
- return questionHasOptions(question) ? "select" : "text";
14309
+ function categoryLabel(category) {
14310
+ switch (category) {
14311
+ case "accountability":
14312
+ return "Accountability";
14313
+ case "evidence":
14314
+ return "Evidence bundles";
14315
+ case "receipt":
14316
+ return "Receipts";
14317
+ case "dispatch":
14318
+ return "Dispatch outputs";
14319
+ case "task-ledger":
14320
+ return "Task ledgers";
14321
+ case "workspace":
14322
+ return "Workspace outputs";
14323
+ case "tool-output":
14324
+ return "Tool outputs";
14325
+ case "protected-artifact":
14326
+ return "Protected artifacts";
14327
+ case "compaction":
14328
+ return "Compaction summaries";
14329
+ case "prompt-manifest":
14330
+ return "Prompt manifests";
14331
+ case "audit":
14332
+ return "Safety audit rows";
14333
+ }
14143
14334
  }
14144
- function createQuestionState(question) {
14145
- return {
14146
- mode: initialMode(question),
14147
- selected: /* @__PURE__ */ new Set(),
14148
- customAnswer: "",
14149
- inputValue: "",
14150
- answer: ""
14151
- };
14335
+ var VIEW_ARTIFACT_CATEGORY_SET = new Set(VIEW_ARTIFACT_CATEGORIES);
14336
+ var BARE_CATEGORY_FILTERS = /* @__PURE__ */ new Set([
14337
+ "task-ledger",
14338
+ "workspace",
14339
+ "protected-artifact",
14340
+ "compaction",
14341
+ "prompt-manifest",
14342
+ "audit"
14343
+ ]);
14344
+ function isViewArtifactCategory(value) {
14345
+ return VIEW_ARTIFACT_CATEGORY_SET.has(value);
14152
14346
  }
14153
- function isOtherOption(label) {
14154
- const normalized = label.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
14155
- return normalized === "other" || normalized === "custom" || normalized === "something else" || normalized.startsWith("other ");
14347
+ function parseViewFilterQuery(query) {
14348
+ const text = query.trim();
14349
+ if (text.length === 0) return { kind: "text", text };
14350
+ const colon = text.indexOf(":");
14351
+ if (colon >= 0) {
14352
+ const prefix = text.slice(0, colon).trim().toLowerCase();
14353
+ if (isViewArtifactCategory(prefix)) {
14354
+ return { kind: "category", category: prefix, value: text.slice(colon + 1).trim() };
14355
+ }
14356
+ return { kind: "text", text };
14357
+ }
14358
+ const bare = text.toLowerCase();
14359
+ if (isViewArtifactCategory(bare) && BARE_CATEGORY_FILTERS.has(bare)) {
14360
+ return { kind: "category", category: bare, value: "" };
14361
+ }
14362
+ return { kind: "text", text };
14156
14363
  }
14157
- function optionItems(question, selected) {
14158
- const options = question.options ?? [];
14159
- const explicitOtherIndex = options.findIndex((option) => isOtherOption(option.label));
14160
- const items = [];
14161
- for (let index = 0; index < options.length; index += 1) {
14162
- const option = options[index];
14163
- if (!option) continue;
14164
- const isExplicitOther = index === explicitOtherIndex;
14165
- const label = question.multi_select === true && !isExplicitOther ? `${selected.has(index) ? "[x]" : "[ ]"} ${option.label}` : option.label;
14166
- const item = {
14167
- value: isExplicitOther ? "other" : `option:${index}`,
14168
- label
14169
- };
14170
- if (option.description) item.description = option.description;
14171
- items.push(item);
14364
+ function isNonEmptySearchValue(value) {
14365
+ return typeof value === "string" && value.trim().length > 0;
14366
+ }
14367
+ function broadFilterText(artifact) {
14368
+ return `${artifact.id} ${artifact.category} ${artifact.title}`;
14369
+ }
14370
+ function artifactSearchValues(artifact) {
14371
+ return [
14372
+ artifact.id,
14373
+ `${artifact.category}:${artifact.id}`,
14374
+ artifact.category,
14375
+ artifact.title,
14376
+ artifact.description,
14377
+ artifact.runId,
14378
+ artifact.sessionId,
14379
+ artifact.correlationId,
14380
+ artifact.toolName,
14381
+ artifact.path,
14382
+ ...artifact.searchText ?? []
14383
+ ].filter(isNonEmptySearchValue);
14384
+ }
14385
+ function categoryFilterText(artifact) {
14386
+ return artifactSearchValues(artifact).join(" ");
14387
+ }
14388
+ function matchesExactArtifactValue(artifact, value) {
14389
+ const exact = value.trim();
14390
+ return exact.length > 0 && artifactSearchValues(artifact).some((candidate) => candidate === exact);
14391
+ }
14392
+ function formatArtifactSize(sizeBytes) {
14393
+ if (sizeBytes === void 0) return "";
14394
+ if (sizeBytes < 1024) return `${sizeBytes} B`;
14395
+ if (sizeBytes < 1024 * 1024) return `${(sizeBytes / 1024).toFixed(1)} KB`;
14396
+ return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
14397
+ }
14398
+ function formatRelativeTime(timestamp, now = Date.now()) {
14399
+ if (!Number.isFinite(timestamp) || timestamp <= 0) return "";
14400
+ const seconds = Math.max(0, Math.floor((now - timestamp) / 1e3));
14401
+ if (seconds < 60) return `${seconds}s`;
14402
+ const minutes = Math.floor(seconds / 60);
14403
+ if (minutes < 60) return `${minutes}m`;
14404
+ const hours = Math.floor(minutes / 60);
14405
+ if (hours < 24) return `${hours}h`;
14406
+ const days = Math.floor(hours / 24);
14407
+ return `${days}d`;
14408
+ }
14409
+ function formatLocalTime2(timestamp) {
14410
+ return !Number.isFinite(timestamp) || timestamp <= 0 ? "unknown time" : clockLocal(timestamp);
14411
+ }
14412
+ function filterViewArtifacts(artifacts, query) {
14413
+ const parsed = parseViewFilterQuery(query);
14414
+ if (parsed.kind === "category") {
14415
+ const categoryArtifacts = artifacts.filter((artifact) => artifact.category === parsed.category);
14416
+ if (parsed.value.length === 0) return categoryArtifacts;
14417
+ return fuzzyFilter(categoryArtifacts, parsed.value, categoryFilterText);
14172
14418
  }
14173
- if (explicitOtherIndex === -1) {
14174
- items.push({
14175
- value: "other",
14176
- label: question.multi_select === true ? `[ ] ${ASK_USER_OTHER_LABEL}` : ASK_USER_OTHER_LABEL,
14177
- description: "type your answer"
14178
- });
14419
+ if (parsed.text.length === 0) return [...artifacts];
14420
+ return fuzzyFilter([...artifacts], parsed.text, broadFilterText);
14421
+ }
14422
+ function artifactsInCategoryOrder(artifacts) {
14423
+ return VIEW_ARTIFACT_CATEGORIES.flatMap((category) => artifacts.filter((artifact) => artifact.category === category));
14424
+ }
14425
+ function initialViewSelection(artifacts, initialFilter = "") {
14426
+ const filtered = filterViewArtifacts(artifacts, initialFilter);
14427
+ const first = filtered[0];
14428
+ if (!first) return 0;
14429
+ let selected = first;
14430
+ const parsed = parseViewFilterQuery(initialFilter);
14431
+ if (parsed.kind === "category" && parsed.value.length > 0) {
14432
+ const exactIndexes = filtered.flatMap(
14433
+ (artifact, index) => matchesExactArtifactValue(artifact, parsed.value) ? [index] : []
14434
+ );
14435
+ if (exactIndexes.length === 1) selected = filtered[exactIndexes[0] ?? 0] ?? selected;
14179
14436
  }
14180
- return items;
14437
+ const exact = initialFilter.trim();
14438
+ if (exact.length > 0) {
14439
+ const exactArtifact = filtered.find(
14440
+ (artifact) => artifact.id === exact || `${artifact.category}:${artifact.id}` === exact
14441
+ );
14442
+ if (exactArtifact) selected = exactArtifact;
14443
+ }
14444
+ return Math.max(0, artifactsInCategoryOrder(filtered).indexOf(selected));
14181
14445
  }
14182
- function optionIndexFromValue(value) {
14183
- if (!value.startsWith("option:")) return null;
14184
- const index = Number(value.slice("option:".length));
14185
- return Number.isInteger(index) && index >= 0 ? index : null;
14446
+ function nextCategorySelection(artifacts, currentIndex, direction) {
14447
+ if (artifacts.length === 0) return 0;
14448
+ const safeCurrent = Math.max(0, Math.min(currentIndex, artifacts.length - 1));
14449
+ const currentCategory = artifacts[safeCurrent]?.category;
14450
+ const categoryIndex = currentCategory === void 0 ? -1 : VIEW_ARTIFACT_CATEGORIES.indexOf(currentCategory);
14451
+ for (let offset = 1; offset < VIEW_ARTIFACT_CATEGORIES.length; offset += 1) {
14452
+ const nextCategoryIndex = (categoryIndex + direction * offset + VIEW_ARTIFACT_CATEGORIES.length) % VIEW_ARTIFACT_CATEGORIES.length;
14453
+ const nextCategory = VIEW_ARTIFACT_CATEGORIES[nextCategoryIndex];
14454
+ const artifactIndex = artifacts.findIndex((artifact) => artifact.category === nextCategory);
14455
+ if (artifactIndex >= 0) return artifactIndex;
14456
+ }
14457
+ return safeCurrent;
14186
14458
  }
14187
- function answerText(question, selected, customAnswer) {
14188
- const parts = [];
14189
- for (const index of [...selected].sort((a, b) => a - b)) {
14190
- const label = question.options?.[index]?.label;
14191
- if (label) parts.push(label);
14459
+ function groupedViewRows(artifacts) {
14460
+ const rows = [];
14461
+ for (const category of VIEW_ARTIFACT_CATEGORIES) {
14462
+ const items = artifacts.filter((artifact) => artifact.category === category);
14463
+ rows.push({ type: "group", category });
14464
+ if (items.length === 0) {
14465
+ rows.push({ type: "empty", category });
14466
+ continue;
14467
+ }
14468
+ for (const item of items) {
14469
+ rows.push({ type: "item", category, item, itemIndex: artifacts.indexOf(item) });
14470
+ }
14192
14471
  }
14193
- const custom = customAnswer.trim();
14194
- if (custom.length > 0) parts.push(custom);
14195
- return parts.join("; ");
14472
+ return rows;
14196
14473
  }
14197
- function fitLine(text, width) {
14198
- const safeWidth = Math.max(1, width);
14199
- if (visibleWidth(text) <= safeWidth) return text;
14200
- return truncateToWidth(text, safeWidth, ELLIPSIS, true);
14474
+ function nextContentScrollOffset(current, totalLines, height, action) {
14475
+ const maxOffset = Math.max(0, totalLines - Math.max(1, height));
14476
+ const page = Math.max(1, height - 1);
14477
+ const half = Math.max(1, Math.floor(height / 2));
14478
+ let next = current;
14479
+ switch (action) {
14480
+ case "line-up":
14481
+ next -= 1;
14482
+ break;
14483
+ case "line-down":
14484
+ next += 1;
14485
+ break;
14486
+ case "page-up":
14487
+ next -= page;
14488
+ break;
14489
+ case "page-down":
14490
+ next += page;
14491
+ break;
14492
+ case "half-up":
14493
+ next -= half;
14494
+ break;
14495
+ case "half-down":
14496
+ next += half;
14497
+ break;
14498
+ case "top":
14499
+ next = 0;
14500
+ break;
14501
+ case "bottom":
14502
+ next = maxOffset;
14503
+ break;
14504
+ }
14505
+ return Math.max(0, Math.min(next, maxOffset));
14201
14506
  }
14202
- function compactTitle(question) {
14203
- return (question.header ?? question.question).replace(/\s+/g, " ").trim();
14507
+ function verificationText(state) {
14508
+ if (!state || state.status === "idle") return "";
14509
+ if (state.status === "running") return `${GLYPH.running} verify running`;
14510
+ if (state.status === "ok") return `${GLYPH.ok} verify ok ${state.detail}`;
14511
+ return `${GLYPH.error} verify fail ${state.detail}`;
14204
14512
  }
14205
- var AskUserOverlayView = class {
14206
- constructor(deps) {
14207
- this.deps = deps;
14513
+ function buildArtifactHeader(artifact, verification, width) {
14514
+ if (!artifact) return padAnsi(clioTheme().fg("muted", "No artifact selected"), width, ELLIPSIS);
14515
+ const theme6 = clioTheme();
14516
+ const timestamp = formatLocalTime2(artifact.timestamp);
14517
+ const size = formatArtifactSize(artifact.sizeBytes);
14518
+ const parts = [theme6.fg("dim", artifact.category), theme6.fg("muted", artifact.id), theme6.fg("dim", timestamp)];
14519
+ if (size.length > 0) parts.push(theme6.fg("dim", size));
14520
+ const verify = verificationText(verification);
14521
+ if (verify.length > 0) {
14522
+ const token = verification?.status === "ok" ? "success" : verification?.status === "fail" ? "error" : "info";
14523
+ parts.push(theme6.fg(token, verify));
14208
14524
  }
14209
- deps;
14210
- phase = "waiting";
14211
- surface = ASK_USER_DEFAULT_SURFACE;
14212
- index = 0;
14213
- status = "";
14214
- questions = [];
14215
- states = [];
14216
- history = [];
14217
- list = null;
14218
- input = null;
14219
- resolveCurrent = null;
14220
- begin(questions) {
14221
- if (this.phase === "closed") return Promise.resolve(cancelledAskUserResult());
14222
- if (this.resolveCurrent) return Promise.resolve(cancelledAskUserResult());
14223
- this.phase = "asking";
14224
- this.index = 0;
14225
- this.status = "";
14226
- this.questions = [...questions];
14227
- this.states = this.questions.map((question) => createQuestionState(question));
14228
- this.list = null;
14229
- this.input = null;
14230
- this.setSurface(askUserSurface(this.questions, this.history.length));
14231
- this.rebuildControl();
14232
- this.deps.requestRender();
14233
- return new Promise((resolve6) => {
14234
- this.resolveCurrent = resolve6;
14235
- });
14525
+ return padAnsi(parts.join(" "), width, ELLIPSIS);
14526
+ }
14527
+ function viewFooterHint(focus, canVerify, innerWidth) {
14528
+ if (innerWidth !== void 0 && viewUsesSinglePane(innerWidth - 2)) {
14529
+ const budget = innerWidth - 3;
14530
+ const tiers = focus === "list" ? [
14531
+ "[\u2191\u2193] select \xB7 [type] filter \xB7 [Tab] detail \xB7 [Esc] close",
14532
+ "[\u2191\u2193] select \xB7 [Tab] detail \xB7 [Esc] close",
14533
+ "[\u2191\u2193] select \xB7 [Tab] detail"
14534
+ ] : ["[\u2191\u2193] scroll \xB7 [Tab] list \xB7 [Esc] close", "[\u2191\u2193] scroll \xB7 [Tab] list"];
14535
+ return tiers.find((tier) => visibleWidth(tier) <= budget) ?? tiers.at(-1);
14236
14536
  }
14237
- cancel() {
14238
- this.finish(cancelledAskUserResult());
14537
+ if (focus === "list") {
14538
+ return buildHint([
14539
+ { key: "\u2191\u2193", verb: "select" },
14540
+ { key: "\u2190\u2192", verb: "category" },
14541
+ { key: "type", verb: "filter" },
14542
+ { key: "Tab", verb: "content" },
14543
+ ...canVerify ? [{ key: "v", verb: "verify" }] : [],
14544
+ { key: "o", verb: "path" }
14545
+ ]);
14239
14546
  }
14240
- close() {
14241
- this.phase = "closed";
14242
- this.finish(cancelledAskUserResult());
14547
+ return buildHint([
14548
+ { key: "\u2191\u2193", verb: "scroll" },
14549
+ { key: "\u2190\u2192", verb: "category" },
14550
+ { key: "PgUp/PgDn", verb: "page" },
14551
+ { key: "g/G", verb: "top/bottom" },
14552
+ { key: "Tab", verb: "list" },
14553
+ ...canVerify ? [{ key: "v", verb: "verify" }] : [],
14554
+ { key: "o", verb: "path" }
14555
+ ]);
14556
+ }
14557
+ var ViewOverlayView = class {
14558
+ constructor(options) {
14559
+ this.options = options;
14560
+ this.filterText = options.initialFilter ?? "";
14243
14561
  }
14244
- isWaiting() {
14245
- return this.phase === "waiting" && this.resolveCurrent === null;
14562
+ options;
14563
+ artifacts = [];
14564
+ filterText = "";
14565
+ selectedIndex = 0;
14566
+ listScrollOffset = 0;
14567
+ contentScrollOffset = 0;
14568
+ focus = "list";
14569
+ loadingArtifacts = true;
14570
+ artifactError = null;
14571
+ content = null;
14572
+ loadToken = 0;
14573
+ lastContentWidth = 80;
14574
+ lastContentBodyHeight = 1;
14575
+ verifications = /* @__PURE__ */ new Map();
14576
+ refresh() {
14577
+ this.loadingArtifacts = true;
14578
+ this.artifactError = null;
14579
+ this.options.requestRender?.();
14580
+ void (async () => {
14581
+ try {
14582
+ this.artifacts = await listViewArtifacts(this.options.providers);
14583
+ this.selectedIndex = initialViewSelection(this.artifacts, this.filterText);
14584
+ this.contentScrollOffset = 0;
14585
+ this.content = null;
14586
+ } catch (err) {
14587
+ this.artifactError = err instanceof Error ? err.message : String(err);
14588
+ this.artifacts = [];
14589
+ } finally {
14590
+ this.loadingArtifacts = false;
14591
+ this.options.requestRender?.();
14592
+ }
14593
+ })();
14246
14594
  }
14247
- /** True while the overlay is holding a question the operator has to answer. */
14248
- isDecisionPending() {
14249
- return this.phase === "asking";
14595
+ getHint(innerWidth) {
14596
+ return viewFooterHint(this.focus, !!this.selectedArtifact()?.verify, innerWidth);
14250
14597
  }
14251
- currentSurface() {
14252
- return this.surface;
14598
+ filteredArtifacts() {
14599
+ return artifactsInCategoryOrder(filterViewArtifacts(this.artifacts, this.filterText));
14253
14600
  }
14254
- invalidate() {
14255
- this.list?.invalidate();
14256
- this.input?.invalidate();
14601
+ selectedArtifact() {
14602
+ const filtered = this.filteredArtifacts();
14603
+ if (this.selectedIndex >= filtered.length) this.selectedIndex = Math.max(0, filtered.length - 1);
14604
+ return filtered[this.selectedIndex];
14257
14605
  }
14258
- handleInput(data) {
14259
- if (this.phase !== "asking") return;
14260
- const question = this.currentQuestion();
14261
- const state = this.currentState();
14262
- if (!question || !state) return;
14263
- if (state.mode === "text") {
14264
- if (this.isTextModePreviousKey(data)) {
14265
- this.goToRelativeQuestion(-1);
14266
- return;
14267
- }
14268
- if (this.isTextModeNextKey(data)) {
14269
- this.goToRelativeQuestion(1);
14270
- return;
14271
- }
14272
- this.input?.handleInput(data);
14273
- return;
14274
- }
14275
- if (this.isPreviousQuestionKey(data)) {
14276
- this.goToRelativeQuestion(-1);
14277
- return;
14278
- }
14279
- if (this.isNextQuestionKey(data)) {
14280
- this.goToRelativeQuestion(1);
14606
+ selectIndex(next) {
14607
+ const filtered = this.filteredArtifacts();
14608
+ if (filtered.length === 0) {
14609
+ this.selectedIndex = 0;
14610
+ this.content = null;
14281
14611
  return;
14282
14612
  }
14283
- if (question.multi_select === true && data === " ") {
14284
- this.toggleCurrentSelection(question, state);
14613
+ this.selectedIndex = (next + filtered.length) % filtered.length;
14614
+ this.contentScrollOffset = 0;
14615
+ this.content = null;
14616
+ this.options.requestRender?.();
14617
+ }
14618
+ selectCategory(direction) {
14619
+ const filtered = this.filteredArtifacts();
14620
+ if (filtered.length === 0) return;
14621
+ this.selectIndex(nextCategorySelection(filtered, this.selectedIndex, direction));
14622
+ }
14623
+ selectInitialFilterMatch() {
14624
+ this.selectIndex(initialViewSelection(this.artifacts, this.filterText));
14625
+ }
14626
+ ensureContentLoaded(artifact) {
14627
+ if (!artifact) return;
14628
+ const key = artifactKey(artifact);
14629
+ if (this.content?.key === key) return;
14630
+ const token = ++this.loadToken;
14631
+ this.content = { key, status: "loading", lines: [clioTheme().fg("dim", "loading artifact\u2026")], format: "text" };
14632
+ void (async () => {
14633
+ try {
14634
+ const loaded = await artifact.load();
14635
+ if (token !== this.loadToken) return;
14636
+ this.content = { key, status: "loaded", lines: loaded.lines, format: loaded.format };
14637
+ } catch (err) {
14638
+ if (token !== this.loadToken) return;
14639
+ const message = err instanceof Error ? err.message : String(err);
14640
+ this.content = {
14641
+ key,
14642
+ status: "error",
14643
+ lines: [clioTheme().fg("error", `load failed: ${message}`)],
14644
+ format: "text",
14645
+ error: message
14646
+ };
14647
+ } finally {
14648
+ if (token === this.loadToken) this.options.requestRender?.();
14649
+ }
14650
+ })();
14651
+ }
14652
+ renderedContentLines(width) {
14653
+ const content = this.content;
14654
+ if (!content) return [];
14655
+ if (content.format !== "markdown") return content.lines;
14656
+ if (content.renderedLines && content.renderWidth === width) return content.renderedLines;
14657
+ const md = new Markdown(content.lines.join("\n"), 0, 0, markdownTheme(clioTheme()));
14658
+ const rendered = md.render(width);
14659
+ content.renderWidth = width;
14660
+ content.renderedLines = rendered;
14661
+ return rendered;
14662
+ }
14663
+ renderList(width, height) {
14664
+ const theme6 = clioTheme();
14665
+ const lines = [];
14666
+ const filterLabel = this.focus === "list" ? theme6.fg("accent", "filter") : theme6.fg("dim", "filter");
14667
+ const filterValue = this.filterText.length > 0 ? this.filterText : theme6.fg("dim", "(empty)");
14668
+ lines.push(padAnsi(`${filterLabel}: ${filterValue}`, width, ELLIPSIS));
14669
+ if (this.loadingArtifacts) {
14670
+ lines.push(padAnsi(theme6.fg("dim", "loading artifacts\u2026"), width, ELLIPSIS));
14671
+ return this.fixedLines(lines, width, height);
14672
+ }
14673
+ if (this.artifactError) {
14674
+ lines.push(padAnsi(theme6.fg("error", this.artifactError), width, ELLIPSIS));
14675
+ return this.fixedLines(lines, width, height);
14676
+ }
14677
+ const filtered = this.filteredArtifacts();
14678
+ const rows = groupedViewRows(filtered);
14679
+ const selectedRow = rows.findIndex((row2) => row2.type === "item" && row2.itemIndex === this.selectedIndex);
14680
+ const rowHeight = Math.max(1, height - 1);
14681
+ if (selectedRow >= 0) {
14682
+ if (selectedRow < this.listScrollOffset) this.listScrollOffset = selectedRow;
14683
+ if (selectedRow >= this.listScrollOffset + rowHeight) this.listScrollOffset = selectedRow - rowHeight + 1;
14684
+ }
14685
+ this.listScrollOffset = Math.max(0, Math.min(this.listScrollOffset, Math.max(0, rows.length - rowHeight)));
14686
+ for (const row2 of rows.slice(this.listScrollOffset, this.listScrollOffset + rowHeight)) {
14687
+ if (row2.type === "group") {
14688
+ const count2 = filtered.filter((artifact) => artifact.category === row2.category).length;
14689
+ lines.push(padAnsi(theme6.fg("dim", `\u2500\u2500 ${categoryLabel(row2.category)} (${count2})`), width, ELLIPSIS));
14690
+ continue;
14691
+ }
14692
+ if (row2.type === "empty") {
14693
+ lines.push(padAnsi(theme6.fg("dim", " (empty)"), width, ELLIPSIS));
14694
+ continue;
14695
+ }
14696
+ if (!row2.item) continue;
14697
+ const selected = row2.itemIndex === this.selectedIndex;
14698
+ const cursor = selected ? theme6.fg("accent", `${GLYPH.cursor} `) : " ";
14699
+ const title = selected ? theme6.style("accent", row2.item.title, { bold: true }) : row2.item.title;
14700
+ const metaParts = [formatRelativeTime(row2.item.timestamp), formatArtifactSize(row2.item.sizeBytes)].filter(Boolean);
14701
+ const meta = metaParts.length > 0 ? theme6.fg("dim", metaParts.join(" ")) : "";
14702
+ const available = Math.max(1, width - visibleWidth(cursor));
14703
+ const metaWidth = visibleWidth(meta);
14704
+ const titleWidth = Math.max(1, available - (metaWidth > 0 ? metaWidth + 1 : 0));
14705
+ const clippedTitle = truncateToWidth(title, titleWidth, ELLIPSIS, true);
14706
+ const gap = " ".repeat(Math.max(1, available - visibleWidth(clippedTitle) - metaWidth));
14707
+ lines.push(padAnsi(`${cursor}${clippedTitle}${metaWidth > 0 ? `${gap}${meta}` : ""}`, width, ELLIPSIS));
14708
+ }
14709
+ return this.fixedLines(lines, width, height);
14710
+ }
14711
+ renderContent(width, height) {
14712
+ const artifact = this.selectedArtifact();
14713
+ this.ensureContentLoaded(artifact);
14714
+ this.lastContentWidth = width;
14715
+ const verification = artifact ? this.verifications.get(artifactKey(artifact)) : void 0;
14716
+ const header = buildArtifactHeader(artifact, verification, width);
14717
+ const bodyHeight = Math.max(0, height - 1);
14718
+ this.lastContentBodyHeight = Math.max(1, bodyHeight);
14719
+ const body = this.renderedContentLines(width);
14720
+ const maxOffset = Math.max(0, body.length - Math.max(1, bodyHeight));
14721
+ if (this.contentScrollOffset > maxOffset) this.contentScrollOffset = maxOffset;
14722
+ const visible = body.slice(this.contentScrollOffset, this.contentScrollOffset + bodyHeight).map((line) => padAnsi(line, width, ELLIPSIS));
14723
+ const lines = [header, ...visible];
14724
+ return this.fixedLines(lines, width, height);
14725
+ }
14726
+ fixedLines(lines, width, height) {
14727
+ const out = lines.slice(0, height).map((line) => padAnsi(line, width, ELLIPSIS));
14728
+ while (out.length < height) out.push(" ".repeat(Math.max(0, width)));
14729
+ return out;
14730
+ }
14731
+ render(width) {
14732
+ const bodyHeight = Math.max(1, this.options.getBodyHeight());
14733
+ if (viewUsesSinglePane(width)) {
14734
+ return this.focus === "content" ? this.renderContent(width, bodyHeight) : this.renderList(width, bodyHeight);
14735
+ }
14736
+ const separatorWidth = visibleWidth(SEPARATOR);
14737
+ const leftWidth = Math.min(
14738
+ LEFT_PANE_MAX_WIDTH,
14739
+ Math.max(LEFT_PANE_MIN_WIDTH, Math.min(LEFT_PANE_TARGET_WIDTH, Math.floor(width * 0.38)))
14740
+ );
14741
+ const rightWidth = Math.max(1, width - leftWidth - separatorWidth);
14742
+ const list = this.renderList(leftWidth, bodyHeight);
14743
+ const content = this.renderContent(rightWidth, bodyHeight);
14744
+ const separator = clioTheme().fg("frame", SEPARATOR);
14745
+ return Array.from({ length: bodyHeight }, (_, index) => `${list[index] ?? ""}${separator}${content[index] ?? ""}`);
14746
+ }
14747
+ handleInput(data) {
14748
+ if (matchesKey(data, "tab") || matchesKey(data, "shift+tab")) {
14749
+ this.focus = this.focus === "list" ? "content" : "list";
14750
+ this.options.requestRender?.();
14751
+ return;
14752
+ }
14753
+ if (matchesKey(data, "left")) {
14754
+ this.selectCategory(-1);
14755
+ return;
14756
+ }
14757
+ if (matchesKey(data, "right")) {
14758
+ this.selectCategory(1);
14759
+ return;
14760
+ }
14761
+ if (data === "v") {
14762
+ this.verifySelected();
14763
+ return;
14764
+ }
14765
+ if (data === "o") {
14766
+ this.openPathNotice();
14767
+ return;
14768
+ }
14769
+ if (matchesKey(data, "esc")) {
14770
+ if (this.focus === "list" && this.filterText.length > 0) {
14771
+ this.filterText = "";
14772
+ this.selectInitialFilterMatch();
14773
+ return;
14774
+ }
14775
+ this.options.onClose();
14776
+ return;
14777
+ }
14778
+ if (this.focus === "content") {
14779
+ if (this.handleContentInput(data)) return;
14780
+ } else if (this.handleListInput(data)) {
14781
+ return;
14782
+ }
14783
+ }
14784
+ invalidate() {
14785
+ }
14786
+ handleListInput(data) {
14787
+ const filtered = this.filteredArtifacts();
14788
+ if (matchesKey(data, "up") || data === "k") {
14789
+ if (filtered.length > 0) this.selectIndex(this.selectedIndex - 1);
14790
+ return true;
14791
+ }
14792
+ if (matchesKey(data, "down") || data === "j") {
14793
+ if (filtered.length > 0) this.selectIndex(this.selectedIndex + 1);
14794
+ return true;
14795
+ }
14796
+ if (matchesKey(data, "backspace")) {
14797
+ if (this.filterText.length > 0) {
14798
+ this.filterText = this.filterText.slice(0, -1);
14799
+ this.selectInitialFilterMatch();
14800
+ }
14801
+ return true;
14802
+ }
14803
+ if (data.length === 1 && data >= " " && data !== "\x7F") {
14804
+ this.filterText += data;
14805
+ this.selectInitialFilterMatch();
14806
+ return true;
14807
+ }
14808
+ return false;
14809
+ }
14810
+ handleContentInput(data) {
14811
+ const bodyHeight = this.lastContentBodyHeight;
14812
+ const total = this.renderedContentLines(this.lastContentWidth).length;
14813
+ let action = null;
14814
+ if (matchesKey(data, "up") || data === "k") action = "line-up";
14815
+ else if (matchesKey(data, "down") || data === "j") action = "line-down";
14816
+ else if (data === "\x1B[5~") action = "page-up";
14817
+ else if (data === "\x1B[6~") action = "page-down";
14818
+ else if (data === "") action = "half-up";
14819
+ else if (data === "") action = "half-down";
14820
+ else if (data === "g") action = "top";
14821
+ else if (data === "G") action = "bottom";
14822
+ if (!action) return false;
14823
+ this.contentScrollOffset = nextContentScrollOffset(this.contentScrollOffset, total, bodyHeight, action);
14824
+ this.options.requestRender?.();
14825
+ return true;
14826
+ }
14827
+ verifySelected() {
14828
+ const artifact = this.selectedArtifact();
14829
+ if (!artifact?.verify) return;
14830
+ const key = artifactKey(artifact);
14831
+ this.verifications.set(key, { status: "running" });
14832
+ this.options.requestRender?.();
14833
+ void artifact.verify().then((result) => {
14834
+ this.verifications.set(
14835
+ key,
14836
+ result.ok ? { status: "ok", detail: result.detail } : { status: "fail", detail: result.detail }
14837
+ );
14838
+ }).catch((err) => {
14839
+ const detail = err instanceof Error ? err.message : String(err);
14840
+ this.verifications.set(key, { status: "fail", detail });
14841
+ }).finally(() => this.options.requestRender?.());
14842
+ }
14843
+ openPathNotice() {
14844
+ const artifact = this.selectedArtifact();
14845
+ if (!artifact?.path) {
14846
+ this.options.notice?.("warning", "view: selected artifact has no backing path", "view:path");
14847
+ return;
14848
+ }
14849
+ this.options.notice?.("info", `artifact path: ${artifact.path}`, `view:path:${artifact.category}:${artifact.id}`);
14850
+ }
14851
+ };
14852
+ function viewBodyHeight(tui) {
14853
+ return Math.max(1, tui.terminal.rows - VIEW_OVERLAY_MARGIN.top - VIEW_OVERLAY_MARGIN.bottom - 2);
14854
+ }
14855
+ function openViewOverlay(tui, options) {
14856
+ const view = new ViewOverlayView({
14857
+ ...options,
14858
+ getBodyHeight: () => viewBodyHeight(tui),
14859
+ requestRender: () => tui.requestRender()
14860
+ });
14861
+ const handle = showClioOverlayFrame(tui, view, {
14862
+ anchor: "top-left",
14863
+ width: VIEW_OVERLAY_WIDTH,
14864
+ maxHeight: VIEW_OVERLAY_MAX_HEIGHT,
14865
+ margin: VIEW_OVERLAY_MARGIN,
14866
+ title: "View",
14867
+ footerHint: (innerWidth) => view.getHint(innerWidth)
14868
+ });
14869
+ view.refresh();
14870
+ return handle;
14871
+ }
14872
+
14873
+ // src/interactive/overlays/ask-user.ts
14874
+ var ASK_USER_DECISION_TONE = "action";
14875
+ var ASK_USER_DECISION_TITLE = "Decision required";
14876
+ var ASK_USER_WAITING_TITLE = "Ask User";
14877
+ var ASK_USER_COMPACT_MAX_OPTIONS = 3;
14878
+ var ASK_USER_DEFAULT_SURFACE = "panel";
14879
+ var ASK_USER_SURFACE_GEOMETRY = {
14880
+ compact: { width: 68, maxInnerRows: 10, anchor: "bottom-center", margin: { top: 1, right: 2, bottom: 1, left: 2 } },
14881
+ panel: { width: 88, maxInnerRows: 18, anchor: "center", margin: 1 },
14882
+ // The interview is a workspace, not a modal. A cap of 42 turned a 66-row
14883
+ // terminal into a centered box with twenty blank rows around it, so the
14884
+ // interview takes every row the frame has and lays its own chrome out inside
14885
+ // them. The one-row margin keeps the border off the top row and the footer.
14886
+ interview: { width: 0, maxInnerRows: 0, anchor: "center", margin: 1 }
14887
+ };
14888
+ var ASK_USER_FRAME_ROWS = 2;
14889
+ var ASK_USER_FRAME_AND_MARGIN_ROWS = 4;
14890
+ var MIN_VISIBLE_OPTIONS = 3;
14891
+ var INTERVIEW_CHROME_ROWS = 7;
14892
+ var MAX_VISIBLE_OPTIONS = 12;
14893
+ var ELLIPSIS2 = "\u2026";
14894
+ function askUserSurface(questions, priorRounds = 0) {
14895
+ if (priorRounds > 0 || questions.length !== 1) return "interview";
14896
+ const question = questions[0];
14897
+ if (!question) return "interview";
14898
+ return (question.options?.length ?? 0) <= ASK_USER_COMPACT_MAX_OPTIONS ? "compact" : "panel";
14899
+ }
14900
+ function questionHasOptions(question) {
14901
+ return (question.options?.length ?? 0) > 0;
14902
+ }
14903
+ function initialMode(question) {
14904
+ return questionHasOptions(question) ? "select" : "text";
14905
+ }
14906
+ function createQuestionState(question) {
14907
+ return {
14908
+ mode: initialMode(question),
14909
+ selected: /* @__PURE__ */ new Set(),
14910
+ customAnswer: "",
14911
+ inputValue: "",
14912
+ answer: ""
14913
+ };
14914
+ }
14915
+ function isOtherOption(label) {
14916
+ const normalized = label.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
14917
+ return normalized === "other" || normalized === "custom" || normalized === "something else" || normalized.startsWith("other ");
14918
+ }
14919
+ function optionItems(question, selected) {
14920
+ const options = question.options ?? [];
14921
+ const explicitOtherIndex = options.findIndex((option) => isOtherOption(option.label));
14922
+ const items = [];
14923
+ for (let index = 0; index < options.length; index += 1) {
14924
+ const option = options[index];
14925
+ if (!option) continue;
14926
+ const isExplicitOther = index === explicitOtherIndex;
14927
+ const label = question.multi_select === true && !isExplicitOther ? `${selected.has(index) ? "[x]" : "[ ]"} ${option.label}` : option.label;
14928
+ const item = {
14929
+ value: isExplicitOther ? "other" : `option:${index}`,
14930
+ label
14931
+ };
14932
+ if (option.description) item.description = option.description;
14933
+ items.push(item);
14934
+ }
14935
+ if (explicitOtherIndex === -1) {
14936
+ items.push({
14937
+ value: "other",
14938
+ label: question.multi_select === true ? `[ ] ${ASK_USER_OTHER_LABEL}` : ASK_USER_OTHER_LABEL,
14939
+ description: "type your answer"
14940
+ });
14941
+ }
14942
+ return items;
14943
+ }
14944
+ function optionIndexFromValue(value) {
14945
+ if (!value.startsWith("option:")) return null;
14946
+ const index = Number(value.slice("option:".length));
14947
+ return Number.isInteger(index) && index >= 0 ? index : null;
14948
+ }
14949
+ function answerText(question, selected, customAnswer) {
14950
+ const parts = [];
14951
+ for (const index of [...selected].sort((a, b) => a - b)) {
14952
+ const label = question.options?.[index]?.label;
14953
+ if (label) parts.push(label);
14954
+ }
14955
+ const custom = customAnswer.trim();
14956
+ if (custom.length > 0) parts.push(custom);
14957
+ return parts.join("; ");
14958
+ }
14959
+ function fitLine(text, width) {
14960
+ const safeWidth = Math.max(1, width);
14961
+ if (visibleWidth(text) <= safeWidth) return text;
14962
+ return truncateToWidth(text, safeWidth, ELLIPSIS2, true);
14963
+ }
14964
+ function compactTitle(question) {
14965
+ return (question.header ?? question.question).replace(/\s+/g, " ").trim();
14966
+ }
14967
+ var AskUserOverlayView = class {
14968
+ constructor(deps) {
14969
+ this.deps = deps;
14970
+ }
14971
+ deps;
14972
+ phase = "waiting";
14973
+ surface = ASK_USER_DEFAULT_SURFACE;
14974
+ index = 0;
14975
+ status = "";
14976
+ questions = [];
14977
+ states = [];
14978
+ history = [];
14979
+ list = null;
14980
+ input = null;
14981
+ resolveCurrent = null;
14982
+ /** Rows of the interview's scrollable middle already scrolled past. */
14983
+ contentScrollOffset = 0;
14984
+ /** Whether the last interview render had more content than the region held. */
14985
+ contentOverflows = false;
14986
+ /** Rows the scroll region drew on the last render, for a page-sized jump. */
14987
+ contentRegionHeight = 1;
14988
+ /** Rows the scroll region wanted on the last render. */
14989
+ contentTotalRows = 0;
14990
+ begin(questions) {
14991
+ if (this.phase === "closed") return Promise.resolve(cancelledAskUserResult());
14992
+ if (this.resolveCurrent) return Promise.resolve(cancelledAskUserResult());
14993
+ this.phase = "asking";
14994
+ this.index = 0;
14995
+ this.status = "";
14996
+ this.questions = [...questions];
14997
+ this.states = this.questions.map((question) => createQuestionState(question));
14998
+ this.list = null;
14999
+ this.input = null;
15000
+ this.resetContentScroll();
15001
+ this.setSurface(askUserSurface(this.questions, this.history.length));
15002
+ this.rebuildControl();
15003
+ this.deps.requestRender();
15004
+ return new Promise((resolve6) => {
15005
+ this.resolveCurrent = resolve6;
15006
+ });
15007
+ }
15008
+ cancel() {
15009
+ this.finish(cancelledAskUserResult());
15010
+ }
15011
+ close() {
15012
+ this.phase = "closed";
15013
+ this.finish(cancelledAskUserResult());
15014
+ }
15015
+ isWaiting() {
15016
+ return this.phase === "waiting" && this.resolveCurrent === null;
15017
+ }
15018
+ /** True while the overlay is holding a question the operator has to answer. */
15019
+ isDecisionPending() {
15020
+ return this.phase === "asking";
15021
+ }
15022
+ currentSurface() {
15023
+ return this.surface;
15024
+ }
15025
+ invalidate() {
15026
+ this.list?.invalidate();
15027
+ this.input?.invalidate();
15028
+ }
15029
+ handleInput(data) {
15030
+ if (this.phase !== "asking") return;
15031
+ const question = this.currentQuestion();
15032
+ const state = this.currentState();
15033
+ if (!question || !state) return;
15034
+ if (this.handleScrollInput(data, state.mode)) return;
15035
+ if (state.mode === "text") {
15036
+ if (this.isTextModePreviousKey(data)) {
15037
+ this.goToRelativeQuestion(-1);
15038
+ return;
15039
+ }
15040
+ if (this.isTextModeNextKey(data)) {
15041
+ this.goToRelativeQuestion(1);
15042
+ return;
15043
+ }
15044
+ this.input?.handleInput(data);
15045
+ return;
15046
+ }
15047
+ if (this.isPreviousQuestionKey(data)) {
15048
+ this.goToRelativeQuestion(-1);
15049
+ return;
15050
+ }
15051
+ if (this.isNextQuestionKey(data)) {
15052
+ this.goToRelativeQuestion(1);
15053
+ return;
15054
+ }
15055
+ if (question.multi_select === true && data === " ") {
15056
+ this.toggleCurrentSelection(question, state);
14285
15057
  return;
14286
15058
  }
14287
15059
  if (question.multi_select === true && (matchesKey(data, "enter") || data === "\n")) {
@@ -14305,19 +15077,88 @@ var AskUserOverlayView = class {
14305
15077
  if (this.phase !== "asking") return this.renderWaiting(safeWidth, maxRows);
14306
15078
  const question = this.currentQuestion();
14307
15079
  if (!question) return [clioTheme().fg("muted", "No questions.")];
14308
- const interview = this.surface === "interview";
15080
+ if (this.surface === "interview") return this.renderInterview(question, safeWidth, maxRows);
15081
+ this.contentOverflows = false;
14309
15082
  const controlLines = this.renderControlLines(safeWidth);
14310
- const ledgerLines = interview ? this.renderAnswerLedger(safeWidth) : [];
14311
15083
  const statusLines = this.status.length > 0 ? ["", fitLine(clioTheme().fg("dim", this.status), safeWidth)] : [];
14312
- const stripLines = interview ? [this.renderQuestionStrip(safeWidth), ""] : [];
14313
15084
  const headerLine4 = this.renderQuestionHeader(question, safeWidth);
14314
15085
  const headerLines = headerLine4.length > 0 ? [headerLine4] : [];
14315
- const spent = stripLines.length + headerLines.length + statusLines.length + 1 + controlLines.length + ledgerLines.length;
15086
+ const spent = headerLines.length + statusLines.length + 1 + controlLines.length;
14316
15087
  const questionLines = wrapTextWithAnsi(question.question, safeWidth).slice(0, Math.max(1, maxRows - spent));
14317
- return [...stripLines, ...headerLines, ...questionLines, ...statusLines, "", ...controlLines, ...ledgerLines].slice(
14318
- 0,
14319
- maxRows
15088
+ return [...headerLines, ...questionLines, ...statusLines, "", ...controlLines].slice(0, maxRows);
15089
+ }
15090
+ /**
15091
+ * The interview, as a workspace with fixed chrome and a scrolling middle.
15092
+ *
15093
+ * The round strip and the question header hold the top; the status line and
15094
+ * the active control hold the bottom, at their full row cost, so the thing the
15095
+ * operator has to act on is never the thing that gets clipped. The question
15096
+ * text and the answer ledger share whatever is left and scroll through it.
15097
+ * Everything is re-derived per render, so a resize is just the next frame.
15098
+ */
15099
+ renderInterview(question, width, maxRows) {
15100
+ const theme6 = clioTheme();
15101
+ const controlLines = this.renderControlLines(width);
15102
+ const statusLines = this.status.length > 0 ? ["", fitLine(theme6.fg("dim", this.status), width)] : [];
15103
+ const headerLine4 = this.renderQuestionHeader(question, width);
15104
+ const headerLines = headerLine4.length > 0 ? [headerLine4] : [];
15105
+ const stripLines = [this.renderQuestionStrip(width), ""];
15106
+ const content = [...wrapTextWithAnsi(question.question, width), ...this.renderAnswerLedger(width)];
15107
+ let top = [...stripLines, ...headerLines];
15108
+ let bottom = [...statusLines, "", ...controlLines];
15109
+ const region = () => maxRows - top.length - bottom.length;
15110
+ if (region() < 1) top = [...headerLines];
15111
+ if (region() < 1) top = [];
15112
+ if (region() < 1) bottom = ["", ...controlLines];
15113
+ if (region() < 1) bottom = [...controlLines];
15114
+ const regionRows = Math.max(1, region());
15115
+ const overflows = content.length > regionRows;
15116
+ this.contentOverflows = overflows;
15117
+ this.contentTotalRows = content.length;
15118
+ const viewRows = overflows ? Math.max(1, regionRows - 1) : regionRows;
15119
+ this.contentRegionHeight = viewRows;
15120
+ this.contentScrollOffset = Math.max(0, Math.min(this.contentScrollOffset, Math.max(0, content.length - viewRows)));
15121
+ const visible = content.slice(this.contentScrollOffset, this.contentScrollOffset + viewRows);
15122
+ const middle = [...visible];
15123
+ if (overflows) middle.push(fitLine(theme6.fg("dim", this.scrollIndicator(content.length, viewRows)), width));
15124
+ while (middle.length < regionRows) middle.push("");
15125
+ return [...top, ...middle, ...bottom].slice(0, maxRows);
15126
+ }
15127
+ scrollIndicator(total, viewRows) {
15128
+ const above = this.contentScrollOffset;
15129
+ const below = Math.max(0, total - viewRows - above);
15130
+ const parts = [];
15131
+ if (above > 0) parts.push(`\u2191 ${above} more`);
15132
+ if (below > 0) parts.push(`\u2193 ${below} more`);
15133
+ parts.push(`(${Math.min(total, above + viewRows)}/${total})`);
15134
+ return parts.join(" ");
15135
+ }
15136
+ /** True when the key belonged to the scroll region rather than to a control. */
15137
+ handleScrollInput(data, mode) {
15138
+ if (this.surface !== "interview") return false;
15139
+ const action = this.scrollAction(data, mode);
15140
+ if (!action) return false;
15141
+ const next = nextContentScrollOffset(
15142
+ this.contentScrollOffset,
15143
+ this.contentTotalRows,
15144
+ this.contentRegionHeight,
15145
+ action
14320
15146
  );
15147
+ if (next !== this.contentScrollOffset) {
15148
+ this.contentScrollOffset = next;
15149
+ this.deps.requestRender();
15150
+ }
15151
+ return true;
15152
+ }
15153
+ scrollAction(data, mode) {
15154
+ if (matchesKey(data, "pageUp")) return "page-up";
15155
+ if (matchesKey(data, "pageDown")) return "page-down";
15156
+ if (mode === "select" && matchesKey(data, "ctrl+u")) return "half-up";
15157
+ if (mode === "select" && matchesKey(data, "ctrl+d")) return "half-down";
15158
+ return null;
15159
+ }
15160
+ resetContentScroll() {
15161
+ this.contentScrollOffset = 0;
14321
15162
  }
14322
15163
  footerHint() {
14323
15164
  if (this.phase !== "asking") {
@@ -14327,25 +15168,34 @@ var AskUserOverlayView = class {
14327
15168
  const state = this.currentState();
14328
15169
  if (!question || !state) return buildHint([]);
14329
15170
  if (state.mode === "text") {
14330
- return this.questions.length > 1 ? buildHint([
15171
+ return this.questions.length > 1 ? this.withScrollHint([
14331
15172
  { key: "Enter", verb: "submit" },
14332
15173
  { key: "Alt+Left/Right", verb: "question" }
14333
- ]) : buildHint([{ key: "Enter", verb: "submit" }]);
15174
+ ]) : this.withScrollHint([{ key: "Enter", verb: "submit" }]);
14334
15175
  }
14335
15176
  if (question.multi_select === true) {
14336
- return this.questions.length > 1 ? buildHint([
15177
+ return this.questions.length > 1 ? this.withScrollHint([
14337
15178
  { key: "Left/Right", verb: "question" },
14338
15179
  { key: "Space", verb: "toggle" },
14339
15180
  { key: "Enter", verb: "accept" }
14340
- ]) : buildHint([
15181
+ ]) : this.withScrollHint([
14341
15182
  { key: "Space", verb: "toggle" },
14342
15183
  { key: "Enter", verb: "accept" }
14343
15184
  ]);
14344
15185
  }
14345
- return this.questions.length > 1 ? buildHint([
15186
+ return this.questions.length > 1 ? this.withScrollHint([
14346
15187
  { key: "Left/Right", verb: "question" },
14347
15188
  { key: "Enter", verb: "accept" }
14348
- ]) : buildHint([{ key: "Enter", verb: "accept" }]);
15189
+ ]) : this.withScrollHint([{ key: "Enter", verb: "accept" }]);
15190
+ }
15191
+ /**
15192
+ * The keys for this question, plus the scroll keys when there is something to
15193
+ * scroll. A footer that always advertised PgUp would be advertising a key that
15194
+ * does nothing on most rounds.
15195
+ */
15196
+ withScrollHint(entries) {
15197
+ if (this.surface !== "interview" || !this.contentOverflows) return buildHint(entries);
15198
+ return buildHint([...entries, { key: "PgUp/PgDn", verb: "scroll" }]);
14349
15199
  }
14350
15200
  setSurface(next) {
14351
15201
  if (this.surface === next) return;
@@ -14395,13 +15245,14 @@ var AskUserOverlayView = class {
14395
15245
  maxInnerRows() {
14396
15246
  const cap = ASK_USER_SURFACE_GEOMETRY[this.surface].maxInnerRows;
14397
15247
  const rows = this.deps.getTerminalRows();
14398
- if (!Number.isFinite(rows) || rows <= 0) return cap;
14399
- return Math.max(1, Math.min(cap, Math.floor(rows) - ASK_USER_FRAME_AND_MARGIN_ROWS));
15248
+ if (!Number.isFinite(rows) || rows <= 0) return cap > 0 ? cap : 1;
15249
+ const available = Math.max(1, Math.floor(rows) - ASK_USER_FRAME_AND_MARGIN_ROWS);
15250
+ return cap > 0 ? Math.min(cap, available) : available;
14400
15251
  }
14401
15252
  maxVisibleOptions() {
14402
15253
  const inner = this.maxInnerRows();
14403
15254
  if (this.surface === "interview") {
14404
- return Math.max(MIN_VISIBLE_OPTIONS, Math.min(MAX_VISIBLE_OPTIONS, inner - 12));
15255
+ return Math.max(MIN_VISIBLE_OPTIONS, Math.min(MAX_VISIBLE_OPTIONS, inner - INTERVIEW_CHROME_ROWS));
14405
15256
  }
14406
15257
  return Math.max(1, Math.min(MAX_VISIBLE_OPTIONS, inner - 4));
14407
15258
  }
@@ -14536,13 +15387,13 @@ var AskUserOverlayView = class {
14536
15387
  let body;
14537
15388
  if (description && width > 40) {
14538
15389
  const labelWidth = Math.max(1, Math.min(primaryColumnWidth - 2, available - 4));
14539
- const label = truncateToWidth(item.label, labelWidth, ELLIPSIS, false);
15390
+ const label = truncateToWidth(item.label, labelWidth, ELLIPSIS2, false);
14540
15391
  const spacing = " ".repeat(Math.max(1, primaryColumnWidth - visibleWidth(label)));
14541
15392
  const descriptionWidth = Math.max(1, available - visibleWidth(label) - visibleWidth(spacing));
14542
- const desc = truncateToWidth(description, descriptionWidth, ELLIPSIS, false);
15393
+ const desc = truncateToWidth(description, descriptionWidth, ELLIPSIS2, false);
14543
15394
  body = selected ? theme6.style("accent", `${label}${spacing}${desc}`, { bold: true }) : `${label}${theme6.fg("muted", `${spacing}${desc}`)}`;
14544
15395
  } else {
14545
- const label = truncateToWidth(item.label, available, ELLIPSIS, false);
15396
+ const label = truncateToWidth(item.label, available, ELLIPSIS2, false);
14546
15397
  body = selected ? theme6.style("accent", label, { bold: true }) : label;
14547
15398
  }
14548
15399
  return fitLine(`${prefix}${body}${affordance}`, width);
@@ -14706,6 +15557,7 @@ var AskUserOverlayView = class {
14706
15557
  const next = this.nextUnansweredIndex();
14707
15558
  if (next !== null) {
14708
15559
  this.index = next;
15560
+ this.resetContentScroll();
14709
15561
  const nextState = this.currentState();
14710
15562
  const nextQuestion = this.currentQuestion();
14711
15563
  if (nextState && nextQuestion && !nextState.answer.trim()) nextState.mode = initialMode(nextQuestion);
@@ -14737,6 +15589,7 @@ var AskUserOverlayView = class {
14737
15589
  this.syncActiveControl();
14738
15590
  this.index = (this.index + delta + this.questions.length) % this.questions.length;
14739
15591
  this.status = "";
15592
+ this.resetContentScroll();
14740
15593
  this.rebuildControl();
14741
15594
  this.deps.requestRender();
14742
15595
  }
@@ -14767,7 +15620,9 @@ function openAskUserOverlay(tui, deps) {
14767
15620
  handle = showClioOverlayFrame(tui, view, {
14768
15621
  anchor: geometry.anchor,
14769
15622
  width: geometry.width,
14770
- maxHeight: geometry.maxInnerRows + ASK_USER_FRAME_ROWS,
15623
+ // A zero cap means the frame keeps its own budget, which is every row
15624
+ // the terminal has left after the margins.
15625
+ ...geometry.maxInnerRows > 0 ? { maxHeight: geometry.maxInnerRows + ASK_USER_FRAME_ROWS } : {},
14771
15626
  margin: geometry.margin,
14772
15627
  title: () => view.isDecisionPending() ? ASK_USER_DECISION_TITLE : ASK_USER_WAITING_TITLE,
14773
15628
  tone: () => view.isDecisionPending() ? ASK_USER_DECISION_TONE : void 0,
@@ -14882,12 +15737,12 @@ import { spawn } from "node:child_process";
14882
15737
  // src/interactive/overlays/auth-dialog.ts
14883
15738
  init_esm_shims();
14884
15739
  var AUTH_DIALOG_WIDTH = 88;
14885
- var ELLIPSIS2 = "\u2026";
15740
+ var ELLIPSIS3 = "\u2026";
14886
15741
  var KEY_WIDTH = 10;
14887
15742
  function fitLine2(text, width) {
14888
15743
  const safeWidth = Math.max(1, width);
14889
15744
  if (visibleWidth(text) <= safeWidth) return text;
14890
- return truncateToWidth(text, safeWidth, ELLIPSIS2, true);
15745
+ return truncateToWidth(text, safeWidth, ELLIPSIS3, true);
14891
15746
  }
14892
15747
  function keyCell(label) {
14893
15748
  const theme6 = clioTheme();
@@ -14896,7 +15751,7 @@ function keyCell(label) {
14896
15751
  return `${key}${" ".repeat(Math.max(0, KEY_WIDTH - width))}`;
14897
15752
  }
14898
15753
  function normalizeAuthLine(line) {
14899
- return line.replace(/\.\.\./g, ELLIPSIS2);
15754
+ return line.replace(/\.\.\./g, ELLIPSIS3);
14900
15755
  }
14901
15756
  function formatAuthChoiceLine(line) {
14902
15757
  const match = /^(?<marker>[* ])\s*(?<index>\d+)\.\s*(?<label>.+)$/.exec(line);
@@ -15634,7 +16489,7 @@ init_esm_shims();
15634
16489
 
15635
16490
  // src/interactive/overlays/list-overlay.ts
15636
16491
  init_esm_shims();
15637
- var ELLIPSIS3 = "\u2026";
16492
+ var ELLIPSIS4 = "\u2026";
15638
16493
  var ListOverlayView = class {
15639
16494
  constructor(options, onChange) {
15640
16495
  this.options = options;
@@ -15768,7 +16623,7 @@ var ListOverlayView = class {
15768
16623
  }
15769
16624
  padLine(line, targetWidth) {
15770
16625
  const w = visibleWidth(line);
15771
- if (w >= targetWidth) return truncateToWidth(line, targetWidth, ELLIPSIS3, true);
16626
+ if (w >= targetWidth) return truncateToWidth(line, targetWidth, ELLIPSIS4, true);
15772
16627
  return line + " ".repeat(targetWidth - w);
15773
16628
  }
15774
16629
  renderList(width, listMaxLines, filteredItems, pad) {
@@ -15838,7 +16693,7 @@ var ListOverlayView = class {
15838
16693
  const metaStr = item.meta ?? "";
15839
16694
  const metaLen = metaStr ? visibleWidth(metaStr) : 0;
15840
16695
  const maxLabelWidth = Math.max(1, availableWidth - (metaLen > 0 ? metaLen + 2 : 0));
15841
- const truncatedLabel = truncateToWidth(item.label, maxLabelWidth, ELLIPSIS3, true);
16696
+ const truncatedLabel = truncateToWidth(item.label, maxLabelWidth, ELLIPSIS4, true);
15842
16697
  const actualLabelWidth = visibleWidth(truncatedLabel);
15843
16698
  const spacing = " ".repeat(Math.max(1, availableWidth - actualLabelWidth - metaLen));
15844
16699
  let labelPart = truncatedLabel;
@@ -15886,7 +16741,7 @@ var ListOverlayView = class {
15886
16741
  const sliced = mdLines.slice(this.detailScrollOffset, this.detailScrollOffset + height);
15887
16742
  const padded = sliced.map((line) => {
15888
16743
  const w = visibleWidth(line);
15889
- if (w >= width) return truncateToWidth(line, width, ELLIPSIS3, true);
16744
+ if (w >= width) return truncateToWidth(line, width, ELLIPSIS4, true);
15890
16745
  return line + " ".repeat(width - w);
15891
16746
  });
15892
16747
  while (padded.length < height) {
@@ -16288,7 +17143,7 @@ function openMemoryOverlay(tui, getStatus, getRecords, options = {}) {
16288
17143
  // src/interactive/overlays/context-reset.ts
16289
17144
  init_esm_shims();
16290
17145
  var CONTEXT_RESET_OVERLAY_WIDTH = 72;
16291
- var ELLIPSIS4 = "\u2026";
17146
+ var ELLIPSIS5 = "\u2026";
16292
17147
  function contextResetOptions(choice) {
16293
17148
  return choice === "delete-clio-md" ? { all: true, confirmed: true, confirmedAll: true } : { confirmed: true };
16294
17149
  }
@@ -16325,13 +17180,13 @@ function buildSelectPresentation(items) {
16325
17180
  const label = singleLine(text);
16326
17181
  const description = descriptions.get(item.value) ?? "";
16327
17182
  if (description.length === 0 || maxWidth < 44) {
16328
- return truncateToWidth(label, maxWidth, ELLIPSIS4, true);
17183
+ return truncateToWidth(label, maxWidth, ELLIPSIS5, true);
16329
17184
  }
16330
17185
  const labelWidth = Math.max(1, Math.min(22, maxWidth - 14));
16331
- const fittedLabel = truncateToWidth(label, labelWidth, ELLIPSIS4, true);
17186
+ const fittedLabel = truncateToWidth(label, labelWidth, ELLIPSIS5, true);
16332
17187
  const spacing = " ".repeat(Math.max(1, labelWidth + 2 - visibleWidth(fittedLabel)));
16333
17188
  const descriptionWidth = Math.max(1, maxWidth - visibleWidth(fittedLabel) - visibleWidth(spacing));
16334
- const fittedDescription = truncateToWidth(description, descriptionWidth, ELLIPSIS4, true);
17189
+ const fittedDescription = truncateToWidth(description, descriptionWidth, ELLIPSIS5, true);
16335
17190
  const body = `${fittedLabel}${spacing}${fittedDescription}`;
16336
17191
  return isSelected ? body : `${fittedLabel}${clioTheme().fg("muted", `${spacing}${fittedDescription}`)}`;
16337
17192
  }
@@ -17416,7 +18271,7 @@ var DROP_PATH_COLUMN_WIDTH = 52;
17416
18271
  var ULTRA_NARROW_TERMINAL_WIDTH = 60;
17417
18272
  var FALLBACK_THINKING_VALUES = THINKING_LEVELS;
17418
18273
  var ROW_GAP = " ";
17419
- var ELLIPSIS5 = "\u2026";
18274
+ var ELLIPSIS6 = "\u2026";
17420
18275
  var SELECT_UP = "\x1B[A";
17421
18276
  var SELECT_DOWN = "\x1B[B";
17422
18277
  var SettingsSelectList = class extends SelectList {
@@ -17963,7 +18818,7 @@ var ScopedModelChecklist = class {
17963
18818
  const pointer = selected2 ? theme6.fg("accent", `${GLYPH.cursor} `) : " ";
17964
18819
  const check = theme6.fg(checked ? "accent" : "dim", checked ? "[x]" : "[ ]");
17965
18820
  const label = selected2 ? theme6.style("accent", row2.label, { bold: true }) : theme6.fg("muted", row2.label);
17966
- return truncateToWidth(`${pointer}${check} ${label}`, Math.max(1, width), ELLIPSIS5, true);
18821
+ return truncateToWidth(`${pointer}${check} ${label}`, Math.max(1, width), ELLIPSIS6, true);
17967
18822
  });
17968
18823
  const selected = this.rows[this.selectedRow];
17969
18824
  if (selected?.kind === "entry") {
@@ -17971,7 +18826,7 @@ var ScopedModelChecklist = class {
17971
18826
  const wrapped = wrapTextWithAnsi(theme6.fg("dim", detail), Math.max(1, width));
17972
18827
  const kept = wrapped.slice(0, 3);
17973
18828
  const last = kept.at(-1);
17974
- const marked = wrapped.length > kept.length && last !== void 0 ? [...kept.slice(0, -1), `${last}${ELLIPSIS5}`] : kept;
18829
+ const marked = wrapped.length > kept.length && last !== void 0 ? [...kept.slice(0, -1), `${last}${ELLIPSIS6}`] : kept;
17975
18830
  lines.push("", ...marked);
17976
18831
  }
17977
18832
  return lines;
@@ -19083,23 +19938,23 @@ function humanizeChangePlanValue(plan) {
19083
19938
  function truncateProfileFact(text, width) {
19084
19939
  const safeWidth = Math.max(1, width);
19085
19940
  if (visibleWidth(text) <= safeWidth) return text;
19086
- if (safeWidth <= visibleWidth(ELLIPSIS5)) return ELLIPSIS5;
19941
+ if (safeWidth <= visibleWidth(ELLIPSIS6)) return ELLIPSIS6;
19087
19942
  const characters = [];
19088
19943
  let used = 0;
19089
- const contentWidth = safeWidth - visibleWidth(ELLIPSIS5);
19944
+ const contentWidth = safeWidth - visibleWidth(ELLIPSIS6);
19090
19945
  for (const character of Array.from(text)) {
19091
19946
  const characterWidth = visibleWidth(character);
19092
19947
  if (used + characterWidth > contentWidth) break;
19093
19948
  characters.push(character);
19094
19949
  used += characterWidth;
19095
19950
  }
19096
- return `${characters.join("")}${ELLIPSIS5}`;
19951
+ return `${characters.join("")}${ELLIPSIS6}`;
19097
19952
  }
19098
19953
  function ellipsizeFromLeft(text, width) {
19099
19954
  const safeWidth = Math.max(1, width);
19100
19955
  if (visibleWidth(text) <= safeWidth) return text;
19101
- if (safeWidth <= visibleWidth(ELLIPSIS5)) return ELLIPSIS5;
19102
- const suffixWidth = safeWidth - visibleWidth(ELLIPSIS5);
19956
+ if (safeWidth <= visibleWidth(ELLIPSIS6)) return ELLIPSIS6;
19957
+ const suffixWidth = safeWidth - visibleWidth(ELLIPSIS6);
19103
19958
  const suffix = [];
19104
19959
  let used = 0;
19105
19960
  for (const character of Array.from(text).reverse()) {
@@ -19108,7 +19963,7 @@ function ellipsizeFromLeft(text, width) {
19108
19963
  suffix.unshift(character);
19109
19964
  used += characterWidth;
19110
19965
  }
19111
- return `${ELLIPSIS5}${suffix.join("")}`;
19966
+ return `${ELLIPSIS6}${suffix.join("")}`;
19112
19967
  }
19113
19968
  function propagationFor(id) {
19114
19969
  if (RESTART_REQUIRED_IDS.has(id)) return "takes effect next session";
@@ -19118,7 +19973,7 @@ function propagationFor(id) {
19118
19973
  return null;
19119
19974
  }
19120
19975
  function fixedLines(lines, width, height) {
19121
- const out = lines.slice(0, height).map((line) => padAnsi(line, width, ELLIPSIS5));
19976
+ const out = lines.slice(0, height).map((line) => padAnsi(line, width, ELLIPSIS6));
19122
19977
  while (out.length < height) out.push(" ".repeat(Math.max(0, width)));
19123
19978
  return out;
19124
19979
  }
@@ -19163,28 +20018,28 @@ function formatSettingRow(item, width, selected, columns, indentWidth, displayVa
19163
20018
  return truncateToWidth(
19164
20019
  `${indent}${prefix2}${label2}${ROW_GAP}${theme6.fg(selected ? "accent" : "muted", item.currentValue)}`,
19165
20020
  width,
19166
- ELLIPSIS5,
20021
+ ELLIPSIS6,
19167
20022
  true
19168
20023
  );
19169
20024
  }
19170
20025
  const prefix = selected ? theme6.fg("accent", `${GLYPH.cursor} `) : " ";
19171
- const labelText = padAnsi(item.label, columns.label, ELLIPSIS5);
20026
+ const labelText = padAnsi(item.label, columns.label, ELLIPSIS6);
19172
20027
  const label = selected ? theme6.style("accent", labelText, { bold: true }) : item.presentationKind === "group-header" ? theme6.style("dim", labelText, { bold: true }) : item.presentationKind === "action" ? theme6.style("accentDeep", labelText, { bold: true }) : labelText;
19173
20028
  if (item.presentationKind === "group-header") {
19174
- return truncateToWidth(`${indent} ${label}`, width, ELLIPSIS5, true);
20029
+ return truncateToWidth(`${indent} ${label}`, width, ELLIPSIS6, true);
19175
20030
  }
19176
20031
  const modified = !item.readOnly && item.defaultValue !== void 0 && item.currentValue !== item.defaultValue;
19177
20032
  const marker = pending ? theme6.fg("accent", `${GLYPH.scoped} `) : modified ? theme6.fg("accent", `${GLYPH.scoped} `) : " ";
19178
20033
  let used = visibleWidth(indent) + 2 + columns.label + visibleWidth(ROW_GAP);
19179
20034
  let pathSegment = "";
19180
20035
  if (columns.path > 0 && !(item.presentationKind === "status" && width < 64)) {
19181
- pathSegment = `${theme6.fg("dim", padAnsi(item.configPath, columns.path, ELLIPSIS5))}${ROW_GAP}`;
20036
+ pathSegment = `${theme6.fg("dim", padAnsi(item.configPath, columns.path, ELLIPSIS6))}${ROW_GAP}`;
19182
20037
  used += columns.path + visibleWidth(ROW_GAP);
19183
20038
  }
19184
20039
  const valueWidth = Math.max(1, width - used - 2);
19185
20040
  const valueSegments = pending ? [{ text: displayValue, tone: "neutral" }] : item.presentationKind === "read-only-fact" ? [{ text: "\u2014 ", tone: "neutral" }, ...item.valueSegments] : item.valueSegments;
19186
20041
  const value = renderSettingValue(valueSegments, valueWidth, selected, item.readOnly);
19187
- return truncateToWidth(`${indent}${prefix}${label}${ROW_GAP}${pathSegment}${marker}${value}`, width, ELLIPSIS5, true);
20042
+ return truncateToWidth(`${indent}${prefix}${label}${ROW_GAP}${pathSegment}${marker}${value}`, width, ELLIPSIS6, true);
19188
20043
  }
19189
20044
  function targetConsoleColumns(width) {
19190
20045
  const safeWidth = Math.max(1, width);
@@ -19236,8 +20091,8 @@ function formatTargetConsoleHeader(width, indentWidth) {
19236
20091
  const indent = " ".repeat(Math.max(0, indentWidth));
19237
20092
  const available = Math.max(1, width - visibleWidth(indent) - 2);
19238
20093
  const labels = { health: "HEALTH", id: "TARGET", roles: "ROLES", runtime: "RUNTIME", latency: "LATENCY" };
19239
- const cells = targetConsoleColumns(available).map((column) => padAnsi(labels[column.key], column.width, ELLIPSIS5));
19240
- return truncateToWidth(`${indent} ${theme6.style("dim", cells.join(ROW_GAP), { bold: true })}`, width, ELLIPSIS5, true);
20094
+ const cells = targetConsoleColumns(available).map((column) => padAnsi(labels[column.key], column.width, ELLIPSIS6));
20095
+ return truncateToWidth(`${indent} ${theme6.style("dim", cells.join(ROW_GAP), { bold: true })}`, width, ELLIPSIS6, true);
19241
20096
  }
19242
20097
  function formatTargetConsoleRow(item, width, selected, indentWidth) {
19243
20098
  const console = item.targetConsole;
@@ -19248,13 +20103,13 @@ function formatTargetConsoleRow(item, width, selected, indentWidth) {
19248
20103
  const available = Math.max(1, width - visibleWidth(indent) - 2);
19249
20104
  const cells = targetConsoleColumns(available).map((column) => {
19250
20105
  const text = column.key === "health" ? console.health.text : console[column.key];
19251
- const padded = padAnsi(text, column.width, ELLIPSIS5);
20106
+ const padded = padAnsi(text, column.width, ELLIPSIS6);
19252
20107
  if (column.key === "health")
19253
20108
  return renderSettingValue([{ ...console.health, text: padded }], column.width, selected, false);
19254
20109
  if (column.key === "id" && selected) return theme6.style("accent", padded, { bold: true });
19255
20110
  return theme6.fg(selected ? "accent" : "muted", padded);
19256
20111
  });
19257
- return truncateToWidth(`${indent}${prefix}${cells.join(ROW_GAP)}`, width, ELLIPSIS5, true);
20112
+ return truncateToWidth(`${indent}${prefix}${cells.join(ROW_GAP)}`, width, ELLIPSIS6, true);
19258
20113
  }
19259
20114
  function renderSettingValue(segments, width, selected, readOnly) {
19260
20115
  const theme6 = clioTheme();
@@ -19274,13 +20129,13 @@ function renderSettingValue(segments, width, selected, readOnly) {
19274
20129
  visible = [
19275
20130
  ...prefixWidth > 0 ? [
19276
20131
  {
19277
- text: visibleWidth(prefix) <= prefixWidth ? prefix : truncateToWidth(prefix, prefixWidth, ELLIPSIS5, true),
20132
+ text: visibleWidth(prefix) <= prefixWidth ? prefix : truncateToWidth(prefix, prefixWidth, ELLIPSIS6, true),
19278
20133
  tone: "neutral"
19279
20134
  }
19280
20135
  ] : [],
19281
20136
  {
19282
20137
  ...semantic,
19283
- text: truncateToWidth(semantic.text, semanticWidth, ELLIPSIS5, true)
20138
+ text: truncateToWidth(semantic.text, semanticWidth, ELLIPSIS6, true)
19284
20139
  }
19285
20140
  ];
19286
20141
  }
@@ -19288,7 +20143,7 @@ function renderSettingValue(segments, width, selected, readOnly) {
19288
20143
  const rendered = [];
19289
20144
  for (const segment of visible) {
19290
20145
  if (remaining <= 0) break;
19291
- const text = truncateToWidth(segment.text, remaining, ELLIPSIS5);
20146
+ const text = truncateToWidth(segment.text, remaining, ELLIPSIS6);
19292
20147
  remaining -= visibleWidth(text);
19293
20148
  const token = segment.tone === "healthy" ? "success" : segment.tone === "degraded" ? "warning" : segment.tone === "unhealthy" ? "error" : segment.tone === "activity" ? "action" : segment.tone === "unknown" ? "dim" : readOnly ? "dim" : selected ? "accent" : "muted";
19294
20149
  rendered.push(theme6.fg(token, text));
@@ -19695,7 +20550,7 @@ var SettingsCenter = class {
19695
20550
  ...head,
19696
20551
  ...Array.from(
19697
20552
  { length: bodyRows },
19698
- (_, index) => `${padAnsi(left[index] ?? "", leftWidth, ELLIPSIS5)}${separator}${padAnsi(work[index] ?? "", workWidth, ELLIPSIS5)}`
20553
+ (_, index) => `${padAnsi(left[index] ?? "", leftWidth, ELLIPSIS6)}${separator}${padAnsi(work[index] ?? "", workWidth, ELLIPSIS6)}`
19699
20554
  )
19700
20555
  ];
19701
20556
  }
@@ -19708,9 +20563,9 @@ var SettingsCenter = class {
19708
20563
  ...Array.from(
19709
20564
  { length: bodyRows },
19710
20565
  (_, index) => [
19711
- padAnsi(left[index] ?? "", leftWidth, ELLIPSIS5),
19712
- padAnsi(center[index] ?? "", centerWidth, ELLIPSIS5),
19713
- padAnsi(right[index] ?? "", detailWidth, ELLIPSIS5)
20566
+ padAnsi(left[index] ?? "", leftWidth, ELLIPSIS6),
20567
+ padAnsi(center[index] ?? "", centerWidth, ELLIPSIS6),
20568
+ padAnsi(right[index] ?? "", detailWidth, ELLIPSIS6)
19714
20569
  ].join(separator)
19715
20570
  )
19716
20571
  ];
@@ -19719,14 +20574,14 @@ var SettingsCenter = class {
19719
20574
  filterEditorLines(width) {
19720
20575
  if (this.filterDraft === null) return [];
19721
20576
  const theme6 = clioTheme();
19722
- return [truncateToWidth(theme6.fg("accent", `Filter settings: ${this.filterDraft}_`), width, ELLIPSIS5, true)];
20577
+ return [truncateToWidth(theme6.fg("accent", `Filter settings: ${this.filterDraft}_`), width, ELLIPSIS6, true)];
19723
20578
  }
19724
20579
  emptyFilterLines(width, height) {
19725
20580
  const theme6 = clioTheme();
19726
20581
  return fixedLines(
19727
20582
  [
19728
- theme6.fg("muted", truncateToWidth(`No settings match \u201C${this.effectiveFilterQuery()}\u201D`, width, ELLIPSIS5, true)),
19729
- theme6.fg("dim", truncateToWidth("/ edit filter \xB7 empty Enter clears", width, ELLIPSIS5, true))
20583
+ theme6.fg("muted", truncateToWidth(`No settings match \u201C${this.effectiveFilterQuery()}\u201D`, width, ELLIPSIS6, true)),
20584
+ theme6.fg("dim", truncateToWidth("/ edit filter \xB7 empty Enter clears", width, ELLIPSIS6, true))
19730
20585
  ],
19731
20586
  width,
19732
20587
  height
@@ -19766,7 +20621,7 @@ var SettingsCenter = class {
19766
20621
  const kept = body.slice(0, bodyBudget);
19767
20622
  while (kept.length > 0 && (kept.at(-1) ?? "").trim().length === 0) kept.pop();
19768
20623
  const last = kept.at(-1);
19769
- const marked = body.length > kept.length && last !== void 0 ? [...kept.slice(0, -1), `${last}${ELLIPSIS5}`] : kept;
20624
+ const marked = body.length > kept.length && last !== void 0 ? [...kept.slice(0, -1), `${last}${ELLIPSIS6}`] : kept;
19770
20625
  const filler = Array.from({ length: Math.max(0, height - marked.length - note.length) }, () => "");
19771
20626
  return fixedLines([...marked, ...filler, ...note], width, height);
19772
20627
  }
@@ -19784,7 +20639,7 @@ var SettingsCenter = class {
19784
20639
  const right = this.renderRightLane(rightWidth, contentHeight);
19785
20640
  const body = Array.from(
19786
20641
  { length: contentHeight },
19787
- (_, index) => `${padAnsi(left[index] ?? "", leftWidth, ELLIPSIS5)}${separator}${padAnsi(right[index] ?? "", rightWidth, ELLIPSIS5)}`
20642
+ (_, index) => `${padAnsi(left[index] ?? "", leftWidth, ELLIPSIS6)}${separator}${padAnsi(right[index] ?? "", rightWidth, ELLIPSIS6)}`
19788
20643
  );
19789
20644
  return [...head, ...body, ...footer];
19790
20645
  }
@@ -19886,7 +20741,7 @@ var SettingsCenter = class {
19886
20741
  if (item) trail.push(item.label);
19887
20742
  }
19888
20743
  const query = this.filterQuery.trim().length > 0 ? theme6.fg("accent", ` /${this.filterQuery}`) : "";
19889
- return truncateToWidth(`${screenTitle(theme6, trail.join(" \u203A "))}${query}`, width, ELLIPSIS5, true);
20744
+ return truncateToWidth(`${screenTitle(theme6, trail.join(" \u203A "))}${query}`, width, ELLIPSIS6, true);
19890
20745
  }
19891
20746
  /**
19892
20747
  * Prose is the first thing a short terminal can spare. Sixteen body rows keep
@@ -19905,7 +20760,7 @@ var SettingsCenter = class {
19905
20760
  const wrapped = wrapTextWithAnsi(theme6.fg("muted", text), Math.max(1, width));
19906
20761
  const kept = wrapped.slice(0, budget);
19907
20762
  const last = kept.at(-1);
19908
- return wrapped.length > kept.length && last !== void 0 ? [...kept.slice(0, -1), `${last}${ELLIPSIS5}`] : kept;
20763
+ return wrapped.length > kept.length && last !== void 0 ? [...kept.slice(0, -1), `${last}${ELLIPSIS6}`] : kept;
19909
20764
  }
19910
20765
  renderSectionsPage(width, height) {
19911
20766
  const rows = this.sectionCatalogRows();
@@ -19960,7 +20815,7 @@ var SettingsCenter = class {
19960
20815
  if (item.help) contentLines.push(...wrapTextWithAnsi(theme6.fg("dim", item.help), safeWidth));
19961
20816
  const detail = this.footerDetail(item, theme6);
19962
20817
  if (detail) contentLines.push(...wrapTextWithAnsi(detail, safeWidth));
19963
- const note = theme6.fg("dim", truncateToWidth(this.footerScopeNote(item), safeWidth, ELLIPSIS5, true));
20818
+ const note = theme6.fg("dim", truncateToWidth(this.footerScopeNote(item), safeWidth, ELLIPSIS6, true));
19964
20819
  return this.assembleFooter([separator, breadcrumb], contentLines, note, maxFooterLines, safeWidth);
19965
20820
  }
19966
20821
  footerDetail(item, theme6) {
@@ -19991,7 +20846,7 @@ var SettingsCenter = class {
19991
20846
  * "where does this land" guidance visible even on short terminals.
19992
20847
  */
19993
20848
  assembleFooter(top, middle, note, maxFooterLines, width) {
19994
- const fit2 = (line) => truncateToWidth(line, width, ELLIPSIS5, true);
20849
+ const fit2 = (line) => truncateToWidth(line, width, ELLIPSIS6, true);
19995
20850
  let out;
19996
20851
  if (maxFooterLines <= top.length) {
19997
20852
  out = top.slice(0, maxFooterLines).map(fit2);
@@ -19999,7 +20854,7 @@ var SettingsCenter = class {
19999
20854
  const middleBudget = Math.max(0, maxFooterLines - top.length - 1);
20000
20855
  const kept = middle.slice(0, middleBudget);
20001
20856
  const last = kept.at(-1);
20002
- const marked = middle.length > kept.length && last !== void 0 ? [...kept.slice(0, -1), `${last}${ELLIPSIS5}`] : kept;
20857
+ const marked = middle.length > kept.length && last !== void 0 ? [...kept.slice(0, -1), `${last}${ELLIPSIS6}`] : kept;
20003
20858
  out = [...top.map(fit2), ...marked.map(fit2), fit2(note)];
20004
20859
  }
20005
20860
  while (out.length < maxFooterLines) out.push("");
@@ -21733,587 +22588,6 @@ function openTasksOverlay(tui, getBoard, options = {}) {
21733
22588
  };
21734
22589
  }
21735
22590
 
21736
- // src/interactive/view/view-overlay.ts
21737
- init_esm_shims();
21738
- var VIEW_OVERLAY_WIDTH = "100%";
21739
- var VIEW_OVERLAY_MAX_HEIGHT = "100%";
21740
- var VIEW_OVERLAY_MARGIN = { top: 0, right: 0, bottom: 0, left: 0 };
21741
- var LEFT_PANE_TARGET_WIDTH = 38;
21742
- var LEFT_PANE_MIN_WIDTH = 30;
21743
- var LEFT_PANE_MAX_WIDTH = 44;
21744
- var SEPARATOR = " \u2502 ";
21745
- var ELLIPSIS6 = "\u2026";
21746
- var TWO_PANE_MIN_WIDTH = LEFT_PANE_MIN_WIDTH + 3 + 24;
21747
- function viewUsesSinglePane(bodyWidth) {
21748
- return bodyWidth < TWO_PANE_MIN_WIDTH;
21749
- }
21750
- function artifactKey(artifact) {
21751
- return `${artifact.category}:${artifact.id}:${artifact.path ?? ""}`;
21752
- }
21753
- function categoryLabel(category) {
21754
- switch (category) {
21755
- case "accountability":
21756
- return "Accountability";
21757
- case "evidence":
21758
- return "Evidence bundles";
21759
- case "receipt":
21760
- return "Receipts";
21761
- case "dispatch":
21762
- return "Dispatch outputs";
21763
- case "task-ledger":
21764
- return "Task ledgers";
21765
- case "workspace":
21766
- return "Workspace outputs";
21767
- case "tool-output":
21768
- return "Tool outputs";
21769
- case "protected-artifact":
21770
- return "Protected artifacts";
21771
- case "compaction":
21772
- return "Compaction summaries";
21773
- case "prompt-manifest":
21774
- return "Prompt manifests";
21775
- case "audit":
21776
- return "Safety audit rows";
21777
- }
21778
- }
21779
- var VIEW_ARTIFACT_CATEGORY_SET = new Set(VIEW_ARTIFACT_CATEGORIES);
21780
- var BARE_CATEGORY_FILTERS = /* @__PURE__ */ new Set([
21781
- "task-ledger",
21782
- "workspace",
21783
- "protected-artifact",
21784
- "compaction",
21785
- "prompt-manifest",
21786
- "audit"
21787
- ]);
21788
- function isViewArtifactCategory(value) {
21789
- return VIEW_ARTIFACT_CATEGORY_SET.has(value);
21790
- }
21791
- function parseViewFilterQuery(query) {
21792
- const text = query.trim();
21793
- if (text.length === 0) return { kind: "text", text };
21794
- const colon = text.indexOf(":");
21795
- if (colon >= 0) {
21796
- const prefix = text.slice(0, colon).trim().toLowerCase();
21797
- if (isViewArtifactCategory(prefix)) {
21798
- return { kind: "category", category: prefix, value: text.slice(colon + 1).trim() };
21799
- }
21800
- return { kind: "text", text };
21801
- }
21802
- const bare = text.toLowerCase();
21803
- if (isViewArtifactCategory(bare) && BARE_CATEGORY_FILTERS.has(bare)) {
21804
- return { kind: "category", category: bare, value: "" };
21805
- }
21806
- return { kind: "text", text };
21807
- }
21808
- function isNonEmptySearchValue(value) {
21809
- return typeof value === "string" && value.trim().length > 0;
21810
- }
21811
- function broadFilterText(artifact) {
21812
- return `${artifact.id} ${artifact.category} ${artifact.title}`;
21813
- }
21814
- function artifactSearchValues(artifact) {
21815
- return [
21816
- artifact.id,
21817
- `${artifact.category}:${artifact.id}`,
21818
- artifact.category,
21819
- artifact.title,
21820
- artifact.description,
21821
- artifact.runId,
21822
- artifact.sessionId,
21823
- artifact.correlationId,
21824
- artifact.toolName,
21825
- artifact.path,
21826
- ...artifact.searchText ?? []
21827
- ].filter(isNonEmptySearchValue);
21828
- }
21829
- function categoryFilterText(artifact) {
21830
- return artifactSearchValues(artifact).join(" ");
21831
- }
21832
- function matchesExactArtifactValue(artifact, value) {
21833
- const exact = value.trim();
21834
- return exact.length > 0 && artifactSearchValues(artifact).some((candidate) => candidate === exact);
21835
- }
21836
- function formatArtifactSize(sizeBytes) {
21837
- if (sizeBytes === void 0) return "";
21838
- if (sizeBytes < 1024) return `${sizeBytes} B`;
21839
- if (sizeBytes < 1024 * 1024) return `${(sizeBytes / 1024).toFixed(1)} KB`;
21840
- return `${(sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
21841
- }
21842
- function formatRelativeTime(timestamp, now = Date.now()) {
21843
- if (!Number.isFinite(timestamp) || timestamp <= 0) return "";
21844
- const seconds = Math.max(0, Math.floor((now - timestamp) / 1e3));
21845
- if (seconds < 60) return `${seconds}s`;
21846
- const minutes = Math.floor(seconds / 60);
21847
- if (minutes < 60) return `${minutes}m`;
21848
- const hours = Math.floor(minutes / 60);
21849
- if (hours < 24) return `${hours}h`;
21850
- const days = Math.floor(hours / 24);
21851
- return `${days}d`;
21852
- }
21853
- function formatLocalTime2(timestamp) {
21854
- return !Number.isFinite(timestamp) || timestamp <= 0 ? "unknown time" : clockLocal(timestamp);
21855
- }
21856
- function filterViewArtifacts(artifacts, query) {
21857
- const parsed = parseViewFilterQuery(query);
21858
- if (parsed.kind === "category") {
21859
- const categoryArtifacts = artifacts.filter((artifact) => artifact.category === parsed.category);
21860
- if (parsed.value.length === 0) return categoryArtifacts;
21861
- return fuzzyFilter(categoryArtifacts, parsed.value, categoryFilterText);
21862
- }
21863
- if (parsed.text.length === 0) return [...artifacts];
21864
- return fuzzyFilter([...artifacts], parsed.text, broadFilterText);
21865
- }
21866
- function artifactsInCategoryOrder(artifacts) {
21867
- return VIEW_ARTIFACT_CATEGORIES.flatMap((category) => artifacts.filter((artifact) => artifact.category === category));
21868
- }
21869
- function initialViewSelection(artifacts, initialFilter = "") {
21870
- const filtered = filterViewArtifacts(artifacts, initialFilter);
21871
- const first = filtered[0];
21872
- if (!first) return 0;
21873
- let selected = first;
21874
- const parsed = parseViewFilterQuery(initialFilter);
21875
- if (parsed.kind === "category" && parsed.value.length > 0) {
21876
- const exactIndexes = filtered.flatMap(
21877
- (artifact, index) => matchesExactArtifactValue(artifact, parsed.value) ? [index] : []
21878
- );
21879
- if (exactIndexes.length === 1) selected = filtered[exactIndexes[0] ?? 0] ?? selected;
21880
- }
21881
- const exact = initialFilter.trim();
21882
- if (exact.length > 0) {
21883
- const exactArtifact = filtered.find(
21884
- (artifact) => artifact.id === exact || `${artifact.category}:${artifact.id}` === exact
21885
- );
21886
- if (exactArtifact) selected = exactArtifact;
21887
- }
21888
- return Math.max(0, artifactsInCategoryOrder(filtered).indexOf(selected));
21889
- }
21890
- function nextCategorySelection(artifacts, currentIndex, direction) {
21891
- if (artifacts.length === 0) return 0;
21892
- const safeCurrent = Math.max(0, Math.min(currentIndex, artifacts.length - 1));
21893
- const currentCategory = artifacts[safeCurrent]?.category;
21894
- const categoryIndex = currentCategory === void 0 ? -1 : VIEW_ARTIFACT_CATEGORIES.indexOf(currentCategory);
21895
- for (let offset = 1; offset < VIEW_ARTIFACT_CATEGORIES.length; offset += 1) {
21896
- const nextCategoryIndex = (categoryIndex + direction * offset + VIEW_ARTIFACT_CATEGORIES.length) % VIEW_ARTIFACT_CATEGORIES.length;
21897
- const nextCategory = VIEW_ARTIFACT_CATEGORIES[nextCategoryIndex];
21898
- const artifactIndex = artifacts.findIndex((artifact) => artifact.category === nextCategory);
21899
- if (artifactIndex >= 0) return artifactIndex;
21900
- }
21901
- return safeCurrent;
21902
- }
21903
- function groupedViewRows(artifacts) {
21904
- const rows = [];
21905
- for (const category of VIEW_ARTIFACT_CATEGORIES) {
21906
- const items = artifacts.filter((artifact) => artifact.category === category);
21907
- rows.push({ type: "group", category });
21908
- if (items.length === 0) {
21909
- rows.push({ type: "empty", category });
21910
- continue;
21911
- }
21912
- for (const item of items) {
21913
- rows.push({ type: "item", category, item, itemIndex: artifacts.indexOf(item) });
21914
- }
21915
- }
21916
- return rows;
21917
- }
21918
- function nextContentScrollOffset(current, totalLines, height, action) {
21919
- const maxOffset = Math.max(0, totalLines - Math.max(1, height));
21920
- const page = Math.max(1, height - 1);
21921
- const half = Math.max(1, Math.floor(height / 2));
21922
- let next = current;
21923
- switch (action) {
21924
- case "line-up":
21925
- next -= 1;
21926
- break;
21927
- case "line-down":
21928
- next += 1;
21929
- break;
21930
- case "page-up":
21931
- next -= page;
21932
- break;
21933
- case "page-down":
21934
- next += page;
21935
- break;
21936
- case "half-up":
21937
- next -= half;
21938
- break;
21939
- case "half-down":
21940
- next += half;
21941
- break;
21942
- case "top":
21943
- next = 0;
21944
- break;
21945
- case "bottom":
21946
- next = maxOffset;
21947
- break;
21948
- }
21949
- return Math.max(0, Math.min(next, maxOffset));
21950
- }
21951
- function verificationText(state) {
21952
- if (!state || state.status === "idle") return "";
21953
- if (state.status === "running") return `${GLYPH.running} verify running`;
21954
- if (state.status === "ok") return `${GLYPH.ok} verify ok ${state.detail}`;
21955
- return `${GLYPH.error} verify fail ${state.detail}`;
21956
- }
21957
- function buildArtifactHeader(artifact, verification, width) {
21958
- if (!artifact) return padAnsi(clioTheme().fg("muted", "No artifact selected"), width, ELLIPSIS6);
21959
- const theme6 = clioTheme();
21960
- const timestamp = formatLocalTime2(artifact.timestamp);
21961
- const size = formatArtifactSize(artifact.sizeBytes);
21962
- const parts = [theme6.fg("dim", artifact.category), theme6.fg("muted", artifact.id), theme6.fg("dim", timestamp)];
21963
- if (size.length > 0) parts.push(theme6.fg("dim", size));
21964
- const verify = verificationText(verification);
21965
- if (verify.length > 0) {
21966
- const token = verification?.status === "ok" ? "success" : verification?.status === "fail" ? "error" : "info";
21967
- parts.push(theme6.fg(token, verify));
21968
- }
21969
- return padAnsi(parts.join(" "), width, ELLIPSIS6);
21970
- }
21971
- function viewFooterHint(focus, canVerify, innerWidth) {
21972
- if (innerWidth !== void 0 && viewUsesSinglePane(innerWidth - 2)) {
21973
- const budget = innerWidth - 3;
21974
- const tiers = focus === "list" ? [
21975
- "[\u2191\u2193] select \xB7 [type] filter \xB7 [Tab] detail \xB7 [Esc] close",
21976
- "[\u2191\u2193] select \xB7 [Tab] detail \xB7 [Esc] close",
21977
- "[\u2191\u2193] select \xB7 [Tab] detail"
21978
- ] : ["[\u2191\u2193] scroll \xB7 [Tab] list \xB7 [Esc] close", "[\u2191\u2193] scroll \xB7 [Tab] list"];
21979
- return tiers.find((tier) => visibleWidth(tier) <= budget) ?? tiers.at(-1);
21980
- }
21981
- if (focus === "list") {
21982
- return buildHint([
21983
- { key: "\u2191\u2193", verb: "select" },
21984
- { key: "\u2190\u2192", verb: "category" },
21985
- { key: "type", verb: "filter" },
21986
- { key: "Tab", verb: "content" },
21987
- ...canVerify ? [{ key: "v", verb: "verify" }] : [],
21988
- { key: "o", verb: "path" }
21989
- ]);
21990
- }
21991
- return buildHint([
21992
- { key: "\u2191\u2193", verb: "scroll" },
21993
- { key: "\u2190\u2192", verb: "category" },
21994
- { key: "PgUp/PgDn", verb: "page" },
21995
- { key: "g/G", verb: "top/bottom" },
21996
- { key: "Tab", verb: "list" },
21997
- ...canVerify ? [{ key: "v", verb: "verify" }] : [],
21998
- { key: "o", verb: "path" }
21999
- ]);
22000
- }
22001
- var ViewOverlayView = class {
22002
- constructor(options) {
22003
- this.options = options;
22004
- this.filterText = options.initialFilter ?? "";
22005
- }
22006
- options;
22007
- artifacts = [];
22008
- filterText = "";
22009
- selectedIndex = 0;
22010
- listScrollOffset = 0;
22011
- contentScrollOffset = 0;
22012
- focus = "list";
22013
- loadingArtifacts = true;
22014
- artifactError = null;
22015
- content = null;
22016
- loadToken = 0;
22017
- lastContentWidth = 80;
22018
- lastContentBodyHeight = 1;
22019
- verifications = /* @__PURE__ */ new Map();
22020
- refresh() {
22021
- this.loadingArtifacts = true;
22022
- this.artifactError = null;
22023
- this.options.requestRender?.();
22024
- void (async () => {
22025
- try {
22026
- this.artifacts = await listViewArtifacts(this.options.providers);
22027
- this.selectedIndex = initialViewSelection(this.artifacts, this.filterText);
22028
- this.contentScrollOffset = 0;
22029
- this.content = null;
22030
- } catch (err) {
22031
- this.artifactError = err instanceof Error ? err.message : String(err);
22032
- this.artifacts = [];
22033
- } finally {
22034
- this.loadingArtifacts = false;
22035
- this.options.requestRender?.();
22036
- }
22037
- })();
22038
- }
22039
- getHint(innerWidth) {
22040
- return viewFooterHint(this.focus, !!this.selectedArtifact()?.verify, innerWidth);
22041
- }
22042
- filteredArtifacts() {
22043
- return artifactsInCategoryOrder(filterViewArtifacts(this.artifacts, this.filterText));
22044
- }
22045
- selectedArtifact() {
22046
- const filtered = this.filteredArtifacts();
22047
- if (this.selectedIndex >= filtered.length) this.selectedIndex = Math.max(0, filtered.length - 1);
22048
- return filtered[this.selectedIndex];
22049
- }
22050
- selectIndex(next) {
22051
- const filtered = this.filteredArtifacts();
22052
- if (filtered.length === 0) {
22053
- this.selectedIndex = 0;
22054
- this.content = null;
22055
- return;
22056
- }
22057
- this.selectedIndex = (next + filtered.length) % filtered.length;
22058
- this.contentScrollOffset = 0;
22059
- this.content = null;
22060
- this.options.requestRender?.();
22061
- }
22062
- selectCategory(direction) {
22063
- const filtered = this.filteredArtifacts();
22064
- if (filtered.length === 0) return;
22065
- this.selectIndex(nextCategorySelection(filtered, this.selectedIndex, direction));
22066
- }
22067
- selectInitialFilterMatch() {
22068
- this.selectIndex(initialViewSelection(this.artifacts, this.filterText));
22069
- }
22070
- ensureContentLoaded(artifact) {
22071
- if (!artifact) return;
22072
- const key = artifactKey(artifact);
22073
- if (this.content?.key === key) return;
22074
- const token = ++this.loadToken;
22075
- this.content = { key, status: "loading", lines: [clioTheme().fg("dim", "loading artifact\u2026")], format: "text" };
22076
- void (async () => {
22077
- try {
22078
- const loaded = await artifact.load();
22079
- if (token !== this.loadToken) return;
22080
- this.content = { key, status: "loaded", lines: loaded.lines, format: loaded.format };
22081
- } catch (err) {
22082
- if (token !== this.loadToken) return;
22083
- const message = err instanceof Error ? err.message : String(err);
22084
- this.content = {
22085
- key,
22086
- status: "error",
22087
- lines: [clioTheme().fg("error", `load failed: ${message}`)],
22088
- format: "text",
22089
- error: message
22090
- };
22091
- } finally {
22092
- if (token === this.loadToken) this.options.requestRender?.();
22093
- }
22094
- })();
22095
- }
22096
- renderedContentLines(width) {
22097
- const content = this.content;
22098
- if (!content) return [];
22099
- if (content.format !== "markdown") return content.lines;
22100
- if (content.renderedLines && content.renderWidth === width) return content.renderedLines;
22101
- const md = new Markdown(content.lines.join("\n"), 0, 0, markdownTheme(clioTheme()));
22102
- const rendered = md.render(width);
22103
- content.renderWidth = width;
22104
- content.renderedLines = rendered;
22105
- return rendered;
22106
- }
22107
- renderList(width, height) {
22108
- const theme6 = clioTheme();
22109
- const lines = [];
22110
- const filterLabel = this.focus === "list" ? theme6.fg("accent", "filter") : theme6.fg("dim", "filter");
22111
- const filterValue = this.filterText.length > 0 ? this.filterText : theme6.fg("dim", "(empty)");
22112
- lines.push(padAnsi(`${filterLabel}: ${filterValue}`, width, ELLIPSIS6));
22113
- if (this.loadingArtifacts) {
22114
- lines.push(padAnsi(theme6.fg("dim", "loading artifacts\u2026"), width, ELLIPSIS6));
22115
- return this.fixedLines(lines, width, height);
22116
- }
22117
- if (this.artifactError) {
22118
- lines.push(padAnsi(theme6.fg("error", this.artifactError), width, ELLIPSIS6));
22119
- return this.fixedLines(lines, width, height);
22120
- }
22121
- const filtered = this.filteredArtifacts();
22122
- const rows = groupedViewRows(filtered);
22123
- const selectedRow = rows.findIndex((row2) => row2.type === "item" && row2.itemIndex === this.selectedIndex);
22124
- const rowHeight = Math.max(1, height - 1);
22125
- if (selectedRow >= 0) {
22126
- if (selectedRow < this.listScrollOffset) this.listScrollOffset = selectedRow;
22127
- if (selectedRow >= this.listScrollOffset + rowHeight) this.listScrollOffset = selectedRow - rowHeight + 1;
22128
- }
22129
- this.listScrollOffset = Math.max(0, Math.min(this.listScrollOffset, Math.max(0, rows.length - rowHeight)));
22130
- for (const row2 of rows.slice(this.listScrollOffset, this.listScrollOffset + rowHeight)) {
22131
- if (row2.type === "group") {
22132
- const count2 = filtered.filter((artifact) => artifact.category === row2.category).length;
22133
- lines.push(padAnsi(theme6.fg("dim", `\u2500\u2500 ${categoryLabel(row2.category)} (${count2})`), width, ELLIPSIS6));
22134
- continue;
22135
- }
22136
- if (row2.type === "empty") {
22137
- lines.push(padAnsi(theme6.fg("dim", " (empty)"), width, ELLIPSIS6));
22138
- continue;
22139
- }
22140
- if (!row2.item) continue;
22141
- const selected = row2.itemIndex === this.selectedIndex;
22142
- const cursor = selected ? theme6.fg("accent", `${GLYPH.cursor} `) : " ";
22143
- const title = selected ? theme6.style("accent", row2.item.title, { bold: true }) : row2.item.title;
22144
- const metaParts = [formatRelativeTime(row2.item.timestamp), formatArtifactSize(row2.item.sizeBytes)].filter(Boolean);
22145
- const meta = metaParts.length > 0 ? theme6.fg("dim", metaParts.join(" ")) : "";
22146
- const available = Math.max(1, width - visibleWidth(cursor));
22147
- const metaWidth = visibleWidth(meta);
22148
- const titleWidth = Math.max(1, available - (metaWidth > 0 ? metaWidth + 1 : 0));
22149
- const clippedTitle = truncateToWidth(title, titleWidth, ELLIPSIS6, true);
22150
- const gap = " ".repeat(Math.max(1, available - visibleWidth(clippedTitle) - metaWidth));
22151
- lines.push(padAnsi(`${cursor}${clippedTitle}${metaWidth > 0 ? `${gap}${meta}` : ""}`, width, ELLIPSIS6));
22152
- }
22153
- return this.fixedLines(lines, width, height);
22154
- }
22155
- renderContent(width, height) {
22156
- const artifact = this.selectedArtifact();
22157
- this.ensureContentLoaded(artifact);
22158
- this.lastContentWidth = width;
22159
- const verification = artifact ? this.verifications.get(artifactKey(artifact)) : void 0;
22160
- const header = buildArtifactHeader(artifact, verification, width);
22161
- const bodyHeight = Math.max(0, height - 1);
22162
- this.lastContentBodyHeight = Math.max(1, bodyHeight);
22163
- const body = this.renderedContentLines(width);
22164
- const maxOffset = Math.max(0, body.length - Math.max(1, bodyHeight));
22165
- if (this.contentScrollOffset > maxOffset) this.contentScrollOffset = maxOffset;
22166
- const visible = body.slice(this.contentScrollOffset, this.contentScrollOffset + bodyHeight).map((line) => padAnsi(line, width, ELLIPSIS6));
22167
- const lines = [header, ...visible];
22168
- return this.fixedLines(lines, width, height);
22169
- }
22170
- fixedLines(lines, width, height) {
22171
- const out = lines.slice(0, height).map((line) => padAnsi(line, width, ELLIPSIS6));
22172
- while (out.length < height) out.push(" ".repeat(Math.max(0, width)));
22173
- return out;
22174
- }
22175
- render(width) {
22176
- const bodyHeight = Math.max(1, this.options.getBodyHeight());
22177
- if (viewUsesSinglePane(width)) {
22178
- return this.focus === "content" ? this.renderContent(width, bodyHeight) : this.renderList(width, bodyHeight);
22179
- }
22180
- const separatorWidth = visibleWidth(SEPARATOR);
22181
- const leftWidth = Math.min(
22182
- LEFT_PANE_MAX_WIDTH,
22183
- Math.max(LEFT_PANE_MIN_WIDTH, Math.min(LEFT_PANE_TARGET_WIDTH, Math.floor(width * 0.38)))
22184
- );
22185
- const rightWidth = Math.max(1, width - leftWidth - separatorWidth);
22186
- const list = this.renderList(leftWidth, bodyHeight);
22187
- const content = this.renderContent(rightWidth, bodyHeight);
22188
- const separator = clioTheme().fg("frame", SEPARATOR);
22189
- return Array.from({ length: bodyHeight }, (_, index) => `${list[index] ?? ""}${separator}${content[index] ?? ""}`);
22190
- }
22191
- handleInput(data) {
22192
- if (matchesKey(data, "tab") || matchesKey(data, "shift+tab")) {
22193
- this.focus = this.focus === "list" ? "content" : "list";
22194
- this.options.requestRender?.();
22195
- return;
22196
- }
22197
- if (matchesKey(data, "left")) {
22198
- this.selectCategory(-1);
22199
- return;
22200
- }
22201
- if (matchesKey(data, "right")) {
22202
- this.selectCategory(1);
22203
- return;
22204
- }
22205
- if (data === "v") {
22206
- this.verifySelected();
22207
- return;
22208
- }
22209
- if (data === "o") {
22210
- this.openPathNotice();
22211
- return;
22212
- }
22213
- if (matchesKey(data, "esc")) {
22214
- if (this.focus === "list" && this.filterText.length > 0) {
22215
- this.filterText = "";
22216
- this.selectInitialFilterMatch();
22217
- return;
22218
- }
22219
- this.options.onClose();
22220
- return;
22221
- }
22222
- if (this.focus === "content") {
22223
- if (this.handleContentInput(data)) return;
22224
- } else if (this.handleListInput(data)) {
22225
- return;
22226
- }
22227
- }
22228
- invalidate() {
22229
- }
22230
- handleListInput(data) {
22231
- const filtered = this.filteredArtifacts();
22232
- if (matchesKey(data, "up") || data === "k") {
22233
- if (filtered.length > 0) this.selectIndex(this.selectedIndex - 1);
22234
- return true;
22235
- }
22236
- if (matchesKey(data, "down") || data === "j") {
22237
- if (filtered.length > 0) this.selectIndex(this.selectedIndex + 1);
22238
- return true;
22239
- }
22240
- if (matchesKey(data, "backspace")) {
22241
- if (this.filterText.length > 0) {
22242
- this.filterText = this.filterText.slice(0, -1);
22243
- this.selectInitialFilterMatch();
22244
- }
22245
- return true;
22246
- }
22247
- if (data.length === 1 && data >= " " && data !== "\x7F") {
22248
- this.filterText += data;
22249
- this.selectInitialFilterMatch();
22250
- return true;
22251
- }
22252
- return false;
22253
- }
22254
- handleContentInput(data) {
22255
- const bodyHeight = this.lastContentBodyHeight;
22256
- const total = this.renderedContentLines(this.lastContentWidth).length;
22257
- let action = null;
22258
- if (matchesKey(data, "up") || data === "k") action = "line-up";
22259
- else if (matchesKey(data, "down") || data === "j") action = "line-down";
22260
- else if (data === "\x1B[5~") action = "page-up";
22261
- else if (data === "\x1B[6~") action = "page-down";
22262
- else if (data === "") action = "half-up";
22263
- else if (data === "") action = "half-down";
22264
- else if (data === "g") action = "top";
22265
- else if (data === "G") action = "bottom";
22266
- if (!action) return false;
22267
- this.contentScrollOffset = nextContentScrollOffset(this.contentScrollOffset, total, bodyHeight, action);
22268
- this.options.requestRender?.();
22269
- return true;
22270
- }
22271
- verifySelected() {
22272
- const artifact = this.selectedArtifact();
22273
- if (!artifact?.verify) return;
22274
- const key = artifactKey(artifact);
22275
- this.verifications.set(key, { status: "running" });
22276
- this.options.requestRender?.();
22277
- void artifact.verify().then((result) => {
22278
- this.verifications.set(
22279
- key,
22280
- result.ok ? { status: "ok", detail: result.detail } : { status: "fail", detail: result.detail }
22281
- );
22282
- }).catch((err) => {
22283
- const detail = err instanceof Error ? err.message : String(err);
22284
- this.verifications.set(key, { status: "fail", detail });
22285
- }).finally(() => this.options.requestRender?.());
22286
- }
22287
- openPathNotice() {
22288
- const artifact = this.selectedArtifact();
22289
- if (!artifact?.path) {
22290
- this.options.notice?.("warning", "view: selected artifact has no backing path", "view:path");
22291
- return;
22292
- }
22293
- this.options.notice?.("info", `artifact path: ${artifact.path}`, `view:path:${artifact.category}:${artifact.id}`);
22294
- }
22295
- };
22296
- function viewBodyHeight(tui) {
22297
- return Math.max(1, tui.terminal.rows - VIEW_OVERLAY_MARGIN.top - VIEW_OVERLAY_MARGIN.bottom - 2);
22298
- }
22299
- function openViewOverlay(tui, options) {
22300
- const view = new ViewOverlayView({
22301
- ...options,
22302
- getBodyHeight: () => viewBodyHeight(tui),
22303
- requestRender: () => tui.requestRender()
22304
- });
22305
- const handle = showClioOverlayFrame(tui, view, {
22306
- anchor: "top-left",
22307
- width: VIEW_OVERLAY_WIDTH,
22308
- maxHeight: VIEW_OVERLAY_MAX_HEIGHT,
22309
- margin: VIEW_OVERLAY_MARGIN,
22310
- title: "View",
22311
- footerHint: (innerWidth) => view.getHint(innerWidth)
22312
- });
22313
- view.refresh();
22314
- return handle;
22315
- }
22316
-
22317
22591
  // src/interactive/overlay-general-openers.ts
22318
22592
  function createOverlayGeneralOpeners(deps) {
22319
22593
  const openCostOverlayFactory = deps.openCostOverlay ?? openCostOverlay;
@@ -25776,15 +26050,27 @@ function createEditorSubmitController(deps) {
25776
26050
  totalBytes: 0,
25777
26051
  excludeFromContext: parsed.excludeFromContext
25778
26052
  };
26053
+ let fold;
26054
+ const bashPresentation = toolPresentationPolicy(ToolNames.Bash, void 0);
26055
+ const policyFold = (detail) => execution.running ? policyRunningToolFold(detail) : policyToolFold(detail, bashPresentation);
25779
26056
  deps.chatPanel.appendReplayBlock(
25780
- (width) => renderBashTranscriptExecution(
26057
+ (width, detail) => renderBashTranscriptExecution(
25781
26058
  {
25782
26059
  ...execution,
26060
+ folded: resolveFold(fold, policyFold(detail)) === "folded",
25783
26061
  ...execution.running ? { elapsedMs: Math.max(0, performance.now() - startedAt) } : {}
25784
26062
  },
25785
26063
  width
25786
26064
  ),
25787
- () => execution.running
26065
+ () => execution.running,
26066
+ {
26067
+ policyFold,
26068
+ fold: () => fold,
26069
+ setFold: (next) => {
26070
+ fold = next;
26071
+ deps.ui.requestRender();
26072
+ }
26073
+ }
25788
26074
  );
25789
26075
  deps.ui.requestRender();
25790
26076
  let settlement;
@@ -26401,6 +26687,9 @@ function createInteractiveEventProjection(deps) {
26401
26687
  let statusInlineFrame = 0;
26402
26688
  primaryUnsubscribers.push(
26403
26689
  deps.status.subscribe((status) => {
26690
+ deps.setLiveReasoning?.(
26691
+ status.phase === "idle" || status.phase === "ended" ? null : reasoningFromTally(status.runTally)
26692
+ );
26404
26693
  if (status.phase === "idle") {
26405
26694
  deps.setStatusLine(null);
26406
26695
  } else if (status.phase === "ended") {
@@ -27418,33 +27707,30 @@ function extractAssistantThinking(message) {
27418
27707
  (item) => item?.type === "thinking" && typeof item.thinking === "string"
27419
27708
  ).map((item) => item.thinking).join("");
27420
27709
  }
27421
- function finiteToken(value) {
27422
- return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
27423
- }
27424
27710
  function assistantUsage(message) {
27425
27711
  if (!message || typeof message !== "object" || message.role !== "assistant") return void 0;
27426
- const record2 = message;
27427
- const usage = record2.usage && typeof record2.usage === "object" ? record2.usage : void 0;
27428
- const thinking = extractAssistantThinking(message);
27429
- const reportedReasoning = usage ? extractReasoningTokens(usage) : null;
27430
- const estimatedReasoning = reportedReasoning === null ? estimateReasoningTextTokens(thinking) : null;
27431
- const inputTokens = finiteToken(usage?.input);
27432
- const outputTokens = finiteToken(usage?.output);
27433
- const cacheReadTokens = finiteToken(usage?.cacheRead);
27434
- const cacheWriteTokens = finiteToken(usage?.cacheWrite);
27435
- if (inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens === 0 && reportedReasoning === null && estimatedReasoning === null) {
27712
+ const tally = foldMessageIntoRunTally(emptyRunTally(), message);
27713
+ const reasoning = reasoningFromTally(tally);
27714
+ if (tally.inputTokens + tally.outputTokens + tally.cacheReadTokens + tally.cacheWriteTokens === 0 && reasoning.provenance === "unmeasured") {
27436
27715
  return void 0;
27437
27716
  }
27438
- const turnUsage = { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, modelCalls: 1 };
27439
- if (reportedReasoning !== null) {
27440
- turnUsage.reasoningTokens = reportedReasoning;
27441
- turnUsage.reasoningTokenProvenance = "provider";
27442
- } else if (estimatedReasoning !== null) {
27443
- turnUsage.reasoningTokens = estimatedReasoning;
27444
- turnUsage.reasoningTokenProvenance = "estimated";
27717
+ const turnUsage = {
27718
+ inputTokens: tally.inputTokens,
27719
+ outputTokens: tally.outputTokens,
27720
+ cacheReadTokens: tally.cacheReadTokens,
27721
+ cacheWriteTokens: tally.cacheWriteTokens,
27722
+ modelCalls: 1
27723
+ };
27724
+ if (reasoning.provenance !== "unmeasured") {
27725
+ turnUsage.reasoningTokens = reasoning.tokens;
27726
+ turnUsage.reasoningTokenProvenance = reasoning.provenance;
27445
27727
  }
27446
27728
  return turnUsage;
27447
27729
  }
27730
+ function reasoningFromTurnUsage(usage) {
27731
+ if (!usage || usage.reasoningTokenProvenance === void 0) return UNMEASURED_REASONING;
27732
+ return { tokens: Math.max(0, usage.reasoningTokens ?? 0), provenance: usage.reasoningTokenProvenance };
27733
+ }
27448
27734
  function aggregateAssistantUsage(messages) {
27449
27735
  if (!Array.isArray(messages)) return void 0;
27450
27736
  const total = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
@@ -27497,6 +27783,19 @@ function scopeTerminalErrorAfterSuccessfulTool(entry, terminalError) {
27497
27783
  );
27498
27784
  return `[error] ${modelFailure}${detachedDispatchSucceeded ? "; detached runs continue" : ""}`;
27499
27785
  }
27786
+ function hasThinking(entry) {
27787
+ return entry.segments.some((seg) => seg.kind === "thinking" && seg.text.length > 0);
27788
+ }
27789
+ function openThinkingSegment(entry) {
27790
+ const tail = entry.segments[entry.segments.length - 1];
27791
+ return tail?.kind === "thinking" && !tail.finalized ? tail : null;
27792
+ }
27793
+ function lastThinkingIndex(entry) {
27794
+ for (let index = entry.segments.length - 1; index >= 0; index -= 1) {
27795
+ if (entry.segments[index]?.kind === "thinking") return index;
27796
+ }
27797
+ return -1;
27798
+ }
27500
27799
  function hasVisibleOutput(entry) {
27501
27800
  for (const seg of entry.segments) {
27502
27801
  if (seg.kind === "tool") return true;
@@ -27578,31 +27877,29 @@ function hangProseLines(lines, firstPrefix) {
27578
27877
  }
27579
27878
  var THINKING_HIDDEN_LABEL = `Thinking${GLYPH.ellipsis}`;
27580
27879
  var THINKING_LINE_LIMIT = 12;
27581
- var REASONING_CHARS_PER_TOKEN = 4;
27582
- function estimateThinkingTokens(thinking) {
27583
- return estimateReasoningTextTokens(thinking) ?? Math.max(1, Math.round(thinking.length / REASONING_CHARS_PER_TOKEN));
27880
+ function dimLine(text, width) {
27881
+ return `${DIM}${truncateToWidth(text, Math.max(1, width), GLYPH.ellipsis, false)}${RESET}`;
27584
27882
  }
27585
- function reasoningDisplay(usage, thinking) {
27586
- if (usage?.reasoningTokens !== void 0) {
27587
- return {
27588
- tokens: usage.reasoningTokens,
27589
- marker: usage.reasoningTokenProvenance === "provider" ? "provider-reported" : "mixed/estimated"
27590
- };
27591
- }
27592
- return { tokens: estimateThinkingTokens(thinking), marker: "estimated" };
27883
+ function renderSettledThinkingMarker(view, width) {
27884
+ const chip = formatReasoningChip(view, compactReasoningTokens);
27885
+ return dimLine(
27886
+ chip === null ? THINKING_HIDDEN_LABEL : `${THINKING_HIDDEN_LABEL} \xB7 ${chip} ${formatReasoningLabel(view)}`,
27887
+ width
27888
+ );
27889
+ }
27890
+ function renderLiveReasoningLine(view, elapsedMs, width) {
27891
+ const chip = formatReasoningChip(view, compactReasoningTokens);
27892
+ const head = chip === null ? THINKING_HIDDEN_LABEL : `Thinking \xB7 ${chip} ${formatReasoningLabel(view)}`;
27893
+ const seconds = elapsedMs === void 0 ? 0 : Math.floor(Math.max(0, elapsedMs) / 1e3);
27894
+ return dimLine(seconds > 0 ? `${head} \xB7 ${seconds}s` : head, width);
27593
27895
  }
27594
- function renderThinkingLines(thinking, expanded, width, streaming, usage) {
27896
+ function renderThinkingRail(thinking, width, streaming, unbounded = false) {
27595
27897
  if (thinking.length === 0) return [];
27596
- const dimWrap = (s) => `${DIM}${s}${RESET}`;
27597
- if (!expanded) {
27598
- const lineBudget = Math.max(1, width);
27599
- const display = reasoningDisplay(usage, thinking);
27600
- const label = streaming ? `Thinking (${display.tokens} tokens)${display.marker === "provider-reported" ? "" : " \u2248 estimated"}\u2026` : THINKING_HIDDEN_LABEL;
27601
- return [dimWrap(truncateToWidth(label, lineBudget, GLYPH.ellipsis, false))];
27602
- }
27603
27898
  const splitLines = thinking.split("\n");
27604
27899
  let visible;
27605
- if (streaming) {
27900
+ if (unbounded) {
27901
+ visible = splitLines;
27902
+ } else if (streaming) {
27606
27903
  if (splitLines.length > THINKING_LINE_LIMIT) {
27607
27904
  const hiddenCount = splitLines.length - THINKING_LINE_LIMIT;
27608
27905
  visible = [`\u2026 ${hiddenCount} earlier lines hidden`, ...splitLines.slice(-THINKING_LINE_LIMIT)];
@@ -27622,21 +27919,22 @@ function renderThinkingLines(thinking, expanded, width, streaming, usage) {
27622
27919
  }
27623
27920
  return out;
27624
27921
  }
27625
- function renderTurnUsageLine(usage, width, verbosity) {
27626
- if (verbosity === "minimal") return [];
27627
- if (verbosity === "default") {
27628
- const receipt = truncateToWidth(
27922
+ function renderTurnUsageLine(usage, width, receipt) {
27923
+ if (receipt === "none") return [];
27924
+ if (receipt === "compact") {
27925
+ const receipt2 = truncateToWidth(
27629
27926
  ` turn \xB7 in ${usage.inputTokens} \xB7 out ${usage.outputTokens}`,
27630
27927
  width,
27631
27928
  GLYPH.ellipsis,
27632
27929
  false
27633
27930
  );
27634
- return [`${DIM}${receipt}${RESET}`];
27931
+ return [`${DIM}${receipt2}${RESET}`];
27635
27932
  }
27636
27933
  const calls = usage.modelCalls !== void 0 && usage.modelCalls > 1 ? ` over ${usage.modelCalls} calls` : "";
27637
- const reason = usage.reasoningTokens !== void 0 && usage.reasoningTokens > 0 ? ` reasoning ${usage.reasoningTokenProvenance === "provider" ? `${usage.reasoningTokens} provider` : `\u2248${usage.reasoningTokens} estimated`}` : "";
27934
+ const view = reasoningFromTurnUsage(usage);
27935
+ const reason = view.tokens > 0 && view.provenance !== "unmeasured" ? ` reasoning ${view.provenance === "provider" ? "" : "\u2248"}${view.tokens} ${formatReasoningLabel(view)}` : "";
27638
27936
  const cache2 = usage.cacheReadTokens > 0 || usage.cacheWriteTokens > 0 ? ` cache ${usage.cacheReadTokens}/${usage.cacheWriteTokens}` : "";
27639
- const caveat = usage.reasoningTokens !== void 0 && usage.reasoningTokens > 0 ? " \xB7 reasoning text is a UI excerpt, not a verification" : "";
27937
+ const caveat = view.tokens > 0 ? " \xB7 reasoning text is a UI excerpt, not a verification" : "";
27640
27938
  return wrapTextWithAnsi(
27641
27939
  `${DIM} turn \xB7 in ${usage.inputTokens}${calls} \xB7 out ${usage.outputTokens}${cache2}${reason}${caveat}${RESET}`,
27642
27940
  width
@@ -27649,9 +27947,17 @@ function styleStatusVerb(text, toneHint) {
27649
27947
  if (toneHint === "ok") return `${GREEN_OK}${text}${RESET}`;
27650
27948
  return `${DIM}${text}${RESET}`;
27651
27949
  }
27652
- function renderToolSegmentLines(seg, width, expandKey, latestHintToolId, nowMs, unboundedToolBodies, verbosity, liveToolOutput) {
27950
+ function policySegmentFold(seg, detail) {
27951
+ if (!seg.finished) return policyRunningToolFold(detail);
27952
+ if (seg.isError && detail.errors === "body") return "expanded";
27953
+ return policyToolFold(detail, toolPresentationPolicy(seg.name, seg.args));
27954
+ }
27955
+ function toolSegmentExpanded(seg, detail) {
27956
+ return resolveFold(seg.fold, policySegmentFold(seg, detail)) === "expanded";
27957
+ }
27958
+ function renderToolSegmentLines(seg, width, expandKey, latestHintToolId, nowMs, unboundedToolBodies, detail, liveToolOutput) {
27653
27959
  const hintKey = seg.id === latestHintToolId ? expandKey : void 0;
27654
- const expanded = verbosity === "verbose" || verbosity !== "minimal" && seg.expanded;
27960
+ const expanded = toolSegmentExpanded(seg, detail);
27655
27961
  const elapsedMs = seg.startedAtMs !== void 0 ? Math.max(0, rawDurationMs(seg.startedAtMs, nowMs)) : void 0;
27656
27962
  const phase = seg.executionStarted ? "running" : seg.argsComplete ? "ready" : "forming";
27657
27963
  if (!seg.finished && seg.awaitingApproval === true) {
@@ -27675,7 +27981,11 @@ function renderToolSegmentLines(seg, width, expandKey, latestHintToolId, nowMs,
27675
27981
  blockReason: seg.blockReason
27676
27982
  } : { toolCallId: seg.id, toolName: seg.name, args: seg.args, elapsedMs, phase },
27677
27983
  width,
27678
- hintKey
27984
+ hintKey,
27985
+ {
27986
+ diffStyle: seg.replayed === true ? "plain" : "color",
27987
+ foldedExtras: detail.toolBody === "folded" ? "none" : "per-tool"
27988
+ }
27679
27989
  );
27680
27990
  }
27681
27991
  if (!seg.finished) {
@@ -27705,14 +28015,15 @@ function renderToolSegmentLines(seg, width, expandKey, latestHintToolId, nowMs,
27705
28015
  { unbounded: unboundedToolBodies, diffStyle: seg.replayed === true ? "plain" : "color" }
27706
28016
  );
27707
28017
  }
27708
- function workerEntryFolded(entry, verbosity) {
27709
- if (verbosity === "verbose") return false;
27710
- if (verbosity === "minimal") return true;
27711
- return entry.folded;
28018
+ function workerEntryFolded(entry, detail) {
28019
+ return resolveFold(entry.fold, policyWorkerFold(detail, workerAskedByModel(entry.state))) === "folded";
28020
+ }
28021
+ function thinkingExpanded(segment, detail) {
28022
+ return resolveFold(segment.fold, policyThinkingFold(detail)) === "expanded";
27712
28023
  }
27713
- function renderEntryLines(entry, width, expandKey, latestHintToolId, latestFoldedWorkerId, nowMs, unboundedToolBodies, verbosity, liveToolOutput) {
28024
+ function renderEntryLines(entry, width, expandKey, latestHintToolId, latestFoldedWorkerId, nowMs, unboundedToolBodies, detail, liveToolOutput, liveReasoning) {
27714
28025
  if (entry.role === "replayBlock") {
27715
- return entry.renderBlock(width);
28026
+ return entry.renderBlock(width, detail);
27716
28027
  }
27717
28028
  if (entry.role === "user") {
27718
28029
  const contentWidth = Math.max(1, width - PROSE_GUTTER_WIDTH);
@@ -27727,30 +28038,45 @@ function renderEntryLines(entry, width, expandKey, latestHintToolId, latestFolde
27727
28038
  }
27728
28039
  if (entry.role === "worker") {
27729
28040
  return renderWorkerEntryLines(entry.state, width, {
27730
- folded: workerEntryFolded(entry, verbosity),
28041
+ folded: workerEntryFolded(entry, detail),
27731
28042
  ...expandKey !== void 0 && entry.state.assignmentId === latestFoldedWorkerId ? { expandKey } : {},
27732
28043
  unbounded: unboundedToolBodies
27733
28044
  });
27734
28045
  }
27735
- if (!entry.pending && entry.thinking.length === 0 && entry.turnUsage === void 0 && !hasVisibleOutput(entry) && entry.segments.length === 0) {
28046
+ if (!entry.pending && entry.turnUsage === void 0 && !hasVisibleOutput(entry) && entry.segments.length === 0) {
27736
28047
  return [];
27737
28048
  }
27738
28049
  const lines = [];
27739
- if (entry.thinking.length > 0) {
27740
- lines.push(
27741
- ...renderThinkingLines(
27742
- entry.thinking,
27743
- verbosity === "verbose" || verbosity !== "minimal" && entry.expandedThinking === true,
27744
- width,
27745
- entry.pending,
27746
- entry.turnUsage
27747
- )
27748
- );
27749
- }
28050
+ const chipIndex = entry.pending ? -1 : lastThinkingIndex(entry);
27750
28051
  const clioPrefix = entry.isError ? CLIO_PREFIX_ERROR : CLIO_PREFIX;
27751
28052
  const proseWidth = Math.max(1, width - PROSE_GUTTER_WIDTH);
27752
28053
  let labeled = false;
27753
- for (const seg of entry.segments) {
28054
+ let liveIndicatorShown = false;
28055
+ let latestThinkingExpanded = policyThinkingFold(detail) === "expanded";
28056
+ for (let segIndex = 0; segIndex < entry.segments.length; segIndex += 1) {
28057
+ const seg = entry.segments[segIndex];
28058
+ if (seg === void 0) continue;
28059
+ if (seg.kind === "thinking") {
28060
+ if (seg.text.length === 0) continue;
28061
+ const thinkingExpandedNow = thinkingExpanded(seg, detail);
28062
+ latestThinkingExpanded = thinkingExpandedNow;
28063
+ const live = entry.pending && !seg.finalized;
28064
+ if (thinkingExpandedNow) lines.push(...renderThinkingRail(seg.text, width, live, unboundedToolBodies));
28065
+ if (live) {
28066
+ liveIndicatorShown = true;
28067
+ lines.push(
28068
+ detail.thinking === "marker" && !thinkingExpandedNow ? dimLine(THINKING_HIDDEN_LABEL, width) : renderLiveReasoningLine(
28069
+ liveReasoning,
28070
+ seg.startedAtMs === void 0 ? void 0 : Math.max(0, nowMs - seg.startedAtMs),
28071
+ width
28072
+ )
28073
+ );
28074
+ } else if (!thinkingExpandedNow) {
28075
+ const view = segIndex === chipIndex ? reasoningFromTurnUsage(entry.turnUsage) : UNMEASURED_REASONING;
28076
+ lines.push(renderSettledThinkingMarker(view, width));
28077
+ }
28078
+ continue;
28079
+ }
27754
28080
  if (seg.kind === "tool") {
27755
28081
  lines.push(
27756
28082
  ...renderToolSegmentLines(
@@ -27760,7 +28086,7 @@ function renderEntryLines(entry, width, expandKey, latestHintToolId, latestFolde
27760
28086
  latestHintToolId,
27761
28087
  nowMs,
27762
28088
  unboundedToolBodies,
27763
- verbosity,
28089
+ detail,
27764
28090
  liveToolOutput
27765
28091
  )
27766
28092
  );
@@ -27786,16 +28112,16 @@ function renderEntryLines(entry, width, expandKey, latestHintToolId, latestFolde
27786
28112
  lines.push(...hangProseLines(rendered));
27787
28113
  }
27788
28114
  }
27789
- if (entry.turnUsage && !entry.pending) lines.push(...renderTurnUsageLine(entry.turnUsage, width, verbosity));
27790
- const shouldRenderStatus = entry.pending && entry.statusLine !== null && entry.statusLine !== void 0 && !(entry.statusLine.phase === "writing" && hasStreamingText(entry)) && !(entry.statusLine.phase === "thinking" && entry.thinking.length > 0);
27791
- if (!labeled && !hasVisibleOutput(entry)) {
27792
- lines.push(clioPrefix.trimEnd());
27793
- if (shouldRenderStatus) {
27794
- lines.push(
27795
- `${STATUS_INDENT}${styleStatusVerb(entry.statusLine?.verb ?? "", entry.statusLine?.toneHint ?? "muted")}`
27796
- );
27797
- }
27798
- } else if (shouldRenderStatus) {
28115
+ if (entry.pending && hasThinking(entry) && !liveIndicatorShown) {
28116
+ lines.push(
28117
+ detail.thinking === "marker" && !latestThinkingExpanded ? dimLine(THINKING_HIDDEN_LABEL, width) : renderLiveReasoningLine(liveReasoning, void 0, width)
28118
+ );
28119
+ liveIndicatorShown = true;
28120
+ }
28121
+ if (entry.turnUsage && !entry.pending) lines.push(...renderTurnUsageLine(entry.turnUsage, width, detail.receipt));
28122
+ const shouldRenderStatus = entry.pending && entry.statusLine !== null && entry.statusLine !== void 0 && !(entry.statusLine.phase === "writing" && hasStreamingText(entry)) && !(entry.statusLine.phase === "thinking" && liveIndicatorShown);
28123
+ if (!labeled && !hasVisibleOutput(entry)) lines.push(clioPrefix.trimEnd());
28124
+ if (shouldRenderStatus) {
27799
28125
  lines.push(`${STATUS_INDENT}${styleStatusVerb(entry.statusLine?.verb ?? "", entry.statusLine?.toneHint ?? "muted")}`);
27800
28126
  }
27801
28127
  return lines;
@@ -27807,8 +28133,9 @@ function createChatPanel(options = {}) {
27807
28133
  let cachedWidth;
27808
28134
  let cachedLines = [];
27809
28135
  let cachedExpandKey;
27810
- let cachedVerbosity;
28136
+ let cachedDetail;
27811
28137
  let cachedLiveToolOutput;
28138
+ let lastVerbosity = null;
27812
28139
  let cachedTick = 0;
27813
28140
  let renderedRunningTool = false;
27814
28141
  const entryRenderCache = /* @__PURE__ */ new Map();
@@ -27816,8 +28143,8 @@ function createChatPanel(options = {}) {
27816
28143
  const MAX_ENTRY_RENDER_CACHE = 4096;
27817
28144
  const entryCacheCapacity = () => Math.max(MIN_ENTRY_RENDER_CACHE, Math.min(MAX_ENTRY_RENDER_CACHE, transcript.length));
27818
28145
  let frozen = null;
27819
- let thinkingExpanded = false;
27820
28146
  let liveToolOutput = true;
28147
+ let liveReasoning = UNMEASURED_REASONING;
27821
28148
  const unboundedToolBodies = options.unboundedToolBodies === true;
27822
28149
  const markDirty = () => {
27823
28150
  dirty = true;
@@ -27839,6 +28166,25 @@ function createChatPanel(options = {}) {
27839
28166
  return key;
27840
28167
  };
27841
28168
  const now = () => options.now?.() ?? Date.now();
28169
+ const currentDetail = () => transcriptDetail(options.getOutputVerbosity?.());
28170
+ const dropFoldOverrides = () => {
28171
+ for (const entry of transcript) {
28172
+ if (entry.role === "replayBlock") {
28173
+ entry.fold?.setFold(void 0);
28174
+ continue;
28175
+ }
28176
+ if (entry.role === "worker") {
28177
+ entry.fold = void 0;
28178
+ continue;
28179
+ }
28180
+ if (entry.role !== "assistant") continue;
28181
+ for (const seg of entry.segments) {
28182
+ if (seg.kind === "tool" || seg.kind === "thinking") seg.fold = void 0;
28183
+ }
28184
+ }
28185
+ clearRenderCaches();
28186
+ markDirty();
28187
+ };
27842
28188
  const settleUnfinishedToolSegment = (seg, settlement = "orphaned") => {
27843
28189
  if (seg.finished) return;
27844
28190
  seg.finished = true;
@@ -27877,17 +28223,32 @@ function createChatPanel(options = {}) {
27877
28223
  const entry = {
27878
28224
  role: "assistant",
27879
28225
  segments: [],
27880
- thinking: "",
27881
- expandedThinking: thinkingExpanded,
27882
28226
  pending: false,
27883
28227
  isError: false
27884
28228
  };
27885
28229
  transcript.push(entry);
27886
28230
  return entry;
27887
28231
  };
28232
+ const closeOpenThinking = (entry) => {
28233
+ const open = openThinkingSegment(entry);
28234
+ if (open === null) return;
28235
+ open.finalized = true;
28236
+ invalidateEntryCache(entry);
28237
+ };
28238
+ const appendThinkingDelta = (entry, delta) => {
28239
+ if (delta.length === 0) return;
28240
+ invalidateEntryCache(entry);
28241
+ const open = openThinkingSegment(entry);
28242
+ if (open !== null) {
28243
+ open.text += delta;
28244
+ return;
28245
+ }
28246
+ entry.segments.push({ kind: "thinking", text: delta, finalized: false, startedAtMs: now() });
28247
+ };
27888
28248
  const appendTextDelta = (entry, delta) => {
27889
28249
  if (delta.length === 0) return;
27890
28250
  invalidateEntryCache(entry);
28251
+ closeOpenThinking(entry);
27891
28252
  const tail = entry.segments[entry.segments.length - 1];
27892
28253
  if (tail && tail.kind === "text" && !tail.finalized) {
27893
28254
  tail.text += delta;
@@ -27897,12 +28258,33 @@ function createChatPanel(options = {}) {
27897
28258
  };
27898
28259
  const canonicalizeMessageText = (entry, text, replaceTail = false) => {
27899
28260
  if (text.length === 0) return;
27900
- const tail = entry.segments[entry.segments.length - 1];
27901
- if (tail?.kind === "text" && !tail.finalized && (replaceTail || text.startsWith(tail.text))) {
27902
- tail.text = text;
27903
- tail.finalized = true;
27904
- delete tail.wrapCache;
27905
- if (tail.md) tail.md.setText(text);
28261
+ closeOpenThinking(entry);
28262
+ const messageStart = Math.min(entry.messageStartSegmentIndex ?? 0, entry.segments.length);
28263
+ const streamed = [];
28264
+ for (let index = messageStart; index < entry.segments.length; index += 1) {
28265
+ const segment = entry.segments[index];
28266
+ if (segment?.kind === "text" && !segment.finalized) streamed.push(segment);
28267
+ }
28268
+ const finalize = (segment, value) => {
28269
+ segment.text = value;
28270
+ segment.finalized = true;
28271
+ delete segment.wrapCache;
28272
+ if (segment.md) segment.md.setText(value);
28273
+ };
28274
+ if (replaceTail && streamed.length > 0) {
28275
+ const [first, ...rest] = streamed;
28276
+ if (first) finalize(first, text);
28277
+ for (const dead of rest) entry.segments.splice(entry.segments.indexOf(dead), 1);
28278
+ return;
28279
+ }
28280
+ if (streamed.length === 1 && streamed[0] !== void 0) {
28281
+ const only = streamed[0];
28282
+ if (text.startsWith(only.text)) {
28283
+ finalize(only, text);
28284
+ return;
28285
+ }
28286
+ } else if (streamed.length > 1) {
28287
+ for (const segment of streamed) finalize(segment, segment.text);
27906
28288
  return;
27907
28289
  }
27908
28290
  entry.segments.push({ kind: "text", text, finalized: true });
@@ -27912,18 +28294,21 @@ function createChatPanel(options = {}) {
27912
28294
  if (tail?.kind === "error" && tail.text === text) return;
27913
28295
  entry.segments.push({ kind: "error", text });
27914
28296
  };
27915
- const expandHintOwner = () => {
28297
+ const expandHintOwner = (detail) => {
27916
28298
  let workerMayOwn = true;
27917
28299
  for (let entryIndex = transcript.length - 1; entryIndex >= 0; entryIndex -= 1) {
27918
28300
  const entry = transcript[entryIndex];
27919
28301
  if (entry?.role === "worker") {
27920
- return { toolId: null, workerId: workerMayOwn && entry.folded ? entry.state.assignmentId : null };
28302
+ return {
28303
+ toolId: null,
28304
+ workerId: workerMayOwn && workerEntryFolded(entry, detail) ? entry.state.assignmentId : null
28305
+ };
27921
28306
  }
27922
28307
  if (entry?.role !== "assistant") continue;
27923
28308
  for (let segIndex = entry.segments.length - 1; segIndex >= 0; segIndex -= 1) {
27924
28309
  const seg = entry.segments[segIndex];
27925
28310
  if (seg?.kind !== "tool") continue;
27926
- if (seg.finished && !seg.expanded) return { toolId: seg.id, workerId: null };
28311
+ if (seg.finished && !toolSegmentExpanded(seg, detail)) return { toolId: seg.id, workerId: null };
27927
28312
  workerMayOwn = false;
27928
28313
  }
27929
28314
  }
@@ -27941,22 +28326,27 @@ function createChatPanel(options = {}) {
27941
28326
  return index >= transcript.length ? null : index;
27942
28327
  };
27943
28328
  const entryContainsHint = (entry, latestHintToolId) => latestHintToolId !== null && entry.role === "assistant" && entry.segments.some((segment) => segment.kind === "tool" && segment.id === latestHintToolId);
27944
- const entryHasRunningTool = (entry) => entry.role === "assistant" && entry.segments.some(
28329
+ const entryHasRunningTool = (entry) => entry.role === "assistant" && (entry.segments.some(
27945
28330
  (segment) => segment.kind === "tool" && !segment.finished && segment.startedAtMs !== void 0
27946
- ) || entry.role === "replayBlock" && entry.isLive?.() === true;
28331
+ // The live reasoning line counts its own elapsed, so a turn that is
28332
+ // only thinking still needs the time-keyed render key.
28333
+ ) || entry.pending && openThinkingSegment(entry)?.startedAtMs !== void 0) || entry.role === "replayBlock" && entry.isLive?.() === true;
27947
28334
  const entryIsStable = (entry) => entry.role === "user" || entry.role === "replayBlock" && entry.isLive?.() !== true || entry.role === "worker" && !entry.state.pending || entry.role === "assistant" && !entry.pending && !entry.segments.some((segment) => segment.kind === "tool" && !segment.finished);
27948
28335
  const render2 = (width) => {
27949
28336
  const startedAt = performance5.now();
27950
28337
  const expandKey = resolveExpandKey();
27951
- const verbosity = options.getOutputVerbosity?.() ?? "default";
28338
+ const verbosity = options.getOutputVerbosity?.();
28339
+ if (lastVerbosity !== null && verbosity !== lastVerbosity) dropFoldOverrides();
28340
+ lastVerbosity = verbosity;
28341
+ const detail = transcriptDetail(verbosity);
27952
28342
  const nowMs = now();
27953
28343
  const tick = renderedRunningTool ? Math.floor(nowMs / 100) : 0;
27954
- if (!dirty && cachedWidth === width && cachedExpandKey === expandKey && cachedVerbosity === verbosity && cachedLiveToolOutput === liveToolOutput && cachedTick === tick) {
28344
+ if (!dirty && cachedWidth === width && cachedExpandKey === expandKey && cachedDetail === detail && cachedLiveToolOutput === liveToolOutput && cachedTick === tick) {
27955
28345
  options.onRenderMetrics?.({ durationMs: performance5.now() - startedAt, cacheHit: true, entriesRendered: 0 });
27956
28346
  return cachedLines;
27957
28347
  }
27958
- const { toolId: latestHintToolId, workerId: latestFoldedWorkerId } = expandHintOwner();
27959
- const baseKey = `${width}|${expandKey ?? ""}|${verbosity}|${liveToolOutput}`;
28348
+ const { toolId: latestHintToolId, workerId: latestFoldedWorkerId } = expandHintOwner(detail);
28349
+ const baseKey = `${width}|${expandKey ?? ""}|${detail.toolBody}:${detail.runningTool}:${detail.thinking}:${detail.worker}:${detail.receipt}:${detail.errors}|${liveToolOutput}`;
27960
28350
  const capacity = entryCacheCapacity();
27961
28351
  if (frozen !== null && frozen.key !== baseKey) frozen = null;
27962
28352
  const out = frozen === null ? [] : frozen.lines.slice();
@@ -27971,7 +28361,7 @@ function createChatPanel(options = {}) {
27971
28361
  if (!entry) continue;
27972
28362
  if (!sawRunningTool && entryHasRunningTool(entry)) sawRunningTool = true;
27973
28363
  const previous = i > 0 ? transcript[i - 1] : void 0;
27974
- const stacksOnPrevious = entry.role === "worker" && previous?.role === "worker" && workerEntryFolded(entry, verbosity) && workerEntryFolded(previous, verbosity);
28364
+ const stacksOnPrevious = entry.role === "worker" && previous?.role === "worker" && workerEntryFolded(entry, detail) && workerEntryFolded(previous, detail);
27975
28365
  if (i > 0 && !stacksOnPrevious) out.push("");
27976
28366
  const containsHint = entryContainsHint(entry, latestHintToolId) || entry.role === "worker" && entry.state.assignmentId === latestFoldedWorkerId;
27977
28367
  const entryKey = containsHint ? `${baseKey}|hint:${latestHintToolId}|${latestFoldedWorkerId}` : baseKey;
@@ -27989,8 +28379,9 @@ function createChatPanel(options = {}) {
27989
28379
  latestFoldedWorkerId,
27990
28380
  nowMs,
27991
28381
  unboundedToolBodies,
27992
- verbosity,
27993
- liveToolOutput
28382
+ detail,
28383
+ liveToolOutput,
28384
+ liveReasoning
27994
28385
  );
27995
28386
  for (const line of renderedEntry) out.push(line);
27996
28387
  if (cacheable) {
@@ -28013,7 +28404,7 @@ function createChatPanel(options = {}) {
28013
28404
  cachedLines = out;
28014
28405
  cachedWidth = width;
28015
28406
  cachedExpandKey = expandKey;
28016
- cachedVerbosity = verbosity;
28407
+ cachedDetail = detail;
28017
28408
  cachedLiveToolOutput = liveToolOutput;
28018
28409
  cachedTick = tick;
28019
28410
  renderedRunningTool = sawRunningTool;
@@ -28026,8 +28417,8 @@ function createChatPanel(options = {}) {
28026
28417
  transcript.push({ role: "user", text });
28027
28418
  markDirty();
28028
28419
  },
28029
- appendReplayBlock(renderBlock, isLive2) {
28030
- transcript.push({ role: "replayBlock", renderBlock, isLive: isLive2 });
28420
+ appendReplayBlock(renderBlock, isLive2, fold) {
28421
+ transcript.push({ role: "replayBlock", renderBlock, isLive: isLive2, fold });
28031
28422
  markDirty();
28032
28423
  },
28033
28424
  applyWorkerState(state) {
@@ -28037,7 +28428,7 @@ function createChatPanel(options = {}) {
28037
28428
  markDirty();
28038
28429
  return;
28039
28430
  }
28040
- const entry = { role: "worker", state, folded: workerAskedByModel(state) };
28431
+ const entry = { role: "worker", state };
28041
28432
  workerEntries.set(state.assignmentId, entry);
28042
28433
  const at = workerInsertionIndex(state);
28043
28434
  if (at === null) {
@@ -28052,10 +28443,17 @@ function createChatPanel(options = {}) {
28052
28443
  return [...workerEntries.values()].map((entry) => entry.state);
28053
28444
  },
28054
28445
  toggleLastToolExpanded() {
28446
+ const detail = currentDetail();
28055
28447
  for (let entryIndex = transcript.length - 1; entryIndex >= 0; entryIndex -= 1) {
28056
28448
  const entry = transcript[entryIndex];
28449
+ if (entry?.role === "replayBlock" && entry.fold !== void 0) {
28450
+ entry.fold.setFold(toggledFold(resolveFold(entry.fold.fold(), entry.fold.policyFold(detail))));
28451
+ clearRenderCaches();
28452
+ markDirty();
28453
+ return true;
28454
+ }
28057
28455
  if (entry?.role === "worker") {
28058
- entry.folded = !entry.folded;
28456
+ entry.fold = workerEntryFolded(entry, detail) ? "expanded" : "folded";
28059
28457
  clearRenderCaches();
28060
28458
  markDirty();
28061
28459
  return true;
@@ -28064,7 +28462,7 @@ function createChatPanel(options = {}) {
28064
28462
  for (let segIndex = entry.segments.length - 1; segIndex >= 0; segIndex -= 1) {
28065
28463
  const seg = entry.segments[segIndex];
28066
28464
  if (seg?.kind !== "tool") continue;
28067
- seg.expanded = !seg.expanded;
28465
+ seg.fold = toolSegmentExpanded(seg, detail) ? "folded" : "expanded";
28068
28466
  clearRenderCaches();
28069
28467
  markDirty();
28070
28468
  return true;
@@ -28073,9 +28471,15 @@ function createChatPanel(options = {}) {
28073
28471
  return false;
28074
28472
  },
28075
28473
  toggleAllToolsExpanded() {
28474
+ const detail = currentDetail();
28076
28475
  const tools = [];
28077
28476
  const workers = [];
28477
+ const blocks = [];
28078
28478
  for (const entry of transcript) {
28479
+ if (entry.role === "replayBlock") {
28480
+ if (entry.fold !== void 0) blocks.push(entry.fold);
28481
+ continue;
28482
+ }
28079
28483
  if (entry.role === "worker") {
28080
28484
  workers.push(entry);
28081
28485
  continue;
@@ -28085,59 +28489,63 @@ function createChatPanel(options = {}) {
28085
28489
  if (seg.kind === "tool") tools.push(seg);
28086
28490
  }
28087
28491
  }
28088
- if (tools.length === 0 && workers.length === 0) return false;
28089
- const expand = tools.some((seg) => !seg.expanded) || workers.some((entry) => entry.folded);
28090
- for (const seg of tools) seg.expanded = expand;
28091
- for (const entry of workers) entry.folded = !expand;
28492
+ if (tools.length === 0 && workers.length === 0 && blocks.length === 0) return false;
28493
+ const expand = tools.some((seg) => !toolSegmentExpanded(seg, detail)) || workers.some((entry) => workerEntryFolded(entry, detail)) || blocks.some((fold) => resolveFold(fold.fold(), fold.policyFold(detail)) === "folded");
28494
+ const next = expand ? "expanded" : "folded";
28495
+ for (const seg of tools) seg.fold = next;
28496
+ for (const entry of workers) entry.fold = next;
28497
+ for (const fold of blocks) fold.setFold(next);
28092
28498
  clearRenderCaches();
28093
28499
  markDirty();
28094
28500
  return true;
28095
28501
  },
28096
- collapseAllTools() {
28097
- for (const entry of transcript) {
28098
- if (entry.role === "worker") {
28099
- entry.folded = workerAskedByModel(entry.state);
28100
- continue;
28101
- }
28102
- if (entry.role !== "assistant") continue;
28103
- for (const seg of entry.segments) {
28104
- if (seg.kind === "tool") seg.expanded = false;
28105
- }
28106
- }
28107
- clearRenderCaches();
28108
- markDirty();
28502
+ clearFoldOverrides() {
28503
+ dropFoldOverrides();
28109
28504
  },
28110
28505
  toggleLastThinking() {
28506
+ const detail = currentDetail();
28111
28507
  for (let entryIndex = transcript.length - 1; entryIndex >= 0; entryIndex -= 1) {
28112
28508
  const entry = transcript[entryIndex];
28113
28509
  if (entry?.role !== "assistant") continue;
28114
- if (entry.thinking.length === 0) continue;
28115
- entry.expandedThinking = entry.expandedThinking !== true;
28116
- thinkingExpanded = entry.expandedThinking === true;
28117
- clearRenderCaches();
28118
- markDirty();
28119
- return true;
28510
+ for (let segIndex = entry.segments.length - 1; segIndex >= 0; segIndex -= 1) {
28511
+ const segment = entry.segments[segIndex];
28512
+ if (segment?.kind !== "thinking" || segment.text.length === 0) continue;
28513
+ segment.fold = thinkingExpanded(segment, detail) ? "folded" : "expanded";
28514
+ clearRenderCaches();
28515
+ markDirty();
28516
+ return true;
28517
+ }
28120
28518
  }
28121
- thinkingExpanded = !thinkingExpanded;
28122
- return true;
28519
+ return false;
28123
28520
  },
28124
28521
  toggleAllThinking() {
28125
- const entries = [];
28522
+ const detail = currentDetail();
28523
+ const segments = [];
28126
28524
  for (const entry of transcript) {
28127
- if (entry.role === "assistant" && entry.thinking.length > 0) entries.push(entry);
28128
- }
28129
- if (entries.length === 0) {
28130
- thinkingExpanded = !thinkingExpanded;
28131
- return true;
28525
+ if (entry.role !== "assistant") continue;
28526
+ for (const segment of entry.segments) {
28527
+ if (segment.kind === "thinking" && segment.text.length > 0) segments.push(segment);
28528
+ }
28132
28529
  }
28133
- const expand = entries.some((entry) => entry.expandedThinking !== true);
28134
- for (const entry of entries) entry.expandedThinking = expand;
28135
- thinkingExpanded = expand;
28530
+ if (segments.length === 0) return false;
28531
+ const expand = segments.some((segment) => !thinkingExpanded(segment, detail));
28532
+ for (const segment of segments) segment.fold = expand ? "expanded" : "folded";
28136
28533
  clearRenderCaches();
28137
28534
  markDirty();
28138
28535
  return true;
28139
28536
  },
28140
- isThinkingExpanded: () => thinkingExpanded,
28537
+ isThinkingExpanded() {
28538
+ const detail = currentDetail();
28539
+ const index = lastAssistantIndex(transcript);
28540
+ const entry = index === null ? void 0 : transcript[index];
28541
+ if (entry?.role === "assistant") {
28542
+ for (let segIndex = entry.segments.length - 1; segIndex >= 0; segIndex -= 1) {
28543
+ const segment = entry.segments[segIndex];
28544
+ if (segment?.kind === "thinking" && segment.text.length > 0) return thinkingExpanded(segment, detail);
28545
+ }
28546
+ }
28547
+ return policyThinkingFold(detail) === "expanded";
28548
+ },
28141
28549
  toggleLiveToolOutput() {
28142
28550
  liveToolOutput = !liveToolOutput;
28143
28551
  markDirty();
@@ -28146,6 +28554,7 @@ function createChatPanel(options = {}) {
28146
28554
  reset() {
28147
28555
  transcript.length = 0;
28148
28556
  workerEntries.clear();
28557
+ liveReasoning = UNMEASURED_REASONING;
28149
28558
  clearRenderCaches();
28150
28559
  markDirty();
28151
28560
  },
@@ -28196,6 +28605,7 @@ function createChatPanel(options = {}) {
28196
28605
  } else if (existing === void 0) {
28197
28606
  const assistant = ensureAssistant();
28198
28607
  assistant.pending = true;
28608
+ closeOpenThinking(assistant);
28199
28609
  assistant.segments.push({
28200
28610
  kind: "tool",
28201
28611
  id: streamed.id,
@@ -28204,8 +28614,7 @@ function createChatPanel(options = {}) {
28204
28614
  finished: false,
28205
28615
  executionStarted: false,
28206
28616
  argsComplete: assistantEvent.type === "toolcall_end",
28207
- isError: false,
28208
- expanded: false
28617
+ isError: false
28209
28618
  });
28210
28619
  }
28211
28620
  markDirty();
@@ -28221,13 +28630,14 @@ function createChatPanel(options = {}) {
28221
28630
  if (event.type === "thinking_delta") {
28222
28631
  const assistant = ensureAssistant();
28223
28632
  assistant.pending = true;
28224
- assistant.thinking += event.delta;
28225
- assistant.expandedThinking = thinkingExpanded;
28633
+ appendThinkingDelta(assistant, event.delta);
28226
28634
  markDirty();
28227
28635
  return;
28228
28636
  }
28229
28637
  if (event.type === "message_start" && event.message.role === "assistant") {
28230
- ensureAssistant().pending = true;
28638
+ const assistant = ensureAssistant();
28639
+ assistant.pending = true;
28640
+ assistant.messageStartSegmentIndex = assistant.segments.length;
28231
28641
  markDirty();
28232
28642
  return;
28233
28643
  }
@@ -28241,7 +28651,6 @@ function createChatPanel(options = {}) {
28241
28651
  streamed.executionStarted = true;
28242
28652
  streamed.argsComplete = true;
28243
28653
  streamed.startedAtMs = now();
28244
- streamed.expanded = classifyResourceRead(event.toolName, event.args) === null;
28245
28654
  markDirty();
28246
28655
  return;
28247
28656
  }
@@ -28252,8 +28661,8 @@ function createChatPanel(options = {}) {
28252
28661
  }
28253
28662
  }
28254
28663
  const assistant = ensureAssistant();
28255
- const expanded = classifyResourceRead(event.toolName, event.args) === null;
28256
28664
  assistant.pending = true;
28665
+ closeOpenThinking(assistant);
28257
28666
  assistant.segments.push({
28258
28667
  kind: "tool",
28259
28668
  id: event.toolCallId,
@@ -28263,7 +28672,6 @@ function createChatPanel(options = {}) {
28263
28672
  executionStarted: true,
28264
28673
  argsComplete: true,
28265
28674
  isError: false,
28266
- expanded,
28267
28675
  startedAtMs: now()
28268
28676
  });
28269
28677
  markDirty();
@@ -28343,10 +28751,15 @@ function createChatPanel(options = {}) {
28343
28751
  invalidateEntryCache(assistant);
28344
28752
  if (usage !== void 0) assistant.turnUsage = usage;
28345
28753
  if (terminalError.length > 0) assistant.isError = true;
28754
+ closeOpenThinking(assistant);
28346
28755
  if (thinking.length > 0) {
28347
- assistant.thinking = thinking;
28348
- assistant.expandedThinking = thinkingExpanded;
28756
+ const messageStart = Math.min(assistant.messageStartSegmentIndex ?? 0, assistant.segments.length);
28757
+ const streamedThisMessage = assistant.segments.slice(messageStart).some((segment) => segment.kind === "thinking" && segment.text.length > 0);
28758
+ if (!streamedThisMessage) {
28759
+ assistant.segments.splice(messageStart, 0, { kind: "thinking", text: thinking, finalized: true });
28760
+ }
28349
28761
  }
28762
+ assistant.messageStartSegmentIndex = void 0;
28350
28763
  const sanitized = event.lockedSynthesisSanitized === true;
28351
28764
  if (text.length > 0) canonicalizeMessageText(assistant, text, sanitized);
28352
28765
  if (terminalError.length > 0) appendErrorSegment(assistant, terminalError);
@@ -28392,13 +28805,24 @@ function createChatPanel(options = {}) {
28392
28805
  }
28393
28806
  if (entry.pending) {
28394
28807
  invalidateEntryCache(entry);
28808
+ closeOpenThinking(entry);
28395
28809
  entry.pending = false;
28396
28810
  entry.statusLine = null;
28811
+ entry.messageStartSegmentIndex = void 0;
28397
28812
  }
28398
28813
  }
28399
28814
  markDirty();
28400
28815
  }
28401
28816
  },
28817
+ setLiveReasoning(view) {
28818
+ const next = view ?? UNMEASURED_REASONING;
28819
+ if (next.tokens === liveReasoning.tokens && next.provenance === liveReasoning.provenance) return;
28820
+ liveReasoning = next;
28821
+ unfreezeTail();
28822
+ const last = transcript[transcript.length - 1];
28823
+ if (last !== void 0) invalidateEntryCache(last);
28824
+ markDirty();
28825
+ },
28402
28826
  setStatusLine(line) {
28403
28827
  if (line) {
28404
28828
  const assistant = ensureAssistant();
@@ -30381,6 +30805,8 @@ function createInteractiveSlashRuntime(deps) {
30381
30805
  next.terminal.outputVerbosity = match;
30382
30806
  if (deps.commitSetting) deps.commitSetting("terminal.outputVerbosity", next, "session");
30383
30807
  else deps.writeSettings?.(next);
30808
+ deps.chatPanel.clearFoldOverrides();
30809
+ deps.refreshFooter();
30384
30810
  return { status: "applied", verbosity: match };
30385
30811
  },
30386
30812
  openModel: deps.openModel,
@@ -30416,13 +30842,12 @@ function createInteractiveSlashRuntime(deps) {
30416
30842
  }
30417
30843
  try {
30418
30844
  const turns = deps.readStructuredEntries(sessionId);
30419
- const exportPanel = createChatPanel({ unboundedToolBodies: true });
30845
+ const exportPanel = createChatPanel({ unboundedToolBodies: true, getOutputVerbosity: () => "verbose" });
30420
30846
  const leafTurnId = deps.session?.tree(sessionId).leafId ?? null;
30421
30847
  rehydrateChatPanelFromTurns(exportPanel, turns, {
30422
30848
  unboundedToolBodies: true,
30423
30849
  ...leafTurnId ? { activeLeafTurnId: leafTurnId } : {}
30424
30850
  });
30425
- exportPanel.toggleAllToolsExpanded();
30426
30851
  const ansiLines = exportPanel.render(EXPORT_RENDER_WIDTH);
30427
30852
  const exportedAt = deps.now?.() ?? /* @__PURE__ */ new Date();
30428
30853
  const date = dateLocal(exportedAt);
@@ -31048,6 +31473,7 @@ async function createInteractiveApplication(deps) {
31048
31473
  recordToolStart: (toolName2, toolCallId) => presentation.recordToolStart(toolCallId, toolName2),
31049
31474
  recordToolEnd: (_toolName, toolCallId, isError, truncated) => presentation.recordToolEnd({ toolCallId, isError, truncated }),
31050
31475
  setStatusLine: (line) => chatPanel.setStatusLine(line),
31476
+ setLiveReasoning: (view) => chatPanel.setLiveReasoning(view),
31051
31477
  setLastTurnSummary: (summary) => presentation.setLastTurnSummary(summary),
31052
31478
  startTerminalProgress: () => agentProgress.start(),
31053
31479
  stopTerminalProgress: () => agentProgress.stop(),
@@ -31076,7 +31502,8 @@ async function createInteractiveApplication(deps) {
31076
31502
  chat: deps.chat,
31077
31503
  chatPanel: {
31078
31504
  appendReplayBlock: (...args) => chatRenderer.mutate(() => chatPanel.appendReplayBlock(...args), "slash-output"),
31079
- appendUser: (text) => chatRenderer.mutate(() => chatPanel.appendUser(text), "user-submit")
31505
+ appendUser: (text) => chatRenderer.mutate(() => chatPanel.appendUser(text), "user-submit"),
31506
+ clearFoldOverrides: () => chatRenderer.mutate(() => chatPanel.clearFoldOverrides(), "output-detail")
31080
31507
  },
31081
31508
  beforeSemanticSubmit: () => chatRenderer.flush(),
31082
31509
  settleVisibleFrame: (reason) => chatRenderer.flushAndCommit(reason),
@@ -32091,7 +32518,7 @@ function createDispatchTool(inputDeps, surfaceOptions = {}) {
32091
32518
  const admission = createDispatchAdmissionController(deps);
32092
32519
  let runnerPromise = null;
32093
32520
  const loadRunner = () => {
32094
- runnerPromise ??= (surfaceOptions.loadRunner ?? (() => import("./dispatch-runner-VKBRCWQC.js")))();
32521
+ runnerPromise ??= (surfaceOptions.loadRunner ?? (() => import("./dispatch-runner-QPRDDBDX.js")))();
32095
32522
  return runnerPromise;
32096
32523
  };
32097
32524
  return {
@@ -32295,7 +32722,7 @@ function registerAllTools(registry, deps = {}) {
32295
32722
  ...builtin(
32296
32723
  lazyTool(
32297
32724
  monitorToolSurface,
32298
- async () => (await import("./monitor-MEQA5C3I.js")).createMonitorTool({ dispatch, runEvents: dispatchRunEvents })
32725
+ async () => (await import("./monitor-GE4ID3IA.js")).createMonitorTool({ dispatch, runEvents: dispatchRunEvents })
32299
32726
  ),
32300
32727
  {
32301
32728
  path: "src/tools/monitor.ts",
@@ -33729,4 +34156,4 @@ export {
33729
34156
  resolveApiKeyForTarget,
33730
34157
  synthesizeOrchestratorModel
33731
34158
  };
33732
- //# sourceMappingURL=orchestrator-CGFKEP27.js.map
34159
+ //# sourceMappingURL=orchestrator-EM5MC3HM.js.map