omniharness-cli 0.1.80 → 0.1.82
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/cli.js +18 -32
- package/dist/ui/sidebar.js +105 -0
- package/dist/ui/terminalInterface.js +81 -27
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,23 +7,6 @@ import { ownVersion, runUpdate } from './update.js';
|
|
|
7
7
|
import { readActiveCombo } from './config/settings.js';
|
|
8
8
|
import { OmniRouteClient } from './config/omniRoute.js';
|
|
9
9
|
import { doctor, helpText, models } from './doctor.js';
|
|
10
|
-
const ALT_ENTER = '\x1b[?1049h\x1b[H';
|
|
11
|
-
const ALT_LEAVE = '\x1b[?1049l';
|
|
12
|
-
/** Plain-text transcript flushed to the primary buffer on exit — the audit trail. */
|
|
13
|
-
function dumpTranscript(messages) {
|
|
14
|
-
if (messages.length === 0)
|
|
15
|
-
return;
|
|
16
|
-
const label = {
|
|
17
|
-
user: 'you', assistant: 'assistant', thought: 'think', action: 'action',
|
|
18
|
-
tool: 'tool', command: 'command', error: 'error',
|
|
19
|
-
};
|
|
20
|
-
process.stdout.write('\n');
|
|
21
|
-
for (const message of messages) {
|
|
22
|
-
const who = message.model ?? label[message.role] ?? message.role;
|
|
23
|
-
process.stdout.write(`\n─ ${who} ─────\n${message.content.trim()}\n`);
|
|
24
|
-
}
|
|
25
|
-
process.stdout.write('\n');
|
|
26
|
-
}
|
|
27
10
|
// A crash anywhere below would otherwise surface as a raw Node stack trace, or
|
|
28
11
|
// as an unhandled rejection that terminates the process without saying why.
|
|
29
12
|
// A CLI should fail with a sentence.
|
|
@@ -81,23 +64,26 @@ else {
|
|
|
81
64
|
void (async () => {
|
|
82
65
|
const saved = await readActiveCombo();
|
|
83
66
|
const engine = await createMastraEngine({ workspaceRoot: process.cwd(), model: saved });
|
|
84
|
-
// Render the
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
67
|
+
// Render in the primary buffer, not the alternate screen.
|
|
68
|
+
//
|
|
69
|
+
// The alternate screen has no scrollback — that is what it is for — so
|
|
70
|
+
// while the harness was running you could not scroll back to anything: not
|
|
71
|
+
// a file the agent read three steps ago, not the reasoning behind an edit.
|
|
72
|
+
// The transcript only appeared once you quit, dumped into the primary
|
|
73
|
+
// buffer on exit, which is the wrong time to want it.
|
|
74
|
+
//
|
|
75
|
+
// Staying in the primary buffer means the TUI's <Static> region writes
|
|
76
|
+
// settled turns straight into real scrollback, where the terminal scrolls
|
|
77
|
+
// them like any other output and they are still there afterwards. The exit
|
|
78
|
+
// dump goes with it: scrollback already holds the transcript, and printing
|
|
79
|
+
// it again would duplicate every line.
|
|
80
|
+
//
|
|
81
|
+
// The cost is that quitting no longer restores the pre-launch screen. For
|
|
82
|
+
// a tool whose output you are meant to read back, that is the right trade.
|
|
83
|
+
//
|
|
90
84
|
// The app owns Ctrl+C so idle quits but an in-flight run is cancelled first.
|
|
91
85
|
const { waitUntilExit } = render(_jsx(TerminalInterface, { engine: engine }), { exitOnCtrlC: false });
|
|
92
|
-
|
|
93
|
-
await waitUntilExit();
|
|
94
|
-
}
|
|
95
|
-
finally {
|
|
96
|
-
if (alt) {
|
|
97
|
-
process.stdout.write(ALT_LEAVE);
|
|
98
|
-
dumpTranscript(engine.state.messages);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
86
|
+
await waitUntilExit();
|
|
101
87
|
})();
|
|
102
88
|
}
|
|
103
89
|
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data shaping for the sidebar. The layout lives in the Ink component; every
|
|
3
|
+
* decision it has to make is here so it can be tested without a terminal.
|
|
4
|
+
*
|
|
5
|
+
* The shape follows OpenCode's session sidebar (MIT, sst/opencode): a fixed
|
|
6
|
+
* narrow panel carrying what the conversation column should not have to —
|
|
7
|
+
* session identity, what is running, and what is queued. The implementation is
|
|
8
|
+
* ours; Ink has no scrollbox and no absolute positioning, so the behaviour
|
|
9
|
+
* differs where it has to.
|
|
10
|
+
*/
|
|
11
|
+
/** Fixed width of the panel, matching the measure OpenCode settled on. */
|
|
12
|
+
export const SIDEBAR_WIDTH = 34;
|
|
13
|
+
/**
|
|
14
|
+
* Below this the terminal cannot hold a sidebar and a readable conversation
|
|
15
|
+
* at once, so the toggle shows the sidebar *instead of* the conversation
|
|
16
|
+
* rather than beside it. Ink cannot overlay one panel on another, which is
|
|
17
|
+
* what OpenCode does at this size.
|
|
18
|
+
*/
|
|
19
|
+
export const SIDEBAR_MIN_SPLIT = 92;
|
|
20
|
+
/**
|
|
21
|
+
* How the sidebar should be shown, given the terminal width and whether the
|
|
22
|
+
* user has it toggled on.
|
|
23
|
+
*/
|
|
24
|
+
export function sidebarMode(width, wanted) {
|
|
25
|
+
if (!wanted)
|
|
26
|
+
return 'hidden';
|
|
27
|
+
return width >= SIDEBAR_MIN_SPLIT ? 'split' : 'replace';
|
|
28
|
+
}
|
|
29
|
+
/** Width left for the conversation once the sidebar has taken its share. */
|
|
30
|
+
export function conversationWidth(width, mode) {
|
|
31
|
+
if (mode !== 'split')
|
|
32
|
+
return width;
|
|
33
|
+
return Math.max(20, width - SIDEBAR_WIDTH - 1);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Render a token count the way it is read: exact while small, then thousands.
|
|
37
|
+
* A six-digit number in a 34-column panel is noise, not information.
|
|
38
|
+
*/
|
|
39
|
+
export function compactTokens(n) {
|
|
40
|
+
if (!Number.isFinite(n) || n < 0)
|
|
41
|
+
return '0';
|
|
42
|
+
if (n < 1000)
|
|
43
|
+
return String(Math.round(n));
|
|
44
|
+
if (n < 1_000_000) {
|
|
45
|
+
const k = n / 1000;
|
|
46
|
+
return `${k < 10 ? k.toFixed(1) : Math.round(k)}k`;
|
|
47
|
+
}
|
|
48
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Cost to four places under a cent, two above. Sub-cent spend is the normal
|
|
52
|
+
* case for a single turn and "$0.00" would report every one of them as free.
|
|
53
|
+
*/
|
|
54
|
+
export function formatCost(usd) {
|
|
55
|
+
if (!Number.isFinite(usd) || usd <= 0)
|
|
56
|
+
return '$0';
|
|
57
|
+
return usd < 0.01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The usage rows, or none at all when nothing has been spent yet. An empty
|
|
61
|
+
* panel section reads as broken; a missing one reads as "not yet".
|
|
62
|
+
*/
|
|
63
|
+
export function usageRows(usage) {
|
|
64
|
+
const rows = [];
|
|
65
|
+
const inTokens = usage.tokensIn ?? 0;
|
|
66
|
+
const outTokens = usage.tokensOut ?? 0;
|
|
67
|
+
if (inTokens > 0 || outTokens > 0) {
|
|
68
|
+
// Only name a direction that was measured: "1.2k in · 0 out" claims zero
|
|
69
|
+
// output rather than admitting it is not counted here.
|
|
70
|
+
const parts = [];
|
|
71
|
+
if (inTokens > 0)
|
|
72
|
+
parts.push(`${compactTokens(inTokens)} in`);
|
|
73
|
+
if (outTokens > 0)
|
|
74
|
+
parts.push(`${compactTokens(outTokens)} out`);
|
|
75
|
+
rows.push({ label: 'tokens', value: parts.join(' · ') });
|
|
76
|
+
}
|
|
77
|
+
if ((usage.costUSD ?? 0) > 0)
|
|
78
|
+
rows.push({ label: 'cost', value: formatCost(usage.costUSD ?? 0) });
|
|
79
|
+
if ((usage.requests ?? 0) > 0)
|
|
80
|
+
rows.push({ label: 'calls', value: String(usage.requests) });
|
|
81
|
+
return rows;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Task queue rows. Plain-word markers rather than glyphs, matching the rest of
|
|
85
|
+
* the interface. The active item is marked so the eye finds it without colour,
|
|
86
|
+
* which matters under NO_COLOR.
|
|
87
|
+
*/
|
|
88
|
+
export function todoRows(todos, limit, width) {
|
|
89
|
+
return todos.slice(0, Math.max(0, limit)).map((todo) => ({
|
|
90
|
+
marker: todo.status === 'done' ? 'x' : todo.status === 'active' ? '>' : '-',
|
|
91
|
+
title: clip(todo.title, Math.max(4, width)),
|
|
92
|
+
active: todo.status === 'active',
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
/** How many queued items were not shown, for a "+N more" line. */
|
|
96
|
+
export function overflowCount(total, shown) {
|
|
97
|
+
return Math.max(0, total - shown);
|
|
98
|
+
}
|
|
99
|
+
export function clip(text, width) {
|
|
100
|
+
const runes = [...text.replace(/\s+/g, ' ')];
|
|
101
|
+
if (runes.length <= width)
|
|
102
|
+
return runes.join('');
|
|
103
|
+
return `${runes.slice(0, Math.max(0, width - 1)).join('')}…`;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=sidebar.js.map
|
|
@@ -9,6 +9,7 @@ import { renderMarkdown } from './markdown.js';
|
|
|
9
9
|
import { looksLikeDiff, diffSegments } from './diff.js';
|
|
10
10
|
import { palette } from './palette.js';
|
|
11
11
|
import { capabilityLine, recentRows, shortenPath, twoColumn } from './home.js';
|
|
12
|
+
import { conversationWidth, overflowCount, sidebarMode, SIDEBAR_WIDTH, todoRows, usageRows, clip as clipRow } from './sidebar.js';
|
|
12
13
|
import { contextMeter, meterBar } from './modelWindows.js';
|
|
13
14
|
import { BEL, SYNC_QUERY, isSyncOutputReply, osc9Notify, osc52Copy, shouldNudgeOnFinish, wrapSynchronizedOutput } from './termcaps.js';
|
|
14
15
|
import { KITTY_POP, KITTY_PUSH, KITTY_QUERY, isEncodedKey, isKittyQueryResponse, parseRawKey } from './keys.js';
|
|
@@ -80,6 +81,12 @@ const MODE_SEQ = ['plan', 'build', 'research', 'crazy'];
|
|
|
80
81
|
const PERM_SEQ = ['ask', 'acceptEdits', 'bypass'];
|
|
81
82
|
const PERM_LABEL = { ask: 'manual', acceptEdits: 'accept edits', bypass: 'bypass' };
|
|
82
83
|
const PERM_COLOR = (p) => (p === 'bypass' ? PALETTE.error : p === 'acceptEdits' ? PALETTE.warn : PALETTE.muted);
|
|
84
|
+
/**
|
|
85
|
+
* The widest the content is allowed to get, however wide the terminal is.
|
|
86
|
+
* Past roughly this the eye loses the start of a line on the way back from
|
|
87
|
+
* the end of it, and the chrome ends up further from what it describes.
|
|
88
|
+
*/
|
|
89
|
+
const MAX_MEASURE = 100;
|
|
83
90
|
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
84
91
|
const widthOf = (stdout) => Math.max(48, stdout.columns ?? 80);
|
|
85
92
|
const clip = (text, width) => text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}…`;
|
|
@@ -174,6 +181,30 @@ function TranscriptEntry({ line, width, fallbackModel }) {
|
|
|
174
181
|
const bullet = line.role === 'user' ? '>' : line.role === 'error' ? '!' : '-';
|
|
175
182
|
return _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { bold: true, color: colorFor(line.role), children: [bullet, " ", label] }), rows.map((segments, index) => _jsx(SegmentText, { segments: segments, role: line.role }, index)), line.saved ? _jsxs(Text, { dimColor: true, children: [" ", line.saved] }) : null] });
|
|
176
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* The session panel, shown beside the live region on Ctrl+B.
|
|
186
|
+
*
|
|
187
|
+
* It carries what the conversation column should not have to: what is
|
|
188
|
+
* running, what is queued, and what the turn has cost. The shape follows
|
|
189
|
+
* OpenCode's session sidebar (MIT); the implementation is ours, because Ink
|
|
190
|
+
* has neither a scrollbox nor absolute positioning, so it cannot scroll
|
|
191
|
+
* independently or overlay the conversation.
|
|
192
|
+
*
|
|
193
|
+
* Sections are absent rather than empty. A heading with nothing under it
|
|
194
|
+
* reads as something failing to load.
|
|
195
|
+
*/
|
|
196
|
+
function SidebarPanel(props) {
|
|
197
|
+
const { width, model, mode, perm, workspace, usage, agents, todos, skills, plugins } = props;
|
|
198
|
+
const inner = Math.max(10, width - 4);
|
|
199
|
+
// Two columns of slack, not one. At an exact fit Ink still wraps, and a
|
|
200
|
+
// wrapped value in a 34-column panel shows as a stray blank line under a
|
|
201
|
+
// bare label — measured, not reasoned about.
|
|
202
|
+
const value = Math.max(6, inner - LABEL_WIDTH - 2);
|
|
203
|
+
const usageLines = usageRows(usage);
|
|
204
|
+
const shownTodos = todoRows(todos, 6, inner - 2);
|
|
205
|
+
const moreTodos = overflowCount(todos.length, shownTodos.length);
|
|
206
|
+
return _jsxs(Box, { flexDirection: "column", width: width, borderStyle: "round", borderColor: PALETTE.muted, paddingX: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "session" }), _jsx(Field, { label: "model", children: clip(model, value) }), _jsx(Field, { label: "mode", children: _jsx(Text, { color: MODE_ACCENT[mode], children: mode }) }), _jsx(Field, { label: "perms", children: _jsx(Text, { color: mode === 'crazy' ? PALETTE.error : PERM_COLOR(perm), children: mode === 'crazy' ? 'bypass' : PERM_LABEL[perm] }) }), _jsx(Field, { label: "cwd", children: shortenPath(workspace, value) }), skills > 0 && _jsx(Field, { label: "skills", children: clip(plugins > 0 ? `${skills} · ${plugins} plugins` : String(skills), value) }), usageLines.length > 0 && _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "usage" }), usageLines.map((row) => _jsx(Field, { label: row.label, children: clip(row.value, value) }, row.label))] }), agents.length > 0 && _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "agents" }), agents.slice(-6).map((lane, index) => _jsxs(Text, { children: [_jsx(Text, { color: AGENT_COLORS[index % AGENT_COLORS.length], children: lane.status === 'error' ? 'FAIL' : lane.status === 'done' ? 'ok ' : '.. ' }), _jsx(Text, { dimColor: true, children: clipRow(lane.label, inner - 5) })] }, lane.id))] }), shownTodos.length > 0 && _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: "plan" }), shownTodos.map((row) => _jsxs(Text, { children: [_jsxs(Text, { color: row.active ? PALETTE.accent : PALETTE.muted, children: [row.marker, " "] }), _jsx(Text, { dimColor: !row.active, children: row.title })] }, row.title)), moreTodos > 0 && _jsxs(Text, { dimColor: true, children: ["+", moreTodos, " more"] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Ctrl+B hides this" }) })] });
|
|
207
|
+
}
|
|
177
208
|
/**
|
|
178
209
|
* Width of the dim label column on the home screen. The value gets whatever is
|
|
179
210
|
* left, and every caller has to subtract this: a value sized to the whole box
|
|
@@ -197,7 +228,7 @@ function Field({ label, children }) {
|
|
|
197
228
|
export function Hero(props) {
|
|
198
229
|
const { width, endpoint, model, mode, perm, workspace, sessions, skills, plugins, mcpTools } = props;
|
|
199
230
|
const wide = twoColumn(width);
|
|
200
|
-
const outer =
|
|
231
|
+
const outer = width;
|
|
201
232
|
const column = wide ? Math.floor((outer - 3) / 2) : outer;
|
|
202
233
|
const inner = Math.max(12, column - 4);
|
|
203
234
|
// Values sit to the right of the label, so that is the room they actually get.
|
|
@@ -214,7 +245,7 @@ export function TerminalInterface({ engine }) {
|
|
|
214
245
|
const { stdin } = useStdin();
|
|
215
246
|
const [width, setWidth] = useState(() => widthOf(stdout));
|
|
216
247
|
const [edit, setEdit] = useState({ value: '', cursor: 0 });
|
|
217
|
-
const inputWidth = Math.max(16, width - 12);
|
|
248
|
+
const inputWidth = Math.max(16, Math.min(width, MAX_MEASURE) - 12); // recomputed below once the split is known
|
|
218
249
|
// Settled transcript. Rendered once each into <Static> — the terminal's own
|
|
219
250
|
// scrollback is the history; there is no in-app viewport to scroll.
|
|
220
251
|
const [lines, setLines] = useState(() => engine.state.messages.map(lineFromMessage));
|
|
@@ -250,6 +281,9 @@ export function TerminalInterface({ engine }) {
|
|
|
250
281
|
// Recent snapshots for the home screen. Read once on mount and left
|
|
251
282
|
// alone: the home screen is only on screen before the first turn.
|
|
252
283
|
const [recentSessions, setRecentSessions] = useState([]);
|
|
284
|
+
// Off by default: it is a second thing to read, and the conversation is
|
|
285
|
+
// the first. Ctrl+B brings it in.
|
|
286
|
+
const [sidebarWanted, setSidebarWanted] = useState(false);
|
|
253
287
|
const [layoutDebug, setLayoutDebug] = useState(false);
|
|
254
288
|
const syncRestoreRef = useRef(null);
|
|
255
289
|
const pushLine = (line) => setLines((current) => [...current, line]);
|
|
@@ -690,6 +724,9 @@ export function TerminalInterface({ engine }) {
|
|
|
690
724
|
case 'ctrlE':
|
|
691
725
|
cycleMode();
|
|
692
726
|
return;
|
|
727
|
+
case 'ctrlB':
|
|
728
|
+
setSidebarWanted((on) => !on);
|
|
729
|
+
return;
|
|
693
730
|
case 'up':
|
|
694
731
|
if (sessionsOpen) {
|
|
695
732
|
setSessionsIndex((current) => clamp(current - 1, 0, Math.max(0, sessionsList.length - 1)));
|
|
@@ -783,6 +820,10 @@ export function TerminalInterface({ engine }) {
|
|
|
783
820
|
applyAction({ kind: 'ctrlE' });
|
|
784
821
|
return;
|
|
785
822
|
}
|
|
823
|
+
if (key.ctrl && value.toLowerCase() === 'b') {
|
|
824
|
+
applyAction({ kind: 'ctrlB' });
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
786
827
|
if (pickerOpen) {
|
|
787
828
|
if (key.escape) {
|
|
788
829
|
applyAction({ kind: 'escape' });
|
|
@@ -912,7 +953,17 @@ export function TerminalInterface({ engine }) {
|
|
|
912
953
|
}, [busy]);
|
|
913
954
|
const modeKey = kitty === true ? 'M' : 'E';
|
|
914
955
|
const modeAccent = MODE_ACCENT[mode];
|
|
915
|
-
|
|
956
|
+
// On a wide terminal the UI used to stretch edge to edge, which puts the
|
|
957
|
+
// input box and the text you are reading at opposite ends of the screen.
|
|
958
|
+
// Hold the content to a readable measure and centre it instead; below that
|
|
959
|
+
// measure the gutter collapses and nothing changes.
|
|
960
|
+
const sideMode = sidebarMode(width, sidebarWanted);
|
|
961
|
+
// With the panel beside it the conversation gets what is left, so the
|
|
962
|
+
// centred measure applies to the pair rather than to the text alone.
|
|
963
|
+
const measure = Math.min(width, sideMode === 'split' ? MAX_MEASURE + SIDEBAR_WIDTH + 1 : MAX_MEASURE);
|
|
964
|
+
const gutter = Math.max(2, Math.floor((width - measure) / 2));
|
|
965
|
+
const convoWidth = conversationWidth(measure, sideMode);
|
|
966
|
+
const contentWidth = Math.max(20, convoWidth - 6);
|
|
916
967
|
const terminalRows = stdout.rows ?? 24;
|
|
917
968
|
const metrics = engine.client.snapshotMetrics();
|
|
918
969
|
const meter = contextMeter(metrics.compression.inputTokens, engine.state.activeModel, metrics.fallback.activeProvider);
|
|
@@ -933,29 +984,32 @@ export function TerminalInterface({ engine }) {
|
|
|
933
984
|
const liveThinkView = liveThinkLines.slice(-Math.max(2, Math.floor(liveBudget / 2)));
|
|
934
985
|
const liveAnswerView = liveAnswerLines.slice(-liveBudget);
|
|
935
986
|
const doneAgents = agents.filter((lane) => lane.status === 'done').length;
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
987
|
+
const panel = _jsx(SidebarPanel, { width: SIDEBAR_WIDTH, model: engine.state.activeModel, mode: mode, perm: permMode, workspace: engine.state.workspace.root, usage: { tokensIn: metrics.compression.inputTokens, requests: metrics.requestCount }, agents: agents, todos: taskQueue, skills: engine.skills.length, plugins: new Set(engine.skills.map((skill) => skill.source).filter((source) => source !== undefined)).size });
|
|
988
|
+
return _jsxs(Box, { flexDirection: "column", width: width, paddingLeft: gutter, paddingRight: gutter, children: [_jsx(Static, { items: lines, children: (line, index) => _jsx(TranscriptEntry, { line: line, width: contentWidth, fallbackModel: engine.state.activeModel }, index) }, staticKey), sideMode === 'replace' && _jsx(Box, { marginBottom: 1, children: panel }), _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", children: [_jsxs(Box, { flexDirection: "column", width: sideMode === 'split' ? convoWidth : undefined, flexGrow: sideMode === 'split' ? 0 : 1, children: [lines.length === 0 && !busy && _jsx(Hero, { width: contentWidth + 2, endpoint: engine.client.endpoint ?? 'omniroute', model: engine.state.activeModel, mode: mode, perm: permMode, workspace: engine.state.workspace.root, sessions: recentSessions, skills: engine.skills.length, plugins: new Set(engine.skills.map((skill) => skill.source).filter((source) => source !== undefined)).size, mcpTools: engine.mcpTools.length }), liveThink !== '' && _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: PALETTE.warn, children: "\u00B7 thinking" }), liveThinkView.map((segments, index) => _jsx(SegmentText, { segments: segments, role: "thinking" }, index))] }), liveAnswer !== '' && _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: PALETTE.accent, children: engine.state.activeModel }), liveAnswerView.map((segments, index) => _jsx(SegmentText, { segments: segments, role: "assistant" }, index))] }), toolCards.slice(-5).map((card) => {
|
|
989
|
+
const expanded = expandedTool === card.id;
|
|
990
|
+
const dot = card.status === 'running' ? _jsx(Text, { color: PALETTE.warn, children: ".." }) : card.status === 'error' ? _jsx(Text, { color: PALETTE.error, children: "FAIL" }) : _jsx(Text, { color: PALETTE.success, children: "ok" });
|
|
991
|
+
const head = card.name === 'run_command'
|
|
992
|
+
? `$ ${clip(card.target || '…', Math.max(10, contentWidth - 20))}`
|
|
993
|
+
: `${toolVerb(card.name)}${card.target ? ` ${clip(card.target, Math.max(10, contentWidth - 24))}` : ''}`;
|
|
994
|
+
return _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: [dot, " ", head, expanded ? ' Ctrl+T collapse' : ''] }), expanded && renderToolBody(card, contentWidth, PALETTE)] }, card.id);
|
|
995
|
+
}), sideMode === 'hidden' && agents.length > 0 && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: PALETTE.error, paddingX: 2, marginTop: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: PALETTE.error, children: "swarm" }), _jsxs(Text, { dimColor: true, children: [doneAgents, "/", agents.length, " lanes done"] })] }), agents.map((lane, index) => {
|
|
996
|
+
const color = AGENT_COLORS[index % AGENT_COLORS.length];
|
|
997
|
+
const glyph = lane.status === 'done' ? 'ok' : lane.status === 'error' ? 'FAIL' : lane.status === 'working' ? '..' : '--';
|
|
998
|
+
const detail = lane.note ?? (lane.label !== lane.id ? lane.label : lane.status);
|
|
999
|
+
return _jsxs(Text, { color: color, children: [glyph, " ", lane.id, " ", _jsx(Text, { dimColor: true, children: clip(detail, Math.max(12, contentWidth - 8)) })] }, lane.id);
|
|
1000
|
+
})] }), sideMode === 'hidden' && taskQueue.length > 0 && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: PALETTE.accent, paddingX: 2, marginTop: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: PALETTE.accent, children: "plan" }), _jsxs(Text, { dimColor: true, children: [taskQueue.filter((item) => item.status === 'done').length, "/", taskQueue.length, " done"] })] }), taskQueue.slice(-6).map((item) => {
|
|
1001
|
+
const marker = item.status === 'done' ? 'ok' : item.status === 'active' ? '>' : '-';
|
|
1002
|
+
const color = item.status === 'done' ? PALETTE.success : item.status === 'active' ? PALETTE.accent : undefined;
|
|
1003
|
+
return _jsxs(Text, { color: color, dimColor: item.status === 'done', children: [marker, " ", clip(item.title, contentWidth - 4)] }, item.id);
|
|
1004
|
+
})] }), engine.state.preview && _jsxs(Text, { color: PALETTE.success, children: ["preview live \u00B7 ", engine.state.preview.url] }), sessionsOpen && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: PALETTE.info, paddingX: 2, marginTop: 1, children: [_jsx(Text, { bold: true, color: PALETTE.info, children: "saved sessions" }), _jsx(Text, { dimColor: true, children: "up/down navigate \u00B7 enter resume \u00B7 esc close" }), sessionsList.map((session, index) => (_jsxs(Text, { color: index === sessionsIndex ? PALETTE.info : 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: PALETTE.accent, paddingX: 2, marginTop: 1, children: [_jsx(Text, { bold: true, color: PALETTE.accent, children: "choose an OmniRoute model" }), _jsx(Text, { dimColor: true, children: "up/down / j k navigate \u00B7 enter select \u00B7 esc close" }), pickerError && _jsx(Text, { color: PALETTE.error, children: clip(pickerError, contentWidth) }), pickerItems.length === 0 && !pickerError && _jsx(Text, { dimColor: true, children: "no models returned by OmniRoute." }), pickerItems.map((item, index) => {
|
|
1005
|
+
const header = index === 0 || pickerItems[index - 1].group !== item.group
|
|
1006
|
+
? _jsx(Text, { dimColor: true, bold: true, children: item.group === 'combos' ? 'your combos' : 'auto engine' }, `h-${item.group}`)
|
|
1007
|
+
: null;
|
|
1008
|
+
return _jsxs(Box, { flexDirection: "column", children: [header, _jsxs(Text, { color: index === pickerIndex ? PALETTE.accent : undefined, children: [index === pickerIndex ? '> ' : ' ', item.id, item.strategy ? _jsxs(Text, { dimColor: true, children: [" \u00B7 ", item.strategy] }) : null, item.id === engine.state.activeModel ? ' *' : ''] })] }, item.id);
|
|
1009
|
+
})] }), approval && _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: PALETTE.warn, paddingX: 2, marginTop: 1, children: [_jsxs(Text, { bold: true, color: PALETTE.warn, 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: PALETTE.muted, paddingX: 2, marginTop: 1, children: [_jsxs(Text, { bold: true, dimColor: true, children: ["layout \u00B7 ", width, "\u00D7", terminalRows, " \u00B7 Ctrl+L to hide"] }), _jsxs(Text, { dimColor: true, children: ["static entries ", lines.length, " \u00B7 live budget ", liveBudget, " \u00B7 think ", liveThinkLines.length, " \u00B7 answer ", liveAnswerLines.length] }), _jsxs(Text, { dimColor: true, children: ["plan ", taskQueue.length, " \u00B7 swarm ", agents.length, " \u00B7 tool cards ", toolCards.length, " \u00B7 editor rows ", editorLayout.lines.length] })] }), queued && _jsxs(Text, { color: PALETTE.warn, children: ["queued \u00B7 ", clip(queued, Math.max(12, contentWidth - 12))] }), _jsx(Box, { borderStyle: "round", borderColor: error ? PALETTE.error : modeAccent, paddingX: 1, marginTop: 1, flexDirection: "column", children: edit.value === ''
|
|
1010
|
+
? _jsxs(Text, { color: modeAccent, children: ['>', " ", _jsx(Text, { dimColor: true, children: busy ? 'type to queue the next task' : 'describe the work and press enter' })] })
|
|
1011
|
+
: editorLayout.lines.map((text, index) => _jsxs(Text, { color: modeAccent, 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 ? modeAccent : PALETTE.muted, children: [busy && _jsx(Spinner, { type: "line" }), busy ? ' ' : '', phase, elapsed ? ` · ${elapsed}` : '', agents.length > 0 ? ` · swarm ${doneAgents}/${agents.length}` : ''] }), _jsxs(Text, { color: PALETTE.muted, children: [_jsx(Text, { color: modeAccent, children: mode }), _jsx(Text, { children: " \u00B7 " }), _jsx(Text, { color: mode === 'crazy' ? PALETTE.error : PERM_COLOR(permMode), children: mode === 'crazy' ? 'bypass' : PERM_LABEL[permMode] }), _jsxs(Text, { children: [" \u00B7 ", engine.state.activeModel] }), metrics.fallback.activeProvider ? _jsxs(Text, { children: [" \u00B7 via ", metrics.fallback.activeProvider] }) : null, contextLabel ? _jsxs(Text, { color: meterColor, children: [" \u00B7 ", contextLabel] }) : null] })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { dimColor: true, children: sideMode === 'split'
|
|
1012
|
+
? `Ctrl+B panel · Ctrl+${modeKey} mode · Ctrl+C ${busy ? 'cancel' : 'quit'} · /help`
|
|
1013
|
+
: `Enter send · Ctrl+J newline · Ctrl+O models · Ctrl+${modeKey} mode · Shift+Tab perms · Ctrl+B panel · Ctrl+T tool · Ctrl+Y copy · Ctrl+C ${busy ? 'cancel' : 'quit'} · /help` }), compression ? _jsxs(Text, { dimColor: true, children: ["saved ", compression, metrics.remainingQuota !== undefined ? ` · quota ${metrics.remainingQuota}` : ''] }) : null] })] })] }), sideMode === 'split' && _jsx(Box, { marginLeft: 1, children: panel })] })] });
|
|
960
1014
|
}
|
|
961
1015
|
//# sourceMappingURL=terminalInterface.js.map
|