@visorcraft/idlehands 1.1.16 → 1.2.0

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 (212) hide show
  1. package/dist/agent/formatting.js +30 -13
  2. package/dist/agent/formatting.js.map +1 -1
  3. package/dist/agent/review-artifact.js +12 -8
  4. package/dist/agent/review-artifact.js.map +1 -1
  5. package/dist/agent/tool-calls.js +62 -21
  6. package/dist/agent/tool-calls.js.map +1 -1
  7. package/dist/agent/tool-loop-detection.js +310 -0
  8. package/dist/agent/tool-loop-detection.js.map +1 -0
  9. package/dist/agent/tool-loop-guard.js +235 -0
  10. package/dist/agent/tool-loop-guard.js.map +1 -0
  11. package/dist/agent.js +585 -144
  12. package/dist/agent.js.map +1 -1
  13. package/dist/anton/controller.js +46 -30
  14. package/dist/anton/controller.js.map +1 -1
  15. package/dist/anton/lock.js +5 -1
  16. package/dist/anton/lock.js.map +1 -1
  17. package/dist/anton/parser.js +18 -19
  18. package/dist/anton/parser.js.map +1 -1
  19. package/dist/anton/prompt.js +42 -11
  20. package/dist/anton/prompt.js.map +1 -1
  21. package/dist/anton/reporter.js.map +1 -1
  22. package/dist/anton/session.js.map +1 -1
  23. package/dist/anton/verifier.js +3 -5
  24. package/dist/anton/verifier.js.map +1 -1
  25. package/dist/bench/compare.js +53 -20
  26. package/dist/bench/compare.js.map +1 -1
  27. package/dist/bench/openclaw.js +4 -4
  28. package/dist/bench/openclaw.js.map +1 -1
  29. package/dist/bench/report.js +11 -3
  30. package/dist/bench/report.js.map +1 -1
  31. package/dist/bench/runner.js +20 -14
  32. package/dist/bench/runner.js.map +1 -1
  33. package/dist/bot/commands.js +69 -26
  34. package/dist/bot/commands.js.map +1 -1
  35. package/dist/bot/confirm-discord.js +32 -9
  36. package/dist/bot/confirm-discord.js.map +1 -1
  37. package/dist/bot/confirm-telegram.js +26 -10
  38. package/dist/bot/confirm-telegram.js.map +1 -1
  39. package/dist/bot/dir-guard.js +18 -3
  40. package/dist/bot/dir-guard.js.map +1 -1
  41. package/dist/bot/discord-routing.js +28 -4
  42. package/dist/bot/discord-routing.js.map +1 -1
  43. package/dist/bot/discord-streaming.js +3 -3
  44. package/dist/bot/discord-streaming.js.map +1 -1
  45. package/dist/bot/discord.js +93 -37
  46. package/dist/bot/discord.js.map +1 -1
  47. package/dist/bot/escalation.js +124 -0
  48. package/dist/bot/escalation.js.map +1 -0
  49. package/dist/bot/format.js +2 -5
  50. package/dist/bot/format.js.map +1 -1
  51. package/dist/bot/session-manager.js +17 -6
  52. package/dist/bot/session-manager.js.map +1 -1
  53. package/dist/bot/telegram.js +92 -29
  54. package/dist/bot/telegram.js.map +1 -1
  55. package/dist/cli/agent-turn.js +10 -4
  56. package/dist/cli/agent-turn.js.map +1 -1
  57. package/dist/cli/args.js +51 -9
  58. package/dist/cli/args.js.map +1 -1
  59. package/dist/cli/bot.js +19 -9
  60. package/dist/cli/bot.js.map +1 -1
  61. package/dist/cli/build-repl-context.js +60 -26
  62. package/dist/cli/build-repl-context.js.map +1 -1
  63. package/dist/cli/command-registry.js.map +1 -1
  64. package/dist/cli/commands/anton.js +5 -3
  65. package/dist/cli/commands/anton.js.map +1 -1
  66. package/dist/cli/commands/editing.js +27 -12
  67. package/dist/cli/commands/editing.js.map +1 -1
  68. package/dist/cli/commands/model.js +16 -7
  69. package/dist/cli/commands/model.js.map +1 -1
  70. package/dist/cli/commands/project.js +52 -17
  71. package/dist/cli/commands/project.js.map +1 -1
  72. package/dist/cli/commands/runtime.js +1 -1
  73. package/dist/cli/commands/runtime.js.map +1 -1
  74. package/dist/cli/commands/secrets.js +279 -0
  75. package/dist/cli/commands/secrets.js.map +1 -0
  76. package/dist/cli/commands/session.js +57 -2
  77. package/dist/cli/commands/session.js.map +1 -1
  78. package/dist/cli/commands/tools.js +3 -1
  79. package/dist/cli/commands/tools.js.map +1 -1
  80. package/dist/cli/commands/trifecta.js +1 -1
  81. package/dist/cli/commands/trifecta.js.map +1 -1
  82. package/dist/cli/commands/tui.js.map +1 -1
  83. package/dist/cli/init.js +50 -16
  84. package/dist/cli/init.js.map +1 -1
  85. package/dist/cli/input.js +25 -7
  86. package/dist/cli/input.js.map +1 -1
  87. package/dist/cli/oneshot.js +31 -19
  88. package/dist/cli/oneshot.js.map +1 -1
  89. package/dist/cli/repl-dispatch.js +10 -6
  90. package/dist/cli/repl-dispatch.js.map +1 -1
  91. package/dist/cli/runtime-cmds.js +110 -46
  92. package/dist/cli/runtime-cmds.js.map +1 -1
  93. package/dist/cli/service.js +3 -3
  94. package/dist/cli/service.js.map +1 -1
  95. package/dist/cli/session-state.js +12 -5
  96. package/dist/cli/session-state.js.map +1 -1
  97. package/dist/cli/setup.js +86 -33
  98. package/dist/cli/setup.js.map +1 -1
  99. package/dist/cli/shell.js +4 -4
  100. package/dist/cli/shell.js.map +1 -1
  101. package/dist/cli/status.js +56 -12
  102. package/dist/cli/status.js.map +1 -1
  103. package/dist/client.js +40 -21
  104. package/dist/client.js.map +1 -1
  105. package/dist/commands.js +1 -1
  106. package/dist/commands.js.map +1 -1
  107. package/dist/config.js +171 -15
  108. package/dist/config.js.map +1 -1
  109. package/dist/confirm/auto.js.map +1 -1
  110. package/dist/confirm/headless.js +13 -2
  111. package/dist/confirm/headless.js.map +1 -1
  112. package/dist/confirm/terminal.js +1 -5
  113. package/dist/confirm/terminal.js.map +1 -1
  114. package/dist/context.js +9 -3
  115. package/dist/context.js.map +1 -1
  116. package/dist/git.js +56 -61
  117. package/dist/git.js.map +1 -1
  118. package/dist/harnesses.js +137 -37
  119. package/dist/harnesses.js.map +1 -1
  120. package/dist/history.js +12 -4
  121. package/dist/history.js.map +1 -1
  122. package/dist/hooks/index.js +2 -2
  123. package/dist/hooks/index.js.map +1 -1
  124. package/dist/hooks/loader.js +6 -5
  125. package/dist/hooks/loader.js.map +1 -1
  126. package/dist/hooks/manager.js.map +1 -1
  127. package/dist/hooks/plugins/example-console.js.map +1 -1
  128. package/dist/hooks/scaffold.js +8 -6
  129. package/dist/hooks/scaffold.js.map +1 -1
  130. package/dist/index.js +120 -66
  131. package/dist/index.js.map +1 -1
  132. package/dist/indexer.js +6 -18
  133. package/dist/indexer.js.map +1 -1
  134. package/dist/jsonrpc.js.map +1 -1
  135. package/dist/lens.js +38 -16
  136. package/dist/lens.js.map +1 -1
  137. package/dist/lsp.js +60 -24
  138. package/dist/lsp.js.map +1 -1
  139. package/dist/markdown.js +6 -6
  140. package/dist/markdown.js.map +1 -1
  141. package/dist/mcp.js +15 -6
  142. package/dist/mcp.js.map +1 -1
  143. package/dist/model-customization.js +7 -3
  144. package/dist/model-customization.js.map +1 -1
  145. package/dist/progress/message-edit-scheduler.js +15 -3
  146. package/dist/progress/message-edit-scheduler.js.map +1 -1
  147. package/dist/progress/progress-message-renderer.js.map +1 -1
  148. package/dist/progress/progress-presenter.js +3 -3
  149. package/dist/progress/progress-presenter.js.map +1 -1
  150. package/dist/progress/serialize-telegram.js.map +1 -1
  151. package/dist/progress/tool-summary.js +3 -1
  152. package/dist/progress/tool-summary.js.map +1 -1
  153. package/dist/progress/turn-progress.js +3 -1
  154. package/dist/progress/turn-progress.js.map +1 -1
  155. package/dist/recovery.js +11 -3
  156. package/dist/recovery.js.map +1 -1
  157. package/dist/replay.js +9 -3
  158. package/dist/replay.js.map +1 -1
  159. package/dist/replay_cli.js +5 -3
  160. package/dist/replay_cli.js.map +1 -1
  161. package/dist/runtime/executor.js +66 -20
  162. package/dist/runtime/executor.js.map +1 -1
  163. package/dist/runtime/health.js.map +1 -1
  164. package/dist/runtime/host-runner.js +103 -0
  165. package/dist/runtime/host-runner.js.map +1 -0
  166. package/dist/runtime/planner.js +3 -1
  167. package/dist/runtime/planner.js.map +1 -1
  168. package/dist/runtime/secrets.js +102 -0
  169. package/dist/runtime/secrets.js.map +1 -0
  170. package/dist/runtime/store.js +95 -19
  171. package/dist/runtime/store.js.map +1 -1
  172. package/dist/safety.js +38 -21
  173. package/dist/safety.js.map +1 -1
  174. package/dist/spinner.js +7 -8
  175. package/dist/spinner.js.map +1 -1
  176. package/dist/sys/context.js +3 -3
  177. package/dist/sys/context.js.map +1 -1
  178. package/dist/term.js +1 -1
  179. package/dist/term.js.map +1 -1
  180. package/dist/themes.js +11 -5
  181. package/dist/themes.js.map +1 -1
  182. package/dist/tools/tool-error.js +2 -5
  183. package/dist/tools/tool-error.js.map +1 -1
  184. package/dist/tools.js +84 -35
  185. package/dist/tools.js.map +1 -1
  186. package/dist/tui/branch-picker.js +9 -3
  187. package/dist/tui/branch-picker.js.map +1 -1
  188. package/dist/tui/command-handler.js +88 -36
  189. package/dist/tui/command-handler.js.map +1 -1
  190. package/dist/tui/confirm.js.map +1 -1
  191. package/dist/tui/controller.js +234 -117
  192. package/dist/tui/controller.js.map +1 -1
  193. package/dist/tui/event-bridge.js.map +1 -1
  194. package/dist/tui/keymap.js +93 -71
  195. package/dist/tui/keymap.js.map +1 -1
  196. package/dist/tui/layout.js +9 -1
  197. package/dist/tui/layout.js.map +1 -1
  198. package/dist/tui/render.js +17 -5
  199. package/dist/tui/render.js.map +1 -1
  200. package/dist/tui/screen.js.map +1 -1
  201. package/dist/tui/state.js +129 -63
  202. package/dist/tui/state.js.map +1 -1
  203. package/dist/tui/theme.js +12 -3
  204. package/dist/tui/theme.js.map +1 -1
  205. package/dist/upgrade.js +28 -15
  206. package/dist/upgrade.js.map +1 -1
  207. package/dist/utils.js +8 -5
  208. package/dist/utils.js.map +1 -1
  209. package/dist/vault.js +48 -12
  210. package/dist/vault.js.map +1 -1
  211. package/dist/vim.js.map +1 -1
  212. package/package.json +11 -2
@@ -1,19 +1,19 @@
1
- import { createSession } from "../agent.js";
2
- import { decodeRawInput, resolveAction } from "./keymap.js";
3
- import { createInitialTuiState, reduceTuiState } from "./state.js";
4
- import { renderTui, setRenderTheme } from "./render.js";
5
- import { calculateLayout } from "./layout.js";
6
- import { enterFullScreen, leaveFullScreen } from "./screen.js";
7
- import { saveSessionFile, lastSessionPath, projectSessionPath } from "../cli/session-state.js";
8
- import { loadBranches, executeBranchSelect } from "./branch-picker.js";
9
- import { ensureCommandsRegistered, allCommandNames, runShellCommand, runSlashCommand } from "./command-handler.js";
10
- import { projectDir } from "../utils.js";
11
- import { formatWatchdogCancelMessage, resolveWatchdogSettings } from "../watchdog.js";
12
- import { TuiConfirmProvider } from "./confirm.js";
1
+ import { createSession } from '../agent.js';
13
2
  import { splitTokens } from '../cli/command-utils.js';
3
+ import { saveSessionFile, lastSessionPath, projectSessionPath } from '../cli/session-state.js';
14
4
  import { chainAgentHooks } from '../progress/agent-hooks.js';
15
- import { formatToolCallSummary } from '../progress/tool-summary.js';
16
5
  import { ProgressPresenter } from '../progress/progress-presenter.js';
6
+ import { formatToolCallSummary } from '../progress/tool-summary.js';
7
+ import { projectDir } from '../utils.js';
8
+ import { formatWatchdogCancelMessage, resolveWatchdogSettings } from '../watchdog.js';
9
+ import { loadBranches, executeBranchSelect } from './branch-picker.js';
10
+ import { ensureCommandsRegistered, allCommandNames, runShellCommand, runSlashCommand, } from './command-handler.js';
11
+ import { TuiConfirmProvider } from './confirm.js';
12
+ import { decodeRawInput, resolveAction } from './keymap.js';
13
+ import { calculateLayout } from './layout.js';
14
+ import { renderTui, setRenderTheme } from './render.js';
15
+ import { enterFullScreen, leaveFullScreen } from './screen.js';
16
+ import { createInitialTuiState, reduceTuiState } from './state.js';
17
17
  const THEME_OPTIONS = ['default', 'dark', 'light', 'minimal', 'hacker'];
18
18
  const APPROVAL_OPTIONS = ['plan', 'default', 'auto-edit', 'yolo'];
19
19
  export class TuiController {
@@ -59,9 +59,19 @@ export class TuiController {
59
59
  this.tabIndex = -1;
60
60
  const names = allCommandNames();
61
61
  // Add TUI-specific commands that aren't in the registry
62
- const extra = ['/quit', '/exit', '/clear', '/cancel', '/help', '/branches', '/steps', '/settings', '/hooks'];
62
+ const extra = [
63
+ '/quit',
64
+ '/exit',
65
+ '/clear',
66
+ '/cancel',
67
+ '/help',
68
+ '/branches',
69
+ '/steps',
70
+ '/settings',
71
+ '/hooks',
72
+ ];
63
73
  const all = [...new Set([...names, ...extra])];
64
- this.tabCandidates = all.filter(n => n.toLowerCase().startsWith(prefix)).sort();
74
+ this.tabCandidates = all.filter((n) => n.toLowerCase().startsWith(prefix)).sort();
65
75
  }
66
76
  if (!this.tabCandidates.length)
67
77
  return;
@@ -101,7 +111,12 @@ export class TuiController {
101
111
  if (result.ok)
102
112
  this.pushSystemMessage(result.message);
103
113
  else
104
- this.dispatch({ type: 'ALERT_PUSH', id: `br_${Date.now()}`, level: result.level ?? 'error', text: result.message });
114
+ this.dispatch({
115
+ type: 'ALERT_PUSH',
116
+ id: `br_${Date.now()}`,
117
+ level: result.level ?? 'error',
118
+ text: result.message,
119
+ });
105
120
  }
106
121
  }
107
122
  transcriptLineStarts() {
@@ -118,7 +133,9 @@ export class TuiController {
118
133
  const q = (query ?? '').trim().toLowerCase();
119
134
  const starts = this.transcriptLineStarts();
120
135
  const items = this.state.transcript.map((item, idx) => {
121
- const preview = String(item.text ?? '').split(/\r?\n/)[0]?.trim() ?? '';
136
+ const preview = String(item.text ?? '')
137
+ .split(/\r?\n/)[0]
138
+ ?.trim() ?? '';
122
139
  return {
123
140
  id: item.id,
124
141
  ts: item.ts,
@@ -129,7 +146,7 @@ export class TuiController {
129
146
  });
130
147
  if (!q)
131
148
  return items;
132
- return items.filter((it) => (`${it.role} ${it.preview}`).toLowerCase().includes(q));
149
+ return items.filter((it) => `${it.role} ${it.preview}`.toLowerCase().includes(q));
133
150
  }
134
151
  openStepNavigator(query = '') {
135
152
  const items = this.buildStepNavigatorItems(query);
@@ -160,13 +177,19 @@ export class TuiController {
160
177
  const starts = this.transcriptLineStarts();
161
178
  const totalLines = starts.length
162
179
  ? (starts[starts.length - 1] ?? 0) +
163
- Math.max(1, String(this.state.transcript[this.state.transcript.length - 1]?.text ?? '').split('\n').length)
180
+ Math.max(1, String(this.state.transcript[this.state.transcript.length - 1]?.text ?? '').split('\n')
181
+ .length)
164
182
  : 0;
165
183
  const desiredStart = Math.max(0, Math.min(selected.lineStart, Math.max(0, totalLines - layout.transcriptRows)));
166
184
  const scrollBack = Math.max(0, totalLines - (desiredStart + layout.transcriptRows));
167
185
  this.dispatch({ type: 'SCROLL_SET', panel: 'transcript', value: scrollBack });
168
186
  this.dispatch({ type: 'STEP_NAV_CLOSE' });
169
- this.dispatch({ type: 'ALERT_PUSH', id: `step_${Date.now()}`, level: 'info', text: `Jumped to ${selected.role}: ${selected.preview || '(no preview)'}` });
187
+ this.dispatch({
188
+ type: 'ALERT_PUSH',
189
+ id: `step_${Date.now()}`,
190
+ level: 'info',
191
+ text: `Jumped to ${selected.role}: ${selected.preview || '(no preview)'}`,
192
+ });
170
193
  }
171
194
  buildSettingsItems() {
172
195
  const watchdog = resolveWatchdogSettings(undefined, this.config);
@@ -234,7 +257,8 @@ export class TuiController {
234
257
  break;
235
258
  }
236
259
  case 'approval': {
237
- const current = (this.config.approval_mode || 'default');
260
+ const current = (this.config.approval_mode ||
261
+ 'default');
238
262
  const idx = Math.max(0, APPROVAL_OPTIONS.indexOf(current));
239
263
  const next = APPROVAL_OPTIONS[(idx + (delta >= 0 ? 1 : -1) + APPROVAL_OPTIONS.length) % APPROVAL_OPTIONS.length];
240
264
  this.config.approval_mode = next;
@@ -314,7 +338,7 @@ export class TuiController {
314
338
  }
315
339
  /** Push a system-role transcript item and re-render. */
316
340
  pushSystemMessage(text) {
317
- const item = { id: `sys_${Date.now()}`, role: "system", text, ts: Date.now() };
341
+ const item = { id: `sys_${Date.now()}`, role: 'system', text, ts: Date.now() };
318
342
  this.state = { ...this.state, transcript: [...this.state.transcript, item] };
319
343
  renderTui(this.state);
320
344
  }
@@ -322,28 +346,46 @@ export class TuiController {
322
346
  async handleShellCommand(line) {
323
347
  const result = await runShellCommand(line, this.config);
324
348
  if (!result.command) {
325
- this.dispatch({ type: "ALERT_PUSH", id: `sh_${Date.now()}`, level: "info", text: "Usage: !<command> or !!<command> to inject output" });
349
+ this.dispatch({
350
+ type: 'ALERT_PUSH',
351
+ id: `sh_${Date.now()}`,
352
+ level: 'info',
353
+ text: 'Usage: !<command> or !!<command> to inject output',
354
+ });
326
355
  return;
327
356
  }
328
357
  this.pushSystemMessage(`$ ${result.command}`);
329
358
  if (result.output.trim())
330
359
  this.pushSystemMessage(result.output);
331
360
  if (result.rc !== 0)
332
- this.dispatch({ type: "ALERT_PUSH", id: `sh_${Date.now()}`, level: "warn", text: `Shell exited rc=${result.rc}` });
361
+ this.dispatch({
362
+ type: 'ALERT_PUSH',
363
+ id: `sh_${Date.now()}`,
364
+ level: 'warn',
365
+ text: `Shell exited rc=${result.rc}`,
366
+ });
333
367
  if (result.inject && this.session) {
334
- this.session.messages.push({ role: "user", content: `[Shell output]\n$ ${result.command}\n${result.output}` });
335
- this.dispatch({ type: "ALERT_PUSH", id: `sh_${Date.now()}`, level: "info", text: "Output injected into context" });
368
+ this.session.messages.push({
369
+ role: 'user',
370
+ content: `[Shell output]\n$ ${result.command}\n${result.output}`,
371
+ });
372
+ this.dispatch({
373
+ type: 'ALERT_PUSH',
374
+ id: `sh_${Date.now()}`,
375
+ level: 'info',
376
+ text: 'Output injected into context',
377
+ });
336
378
  }
337
379
  }
338
380
  /** Handle a slash command. Returns true if handled. */
339
381
  async handleSlashCommand(line) {
340
382
  const parts = splitTokens(line);
341
- const head = (parts[0] || "").toLowerCase();
342
- if (!head.startsWith("/"))
383
+ const head = (parts[0] || '').toLowerCase();
384
+ if (!head.startsWith('/'))
343
385
  return false;
344
386
  ensureCommandsRegistered();
345
387
  // TUI-specific overrides
346
- if (head === "/quit" || head === "/exit") {
388
+ if (head === '/quit' || head === '/exit') {
347
389
  if (this.cleanupFn)
348
390
  await this.cleanupFn();
349
391
  return true;
@@ -358,29 +400,43 @@ export class TuiController {
358
400
  this.session?.cancel();
359
401
  }
360
402
  catch { }
361
- this.dispatch({ type: 'ALERT_PUSH', id: `cancel_${Date.now()}`, level: 'warn', text: '⏹ Cancel requested.' });
403
+ this.dispatch({
404
+ type: 'ALERT_PUSH',
405
+ id: `cancel_${Date.now()}`,
406
+ level: 'warn',
407
+ text: '⏹ Cancel requested.',
408
+ });
362
409
  }
363
410
  else {
364
- this.dispatch({ type: 'ALERT_PUSH', id: `cancel_${Date.now()}`, level: 'info', text: 'Nothing to cancel.' });
411
+ this.dispatch({
412
+ type: 'ALERT_PUSH',
413
+ id: `cancel_${Date.now()}`,
414
+ level: 'info',
415
+ text: 'Nothing to cancel.',
416
+ });
365
417
  }
366
418
  return true;
367
419
  }
368
- if (head === "/clear") {
420
+ if (head === '/clear') {
369
421
  this.state = { ...this.state, transcript: [], toolEvents: [], alerts: [] };
370
422
  renderTui(this.state);
371
423
  return true;
372
424
  }
373
- if (head === "/help") {
374
- const cmds = allCommandNames().join(" ");
425
+ if (head === '/help') {
426
+ const cmds = allCommandNames().join(' ');
375
427
  this.pushSystemMessage(`Commands: ${cmds}\n` +
376
428
  `Shell: !<cmd> to run, !! to inject output\n` +
377
- `TUI: /cancel (stop active run), /branches [browse|checkout|merge], /steps, /settings, /hooks [status|errors|slow|plugins]\n` +
429
+ `TUI: /cancel (stop active run), /branches [browse|checkout|merge], /steps, /settings, /hooks [status|errors|slow|plugins], /version\n` +
378
430
  `Hotkeys: Ctrl+C cancel in-flight run, Ctrl+G step navigator, Ctrl+O quick settings`);
379
431
  return true;
380
432
  }
381
- if (head === "/branches") {
433
+ if (head === '/branches') {
382
434
  const sub = (parts[1] || '').toLowerCase();
383
- const action = sub === 'checkout' ? 'checkout' : sub === 'merge' ? 'merge' : 'browse';
435
+ const action = sub === 'checkout'
436
+ ? 'checkout'
437
+ : sub === 'merge'
438
+ ? 'merge'
439
+ : 'browse';
384
440
  await this.openBranchPicker(action);
385
441
  return true;
386
442
  }
@@ -394,10 +450,18 @@ export class TuiController {
394
450
  return true;
395
451
  }
396
452
  if (head === '/hooks') {
397
- const modeRaw = line.replace(/^\/hooks\s*/i, '').trim().toLowerCase();
453
+ const modeRaw = line
454
+ .replace(/^\/hooks\s*/i, '')
455
+ .trim()
456
+ .toLowerCase();
398
457
  const mode = (modeRaw || 'status');
399
458
  if (!['status', 'errors', 'slow', 'plugins'].includes(mode)) {
400
- this.dispatch({ type: 'ALERT_PUSH', id: `hooks_${Date.now()}`, level: 'warn', text: 'Usage: /hooks [status|errors|slow|plugins]' });
459
+ this.dispatch({
460
+ type: 'ALERT_PUSH',
461
+ id: `hooks_${Date.now()}`,
462
+ level: 'warn',
463
+ text: 'Usage: /hooks [status|errors|slow|plugins]',
464
+ });
401
465
  return true;
402
466
  }
403
467
  this.openHooksInspector(mode);
@@ -405,7 +469,12 @@ export class TuiController {
405
469
  }
406
470
  const result = await runSlashCommand(line, this.session, this.config, this.cleanupFn, () => this.saveTuiSessionSnapshot());
407
471
  if (!result.found) {
408
- this.dispatch({ type: "ALERT_PUSH", id: `cmd_${Date.now()}`, level: "warn", text: `Unknown command: ${head}` });
472
+ this.dispatch({
473
+ type: 'ALERT_PUSH',
474
+ id: `cmd_${Date.now()}`,
475
+ level: 'warn',
476
+ text: `Unknown command: ${head}`,
477
+ });
409
478
  }
410
479
  else if (result.output) {
411
480
  this.pushSystemMessage(result.output);
@@ -421,7 +490,7 @@ export class TuiController {
421
490
  harness: this.session.harness,
422
491
  contextWindow: this.session.contextWindow,
423
492
  messages: this.session.messages,
424
- mode: "tui",
493
+ mode: 'tui',
425
494
  };
426
495
  const cwd = projectDir(this.config);
427
496
  const targets = new Set([lastSessionPath(), projectSessionPath(cwd)]);
@@ -436,33 +505,43 @@ export class TuiController {
436
505
  return;
437
506
  // Slash commands: route through registry instead of agent.
438
507
  // While a generation is in-flight, only /cancel is accepted immediately.
439
- if (trimmed.startsWith("/")) {
508
+ if (trimmed.startsWith('/')) {
440
509
  const head = (splitTokens(trimmed)[0] || '').toLowerCase();
441
510
  if (this.inFlight && head !== '/cancel') {
442
- this.dispatch({ type: 'ALERT_PUSH', id: `busy_${Date.now()}`, level: 'warn', text: 'Generation in progress. Use /cancel first.' });
511
+ this.dispatch({
512
+ type: 'ALERT_PUSH',
513
+ id: `busy_${Date.now()}`,
514
+ level: 'warn',
515
+ text: 'Generation in progress. Use /cancel first.',
516
+ });
443
517
  return;
444
518
  }
445
- this.dispatch({ type: "USER_INPUT_SUBMIT", text: trimmed });
519
+ this.dispatch({ type: 'USER_INPUT_SUBMIT', text: trimmed });
446
520
  await this.handleSlashCommand(trimmed);
447
521
  return;
448
522
  }
449
523
  if (this.inFlight) {
450
- this.dispatch({ type: 'ALERT_PUSH', id: `busy_${Date.now()}`, level: 'warn', text: 'Generation in progress. Use /cancel first.' });
524
+ this.dispatch({
525
+ type: 'ALERT_PUSH',
526
+ id: `busy_${Date.now()}`,
527
+ level: 'warn',
528
+ text: 'Generation in progress. Use /cancel first.',
529
+ });
451
530
  return;
452
531
  }
453
532
  // Shell commands: !cmd or !!cmd
454
533
  if (/^!{1,2}\s*\S/.test(trimmed)) {
455
- this.dispatch({ type: "USER_INPUT_SUBMIT", text: trimmed });
534
+ this.dispatch({ type: 'USER_INPUT_SUBMIT', text: trimmed });
456
535
  await this.handleShellCommand(trimmed);
457
536
  return;
458
537
  }
459
- this.dispatch({ type: "USER_INPUT_SUBMIT", text: trimmed });
538
+ this.dispatch({ type: 'USER_INPUT_SUBMIT', text: trimmed });
460
539
  const id = `a_${Date.now()}`;
461
540
  this.inFlight = true;
462
541
  this.aborter = new AbortController();
463
542
  this.lastProgressAt = Date.now();
464
543
  this.watchdogCompactAttempts = 0;
465
- this.dispatch({ type: "AGENT_STREAM_START", id });
544
+ this.dispatch({ type: 'AGENT_STREAM_START', id });
466
545
  const watchdogSettings = resolveWatchdogSettings(undefined, this.config);
467
546
  const watchdogMs = watchdogSettings.timeoutMs;
468
547
  const maxWatchdogCompacts = watchdogSettings.maxCompactions;
@@ -501,10 +580,10 @@ export class TuiController {
501
580
  presenter.setBanner('⏳ Still working... model is taking longer than usual.');
502
581
  console.error(`[tui] watchdog inactivity — applying grace period (${watchdogGraceUsed}/${watchdogIdleGraceTimeouts})`);
503
582
  this.dispatch({
504
- type: "ALERT_PUSH",
583
+ type: 'ALERT_PUSH',
505
584
  id: `watchdog_grace_${Date.now()}`,
506
- level: "info",
507
- text: "Still working... model is taking longer than usual.",
585
+ level: 'info',
586
+ text: 'Still working... model is taking longer than usual.',
508
587
  });
509
588
  return;
510
589
  }
@@ -517,12 +596,14 @@ export class TuiController {
517
596
  this.aborter?.abort();
518
597
  }
519
598
  catch { }
520
- this.session.compactHistory({ force: true }).then((result) => {
599
+ this.session.compactHistory({ force: true })
600
+ .then((result) => {
521
601
  console.error(`[tui] watchdog compaction: freed ${result.freedTokens} tokens, dropped ${result.droppedMessages} messages`);
522
602
  presenter.setBanner(null);
523
603
  this.lastProgressAt = Date.now();
524
604
  watchdogCompactPending = false;
525
- }).catch((e) => {
605
+ })
606
+ .catch((e) => {
526
607
  console.error(`[tui] watchdog compaction failed: ${e?.message ?? e}`);
527
608
  presenter.setBanner(null);
528
609
  watchdogCompactPending = false;
@@ -597,7 +678,12 @@ export class TuiController {
597
678
  presenterHooks.onToolResult?.(r);
598
679
  if (activeToolId === r.id)
599
680
  activeToolId = null;
600
- this.dispatch({ type: r.success ? 'TOOL_END' : 'TOOL_ERROR', id: r.id, name: r.name, detail: r.summary });
681
+ this.dispatch({
682
+ type: r.success ? 'TOOL_END' : 'TOOL_ERROR',
683
+ id: r.id,
684
+ name: r.name,
685
+ detail: r.summary,
686
+ });
601
687
  },
602
688
  onTurnEnd: (stats) => {
603
689
  this.lastProgressAt = Date.now();
@@ -614,7 +700,12 @@ export class TuiController {
614
700
  const msg = e?.message ?? String(e);
615
701
  const isAbort = msg.includes('AbortError') || msg.toLowerCase().includes('aborted');
616
702
  if (isAbort && watchdogCompactPending) {
617
- this.dispatch({ type: 'ALERT_PUSH', id: `compact_${Date.now()}`, level: 'info', text: `Context too large — compacting and retrying (attempt ${this.watchdogCompactAttempts}/${maxWatchdogCompacts})...` });
703
+ this.dispatch({
704
+ type: 'ALERT_PUSH',
705
+ id: `compact_${Date.now()}`,
706
+ level: 'info',
707
+ text: `Context too large — compacting and retrying (attempt ${this.watchdogCompactAttempts}/${maxWatchdogCompacts})...`,
708
+ });
618
709
  while (watchdogCompactPending) {
619
710
  await new Promise((r) => setTimeout(r, 500));
620
711
  }
@@ -632,7 +723,12 @@ export class TuiController {
632
723
  this.dispatch({ type: 'ALERT_PUSH', id: `err_${Date.now()}`, level: 'error', text });
633
724
  }
634
725
  else {
635
- this.dispatch({ type: 'ALERT_PUSH', id: `err_${Date.now()}`, level: 'error', text: msg });
726
+ this.dispatch({
727
+ type: 'ALERT_PUSH',
728
+ id: `err_${Date.now()}`,
729
+ level: 'error',
730
+ text: msg,
731
+ });
636
732
  }
637
733
  }
638
734
  }
@@ -655,25 +751,36 @@ export class TuiController {
655
751
  confirm: async (prompt) => {
656
752
  // Legacy confirm fallback — route through the TUI confirm provider
657
753
  return this.confirmProvider.confirm({
658
- tool: 'unknown', args: {}, summary: prompt, mode: this.config.approval_mode ?? 'suggest',
754
+ tool: 'unknown',
755
+ args: {},
756
+ summary: prompt,
757
+ mode: this.config.approval_mode ?? 'suggest',
659
758
  });
660
759
  },
661
760
  });
662
- this.dispatch({ type: "RUNTIME_STATE_UPDATE", runtime: { modelId: this.session.model, endpoint: this.session.endpoint, healthy: true } });
761
+ this.dispatch({
762
+ type: 'RUNTIME_STATE_UPDATE',
763
+ runtime: { modelId: this.session.model, endpoint: this.session.endpoint, healthy: true },
764
+ });
663
765
  enterFullScreen();
664
766
  renderTui(this.state);
665
767
  this.dispatch({
666
- type: "ALERT_PUSH",
768
+ type: 'ALERT_PUSH',
667
769
  id: `info_${Date.now()}`,
668
- level: "info",
669
- text: "Input: Enter=send, Ctrl+J/Alt+Enter=newline, Up/Down=history, Ctrl+G=steps, Ctrl+O=settings, /hooks inspector.",
770
+ level: 'info',
771
+ text: 'Input: Enter=send, Ctrl+J/Alt+Enter=newline, Up/Down=history, Ctrl+G=steps, Ctrl+O=settings, /hooks inspector.',
670
772
  });
671
773
  const onSigwinch = () => {
672
774
  renderTui(this.state);
673
775
  };
674
776
  const onFatal = async (err, source) => {
675
777
  const text = err instanceof Error ? err.message : String(err);
676
- this.dispatch({ type: "ALERT_PUSH", id: `fatal_${Date.now()}`, level: "error", text: `${source}: ${text}` });
778
+ this.dispatch({
779
+ type: 'ALERT_PUSH',
780
+ id: `fatal_${Date.now()}`,
781
+ level: 'error',
782
+ text: `${source}: ${text}`,
783
+ });
677
784
  try {
678
785
  await this.saveTuiSessionSnapshot();
679
786
  }
@@ -682,25 +789,25 @@ export class TuiController {
682
789
  await cleanup();
683
790
  };
684
791
  const onData = (buf) => {
685
- const keys = decodeRawInput(buf.toString("utf8"));
792
+ const keys = decodeRawInput(buf.toString('utf8'));
686
793
  for (const key of keys) {
687
794
  // Confirmation mode: route y/n/d to confirm provider
688
795
  if (this.state.confirmPending) {
689
- if (key === "text:y" || key === "text:Y") {
796
+ if (key === 'text:y' || key === 'text:Y') {
690
797
  this.confirmProvider.resolve(true);
691
798
  continue;
692
799
  }
693
- if (key === "text:n" || key === "text:N") {
800
+ if (key === 'text:n' || key === 'text:N') {
694
801
  this.confirmProvider.resolve(false);
695
802
  continue;
696
803
  }
697
- if (key === "text:d" || key === "text:D") {
804
+ if (key === 'text:d' || key === 'text:D') {
698
805
  this.confirmProvider.toggleDiff();
699
806
  continue;
700
807
  }
701
808
  // Ctrl+C rejects during confirm
702
809
  const cAction = resolveAction(key);
703
- if (cAction === "cancel") {
810
+ if (cAction === 'cancel') {
704
811
  this.confirmProvider.resolve(false);
705
812
  continue;
706
813
  }
@@ -709,20 +816,20 @@ export class TuiController {
709
816
  // Branch picker mode: arrow keys navigate, Enter selects, Esc/q closes
710
817
  if (this.state.branchPicker) {
711
818
  const bAction = resolveAction(key);
712
- if (bAction === "history_prev" || key === "up") {
713
- this.dispatch({ type: "BRANCH_PICKER_MOVE", delta: -1 });
819
+ if (bAction === 'history_prev' || key === 'up') {
820
+ this.dispatch({ type: 'BRANCH_PICKER_MOVE', delta: -1 });
714
821
  continue;
715
822
  }
716
- if (bAction === "history_next" || key === "down") {
717
- this.dispatch({ type: "BRANCH_PICKER_MOVE", delta: 1 });
823
+ if (bAction === 'history_next' || key === 'down') {
824
+ this.dispatch({ type: 'BRANCH_PICKER_MOVE', delta: 1 });
718
825
  continue;
719
826
  }
720
- if (bAction === "send") {
827
+ if (bAction === 'send') {
721
828
  void this.handleBranchSelect();
722
829
  continue;
723
830
  }
724
- if (bAction === "cancel" || bAction === "quit" || key === "text:q") {
725
- this.dispatch({ type: "BRANCH_PICKER_CLOSE" });
831
+ if (bAction === 'cancel' || bAction === 'quit' || key === 'text:q') {
832
+ this.dispatch({ type: 'BRANCH_PICKER_CLOSE' });
726
833
  continue;
727
834
  }
728
835
  continue; // swallow all other input during picker
@@ -805,22 +912,25 @@ export class TuiController {
805
912
  this.dispatch({ type: 'HOOKS_INSPECTOR_MOVE', delta: 1 });
806
913
  continue;
807
914
  }
808
- if (hAction === 'cancel' || hAction === 'quit' || key === 'text:q' || hAction === 'send') {
915
+ if (hAction === 'cancel' ||
916
+ hAction === 'quit' ||
917
+ key === 'text:q' ||
918
+ hAction === 'send') {
809
919
  this.dispatch({ type: 'HOOKS_INSPECTOR_CLOSE' });
810
920
  continue;
811
921
  }
812
922
  continue;
813
923
  }
814
- if (key.startsWith("text:")) {
924
+ if (key.startsWith('text:')) {
815
925
  this.resetTab();
816
- this.dispatch({ type: "USER_INPUT_INSERT", text: key.slice(5) });
926
+ this.dispatch({ type: 'USER_INPUT_INSERT', text: key.slice(5) });
817
927
  continue;
818
928
  }
819
929
  const action = resolveAction(key);
820
930
  if (!action)
821
931
  continue;
822
932
  // Tab completion doesn't reset tab state
823
- if (action === "tab_complete") {
933
+ if (action === 'tab_complete') {
824
934
  this.handleTabComplete();
825
935
  continue;
826
936
  }
@@ -834,11 +944,11 @@ export class TuiController {
834
944
  this.openSettingsMenu();
835
945
  continue;
836
946
  }
837
- if (action === "quit") {
947
+ if (action === 'quit') {
838
948
  void cleanup();
839
949
  continue;
840
950
  }
841
- if (action === "cancel") {
951
+ if (action === 'cancel') {
842
952
  if (this.inFlight && this.aborter) {
843
953
  this.aborter.abort();
844
954
  this.session?.cancel();
@@ -850,55 +960,60 @@ export class TuiController {
850
960
  continue;
851
961
  }
852
962
  this.ctrlCAt = now;
853
- this.dispatch({ type: "ALERT_PUSH", id: `warn_${now}`, level: "warn", text: "Press Ctrl+C again to quit" });
963
+ this.dispatch({
964
+ type: 'ALERT_PUSH',
965
+ id: `warn_${now}`,
966
+ level: 'warn',
967
+ text: 'Press Ctrl+C again to quit',
968
+ });
854
969
  continue;
855
970
  }
856
- if (action === "send") {
971
+ if (action === 'send') {
857
972
  void this.submitInput(this.state.inputBuffer);
858
973
  continue;
859
974
  }
860
- if (action === "insert_newline") {
861
- this.dispatch({ type: "USER_INPUT_INSERT", text: "\n" });
975
+ if (action === 'insert_newline') {
976
+ this.dispatch({ type: 'USER_INPUT_INSERT', text: '\n' });
862
977
  continue;
863
978
  }
864
- if (action === "backspace") {
865
- this.dispatch({ type: "USER_INPUT_BACKSPACE" });
979
+ if (action === 'backspace') {
980
+ this.dispatch({ type: 'USER_INPUT_BACKSPACE' });
866
981
  continue;
867
982
  }
868
- if (action === "delete_forward") {
869
- this.dispatch({ type: "USER_INPUT_DELETE_FORWARD" });
983
+ if (action === 'delete_forward') {
984
+ this.dispatch({ type: 'USER_INPUT_DELETE_FORWARD' });
870
985
  continue;
871
986
  }
872
- if (action === "cursor_left") {
873
- this.dispatch({ type: "USER_INPUT_CURSOR_MOVE", delta: -1 });
987
+ if (action === 'cursor_left') {
988
+ this.dispatch({ type: 'USER_INPUT_CURSOR_MOVE', delta: -1 });
874
989
  continue;
875
990
  }
876
- if (action === "cursor_right") {
877
- this.dispatch({ type: "USER_INPUT_CURSOR_MOVE", delta: 1 });
991
+ if (action === 'cursor_right') {
992
+ this.dispatch({ type: 'USER_INPUT_CURSOR_MOVE', delta: 1 });
878
993
  continue;
879
994
  }
880
- if (action === "cursor_home") {
881
- this.dispatch({ type: "USER_INPUT_CURSOR_HOME" });
995
+ if (action === 'cursor_home') {
996
+ this.dispatch({ type: 'USER_INPUT_CURSOR_HOME' });
882
997
  continue;
883
998
  }
884
- if (action === "cursor_end") {
885
- this.dispatch({ type: "USER_INPUT_CURSOR_END" });
999
+ if (action === 'cursor_end') {
1000
+ this.dispatch({ type: 'USER_INPUT_CURSOR_END' });
886
1001
  continue;
887
1002
  }
888
- if (action === "history_prev") {
889
- this.dispatch({ type: "USER_INPUT_HISTORY_PREV" });
1003
+ if (action === 'history_prev') {
1004
+ this.dispatch({ type: 'USER_INPUT_HISTORY_PREV' });
890
1005
  continue;
891
1006
  }
892
- if (action === "history_next") {
893
- this.dispatch({ type: "USER_INPUT_HISTORY_NEXT" });
1007
+ if (action === 'history_next') {
1008
+ this.dispatch({ type: 'USER_INPUT_HISTORY_NEXT' });
894
1009
  continue;
895
1010
  }
896
- if (action === "scroll_up") {
897
- this.dispatch({ type: "SCROLL", panel: "transcript", delta: -5 });
1011
+ if (action === 'scroll_up') {
1012
+ this.dispatch({ type: 'SCROLL', panel: 'transcript', delta: -5 });
898
1013
  continue;
899
1014
  }
900
- if (action === "scroll_down") {
901
- this.dispatch({ type: "SCROLL", panel: "transcript", delta: 5 });
1015
+ if (action === 'scroll_down') {
1016
+ this.dispatch({ type: 'SCROLL', panel: 'transcript', delta: 5 });
902
1017
  }
903
1018
  }
904
1019
  };
@@ -909,19 +1024,19 @@ export class TuiController {
909
1024
  cleaned = true;
910
1025
  this.cleanupFn = null;
911
1026
  try {
912
- process.stdin.off("data", onData);
1027
+ process.stdin.off('data', onData);
913
1028
  }
914
1029
  catch { }
915
1030
  try {
916
- process.off("SIGWINCH", onSigwinch);
1031
+ process.off('SIGWINCH', onSigwinch);
917
1032
  }
918
1033
  catch { }
919
1034
  try {
920
- process.off("uncaughtException", onFatal);
1035
+ process.off('uncaughtException', onFatal);
921
1036
  }
922
1037
  catch { }
923
1038
  try {
924
- process.off("unhandledRejection", onFatal);
1039
+ process.off('unhandledRejection', onFatal);
925
1040
  }
926
1041
  catch { }
927
1042
  try {
@@ -945,33 +1060,35 @@ export class TuiController {
945
1060
  resolveDone();
946
1061
  };
947
1062
  let resolveDone;
948
- const done = new Promise((resolve) => { resolveDone = resolve; });
1063
+ const done = new Promise((resolve) => {
1064
+ resolveDone = resolve;
1065
+ });
949
1066
  this.cleanupFn = cleanup;
950
1067
  try {
951
1068
  if (process.stdin.isTTY)
952
1069
  process.stdin.setRawMode(true);
953
1070
  process.stdin.resume();
954
- process.stdin.on("data", onData);
955
- process.on("SIGWINCH", onSigwinch);
956
- process.on("uncaughtException", onFatal);
957
- process.on("unhandledRejection", onFatal);
1071
+ process.stdin.on('data', onData);
1072
+ process.on('SIGWINCH', onSigwinch);
1073
+ process.on('uncaughtException', onFatal);
1074
+ process.on('unhandledRejection', onFatal);
958
1075
  await done;
959
1076
  }
960
1077
  finally {
961
1078
  try {
962
- process.stdin.off("data", onData);
1079
+ process.stdin.off('data', onData);
963
1080
  }
964
1081
  catch { }
965
1082
  try {
966
- process.off("SIGWINCH", onSigwinch);
1083
+ process.off('SIGWINCH', onSigwinch);
967
1084
  }
968
1085
  catch { }
969
1086
  try {
970
- process.off("uncaughtException", onFatal);
1087
+ process.off('uncaughtException', onFatal);
971
1088
  }
972
1089
  catch { }
973
1090
  try {
974
- process.off("unhandledRejection", onFatal);
1091
+ process.off('unhandledRejection', onFatal);
975
1092
  }
976
1093
  catch { }
977
1094
  try {