omniharness-cli 0.1.81 → 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.
@@ -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';
@@ -180,6 +181,30 @@ function TranscriptEntry({ line, width, fallbackModel }) {
180
181
  const bullet = line.role === 'user' ? '>' : line.role === 'error' ? '!' : '-';
181
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] });
182
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
+ }
183
208
  /**
184
209
  * Width of the dim label column on the home screen. The value gets whatever is
185
210
  * left, and every caller has to subtract this: a value sized to the whole box
@@ -220,7 +245,7 @@ export function TerminalInterface({ engine }) {
220
245
  const { stdin } = useStdin();
221
246
  const [width, setWidth] = useState(() => widthOf(stdout));
222
247
  const [edit, setEdit] = useState({ value: '', cursor: 0 });
223
- const inputWidth = Math.max(16, Math.min(width, MAX_MEASURE) - 12);
248
+ const inputWidth = Math.max(16, Math.min(width, MAX_MEASURE) - 12); // recomputed below once the split is known
224
249
  // Settled transcript. Rendered once each into <Static> — the terminal's own
225
250
  // scrollback is the history; there is no in-app viewport to scroll.
226
251
  const [lines, setLines] = useState(() => engine.state.messages.map(lineFromMessage));
@@ -256,6 +281,9 @@ export function TerminalInterface({ engine }) {
256
281
  // Recent snapshots for the home screen. Read once on mount and left
257
282
  // alone: the home screen is only on screen before the first turn.
258
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);
259
287
  const [layoutDebug, setLayoutDebug] = useState(false);
260
288
  const syncRestoreRef = useRef(null);
261
289
  const pushLine = (line) => setLines((current) => [...current, line]);
@@ -696,6 +724,9 @@ export function TerminalInterface({ engine }) {
696
724
  case 'ctrlE':
697
725
  cycleMode();
698
726
  return;
727
+ case 'ctrlB':
728
+ setSidebarWanted((on) => !on);
729
+ return;
699
730
  case 'up':
700
731
  if (sessionsOpen) {
701
732
  setSessionsIndex((current) => clamp(current - 1, 0, Math.max(0, sessionsList.length - 1)));
@@ -789,6 +820,10 @@ export function TerminalInterface({ engine }) {
789
820
  applyAction({ kind: 'ctrlE' });
790
821
  return;
791
822
  }
823
+ if (key.ctrl && value.toLowerCase() === 'b') {
824
+ applyAction({ kind: 'ctrlB' });
825
+ return;
826
+ }
792
827
  if (pickerOpen) {
793
828
  if (key.escape) {
794
829
  applyAction({ kind: 'escape' });
@@ -922,9 +957,13 @@ export function TerminalInterface({ engine }) {
922
957
  // input box and the text you are reading at opposite ends of the screen.
923
958
  // Hold the content to a readable measure and centre it instead; below that
924
959
  // measure the gutter collapses and nothing changes.
925
- const measure = Math.min(width, MAX_MEASURE);
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);
926
964
  const gutter = Math.max(2, Math.floor((width - measure) / 2));
927
- const contentWidth = Math.max(20, measure - 6);
965
+ const convoWidth = conversationWidth(measure, sideMode);
966
+ const contentWidth = Math.max(20, convoWidth - 6);
928
967
  const terminalRows = stdout.rows ?? 24;
929
968
  const metrics = engine.client.snapshotMetrics();
930
969
  const meter = contextMeter(metrics.compression.inputTokens, engine.state.activeModel, metrics.fallback.activeProvider);
@@ -945,29 +984,32 @@ export function TerminalInterface({ engine }) {
945
984
  const liveThinkView = liveThinkLines.slice(-Math.max(2, Math.floor(liveBudget / 2)));
946
985
  const liveAnswerView = liveAnswerLines.slice(-liveBudget);
947
986
  const doneAgents = agents.filter((lane) => lane.status === 'done').length;
948
- 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), _jsxs(Box, { flexDirection: "column", 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) => {
949
- const expanded = expandedTool === card.id;
950
- 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" });
951
- const head = card.name === 'run_command'
952
- ? `$ ${clip(card.target || '', Math.max(10, contentWidth - 20))}`
953
- : `${toolVerb(card.name)}${card.target ? ` ${clip(card.target, Math.max(10, contentWidth - 24))}` : ''}`;
954
- return _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: [dot, " ", head, expanded ? ' Ctrl+T collapse' : ''] }), expanded && renderToolBody(card, contentWidth, PALETTE)] }, card.id);
955
- }), 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) => {
956
- const color = AGENT_COLORS[index % AGENT_COLORS.length];
957
- const glyph = lane.status === 'done' ? 'ok' : lane.status === 'error' ? 'FAIL' : lane.status === 'working' ? '..' : '--';
958
- const detail = lane.note ?? (lane.label !== lane.id ? lane.label : lane.status);
959
- return _jsxs(Text, { color: color, children: [glyph, " ", lane.id, " ", _jsx(Text, { dimColor: true, children: clip(detail, Math.max(12, contentWidth - 8)) })] }, lane.id);
960
- })] }), 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) => {
961
- const marker = item.status === 'done' ? 'ok' : item.status === 'active' ? '>' : '-';
962
- const color = item.status === 'done' ? PALETTE.success : item.status === 'active' ? PALETTE.accent : undefined;
963
- return _jsxs(Text, { color: color, dimColor: item.status === 'done', children: [marker, " ", clip(item.title, contentWidth - 4)] }, item.id);
964
- })] }), 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) => {
965
- const header = index === 0 || pickerItems[index - 1].group !== item.group
966
- ? _jsx(Text, { dimColor: true, bold: true, children: item.group === 'combos' ? 'your combos' : 'auto engine' }, `h-${item.group}`)
967
- : null;
968
- 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);
969
- })] }), 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 === ''
970
- ? _jsxs(Text, { color: modeAccent, children: ['>', " ", _jsx(Text, { dimColor: true, children: busy ? 'type to queue the next task' : 'describe the work and press enter' })] })
971
- : 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: [_jsxs(Text, { dimColor: true, children: ["Enter send \u00B7 Ctrl+J newline \u00B7 Ctrl+O models \u00B7 Ctrl+", modeKey, " mode \u00B7 Shift+Tab perms \u00B7 Ctrl+T tool \u00B7 Ctrl+Y copy \u00B7 Ctrl+C ", busy ? 'cancel' : 'quit', " \u00B7 /help"] }), compression ? _jsxs(Text, { dimColor: true, children: ["saved ", compression, metrics.remainingQuota !== undefined ? ` · quota ${metrics.remainingQuota}` : ''] }) : null] })] })] })] });
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 })] })] });
972
1014
  }
973
1015
  //# sourceMappingURL=terminalInterface.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omniharness-cli",
3
- "version": "0.1.81",
3
+ "version": "0.1.82",
4
4
  "description": "OmniHarness — local-first agent orchestration harness for OmniRoute.",
5
5
  "license": "MIT",
6
6
  "type": "module",