openhorse 0.2.13 → 0.2.17
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/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +217 -49
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/types.d.ts +4 -1
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/core/checkpoint.d.ts +7 -0
- package/dist/core/checkpoint.d.ts.map +1 -1
- package/dist/core/checkpoint.js +10 -0
- package/dist/core/checkpoint.js.map +1 -1
- package/dist/core/cost-tracker.d.ts.map +1 -1
- package/dist/core/cost-tracker.js +11 -0
- package/dist/core/cost-tracker.js.map +1 -1
- package/dist/ink-ui/components/PromptInput.d.ts.map +1 -1
- package/dist/ink-ui/components/PromptInput.js +5 -3
- package/dist/ink-ui/components/PromptInput.js.map +1 -1
- package/dist/ink-ui/components/SelectList.d.ts.map +1 -1
- package/dist/ink-ui/components/SelectList.js +10 -4
- package/dist/ink-ui/components/SelectList.js.map +1 -1
- package/dist/ink-ui/components/StatusLine.d.ts +3 -1
- package/dist/ink-ui/components/StatusLine.d.ts.map +1 -1
- package/dist/ink-ui/components/StatusLine.js +5 -3
- package/dist/ink-ui/components/StatusLine.js.map +1 -1
- package/dist/ink-ui/components/ToolActivity.d.ts +3 -1
- package/dist/ink-ui/components/ToolActivity.d.ts.map +1 -1
- package/dist/ink-ui/components/ToolActivity.js +35 -9
- package/dist/ink-ui/components/ToolActivity.js.map +1 -1
- package/dist/ink-ui/launch.d.ts.map +1 -1
- package/dist/ink-ui/launch.js +39 -10
- package/dist/ink-ui/launch.js.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.d.ts +3 -1
- package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.js +63 -93
- package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
- package/dist/print-ui/launch.d.ts.map +1 -1
- package/dist/print-ui/launch.js +1 -0
- package/dist/print-ui/launch.js.map +1 -1
- package/dist/runtime/agent-runtime-controller.d.ts +5 -0
- package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-controller.js +15 -3
- package/dist/runtime/agent-runtime-controller.js.map +1 -1
- package/dist/runtime/agent-status.d.ts +5 -0
- package/dist/runtime/agent-status.d.ts.map +1 -1
- package/dist/runtime/agent-status.js +28 -2
- package/dist/runtime/agent-status.js.map +1 -1
- package/dist/runtime/chat-controller.d.ts +4 -0
- package/dist/runtime/chat-controller.d.ts.map +1 -1
- package/dist/runtime/chat-controller.js +199 -67
- package/dist/runtime/chat-controller.js.map +1 -1
- package/dist/runtime/turn-controller.d.ts +4 -0
- package/dist/runtime/turn-controller.d.ts.map +1 -1
- package/dist/runtime/turn-controller.js +8 -0
- package/dist/runtime/turn-controller.js.map +1 -1
- package/dist/runtime/ui-events.d.ts +7 -0
- package/dist/runtime/ui-events.d.ts.map +1 -1
- package/dist/runtime/ui-events.js.map +1 -1
- package/dist/runtime/ui-view-model.d.ts +287 -0
- package/dist/runtime/ui-view-model.d.ts.map +1 -0
- package/dist/runtime/ui-view-model.js +642 -0
- package/dist/runtime/ui-view-model.js.map +1 -0
- package/dist/services/file-glob.d.ts +6 -2
- package/dist/services/file-glob.d.ts.map +1 -1
- package/dist/services/file-glob.js +10 -4
- package/dist/services/file-glob.js.map +1 -1
- package/dist/services/model-catalog.d.ts +14 -0
- package/dist/services/model-catalog.d.ts.map +1 -0
- package/dist/services/model-catalog.js +73 -0
- package/dist/services/model-catalog.js.map +1 -0
- package/dist/services/model-context.d.ts.map +1 -1
- package/dist/services/model-context.js +3 -0
- package/dist/services/model-context.js.map +1 -1
- package/dist/services/session-storage.d.ts +1 -0
- package/dist/services/session-storage.d.ts.map +1 -1
- package/dist/services/session-storage.js.map +1 -1
- package/dist/services/verification-profile.d.ts +15 -0
- package/dist/services/verification-profile.d.ts.map +1 -1
- package/dist/services/verification-profile.js +67 -0
- package/dist/services/verification-profile.js.map +1 -1
- package/dist/terminal-ui/completion.d.ts.map +1 -1
- package/dist/terminal-ui/completion.js +9 -6
- package/dist/terminal-ui/completion.js.map +1 -1
- package/dist/terminal-ui/launch.d.ts +15 -1
- package/dist/terminal-ui/launch.d.ts.map +1 -1
- package/dist/terminal-ui/launch.js +300 -56
- package/dist/terminal-ui/launch.js.map +1 -1
- package/dist/terminal-ui/raw-editor.d.ts +4 -0
- package/dist/terminal-ui/raw-editor.d.ts.map +1 -1
- package/dist/terminal-ui/raw-editor.js +29 -0
- package/dist/terminal-ui/raw-editor.js.map +1 -1
- package/dist/tui-ui/launch.d.ts.map +1 -1
- package/dist/tui-ui/launch.js +1 -0
- package/dist/tui-ui/launch.js.map +1 -1
- package/dist/tui-ui/layout.d.ts.map +1 -1
- package/dist/tui-ui/layout.js +21 -28
- package/dist/tui-ui/layout.js.map +1 -1
- package/dist/tui-ui/pickers.d.ts.map +1 -1
- package/dist/tui-ui/pickers.js +21 -62
- package/dist/tui-ui/pickers.js.map +1 -1
- package/dist/tui-ui/state.d.ts +2 -4
- package/dist/tui-ui/state.d.ts.map +1 -1
- package/dist/tui-ui/state.js +13 -11
- package/dist/tui-ui/state.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,9 +5,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.TerminalInputComposer = exports.TerminalEventSink = void 0;
|
|
7
7
|
exports.resolveTerminalSessionPickerInput = resolveTerminalSessionPickerInput;
|
|
8
|
+
exports.formatTerminalErrorMessage = formatTerminalErrorMessage;
|
|
9
|
+
exports.inferTerminalErrorLayer = inferTerminalErrorLayer;
|
|
10
|
+
exports.formatTerminalStatusMessage = formatTerminalStatusMessage;
|
|
11
|
+
exports.formatTerminalSessionRestored = formatTerminalSessionRestored;
|
|
8
12
|
exports.visibleLength = visibleLength;
|
|
9
13
|
exports.terminalContentWidth = terminalContentWidth;
|
|
10
14
|
exports.truncateTerminalText = truncateTerminalText;
|
|
15
|
+
exports.renderTerminalCapabilitySummary = renderTerminalCapabilitySummary;
|
|
16
|
+
exports.formatTerminalSessionPickerItem = formatTerminalSessionPickerItem;
|
|
17
|
+
exports.formatTerminalSessionPickerHeader = formatTerminalSessionPickerHeader;
|
|
18
|
+
exports.formatTerminalEditPreviewHeader = formatTerminalEditPreviewHeader;
|
|
19
|
+
exports.formatTerminalEditPreviewCandidate = formatTerminalEditPreviewCandidate;
|
|
20
|
+
exports.renderTerminalBanner = renderTerminalBanner;
|
|
21
|
+
exports.formatTerminalPermissionPrompt = formatTerminalPermissionPrompt;
|
|
11
22
|
exports.parseEditInput = parseEditInput;
|
|
12
23
|
exports.renderTerminalShortcuts = renderTerminalShortcuts;
|
|
13
24
|
exports.normalizeTerminalAnswer = normalizeTerminalAnswer;
|
|
@@ -17,7 +28,9 @@ const string_width_1 = __importDefault(require("string-width"));
|
|
|
17
28
|
const agent_runtime_controller_1 = require("../runtime/agent-runtime-controller");
|
|
18
29
|
const agent_runtime_protocol_1 = require("../runtime/agent-runtime-protocol");
|
|
19
30
|
const ui_events_1 = require("../runtime/ui-events");
|
|
31
|
+
const ui_view_model_1 = require("../runtime/ui-view-model");
|
|
20
32
|
const format_1 = require("../services/format");
|
|
33
|
+
const redaction_1 = require("../services/redaction");
|
|
21
34
|
const completion_1 = require("./completion");
|
|
22
35
|
const editor_1 = require("./editor");
|
|
23
36
|
const raw_editor_1 = require("./raw-editor");
|
|
@@ -29,9 +42,6 @@ const BORDER = chalk_1.default.hex('#38556A');
|
|
|
29
42
|
function stripTrailingNewlines(text) {
|
|
30
43
|
return text.replace(/\n+$/g, '');
|
|
31
44
|
}
|
|
32
|
-
function sessionTitle(session) {
|
|
33
|
-
return session.name || session.taskSummary || '(untitled)';
|
|
34
|
-
}
|
|
35
45
|
function normalizePickerText(value) {
|
|
36
46
|
return value.trim().toLowerCase();
|
|
37
47
|
}
|
|
@@ -62,6 +72,12 @@ function resolveTerminalSessionPickerInput(input, request) {
|
|
|
62
72
|
return { type: 'cancelled' };
|
|
63
73
|
if (trimmed.startsWith('/'))
|
|
64
74
|
return { type: 'slash', input: trimmed };
|
|
75
|
+
if (trimmed === '--last') {
|
|
76
|
+
const latest = request.sessions[0];
|
|
77
|
+
if (latest)
|
|
78
|
+
return { type: 'selected', sessionId: latest.id };
|
|
79
|
+
return { type: 'error', message: 'No recent session to resume.' };
|
|
80
|
+
}
|
|
65
81
|
const explicitIndex = trimmed.match(/^#(\d+)$/);
|
|
66
82
|
if (explicitIndex) {
|
|
67
83
|
const index = Number(explicitIndex[1]) - 1;
|
|
@@ -84,7 +100,7 @@ function resolveTerminalSessionPickerInput(input, request) {
|
|
|
84
100
|
if (matches.length > 1) {
|
|
85
101
|
const preview = matches
|
|
86
102
|
.slice(0, 3)
|
|
87
|
-
.map(session => `${session.id.slice(0, 8)} ${
|
|
103
|
+
.map(session => `${session.id.slice(0, 8)} ${(0, ui_view_model_1.sessionPickerTitle)(session)}`)
|
|
88
104
|
.join(', ');
|
|
89
105
|
const suffix = matches.length > 3 ? `, +${matches.length - 3} more` : '';
|
|
90
106
|
return {
|
|
@@ -110,7 +126,7 @@ function formatTranscriptEntry(entry) {
|
|
|
110
126
|
case 'tool':
|
|
111
127
|
return TOOL(content);
|
|
112
128
|
case 'error':
|
|
113
|
-
return ERROR(content);
|
|
129
|
+
return ERROR(formatTerminalErrorMessage(content, entry.errorLayer));
|
|
114
130
|
case 'status':
|
|
115
131
|
return DIM(content);
|
|
116
132
|
case 'command':
|
|
@@ -121,9 +137,87 @@ function formatTranscriptEntry(entry) {
|
|
|
121
137
|
return content;
|
|
122
138
|
}
|
|
123
139
|
}
|
|
140
|
+
const TERMINAL_ERROR_LAYERS = [
|
|
141
|
+
'renderer',
|
|
142
|
+
'runtime',
|
|
143
|
+
'provider',
|
|
144
|
+
'tool',
|
|
145
|
+
'session',
|
|
146
|
+
'memory',
|
|
147
|
+
'MCP',
|
|
148
|
+
'skills',
|
|
149
|
+
];
|
|
150
|
+
function formatTerminalErrorMessage(message, explicitLayer) {
|
|
151
|
+
const trimmed = message.trim();
|
|
152
|
+
if (!trimmed)
|
|
153
|
+
return trimmed;
|
|
154
|
+
if (hasErrorLayerPrefix(trimmed))
|
|
155
|
+
return trimmed;
|
|
156
|
+
if (explicitLayer) {
|
|
157
|
+
return `[${explicitLayer.toUpperCase()}] ${trimmed}`;
|
|
158
|
+
}
|
|
159
|
+
return `[${inferTerminalErrorLayer(trimmed)}] ${trimmed}`;
|
|
160
|
+
}
|
|
161
|
+
function hasErrorLayerPrefix(message) {
|
|
162
|
+
const lower = message.toLowerCase();
|
|
163
|
+
return TERMINAL_ERROR_LAYERS.some(layer => {
|
|
164
|
+
const label = layer.toLowerCase();
|
|
165
|
+
return lower.startsWith(`[${label}] `)
|
|
166
|
+
|| lower.startsWith(`error [${label}]:`)
|
|
167
|
+
|| lower.startsWith(`error: [${label}] `);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function inferTerminalErrorLayer(message) {
|
|
171
|
+
const lower = message.toLowerCase();
|
|
172
|
+
if (/\bmcp\b/u.test(lower))
|
|
173
|
+
return 'MCP';
|
|
174
|
+
if (/\b(skill|skills)\b/u.test(lower))
|
|
175
|
+
return 'skills';
|
|
176
|
+
if (/\b(memory|vector store|recall|forget)\b/u.test(lower))
|
|
177
|
+
return 'memory';
|
|
178
|
+
if (/\b(session|resume|compact)\b/u.test(lower))
|
|
179
|
+
return 'session';
|
|
180
|
+
if (/\b(renderer|terminal|tty|prompt|resize|input editor|scrollback)\b/u.test(lower))
|
|
181
|
+
return 'renderer';
|
|
182
|
+
if (/\btool\b/u.test(lower)
|
|
183
|
+
|| /\b(exec_command|read_file|write_file|edit_file|grep|glob|list_files)\b/u.test(lower)
|
|
184
|
+
|| /command exited with code/u.test(lower)
|
|
185
|
+
|| /path is a directory|not a file|enoent|eacces/u.test(lower)) {
|
|
186
|
+
return 'tool';
|
|
187
|
+
}
|
|
188
|
+
if (/\b(provider|model|llm|api|quota|rate limit|rate_limit|provider_busy)\b/u.test(lower)
|
|
189
|
+
|| /\b(openai|anthropic|dashscope|bailian|xunfei|glm|qwen)\b/u.test(lower)
|
|
190
|
+
|| /\bstatus code\b/u.test(lower)
|
|
191
|
+
|| /notEnoughCvError|engineInternalError/i.test(message)) {
|
|
192
|
+
return 'provider';
|
|
193
|
+
}
|
|
194
|
+
return 'runtime';
|
|
195
|
+
}
|
|
124
196
|
function shouldShowStatus(message) {
|
|
125
197
|
return Boolean(message.trim());
|
|
126
198
|
}
|
|
199
|
+
function formatTerminalStatusMessage(message, width = terminalContentWidth(120)) {
|
|
200
|
+
return truncateTerminalText(message.replace(/\s+/g, ' ').trim(), Math.max(1, width));
|
|
201
|
+
}
|
|
202
|
+
function formatTerminalSessionRestored(event) {
|
|
203
|
+
const width = terminalContentWidth(120);
|
|
204
|
+
const view = (0, ui_view_model_1.createSessionRestoredView)(event);
|
|
205
|
+
const fitLine = (prefix, value) => {
|
|
206
|
+
const prefixWidth = visibleLength(prefix);
|
|
207
|
+
if (prefixWidth >= width) {
|
|
208
|
+
return truncateTerminalText(prefix, width);
|
|
209
|
+
}
|
|
210
|
+
return `${prefix}${truncateTerminalText(value, width - prefixWidth)}`;
|
|
211
|
+
};
|
|
212
|
+
const lines = [
|
|
213
|
+
truncateTerminalText(view.headline, width),
|
|
214
|
+
fitLine(` Model: ${view.model} · Project: `, view.projectPath),
|
|
215
|
+
];
|
|
216
|
+
if (view.summary) {
|
|
217
|
+
lines.push(fitLine(' Summary: ', (0, redaction_1.redactTraceText)(view.summary)));
|
|
218
|
+
}
|
|
219
|
+
return lines.join('\n');
|
|
220
|
+
}
|
|
127
221
|
class DirectTerminalWriter {
|
|
128
222
|
write(text) {
|
|
129
223
|
process.stdout.write(text);
|
|
@@ -138,6 +232,10 @@ function terminalContentWidth(fallback = 88) {
|
|
|
138
232
|
if (typeof columns === 'number' && columns > 0) {
|
|
139
233
|
return Math.max(1, Math.min(columns, 200));
|
|
140
234
|
}
|
|
235
|
+
const envColumns = Number(process.env.COLUMNS);
|
|
236
|
+
if (Number.isFinite(envColumns) && envColumns > 0) {
|
|
237
|
+
return Math.max(1, Math.min(envColumns, 200));
|
|
238
|
+
}
|
|
141
239
|
return Math.max(60, Math.min(fallback, 200));
|
|
142
240
|
}
|
|
143
241
|
function truncateTerminalText(text, maxWidth) {
|
|
@@ -163,6 +261,80 @@ function bannerRow(content, width) {
|
|
|
163
261
|
const padding = ' '.repeat(Math.max(0, innerWidth - visibleLength(safeContent)));
|
|
164
262
|
return `${BORDER('│')}${safeContent}${padding}${BORDER('│')}`;
|
|
165
263
|
}
|
|
264
|
+
function renderTerminalCapabilitySummary(runtime) {
|
|
265
|
+
const snapshot = runtime.store.getSnapshot();
|
|
266
|
+
return (0, ui_view_model_1.createRuntimeCapabilitySummary)({
|
|
267
|
+
projectInstructionsContent: snapshot.projectInstructionsContent,
|
|
268
|
+
skillsContent: snapshot.skillsContent,
|
|
269
|
+
memoryContent: snapshot.memoryContent,
|
|
270
|
+
tools: snapshot.tools,
|
|
271
|
+
webSearchConfigured: Boolean(runtime.config.webSearch),
|
|
272
|
+
}).text;
|
|
273
|
+
}
|
|
274
|
+
function formatTerminalSessionPickerItem(item, width = terminalContentWidth(120)) {
|
|
275
|
+
const safeWidth = Math.max(1, width);
|
|
276
|
+
const prefixPlain = `${String(item.globalIndex).padStart(2, ' ')}. ${item.shortId} `;
|
|
277
|
+
const prefix = `${String(item.globalIndex).padStart(2, ' ')}. ${ACCENT(item.shortId)} `;
|
|
278
|
+
const size = (0, format_1.formatBytes)(item.historySizeBytes);
|
|
279
|
+
const project = item.showProject ? ` ${item.projectPath}` : '';
|
|
280
|
+
const metadataCandidates = [
|
|
281
|
+
`${item.messageCount} msgs ${size} ${item.model}${project}`,
|
|
282
|
+
`${item.messageCount} msgs ${size} ${item.model}`,
|
|
283
|
+
`${item.messageCount} msgs ${size}`,
|
|
284
|
+
`${item.messageCount} msgs`,
|
|
285
|
+
'',
|
|
286
|
+
];
|
|
287
|
+
for (const metadata of metadataCandidates) {
|
|
288
|
+
const suffixPlain = metadata ? ` ${metadata}` : '';
|
|
289
|
+
const titleBudget = safeWidth - visibleLength(prefixPlain) - visibleLength(suffixPlain);
|
|
290
|
+
if (titleBudget < 8)
|
|
291
|
+
continue;
|
|
292
|
+
const title = truncateTerminalText(item.title, titleBudget);
|
|
293
|
+
const row = `${prefix}${title}${metadata ? ` ${DIM(metadata)}` : ''}`;
|
|
294
|
+
if (visibleLength(row) <= safeWidth)
|
|
295
|
+
return row;
|
|
296
|
+
}
|
|
297
|
+
return truncateTerminalText(`${String(item.globalIndex).padStart(2, ' ')}. ${item.shortId} ${item.title}`, safeWidth);
|
|
298
|
+
}
|
|
299
|
+
function formatTerminalSessionPickerHeader(title, page, pageCount, width = terminalContentWidth(120)) {
|
|
300
|
+
const safeWidth = Math.max(1, width);
|
|
301
|
+
const pageLabelPlain = ` page ${page}/${pageCount}`;
|
|
302
|
+
const titleBudget = safeWidth - visibleLength(pageLabelPlain);
|
|
303
|
+
if (titleBudget < 4) {
|
|
304
|
+
return truncateTerminalText(`${title}${pageLabelPlain}`, safeWidth);
|
|
305
|
+
}
|
|
306
|
+
return `${ACCENT(truncateTerminalText(title, titleBudget))}${DIM(pageLabelPlain)}`;
|
|
307
|
+
}
|
|
308
|
+
function formatSessionPickerInstruction(width) {
|
|
309
|
+
const text = width < 72
|
|
310
|
+
? 'Select number/id, n/p page, empty cancels.'
|
|
311
|
+
: 'Type number/#number, session id prefix, unique title text, or /resume --last. Empty input cancels.';
|
|
312
|
+
return truncateTerminalText(text, Math.max(1, width));
|
|
313
|
+
}
|
|
314
|
+
function editPreviewKindLabel(request) {
|
|
315
|
+
return request.kind === 'fuzzy' ? `fuzzy (${request.strategy ?? 'match'})` : 'exact';
|
|
316
|
+
}
|
|
317
|
+
function formatTerminalEditPreviewHeader(request, width = terminalContentWidth(120)) {
|
|
318
|
+
return ACCENT(truncateTerminalText(`Edit Preview: ${request.path} (${editPreviewKindLabel(request)})`, Math.max(1, width)));
|
|
319
|
+
}
|
|
320
|
+
function formatTerminalEditPreviewCandidate(candidate, newString, width = terminalContentWidth(120)) {
|
|
321
|
+
const safeWidth = Math.max(1, width);
|
|
322
|
+
const prefix = ` line ${String(candidate.line).padStart(3, ' ')} `;
|
|
323
|
+
const fixedWidth = visibleLength(`${prefix}"" → ""`);
|
|
324
|
+
const contentBudget = safeWidth - fixedWidth;
|
|
325
|
+
if (contentBudget < 8) {
|
|
326
|
+
return truncateTerminalText(`${prefix}"${candidate.match}" → "${newString}"`, safeWidth);
|
|
327
|
+
}
|
|
328
|
+
const matchBudget = Math.max(1, Math.floor(contentBudget * 0.6));
|
|
329
|
+
const replacementBudget = Math.max(1, contentBudget - matchBudget);
|
|
330
|
+
const row = [
|
|
331
|
+
prefix,
|
|
332
|
+
`"${truncateTerminalText(candidate.match, matchBudget)}"`,
|
|
333
|
+
' → ',
|
|
334
|
+
`"${truncateTerminalText(newString, replacementBudget)}"`,
|
|
335
|
+
].join('');
|
|
336
|
+
return visibleLength(row) <= safeWidth ? row : truncateTerminalText(row, safeWidth);
|
|
337
|
+
}
|
|
166
338
|
class TerminalEventSink {
|
|
167
339
|
constructor(runtime, writer = new DirectTerminalWriter()) {
|
|
168
340
|
this.runtime = runtime;
|
|
@@ -235,7 +407,7 @@ class TerminalEventSink {
|
|
|
235
407
|
if (message === this.lastStatusMessage)
|
|
236
408
|
return;
|
|
237
409
|
this.lastStatusMessage = message;
|
|
238
|
-
this.writer.write(`${DIM(message)}\n`);
|
|
410
|
+
this.writer.write(`${DIM(formatTerminalStatusMessage(message))}\n`);
|
|
239
411
|
}
|
|
240
412
|
showSessionPicker(request) {
|
|
241
413
|
this.pendingPicker = request;
|
|
@@ -244,18 +416,23 @@ class TerminalEventSink {
|
|
|
244
416
|
}
|
|
245
417
|
showEditPreview(request) {
|
|
246
418
|
this.pendingEditPreview = request;
|
|
247
|
-
const
|
|
248
|
-
this.writer.write(`\n${
|
|
419
|
+
const rowWidth = terminalContentWidth(120);
|
|
420
|
+
this.writer.write(`\n${formatTerminalEditPreviewHeader(request, rowWidth)}\n`);
|
|
249
421
|
this.writer.write(`${BORDER('─'.repeat(terminalContentWidth(80)))}\n`);
|
|
250
422
|
request.candidates.slice(0, 10).forEach(c => {
|
|
251
|
-
|
|
252
|
-
const newPreview = request.newString.length > 40 ? request.newString.slice(0, 37) + '...' : request.newString;
|
|
253
|
-
this.writer.write(` line ${String(c.line).padStart(3, ' ')} "${matchPreview}" → "${newPreview}"\n`);
|
|
423
|
+
this.writer.write(`${formatTerminalEditPreviewCandidate(c, request.newString, rowWidth)}\n`);
|
|
254
424
|
});
|
|
255
425
|
if (request.candidates.length > 10) {
|
|
256
|
-
this.writer.write(`${DIM(` ... ${request.candidates.length - 10} more candidates
|
|
426
|
+
this.writer.write(`${DIM(truncateTerminalText(` ... ${request.candidates.length - 10} more candidates`, rowWidth))}\n`);
|
|
257
427
|
}
|
|
258
|
-
this.writer.write(`${DIM('Press Enter to dismiss.')}\n`);
|
|
428
|
+
this.writer.write(`${DIM(truncateTerminalText('Press Enter to dismiss.', rowWidth))}\n`);
|
|
429
|
+
}
|
|
430
|
+
sessionRestored(event) {
|
|
431
|
+
this.append({
|
|
432
|
+
role: 'status',
|
|
433
|
+
title: 'resume',
|
|
434
|
+
content: formatTerminalSessionRestored(event),
|
|
435
|
+
});
|
|
259
436
|
}
|
|
260
437
|
setProcessing(_processing) {
|
|
261
438
|
// The stable terminal UI is append-only, so there is no live spinner state.
|
|
@@ -307,38 +484,28 @@ class TerminalEventSink {
|
|
|
307
484
|
const request = this.pendingPicker;
|
|
308
485
|
if (!request)
|
|
309
486
|
return;
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
this.
|
|
314
|
-
const visibleSessions = request.sessions.slice(this.pickerOffset, this.pickerOffset + visibleLimit);
|
|
315
|
-
const page = total === 0 ? 1 : Math.floor(this.pickerOffset / visibleLimit) + 1;
|
|
316
|
-
const pages = total === 0 ? 1 : Math.ceil(total / visibleLimit);
|
|
317
|
-
this.writer.write(`\n${ACCENT(request.title)} ${DIM(`page ${page}/${pages}`)}\n`);
|
|
487
|
+
const state = (0, ui_view_model_1.createSessionPickerState)(request, this.pickerOffset);
|
|
488
|
+
this.pickerOffset = state.visibleStart;
|
|
489
|
+
const rowWidth = terminalContentWidth(120);
|
|
490
|
+
this.writer.write(`\n${formatTerminalSessionPickerHeader(state.title, state.page, state.pageCount, rowWidth)}\n`);
|
|
318
491
|
this.writer.write(`${BORDER('─'.repeat(terminalContentWidth(80)))}\n`);
|
|
319
|
-
if (
|
|
492
|
+
if (state.visibleItems.length === 0) {
|
|
320
493
|
this.writer.write(`${DIM('No saved sessions found.')}\n`);
|
|
321
494
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const id = session.id.slice(0, 8);
|
|
325
|
-
const size = (0, format_1.formatBytes)(session.historySizeBytes ?? 0);
|
|
326
|
-
const messages = session.messageCount ?? 0;
|
|
327
|
-
const project = request.showProject ? ` ${session.projectPath}` : '';
|
|
328
|
-
this.writer.write(`${String(globalIndex).padStart(2, ' ')}. ${ACCENT(id)} ${sessionTitle(session)} ${DIM(`${messages} msgs ${size} ${session.model}${project}`)}\n`);
|
|
495
|
+
state.visibleItems.forEach(item => {
|
|
496
|
+
this.writer.write(`${formatTerminalSessionPickerItem(item, rowWidth)}\n`);
|
|
329
497
|
});
|
|
330
|
-
if (
|
|
331
|
-
this.writer.write(`${DIM(`Showing ${
|
|
498
|
+
if (state.totalItems > state.visibleLimit) {
|
|
499
|
+
this.writer.write(`${DIM(truncateTerminalText(`Showing ${state.visibleStart + 1}-${state.visibleStart + state.visibleItems.length} of ${state.totalItems}. Type n/next or p/prev to page.`, rowWidth))}\n`);
|
|
332
500
|
}
|
|
333
|
-
this.writer.write(`${DIM(
|
|
501
|
+
this.writer.write(`${DIM(formatSessionPickerInstruction(rowWidth))}\n`);
|
|
334
502
|
}
|
|
335
503
|
moveSessionPickerPage(delta) {
|
|
336
504
|
const request = this.pendingPicker;
|
|
337
505
|
if (!request)
|
|
338
506
|
return;
|
|
339
|
-
const
|
|
340
|
-
const
|
|
341
|
-
const nextOffset = Math.max(0, Math.min(this.pickerOffset + delta * visibleLimit, maxOffset));
|
|
507
|
+
const state = (0, ui_view_model_1.createSessionPickerState)(request, this.pickerOffset);
|
|
508
|
+
const nextOffset = (0, ui_view_model_1.movePickerPageOffset)(state, delta);
|
|
342
509
|
if (nextOffset === this.pickerOffset) {
|
|
343
510
|
this.writer.write(`${DIM(delta > 0 ? 'Already at last session page.' : 'Already at first session page.')}\n`);
|
|
344
511
|
return;
|
|
@@ -385,29 +552,83 @@ class TerminalEventSink {
|
|
|
385
552
|
}
|
|
386
553
|
}
|
|
387
554
|
exports.TerminalEventSink = TerminalEventSink;
|
|
388
|
-
function
|
|
389
|
-
const width = terminalContentWidth(88);
|
|
390
|
-
const line = '─'.repeat(Math.max(
|
|
555
|
+
function renderTerminalBanner(runtime) {
|
|
556
|
+
const width = Math.max(2, terminalContentWidth(88));
|
|
557
|
+
const line = '─'.repeat(Math.max(0, width - 2));
|
|
391
558
|
const firstLine = ` ${ACCENT.bold('OPENHORSE')} ${DIM(`v${runtime.version}`)} ${DIM('stable terminal UI')}`;
|
|
392
559
|
const projectPrefix = ` ${DIM('Model')} ${ACCENT(runtime.config.model)} ${DIM('Project')} `;
|
|
393
560
|
const project = truncateTerminalText(runtime.cwd, Math.max(10, width - 2 - visibleLength(projectPrefix)));
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
561
|
+
const session = runtime.getSession()?.id.slice(0, 8) ?? 'new';
|
|
562
|
+
const renderer = runtime.config.ui?.renderer ?? 'terminal';
|
|
563
|
+
const rendererLine = ` ${DIM('Session')} ${ACCENT(session)} ${DIM('Renderer')} ${ACCENT(renderer)}`;
|
|
564
|
+
const capabilityPrefix = ` ${DIM('Capabilities')} `;
|
|
565
|
+
const capabilityText = renderTerminalCapabilitySummary(runtime);
|
|
566
|
+
const capabilities = truncateTerminalText(capabilityText, Math.max(10, width - 2 - visibleLength(capabilityPrefix)));
|
|
567
|
+
return [
|
|
568
|
+
'',
|
|
569
|
+
BORDER(`╭${line}╮`),
|
|
570
|
+
bannerRow(firstLine, width),
|
|
571
|
+
bannerRow(`${projectPrefix}${project}`, width),
|
|
572
|
+
bannerRow(rendererLine, width),
|
|
573
|
+
bannerRow(`${capabilityPrefix}${capabilities}`, width),
|
|
574
|
+
BORDER(`╰${line}╯`),
|
|
575
|
+
'',
|
|
576
|
+
'',
|
|
577
|
+
].join('\n');
|
|
578
|
+
}
|
|
579
|
+
function printBanner(runtime) {
|
|
580
|
+
process.stdout.write(renderTerminalBanner(runtime));
|
|
399
581
|
}
|
|
400
582
|
function promptText(runtime) {
|
|
401
583
|
const session = runtime.getSession()?.id.slice(0, 8) ?? 'new';
|
|
402
584
|
return `${DIM(`[${session}]`)} ${ACCENT('›')} `;
|
|
403
585
|
}
|
|
404
|
-
function
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
586
|
+
function compactPermissionValue(value, maxWidth) {
|
|
587
|
+
const singleLine = (0, redaction_1.redactTraceText)(value).replace(/\s+/g, ' ').trim();
|
|
588
|
+
return truncateTerminalText(singleLine, Math.max(8, maxWidth));
|
|
589
|
+
}
|
|
590
|
+
function formatTerminalPermissionScope(state) {
|
|
591
|
+
const width = terminalContentWidth(120);
|
|
592
|
+
const maxScopeWidth = Math.max(24, Math.min(72, Math.floor(width * 0.35)));
|
|
593
|
+
return compactPermissionValue((0, ui_view_model_1.permissionScopeDisplayValue)(state.scope), maxScopeWidth);
|
|
594
|
+
}
|
|
595
|
+
function formatTerminalPermissionPrompt(request, cwd) {
|
|
596
|
+
const state = (0, ui_view_model_1.createPermissionPromptState)(request, cwd);
|
|
597
|
+
const width = terminalContentWidth(120);
|
|
598
|
+
const budget = Math.max(1, width);
|
|
599
|
+
const base = `${ACCENT('?')} Allow tool ${ACCENT(state.toolName)}?`;
|
|
600
|
+
const options = DIM(`[${state.options.approve} ${state.options.deny}]`);
|
|
601
|
+
const scope = DIM(formatTerminalPermissionScope(state));
|
|
602
|
+
const cwdLabel = DIM(`cwd=${compactPermissionValue(state.cwd, Math.max(12, Math.min(48, Math.floor(width * 0.22))))}`);
|
|
603
|
+
const risk = DIM(`risk=${compactPermissionValue((0, ui_view_model_1.permissionRiskDisplayValue)(state.risk), Math.max(12, Math.min(48, Math.floor(width * 0.24))))}`);
|
|
604
|
+
const parts = [base, scope, cwdLabel, risk, options];
|
|
605
|
+
while (parts.length > 3 && visibleLength(`${parts.join(' ')} `) > budget) {
|
|
606
|
+
const riskIndex = parts.indexOf(risk);
|
|
607
|
+
if (riskIndex >= 0) {
|
|
608
|
+
parts.splice(riskIndex, 1);
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const cwdIndex = parts.indexOf(cwdLabel);
|
|
612
|
+
if (cwdIndex >= 0) {
|
|
613
|
+
parts.splice(cwdIndex, 1);
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
let prompt = `${parts.join(' ')} `;
|
|
619
|
+
if (visibleLength(prompt) <= budget)
|
|
620
|
+
return prompt;
|
|
621
|
+
const fixed = `${base} ${options} `;
|
|
622
|
+
const scopeBudget = Math.max(8, budget - visibleLength(fixed) - 1);
|
|
623
|
+
prompt = `${base} ${DIM(truncateTerminalText(formatTerminalPermissionScope(state), scopeBudget))} ${options} `;
|
|
624
|
+
if (visibleLength(prompt) <= budget)
|
|
625
|
+
return prompt;
|
|
626
|
+
const optionWidth = visibleLength(options);
|
|
627
|
+
if (budget <= optionWidth + 1) {
|
|
628
|
+
return truncateTerminalText(`${stripTrailingNewlines(base)} ${stripTrailingNewlines(options)} `, budget);
|
|
629
|
+
}
|
|
630
|
+
const baseBudget = Math.max(1, budget - optionWidth - 2);
|
|
631
|
+
return `${truncateTerminalText(stripTrailingNewlines(base), baseBudget)} ${options} `;
|
|
411
632
|
}
|
|
412
633
|
function isExitInput(input) {
|
|
413
634
|
return ['/exit', '/quit', '/q'].includes(input.trim());
|
|
@@ -422,6 +643,29 @@ function parseEditInput(input) {
|
|
|
422
643
|
return { isEdit: false, initialContent: '' };
|
|
423
644
|
}
|
|
424
645
|
function renderTerminalShortcuts() {
|
|
646
|
+
const width = terminalContentWidth(88);
|
|
647
|
+
if (width < 44) {
|
|
648
|
+
const rows = [
|
|
649
|
+
`${ACCENT('Shortcuts')}`,
|
|
650
|
+
`${BORDER('─'.repeat(Math.min(width, 32)))}`,
|
|
651
|
+
`${DIM('Enter')} send ${DIM('Tab')} complete`,
|
|
652
|
+
`${DIM('Ctrl+C')} interrupt; twice exits`,
|
|
653
|
+
`${DIM('/paste')} ${DIM('/edit')} ${DIM('/resume')}`,
|
|
654
|
+
`${DIM('/last-tool')} ${DIM('/trace')}`,
|
|
655
|
+
];
|
|
656
|
+
return `\n${rows.join('\n')}\n`;
|
|
657
|
+
}
|
|
658
|
+
if (width < 72) {
|
|
659
|
+
const rows = [
|
|
660
|
+
`${ACCENT('Shortcuts')}`,
|
|
661
|
+
`${BORDER('─'.repeat(Math.min(width, 40)))}`,
|
|
662
|
+
`${DIM('Enter')} send ${DIM('Tab')} complete ${DIM('↑/↓')} history`,
|
|
663
|
+
`${DIM('Ctrl+U/W')} edit ${DIM('Ctrl+C')} interrupt; twice exits`,
|
|
664
|
+
`${DIM('/paste')} multiline ${DIM('/edit')} editor ${DIM('/resume')} sessions`,
|
|
665
|
+
`${DIM('/last-tool')} tool detail ${DIM('/trace')} timeline`,
|
|
666
|
+
];
|
|
667
|
+
return `\n${rows.join('\n')}\n`;
|
|
668
|
+
}
|
|
425
669
|
const rows = [
|
|
426
670
|
`${ACCENT('Terminal shortcuts')}`,
|
|
427
671
|
`${BORDER('─'.repeat(48))}`,
|
|
@@ -434,6 +678,8 @@ function renderTerminalShortcuts() {
|
|
|
434
678
|
`${DIM('/paste')} collect multiline input until /end`,
|
|
435
679
|
`${DIM('/edit')} open $VISUAL or $EDITOR for long input`,
|
|
436
680
|
`${DIM('/resume')} pick a session; use n/p to page results`,
|
|
681
|
+
`${DIM('/last-tool')} inspect the latest tool call/result`,
|
|
682
|
+
`${DIM('/trace')} show the ordered event timeline`,
|
|
437
683
|
];
|
|
438
684
|
return `\n${rows.join('\n')}\n`;
|
|
439
685
|
}
|
|
@@ -560,10 +806,7 @@ async function launchTerminalUI(runtime) {
|
|
|
560
806
|
return undefined;
|
|
561
807
|
}
|
|
562
808
|
confirmingTool = true;
|
|
563
|
-
|
|
564
|
-
const reason = event.request.reason ? ` ${DIM(event.request.reason)}` : '';
|
|
565
|
-
const suffix = detail ? ` ${DIM(detail)}` : '';
|
|
566
|
-
void editor.ask(`${ACCENT('?')} Allow tool ${ACCENT(event.request.name)}?${suffix}${reason} ${DIM('[y/N]')} `, event.request.abortSignal).then(answer => {
|
|
809
|
+
void editor.ask(formatTerminalPermissionPrompt(event.request, runtime.cwd), event.request.abortSignal).then(answer => {
|
|
567
810
|
agentController.handle({
|
|
568
811
|
type: 'permission_decision',
|
|
569
812
|
requestId: event.request.id,
|
|
@@ -588,6 +831,7 @@ async function launchTerminalUI(runtime) {
|
|
|
588
831
|
runtime,
|
|
589
832
|
eventSink,
|
|
590
833
|
uiCapabilities: (0, ui_events_1.resolveUiRendererCapabilities)(undefined, 'terminal'),
|
|
834
|
+
uiRenderer: 'terminal',
|
|
591
835
|
useRuntimeToolPermissions: true,
|
|
592
836
|
echoSubmittedInput: false,
|
|
593
837
|
beforeTurn: () => writer.write('\n'),
|
|
@@ -597,7 +841,7 @@ async function launchTerminalUI(runtime) {
|
|
|
597
841
|
},
|
|
598
842
|
onTurnError: error => {
|
|
599
843
|
const message = error instanceof Error ? error.message : String(error);
|
|
600
|
-
events.append({ role: 'error', content:
|
|
844
|
+
events.append({ role: 'error', content: message });
|
|
601
845
|
},
|
|
602
846
|
});
|
|
603
847
|
editor = new raw_editor_1.RawTerminalEditor({
|
|
@@ -676,7 +920,7 @@ async function launchTerminalUI(runtime) {
|
|
|
676
920
|
if (!stopping)
|
|
677
921
|
editor.start();
|
|
678
922
|
if (result.error) {
|
|
679
|
-
events.append({ role: 'error', content: `Editor failed: ${result.error}
|
|
923
|
+
events.append({ role: 'error', content: `Editor failed: ${result.error}`, errorLayer: 'renderer' });
|
|
680
924
|
prompt();
|
|
681
925
|
return;
|
|
682
926
|
}
|