omniharness-cli 0.1.37 → 0.1.38
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/dist/agent/mastraEngine.d.ts +22 -1
- package/dist/agent/mastraEngine.js +43 -6
- package/dist/agent/mastraEngine.js.map +1 -1
- package/dist/cli.js +33 -1
- package/dist/cli.js.map +1 -1
- package/dist/sessionList.d.ts +20 -0
- package/dist/sessionList.js +67 -0
- package/dist/sessionList.js.map +1 -0
- package/dist/ui/groups.d.ts +34 -0
- package/dist/ui/groups.js +50 -0
- package/dist/ui/groups.js.map +1 -0
- package/dist/ui/highlight.d.ts +22 -0
- package/dist/ui/highlight.js +148 -0
- package/dist/ui/highlight.js.map +1 -0
- package/dist/ui/markdown.js +4 -5
- package/dist/ui/markdown.js.map +1 -1
- package/dist/ui/modelWindows.d.ts +29 -0
- package/dist/ui/modelWindows.js +68 -0
- package/dist/ui/modelWindows.js.map +1 -0
- package/dist/ui/palette.d.ts +10 -5
- package/dist/ui/palette.js +28 -9
- package/dist/ui/palette.js.map +1 -1
- package/dist/ui/termcaps.d.ts +56 -0
- package/dist/ui/termcaps.js +76 -0
- package/dist/ui/termcaps.js.map +1 -0
- package/dist/ui/terminalInterface.js +392 -73
- package/dist/ui/terminalInterface.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { Box, Text, useApp, useInput, useStdin, useStdout } from 'ink';
|
|
4
4
|
import Spinner from 'ink-spinner';
|
|
5
5
|
import { appendPromptHistory, loadPromptHistory } from '../promptHistory.js';
|
|
6
|
+
import { listSessions, loadSnapshot, saveSnapshot, deleteSnapshot } from '../sessionList.js';
|
|
6
7
|
import { deleteAt, deleteBefore, insertAt, layoutEditor, lineEndAt, lineStartAt, moveHorizontal, moveVerticalWrapped, normalizePaste } from './editor.js';
|
|
7
8
|
import { renderMarkdown } from './markdown.js';
|
|
8
9
|
import { looksLikeDiff, diffSegments } from './diff.js';
|
|
10
|
+
import { foldToolGroups } from './groups.js';
|
|
9
11
|
import { palette } from './palette.js';
|
|
12
|
+
import { contextMeter, meterBar } from './modelWindows.js';
|
|
13
|
+
import { BEL, SYNC_QUERY, isSyncOutputReply, osc9Notify, osc52Copy, shouldNudgeOnFinish, wrapSynchronizedOutput } from './termcaps.js';
|
|
10
14
|
import { KITTY_POP, KITTY_PUSH, KITTY_QUERY, isEncodedKey, isKittyQueryResponse, parseRawKey } from './keys.js';
|
|
11
15
|
function describeTarget(name, input) {
|
|
12
16
|
if (input && typeof input === 'object') {
|
|
@@ -20,6 +24,22 @@ function describeTarget(name, input) {
|
|
|
20
24
|
}
|
|
21
25
|
return '';
|
|
22
26
|
}
|
|
27
|
+
/** Short verb shown on a tool card header, per tool type. */
|
|
28
|
+
function toolVerb(tool) {
|
|
29
|
+
switch (tool) {
|
|
30
|
+
case 'read_file': return 'read';
|
|
31
|
+
case 'write_file': return 'edit';
|
|
32
|
+
case 'run_command': return '$';
|
|
33
|
+
case 'git_diff': return 'diff';
|
|
34
|
+
case 'semantic_search': return 'search';
|
|
35
|
+
case 'index_workspace': return 'index';
|
|
36
|
+
case 'update_todo': return 'plan';
|
|
37
|
+
case 'write_memory': return 'memory';
|
|
38
|
+
case 'start_preview': return 'preview';
|
|
39
|
+
case 'route': return 'route';
|
|
40
|
+
default: return tool;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
23
43
|
function phaseFor(tool) {
|
|
24
44
|
switch (tool) {
|
|
25
45
|
case 'read_file': return 'reading files';
|
|
@@ -103,6 +123,46 @@ function SegmentText({ segments, role }) {
|
|
|
103
123
|
const base = role === 'thinking' ? PALETTE.warn : role === 'tool' ? PALETTE.muted : role === 'assistant' ? PALETTE.success : role === 'user' ? PALETTE.info : undefined;
|
|
104
124
|
return _jsx(Text, { color: base, children: segments.map((segment, index) => (_jsx(Text, { bold: segment.bold, italic: segment.italic, strikethrough: segment.strikethrough, underline: segment.underline, dimColor: segment.dim, color: segment.color ?? base, children: segment.text }, index))) });
|
|
105
125
|
}
|
|
126
|
+
/** Expanded body of a tool card, rendered per tool type. */
|
|
127
|
+
function renderToolBody(card, width, p) {
|
|
128
|
+
const trail = card.trail;
|
|
129
|
+
if (!trail)
|
|
130
|
+
return card.summary ? _jsx(Text, { dimColor: true, children: card.summary.slice(0, width) }) : _jsx(Text, { dimColor: true, children: "(no output)" });
|
|
131
|
+
if (looksLikeDiff(trail)) {
|
|
132
|
+
return _jsx(_Fragment, { children: diffSegments(trail, width).slice(0, 14).map((segments, index) => _jsx(SegmentText, { segments: segments, role: "tool" }, index)) });
|
|
133
|
+
}
|
|
134
|
+
const rows = trail.split('\n').slice(0, 14);
|
|
135
|
+
if (card.name === 'write_file') {
|
|
136
|
+
// A write trail is the new file content — show it as added (green) lines.
|
|
137
|
+
return _jsx(_Fragment, { children: rows.map((line, index) => _jsx(Text, { color: p.success, children: line.slice(0, width) }, index)) });
|
|
138
|
+
}
|
|
139
|
+
if (card.name === 'run_command') {
|
|
140
|
+
return _jsx(_Fragment, { children: rows.map((line, index) => {
|
|
141
|
+
const exit = /^exit (\d+)/.exec(line);
|
|
142
|
+
const color = exit ? (exit[1] === '0' ? p.success : p.error) : undefined;
|
|
143
|
+
return _jsx(Text, { color: color, dimColor: color === undefined, children: line.slice(0, width) }, index);
|
|
144
|
+
}) });
|
|
145
|
+
}
|
|
146
|
+
return _jsx(_Fragment, { children: rows.map((line, index) => _jsx(Text, { dimColor: true, children: line.slice(0, width) }, index)) });
|
|
147
|
+
}
|
|
148
|
+
/** Render one Line into transcript Rows (label row + content rows). */
|
|
149
|
+
function renderLineToRows(line, lineIndex, width, fallback) {
|
|
150
|
+
const baseLabel = labelFor(line.role, line.model, line.toolName, fallback);
|
|
151
|
+
// Route ribbon: the provider a given answer actually came from is provenance —
|
|
152
|
+
// it belongs on the answer's label, next to the model badge.
|
|
153
|
+
const label = line.role === 'assistant' && line.provider
|
|
154
|
+
? `${baseLabel} · via ${line.provider}${line.fallback ? ' (failover)' : ''}`
|
|
155
|
+
: baseLabel;
|
|
156
|
+
const markdown = line.role !== 'tool' && line.role !== 'error';
|
|
157
|
+
const wrapped = markdown
|
|
158
|
+
? renderMarkdown(line.text, width)
|
|
159
|
+
: wrap(line.text, width).map((text) => [{ text }]);
|
|
160
|
+
const next = [];
|
|
161
|
+
wrapped.forEach((segments, rowIndex) => next.push({ key: `message-${lineIndex}-${rowIndex}`, role: line.role, segments, label, first: rowIndex === 0 }));
|
|
162
|
+
if (line.saved)
|
|
163
|
+
next.push({ key: `message-${lineIndex}-saved`, role: 'assistant', segments: [{ text: line.saved }], label: '', first: false, saved: line.saved });
|
|
164
|
+
return next;
|
|
165
|
+
}
|
|
106
166
|
export function TerminalInterface({ engine }) {
|
|
107
167
|
const { exit } = useApp();
|
|
108
168
|
const { stdout } = useStdout();
|
|
@@ -132,6 +192,10 @@ export function TerminalInterface({ engine }) {
|
|
|
132
192
|
const [liveThink, setLiveThink] = useState('');
|
|
133
193
|
const [liveAnswer, setLiveAnswer] = useState('');
|
|
134
194
|
const [kitty, setKitty] = useState(null);
|
|
195
|
+
const [sessionsList, setSessionsList] = useState([]);
|
|
196
|
+
const [sessionsOpen, setSessionsOpen] = useState(false);
|
|
197
|
+
const [sessionsIndex, setSessionsIndex] = useState(0);
|
|
198
|
+
const [groupsExpanded, setGroupsExpanded] = useState(false);
|
|
135
199
|
const [taskQueue, setTaskQueue] = useState(engine.state.taskQueue);
|
|
136
200
|
const [currentTool, setCurrentTool] = useState();
|
|
137
201
|
// Tool activity rendered as collapsible cards: each completed/ongoing tool
|
|
@@ -141,6 +205,13 @@ export function TerminalInterface({ engine }) {
|
|
|
141
205
|
// Run timing: monotonic start time plus a ticking elapsed counter while busy.
|
|
142
206
|
const runStartedAt = useRef(null);
|
|
143
207
|
const [now, setNow] = useState(() => Date.now());
|
|
208
|
+
// A prompt typed while a run is in flight: stashed here and submitted when the run ends.
|
|
209
|
+
const queuedRef = useRef(null);
|
|
210
|
+
const [queued, setQueued] = useState();
|
|
211
|
+
// Ctrl+L paints the row budget of every layout region over the transcript.
|
|
212
|
+
const [layoutDebug, setLayoutDebug] = useState(false);
|
|
213
|
+
// Restores plain stdout.write when synchronized-output bracketing is torn down.
|
|
214
|
+
const syncRestoreRef = useRef(null);
|
|
144
215
|
useEffect(() => {
|
|
145
216
|
let alive = true;
|
|
146
217
|
// Seed history from disk, but only if the user hasn't already submitted a
|
|
@@ -157,8 +228,14 @@ export function TerminalInterface({ engine }) {
|
|
|
157
228
|
stdout.write(KITTY_PUSH);
|
|
158
229
|
let kittyTimer;
|
|
159
230
|
// Terminals with kitty support answer the query (ESC[? flags u); others ignore it.
|
|
231
|
+
// The same probe pass asks about synchronized output (DECSET 2026): a positive
|
|
232
|
+
// reply lets us bracket every frame so streaming never tears mid-repaint.
|
|
160
233
|
const onKitty = (chunk) => {
|
|
161
|
-
|
|
234
|
+
const text = chunk.toString();
|
|
235
|
+
if (isSyncOutputReply(text) && syncRestoreRef.current === null) {
|
|
236
|
+
syncRestoreRef.current = wrapSynchronizedOutput(stdout);
|
|
237
|
+
}
|
|
238
|
+
if (!isKittyQueryResponse(text))
|
|
162
239
|
return;
|
|
163
240
|
if (kittyTimer)
|
|
164
241
|
clearTimeout(kittyTimer);
|
|
@@ -168,6 +245,7 @@ export function TerminalInterface({ engine }) {
|
|
|
168
245
|
if (stdin) {
|
|
169
246
|
stdin.on('data', onKitty);
|
|
170
247
|
kittyTimer = setTimeout(() => { setKitty(false); stdin.off('data', onKitty); }, 300);
|
|
248
|
+
stdout.write(SYNC_QUERY);
|
|
171
249
|
stdout.write(KITTY_QUERY);
|
|
172
250
|
}
|
|
173
251
|
else {
|
|
@@ -189,10 +267,19 @@ export function TerminalInterface({ engine }) {
|
|
|
189
267
|
case 'text':
|
|
190
268
|
setLines((current) => [...current, {
|
|
191
269
|
role: 'assistant', text: event.content, model: event.model,
|
|
270
|
+
provider: event.provider, fallback: event.fallback,
|
|
192
271
|
saved: event.compression ? `${Math.round((1 - event.compression.ratio) * 100)}% saved (${event.compression.strategy.toUpperCase()}) · ${event.compression.savedTokens.toLocaleString()} tokens` : undefined,
|
|
193
272
|
}]);
|
|
194
273
|
setLiveAnswer('');
|
|
195
274
|
break;
|
|
275
|
+
case 'route':
|
|
276
|
+
setLines((current) => [...current, {
|
|
277
|
+
role: 'tool', toolName: 'route',
|
|
278
|
+
text: event.fallback
|
|
279
|
+
? `route · failover → ${event.provider ?? 'unknown'} (attempt ${event.attempts + 1})${event.reason ? ` · ${event.reason}` : ''}`
|
|
280
|
+
: `route · ${event.provider ?? 'unknown'}`,
|
|
281
|
+
}]);
|
|
282
|
+
break;
|
|
196
283
|
case 'tool_start':
|
|
197
284
|
setToolCards((current) => [...current, {
|
|
198
285
|
id: `tc-${Date.now().toString(36)}-${current.length}`, name: event.tool,
|
|
@@ -206,7 +293,9 @@ export function TerminalInterface({ engine }) {
|
|
|
206
293
|
case 'tool_result':
|
|
207
294
|
setToolCards((current) => current.length === 0
|
|
208
295
|
? current
|
|
209
|
-
: current.map((card, index) => index === current.length - 1
|
|
296
|
+
: current.map((card, index) => index === current.length - 1
|
|
297
|
+
? { ...card, status: 'done', summary: event.summary, trail: card.trail ?? event.detail }
|
|
298
|
+
: card));
|
|
210
299
|
setCurrentTool(undefined);
|
|
211
300
|
break;
|
|
212
301
|
case 'todos':
|
|
@@ -233,10 +322,12 @@ export function TerminalInterface({ engine }) {
|
|
|
233
322
|
stdout.off('resize', onResize);
|
|
234
323
|
const pendingApproval = approvalResolve.current;
|
|
235
324
|
approvalResolve.current = null;
|
|
236
|
-
pendingApproval?.(false);
|
|
325
|
+
pendingApproval?.({ approved: false });
|
|
237
326
|
engine.stop();
|
|
238
327
|
unsubscribe();
|
|
239
328
|
process.off('exit', onUnload);
|
|
329
|
+
syncRestoreRef.current?.();
|
|
330
|
+
syncRestoreRef.current = null;
|
|
240
331
|
stdout.write(KITTY_POP);
|
|
241
332
|
};
|
|
242
333
|
}, [engine, stdout, stdin]);
|
|
@@ -268,11 +359,77 @@ export function TerminalInterface({ engine }) {
|
|
|
268
359
|
engine.state.mode = next;
|
|
269
360
|
setLines((current) => [...current, { role: 'tool', text: `mode → ${next}`, toolName: 'mode' }]);
|
|
270
361
|
};
|
|
271
|
-
const approve = (
|
|
362
|
+
const approve = (approved, trust) => {
|
|
272
363
|
const resolve = approvalResolve.current;
|
|
273
364
|
setApproval(null);
|
|
274
365
|
approvalResolve.current = null;
|
|
275
|
-
resolve?.(
|
|
366
|
+
resolve?.({ approved, trust });
|
|
367
|
+
};
|
|
368
|
+
/** Copy the most recent assistant reply (or last transcript line) to the clipboard via OSC 52. */
|
|
369
|
+
const yankLastBlock = () => {
|
|
370
|
+
const target = [...lines].reverse().find((entry) => entry.role === 'assistant') ?? lines[lines.length - 1];
|
|
371
|
+
if (!target)
|
|
372
|
+
return;
|
|
373
|
+
const seq = osc52Copy(target.text);
|
|
374
|
+
if (!seq) {
|
|
375
|
+
setLines((current) => [...current, { role: 'tool', text: 'clipboard: block too large to copy', toolName: 'clipboard' }]);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
stdout.write(seq);
|
|
380
|
+
setLines((current) => [...current, { role: 'tool', text: `copied ${target.text.length} chars to clipboard`, toolName: 'clipboard' }]);
|
|
381
|
+
}
|
|
382
|
+
catch { /* clipboard write is best-effort */ }
|
|
383
|
+
};
|
|
384
|
+
/** Kick off an engine run for `prompt`, attaching `attachSpec` files first when given. */
|
|
385
|
+
const startRun = (prompt, attachSpec) => {
|
|
386
|
+
followTranscriptRef.current = true;
|
|
387
|
+
setScrollOffset(0);
|
|
388
|
+
setEdit({ value: '', cursor: 0 });
|
|
389
|
+
setBusy(true);
|
|
390
|
+
setError(undefined);
|
|
391
|
+
setToolCards([]);
|
|
392
|
+
setExpandedTool(undefined);
|
|
393
|
+
if (runStartedAt.current === null)
|
|
394
|
+
runStartedAt.current = Date.now();
|
|
395
|
+
setNow(Date.now());
|
|
396
|
+
historyIdxRef.current = -1;
|
|
397
|
+
if (prompt) {
|
|
398
|
+
syncPromptHistory([prompt, ...promptHistoryRef.current.filter((entry) => entry !== prompt)].slice(0, 200));
|
|
399
|
+
void appendPromptHistory(prompt).catch(() => { });
|
|
400
|
+
}
|
|
401
|
+
if (!attachSpec)
|
|
402
|
+
setLines((current) => [...current, { role: 'user', text: prompt }]);
|
|
403
|
+
void (async () => {
|
|
404
|
+
try {
|
|
405
|
+
if (attachSpec)
|
|
406
|
+
await engine.attach(attachSpec.split(/\s+/).filter(Boolean));
|
|
407
|
+
await engine.run(prompt);
|
|
408
|
+
/* answer is streamed live via text_delta / text events */
|
|
409
|
+
}
|
|
410
|
+
catch (reason) {
|
|
411
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
412
|
+
setError(message);
|
|
413
|
+
setLines((current) => [...current, { role: 'error', text: message }]);
|
|
414
|
+
}
|
|
415
|
+
finally {
|
|
416
|
+
const startedAt = runStartedAt.current;
|
|
417
|
+
setBusy(false);
|
|
418
|
+
setCurrentTool(undefined);
|
|
419
|
+
setToolCards((current) => current.map((card) => card.status === 'running' ? { ...card, status: 'error' } : card));
|
|
420
|
+
runStartedAt.current = null;
|
|
421
|
+
setLiveThink('');
|
|
422
|
+
setLiveAnswer('');
|
|
423
|
+
// A long run probably pulled focus elsewhere: nudge with a bell + OSC 9 notification.
|
|
424
|
+
if (startedAt !== null && shouldNudgeOnFinish(Date.now() - startedAt)) {
|
|
425
|
+
try {
|
|
426
|
+
stdout.write(osc9Notify('OmniHarness — run finished'));
|
|
427
|
+
stdout.write(BEL);
|
|
428
|
+
}
|
|
429
|
+
catch { /* nudge is best-effort */ }
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
})();
|
|
276
433
|
};
|
|
277
434
|
/** ↑ walks older prompts, ↓ walks newer; ↓ past the newest clears the input. */
|
|
278
435
|
const navigateHistory = (older) => {
|
|
@@ -288,6 +445,24 @@ export function TerminalInterface({ engine }) {
|
|
|
288
445
|
};
|
|
289
446
|
/** Whether ↑/↓ should browse prompt history instead of moving the text caret. */
|
|
290
447
|
const browsingHistory = () => historyIdxRef.current >= 0 || edit.value === '';
|
|
448
|
+
// Kept current each render so jumpToLine (defined before the row layout is
|
|
449
|
+
// computed) can resolve a transcript-line index to a scroll position.
|
|
450
|
+
const allRowsRef = useRef([]);
|
|
451
|
+
const storedHeightRef = useRef(0);
|
|
452
|
+
/** Scroll so the first rendered row of transcript line `lineIndex` sits at the top. */
|
|
453
|
+
const jumpToLine = (lineIndex) => {
|
|
454
|
+
const rows = allRowsRef.current;
|
|
455
|
+
const targetRow = rows.findIndex((row) => row.key.startsWith(`message-${lineIndex}-`));
|
|
456
|
+
if (targetRow < 0)
|
|
457
|
+
return;
|
|
458
|
+
followTranscriptRef.current = false;
|
|
459
|
+
const desired = rows.length - targetRow - storedHeightRef.current;
|
|
460
|
+
setScrollOffset(clamp(desired, 0, maxScrollRef.current));
|
|
461
|
+
};
|
|
462
|
+
/** Chapters: one per user turn, titled by the prompt's first line. */
|
|
463
|
+
const chapters = () => lines.flatMap((line, index) => line.role === 'user'
|
|
464
|
+
? [{ index, title: line.text.split('\n')[0].slice(0, 60) || '(empty prompt)' }]
|
|
465
|
+
: []);
|
|
291
466
|
/** Scroll by rendered transcript rows; positive values move toward older output. */
|
|
292
467
|
const scrollTranscript = (delta) => {
|
|
293
468
|
if (delta > 0 && maxScrollRef.current > 0)
|
|
@@ -303,6 +478,8 @@ export function TerminalInterface({ engine }) {
|
|
|
303
478
|
const applyAction = (action) => {
|
|
304
479
|
if (approval && action.kind !== 'submit' && action.kind !== 'escape' && action.kind !== 'ctrlC')
|
|
305
480
|
return;
|
|
481
|
+
if (sessionsOpen && action.kind !== 'submit' && action.kind !== 'escape' && action.kind !== 'up' && action.kind !== 'down')
|
|
482
|
+
return;
|
|
306
483
|
if (pickerOpen && action.kind !== 'submit' && action.kind !== 'escape' && action.kind !== 'up' && action.kind !== 'down')
|
|
307
484
|
return;
|
|
308
485
|
switch (action.kind) {
|
|
@@ -311,6 +488,22 @@ export function TerminalInterface({ engine }) {
|
|
|
311
488
|
approve(true);
|
|
312
489
|
return;
|
|
313
490
|
}
|
|
491
|
+
if (sessionsOpen) {
|
|
492
|
+
const selected = sessionsList[sessionsIndex];
|
|
493
|
+
if (selected) {
|
|
494
|
+
void loadSnapshot(engine.state.workspace.root, selected.name).then((snapshot) => {
|
|
495
|
+
if (snapshot == null) {
|
|
496
|
+
setLines((current) => [...current, { role: 'error', text: `snapshot ${selected.name} is unreadable` }]);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
setLines(snapshot.messages.map(lineFromMessage));
|
|
500
|
+
setTaskQueue(snapshot.taskQueue);
|
|
501
|
+
setLines((current) => [...current, { role: 'tool', text: `session resumed: ${selected.name} (${snapshot.messages.length} messages)`, toolName: 'sessions' }]);
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
setSessionsOpen(false);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
314
507
|
if (pickerOpen) {
|
|
315
508
|
const selected = pickerItems[pickerIndex];
|
|
316
509
|
if (selected) {
|
|
@@ -327,13 +520,89 @@ export function TerminalInterface({ engine }) {
|
|
|
327
520
|
setLines((current) => [...current,
|
|
328
521
|
{ role: 'tool', text: '/help — show commands', toolName: 'commands' },
|
|
329
522
|
{ role: 'tool', text: '/clear — start a fresh conversation', toolName: 'commands' },
|
|
523
|
+
{ role: 'tool', text: '/sessions — list saved sessions (enter to resume)', toolName: 'commands' },
|
|
524
|
+
{ role: 'tool', text: '/save <name> — snapshot the current session', toolName: 'commands' },
|
|
525
|
+
{ role: 'tool', text: '/forget <name> — delete a saved session', toolName: 'commands' },
|
|
330
526
|
{ role: 'tool', text: '/attach <files> — attach files to the next message', toolName: 'commands' },
|
|
331
|
-
{ role: 'tool', text: '
|
|
527
|
+
{ role: 'tool', text: '/find <text> — jump to the most recent line containing <text>', toolName: 'commands' },
|
|
528
|
+
{ role: 'tool', text: '/chapters — list turns and jump: /chapters <n>', toolName: 'commands' },
|
|
529
|
+
{ role: 'tool', text: 'keys: Ctrl+O models · Ctrl+E mode · Ctrl+C cancel · PgUp/PgDn scroll · Ctrl+G fold tool groups · Ctrl+T tool card · Ctrl+Y copy last reply · Ctrl+L layout budget · ↑/↓ prompt history', toolName: 'commands' },
|
|
530
|
+
{ role: 'tool', text: 'a prompt typed while a run is working is queued and sent when it finishes', toolName: 'commands' },
|
|
531
|
+
]);
|
|
532
|
+
setEdit({ value: '', cursor: 0 });
|
|
533
|
+
historyIdxRef.current = -1;
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const findMatch = /^\/find\s+(.+)$/.exec(raw);
|
|
537
|
+
if (findMatch) {
|
|
538
|
+
const needle = findMatch[1].toLowerCase();
|
|
539
|
+
const hits = lines.map((line, index) => ({ line, index })).filter(({ line }) => line.text.toLowerCase().includes(needle));
|
|
540
|
+
const last = hits[hits.length - 1];
|
|
541
|
+
setLines((current) => [...current, {
|
|
542
|
+
role: 'tool', toolName: 'find',
|
|
543
|
+
text: last ? `find "${findMatch[1]}" · ${hits.length} match${hits.length === 1 ? '' : 'es'} · jumped to the most recent` : `no match for "${findMatch[1]}"`,
|
|
544
|
+
}]);
|
|
545
|
+
if (last)
|
|
546
|
+
jumpToLine(last.index);
|
|
547
|
+
setEdit({ value: '', cursor: 0 });
|
|
548
|
+
historyIdxRef.current = -1;
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const chapterJump = /^\/chapters?\s+(\d+)$/.exec(raw);
|
|
552
|
+
if (chapterJump) {
|
|
553
|
+
const list = chapters();
|
|
554
|
+
const pick = list[Number(chapterJump[1]) - 1];
|
|
555
|
+
if (pick)
|
|
556
|
+
jumpToLine(pick.index);
|
|
557
|
+
setLines((current) => [...current, { role: 'tool', toolName: 'chapters', text: pick ? `jumped to chapter ${chapterJump[1]}: ${pick.title}` : `no chapter ${chapterJump[1]}` }]);
|
|
558
|
+
setEdit({ value: '', cursor: 0 });
|
|
559
|
+
historyIdxRef.current = -1;
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (raw === '/chapters' || raw === '/chapter') {
|
|
563
|
+
const list = chapters();
|
|
564
|
+
setLines((current) => [...current,
|
|
565
|
+
...(list.length === 0 ? [{ role: 'tool', toolName: 'chapters', text: 'no chapters yet — each prompt starts one' }]
|
|
566
|
+
: list.map((chapter, order) => ({ role: 'tool', toolName: 'chapters', text: `${order + 1}. ${chapter.title}` }))),
|
|
567
|
+
...(list.length > 0 ? [{ role: 'tool', toolName: 'chapters', text: '/chapters <n> to jump' }] : []),
|
|
332
568
|
]);
|
|
333
569
|
setEdit({ value: '', cursor: 0 });
|
|
334
570
|
historyIdxRef.current = -1;
|
|
335
571
|
return;
|
|
336
572
|
}
|
|
573
|
+
if (raw === '/sessions') {
|
|
574
|
+
void listSessions(engine.state.workspace.root).then((sessions) => {
|
|
575
|
+
setSessionsList(sessions);
|
|
576
|
+
setSessionsIndex(0);
|
|
577
|
+
setSessionsOpen(sessions.length > 0);
|
|
578
|
+
if (sessions.length === 0) {
|
|
579
|
+
setLines((current) => [...current, { role: 'tool', text: 'no saved sessions — use /save <name> to snapshot this one', toolName: 'sessions' }]);
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
setEdit({ value: '', cursor: 0 });
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const saveMatch = /^\/save\s+([\w.-]+)$/.exec(raw);
|
|
586
|
+
if (saveMatch) {
|
|
587
|
+
const name = saveMatch[1];
|
|
588
|
+
void saveSnapshot(engine.state.workspace.root, name, {
|
|
589
|
+
messages: [...engine.state.messages], taskQueue: [...engine.state.taskQueue], savedAt: new Date().toISOString(),
|
|
590
|
+
}).then(() => {
|
|
591
|
+
setLines((current) => [...current, { role: 'tool', text: `session saved: ${name}`, toolName: 'sessions' }]);
|
|
592
|
+
}).catch((reason) => {
|
|
593
|
+
setLines((current) => [...current, { role: 'error', text: `save failed: ${reason instanceof Error ? reason.message : String(reason)}` }]);
|
|
594
|
+
});
|
|
595
|
+
setEdit({ value: '', cursor: 0 });
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const delMatch = /^\/forget\s+([\w.-]+)$/.exec(raw);
|
|
599
|
+
if (delMatch) {
|
|
600
|
+
void deleteSnapshot(engine.state.workspace.root, delMatch[1]).then(() => {
|
|
601
|
+
setLines((current) => [...current, { role: 'tool', text: `session deleted: ${delMatch[1]}`, toolName: 'sessions' }]);
|
|
602
|
+
});
|
|
603
|
+
setEdit({ value: '', cursor: 0 });
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
337
606
|
if (raw === '/clear') {
|
|
338
607
|
historyIdxRef.current = -1;
|
|
339
608
|
followTranscriptRef.current = true;
|
|
@@ -353,46 +622,19 @@ export function TerminalInterface({ engine }) {
|
|
|
353
622
|
return;
|
|
354
623
|
}
|
|
355
624
|
const prompt = attachMatch ? '' : raw;
|
|
356
|
-
|
|
625
|
+
const attachSpec = attachMatch ? attachMatch[1] : undefined;
|
|
626
|
+
if (!prompt && !attachSpec)
|
|
627
|
+
return;
|
|
628
|
+
// Input stays live during a run: a prompt typed now is queued, not dropped,
|
|
629
|
+
// and fires the moment the current run ends.
|
|
630
|
+
if (busy) {
|
|
631
|
+
queuedRef.current = { prompt, attachSpec };
|
|
632
|
+
setQueued(prompt || `/attach ${attachSpec ?? ''}`.trim());
|
|
633
|
+
setEdit({ value: '', cursor: 0 });
|
|
634
|
+
historyIdxRef.current = -1;
|
|
357
635
|
return;
|
|
358
|
-
followTranscriptRef.current = true;
|
|
359
|
-
setScrollOffset(0);
|
|
360
|
-
setEdit({ value: '', cursor: 0 });
|
|
361
|
-
setBusy(true);
|
|
362
|
-
setError(undefined);
|
|
363
|
-
setToolCards([]);
|
|
364
|
-
setExpandedTool(undefined);
|
|
365
|
-
if (runStartedAt.current === null)
|
|
366
|
-
runStartedAt.current = Date.now();
|
|
367
|
-
setNow(Date.now());
|
|
368
|
-
historyIdxRef.current = -1;
|
|
369
|
-
if (prompt) {
|
|
370
|
-
syncPromptHistory([prompt, ...promptHistoryRef.current.filter((entry) => entry !== prompt)].slice(0, 200));
|
|
371
|
-
void appendPromptHistory(prompt).catch(() => { });
|
|
372
636
|
}
|
|
373
|
-
|
|
374
|
-
setLines((current) => [...current, { role: 'user', text: prompt }]);
|
|
375
|
-
void (async () => {
|
|
376
|
-
try {
|
|
377
|
-
if (attachMatch)
|
|
378
|
-
await engine.attach(attachMatch[1].split(/\s+/).filter(Boolean));
|
|
379
|
-
await engine.run(prompt);
|
|
380
|
-
/* answer is streamed live via text_delta / text events */
|
|
381
|
-
}
|
|
382
|
-
catch (reason) {
|
|
383
|
-
const message = reason instanceof Error ? reason.message : String(reason);
|
|
384
|
-
setError(message);
|
|
385
|
-
setLines((current) => [...current, { role: 'error', text: message }]);
|
|
386
|
-
}
|
|
387
|
-
finally {
|
|
388
|
-
setBusy(false);
|
|
389
|
-
setCurrentTool(undefined);
|
|
390
|
-
setToolCards((current) => current.map((card) => card.status === 'running' ? { ...card, status: 'error' } : card));
|
|
391
|
-
runStartedAt.current = null;
|
|
392
|
-
setLiveThink('');
|
|
393
|
-
setLiveAnswer('');
|
|
394
|
-
}
|
|
395
|
-
})();
|
|
637
|
+
startRun(prompt, attachSpec);
|
|
396
638
|
}
|
|
397
639
|
return;
|
|
398
640
|
case 'escape':
|
|
@@ -400,6 +642,10 @@ export function TerminalInterface({ engine }) {
|
|
|
400
642
|
approve(false);
|
|
401
643
|
return;
|
|
402
644
|
}
|
|
645
|
+
if (sessionsOpen) {
|
|
646
|
+
setSessionsOpen(false);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
403
649
|
if (pickerOpen) {
|
|
404
650
|
setPickerOpen(false);
|
|
405
651
|
return;
|
|
@@ -424,6 +670,10 @@ export function TerminalInterface({ engine }) {
|
|
|
424
670
|
cycleMode();
|
|
425
671
|
return;
|
|
426
672
|
case 'up':
|
|
673
|
+
if (sessionsOpen) {
|
|
674
|
+
setSessionsIndex((current) => clamp(current - 1, 0, Math.max(0, sessionsList.length - 1)));
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
427
677
|
if (pickerOpen) {
|
|
428
678
|
setPickerIndex((current) => clamp(current - 1, 0, Math.max(0, pickerItems.length - 1)));
|
|
429
679
|
return;
|
|
@@ -435,6 +685,10 @@ export function TerminalInterface({ engine }) {
|
|
|
435
685
|
setEdit((current) => ({ ...current, cursor: moveVerticalWrapped(current.value, current.cursor, -1, inputWidth) }));
|
|
436
686
|
return;
|
|
437
687
|
case 'down':
|
|
688
|
+
if (sessionsOpen) {
|
|
689
|
+
setSessionsIndex((current) => clamp(current + 1, 0, Math.max(0, sessionsList.length - 1)));
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
438
692
|
if (pickerOpen) {
|
|
439
693
|
setPickerIndex((current) => clamp(current + 1, 0, Math.max(0, pickerItems.length - 1)));
|
|
440
694
|
return;
|
|
@@ -474,10 +728,23 @@ export function TerminalInterface({ engine }) {
|
|
|
474
728
|
// Legacy keys Ink parses correctly (\r, \n, \x08, ESC[A arrows, ctrl+letters) plus text and paste.
|
|
475
729
|
useInput((value, key) => {
|
|
476
730
|
if (approval) {
|
|
477
|
-
if (value === 'y' || value === 'Y' || key.return)
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
731
|
+
if (value === 'y' || value === 'Y' || key.return) {
|
|
732
|
+
approve(true);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (value === 'n' || value === 'N' || key.escape) {
|
|
736
|
+
approve(false);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (value === 't' || value === 'T') {
|
|
740
|
+
approve(true, approval.scopes[0]?.id);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
const digit = Number(value);
|
|
744
|
+
if (Number.isInteger(digit) && digit >= 1 && digit <= approval.scopes.length) {
|
|
745
|
+
approve(true, approval.scopes[digit - 1]?.id);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
481
748
|
return;
|
|
482
749
|
}
|
|
483
750
|
if (key.ctrl && value === 'c') {
|
|
@@ -515,6 +782,25 @@ export function TerminalInterface({ engine }) {
|
|
|
515
782
|
}
|
|
516
783
|
return;
|
|
517
784
|
}
|
|
785
|
+
if (sessionsOpen) {
|
|
786
|
+
if (key.escape) {
|
|
787
|
+
applyAction({ kind: 'escape' });
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (key.upArrow) {
|
|
791
|
+
applyAction({ kind: 'up' });
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (key.downArrow) {
|
|
795
|
+
applyAction({ kind: 'down' });
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
if (key.return) {
|
|
799
|
+
applyAction({ kind: 'submit' });
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
518
804
|
if (key.pageUp || (key.ctrl && value.toLowerCase() === 'u')) {
|
|
519
805
|
scrollTranscript(pageSizeRef.current);
|
|
520
806
|
return;
|
|
@@ -523,6 +809,14 @@ export function TerminalInterface({ engine }) {
|
|
|
523
809
|
scrollTranscript(-pageSizeRef.current);
|
|
524
810
|
return;
|
|
525
811
|
}
|
|
812
|
+
if (key.ctrl && value.toLowerCase() === 'l') {
|
|
813
|
+
setLayoutDebug((current) => !current);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (key.ctrl && value.toLowerCase() === 'y') {
|
|
817
|
+
yankLastBlock();
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
526
820
|
// Ctrl+T toggles the most recent tool card between collapsed and expanded.
|
|
527
821
|
if (key.ctrl && value.toLowerCase() === 't') {
|
|
528
822
|
const latest = toolCards[toolCards.length - 1];
|
|
@@ -609,8 +903,10 @@ export function TerminalInterface({ engine }) {
|
|
|
609
903
|
const elapsed = busy
|
|
610
904
|
? (elapsedMs >= 60_000 ? `${Math.floor(elapsedMs / 60_000)}m${String(Math.floor((elapsedMs % 60_000) / 1000)).padStart(2, '0')}s` : `${Math.floor(elapsedMs / 1000)}s`)
|
|
611
905
|
: '';
|
|
906
|
+
const meter = contextMeter(metrics.compression.inputTokens, engine.state.activeModel, metrics.fallback.activeProvider);
|
|
907
|
+
const meterColor = meter.zone === 'danger' ? PALETTE.error : meter.zone === 'warn' ? PALETTE.warn : PALETTE.muted;
|
|
612
908
|
const contextLabel = metrics.compression.inputTokens > 0
|
|
613
|
-
?
|
|
909
|
+
? `ctx ${meterBar(meter.fraction, 8)} ${Math.round(meter.fraction * 100)}%`
|
|
614
910
|
: '';
|
|
615
911
|
const contentWidth = Math.max(20, width - 8);
|
|
616
912
|
const terminalRows = stdout.rows ?? 24;
|
|
@@ -643,22 +939,37 @@ export function TerminalInterface({ engine }) {
|
|
|
643
939
|
lines.forEach((line, lineIndex) => {
|
|
644
940
|
let rendered = cache.rows.get(line);
|
|
645
941
|
if (rendered === undefined) {
|
|
646
|
-
|
|
647
|
-
const markdown = line.role !== 'tool' && line.role !== 'error';
|
|
648
|
-
const wrapped = markdown
|
|
649
|
-
? renderMarkdown(line.text, contentWidth)
|
|
650
|
-
: wrap(line.text, contentWidth).map((text) => [{ text }]);
|
|
651
|
-
const next = [];
|
|
652
|
-
wrapped.forEach((segments, rowIndex) => next.push({ key: `message-${lineIndex}-${rowIndex}`, role: line.role, segments, label, first: rowIndex === 0 }));
|
|
653
|
-
if (line.saved)
|
|
654
|
-
next.push({ key: `message-${lineIndex}-saved`, role: 'assistant', segments: [{ text: line.saved }], label: '', first: false, saved: line.saved });
|
|
655
|
-
rendered = next;
|
|
942
|
+
rendered = renderLineToRows(line, lineIndex, contentWidth, fallback);
|
|
656
943
|
cache.rows.set(line, rendered);
|
|
657
944
|
}
|
|
658
945
|
out.push(...rendered);
|
|
659
946
|
});
|
|
660
947
|
return out;
|
|
661
948
|
}, [lines, contentWidth, engine.state.activeModel]);
|
|
949
|
+
// Fold tool-role rows into collapsible groups. Expansion is global (Ctrl+G);
|
|
950
|
+
// folded groups render as a single summary line instead of a wall of output.
|
|
951
|
+
const displayRows = useMemo(() => {
|
|
952
|
+
const lastUserIdx = lines.map((line) => line.role).lastIndexOf('user');
|
|
953
|
+
const folded = foldToolGroups(lines.map((line) => ({ role: line.role, text: line.text, toolName: line.toolName })), groupsExpanded, lastUserIdx >= 0 ? lastUserIdx : lines.length);
|
|
954
|
+
const out = [];
|
|
955
|
+
const fallback = engine.state.activeModel;
|
|
956
|
+
folded.forEach((gline, lineIndex) => {
|
|
957
|
+
const isGroup = 'group' in gline && gline.group !== undefined;
|
|
958
|
+
const key = isGroup ? `group-${lineIndex}` : `message-${lineIndex}`;
|
|
959
|
+
if (isGroup) {
|
|
960
|
+
out.push({
|
|
961
|
+
key, role: 'tool',
|
|
962
|
+
segments: [{ text: gline.text, dim: true }],
|
|
963
|
+
label: labelFor('tool', undefined, 'group'), first: true,
|
|
964
|
+
});
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const line = gline;
|
|
968
|
+
const rendered = renderLineToRows(line, lineIndex, contentWidth, fallback);
|
|
969
|
+
out.push(...rendered);
|
|
970
|
+
});
|
|
971
|
+
return out;
|
|
972
|
+
}, [lines, contentWidth, engine.state.activeModel, groupsExpanded]);
|
|
662
973
|
const planRows = taskQueue.length > 0 ? 6 + Math.min(6, taskQueue.length) : 0;
|
|
663
974
|
const pickerGroups = new Set(pickerItems.map((item) => item.group)).size;
|
|
664
975
|
const pickerRows = pickerOpen ? 6 + pickerItems.length + pickerGroups + (pickerError || pickerItems.length === 0 ? 1 : 0) : 0;
|
|
@@ -674,6 +985,8 @@ export function TerminalInterface({ engine }) {
|
|
|
674
985
|
const visibleLiveRows = liveHeight > 0 ? liveRows.slice(-liveHeight) : [];
|
|
675
986
|
const maxScroll = Math.max(0, allRows.length - storedHeight);
|
|
676
987
|
maxScrollRef.current = maxScroll;
|
|
988
|
+
allRowsRef.current = allRows;
|
|
989
|
+
storedHeightRef.current = storedHeight;
|
|
677
990
|
pageSizeRef.current = Math.max(1, storedHeight - 2);
|
|
678
991
|
const boundedScroll = clamp(scrollOffset, 0, maxScroll);
|
|
679
992
|
const endRow = allRows.length - boundedScroll;
|
|
@@ -712,6 +1025,17 @@ export function TerminalInterface({ engine }) {
|
|
|
712
1025
|
const id = setInterval(() => setNow(Date.now()), 250);
|
|
713
1026
|
return () => clearInterval(id);
|
|
714
1027
|
}, [busy]);
|
|
1028
|
+
// Drain a prompt that was queued while the previous run was in flight.
|
|
1029
|
+
useEffect(() => {
|
|
1030
|
+
if (busy)
|
|
1031
|
+
return;
|
|
1032
|
+
const pending = queuedRef.current;
|
|
1033
|
+
if (!pending)
|
|
1034
|
+
return;
|
|
1035
|
+
queuedRef.current = null;
|
|
1036
|
+
setQueued(undefined);
|
|
1037
|
+
startRun(pending.prompt, pending.attachSpec);
|
|
1038
|
+
}, [busy]);
|
|
715
1039
|
return _jsxs(Box, { flexDirection: "column", width: width, height: terminalRows, paddingX: 2, overflow: "hidden", children: [_jsxs(Box, { justifyContent: "space-between", paddingY: 1, children: [_jsxs(Text, { bold: true, color: "cyan", children: ["OMNIHARNESS ", _jsxs(Text, { dimColor: true, children: ["\u00B7 ", mode, " mode"] })] }), _jsx(Text, { dimColor: true, children: "OMNIROUTE :20128" })] }), _jsxs(Box, { flexDirection: "column", height: messageHeight, overflow: "hidden", children: [lines.length === 0 && _jsxs(Box, { flexDirection: "column", marginTop: 2, children: [_jsx(Text, { color: "cyan", bold: true, children: "Ready when you are." }), _jsxs(Text, { dimColor: true, children: ["Describe the work. OmniHarness routes it through your OmniRoute account. Ctrl+", modeKey, " cycles plan \u00B7 build \u00B7 research \u00B7 crazy."] })] }), visibleRows.map((row, index) => row.saved
|
|
716
1040
|
? _jsx(Text, { dimColor: true, children: row.saved }, row.key)
|
|
717
1041
|
: row.first
|
|
@@ -721,27 +1045,22 @@ export function TerminalInterface({ engine }) {
|
|
|
721
1045
|
: _jsx(SegmentText, { segments: row.segments, role: row.role }, row.key)), toolCards.slice(-6).map((card) => {
|
|
722
1046
|
const expanded = expandedTool === card.id;
|
|
723
1047
|
const status = card.status === 'running' ? _jsx(Text, { color: PALETTE.warn, children: "\u2022 running" }) : card.status === 'error' ? _jsx(Text, { color: PALETTE.error, children: "\u2715 error" }) : _jsx(Text, { color: PALETTE.success, children: "\u2713 done" });
|
|
724
|
-
const
|
|
1048
|
+
const head = card.name === 'run_command'
|
|
1049
|
+
? `$ ${clip(card.target || '…', Math.max(10, contentWidth - 26))}`
|
|
1050
|
+
: `${toolVerb(card.name)}${card.target ? ` · ${clip(card.target, Math.max(10, contentWidth - 30))}` : ''}`;
|
|
725
1051
|
const badge = expanded ? '▾' : '▸';
|
|
726
|
-
return _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: [badge, " ",
|
|
727
|
-
|
|
728
|
-
? diffSegments(card.trail, contentWidth).slice(0, 12).map((segments, index) => _jsx(SegmentText, { segments: segments, role: "tool" }, index))
|
|
729
|
-
// A file-edit trail is the new content; show it as added (green) lines.
|
|
730
|
-
: card.trail.split('\n').slice(0, 12).map((line, index) => _jsx(Text, { color: PALETTE.success, children: line }, index))
|
|
731
|
-
: card.summary
|
|
732
|
-
? _jsx(Text, { dimColor: true, children: clip(card.summary, contentWidth) })
|
|
733
|
-
: _jsx(Text, { dimColor: true, children: "(no output)" }))] }, card.id);
|
|
734
|
-
}), engine.state.preview && _jsxs(Text, { color: "green", dimColor: true, children: ["preview live \u00B7 ", engine.state.preview.url] }), busy && _jsxs(Text, { color: "cyan", children: [_jsx(Spinner, { type: "dots" }), " working in ", mode, " mode on ", engine.state.activeModel, currentTool ? ` · now ${currentTool}` : ''] })] }), taskQueue.length > 0 && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, marginTop: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: "cyan", children: "plan" }), _jsxs(Text, { dimColor: true, children: [taskQueue.filter((item) => item.status === 'done').length, "/", taskQueue.length, " done"] })] }), taskQueue.slice(-6).map((item) => {
|
|
1052
|
+
return _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: [badge, " ", head, " \u00B7 ", status, expanded ? ' · Ctrl+T to collapse' : ''] }), expanded && renderToolBody(card, contentWidth, PALETTE)] }, card.id);
|
|
1053
|
+
}), engine.state.preview && _jsxs(Text, { color: "green", dimColor: true, children: ["preview live \u00B7 ", engine.state.preview.url] }), busy && _jsxs(Text, { color: "cyan", children: [_jsx(Spinner, { type: "dots" }), " working in ", mode, " mode on ", engine.state.activeModel, currentTool ? ` · now ${currentTool}` : ''] }), queued && _jsxs(Text, { color: PALETTE.warn, children: ["\u23CE queued \u00B7 ", clip(queued, Math.max(12, contentWidth - 12))] })] }), taskQueue.length > 0 && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, marginTop: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: "cyan", children: "plan" }), _jsxs(Text, { dimColor: true, children: [taskQueue.filter((item) => item.status === 'done').length, "/", taskQueue.length, " done"] })] }), taskQueue.slice(-6).map((item) => {
|
|
735
1054
|
const marker = item.status === 'done' ? '✓' : item.status === 'active' ? '▸' : '○';
|
|
736
1055
|
const color = item.status === 'done' ? 'green' : item.status === 'active' ? 'cyan' : undefined;
|
|
737
1056
|
return _jsxs(Text, { color: color, dimColor: item.status === 'done', children: [marker, " ", clip(item.title, contentWidth - 4)] }, item.id);
|
|
738
|
-
})] }), pickerOpen && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "magenta", children: "Choose an OmniRoute model" }), _jsx(Text, { dimColor: true, children: "\u2191\u2193 / j k navigate \u00B7 enter select \u00B7 esc close" }), pickerError && _jsx(Text, { color: "red", children: clip(pickerError, contentWidth) }), pickerItems.length === 0 && !pickerError && _jsx(Text, { dimColor: true, children: "No models returned by OmniRoute." }), pickerItems.map((item, index) => {
|
|
1057
|
+
})] }), sessionsOpen && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "blue", paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "blue", children: "Saved sessions" }), _jsx(Text, { dimColor: true, children: "\u2191\u2193 navigate \u00B7 enter resume \u00B7 esc close" }), sessionsList.map((session, index) => (_jsxs(Text, { color: index === sessionsIndex ? 'blue' : undefined, children: [index === sessionsIndex ? '› ' : ' ', session.name, session.savedAt ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", session.savedAt] }) : null] }, session.name)))] }), pickerOpen && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "magenta", children: "Choose an OmniRoute model" }), _jsx(Text, { dimColor: true, children: "\u2191\u2193 / j k navigate \u00B7 enter select \u00B7 esc close" }), pickerError && _jsx(Text, { color: "red", children: clip(pickerError, contentWidth) }), pickerItems.length === 0 && !pickerError && _jsx(Text, { dimColor: true, children: "No models returned by OmniRoute." }), pickerItems.map((item, index) => {
|
|
739
1058
|
const header = index === 0 || pickerItems[index - 1].group !== item.group
|
|
740
1059
|
? _jsx(Text, { dimColor: true, bold: true, children: item.group === 'combos' ? 'your combos' : 'auto engine' }, `h-${item.group}`)
|
|
741
1060
|
: null;
|
|
742
1061
|
return _jsxs(Box, { flexDirection: "column", children: [header, _jsxs(Text, { color: index === pickerIndex ? 'cyan' : undefined, children: [index === pickerIndex ? '› ' : ' ', item.id, item.strategy ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", item.strategy] }) : null, item.id === engine.state.activeModel ? ' ✓' : ''] })] }, item.id);
|
|
743
|
-
})] }), approval && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 2, paddingY: 1, marginTop: 1, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Approve ", approval.tool, "?"] }), _jsxs(Text, { dimColor: true, children: ["args: ", clip(
|
|
1062
|
+
})] }), approval && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 2, paddingY: 1, marginTop: 1, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Approve ", approval.tool, "?"] }), _jsxs(Text, { dimColor: true, children: ["args: ", clip(JSON.stringify(approval.input), contentWidth)] }), approval.scopes.map((scope, index) => _jsxs(Text, { dimColor: true, children: [" ", index + 1, " \u00B7 ", clip(scope.label, Math.max(12, contentWidth - 6))] }, scope.id)), _jsxs(Text, { dimColor: true, children: ["y allow once \u00B7 n deny \u00B7 t always allow \u00B7 1\u2013", approval.scopes.length, " pick a trust scope"] })] }), layoutDebug && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, children: [_jsxs(Text, { bold: true, dimColor: true, children: ["layout \u00B7 ", width, "\u00D7", terminalRows, " \u00B7 Ctrl+L to hide"] }), _jsxs(Text, { dimColor: true, children: ["chrome ", chromeRows, " \u00B7 input ", inputRows, " \u00B7 plan ", planRows, " \u00B7 overlay ", pickerRows + approvalRows, " \u00B7 caps ", kitty !== null ? 1 : 0] }), _jsxs(Text, { dimColor: true, children: ["message ", messageHeight, " = stored ", storedHeight, " + live ", liveHeight, " + status ", statusRows] }), _jsxs(Text, { dimColor: true, children: ["transcript rows ", allRows.length, " \u00B7 scroll ", boundedScroll, "/", maxScroll, " \u00B7 page ", pageSizeRef.current] })] }), _jsx(Box, { borderStyle: "round", borderColor: error ? 'red' : 'cyan', paddingX: 1, marginTop: 1, flexDirection: "column", children: edit.value === ''
|
|
744
1063
|
? _jsxs(Text, { color: "cyan", children: ["\u203A ", _jsx(Text, { dimColor: true, children: "type a task and press enter" })] })
|
|
745
|
-
: editorLayout.lines.map((text, index) => _jsxs(Text, { color: "cyan", children: [index === 0 ? '› ' : ' ', text] }, index)) }), kitty !== null && _jsx(Text, { dimColor: true, children: kitty ? 'kitty protocol active — Shift+Enter makes a new line' : 'this terminal can\'t distinguish Shift+Enter from Enter — use Ctrl+J for a new line' }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: busy ? PALETTE.info : PALETTE.muted, children: [busy && _jsx(Spinner, { type: "dots" }), phase, elapsed ? ` · ${elapsed}` : ''] }),
|
|
1064
|
+
: editorLayout.lines.map((text, index) => _jsxs(Text, { color: "cyan", children: [index === 0 ? '› ' : ' ', text] }, index)) }), kitty !== null && _jsx(Text, { dimColor: true, children: kitty ? 'kitty protocol active — Shift+Enter makes a new line' : 'this terminal can\'t distinguish Shift+Enter from Enter — use Ctrl+J for a new line' }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { color: busy ? PALETTE.info : PALETTE.muted, children: [busy && _jsx(Spinner, { type: "dots" }), phase, elapsed ? ` · ${elapsed}` : ''] }), _jsxs(Text, { color: PALETTE.muted, children: [engine.state.activeModel, " \u00B7 ", mode, contextLabel ? _jsxs(Text, { color: meterColor, children: [" \u00B7 ", contextLabel] }) : null, " \u00B7 ", scrollStatus] })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { dimColor: true, children: ["Enter send \u00B7 Ctrl+J new line \u00B7 Ctrl+O models \u00B7 Ctrl+", modeKey, " mode \u00B7 Ctrl+T tool \u00B7 Ctrl+Y copy \u00B7 Ctrl+L layout \u00B7 Ctrl+C cancel/quit \u00B7 /help"] }), hud.length > 0 && _jsx(Text, { dimColor: true, children: hud.join(' · ') })] })] })] });
|
|
746
1065
|
}
|
|
747
1066
|
//# sourceMappingURL=terminalInterface.js.map
|