@visorcraft/idlehands 1.1.6 → 1.1.8
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.
- package/README.md +32 -0
- package/dist/agent/formatting.js +251 -0
- package/dist/agent/formatting.js.map +1 -0
- package/dist/agent/review-artifact.js +147 -0
- package/dist/agent/review-artifact.js.map +1 -0
- package/dist/agent/tool-calls.js +226 -0
- package/dist/agent/tool-calls.js.map +1 -0
- package/dist/agent.js +314 -695
- package/dist/agent.js.map +1 -1
- package/dist/anton/controller.js +1 -1
- package/dist/anton/controller.js.map +1 -1
- package/dist/anton/lock.js +0 -3
- package/dist/anton/lock.js.map +1 -1
- package/dist/anton/parser.js +0 -1
- package/dist/anton/parser.js.map +1 -1
- package/dist/anton/reporter.js +1 -1
- package/dist/anton/reporter.js.map +1 -1
- package/dist/bot/commands.js +3 -2
- package/dist/bot/commands.js.map +1 -1
- package/dist/bot/confirm-telegram.js +2 -1
- package/dist/bot/confirm-telegram.js.map +1 -1
- package/dist/bot/discord-routing.js +179 -0
- package/dist/bot/discord-routing.js.map +1 -0
- package/dist/bot/discord-streaming.js +171 -0
- package/dist/bot/discord-streaming.js.map +1 -0
- package/dist/bot/discord.js +25 -221
- package/dist/bot/discord.js.map +1 -1
- package/dist/bot/format.js +2 -25
- package/dist/bot/format.js.map +1 -1
- package/dist/bot/telegram.js +56 -12
- package/dist/bot/telegram.js.map +1 -1
- package/dist/cli/args.js +4 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/build-repl-context.js.map +1 -1
- package/dist/cli/command-registry.js +2 -1
- package/dist/cli/command-registry.js.map +1 -1
- package/dist/cli/command-utils.js +27 -0
- package/dist/cli/command-utils.js.map +1 -0
- package/dist/cli/commands/anton.js +3 -2
- package/dist/cli/commands/anton.js.map +1 -1
- package/dist/cli/commands/model.js +8 -7
- package/dist/cli/commands/model.js.map +1 -1
- package/dist/cli/commands/project.js +5 -4
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/session.js +118 -8
- package/dist/cli/commands/session.js.map +1 -1
- package/dist/cli/commands/tools.js +4 -3
- package/dist/cli/commands/tools.js.map +1 -1
- package/dist/cli/input.js +2 -1
- package/dist/cli/input.js.map +1 -1
- package/dist/cli/repl-dispatch.js +85 -0
- package/dist/cli/repl-dispatch.js.map +1 -0
- package/dist/cli/runtime-cmds.js +7 -7
- package/dist/cli/runtime-cmds.js.map +1 -1
- package/dist/cli/service.js +0 -14
- package/dist/cli/service.js.map +1 -1
- package/dist/cli/setup.js +25 -5
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/watch.js +2 -1
- package/dist/cli/watch.js.map +1 -1
- package/dist/client.js +51 -4
- package/dist/client.js.map +1 -1
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -1
- package/dist/context.js +101 -10
- package/dist/context.js.map +1 -1
- package/dist/harnesses.js +1 -1
- package/dist/harnesses.js.map +1 -1
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/loader.js +58 -0
- package/dist/hooks/loader.js.map +1 -0
- package/dist/hooks/manager.js +180 -0
- package/dist/hooks/manager.js.map +1 -0
- package/dist/hooks/plugins/example-console.js +24 -0
- package/dist/hooks/plugins/example-console.js.map +1 -0
- package/dist/hooks/scaffold.js +53 -0
- package/dist/hooks/scaffold.js.map +1 -0
- package/dist/hooks/types.js +8 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.js +16 -64
- package/dist/index.js.map +1 -1
- package/dist/progress/agent-hooks.js +37 -0
- package/dist/progress/agent-hooks.js.map +1 -0
- package/dist/progress/ir.js +7 -0
- package/dist/progress/ir.js.map +1 -0
- package/dist/progress/progress-message-renderer.js +63 -0
- package/dist/progress/progress-message-renderer.js.map +1 -0
- package/dist/progress/serialize-discord.js +60 -0
- package/dist/progress/serialize-discord.js.map +1 -0
- package/dist/progress/serialize-telegram.js +55 -0
- package/dist/progress/serialize-telegram.js.map +1 -0
- package/dist/progress/serialize-tui.js +39 -0
- package/dist/progress/serialize-tui.js.map +1 -0
- package/dist/progress/tool-summary.js +58 -0
- package/dist/progress/tool-summary.js.map +1 -0
- package/dist/progress/tool-tail.js +48 -0
- package/dist/progress/tool-tail.js.map +1 -0
- package/dist/progress/turn-progress.js +215 -0
- package/dist/progress/turn-progress.js.map +1 -0
- package/dist/replay.js +2 -5
- package/dist/replay.js.map +1 -1
- package/dist/safety.js +0 -1
- package/dist/safety.js.map +1 -1
- package/dist/spinner.js +8 -0
- package/dist/spinner.js.map +1 -1
- package/dist/tools.js +422 -29
- package/dist/tools.js.map +1 -1
- package/dist/tui/branch-picker.js.map +1 -1
- package/dist/tui/command-handler.js.map +1 -1
- package/dist/tui/controller.js +417 -33
- package/dist/tui/controller.js.map +1 -1
- package/dist/tui/keymap.js +15 -0
- package/dist/tui/keymap.js.map +1 -1
- package/dist/tui/render.js +115 -3
- package/dist/tui/render.js.map +1 -1
- package/dist/tui/state.js +82 -1
- package/dist/tui/state.js.map +1 -1
- package/dist/upgrade.js.map +1 -1
- package/dist/utils.js +17 -0
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/tui/controller.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createSession } from "../agent.js";
|
|
|
2
2
|
import { decodeRawInput, resolveAction } from "./keymap.js";
|
|
3
3
|
import { createInitialTuiState, reduceTuiState } from "./state.js";
|
|
4
4
|
import { renderTui, setRenderTheme } from "./render.js";
|
|
5
|
+
import { calculateLayout } from "./layout.js";
|
|
5
6
|
import { enterFullScreen, leaveFullScreen } from "./screen.js";
|
|
6
7
|
import { saveSessionFile, lastSessionPath, projectSessionPath } from "../cli/session-state.js";
|
|
7
8
|
import { loadBranches, executeBranchSelect } from "./branch-picker.js";
|
|
@@ -9,8 +10,15 @@ import { ensureCommandsRegistered, allCommandNames, runShellCommand, runSlashCom
|
|
|
9
10
|
import { projectDir } from "../utils.js";
|
|
10
11
|
import { formatWatchdogCancelMessage, resolveWatchdogSettings } from "../watchdog.js";
|
|
11
12
|
import { TuiConfirmProvider } from "./confirm.js";
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
import { splitTokens } from '../cli/command-utils.js';
|
|
14
|
+
import { TurnProgressController } from '../progress/turn-progress.js';
|
|
15
|
+
import { chainAgentHooks } from '../progress/agent-hooks.js';
|
|
16
|
+
import { formatToolCallSummary } from '../progress/tool-summary.js';
|
|
17
|
+
import { ToolTailBuffer } from '../progress/tool-tail.js';
|
|
18
|
+
import { ProgressMessageRenderer } from '../progress/progress-message-renderer.js';
|
|
19
|
+
import { renderTuiLines } from '../progress/serialize-tui.js';
|
|
20
|
+
const THEME_OPTIONS = ['default', 'dark', 'light', 'minimal', 'hacker'];
|
|
21
|
+
const APPROVAL_OPTIONS = ['plan', 'default', 'auto-edit', 'yolo'];
|
|
14
22
|
export class TuiController {
|
|
15
23
|
config;
|
|
16
24
|
state = createInitialTuiState();
|
|
@@ -54,7 +62,7 @@ export class TuiController {
|
|
|
54
62
|
this.tabIndex = -1;
|
|
55
63
|
const names = allCommandNames();
|
|
56
64
|
// Add TUI-specific commands that aren't in the registry
|
|
57
|
-
const extra = ['/quit', '/exit', '/clear', '/help'];
|
|
65
|
+
const extra = ['/quit', '/exit', '/clear', '/help', '/branches', '/steps', '/settings', '/hooks'];
|
|
58
66
|
const all = [...new Set([...names, ...extra])];
|
|
59
67
|
this.tabCandidates = all.filter(n => n.toLowerCase().startsWith(prefix)).sort();
|
|
60
68
|
}
|
|
@@ -99,6 +107,214 @@ export class TuiController {
|
|
|
99
107
|
this.dispatch({ type: 'ALERT_PUSH', id: `br_${Date.now()}`, level: result.level ?? 'error', text: result.message });
|
|
100
108
|
}
|
|
101
109
|
}
|
|
110
|
+
transcriptLineStarts() {
|
|
111
|
+
const starts = [];
|
|
112
|
+
let line = 0;
|
|
113
|
+
for (const item of this.state.transcript) {
|
|
114
|
+
starts.push(line);
|
|
115
|
+
const chunks = String(item.text ?? '').split('\n');
|
|
116
|
+
line += Math.max(1, chunks.length);
|
|
117
|
+
}
|
|
118
|
+
return starts;
|
|
119
|
+
}
|
|
120
|
+
buildStepNavigatorItems(query) {
|
|
121
|
+
const q = (query ?? '').trim().toLowerCase();
|
|
122
|
+
const starts = this.transcriptLineStarts();
|
|
123
|
+
const items = this.state.transcript.map((item, idx) => {
|
|
124
|
+
const preview = String(item.text ?? '').split(/\r?\n/)[0]?.trim() ?? '';
|
|
125
|
+
return {
|
|
126
|
+
id: item.id,
|
|
127
|
+
ts: item.ts,
|
|
128
|
+
role: item.role,
|
|
129
|
+
preview,
|
|
130
|
+
lineStart: starts[idx] ?? 0,
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
if (!q)
|
|
134
|
+
return items;
|
|
135
|
+
return items.filter((it) => (`${it.role} ${it.preview}`).toLowerCase().includes(q));
|
|
136
|
+
}
|
|
137
|
+
openStepNavigator(query = '') {
|
|
138
|
+
const items = this.buildStepNavigatorItems(query);
|
|
139
|
+
this.dispatch({ type: 'STEP_NAV_OPEN', items, query });
|
|
140
|
+
}
|
|
141
|
+
stepNavigatorQueryAppend(text) {
|
|
142
|
+
const current = this.state.stepNavigator?.query ?? '';
|
|
143
|
+
const next = `${current}${text}`;
|
|
144
|
+
this.openStepNavigator(next);
|
|
145
|
+
}
|
|
146
|
+
stepNavigatorQueryBackspace() {
|
|
147
|
+
const current = this.state.stepNavigator?.query ?? '';
|
|
148
|
+
const next = current.slice(0, -1);
|
|
149
|
+
this.openStepNavigator(next);
|
|
150
|
+
}
|
|
151
|
+
jumpToStepSelection() {
|
|
152
|
+
const nav = this.state.stepNavigator;
|
|
153
|
+
if (!nav?.items.length) {
|
|
154
|
+
this.dispatch({ type: 'STEP_NAV_CLOSE' });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const selected = nav.items[nav.selectedIndex];
|
|
158
|
+
if (!selected) {
|
|
159
|
+
this.dispatch({ type: 'STEP_NAV_CLOSE' });
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const layout = calculateLayout(process.stdout.rows ?? 30, process.stdout.columns ?? 120);
|
|
163
|
+
const starts = this.transcriptLineStarts();
|
|
164
|
+
const totalLines = starts.length
|
|
165
|
+
? (starts[starts.length - 1] ?? 0) +
|
|
166
|
+
Math.max(1, String(this.state.transcript[this.state.transcript.length - 1]?.text ?? '').split('\n').length)
|
|
167
|
+
: 0;
|
|
168
|
+
const desiredStart = Math.max(0, Math.min(selected.lineStart, Math.max(0, totalLines - layout.transcriptRows)));
|
|
169
|
+
const scrollBack = Math.max(0, totalLines - (desiredStart + layout.transcriptRows));
|
|
170
|
+
this.dispatch({ type: 'SCROLL_SET', panel: 'transcript', value: scrollBack });
|
|
171
|
+
this.dispatch({ type: 'STEP_NAV_CLOSE' });
|
|
172
|
+
this.dispatch({ type: 'ALERT_PUSH', id: `step_${Date.now()}`, level: 'info', text: `Jumped to ${selected.role}: ${selected.preview || '(no preview)'}` });
|
|
173
|
+
}
|
|
174
|
+
buildSettingsItems() {
|
|
175
|
+
const watchdog = resolveWatchdogSettings(undefined, this.config);
|
|
176
|
+
return [
|
|
177
|
+
{
|
|
178
|
+
key: 'theme',
|
|
179
|
+
label: 'Theme',
|
|
180
|
+
value: this.config.theme || 'default',
|
|
181
|
+
hint: 'Cycle TUI themes instantly.',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
key: 'approval',
|
|
185
|
+
label: 'Approval mode',
|
|
186
|
+
value: this.config.approval_mode || 'default',
|
|
187
|
+
hint: 'Plan/default/auto-edit/yolo for next turns.',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
key: 'watchdog_timeout',
|
|
191
|
+
label: 'Watchdog timeout',
|
|
192
|
+
value: `${watchdog.timeoutMs} ms`,
|
|
193
|
+
hint: 'Longer timeout helps with slower models.',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
key: 'watchdog_compactions',
|
|
197
|
+
label: 'Max compactions',
|
|
198
|
+
value: String(watchdog.maxCompactions),
|
|
199
|
+
hint: 'Retries before watchdog cancellation.',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
key: 'watchdog_grace',
|
|
203
|
+
label: 'Grace windows',
|
|
204
|
+
value: String(watchdog.idleGraceTimeouts),
|
|
205
|
+
hint: 'Extra idle windows before first compaction.',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
key: 'debug_abort',
|
|
209
|
+
label: 'Debug abort reason',
|
|
210
|
+
value: watchdog.debugAbortReason ? 'on' : 'off',
|
|
211
|
+
hint: 'Show raw abort reason in cancellation messages.',
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
openSettingsMenu() {
|
|
216
|
+
this.dispatch({ type: 'SETTINGS_OPEN', items: this.buildSettingsItems() });
|
|
217
|
+
}
|
|
218
|
+
refreshSettingsMenu(selectedIndex) {
|
|
219
|
+
if (!this.state.settingsMenu)
|
|
220
|
+
return;
|
|
221
|
+
this.dispatch({ type: 'SETTINGS_UPDATE', items: this.buildSettingsItems(), selectedIndex });
|
|
222
|
+
}
|
|
223
|
+
adjustSelectedSetting(delta) {
|
|
224
|
+
const menu = this.state.settingsMenu;
|
|
225
|
+
if (!menu?.items.length)
|
|
226
|
+
return;
|
|
227
|
+
const selected = menu.items[menu.selectedIndex];
|
|
228
|
+
if (!selected)
|
|
229
|
+
return;
|
|
230
|
+
switch (selected.key) {
|
|
231
|
+
case 'theme': {
|
|
232
|
+
const current = (this.config.theme || 'default');
|
|
233
|
+
const idx = Math.max(0, THEME_OPTIONS.indexOf(current));
|
|
234
|
+
const next = THEME_OPTIONS[(idx + (delta >= 0 ? 1 : -1) + THEME_OPTIONS.length) % THEME_OPTIONS.length];
|
|
235
|
+
this.config.theme = next;
|
|
236
|
+
setRenderTheme(next);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case 'approval': {
|
|
240
|
+
const current = (this.config.approval_mode || 'default');
|
|
241
|
+
const idx = Math.max(0, APPROVAL_OPTIONS.indexOf(current));
|
|
242
|
+
const next = APPROVAL_OPTIONS[(idx + (delta >= 0 ? 1 : -1) + APPROVAL_OPTIONS.length) % APPROVAL_OPTIONS.length];
|
|
243
|
+
this.config.approval_mode = next;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case 'watchdog_timeout': {
|
|
247
|
+
const cur = this.config.watchdog_timeout_ms ?? 120_000;
|
|
248
|
+
const step = cur >= 180_000 ? 60_000 : 30_000;
|
|
249
|
+
this.config.watchdog_timeout_ms = Math.max(30_000, cur + (delta >= 0 ? step : -step));
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
case 'watchdog_compactions': {
|
|
253
|
+
const cur = this.config.watchdog_max_compactions ?? 3;
|
|
254
|
+
this.config.watchdog_max_compactions = Math.max(0, cur + (delta >= 0 ? 1 : -1));
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
case 'watchdog_grace': {
|
|
258
|
+
const cur = this.config.watchdog_idle_grace_timeouts ?? 1;
|
|
259
|
+
this.config.watchdog_idle_grace_timeouts = Math.max(0, cur + (delta >= 0 ? 1 : -1));
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
case 'debug_abort': {
|
|
263
|
+
this.config.debug_abort_reason = !(this.config.debug_abort_reason === true);
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
default:
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
this.refreshSettingsMenu(menu.selectedIndex);
|
|
270
|
+
}
|
|
271
|
+
buildHookInspectorLines(mode) {
|
|
272
|
+
const manager = this.session?.hookManager;
|
|
273
|
+
if (!manager || typeof manager.getSnapshot !== 'function') {
|
|
274
|
+
return ['Hooks are unavailable for this session.'];
|
|
275
|
+
}
|
|
276
|
+
const snap = manager.getSnapshot();
|
|
277
|
+
const totalEvents = Object.values(snap.eventCounts || {}).reduce((sum, n) => sum + Number(n || 0), 0);
|
|
278
|
+
if (mode === 'errors') {
|
|
279
|
+
return snap.recentErrors?.length
|
|
280
|
+
? snap.recentErrors.map((x) => `• ${x}`)
|
|
281
|
+
: ['No recent hook errors.'];
|
|
282
|
+
}
|
|
283
|
+
if (mode === 'slow') {
|
|
284
|
+
return snap.recentSlowHandlers?.length
|
|
285
|
+
? snap.recentSlowHandlers.map((x) => `• ${x}`)
|
|
286
|
+
: ['No recent slow hook handlers.'];
|
|
287
|
+
}
|
|
288
|
+
if (mode === 'plugins') {
|
|
289
|
+
if (!snap.plugins?.length)
|
|
290
|
+
return ['No hook plugins loaded.'];
|
|
291
|
+
const lines = [];
|
|
292
|
+
for (const p of snap.plugins) {
|
|
293
|
+
lines.push(`• ${p.name} (${p.source})`);
|
|
294
|
+
lines.push(` granted: ${p.grantedCapabilities.join(', ') || 'none'}`);
|
|
295
|
+
if (p.deniedCapabilities?.length)
|
|
296
|
+
lines.push(` denied: ${p.deniedCapabilities.join(', ')}`);
|
|
297
|
+
}
|
|
298
|
+
return lines;
|
|
299
|
+
}
|
|
300
|
+
return [
|
|
301
|
+
`Enabled: ${snap.enabled ? 'yes' : 'no'}`,
|
|
302
|
+
`Strict mode: ${snap.strict ? 'yes' : 'no'}`,
|
|
303
|
+
`Allowed capabilities: ${(snap.allowedCapabilities || []).join(', ')}`,
|
|
304
|
+
`Plugins: ${snap.plugins?.length ?? 0}`,
|
|
305
|
+
`Handlers: ${snap.handlers?.length ?? 0}`,
|
|
306
|
+
`Events observed: ${totalEvents}`,
|
|
307
|
+
`Recent errors: ${snap.recentErrors?.length ?? 0}`,
|
|
308
|
+
`Recent slow handlers: ${snap.recentSlowHandlers?.length ?? 0}`,
|
|
309
|
+
];
|
|
310
|
+
}
|
|
311
|
+
openHooksInspector(mode = 'status') {
|
|
312
|
+
this.dispatch({
|
|
313
|
+
type: 'HOOKS_INSPECTOR_OPEN',
|
|
314
|
+
mode,
|
|
315
|
+
lines: this.buildHookInspectorLines(mode),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
102
318
|
/** Push a system-role transcript item and re-render. */
|
|
103
319
|
pushSystemMessage(text) {
|
|
104
320
|
const item = { id: `sys_${Date.now()}`, role: "system", text, ts: Date.now() };
|
|
@@ -124,7 +340,8 @@ export class TuiController {
|
|
|
124
340
|
}
|
|
125
341
|
/** Handle a slash command. Returns true if handled. */
|
|
126
342
|
async handleSlashCommand(line) {
|
|
127
|
-
const
|
|
343
|
+
const parts = splitTokens(line);
|
|
344
|
+
const head = (parts[0] || "").toLowerCase();
|
|
128
345
|
if (!head.startsWith("/"))
|
|
129
346
|
return false;
|
|
130
347
|
ensureCommandsRegistered();
|
|
@@ -141,16 +358,37 @@ export class TuiController {
|
|
|
141
358
|
}
|
|
142
359
|
if (head === "/help") {
|
|
143
360
|
const cmds = allCommandNames().join(" ");
|
|
144
|
-
this.pushSystemMessage(`Commands: ${cmds}\
|
|
361
|
+
this.pushSystemMessage(`Commands: ${cmds}\n` +
|
|
362
|
+
`Shell: !<cmd> to run, !! to inject output\n` +
|
|
363
|
+
`TUI: /branches [browse|checkout|merge], /steps, /settings, /hooks [status|errors|slow|plugins]\n` +
|
|
364
|
+
`Hotkeys: Ctrl+G step navigator, Ctrl+O quick settings`);
|
|
145
365
|
return true;
|
|
146
366
|
}
|
|
147
367
|
if (head === "/branches") {
|
|
148
|
-
const parts = line.trim().split(/\s+/);
|
|
149
368
|
const sub = (parts[1] || '').toLowerCase();
|
|
150
369
|
const action = sub === 'checkout' ? 'checkout' : sub === 'merge' ? 'merge' : 'browse';
|
|
151
370
|
await this.openBranchPicker(action);
|
|
152
371
|
return true;
|
|
153
372
|
}
|
|
373
|
+
if (head === '/steps') {
|
|
374
|
+
const query = line.replace(/^\/steps\s*/i, '').trim();
|
|
375
|
+
this.openStepNavigator(query);
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
if (head === '/settings') {
|
|
379
|
+
this.openSettingsMenu();
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
if (head === '/hooks') {
|
|
383
|
+
const modeRaw = line.replace(/^\/hooks\s*/i, '').trim().toLowerCase();
|
|
384
|
+
const mode = (modeRaw || 'status');
|
|
385
|
+
if (!['status', 'errors', 'slow', 'plugins'].includes(mode)) {
|
|
386
|
+
this.dispatch({ type: 'ALERT_PUSH', id: `hooks_${Date.now()}`, level: 'warn', text: 'Usage: /hooks [status|errors|slow|plugins]' });
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
this.openHooksInspector(mode);
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
154
392
|
const result = await runSlashCommand(line, this.session, this.config, this.cleanupFn, () => this.saveTuiSessionSnapshot());
|
|
155
393
|
if (!result.found) {
|
|
156
394
|
this.dispatch({ type: "ALERT_PUSH", id: `cmd_${Date.now()}`, level: "warn", text: `Unknown command: ${head}` });
|
|
@@ -258,6 +496,35 @@ export class TuiController {
|
|
|
258
496
|
}
|
|
259
497
|
}
|
|
260
498
|
}, 5_000);
|
|
499
|
+
const progressRenderer = new ProgressMessageRenderer({
|
|
500
|
+
maxToolLines: 6,
|
|
501
|
+
maxTailLines: 4,
|
|
502
|
+
maxAssistantChars: 800,
|
|
503
|
+
});
|
|
504
|
+
const tails = new ToolTailBuffer({ maxChars: 4096, maxLines: 4 });
|
|
505
|
+
let activeToolId = null;
|
|
506
|
+
let streamedSoFar = '';
|
|
507
|
+
const flushStatus = (snap) => {
|
|
508
|
+
const tail = activeToolId ? tails.get(activeToolId) : null;
|
|
509
|
+
const doc = progressRenderer.render({
|
|
510
|
+
statusLine: snap.statusLine,
|
|
511
|
+
toolLines: snap.toolLines,
|
|
512
|
+
toolTail: tail ? { name: tail.name, stream: tail.stream, lines: tail.lines } : null,
|
|
513
|
+
assistantMarkdown: streamedSoFar,
|
|
514
|
+
});
|
|
515
|
+
const lines = renderTuiLines(doc, { maxLines: 8 });
|
|
516
|
+
const status = lines.find((l) => l.trim().length > 0) ?? '';
|
|
517
|
+
this.dispatch({ type: 'STATUS_SET', text: status });
|
|
518
|
+
};
|
|
519
|
+
const progress = new TurnProgressController((snap) => {
|
|
520
|
+
flushStatus(snap);
|
|
521
|
+
}, {
|
|
522
|
+
heartbeatMs: 1000,
|
|
523
|
+
bucketMs: 5000,
|
|
524
|
+
maxToolLines: 6,
|
|
525
|
+
toolCallSummary: (c) => formatToolCallSummary({ name: c.name, args: c.args }),
|
|
526
|
+
});
|
|
527
|
+
progress.start();
|
|
261
528
|
try {
|
|
262
529
|
let askComplete = false;
|
|
263
530
|
let isRetryAfterCompaction = false;
|
|
@@ -267,36 +534,59 @@ export class TuiController {
|
|
|
267
534
|
const askText = isRetryAfterCompaction
|
|
268
535
|
? 'Continue working on the task from where you left off. Context was compacted to free memory — do NOT restart from the beginning.'
|
|
269
536
|
: trimmed;
|
|
537
|
+
const uiHooks = {
|
|
538
|
+
signal: attemptController.signal,
|
|
539
|
+
onToken: (t) => {
|
|
540
|
+
this.lastProgressAt = Date.now();
|
|
541
|
+
watchdogGraceUsed = 0;
|
|
542
|
+
streamedSoFar += t;
|
|
543
|
+
this.dispatch({ type: 'AGENT_STREAM_TOKEN', id, token: t });
|
|
544
|
+
},
|
|
545
|
+
onToolCall: (c) => {
|
|
546
|
+
this.lastProgressAt = Date.now();
|
|
547
|
+
watchdogGraceUsed = 0;
|
|
548
|
+
activeToolId = c.id;
|
|
549
|
+
tails.reset(c.id, c.name);
|
|
550
|
+
this.dispatch({
|
|
551
|
+
type: 'TOOL_START',
|
|
552
|
+
id: c.id,
|
|
553
|
+
name: c.name,
|
|
554
|
+
detail: formatToolCallSummary({ name: c.name, args: c.args }),
|
|
555
|
+
});
|
|
556
|
+
},
|
|
557
|
+
onToolStream: (ev) => {
|
|
558
|
+
if (!activeToolId || ev.id !== activeToolId)
|
|
559
|
+
return;
|
|
560
|
+
const tailSnap = tails.push(ev);
|
|
561
|
+
const last = tailSnap?.lines?.[tailSnap.lines.length - 1];
|
|
562
|
+
if (last)
|
|
563
|
+
this.dispatch({ type: 'TOOL_TAIL', id: ev.id, tail: last });
|
|
564
|
+
flushStatus(progress.snapshot('manual'));
|
|
565
|
+
},
|
|
566
|
+
onToolResult: (r) => {
|
|
567
|
+
this.lastProgressAt = Date.now();
|
|
568
|
+
watchdogGraceUsed = 0;
|
|
569
|
+
if (activeToolId === r.id) {
|
|
570
|
+
activeToolId = null;
|
|
571
|
+
tails.clear(r.id);
|
|
572
|
+
}
|
|
573
|
+
this.dispatch({ type: r.success ? 'TOOL_END' : 'TOOL_ERROR', id: r.id, name: r.name, detail: r.summary });
|
|
574
|
+
},
|
|
575
|
+
onTurnEnd: () => {
|
|
576
|
+
this.lastProgressAt = Date.now();
|
|
577
|
+
watchdogGraceUsed = 0;
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
const hooks = chainAgentHooks(uiHooks, progress.hooks);
|
|
270
581
|
try {
|
|
271
|
-
await this.session.ask(askText,
|
|
272
|
-
signal: attemptController.signal,
|
|
273
|
-
onToken: (t) => {
|
|
274
|
-
this.lastProgressAt = Date.now();
|
|
275
|
-
watchdogGraceUsed = 0;
|
|
276
|
-
this.dispatch({ type: "AGENT_STREAM_TOKEN", id, token: t });
|
|
277
|
-
},
|
|
278
|
-
onToolCall: (c) => {
|
|
279
|
-
this.lastProgressAt = Date.now();
|
|
280
|
-
watchdogGraceUsed = 0;
|
|
281
|
-
this.dispatch({ type: "TOOL_START", id: `${c.name}-${Date.now()}`, name: c.name, detail: JSON.stringify(c.args).slice(0, 120) });
|
|
282
|
-
},
|
|
283
|
-
onToolResult: async (r) => {
|
|
284
|
-
this.lastProgressAt = Date.now();
|
|
285
|
-
watchdogGraceUsed = 0;
|
|
286
|
-
this.dispatch({ type: r.success ? "TOOL_END" : "TOOL_ERROR", id: `${r.name}-${Date.now()}`, name: r.name, detail: r.summary });
|
|
287
|
-
},
|
|
288
|
-
onTurnEnd: () => {
|
|
289
|
-
this.lastProgressAt = Date.now();
|
|
290
|
-
watchdogGraceUsed = 0;
|
|
291
|
-
},
|
|
292
|
-
});
|
|
582
|
+
await this.session.ask(askText, hooks);
|
|
293
583
|
askComplete = true;
|
|
294
584
|
}
|
|
295
585
|
catch (e) {
|
|
296
586
|
const msg = e?.message ?? String(e);
|
|
297
587
|
const isAbort = msg.includes('AbortError') || msg.toLowerCase().includes('aborted');
|
|
298
588
|
if (isAbort && watchdogCompactPending) {
|
|
299
|
-
this.dispatch({ type:
|
|
589
|
+
this.dispatch({ type: 'ALERT_PUSH', id: `compact_${Date.now()}`, level: 'info', text: `Context too large — compacting and retrying (attempt ${this.watchdogCompactAttempts}/${maxWatchdogCompacts})...` });
|
|
300
590
|
while (watchdogCompactPending) {
|
|
301
591
|
await new Promise((r) => setTimeout(r, 500));
|
|
302
592
|
}
|
|
@@ -311,17 +601,19 @@ export class TuiController {
|
|
|
311
601
|
debugAbortReason,
|
|
312
602
|
abortReason: msg,
|
|
313
603
|
});
|
|
314
|
-
this.dispatch({ type:
|
|
604
|
+
this.dispatch({ type: 'ALERT_PUSH', id: `err_${Date.now()}`, level: 'error', text });
|
|
315
605
|
}
|
|
316
606
|
else {
|
|
317
|
-
this.dispatch({ type:
|
|
607
|
+
this.dispatch({ type: 'ALERT_PUSH', id: `err_${Date.now()}`, level: 'error', text: msg });
|
|
318
608
|
}
|
|
319
609
|
}
|
|
320
610
|
}
|
|
321
611
|
}
|
|
322
612
|
finally {
|
|
613
|
+
progress.stop();
|
|
614
|
+
this.dispatch({ type: 'STATUS_CLEAR' });
|
|
323
615
|
clearInterval(watchdog);
|
|
324
|
-
this.dispatch({ type:
|
|
616
|
+
this.dispatch({ type: 'AGENT_STREAM_DONE', id });
|
|
325
617
|
this.inFlight = false;
|
|
326
618
|
this.aborter = null;
|
|
327
619
|
}
|
|
@@ -345,7 +637,7 @@ export class TuiController {
|
|
|
345
637
|
type: "ALERT_PUSH",
|
|
346
638
|
id: `info_${Date.now()}`,
|
|
347
639
|
level: "info",
|
|
348
|
-
text: "Input
|
|
640
|
+
text: "Input: Enter=send, Ctrl+J/Alt+Enter=newline, Up/Down=history, Ctrl+G=steps, Ctrl+O=settings, /hooks inspector.",
|
|
349
641
|
});
|
|
350
642
|
const onSigwinch = () => {
|
|
351
643
|
renderTui(this.state);
|
|
@@ -406,6 +698,90 @@ export class TuiController {
|
|
|
406
698
|
}
|
|
407
699
|
continue; // swallow all other input during picker
|
|
408
700
|
}
|
|
701
|
+
// Step navigator: type to filter, arrows to select, Enter to jump.
|
|
702
|
+
if (this.state.stepNavigator) {
|
|
703
|
+
const nAction = resolveAction(key);
|
|
704
|
+
if (key.startsWith('text:')) {
|
|
705
|
+
const ch = key.slice(5);
|
|
706
|
+
if (ch === 'q' && !this.state.stepNavigator.query) {
|
|
707
|
+
this.dispatch({ type: 'STEP_NAV_CLOSE' });
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
this.stepNavigatorQueryAppend(ch);
|
|
711
|
+
}
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
if (nAction === 'backspace') {
|
|
715
|
+
this.stepNavigatorQueryBackspace();
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (nAction === 'history_prev' || nAction === 'cursor_left') {
|
|
719
|
+
this.dispatch({ type: 'STEP_NAV_MOVE', delta: -1 });
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (nAction === 'history_next' || nAction === 'cursor_right') {
|
|
723
|
+
this.dispatch({ type: 'STEP_NAV_MOVE', delta: 1 });
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (nAction === 'scroll_up') {
|
|
727
|
+
this.dispatch({ type: 'STEP_NAV_MOVE', delta: -10 });
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (nAction === 'scroll_down') {
|
|
731
|
+
this.dispatch({ type: 'STEP_NAV_MOVE', delta: 10 });
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (nAction === 'send') {
|
|
735
|
+
this.jumpToStepSelection();
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (nAction === 'cancel' || nAction === 'quit') {
|
|
739
|
+
this.dispatch({ type: 'STEP_NAV_CLOSE' });
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
// Settings menu: arrows/select to adjust config quickly.
|
|
745
|
+
if (this.state.settingsMenu) {
|
|
746
|
+
const sAction = resolveAction(key);
|
|
747
|
+
if (sAction === 'history_prev') {
|
|
748
|
+
this.dispatch({ type: 'SETTINGS_MOVE', delta: -1 });
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (sAction === 'history_next') {
|
|
752
|
+
this.dispatch({ type: 'SETTINGS_MOVE', delta: 1 });
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (sAction === 'cursor_left') {
|
|
756
|
+
this.adjustSelectedSetting(-1);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (sAction === 'cursor_right' || sAction === 'send') {
|
|
760
|
+
this.adjustSelectedSetting(1);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (sAction === 'cancel' || sAction === 'quit' || key === 'text:q') {
|
|
764
|
+
this.dispatch({ type: 'SETTINGS_CLOSE' });
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
if (this.state.hooksInspector) {
|
|
770
|
+
const hAction = resolveAction(key);
|
|
771
|
+
if (hAction === 'history_prev' || hAction === 'scroll_up') {
|
|
772
|
+
this.dispatch({ type: 'HOOKS_INSPECTOR_MOVE', delta: -1 });
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
if (hAction === 'history_next' || hAction === 'scroll_down') {
|
|
776
|
+
this.dispatch({ type: 'HOOKS_INSPECTOR_MOVE', delta: 1 });
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (hAction === 'cancel' || hAction === 'quit' || key === 'text:q' || hAction === 'send') {
|
|
780
|
+
this.dispatch({ type: 'HOOKS_INSPECTOR_CLOSE' });
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
409
785
|
if (key.startsWith("text:")) {
|
|
410
786
|
this.resetTab();
|
|
411
787
|
this.dispatch({ type: "USER_INPUT_INSERT", text: key.slice(5) });
|
|
@@ -421,6 +797,14 @@ export class TuiController {
|
|
|
421
797
|
}
|
|
422
798
|
// Any non-tab action resets tab cycling
|
|
423
799
|
this.resetTab();
|
|
800
|
+
if (action === 'open_step_navigator') {
|
|
801
|
+
this.openStepNavigator();
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
if (action === 'open_settings') {
|
|
805
|
+
this.openSettingsMenu();
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
424
808
|
if (action === "quit") {
|
|
425
809
|
void cleanup();
|
|
426
810
|
continue;
|