omniharness-cli 0.1.79 → 0.1.81
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/terminalInterface.js +16 -4
- package/package.json +2 -2
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
|
|
@@ -80,6 +80,12 @@ const MODE_SEQ = ['plan', 'build', 'research', 'crazy'];
|
|
|
80
80
|
const PERM_SEQ = ['ask', 'acceptEdits', 'bypass'];
|
|
81
81
|
const PERM_LABEL = { ask: 'manual', acceptEdits: 'accept edits', bypass: 'bypass' };
|
|
82
82
|
const PERM_COLOR = (p) => (p === 'bypass' ? PALETTE.error : p === 'acceptEdits' ? PALETTE.warn : PALETTE.muted);
|
|
83
|
+
/**
|
|
84
|
+
* The widest the content is allowed to get, however wide the terminal is.
|
|
85
|
+
* Past roughly this the eye loses the start of a line on the way back from
|
|
86
|
+
* the end of it, and the chrome ends up further from what it describes.
|
|
87
|
+
*/
|
|
88
|
+
const MAX_MEASURE = 100;
|
|
83
89
|
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
84
90
|
const widthOf = (stdout) => Math.max(48, stdout.columns ?? 80);
|
|
85
91
|
const clip = (text, width) => text.length <= width ? text : `${text.slice(0, Math.max(0, width - 1))}…`;
|
|
@@ -197,7 +203,7 @@ function Field({ label, children }) {
|
|
|
197
203
|
export function Hero(props) {
|
|
198
204
|
const { width, endpoint, model, mode, perm, workspace, sessions, skills, plugins, mcpTools } = props;
|
|
199
205
|
const wide = twoColumn(width);
|
|
200
|
-
const outer =
|
|
206
|
+
const outer = width;
|
|
201
207
|
const column = wide ? Math.floor((outer - 3) / 2) : outer;
|
|
202
208
|
const inner = Math.max(12, column - 4);
|
|
203
209
|
// Values sit to the right of the label, so that is the room they actually get.
|
|
@@ -214,7 +220,7 @@ export function TerminalInterface({ engine }) {
|
|
|
214
220
|
const { stdin } = useStdin();
|
|
215
221
|
const [width, setWidth] = useState(() => widthOf(stdout));
|
|
216
222
|
const [edit, setEdit] = useState({ value: '', cursor: 0 });
|
|
217
|
-
const inputWidth = Math.max(16, width - 12);
|
|
223
|
+
const inputWidth = Math.max(16, Math.min(width, MAX_MEASURE) - 12);
|
|
218
224
|
// Settled transcript. Rendered once each into <Static> — the terminal's own
|
|
219
225
|
// scrollback is the history; there is no in-app viewport to scroll.
|
|
220
226
|
const [lines, setLines] = useState(() => engine.state.messages.map(lineFromMessage));
|
|
@@ -912,7 +918,13 @@ export function TerminalInterface({ engine }) {
|
|
|
912
918
|
}, [busy]);
|
|
913
919
|
const modeKey = kitty === true ? 'M' : 'E';
|
|
914
920
|
const modeAccent = MODE_ACCENT[mode];
|
|
915
|
-
|
|
921
|
+
// On a wide terminal the UI used to stretch edge to edge, which puts the
|
|
922
|
+
// input box and the text you are reading at opposite ends of the screen.
|
|
923
|
+
// Hold the content to a readable measure and centre it instead; below that
|
|
924
|
+
// measure the gutter collapses and nothing changes.
|
|
925
|
+
const measure = Math.min(width, MAX_MEASURE);
|
|
926
|
+
const gutter = Math.max(2, Math.floor((width - measure) / 2));
|
|
927
|
+
const contentWidth = Math.max(20, measure - 6);
|
|
916
928
|
const terminalRows = stdout.rows ?? 24;
|
|
917
929
|
const metrics = engine.client.snapshotMetrics();
|
|
918
930
|
const meter = contextMeter(metrics.compression.inputTokens, engine.state.activeModel, metrics.fallback.activeProvider);
|
|
@@ -933,7 +945,7 @@ export function TerminalInterface({ engine }) {
|
|
|
933
945
|
const liveThinkView = liveThinkLines.slice(-Math.max(2, Math.floor(liveBudget / 2)));
|
|
934
946
|
const liveAnswerView = liveAnswerLines.slice(-liveBudget);
|
|
935
947
|
const doneAgents = agents.filter((lane) => lane.status === 'done').length;
|
|
936
|
-
return _jsxs(Box, { flexDirection: "column", width: width,
|
|
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) => {
|
|
937
949
|
const expanded = expandedTool === card.id;
|
|
938
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" });
|
|
939
951
|
const head = card.name === 'run_command'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omniharness-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.81",
|
|
4
4
|
"description": "OmniHarness — local-first agent orchestration harness for OmniRoute.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"build": "tsc -p tsconfig.json",
|
|
23
23
|
"dev": "tsx src/cli.tsx",
|
|
24
24
|
"start": "node dist/cli.js",
|
|
25
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
25
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
26
26
|
"test": "node --test --import ./test/setup.mjs --import tsx test/**/*.test.ts"
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|