openhorse 0.2.21 → 0.2.23

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 (263) hide show
  1. package/dist/cli.js +25 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/index.d.ts.map +1 -1
  4. package/dist/commands/index.js +189 -49
  5. package/dist/commands/index.js.map +1 -1
  6. package/dist/commands/parser.d.ts.map +1 -1
  7. package/dist/commands/parser.js +8 -3
  8. package/dist/commands/parser.js.map +1 -1
  9. package/dist/commands/types.d.ts +2 -0
  10. package/dist/commands/types.d.ts.map +1 -1
  11. package/dist/commands/types.js.map +1 -1
  12. package/dist/core/cost-tracker.d.ts +45 -5
  13. package/dist/core/cost-tracker.d.ts.map +1 -1
  14. package/dist/core/cost-tracker.js +78 -13
  15. package/dist/core/cost-tracker.js.map +1 -1
  16. package/dist/framework/index.d.ts +1 -1
  17. package/dist/framework/index.d.ts.map +1 -1
  18. package/dist/framework/index.js.map +1 -1
  19. package/dist/framework/query.d.ts +25 -0
  20. package/dist/framework/query.d.ts.map +1 -1
  21. package/dist/framework/query.js +196 -49
  22. package/dist/framework/query.js.map +1 -1
  23. package/dist/framework/store.d.ts +6 -1
  24. package/dist/framework/store.d.ts.map +1 -1
  25. package/dist/framework/store.js +10 -1
  26. package/dist/framework/store.js.map +1 -1
  27. package/dist/harness/capsule.d.ts +1 -0
  28. package/dist/harness/capsule.d.ts.map +1 -1
  29. package/dist/harness/capsule.js +38 -9
  30. package/dist/harness/capsule.js.map +1 -1
  31. package/dist/harness/contract.d.ts +2 -0
  32. package/dist/harness/contract.d.ts.map +1 -1
  33. package/dist/harness/contract.js +78 -14
  34. package/dist/harness/contract.js.map +1 -1
  35. package/dist/harness/index.d.ts +2 -2
  36. package/dist/harness/index.d.ts.map +1 -1
  37. package/dist/harness/index.js +4 -1
  38. package/dist/harness/index.js.map +1 -1
  39. package/dist/harness/ledger.d.ts.map +1 -1
  40. package/dist/harness/ledger.js +6 -2
  41. package/dist/harness/ledger.js.map +1 -1
  42. package/dist/harness/state.d.ts.map +1 -1
  43. package/dist/harness/state.js +37 -7
  44. package/dist/harness/state.js.map +1 -1
  45. package/dist/index.d.ts +2 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/ink-ui/components/StatusLine.d.ts.map +1 -1
  48. package/dist/ink-ui/components/StatusLine.js +0 -2
  49. package/dist/ink-ui/components/StatusLine.js.map +1 -1
  50. package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
  51. package/dist/ink-ui/screens/ReplScreen.js +12 -5
  52. package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
  53. package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
  54. package/dist/runtime/agent-runtime-controller.js +11 -1
  55. package/dist/runtime/agent-runtime-controller.js.map +1 -1
  56. package/dist/runtime/aggregate-tool-presenter.d.ts +13 -0
  57. package/dist/runtime/aggregate-tool-presenter.d.ts.map +1 -0
  58. package/dist/runtime/aggregate-tool-presenter.js +44 -0
  59. package/dist/runtime/aggregate-tool-presenter.js.map +1 -0
  60. package/dist/runtime/chat-controller.d.ts +9 -2
  61. package/dist/runtime/chat-controller.d.ts.map +1 -1
  62. package/dist/runtime/chat-controller.js +345 -90
  63. package/dist/runtime/chat-controller.js.map +1 -1
  64. package/dist/runtime/rich-text/ansi-parser.d.ts +1 -1
  65. package/dist/runtime/rich-text/ansi-parser.d.ts.map +1 -1
  66. package/dist/runtime/rich-text/ansi-parser.js +93 -55
  67. package/dist/runtime/rich-text/ansi-parser.js.map +1 -1
  68. package/dist/runtime/rich-text/layout.d.ts +2 -11
  69. package/dist/runtime/rich-text/layout.d.ts.map +1 -1
  70. package/dist/runtime/rich-text/layout.js +280 -245
  71. package/dist/runtime/rich-text/layout.js.map +1 -1
  72. package/dist/runtime/rich-text/markdown-parser.js +31 -24
  73. package/dist/runtime/rich-text/markdown-parser.js.map +1 -1
  74. package/dist/runtime/rich-text/types.d.ts +1 -1
  75. package/dist/runtime/rich-text/types.d.ts.map +1 -1
  76. package/dist/runtime/subagents/production.d.ts.map +1 -1
  77. package/dist/runtime/subagents/production.js +28 -3
  78. package/dist/runtime/subagents/production.js.map +1 -1
  79. package/dist/runtime/subagents/result-parser.d.ts.map +1 -1
  80. package/dist/runtime/subagents/result-parser.js +54 -40
  81. package/dist/runtime/subagents/result-parser.js.map +1 -1
  82. package/dist/runtime/subagents/types.d.ts +2 -0
  83. package/dist/runtime/subagents/types.d.ts.map +1 -1
  84. package/dist/runtime/subagents/types.js +5 -1
  85. package/dist/runtime/subagents/types.js.map +1 -1
  86. package/dist/runtime/tool-detail-repository.d.ts +45 -0
  87. package/dist/runtime/tool-detail-repository.d.ts.map +1 -0
  88. package/dist/runtime/tool-detail-repository.js +131 -0
  89. package/dist/runtime/tool-detail-repository.js.map +1 -0
  90. package/dist/runtime/tool-output-presentation.d.ts +89 -0
  91. package/dist/runtime/tool-output-presentation.d.ts.map +1 -0
  92. package/dist/runtime/tool-output-presentation.js +329 -0
  93. package/dist/runtime/tool-output-presentation.js.map +1 -0
  94. package/dist/runtime/ui-events.d.ts +17 -0
  95. package/dist/runtime/ui-events.d.ts.map +1 -1
  96. package/dist/runtime/ui-events.js.map +1 -1
  97. package/dist/runtime/ui-view-model.d.ts +9 -0
  98. package/dist/runtime/ui-view-model.d.ts.map +1 -1
  99. package/dist/runtime/ui-view-model.js +59 -37
  100. package/dist/runtime/ui-view-model.js.map +1 -1
  101. package/dist/services/compact/auto-compact.d.ts +15 -2
  102. package/dist/services/compact/auto-compact.d.ts.map +1 -1
  103. package/dist/services/compact/auto-compact.js +56 -24
  104. package/dist/services/compact/auto-compact.js.map +1 -1
  105. package/dist/services/compact/compact.d.ts +2 -0
  106. package/dist/services/compact/compact.d.ts.map +1 -1
  107. package/dist/services/compact/compact.js +22 -5
  108. package/dist/services/compact/compact.js.map +1 -1
  109. package/dist/services/compact/coordinator.d.ts +26 -0
  110. package/dist/services/compact/coordinator.d.ts.map +1 -0
  111. package/dist/services/compact/coordinator.js +44 -0
  112. package/dist/services/compact/coordinator.js.map +1 -0
  113. package/dist/services/compact/index.d.ts +1 -0
  114. package/dist/services/compact/index.d.ts.map +1 -1
  115. package/dist/services/compact/index.js +3 -1
  116. package/dist/services/compact/index.js.map +1 -1
  117. package/dist/services/compact/summary-generator.d.ts +6 -2
  118. package/dist/services/compact/summary-generator.d.ts.map +1 -1
  119. package/dist/services/compact/summary-generator.js +73 -31
  120. package/dist/services/compact/summary-generator.js.map +1 -1
  121. package/dist/services/config-dir.d.ts +11 -1
  122. package/dist/services/config-dir.d.ts.map +1 -1
  123. package/dist/services/config-dir.js +19 -2
  124. package/dist/services/config-dir.js.map +1 -1
  125. package/dist/services/config.d.ts +4 -2
  126. package/dist/services/config.d.ts.map +1 -1
  127. package/dist/services/config.js +40 -0
  128. package/dist/services/config.js.map +1 -1
  129. package/dist/services/doctor.d.ts +2 -0
  130. package/dist/services/doctor.d.ts.map +1 -1
  131. package/dist/services/doctor.js +8 -1
  132. package/dist/services/doctor.js.map +1 -1
  133. package/dist/services/global-config.d.ts +10 -0
  134. package/dist/services/global-config.d.ts.map +1 -1
  135. package/dist/services/global-config.js.map +1 -1
  136. package/dist/services/llm.d.ts +21 -4
  137. package/dist/services/llm.d.ts.map +1 -1
  138. package/dist/services/llm.js +66 -8
  139. package/dist/services/llm.js.map +1 -1
  140. package/dist/services/model-context.d.ts +36 -0
  141. package/dist/services/model-context.d.ts.map +1 -1
  142. package/dist/services/model-context.js +245 -32
  143. package/dist/services/model-context.js.map +1 -1
  144. package/dist/services/redaction.js +1 -1
  145. package/dist/services/redaction.js.map +1 -1
  146. package/dist/services/session-storage.d.ts +42 -1
  147. package/dist/services/session-storage.d.ts.map +1 -1
  148. package/dist/services/session-storage.js +175 -54
  149. package/dist/services/session-storage.js.map +1 -1
  150. package/dist/services/storage-maintenance.d.ts.map +1 -1
  151. package/dist/services/storage-maintenance.js +3 -2
  152. package/dist/services/storage-maintenance.js.map +1 -1
  153. package/dist/services/usage-state.d.ts +55 -5
  154. package/dist/services/usage-state.d.ts.map +1 -1
  155. package/dist/services/usage-state.js +185 -59
  156. package/dist/services/usage-state.js.map +1 -1
  157. package/dist/services/verification-profile.d.ts.map +1 -1
  158. package/dist/services/verification-profile.js +3 -2
  159. package/dist/services/verification-profile.js.map +1 -1
  160. package/dist/services/workspace-diff.d.ts.map +1 -1
  161. package/dist/services/workspace-diff.js +34 -3
  162. package/dist/services/workspace-diff.js.map +1 -1
  163. package/dist/services/yaml-config.js +1 -1
  164. package/dist/skills/runtime.d.ts +9 -5
  165. package/dist/skills/runtime.d.ts.map +1 -1
  166. package/dist/skills/runtime.js +157 -12
  167. package/dist/skills/runtime.js.map +1 -1
  168. package/dist/terminal-ui/editor.d.ts.map +1 -1
  169. package/dist/terminal-ui/editor.js +14 -3
  170. package/dist/terminal-ui/editor.js.map +1 -1
  171. package/dist/terminal-ui/launch.d.ts +21 -0
  172. package/dist/terminal-ui/launch.d.ts.map +1 -1
  173. package/dist/terminal-ui/launch.js +203 -45
  174. package/dist/terminal-ui/launch.js.map +1 -1
  175. package/dist/terminal-ui/output-queue.d.ts +43 -0
  176. package/dist/terminal-ui/output-queue.d.ts.map +1 -0
  177. package/dist/terminal-ui/output-queue.js +139 -0
  178. package/dist/terminal-ui/output-queue.js.map +1 -0
  179. package/dist/terminal-ui/raw-editor.d.ts +42 -0
  180. package/dist/terminal-ui/raw-editor.d.ts.map +1 -1
  181. package/dist/terminal-ui/raw-editor.js +337 -41
  182. package/dist/terminal-ui/raw-editor.js.map +1 -1
  183. package/dist/tools/bash_security.d.ts.map +1 -1
  184. package/dist/tools/bash_security.js +14 -5
  185. package/dist/tools/bash_security.js.map +1 -1
  186. package/dist/tools/index.d.ts.map +1 -1
  187. package/dist/tools/index.js +176 -73
  188. package/dist/tools/index.js.map +1 -1
  189. package/dist/tools/plan.d.ts.map +1 -1
  190. package/dist/tools/plan.js +7 -0
  191. package/dist/tools/plan.js.map +1 -1
  192. package/dist/tools/web.d.ts +10 -0
  193. package/dist/tools/web.d.ts.map +1 -1
  194. package/dist/tools/web.js +81 -1
  195. package/dist/tools/web.js.map +1 -1
  196. package/dist/tui-core/input-parser.d.ts +9 -2
  197. package/dist/tui-core/input-parser.d.ts.map +1 -1
  198. package/dist/tui-core/input-parser.js +57 -3
  199. package/dist/tui-core/input-parser.js.map +1 -1
  200. package/dist/tui-core/style.d.ts +16 -0
  201. package/dist/tui-core/style.d.ts.map +1 -1
  202. package/dist/tui-core/style.js +22 -1
  203. package/dist/tui-core/style.js.map +1 -1
  204. package/dist/tui-ui/inline-surface.d.ts +69 -12
  205. package/dist/tui-ui/inline-surface.d.ts.map +1 -1
  206. package/dist/tui-ui/inline-surface.js +366 -94
  207. package/dist/tui-ui/inline-surface.js.map +1 -1
  208. package/dist/tui-ui/input-ownership.d.ts +46 -0
  209. package/dist/tui-ui/input-ownership.d.ts.map +1 -0
  210. package/dist/tui-ui/input-ownership.js +74 -0
  211. package/dist/tui-ui/input-ownership.js.map +1 -0
  212. package/dist/tui-ui/launch.d.ts +2 -0
  213. package/dist/tui-ui/launch.d.ts.map +1 -1
  214. package/dist/tui-ui/launch.js +289 -43
  215. package/dist/tui-ui/launch.js.map +1 -1
  216. package/dist/tui-ui/layout.d.ts +52 -2
  217. package/dist/tui-ui/layout.d.ts.map +1 -1
  218. package/dist/tui-ui/layout.js +230 -76
  219. package/dist/tui-ui/layout.js.map +1 -1
  220. package/dist/tui-ui/runner.d.ts +101 -8
  221. package/dist/tui-ui/runner.d.ts.map +1 -1
  222. package/dist/tui-ui/runner.js +843 -51
  223. package/dist/tui-ui/runner.js.map +1 -1
  224. package/dist/tui-ui/state.d.ts +70 -4
  225. package/dist/tui-ui/state.d.ts.map +1 -1
  226. package/dist/tui-ui/state.js +251 -20
  227. package/dist/tui-ui/state.js.map +1 -1
  228. package/dist/tui-ui/theme-profile.d.ts +25 -0
  229. package/dist/tui-ui/theme-profile.d.ts.map +1 -0
  230. package/dist/tui-ui/theme-profile.js +130 -0
  231. package/dist/tui-ui/theme-profile.js.map +1 -0
  232. package/dist/tui-ui/theme.d.ts +13 -0
  233. package/dist/tui-ui/theme.d.ts.map +1 -0
  234. package/dist/tui-ui/theme.js +20 -0
  235. package/dist/tui-ui/theme.js.map +1 -0
  236. package/dist/tui-ui/transcript-cache.d.ts +5 -3
  237. package/dist/tui-ui/transcript-cache.d.ts.map +1 -1
  238. package/dist/tui-ui/transcript-cache.js +16 -9
  239. package/dist/tui-ui/transcript-cache.js.map +1 -1
  240. package/dist/tui-ui/transcript-inspector-layout.d.ts +9 -0
  241. package/dist/tui-ui/transcript-inspector-layout.d.ts.map +1 -0
  242. package/dist/tui-ui/transcript-inspector-layout.js +97 -0
  243. package/dist/tui-ui/transcript-inspector-layout.js.map +1 -0
  244. package/dist/tui-ui/transcript-inspector-surface.d.ts +18 -0
  245. package/dist/tui-ui/transcript-inspector-surface.d.ts.map +1 -0
  246. package/dist/tui-ui/transcript-inspector-surface.js +88 -0
  247. package/dist/tui-ui/transcript-inspector-surface.js.map +1 -0
  248. package/dist/tui-ui/transcript-inspector.d.ts +35 -0
  249. package/dist/tui-ui/transcript-inspector.d.ts.map +1 -0
  250. package/dist/tui-ui/transcript-inspector.js +128 -0
  251. package/dist/tui-ui/transcript-inspector.js.map +1 -0
  252. package/dist/tui-ui/transcript-layout.d.ts +22 -0
  253. package/dist/tui-ui/transcript-layout.d.ts.map +1 -0
  254. package/dist/tui-ui/transcript-layout.js +439 -0
  255. package/dist/tui-ui/transcript-layout.js.map +1 -0
  256. package/dist/ui/status-bar.d.ts +1 -1
  257. package/dist/ui/status-bar.d.ts.map +1 -1
  258. package/dist/ui/status-bar.js +2 -15
  259. package/dist/ui/status-bar.js.map +1 -1
  260. package/dist/ui-v2/components/shell.d.ts.map +1 -1
  261. package/dist/ui-v2/components/shell.js +0 -8
  262. package/dist/ui-v2/components/shell.js.map +1 -1
  263. package/package.json +1 -1
@@ -3,12 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TuiRunner = void 0;
4
4
  const frame_1 = require("../tui-core/frame");
5
5
  const input_parser_1 = require("../tui-core/input-parser");
6
- const terminal_writer_1 = require("../tui-core/terminal-writer");
7
6
  const commands_1 = require("../commands");
8
7
  const layout_1 = require("./layout");
9
8
  const pickers_1 = require("./pickers");
10
9
  const render_scheduler_1 = require("./render-scheduler");
11
10
  const state_1 = require("./state");
11
+ const input_ownership_1 = require("./input-ownership");
12
+ const transcript_inspector_1 = require("./transcript-inspector");
13
+ const transcript_inspector_layout_1 = require("./transcript-inspector-layout");
14
+ const transcript_cache_1 = require("./transcript-cache");
15
+ const transcript_layout_1 = require("./transcript-layout");
16
+ const theme_1 = require("./theme");
17
+ const history_1 = require("../runtime/composer/history");
18
+ const promises_1 = require("fs/promises");
19
+ const os_1 = require("os");
20
+ const path_1 = require("path");
12
21
  /** Actions that should use 'stream' priority (FPS-capped). */
13
22
  const STREAM_ACTIONS = new Set([
14
23
  'updateTranscript',
@@ -22,21 +31,40 @@ class TuiRunner {
22
31
  constructor(options) {
23
32
  this.options = options;
24
33
  this.parser = new input_parser_1.TuiInputParser();
34
+ this.inputOwnership = new input_ownership_1.TuiInputOwnershipController();
35
+ this.transcriptCache = new transcript_cache_1.TranscriptLayoutCache();
36
+ this.history = history_1.initialHistoryState;
37
+ this.surfaceFailed = false;
38
+ this.inspectorReady = false;
39
+ this.inspectorSearchActive = false;
40
+ this.modalTransition = Promise.resolve();
41
+ this.commitSequence = 0;
42
+ this.commitInFlight = false;
43
+ this.commitMismatchRetries = 0;
25
44
  this.state = state_1.initialTuiUiState;
26
45
  this.lastFrame = null;
27
- this.lastRenderResult = null;
28
- this.counters = { layoutCount: 0, paintCount: 0, changedRows: 0 };
46
+ this.surfaceResizePending = false;
47
+ this.resizeEpoch = 0;
48
+ this.surfaceResizeGeneration = 0;
49
+ this.counters = {
50
+ layoutCount: 0,
51
+ paintCount: 0,
52
+ changedRows: 0,
53
+ commitCount: 0,
54
+ };
29
55
  this.width = options.width;
30
56
  this.height = options.height;
31
- this.writer = new terminal_writer_1.TuiTerminalWriter(options.output);
32
- if (options.noScheduler !== false) {
33
- this.scheduler = null;
34
- }
35
- else {
36
- this.scheduler = (0, render_scheduler_1.createTuiRenderScheduler)(() => this.render(), options.schedulerDeps);
37
- }
57
+ this.surface = options.surface ?? null;
58
+ this.theme = (0, theme_1.resolveTuiTheme)(options.theme);
59
+ this.themeId = options.themeId ?? theme_1.DEFAULT_TUI_THEME_ID;
60
+ this.inspectorSurface = options.inspectorSurface ?? null;
61
+ this.inspectorController = options.detailRepository
62
+ ? new transcript_inspector_1.TranscriptInspectorController(options.detailRepository, options.cwd ?? process.cwd())
63
+ : null;
64
+ this.scheduler = (0, render_scheduler_1.createTuiRenderScheduler)(() => this.renderLive(), options.schedulerDeps);
38
65
  this.events = (0, state_1.createTuiUiEventSink)(action => this.dispatch(action));
39
- this.render();
66
+ // Initial render: paint the live region immediately.
67
+ this.renderLive();
40
68
  }
41
69
  getState() {
42
70
  return this.state;
@@ -44,61 +72,285 @@ class TuiRunner {
44
72
  getLastFrame() {
45
73
  return this.lastFrame;
46
74
  }
47
- getLastRenderResult() {
48
- return this.lastRenderResult;
49
- }
50
75
  getVisibleRows() {
51
76
  return this.lastFrame ? (0, frame_1.renderFrameRows)(this.lastFrame) : [];
52
77
  }
78
+ /** Get the scheduler for external lifecycle management (flush, stop). */
79
+ getScheduler() {
80
+ return this.scheduler;
81
+ }
82
+ /** Stop stale-width paints as soon as the terminal reports SIGWINCH. */
83
+ beginResize(width = this.width) {
84
+ if (this.state.inspector)
85
+ return;
86
+ if (!this.surface || this.surfaceFailed)
87
+ return;
88
+ this.surfaceResizePending = true;
89
+ this.resizeEpoch += 1;
90
+ this.surfaceResizeGeneration = this.surface.beginResize(width);
91
+ }
53
92
  resize(width, height) {
93
+ if (this.state.inspector) {
94
+ this.width = width;
95
+ this.height = height;
96
+ this.paintInspector();
97
+ return;
98
+ }
99
+ this.beginResize(width);
100
+ const resizeEpoch = this.resizeEpoch;
101
+ const surfaceResizeGeneration = this.surfaceResizeGeneration;
54
102
  this.width = width;
55
103
  this.height = height;
56
- this.writer.reset();
57
- if (this.scheduler) {
58
- this.scheduler.request('immediate');
59
- this.scheduler.flush();
60
- }
61
- else {
62
- this.render();
104
+ // Invalidate transcript cache on resize so committed entries are re-laid-out
105
+ // at the new width.
106
+ this.transcriptCache.invalidate(this.state.transcriptGeneration, this.transcriptWidth, this.themeId);
107
+ if (this.surface && !this.surfaceFailed) {
108
+ // Surface resize is async (serialized FIFO); fire-and-forget is safe
109
+ // because subsequent renders go through the surface queue too.
110
+ void this.surface
111
+ .resize(width, height, () => {
112
+ const frame = this.buildLiveFrame(width);
113
+ this.lastFrame = frame;
114
+ this.counters.layoutCount += 1;
115
+ this.counters.paintCount += 1;
116
+ return frame;
117
+ }, surfaceResizeGeneration)
118
+ .then(() => {
119
+ if (resizeEpoch !== this.resizeEpoch)
120
+ return;
121
+ this.surfaceResizePending = false;
122
+ this.tryCommit(this.state, true);
123
+ this.scheduler.request('immediate');
124
+ this.scheduler.flush();
125
+ })
126
+ .catch(error => this.handleSurfaceError(error));
127
+ return;
63
128
  }
129
+ this.scheduler.request('immediate');
130
+ this.scheduler.flush();
64
131
  }
65
132
  dispatch(action) {
133
+ const prevState = this.state;
66
134
  this.state = (0, state_1.tuiUiReducer)(this.state, action);
67
- if (this.scheduler) {
68
- const priority = STREAM_ACTIONS.has(action.type) ? 'stream' : 'immediate';
69
- this.scheduler.request(priority);
70
- }
71
- else {
72
- this.render();
135
+ if (action.type === 'setToolOutputViewMode' && action.mode !== prevState.toolOutputViewMode) {
136
+ this.transcriptCache.clear();
73
137
  }
138
+ // Check if any transcript entries became committable (finalized).
139
+ this.tryCommit(prevState);
140
+ const priority = STREAM_ACTIONS.has(action.type) ? 'stream' : 'immediate';
141
+ this.scheduler.request(priority);
74
142
  }
75
143
  feedInput(chunk) {
76
- const raw = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : chunk;
77
- if ((0, input_parser_1.isLikelyUnbracketedMultilinePaste)(raw)) {
78
- const event = { type: 'paste', value: (0, input_parser_1.normalizePastedText)(raw) };
79
- this.applyInputEvent(event);
80
- return [event];
81
- }
82
- const events = this.parser.feed(chunk);
144
+ const events = this.parser.feed(chunk, { detectUnbracketedMultilinePaste: true });
83
145
  for (const event of events) {
84
146
  this.applyInputEvent(event);
85
147
  }
86
148
  return events;
87
149
  }
88
- render() {
150
+ /**
151
+ * Render the full frame (legacy path for tests without surface).
152
+ * Uses renderTuiUiFrame which includes both static and live transcript.
153
+ */
154
+ renderFullFrame() {
89
155
  this.counters.layoutCount += 1;
90
156
  const frame = (0, layout_1.renderTuiUiFrame)(this.state, {
91
157
  width: this.width,
92
158
  height: this.height,
159
+ ...this.transcriptLayoutOptions(),
93
160
  });
94
- const result = this.writer.render(frame);
95
161
  this.lastFrame = frame;
96
- this.lastRenderResult = result;
97
162
  this.counters.paintCount += 1;
98
- this.counters.changedRows += result.diff.changedRows.length;
99
- return result;
163
+ return frame;
164
+ }
165
+ /**
166
+ * Render the live region via InlineTerminalSurface.
167
+ * When surface is available, only ephemeral content (live transcript, overlay,
168
+ * status, prompt) is painted into the live region.
169
+ * When no surface (test mode), renders the full frame including committed
170
+ * transcript so tests can inspect complete content via getLastFrame().
171
+ */
172
+ renderLive() {
173
+ if (this.state.inspector) {
174
+ this.paintInspector();
175
+ return;
176
+ }
177
+ if (this.surface && !this.surfaceFailed) {
178
+ if (this.surfaceResizePending)
179
+ return;
180
+ this.counters.layoutCount += 1;
181
+ // Production path: render only the live region for inline surface.
182
+ const frame = this.buildLiveFrame(this.width);
183
+ this.lastFrame = frame;
184
+ this.counters.paintCount += 1;
185
+ // Surface renderLive is async (serialized FIFO), but for production use
186
+ // we fire-and-forget because the next render will also go through the queue.
187
+ void this.surface.renderLive(frame).catch(error => this.handleSurfaceError(error));
188
+ }
189
+ else {
190
+ this.counters.layoutCount += 1;
191
+ // Test path (no surface): render the full frame so getLastFrame() /
192
+ // getVisibleRows() return complete content including committed transcript.
193
+ const frame = (0, layout_1.renderTuiUiFrame)(this.state, {
194
+ width: this.width,
195
+ height: this.height,
196
+ ...this.transcriptLayoutOptions(),
197
+ });
198
+ this.lastFrame = frame;
199
+ this.counters.paintCount += 1;
200
+ }
201
+ }
202
+ /** Build a compact live-region frame that grows only for active content. */
203
+ buildLiveFrame(terminalWidth) {
204
+ const width = this.surface
205
+ ? Math.max(1, Math.floor(terminalWidth) - 1)
206
+ : Math.max(1, Math.floor(terminalWidth));
207
+ const height = this.surface
208
+ ? (0, layout_1.measureTuiLiveFrameHeight)(this.state, width, this.surface.getLiveBandRows(), this.transcriptLayoutOptions())
209
+ : this.height - 1;
210
+ return (0, layout_1.renderTuiLiveFrame)(this.state, {
211
+ width,
212
+ height,
213
+ ...this.transcriptLayoutOptions(),
214
+ });
215
+ }
216
+ /**
217
+ * Try to commit any newly-finalized transcript entries to scrollback.
218
+ * Called after each dispatch. Only commits entries that are committable
219
+ * but not yet queued.
220
+ */
221
+ tryCommit(prevState, force = false) {
222
+ if (!this.surface || this.surfaceResizePending || this.state.inspector || this.commitInFlight)
223
+ return;
224
+ const prevCommittable = prevState.committableTranscriptCount;
225
+ const currCommittable = this.state.committableTranscriptCount;
226
+ const generationChanged = this.state.transcriptGeneration !== prevState.transcriptGeneration;
227
+ if (!force && !generationChanged && currCommittable <= prevCommittable)
228
+ return;
229
+ if (currCommittable <= this.state.queuedTranscriptCount)
230
+ return;
231
+ // Gather entries to commit: from queued boundary to new committable boundary.
232
+ const entriesToCommit = (0, state_1.pendingCommitRecords)(this.state);
233
+ if (entriesToCommit.length === 0)
234
+ return;
235
+ // Advance the queued boundary so we don't double-commit.
236
+ this.state = (0, state_1.markTranscriptQueued)(this.state, entriesToCommit.length);
237
+ // Build the commit batch.
238
+ const committedEntries = entriesToCommit.map(entry => ({
239
+ displayKey: entry.id,
240
+ rows: this.layoutTranscriptRecord(entry, this.transcriptWidth),
241
+ }));
242
+ const batch = {
243
+ batchId: `tui-commit-${++this.commitSequence}`,
244
+ generation: this.state.transcriptGeneration,
245
+ reason: 'finalize',
246
+ entries: committedEntries,
247
+ };
248
+ // The LiveFrameProvider ensures the live frame is rebuilt after commit
249
+ // with the latest state (where committed entries are no longer in the
250
+ // live region).
251
+ const generation = batch.generation;
252
+ const getLatestLiveFrame = () => this.buildLiveFrame(this.width);
253
+ this.commitInFlight = true;
254
+ let continueCommit = true;
255
+ // Surface commit is async (serialized FIFO). Fire-and-forget: the
256
+ // commit's internal getLatestLiveFrame already rebuilds the live region,
257
+ // so no additional scheduler paint is needed here.
258
+ void this.surface
259
+ .commit(batch, getLatestLiveFrame)
260
+ .then(result => {
261
+ if (result.batchId !== batch.batchId ||
262
+ result.generation !== generation ||
263
+ result.committedEntries !== entriesToCommit.length) {
264
+ if (this.state.transcriptGeneration === generation) {
265
+ this.state = {
266
+ ...this.state,
267
+ queuedTranscriptCount: Math.max(0, this.state.queuedTranscriptCount - entriesToCommit.length),
268
+ };
269
+ this.commitMismatchRetries += 1;
270
+ if (this.commitMismatchRetries > 1) {
271
+ continueCommit = false;
272
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
273
+ type: 'setStatus',
274
+ message: 'TUI transcript acknowledgement mismatch; retained content for retry.',
275
+ });
276
+ }
277
+ }
278
+ void this.surface?.forceRedraw(this.buildLiveFrame(this.width));
279
+ return;
280
+ }
281
+ const acknowledgement = (0, state_1.acknowledgeTranscriptCommit)(this.state, {
282
+ generation,
283
+ recordIds: result.displayKeys ?? [],
284
+ });
285
+ if (!acknowledgement.accepted) {
286
+ if (this.state.transcriptGeneration === generation) {
287
+ this.state = {
288
+ ...this.state,
289
+ queuedTranscriptCount: Math.max(0, this.state.queuedTranscriptCount - entriesToCommit.length),
290
+ };
291
+ this.commitMismatchRetries += 1;
292
+ if (this.commitMismatchRetries > 1) {
293
+ continueCommit = false;
294
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
295
+ type: 'setStatus',
296
+ message: 'TUI transcript acknowledgement mismatch; retained content for retry.',
297
+ });
298
+ }
299
+ }
300
+ void this.surface?.forceRedraw(this.buildLiveFrame(this.width));
301
+ return;
302
+ }
303
+ this.state = acknowledgement.state;
304
+ this.commitMismatchRetries = 0;
305
+ this.counters.commitCount += 1;
306
+ })
307
+ .catch(error => this.handleSurfaceError(error))
308
+ .finally(() => {
309
+ this.commitInFlight = false;
310
+ if (!this.surfaceFailed && continueCommit)
311
+ this.tryCommit(this.state, true);
312
+ });
313
+ }
314
+ /** Layout a transcript record once per revision/width/theme combination. */
315
+ layoutTranscriptRecord(entry, width) {
316
+ const cached = this.transcriptCache.get(entry.id, entry.revision, this.state.transcriptGeneration, width, this.themeId);
317
+ if (cached)
318
+ return cached;
319
+ const rows = (0, transcript_layout_1.layoutTranscriptEntry)(entry, {
320
+ width,
321
+ theme: this.theme,
322
+ toolOutputMode: this.state.toolOutputViewMode,
323
+ });
324
+ this.transcriptCache.set(entry.id, entry.revision, rows, this.state.transcriptGeneration, width, this.themeId);
325
+ return rows;
326
+ }
327
+ get transcriptWidth() {
328
+ return this.surface ? Math.max(1, this.width - 1) : Math.max(1, this.width);
329
+ }
330
+ transcriptLayoutOptions() {
331
+ return {
332
+ transcriptWidth: this.transcriptWidth,
333
+ theme: this.theme,
334
+ toolOutputMode: this.state.toolOutputViewMode,
335
+ layoutTranscriptRecord: (entry, width) => this.layoutTranscriptRecord(entry, width),
336
+ };
337
+ }
338
+ handleSurfaceError(error) {
339
+ if (this.surfaceFailed)
340
+ return;
341
+ this.surfaceFailed = true;
342
+ const message = error instanceof Error ? error.message : String(error);
343
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
344
+ type: 'setStatus',
345
+ message: `TUI output error: ${message}`,
346
+ });
347
+ this.options.onSurfaceError?.(error);
100
348
  }
101
349
  applyInputEvent(event) {
350
+ if (this.state.inspector) {
351
+ this.applyInspectorInput(event);
352
+ return;
353
+ }
102
354
  if (this.state.overlay?.type === 'permission') {
103
355
  if (event.type === 'text') {
104
356
  const answer = event.value.trim().toLowerCase();
@@ -143,10 +395,16 @@ class TuiRunner {
143
395
  this.dispatch({ type: 'moveOverlaySelection', delta: 1 });
144
396
  return;
145
397
  case 'pageup':
146
- this.dispatch({ type: 'moveOverlaySelection', delta: -Math.max(1, overlay.request.maxVisibleItems ?? 10) });
398
+ this.dispatch({
399
+ type: 'moveOverlaySelection',
400
+ delta: -Math.max(1, overlay.request.maxVisibleItems ?? 10),
401
+ });
147
402
  return;
148
403
  case 'pagedown':
149
- this.dispatch({ type: 'moveOverlaySelection', delta: Math.max(1, overlay.request.maxVisibleItems ?? 10) });
404
+ this.dispatch({
405
+ type: 'moveOverlaySelection',
406
+ delta: Math.max(1, overlay.request.maxVisibleItems ?? 10),
407
+ });
150
408
  return;
151
409
  case 'enter':
152
410
  this.submitPrompt({ allowEmpty: true });
@@ -192,6 +450,11 @@ class TuiRunner {
192
450
  this.completeCommand(overlay.items[overlay.selectedIndex], false);
193
451
  return;
194
452
  case 'enter':
453
+ if (/^\/(?:tool-output|redraw)(?:\s|$)/u.test(this.state.prompt.value.trim())) {
454
+ this.dispatch({ type: 'closeOverlay' });
455
+ this.submitPrompt();
456
+ return;
457
+ }
195
458
  this.completeCommand(overlay.items[overlay.selectedIndex], true);
196
459
  return;
197
460
  case 'escape':
@@ -250,9 +513,18 @@ class TuiRunner {
250
513
  return;
251
514
  }
252
515
  switch (key) {
516
+ case 'ctrl+o':
517
+ this.openToolInspector();
518
+ return;
519
+ case 'ctrl+l':
520
+ this.forceOwnedRedraw();
521
+ return;
253
522
  case 'enter':
254
523
  this.submitPrompt();
255
524
  return;
525
+ case 'newline':
526
+ this.updatePrompt(insertAtCursor(value, cursor, '\n'));
527
+ return;
256
528
  case 'backspace':
257
529
  this.updatePrompt(deleteBeforeCursor(value, cursor));
258
530
  return;
@@ -266,10 +538,10 @@ class TuiRunner {
266
538
  this.dispatch({ type: 'setPrompt', value, cursor: nextBoundary(value, cursor) });
267
539
  return;
268
540
  case 'home':
269
- this.dispatch({ type: 'setPrompt', value, cursor: 0 });
541
+ this.moveCursorLineHome();
270
542
  return;
271
543
  case 'end':
272
- this.dispatch({ type: 'setPrompt', value, cursor: value.length });
544
+ this.moveCursorLineEnd();
273
545
  return;
274
546
  case 'ctrl+u':
275
547
  this.dispatch({ type: 'setPrompt', value: '', cursor: 0 });
@@ -293,21 +565,97 @@ class TuiRunner {
293
565
  }
294
566
  return;
295
567
  case 'up':
296
- case 'down':
297
- return;
298
- case 'pageup':
299
- this.dispatch({ type: 'scrollTranscript', delta: Math.max(1, this.height - 5) });
568
+ this.moveCursorUpOrHistory();
300
569
  return;
301
- case 'pagedown':
302
- this.dispatch({ type: 'scrollTranscript', delta: -Math.max(1, this.height - 5) });
570
+ case 'down':
571
+ this.moveCursorDownOrHistory();
303
572
  return;
304
573
  }
305
574
  }
575
+ /** Navigate to previous history entry. */
576
+ historyBack() {
577
+ if (this.state.overlay)
578
+ return; // overlay handles up/down itself
579
+ const { value } = this.state.prompt;
580
+ const next = (0, history_1.historyPrevious)(this.history, value);
581
+ this.history = next;
582
+ const displayValue = (0, history_1.historyCurrentValue)(next, value);
583
+ this.dispatch({ type: 'setPrompt', value: displayValue, cursor: displayValue.length });
584
+ }
585
+ /** Navigate to next history entry (or back to draft). */
586
+ historyForward() {
587
+ if (this.state.overlay)
588
+ return;
589
+ const { value } = this.state.prompt;
590
+ const next = (0, history_1.historyNext)(this.history);
591
+ this.history = next;
592
+ const displayValue = (0, history_1.historyCurrentValue)(next, value);
593
+ this.dispatch({ type: 'setPrompt', value: displayValue, cursor: displayValue.length });
594
+ }
595
+ /**
596
+ * Up in a multi-line prompt moves the cursor to the previous visual line;
597
+ * only at the first line does it fall back to command history (matching
598
+ * shell/editor behaviour). Single-line prompts behave exactly as before.
599
+ */
600
+ moveCursorUpOrHistory() {
601
+ if (this.state.overlay)
602
+ return;
603
+ const { value, cursor } = this.state.prompt;
604
+ const { line, col } = lineColOfCursor(value, cursor);
605
+ if (line > 0) {
606
+ this.dispatch({ type: 'setPrompt', value, cursor: cursorOfLineCol(value, line - 1, col) });
607
+ }
608
+ else {
609
+ this.historyBack();
610
+ }
611
+ }
612
+ /**
613
+ * Down in a multi-line prompt moves the cursor to the next visual line;
614
+ * only at the last line does it fall back to command history.
615
+ */
616
+ moveCursorDownOrHistory() {
617
+ if (this.state.overlay)
618
+ return;
619
+ const { value, cursor } = this.state.prompt;
620
+ const lines = value.split('\n');
621
+ const { line, col } = lineColOfCursor(value, cursor);
622
+ if (line < lines.length - 1) {
623
+ this.dispatch({ type: 'setPrompt', value, cursor: cursorOfLineCol(value, line + 1, col) });
624
+ }
625
+ else {
626
+ this.historyForward();
627
+ }
628
+ }
629
+ /** Home: move to the start of the current visual line (not whole buffer). */
630
+ moveCursorLineHome() {
631
+ if (this.state.overlay)
632
+ return;
633
+ const { value, cursor } = this.state.prompt;
634
+ const { line } = lineColOfCursor(value, cursor);
635
+ this.dispatch({ type: 'setPrompt', value, cursor: cursorOfLineCol(value, line, 0) });
636
+ }
637
+ /** End: move to the end of the current visual line (not whole buffer). */
638
+ moveCursorLineEnd() {
639
+ if (this.state.overlay)
640
+ return;
641
+ const { value, cursor } = this.state.prompt;
642
+ const { line } = lineColOfCursor(value, cursor);
643
+ const lines = value.split('\n');
644
+ this.dispatch({
645
+ type: 'setPrompt',
646
+ value,
647
+ cursor: cursorOfLineCol(value, line, lines[line].length),
648
+ });
649
+ }
306
650
  submitPrompt(options = {}) {
307
651
  const input = this.state.prompt.value;
308
652
  if (!input.trim() && !options.allowEmpty)
309
653
  return;
654
+ // Push to history before clearing.
655
+ this.history = (0, history_1.pushHistoryEntry)(this.history, input);
310
656
  this.dispatch({ type: 'setPrompt', value: '', cursor: 0 });
657
+ if (this.handleLocalCommand(input))
658
+ return;
311
659
  try {
312
660
  const submission = this.options.onSubmit?.(input);
313
661
  if (submission) {
@@ -321,6 +669,42 @@ class TuiRunner {
321
669
  reportSubmitFailure() {
322
670
  this.events.append({ role: 'error', content: 'Input submission failed.' });
323
671
  }
672
+ handleLocalCommand(input) {
673
+ const command = input.trim();
674
+ if (command === '/redraw') {
675
+ this.forceOwnedRedraw();
676
+ return true;
677
+ }
678
+ const match = command.match(/^\/tool-output(?:\s+(adaptive|collapsed|full))?$/u);
679
+ if (match) {
680
+ const mode = match[1];
681
+ if (mode)
682
+ this.dispatch({ type: 'setToolOutputViewMode', mode });
683
+ this.dispatch({
684
+ type: 'setStatus',
685
+ message: `tool output: ${mode ?? this.state.toolOutputViewMode}`,
686
+ });
687
+ return true;
688
+ }
689
+ if (command.startsWith('/tool-output')) {
690
+ this.dispatch({
691
+ type: 'setStatus',
692
+ message: 'Usage: /tool-output adaptive|collapsed|full',
693
+ });
694
+ return true;
695
+ }
696
+ return false;
697
+ }
698
+ forceOwnedRedraw() {
699
+ if (this.surface && !this.surfaceFailed) {
700
+ void this.surface
701
+ .forceRedraw(this.buildLiveFrame(this.width))
702
+ .catch(error => this.handleSurfaceError(error));
703
+ return;
704
+ }
705
+ this.lastFrame = null;
706
+ this.scheduler.request('immediate');
707
+ }
324
708
  updatePrompt(next) {
325
709
  this.dispatch({ type: 'setPrompt', value: next.value, cursor: next.cursor });
326
710
  this.syncPromptOverlay(next.value);
@@ -333,7 +717,7 @@ class TuiRunner {
333
717
  this.dispatch({
334
718
  type: 'showCommandPalette',
335
719
  query: value.slice(1),
336
- items: (0, pickers_1.visibleCommandItems)(value),
720
+ items: visibleTuiCommandItems(value),
337
721
  });
338
722
  return;
339
723
  }
@@ -355,7 +739,8 @@ class TuiRunner {
355
739
  if (!item)
356
740
  return;
357
741
  const command = (0, commands_1.getCommands)().find(candidate => candidate.name === item.value);
358
- const needsArgs = Boolean(command?.argumentHint || command?.params?.some(param => param.required));
742
+ const needsArgs = item.value === 'tool-output' ||
743
+ Boolean(command?.argumentHint || command?.params?.some(param => param.required));
359
744
  const value = `/${item.value}${needsArgs ? ' ' : ''}`;
360
745
  const promptAlreadyMatchesCommand = this.state.prompt.value.trim() === `/${item.value}`;
361
746
  const nextValue = promptAlreadyMatchesCommand ? `/${item.value}` : value;
@@ -382,6 +767,366 @@ class TuiRunner {
382
767
  this.dispatch({ type: 'closeOverlay' });
383
768
  void this.options.onPermissionDecision?.(overlay.request.id, approved);
384
769
  }
770
+ /** Restore any temporary Inspector terminal ownership before shutdown. */
771
+ async closeModalSurface() {
772
+ if (this.state.inspector || this.inspectorSurface?.isMounted) {
773
+ this.closeToolInspector();
774
+ }
775
+ await this.modalTransition;
776
+ }
777
+ /** Wait until the current Inspector ownership transition is complete. */
778
+ async waitForModalSurface() {
779
+ await this.modalTransition;
780
+ }
781
+ /** Flush every finalized transcript batch, including batches queued by acknowledgements. */
782
+ async flushTranscriptCommits() {
783
+ if (!this.surface || this.surfaceFailed)
784
+ return;
785
+ for (let attempt = 0; attempt < 1024; attempt += 1) {
786
+ await this.surface.whenIdle();
787
+ await Promise.resolve();
788
+ if (!this.commitInFlight && (0, state_1.pendingCommitRecords)(this.state).length === 0)
789
+ return;
790
+ if (!this.commitInFlight)
791
+ this.tryCommit(this.state, true);
792
+ }
793
+ throw new Error('TUI transcript commits did not settle');
794
+ }
795
+ openToolInspector() {
796
+ if (this.state.inspector || !this.inspectorController || !this.inspectorSurface)
797
+ return;
798
+ this.inputOwnership.capture('inspector', {
799
+ value: this.state.prompt.value,
800
+ cursor: this.state.prompt.cursor,
801
+ parserState: this.parser.getState(),
802
+ });
803
+ this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'openToolInspector' });
804
+ this.inspectorReady = false;
805
+ this.inspectorSearchActive = false;
806
+ this.modalTransition = this.modalTransition
807
+ .then(async () => {
808
+ await this.surface?.suspend();
809
+ this.inspectorReady = true;
810
+ await this.inspectorSurface.mount(this.buildInspectorFrame());
811
+ })
812
+ .catch(error => this.handleSurfaceError(error));
813
+ }
814
+ closeToolInspector() {
815
+ if (!this.state.inspector && !this.inspectorSurface?.isMounted)
816
+ return;
817
+ this.inspectorController?.cancel();
818
+ this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'closeToolInspector' });
819
+ const snapshot = this.inputOwnership.restore();
820
+ if (snapshot) {
821
+ this.parser.setState(snapshot.parserState);
822
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
823
+ type: 'setPrompt',
824
+ value: snapshot.value,
825
+ cursor: snapshot.cursor,
826
+ });
827
+ }
828
+ this.inspectorReady = false;
829
+ this.inspectorSearchActive = false;
830
+ this.modalTransition = this.modalTransition
831
+ .then(async () => {
832
+ await this.inspectorSurface?.unmount();
833
+ if (this.surface) {
834
+ await this.surface.restore(() => this.buildLiveFrame(this.width), this.width, this.height);
835
+ }
836
+ this.scheduler.request('immediate');
837
+ })
838
+ .catch(error => this.handleSurfaceError(error));
839
+ }
840
+ applyInspectorInput(event) {
841
+ if (event.type === 'paste')
842
+ return;
843
+ if (event.type === 'text') {
844
+ if (!this.inspectorSearchActive) {
845
+ if (event.value === 'j') {
846
+ this.moveInspectorSelection(1);
847
+ return;
848
+ }
849
+ if (event.value === 'k') {
850
+ this.moveInspectorSelection(-1);
851
+ return;
852
+ }
853
+ if (event.value === ' ') {
854
+ void this.loadSelectedInspectorDetail(false);
855
+ return;
856
+ }
857
+ if (event.value === 'g') {
858
+ this.moveInspectorSelectionTo('first');
859
+ return;
860
+ }
861
+ if (event.value === 'G') {
862
+ this.moveInspectorSelectionTo('last');
863
+ return;
864
+ }
865
+ if (event.value === 'n' && this.state.inspector?.searchQuery) {
866
+ this.moveInspectorSelection(1, true);
867
+ return;
868
+ }
869
+ if (event.value === 'N' && this.state.inspector?.searchQuery) {
870
+ this.moveInspectorSelection(-1, true);
871
+ return;
872
+ }
873
+ if (event.value === 'v') {
874
+ this.openSelectedInspectorDetailInEditor();
875
+ return;
876
+ }
877
+ }
878
+ if (!this.inspectorSearchActive && (event.value === 'q' || event.value === 'Q')) {
879
+ this.closeToolInspector();
880
+ return;
881
+ }
882
+ if (!this.inspectorSearchActive && event.value === '/') {
883
+ this.inspectorSearchActive = true;
884
+ this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'setToolInspectorSearch', query: '' });
885
+ this.paintInspector();
886
+ return;
887
+ }
888
+ if (!this.inspectorSearchActive && event.value === '[') {
889
+ this.exportInspectorResults();
890
+ return;
891
+ }
892
+ if (this.inspectorSearchActive) {
893
+ const query = `${this.state.inspector?.searchQuery ?? ''}${event.value}`;
894
+ this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'setToolInspectorSearch', query });
895
+ this.paintInspector();
896
+ }
897
+ return;
898
+ }
899
+ switch (event.key) {
900
+ case 'ctrl+o':
901
+ case 'escape':
902
+ this.closeToolInspector();
903
+ return;
904
+ case 'ctrl+c':
905
+ this.closeToolInspector();
906
+ return;
907
+ case 'up':
908
+ this.moveInspectorSelection(-1);
909
+ return;
910
+ case 'down':
911
+ this.moveInspectorSelection(1);
912
+ return;
913
+ case 'pageup':
914
+ case 'ctrl+u':
915
+ this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'scrollToolInspector', delta: -10 });
916
+ this.paintInspector();
917
+ return;
918
+ case 'pagedown':
919
+ case 'ctrl+d':
920
+ this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'scrollToolInspector', delta: 10 });
921
+ void this.loadSelectedInspectorDetail(true);
922
+ return;
923
+ case 'home':
924
+ this.moveInspectorSelectionTo('first');
925
+ return;
926
+ case 'end':
927
+ this.moveInspectorSelectionTo('last');
928
+ return;
929
+ case 'backspace': {
930
+ if (!this.inspectorSearchActive)
931
+ return;
932
+ const query = this.state.inspector?.searchQuery ?? '';
933
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
934
+ type: 'setToolInspectorSearch',
935
+ query: query.slice(0, -1),
936
+ });
937
+ this.paintInspector();
938
+ return;
939
+ }
940
+ case 'enter':
941
+ if (this.inspectorSearchActive) {
942
+ this.inspectorSearchActive = false;
943
+ this.paintInspector();
944
+ return;
945
+ }
946
+ void this.loadSelectedInspectorDetail(false);
947
+ return;
948
+ case 'ctrl+e':
949
+ this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'toggleAllToolInspectorEntries' });
950
+ this.paintInspector();
951
+ return;
952
+ case 'ctrl+l':
953
+ this.paintInspector();
954
+ return;
955
+ }
956
+ }
957
+ async loadSelectedInspectorDetail(append) {
958
+ const inspector = this.state.inspector;
959
+ if (!inspector || !this.inspectorController)
960
+ return;
961
+ const view = this.inspectorController.view(this.state.recentToolDetails, inspector);
962
+ const selected = view.selected;
963
+ if (!selected)
964
+ return;
965
+ const expanded = inspector.expandedCallIds.includes(selected.callId);
966
+ if (!append && expanded) {
967
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
968
+ type: 'toggleToolInspectorEntry',
969
+ callId: selected.callId,
970
+ });
971
+ this.paintInspector();
972
+ return;
973
+ }
974
+ if (!expanded) {
975
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
976
+ type: 'toggleToolInspectorEntry',
977
+ callId: selected.callId,
978
+ });
979
+ }
980
+ this.paintInspector();
981
+ await this.inspectorController.load(selected, append && view.detail?.nextOffsetBytes !== undefined);
982
+ if (this.state.inspector)
983
+ this.paintInspector();
984
+ }
985
+ moveInspectorSelection(delta, wrap = false) {
986
+ const inspector = this.state.inspector;
987
+ if (!inspector || !this.inspectorController)
988
+ return;
989
+ const view = this.inspectorController.view(this.state.recentToolDetails, inspector);
990
+ if (view.entries.length === 0)
991
+ return;
992
+ const target = wrap
993
+ ? (view.selectedIndex + delta + view.entries.length) % view.entries.length
994
+ : Math.max(0, Math.min(view.entries.length - 1, view.selectedIndex + delta));
995
+ this.inspectorController.cancel();
996
+ this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'setToolInspectorSelection', index: target });
997
+ this.paintInspector();
998
+ }
999
+ moveInspectorSelectionTo(edge) {
1000
+ const inspector = this.state.inspector;
1001
+ if (!inspector || !this.inspectorController)
1002
+ return;
1003
+ const view = this.inspectorController.view(this.state.recentToolDetails, inspector);
1004
+ if (view.entries.length === 0)
1005
+ return;
1006
+ this.inspectorController.cancel();
1007
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
1008
+ type: 'setToolInspectorSelection',
1009
+ index: edge === 'first' ? 0 : view.entries.length - 1,
1010
+ });
1011
+ this.paintInspector();
1012
+ }
1013
+ exportInspectorResults() {
1014
+ const inspector = this.state.inspector;
1015
+ if (!inspector || !this.inspectorController)
1016
+ return;
1017
+ const entries = this.inspectorController.view(this.state.recentToolDetails, inspector).entries;
1018
+ this.closeToolInspector();
1019
+ this.modalTransition = this.modalTransition
1020
+ .then(async () => {
1021
+ if (!this.surface) {
1022
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
1023
+ type: 'setStatus',
1024
+ message: 'Inspector export requires an inline terminal surface.',
1025
+ });
1026
+ return;
1027
+ }
1028
+ for (const entry of entries) {
1029
+ let pageIndex = 0;
1030
+ for await (const page of this.inspectorController.readPages(entry)) {
1031
+ const heading = pageIndex === 0 ? `#${entry.sequence} ${entry.toolName}\n` : '';
1032
+ const rows = (0, transcript_layout_1.layoutTranscriptEntry)({
1033
+ role: 'tool',
1034
+ content: `${heading}${page.content}`,
1035
+ }, {
1036
+ width: this.transcriptWidth,
1037
+ theme: this.theme,
1038
+ toolOutputMode: 'full',
1039
+ });
1040
+ await this.surface.commit({
1041
+ batchId: `tui-inspector-export-${++this.commitSequence}`,
1042
+ generation: this.state.transcriptGeneration,
1043
+ reason: 'append',
1044
+ entries: [
1045
+ {
1046
+ displayKey: `inspector-export-${entry.callId}-${pageIndex}`,
1047
+ rows,
1048
+ },
1049
+ ],
1050
+ }, () => this.buildLiveFrame(this.width));
1051
+ pageIndex += 1;
1052
+ }
1053
+ }
1054
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
1055
+ type: 'setStatus',
1056
+ message: `Exported ${entries.length} tool result${entries.length === 1 ? '' : 's'} to scrollback.`,
1057
+ });
1058
+ this.scheduler.request('immediate');
1059
+ })
1060
+ .catch(error => this.handleSurfaceError(error));
1061
+ }
1062
+ openSelectedInspectorDetailInEditor() {
1063
+ const inspector = this.state.inspector;
1064
+ if (!inspector || !this.inspectorController)
1065
+ return;
1066
+ const selected = this.inspectorController.view(this.state.recentToolDetails, inspector).selected;
1067
+ if (!selected)
1068
+ return;
1069
+ if (!this.options.onOpenExternalEditor) {
1070
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
1071
+ type: 'setStatus',
1072
+ message: 'External editor is unavailable in this TUI environment.',
1073
+ });
1074
+ this.paintInspector();
1075
+ return;
1076
+ }
1077
+ this.closeToolInspector();
1078
+ this.modalTransition = this.modalTransition
1079
+ .then(async () => {
1080
+ const directory = await (0, promises_1.mkdtemp)((0, path_1.join)((0, os_1.tmpdir)(), 'openhorse-tool-'));
1081
+ const filePath = (0, path_1.join)(directory, `${selected.sequence}-${safeFileName(selected.toolName)}.txt`);
1082
+ try {
1083
+ await this.inspectorController.writeDetailToFile(selected, filePath);
1084
+ await this.options.onOpenExternalEditor(filePath);
1085
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
1086
+ type: 'setStatus',
1087
+ message: `Viewed #${selected.sequence} ${selected.toolName} in external editor.`,
1088
+ });
1089
+ }
1090
+ finally {
1091
+ await (0, promises_1.rm)(directory, { recursive: true, force: true });
1092
+ }
1093
+ this.forceOwnedRedraw();
1094
+ })
1095
+ .catch(error => {
1096
+ this.state = (0, state_1.tuiUiReducer)(this.state, {
1097
+ type: 'setStatus',
1098
+ message: `External editor failed: ${error instanceof Error ? error.message : String(error)}`,
1099
+ });
1100
+ this.forceOwnedRedraw();
1101
+ });
1102
+ }
1103
+ paintInspector() {
1104
+ if (!this.state.inspector ||
1105
+ !this.inspectorController ||
1106
+ !this.inspectorSurface ||
1107
+ !this.inspectorReady)
1108
+ return;
1109
+ const frame = this.buildInspectorFrame();
1110
+ this.lastFrame = frame;
1111
+ void this.inspectorSurface.paint(frame).catch(error => this.handleSurfaceError(error));
1112
+ }
1113
+ buildInspectorFrame() {
1114
+ const inspector = this.state.inspector;
1115
+ const view = inspector && this.inspectorController
1116
+ ? this.inspectorController.view(this.state.recentToolDetails, inspector)
1117
+ : {
1118
+ entries: [],
1119
+ selectedIndex: 0,
1120
+ expandedCallIds: [],
1121
+ searchQuery: '',
1122
+ detailOffset: 0,
1123
+ };
1124
+ return (0, transcript_inspector_layout_1.renderTranscriptInspectorFrame)(view, {
1125
+ width: this.width,
1126
+ height: this.height,
1127
+ theme: this.theme,
1128
+ });
1129
+ }
385
1130
  }
386
1131
  exports.TuiRunner = TuiRunner;
387
1132
  function insertAtCursor(value, cursor, text) {
@@ -391,6 +1136,28 @@ function insertAtCursor(value, cursor, text) {
391
1136
  cursor: safeCursor + text.length,
392
1137
  };
393
1138
  }
1139
+ function visibleTuiCommandItems(input) {
1140
+ const query = input.startsWith('/')
1141
+ ? input.slice(1).trim().split(/\s+/u, 1)[0].toLowerCase()
1142
+ : '';
1143
+ const local = [
1144
+ {
1145
+ value: 'tool-output',
1146
+ label: '/tool-output',
1147
+ description: 'Set TUI tool output mode',
1148
+ },
1149
+ {
1150
+ value: 'redraw',
1151
+ label: '/redraw',
1152
+ description: 'Redraw the TUI-owned live region',
1153
+ },
1154
+ ].filter(item => item.value.includes(query));
1155
+ const localNames = new Set(local.map(item => item.value));
1156
+ return [...local, ...(0, pickers_1.visibleCommandItems)(input).filter(item => !localNames.has(item.value))];
1157
+ }
1158
+ function safeFileName(value) {
1159
+ return value.replace(/[^a-zA-Z0-9._-]+/gu, '-').replace(/^-+|-+$/gu, '') || 'tool';
1160
+ }
394
1161
  function deleteBeforeCursor(value, cursor) {
395
1162
  const safeCursor = clampCursor(value, cursor);
396
1163
  if (safeCursor === 0)
@@ -460,4 +1227,29 @@ function clampCursor(value, cursor) {
460
1227
  return value.length;
461
1228
  return Math.min(Math.max(0, Math.floor(cursor)), value.length);
462
1229
  }
1230
+ /** Map an absolute character cursor to {line, col} within a multi-line value. */
1231
+ function lineColOfCursor(value, cursor) {
1232
+ const lines = value.split('\n');
1233
+ const safeCursor = clampCursor(value, cursor);
1234
+ let offset = 0;
1235
+ for (let i = 0; i < lines.length; i++) {
1236
+ const lineLen = lines[i].length;
1237
+ if (safeCursor <= offset + lineLen) {
1238
+ return { line: i, col: safeCursor - offset };
1239
+ }
1240
+ offset += lineLen + 1; // +1 for the newline separator
1241
+ }
1242
+ const last = lines.length - 1;
1243
+ return { line: last, col: lines[last].length };
1244
+ }
1245
+ /** Map a {line, col} back to an absolute character cursor within a multi-line value. */
1246
+ function cursorOfLineCol(value, line, col) {
1247
+ const lines = value.split('\n');
1248
+ const safeLine = Math.max(0, Math.min(line, lines.length - 1));
1249
+ let offset = 0;
1250
+ for (let i = 0; i < safeLine; i++) {
1251
+ offset += lines[i].length + 1;
1252
+ }
1253
+ return offset + Math.max(0, Math.min(col, lines[safeLine].length));
1254
+ }
463
1255
  //# sourceMappingURL=runner.js.map