openhorse 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/README.md +23 -0
  2. package/dist/cli.js +163 -22
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/index.d.ts.map +1 -1
  5. package/dist/commands/index.js +161 -22
  6. package/dist/commands/index.js.map +1 -1
  7. package/dist/framework/prompt.d.ts +4 -0
  8. package/dist/framework/prompt.d.ts.map +1 -1
  9. package/dist/framework/prompt.js +18 -0
  10. package/dist/framework/prompt.js.map +1 -1
  11. package/dist/framework/query.d.ts +7 -0
  12. package/dist/framework/query.d.ts.map +1 -1
  13. package/dist/framework/query.js +14 -0
  14. package/dist/framework/query.js.map +1 -1
  15. package/dist/framework/store.d.ts +3 -1
  16. package/dist/framework/store.d.ts.map +1 -1
  17. package/dist/framework/store.js +1 -0
  18. package/dist/framework/store.js.map +1 -1
  19. package/dist/harness/context-harness.d.ts +14 -0
  20. package/dist/harness/context-harness.d.ts.map +1 -1
  21. package/dist/harness/context-harness.js +21 -0
  22. package/dist/harness/context-harness.js.map +1 -1
  23. package/dist/harness/evidence.d.ts.map +1 -1
  24. package/dist/harness/evidence.js +2 -1
  25. package/dist/harness/evidence.js.map +1 -1
  26. package/dist/index.d.ts +0 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1 -4
  29. package/dist/index.js.map +1 -1
  30. package/dist/ink-ui/App.d.ts +4 -1
  31. package/dist/ink-ui/App.d.ts.map +1 -1
  32. package/dist/ink-ui/App.js +2 -2
  33. package/dist/ink-ui/App.js.map +1 -1
  34. package/dist/ink-ui/components/NativeCursor.d.ts +12 -0
  35. package/dist/ink-ui/components/NativeCursor.d.ts.map +1 -0
  36. package/dist/ink-ui/components/NativeCursor.js +35 -0
  37. package/dist/ink-ui/components/NativeCursor.js.map +1 -0
  38. package/dist/ink-ui/components/PromptInput.d.ts +5 -1
  39. package/dist/ink-ui/components/PromptInput.d.ts.map +1 -1
  40. package/dist/ink-ui/components/PromptInput.js +10 -6
  41. package/dist/ink-ui/components/PromptInput.js.map +1 -1
  42. package/dist/ink-ui/components/RunningHorseIndicator.d.ts +1 -1
  43. package/dist/ink-ui/components/RunningHorseIndicator.d.ts.map +1 -1
  44. package/dist/ink-ui/components/RunningHorseIndicator.js +3 -41
  45. package/dist/ink-ui/components/RunningHorseIndicator.js.map +1 -1
  46. package/dist/ink-ui/components/SelectList.d.ts +2 -1
  47. package/dist/ink-ui/components/SelectList.d.ts.map +1 -1
  48. package/dist/ink-ui/components/SelectList.js +26 -11
  49. package/dist/ink-ui/components/SelectList.js.map +1 -1
  50. package/dist/ink-ui/components/StatusLine.d.ts.map +1 -1
  51. package/dist/ink-ui/components/StatusLine.js +22 -31
  52. package/dist/ink-ui/components/StatusLine.js.map +1 -1
  53. package/dist/ink-ui/components/TerminalCursor.d.ts +4 -2
  54. package/dist/ink-ui/components/TerminalCursor.d.ts.map +1 -1
  55. package/dist/ink-ui/components/TerminalCursor.js +18 -43
  56. package/dist/ink-ui/components/TerminalCursor.js.map +1 -1
  57. package/dist/ink-ui/components/ToolActivity.d.ts +17 -0
  58. package/dist/ink-ui/components/ToolActivity.d.ts.map +1 -0
  59. package/dist/ink-ui/components/ToolActivity.js +107 -0
  60. package/dist/ink-ui/components/ToolActivity.js.map +1 -0
  61. package/dist/ink-ui/components/Transcript.d.ts +7 -1
  62. package/dist/ink-ui/components/Transcript.d.ts.map +1 -1
  63. package/dist/ink-ui/components/Transcript.js +40 -3
  64. package/dist/ink-ui/components/Transcript.js.map +1 -1
  65. package/dist/ink-ui/controllers/chat-controller.d.ts +1 -33
  66. package/dist/ink-ui/controllers/chat-controller.d.ts.map +1 -1
  67. package/dist/ink-ui/controllers/chat-controller.js +15 -437
  68. package/dist/ink-ui/controllers/chat-controller.js.map +1 -1
  69. package/dist/ink-ui/hooks/use-raw-input-bridge.d.ts +6 -0
  70. package/dist/ink-ui/hooks/use-raw-input-bridge.d.ts.map +1 -0
  71. package/dist/ink-ui/hooks/use-raw-input-bridge.js +27 -0
  72. package/dist/ink-ui/hooks/use-raw-input-bridge.js.map +1 -0
  73. package/dist/ink-ui/hooks/use-terminal-size.d.ts +7 -0
  74. package/dist/ink-ui/hooks/use-terminal-size.d.ts.map +1 -0
  75. package/dist/ink-ui/hooks/use-terminal-size.js +34 -0
  76. package/dist/ink-ui/hooks/use-terminal-size.js.map +1 -0
  77. package/dist/ink-ui/launch.d.ts +6 -0
  78. package/dist/ink-ui/launch.d.ts.map +1 -1
  79. package/dist/ink-ui/launch.js +60 -2
  80. package/dist/ink-ui/launch.js.map +1 -1
  81. package/dist/ink-ui/runtime/grapheme.d.ts +9 -0
  82. package/dist/ink-ui/runtime/grapheme.d.ts.map +1 -0
  83. package/dist/ink-ui/runtime/grapheme.js +63 -0
  84. package/dist/ink-ui/runtime/grapheme.js.map +1 -0
  85. package/dist/ink-ui/runtime/input-buffer.d.ts +32 -0
  86. package/dist/ink-ui/runtime/input-buffer.d.ts.map +1 -0
  87. package/dist/ink-ui/runtime/input-buffer.js +165 -0
  88. package/dist/ink-ui/runtime/input-buffer.js.map +1 -0
  89. package/dist/ink-ui/runtime/layout-budget.d.ts +13 -0
  90. package/dist/ink-ui/runtime/layout-budget.d.ts.map +1 -0
  91. package/dist/ink-ui/runtime/layout-budget.js +25 -0
  92. package/dist/ink-ui/runtime/layout-budget.js.map +1 -0
  93. package/dist/ink-ui/runtime/native-cursor.d.ts +62 -0
  94. package/dist/ink-ui/runtime/native-cursor.d.ts.map +1 -0
  95. package/dist/ink-ui/runtime/native-cursor.js +423 -0
  96. package/dist/ink-ui/runtime/native-cursor.js.map +1 -0
  97. package/dist/ink-ui/runtime/prompt-layout.d.ts +20 -1
  98. package/dist/ink-ui/runtime/prompt-layout.d.ts.map +1 -1
  99. package/dist/ink-ui/runtime/prompt-layout.js +129 -11
  100. package/dist/ink-ui/runtime/prompt-layout.js.map +1 -1
  101. package/dist/ink-ui/runtime/raw-input.d.ts +4 -0
  102. package/dist/ink-ui/runtime/raw-input.d.ts.map +1 -0
  103. package/dist/ink-ui/runtime/raw-input.js +15 -0
  104. package/dist/ink-ui/runtime/raw-input.js.map +1 -0
  105. package/dist/ink-ui/runtime/stdout.d.ts +11 -0
  106. package/dist/ink-ui/runtime/stdout.d.ts.map +1 -0
  107. package/dist/ink-ui/runtime/stdout.js +82 -0
  108. package/dist/ink-ui/runtime/stdout.js.map +1 -0
  109. package/dist/ink-ui/runtime/transcript-state.d.ts +37 -0
  110. package/dist/ink-ui/runtime/transcript-state.d.ts.map +1 -0
  111. package/dist/ink-ui/runtime/transcript-state.js +89 -0
  112. package/dist/ink-ui/runtime/transcript-state.js.map +1 -0
  113. package/dist/ink-ui/screens/ReplScreen.d.ts +8 -2
  114. package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
  115. package/dist/ink-ui/screens/ReplScreen.js +247 -151
  116. package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
  117. package/dist/ink-ui/types.d.ts +1 -42
  118. package/dist/ink-ui/types.d.ts.map +1 -1
  119. package/dist/ink-ui/types.js +15 -0
  120. package/dist/ink-ui/types.js.map +1 -1
  121. package/dist/print-ui/launch.d.ts +8 -0
  122. package/dist/print-ui/launch.d.ts.map +1 -0
  123. package/dist/print-ui/launch.js +205 -0
  124. package/dist/print-ui/launch.js.map +1 -0
  125. package/dist/runtime/agent-runtime-controller.d.ts +69 -0
  126. package/dist/runtime/agent-runtime-controller.d.ts.map +1 -0
  127. package/dist/runtime/agent-runtime-controller.js +225 -0
  128. package/dist/runtime/agent-runtime-controller.js.map +1 -0
  129. package/dist/runtime/agent-runtime-protocol.d.ts +90 -0
  130. package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -0
  131. package/dist/runtime/agent-runtime-protocol.js +80 -0
  132. package/dist/runtime/agent-runtime-protocol.js.map +1 -0
  133. package/dist/runtime/chat-controller.d.ts +41 -0
  134. package/dist/runtime/chat-controller.d.ts.map +1 -0
  135. package/dist/runtime/chat-controller.js +577 -0
  136. package/dist/runtime/chat-controller.js.map +1 -0
  137. package/dist/runtime/ui-events.d.ts +59 -0
  138. package/dist/runtime/ui-events.d.ts.map +1 -0
  139. package/dist/runtime/ui-events.js +3 -0
  140. package/dist/runtime/ui-events.js.map +1 -0
  141. package/dist/services/commit-plan.d.ts +17 -0
  142. package/dist/services/commit-plan.d.ts.map +1 -0
  143. package/dist/services/commit-plan.js +161 -0
  144. package/dist/services/commit-plan.js.map +1 -0
  145. package/dist/services/compact/auto-compact.d.ts.map +1 -1
  146. package/dist/services/compact/auto-compact.js +3 -2
  147. package/dist/services/compact/auto-compact.js.map +1 -1
  148. package/dist/services/compact/compact.d.ts +3 -0
  149. package/dist/services/compact/compact.d.ts.map +1 -1
  150. package/dist/services/compact/compact.js +4 -1
  151. package/dist/services/compact/compact.js.map +1 -1
  152. package/dist/services/compact/summary-generator.d.ts +11 -0
  153. package/dist/services/compact/summary-generator.d.ts.map +1 -1
  154. package/dist/services/compact/summary-generator.js +57 -0
  155. package/dist/services/compact/summary-generator.js.map +1 -1
  156. package/dist/services/config.d.ts +3 -2
  157. package/dist/services/config.d.ts.map +1 -1
  158. package/dist/services/config.js +7 -6
  159. package/dist/services/config.js.map +1 -1
  160. package/dist/services/doctor.d.ts +33 -0
  161. package/dist/services/doctor.d.ts.map +1 -0
  162. package/dist/services/doctor.js +211 -0
  163. package/dist/services/doctor.js.map +1 -0
  164. package/dist/services/file-context.d.ts +21 -0
  165. package/dist/services/file-context.d.ts.map +1 -0
  166. package/dist/services/file-context.js +194 -0
  167. package/dist/services/file-context.js.map +1 -0
  168. package/dist/services/global-config.d.ts +1 -1
  169. package/dist/services/global-config.d.ts.map +1 -1
  170. package/dist/services/project-instructions.d.ts +16 -0
  171. package/dist/services/project-instructions.d.ts.map +1 -0
  172. package/dist/services/project-instructions.js +135 -0
  173. package/dist/services/project-instructions.js.map +1 -0
  174. package/dist/services/prompt-context.d.ts +3 -0
  175. package/dist/services/prompt-context.d.ts.map +1 -0
  176. package/dist/services/prompt-context.js +13 -0
  177. package/dist/services/prompt-context.js.map +1 -0
  178. package/dist/services/session-index.d.ts +45 -0
  179. package/dist/services/session-index.d.ts.map +1 -0
  180. package/dist/services/session-index.js +183 -0
  181. package/dist/services/session-index.js.map +1 -0
  182. package/dist/services/session-storage.d.ts +3 -0
  183. package/dist/services/session-storage.d.ts.map +1 -1
  184. package/dist/services/session-storage.js +21 -0
  185. package/dist/services/session-storage.js.map +1 -1
  186. package/dist/services/workspace-diff.d.ts +27 -0
  187. package/dist/services/workspace-diff.d.ts.map +1 -0
  188. package/dist/services/workspace-diff.js +129 -0
  189. package/dist/services/workspace-diff.js.map +1 -0
  190. package/dist/terminal-ui/completion.d.ts +6 -0
  191. package/dist/terminal-ui/completion.d.ts.map +1 -0
  192. package/dist/terminal-ui/completion.js +80 -0
  193. package/dist/terminal-ui/completion.js.map +1 -0
  194. package/dist/terminal-ui/editor.d.ts +17 -0
  195. package/dist/terminal-ui/editor.d.ts.map +1 -0
  196. package/dist/terminal-ui/editor.js +42 -0
  197. package/dist/terminal-ui/editor.js.map +1 -0
  198. package/dist/terminal-ui/launch.d.ts +23 -0
  199. package/dist/terminal-ui/launch.d.ts.map +1 -0
  200. package/dist/terminal-ui/launch.js +567 -0
  201. package/dist/terminal-ui/launch.js.map +1 -0
  202. package/dist/terminal-ui/raw-editor.d.ts +57 -0
  203. package/dist/terminal-ui/raw-editor.d.ts.map +1 -0
  204. package/dist/terminal-ui/raw-editor.js +339 -0
  205. package/dist/terminal-ui/raw-editor.js.map +1 -0
  206. package/dist/tools/index.d.ts.map +1 -1
  207. package/dist/tools/index.js +269 -25
  208. package/dist/tools/index.js.map +1 -1
  209. package/dist/tui-core/frame.d.ts +25 -0
  210. package/dist/tui-core/frame.d.ts.map +1 -0
  211. package/dist/tui-core/frame.js +104 -0
  212. package/dist/tui-core/frame.js.map +1 -0
  213. package/dist/tui-core/input-parser.d.ts +25 -0
  214. package/dist/tui-core/input-parser.d.ts.map +1 -0
  215. package/dist/tui-core/input-parser.js +207 -0
  216. package/dist/tui-core/input-parser.js.map +1 -0
  217. package/dist/tui-core/terminal-writer.d.ts +34 -0
  218. package/dist/tui-core/terminal-writer.d.ts.map +1 -0
  219. package/dist/tui-core/terminal-writer.js +92 -0
  220. package/dist/tui-core/terminal-writer.js.map +1 -0
  221. package/dist/tui-ui/launch.d.ts +7 -0
  222. package/dist/tui-ui/launch.d.ts.map +1 -0
  223. package/dist/tui-ui/launch.js +194 -0
  224. package/dist/tui-ui/launch.js.map +1 -0
  225. package/dist/tui-ui/layout.d.ts +9 -0
  226. package/dist/tui-ui/layout.d.ts.map +1 -0
  227. package/dist/tui-ui/layout.js +239 -0
  228. package/dist/tui-ui/layout.js.map +1 -0
  229. package/dist/tui-ui/pickers.d.ts +13 -0
  230. package/dist/tui-ui/pickers.d.ts.map +1 -0
  231. package/dist/tui-ui/pickers.js +79 -0
  232. package/dist/tui-ui/pickers.js.map +1 -0
  233. package/dist/tui-ui/runner.d.ts +43 -0
  234. package/dist/tui-ui/runner.d.ts.map +1 -0
  235. package/dist/tui-ui/runner.js +387 -0
  236. package/dist/tui-ui/runner.js.map +1 -0
  237. package/dist/tui-ui/state.d.ts +106 -0
  238. package/dist/tui-ui/state.d.ts.map +1 -0
  239. package/dist/tui-ui/state.js +202 -0
  240. package/dist/tui-ui/state.js.map +1 -0
  241. package/dist/utils/token-estimate.d.ts +42 -0
  242. package/dist/utils/token-estimate.d.ts.map +1 -0
  243. package/dist/utils/token-estimate.js +150 -0
  244. package/dist/utils/token-estimate.js.map +1 -0
  245. package/package.json +1 -1
@@ -37,24 +37,31 @@ exports.visibleCommandItems = visibleCommandItems;
37
37
  exports.getFileQuery = getFileQuery;
38
38
  exports.visibleFileItems = visibleFileItems;
39
39
  exports.sessionItems = sessionItems;
40
+ exports.permissionItems = permissionItems;
41
+ exports.normalizePastedInput = normalizePastedInput;
42
+ exports.isMultilinePasteValue = isMultilinePasteValue;
40
43
  exports.ReplScreen = ReplScreen;
41
44
  const react_1 = __importStar(require("react"));
42
45
  const ink_1 = require("ink");
43
46
  const fs_1 = require("fs");
44
47
  const path_1 = require("path");
45
48
  const commands_1 = require("../../commands");
46
- const parser_1 = require("../../commands/parser");
47
49
  const types_1 = require("../../commands/types");
48
- const turn_controller_1 = require("../../runtime/turn-controller");
50
+ const agent_runtime_controller_1 = require("../../runtime/agent-runtime-controller");
49
51
  const global_config_1 = require("../../services/global-config");
50
52
  const sessions_1 = require("../../ui-v2/state/sessions");
53
+ const NativeCursor_1 = require("../components/NativeCursor");
51
54
  const PromptInput_1 = require("../components/PromptInput");
52
55
  const PixelHorseBanner_1 = require("../components/PixelHorseBanner");
53
56
  const SelectList_1 = require("../components/SelectList");
54
57
  const StatusLine_1 = require("../components/StatusLine");
55
- const TerminalCursor_1 = require("../components/TerminalCursor");
56
58
  const Transcript_1 = require("../components/Transcript");
57
- const chat_controller_1 = require("../controllers/chat-controller");
59
+ const use_raw_input_bridge_1 = require("../hooks/use-raw-input-bridge");
60
+ const use_terminal_size_1 = require("../hooks/use-terminal-size");
61
+ const input_buffer_1 = require("../runtime/input-buffer");
62
+ const layout_budget_1 = require("../runtime/layout-budget");
63
+ const raw_input_1 = require("../runtime/raw-input");
64
+ const transcript_state_1 = require("../runtime/transcript-state");
58
65
  let nextTranscriptId = 1;
59
66
  function createId() {
60
67
  return `ui-${nextTranscriptId++}`;
@@ -144,162 +151,181 @@ function sessionItems(request) {
144
151
  ].filter(Boolean).join(' '),
145
152
  }));
146
153
  }
147
- function isExitCommand(input) {
148
- const parsed = (0, parser_1.parseInput)(input.trim());
149
- return parsed.isCommand && ['exit', 'quit', 'q'].includes(parsed.name);
154
+ function compactPermissionArgs(args) {
155
+ for (const key of ['path', 'file_path', 'file', 'cwd', 'command', 'pattern', 'query', 'url', 'target', 'sessionId']) {
156
+ const value = args[key];
157
+ if (typeof value === 'string') {
158
+ return value.length > 72 ? `${value.slice(0, 69)}...` : value;
159
+ }
160
+ }
161
+ const firstString = Object.values(args).find(value => typeof value === 'string');
162
+ if (typeof firstString === 'string') {
163
+ return firstString.length > 72 ? `${firstString.slice(0, 69)}...` : firstString;
164
+ }
165
+ return '';
166
+ }
167
+ function permissionItems(request) {
168
+ const detail = compactPermissionArgs(request.args);
169
+ return [
170
+ {
171
+ value: 'allow',
172
+ label: `Allow ${request.name}`,
173
+ description: [detail, request.reason].filter(Boolean).join(' '),
174
+ },
175
+ {
176
+ value: 'deny',
177
+ label: `Deny ${request.name}`,
178
+ description: 'Do not run this tool call',
179
+ },
180
+ ];
181
+ }
182
+ function normalizePastedInput(value) {
183
+ return value
184
+ .replace(/\x1b\[200~/g, '')
185
+ .replace(/\x1b\[201~/g, '')
186
+ .replace(/\r\n/g, '\n')
187
+ .replace(/\r/g, '\n');
150
188
  }
151
- function ReplScreen({ runtime }) {
189
+ function isMultilinePasteValue(value) {
190
+ if (!value)
191
+ return false;
192
+ const normalized = normalizePastedInput(value);
193
+ return normalized.length > 1 && normalized.includes('\n');
194
+ }
195
+ function ReplScreen({ runtime, cursorController, resizeEpoch = 0 }) {
152
196
  const app = (0, ink_1.useApp)();
153
197
  const { stdout } = (0, ink_1.useStdout)();
154
- const [transcript, setTranscript] = (0, react_1.useState)([]);
155
- const [input, setInput] = (0, react_1.useState)('');
198
+ const terminalSize = (0, use_terminal_size_1.useTerminalSize)(stdout);
199
+ const terminalHeight = terminalSize.height;
200
+ const terminalWidth = terminalSize.width;
201
+ const [transcriptState, dispatchTranscript] = (0, react_1.useReducer)(transcript_state_1.transcriptReducer, transcript_state_1.initialTranscriptState);
202
+ const [inputBuffer, dispatchInput] = (0, react_1.useReducer)(input_buffer_1.reduceInputBuffer, input_buffer_1.initialInputBuffer);
203
+ const input = inputBuffer.value;
204
+ const inputCursor = inputBuffer.cursor;
156
205
  const [overlay, setOverlay] = (0, react_1.useState)(null);
206
+ const layout = (0, react_1.useMemo)(() => (0, layout_budget_1.getInkLayoutBudget)(terminalWidth, terminalHeight, { overlayVisible: overlay !== null }), [terminalWidth, terminalHeight, overlay]);
207
+ const { layoutWidth, maxLiveTranscriptItems, maxOverlayItems, maxPromptRows } = layout;
157
208
  const [processing, setProcessing] = (0, react_1.useState)(false);
158
209
  const [statusMessage, setStatusMessage] = (0, react_1.useState)('');
210
+ const [exiting, setExiting] = (0, react_1.useState)(false);
159
211
  const [history, setHistory] = (0, react_1.useState)(() => (0, global_config_1.getInputHistory)());
160
212
  const [historyIndex, setHistoryIndex] = (0, react_1.useState)(-1);
161
213
  const [, setStoreVersion] = (0, react_1.useState)(0);
162
- const turnControllerRef = (0, react_1.useRef)(new turn_controller_1.TurnController({ exitConfirmWindowMs: 5000 }));
163
- const runningRef = (0, react_1.useRef)(false);
164
- const inputRef = (0, react_1.useRef)('');
214
+ const shuttingDownRef = (0, react_1.useRef)(false);
215
+ const lastCtrlCEventAtRef = (0, react_1.useRef)(0);
216
+ const inputRef = (0, react_1.useRef)(input_buffer_1.initialInputBuffer);
217
+ const promptBoxRef = (0, react_1.useRef)(null);
165
218
  (0, react_1.useEffect)(() => {
166
- inputRef.current = input;
167
- }, [input]);
219
+ inputRef.current = inputBuffer;
220
+ }, [inputBuffer]);
168
221
  (0, react_1.useEffect)(() => runtime.store.subscribe(() => setStoreVersion(version => version + 1)), [runtime.store]);
169
222
  const append = (0, react_1.useCallback)((entry) => {
170
223
  const id = createId();
171
- setTranscript(current => [...current, { id, ...entry }]);
224
+ const next = { id, ...entry };
225
+ dispatchTranscript({ type: 'append', entry: next });
172
226
  return id;
173
227
  }, []);
174
228
  const update = (0, react_1.useCallback)((id, patch) => {
175
- setTranscript(current => current.map(entry => entry.id === id ? { ...entry, ...patch } : entry));
229
+ dispatchTranscript({ type: 'update', id, patch });
230
+ }, []);
231
+ const finalize = (0, react_1.useCallback)((id, patch) => {
232
+ dispatchTranscript({ type: 'finalize', id, patch });
233
+ }, []);
234
+ const remove = (0, react_1.useCallback)((id) => {
235
+ dispatchTranscript({ type: 'remove', id });
176
236
  }, []);
177
237
  const events = (0, react_1.useMemo)(() => ({
178
238
  append,
179
239
  update,
180
- clearTranscript: () => setTranscript([]),
240
+ finalize,
241
+ remove,
242
+ replaceTranscript: entries => {
243
+ dispatchTranscript({ type: 'replace', entries });
244
+ },
245
+ clearTranscript: () => {
246
+ dispatchTranscript({ type: 'clear' });
247
+ },
181
248
  setStatus: setStatusMessage,
182
249
  showSessionPicker: request => setOverlay({ type: 'sessions', selectedIndex: 0, request }),
250
+ showPermissionRequest: request => setOverlay({ type: 'permission', selectedIndex: 0, request }),
183
251
  setProcessing,
184
- }), [append, update]);
185
- const controller = (0, react_1.useMemo)(() => new chat_controller_1.InkChatController(runtime, events), [runtime, events]);
186
- const shutdown = (0, react_1.useCallback)(async () => {
187
- await runtime.shutdown();
188
- app.exit();
189
- }, [app, runtime]);
190
- const appendSubmittedInput = (0, react_1.useCallback)((value) => {
191
- const parsed = (0, parser_1.parseInput)(value.trim());
192
- append({
193
- role: parsed.isCommand ? 'command' : 'user',
194
- title: parsed.isCommand ? 'command' : 'you',
195
- content: value,
196
- });
197
- }, [append]);
198
- const runTurn = (0, react_1.useCallback)(async (firstInput) => {
199
- let nextInput = firstInput;
200
- while (nextInput && nextInput.trim()) {
201
- appendSubmittedInput(nextInput);
202
- const turn = turnControllerRef.current.beginTurn(nextInput);
203
- runningRef.current = true;
204
- setProcessing(true);
205
- runtime.store.setProcessing(true);
206
- setStatusMessage('');
207
- try {
208
- await controller.runInput(nextInput, { abortSignal: turn.abortSignal });
209
- }
210
- finally {
211
- const revision = turnControllerRef.current.finishTurn(turn.id);
212
- if (revision?.trim()) {
213
- setStatusMessage('Restarting with latest instruction...');
214
- nextInput = revision;
215
- }
216
- else {
217
- nextInput = undefined;
218
- }
219
- }
220
- }
221
- runningRef.current = false;
222
- setProcessing(false);
252
+ }), [append, finalize, remove, stdout, update]);
253
+ const agentController = (0, react_1.useMemo)(() => new agent_runtime_controller_1.AgentRuntimeController({
254
+ runtime,
255
+ events,
256
+ exitConfirmWindowMs: 5000,
257
+ useRuntimeToolPermissions: true,
258
+ beforeTurn: () => setStatusMessage(''),
259
+ }), [runtime, events]);
260
+ const shutdown = (0, react_1.useCallback)(() => {
261
+ if (shuttingDownRef.current)
262
+ return;
263
+ shuttingDownRef.current = true;
264
+ cursorController.disable();
223
265
  runtime.store.setProcessing(false);
224
- }, [appendSubmittedInput, controller, runtime.store]);
266
+ setProcessing(false);
267
+ setExiting(true);
268
+ setTimeout(() => {
269
+ app.exit();
270
+ }, 50);
271
+ void runtime.shutdown().catch(error => {
272
+ const message = error instanceof Error ? error.message : String(error);
273
+ process.stderr.write(`OpenHorse shutdown warning: ${message}\n`);
274
+ });
275
+ }, [app, cursorController, runtime]);
225
276
  const submit = (0, react_1.useCallback)((value) => {
226
277
  const submitted = value.trim();
227
278
  if (!submitted)
228
279
  return;
229
- setInput('');
280
+ dispatchInput({ type: 'clear' });
230
281
  setOverlay(null);
231
282
  (0, global_config_1.addToInputHistory)(submitted);
232
283
  setHistory((0, global_config_1.getInputHistory)());
233
284
  setHistoryIndex(-1);
234
- if (isExitCommand(submitted)) {
285
+ const result = agentController.handle({ type: 'submit', text: submitted, source: 'composer' });
286
+ if (result.type === 'exit_requested') {
235
287
  void shutdown();
236
- return;
237
- }
238
- if (turnControllerRef.current.hasActiveTurn()) {
239
- const parsed = (0, parser_1.parseInput)(submitted);
240
- if (parsed.isCommand) {
241
- setStatusMessage('Command ignored while agent is running. Press Ctrl+C to interrupt first.');
242
- return;
243
- }
244
- turnControllerRef.current.clearExitIntent();
245
- turnControllerRef.current.requestRevision(submitted);
246
- setStatusMessage('Revision received. Interrupting current response...');
247
- return;
248
288
  }
249
- void runTurn(submitted);
250
- }, [runTurn, shutdown]);
289
+ }, [agentController, shutdown]);
290
+ const answerPermission = (0, react_1.useCallback)((request, approved) => {
291
+ setOverlay(null);
292
+ agentController.handle({
293
+ type: 'permission_decision',
294
+ requestId: request.id,
295
+ approved,
296
+ source: 'keyboard',
297
+ });
298
+ }, [agentController]);
251
299
  const closeOverlay = (0, react_1.useCallback)(() => {
252
300
  if (!overlay)
253
301
  return false;
302
+ if (overlay.type === 'permission') {
303
+ answerPermission(overlay.request, false);
304
+ return true;
305
+ }
254
306
  setOverlay(null);
255
307
  return true;
256
- }, [overlay]);
308
+ }, [answerPermission, overlay]);
257
309
  const handleCtrlC = (0, react_1.useCallback)(() => {
258
310
  if (closeOverlay()) {
259
- turnControllerRef.current.clearExitIntent();
260
- return;
261
- }
262
- if (turnControllerRef.current.hasActiveTurn()) {
263
- const shouldExit = turnControllerRef.current.registerExitIntent();
264
- turnControllerRef.current.interruptActiveTurn();
265
- if (shouldExit) {
266
- void shutdown();
267
- }
268
- else {
269
- setStatusMessage('again exits');
270
- }
311
+ agentController.handle({ type: 'clear_exit_intent' });
271
312
  return;
272
313
  }
273
- if (turnControllerRef.current.registerExitIntent()) {
314
+ const result = agentController.handle({ type: 'interrupt', source: 'keyboard' });
315
+ if (result.type === 'exit_requested') {
274
316
  void shutdown();
317
+ }
318
+ }, [agentController, closeOverlay, shutdown]);
319
+ const handleCtrlCEvent = (0, react_1.useCallback)((options = {}) => {
320
+ const now = Date.now();
321
+ const delta = now - lastCtrlCEventAtRef.current;
322
+ if (!options.allowRapidRepeat && delta < 30) {
275
323
  return;
276
324
  }
277
- setStatusMessage('again exits');
278
- }, [closeOverlay, shutdown]);
279
- (0, react_1.useEffect)(() => {
280
- let lastDataCtrlCAt = 0;
281
- const onData = (chunk) => {
282
- const text = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : chunk;
283
- const ctrlCCount = [...text].filter(char => char === '\u0003').length;
284
- if (ctrlCCount > 0) {
285
- lastDataCtrlCAt = Date.now();
286
- }
287
- for (let i = 0; i < ctrlCCount; i++) {
288
- handleCtrlC();
289
- }
290
- };
291
- const onSignal = () => {
292
- if (Date.now() - lastDataCtrlCAt < 50)
293
- return;
294
- handleCtrlC();
295
- };
296
- process.stdin.on('data', onData);
297
- process.on('SIGINT', onSignal);
298
- return () => {
299
- process.stdin.off('data', onData);
300
- process.off('SIGINT', onSignal);
301
- };
325
+ lastCtrlCEventAtRef.current = now;
326
+ handleCtrlC();
302
327
  }, [handleCtrlC]);
328
+ const lastRawInputRef = (0, use_raw_input_bridge_1.useRawInputBridge)(handleCtrlCEvent);
303
329
  const commandItems = visibleCommandItems(input);
304
330
  const fileItems = visibleFileItems(runtime.cwd, input);
305
331
  const completeCommand = (0, react_1.useCallback)((item, submitImmediately) => {
@@ -310,14 +336,14 @@ function ReplScreen({ runtime }) {
310
336
  submit(value);
311
337
  }
312
338
  else {
313
- setInput(value);
339
+ dispatchInput({ type: 'set', value });
314
340
  }
315
341
  }, [submit]);
316
342
  const completeFile = (0, react_1.useCallback)((item) => {
317
- const fileQuery = getFileQuery(inputRef.current);
343
+ const fileQuery = getFileQuery(inputRef.current.value);
318
344
  if (!fileQuery)
319
345
  return;
320
- setInput(`${fileQuery.base}@${item.value}`);
346
+ dispatchInput({ type: 'set', value: `${fileQuery.base}@${item.value}` });
321
347
  setOverlay(null);
322
348
  }, []);
323
349
  const selectSession = (0, react_1.useCallback)((request, index) => {
@@ -325,16 +351,26 @@ function ReplScreen({ runtime }) {
325
351
  if (!session)
326
352
  return;
327
353
  setOverlay(null);
328
- submit(`/resume ${session.id}${request.allProjects ? ' --all' : ''}`);
329
- }, [submit]);
354
+ const result = agentController.handle({
355
+ type: 'select_session',
356
+ sessionId: session.id,
357
+ allProjects: request.allProjects,
358
+ source: 'picker',
359
+ });
360
+ if (result.type === 'exit_requested') {
361
+ void shutdown();
362
+ }
363
+ }, [agentController, shutdown]);
330
364
  (0, ink_1.useInput)((value, key) => {
331
365
  const isReturn = key?.return || value === '\r' || value === '\n';
332
- if (value === '\u0003' || value?.includes('\u0003')) {
366
+ if (key?.ctrl && value === 'c') {
367
+ handleCtrlCEvent();
333
368
  return;
334
369
  }
335
- if (!key?.ctrl && value && /[\r\n]/.test(value) && value !== '\r' && value !== '\n') {
336
- const beforeReturn = value.split(/[\r\n]/)[0] ?? '';
337
- submit(`${inputRef.current}${beforeReturn}`);
370
+ agentController.handle({ type: 'clear_exit_intent' });
371
+ if (!key?.ctrl && isMultilinePasteValue(value)) {
372
+ dispatchInput({ type: 'inputChunk', text: normalizePastedInput(value) });
373
+ setOverlay(null);
338
374
  return;
339
375
  }
340
376
  if (overlay?.type === 'shortcuts') {
@@ -411,75 +447,135 @@ function ReplScreen({ runtime }) {
411
447
  return;
412
448
  }
413
449
  }
450
+ if (overlay?.type === 'permission') {
451
+ const items = permissionItems(overlay.request);
452
+ if (key?.escape || value?.toLowerCase() === 'n') {
453
+ answerPermission(overlay.request, false);
454
+ return;
455
+ }
456
+ if (value?.toLowerCase() === 'y') {
457
+ answerPermission(overlay.request, true);
458
+ return;
459
+ }
460
+ if (key?.upArrow) {
461
+ setOverlay({ ...overlay, selectedIndex: Math.max(0, overlay.selectedIndex - 1) });
462
+ return;
463
+ }
464
+ if (key?.downArrow || key?.tab) {
465
+ setOverlay({ ...overlay, selectedIndex: Math.min(items.length - 1, overlay.selectedIndex + 1) });
466
+ return;
467
+ }
468
+ if (isReturn) {
469
+ answerPermission(overlay.request, items[overlay.selectedIndex]?.value === 'allow');
470
+ return;
471
+ }
472
+ return;
473
+ }
414
474
  if (isReturn && key?.meta) {
415
- setInput(current => `${current}\n`);
475
+ dispatchInput({ type: 'insert', text: '\n' });
476
+ return;
477
+ }
478
+ if (key?.leftArrow) {
479
+ dispatchInput({ type: 'move', direction: 'left' });
480
+ return;
481
+ }
482
+ if (key?.rightArrow) {
483
+ dispatchInput({ type: 'move', direction: 'right' });
484
+ return;
485
+ }
486
+ if (key?.ctrl && value === 'a') {
487
+ dispatchInput({ type: 'move', direction: 'home' });
488
+ return;
489
+ }
490
+ if (key?.ctrl && value === 'e') {
491
+ dispatchInput({ type: 'move', direction: 'end' });
492
+ return;
493
+ }
494
+ if (key?.ctrl && value === 'u') {
495
+ const rawInput = lastRawInputRef.current;
496
+ dispatchInput({ type: 'inputChunk', text: rawInput.startsWith('\x15') ? rawInput : '\x15' });
497
+ setOverlay(null);
416
498
  return;
417
499
  }
418
500
  if (isReturn) {
419
- submit(inputRef.current);
501
+ submit(inputRef.current.value);
502
+ return;
503
+ }
504
+ if (value && (0, raw_input_1.hasDeletionRawInput)(value)) {
505
+ dispatchInput({ type: 'inputChunk', text: value });
506
+ return;
507
+ }
508
+ if (key?.backspace) {
509
+ dispatchInput({ type: 'backspace' });
420
510
  return;
421
511
  }
422
- if (key?.backspace || key?.delete) {
423
- setInput(current => current.slice(0, -1));
512
+ if (key?.delete) {
513
+ dispatchInput({ type: (0, raw_input_1.deleteActionFromRawInput)(value || lastRawInputRef.current) });
424
514
  return;
425
515
  }
426
- if (key?.upArrow && inputRef.current === '' && history.length > 0) {
516
+ if (key?.upArrow && inputRef.current.value === '' && history.length > 0) {
427
517
  const nextIndex = Math.min(history.length - 1, historyIndex + 1);
428
518
  setHistoryIndex(nextIndex);
429
- setInput(history[nextIndex]?.content ?? '');
519
+ dispatchInput({ type: 'set', value: history[nextIndex]?.content ?? '' });
430
520
  return;
431
521
  }
432
522
  if (key?.downArrow && historyIndex >= 0) {
433
523
  const nextIndex = historyIndex - 1;
434
524
  setHistoryIndex(nextIndex);
435
- setInput(nextIndex >= 0 ? history[nextIndex]?.content ?? '' : '');
525
+ dispatchInput({ type: 'set', value: nextIndex >= 0 ? history[nextIndex]?.content ?? '' : '' });
436
526
  return;
437
527
  }
438
528
  if (key?.tab) {
439
- if (inputRef.current.startsWith('/')) {
529
+ if (inputRef.current.value.startsWith('/')) {
440
530
  setOverlay({ type: 'commands', selectedIndex: 0 });
441
531
  }
442
- else if (getFileQuery(inputRef.current)) {
532
+ else if (getFileQuery(inputRef.current.value)) {
443
533
  setOverlay({ type: 'files', selectedIndex: 0 });
444
534
  }
445
535
  return;
446
536
  }
447
- if (value === '/' && inputRef.current === '' && !turnControllerRef.current.hasActiveTurn()) {
448
- setInput('/');
537
+ if (value === '/' && inputRef.current.value === '' && !agentController.hasActiveTurn()) {
538
+ dispatchInput({ type: 'set', value: '/' });
449
539
  setOverlay({ type: 'commands', selectedIndex: 0 });
450
540
  return;
451
541
  }
452
- if (value === '@' && !turnControllerRef.current.hasActiveTurn()) {
453
- setInput(current => `${current}@`);
542
+ if (value === '@' && !agentController.hasActiveTurn()) {
543
+ dispatchInput({ type: 'insert', text: '@' });
454
544
  setOverlay({ type: 'files', selectedIndex: 0 });
455
545
  return;
456
546
  }
457
- if (value === '?' && inputRef.current === '') {
547
+ if (value === '?' && inputRef.current.value === '') {
458
548
  setOverlay({ type: 'shortcuts' });
459
549
  return;
460
550
  }
461
551
  if (value && !key?.ctrl) {
462
- setInput(current => `${current}${value}`);
552
+ dispatchInput({ type: 'inputChunk', text: value });
463
553
  }
464
554
  });
465
- const terminalHeight = stdout?.rows || process.stdout.rows || 24;
466
- const terminalWidth = stdout?.columns || process.stdout.columns || 80;
467
- const transcriptItems = Math.max(6, terminalHeight - 10);
468
555
  const modeText = (0, types_1.getModeDisplayText)(runtime.store.getSnapshot().permissionMode);
469
- return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", minHeight: terminalHeight },
470
- react_1.default.createElement(ink_1.Box, { flexDirection: "column", marginBottom: 1 },
471
- react_1.default.createElement(PixelHorseBanner_1.PixelHorseBanner, { runtime: runtime, width: terminalWidth })),
472
- react_1.default.createElement(Transcript_1.Transcript, { entries: transcript, maxItems: transcriptItems, width: terminalWidth }),
473
- overlay?.type === 'commands' ? (react_1.default.createElement(SelectList_1.SelectList, { title: `Commands ${input.slice(1) ? `"${input.slice(1)}"` : ''}`, items: commandItems, selectedIndex: overlay.selectedIndex, maxVisibleItems: 10, footer: "\u2191\u2193 navigate Tab complete Enter select Esc cancel" })) : null,
474
- overlay?.type === 'files' ? (react_1.default.createElement(SelectList_1.SelectList, { title: "Files", items: fileItems, selectedIndex: overlay.selectedIndex, maxVisibleItems: 10, footer: "\u2191\u2193 navigate Tab/Enter complete Esc cancel" })) : null,
475
- overlay?.type === 'sessions' ? (react_1.default.createElement(SelectList_1.SelectList, { title: overlay.request.title, items: sessionItems(overlay.request), selectedIndex: overlay.selectedIndex, maxVisibleItems: overlay.request.maxVisibleItems ?? 10, footer: "\u2191\u2193 scroll PgUp/PgDn Enter resume Esc cancel" })) : null,
476
- overlay?.type === 'shortcuts' ? (react_1.default.createElement(ink_1.Box, { borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column" },
556
+ const staticItems = (0, react_1.useMemo)(() => [
557
+ { id: 'openhorse-banner', type: 'banner' },
558
+ ...(0, transcript_state_1.staticTranscriptEntries)(transcriptState).map(entry => ({ ...entry, type: 'entry' })),
559
+ ], [transcriptState]);
560
+ const liveEntries = (0, react_1.useMemo)(() => (0, transcript_state_1.liveTranscriptEntries)(transcriptState), [transcriptState]);
561
+ if (exiting) {
562
+ return react_1.default.createElement(ink_1.Box, { flexDirection: "column" });
563
+ }
564
+ return (react_1.default.createElement(ink_1.Box, { flexDirection: "column" },
565
+ react_1.default.createElement(ink_1.Static, { key: `${transcriptState.generation}:${resizeEpoch}`, items: staticItems }, item => item.type === 'banner' ? (react_1.default.createElement(ink_1.Box, { key: item.id, flexDirection: "column", marginBottom: 1 },
566
+ react_1.default.createElement(PixelHorseBanner_1.PixelHorseBanner, { runtime: runtime, width: layoutWidth }))) : (react_1.default.createElement(Transcript_1.TranscriptEntryBlock, { key: item.id, entry: item, width: layoutWidth }))),
567
+ react_1.default.createElement(Transcript_1.Transcript, { entries: liveEntries, maxItems: maxLiveTranscriptItems, width: layoutWidth, emptyMessage: null }),
568
+ overlay?.type === 'commands' ? (react_1.default.createElement(SelectList_1.SelectList, { title: `Commands ${input.slice(1) ? `"${input.slice(1)}"` : ''}`, items: commandItems, selectedIndex: overlay.selectedIndex, maxVisibleItems: maxOverlayItems, footer: "\u2191\u2193 navigate Tab complete Enter select Esc cancel", width: layoutWidth })) : null,
569
+ overlay?.type === 'files' ? (react_1.default.createElement(SelectList_1.SelectList, { title: "Files", items: fileItems, selectedIndex: overlay.selectedIndex, maxVisibleItems: maxOverlayItems, footer: "\u2191\u2193 navigate Tab/Enter complete Esc cancel", width: layoutWidth })) : null,
570
+ overlay?.type === 'sessions' ? (react_1.default.createElement(SelectList_1.SelectList, { title: overlay.request.title, items: sessionItems(overlay.request), selectedIndex: overlay.selectedIndex, maxVisibleItems: Math.min(overlay.request.maxVisibleItems ?? maxOverlayItems, maxOverlayItems), footer: "\u2191\u2193 scroll PgUp/PgDn Enter resume Esc cancel", width: layoutWidth })) : null,
571
+ overlay?.type === 'permission' ? (react_1.default.createElement(SelectList_1.SelectList, { title: "Tool Permission", items: permissionItems(overlay.request), selectedIndex: overlay.selectedIndex, maxVisibleItems: 2, footer: "\u2191\u2193 choose Enter select y allow n/Esc deny", width: layoutWidth })) : null,
572
+ overlay?.type === 'shortcuts' ? (react_1.default.createElement(ink_1.Box, { width: layoutWidth, borderStyle: "single", borderColor: "gray", paddingX: 1, flexDirection: "column" },
477
573
  react_1.default.createElement(ink_1.Text, { color: "cyan" }, "Shortcuts"),
478
574
  react_1.default.createElement(ink_1.Text, null, "/ commands @ file picker ? shortcuts"),
479
575
  react_1.default.createElement(ink_1.Text, null, "Alt+Enter newline Ctrl+C interrupt / twice exits \u2191\u2193 history or picker navigation"),
480
576
  react_1.default.createElement(ink_1.Text, { color: "gray" }, "Enter or Esc closes this panel."))) : null,
481
- react_1.default.createElement(StatusLine_1.StatusLine, { runtime: runtime, running: processing, statusMessage: statusMessage, width: terminalWidth }),
482
- react_1.default.createElement(PromptInput_1.PromptInput, { value: input, running: processing, modeText: modeText, width: terminalWidth }),
483
- react_1.default.createElement(TerminalCursor_1.TerminalCursor, { value: input, terminalHeight: terminalHeight, terminalWidth: terminalWidth, sticky: processing })));
577
+ react_1.default.createElement(StatusLine_1.StatusLine, { runtime: runtime, running: processing, statusMessage: statusMessage, width: layoutWidth }),
578
+ react_1.default.createElement(PromptInput_1.PromptInput, { ref: promptBoxRef, value: input, cursor: inputCursor, running: processing, modeText: modeText, width: layoutWidth, maxRows: maxPromptRows }),
579
+ react_1.default.createElement(NativeCursor_1.NativeCursor, { cursorController: cursorController, promptRef: promptBoxRef, value: input, cursor: inputCursor, maxRows: maxPromptRows, terminalWidth: layoutWidth })));
484
580
  }
485
581
  //# sourceMappingURL=ReplScreen.js.map