agent-sh 0.7.0 → 0.9.0
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/README.md +28 -33
- package/dist/agent/agent-loop.d.ts +31 -8
- package/dist/agent/agent-loop.js +277 -66
- package/dist/agent/conversation-state.d.ts +41 -9
- package/dist/agent/conversation-state.js +340 -17
- package/dist/agent/history-file.d.ts +36 -0
- package/dist/agent/history-file.js +167 -0
- package/dist/agent/nuclear-form.d.ts +41 -0
- package/dist/agent/nuclear-form.js +176 -0
- package/dist/agent/system-prompt.d.ts +4 -5
- package/dist/agent/system-prompt.js +16 -11
- package/dist/agent/token-budget.d.ts +13 -0
- package/dist/agent/token-budget.js +50 -0
- package/dist/agent/tool-protocol.d.ts +83 -0
- package/dist/agent/tool-protocol.js +386 -0
- package/dist/agent/tools/user-shell.js +4 -1
- package/dist/agent/types.d.ts +21 -1
- package/dist/context-manager.d.ts +0 -1
- package/dist/context-manager.js +5 -110
- package/dist/core.d.ts +7 -7
- package/dist/core.js +76 -180
- package/dist/event-bus.d.ts +40 -0
- package/dist/event-bus.js +20 -1
- package/dist/extension-loader.d.ts +5 -0
- package/dist/extension-loader.js +104 -17
- package/dist/extensions/agent-backend.d.ts +13 -0
- package/dist/extensions/agent-backend.js +167 -0
- package/dist/extensions/command-suggest.d.ts +3 -3
- package/dist/extensions/command-suggest.js +4 -3
- package/dist/extensions/index.d.ts +19 -0
- package/dist/extensions/index.js +25 -0
- package/dist/extensions/slash-commands.d.ts +1 -1
- package/dist/extensions/slash-commands.js +44 -1
- package/dist/extensions/terminal-buffer.d.ts +1 -1
- package/dist/extensions/terminal-buffer.js +22 -8
- package/dist/extensions/tui-renderer.js +177 -122
- package/dist/index.js +14 -20
- package/dist/settings.d.ts +25 -2
- package/dist/settings.js +25 -4
- package/dist/{input-handler.d.ts → shell/input-handler.d.ts} +1 -1
- package/dist/{input-handler.js → shell/input-handler.js} +60 -43
- package/dist/{output-parser.d.ts → shell/output-parser.d.ts} +1 -1
- package/dist/{output-parser.js → shell/output-parser.js} +1 -1
- package/dist/{shell.d.ts → shell/shell.d.ts} +8 -2
- package/dist/{shell.js → shell/shell.js} +24 -6
- package/dist/types.d.ts +49 -32
- package/dist/utils/ansi.d.ts +10 -0
- package/dist/utils/ansi.js +27 -0
- package/dist/utils/compositor.d.ts +62 -0
- package/dist/utils/compositor.js +88 -0
- package/dist/utils/diff-renderer.js +92 -4
- package/dist/utils/floating-panel.d.ts +34 -3
- package/dist/utils/floating-panel.js +315 -82
- package/dist/utils/handler-registry.d.ts +26 -10
- package/dist/utils/handler-registry.js +52 -16
- package/dist/utils/line-editor.d.ts +32 -3
- package/dist/utils/line-editor.js +218 -36
- package/dist/utils/markdown.d.ts +1 -0
- package/dist/utils/markdown.js +4 -4
- package/dist/utils/message-utils.d.ts +35 -0
- package/dist/utils/message-utils.js +75 -0
- package/dist/utils/terminal-buffer.d.ts +9 -1
- package/dist/utils/terminal-buffer.js +31 -2
- package/dist/utils/tool-display.d.ts +1 -0
- package/dist/utils/tool-display.js +1 -1
- package/dist/utils/tool-interactive.d.ts +12 -0
- package/dist/utils/tool-interactive.js +53 -0
- package/examples/extensions/ash-acp-bridge/README.md +39 -0
- package/examples/extensions/ash-acp-bridge/package.json +23 -0
- package/examples/extensions/ash-acp-bridge/src/index.ts +571 -0
- package/examples/extensions/ash-acp-bridge/tsconfig.json +14 -0
- package/examples/extensions/ash-mcp-bridge/README.md +72 -0
- package/examples/extensions/ash-mcp-bridge/index.ts +154 -0
- package/examples/extensions/ash-mcp-bridge/package.json +9 -0
- package/examples/extensions/claude-code-bridge/index.ts +77 -1
- package/examples/extensions/interactive-prompts.ts +82 -110
- package/examples/extensions/overlay-agent.ts +84 -38
- package/examples/extensions/peer-mesh.ts +450 -0
- package/examples/extensions/pi-bridge/index.ts +87 -2
- package/examples/extensions/questionnaire.ts +249 -0
- package/examples/extensions/tmux-pane.ts +307 -0
- package/examples/extensions/web-access.ts +327 -0
- package/package.json +9 -1
- package/dist/extensions/overlay-agent.d.ts +0 -11
- package/dist/extensions/overlay-agent.js +0 -43
- package/examples/extensions/terminal-buffer.ts +0 -184
|
@@ -11,14 +11,13 @@
|
|
|
11
11
|
* can subscribe to the same events.
|
|
12
12
|
*/
|
|
13
13
|
import { highlight } from "cli-highlight";
|
|
14
|
-
import { MarkdownRenderer, wrapLine } from "../utils/markdown.js";
|
|
14
|
+
import { MarkdownRenderer, wrapLine, MAX_CONTENT_WIDTH } from "../utils/markdown.js";
|
|
15
15
|
import { createFencedBlockTransform } from "../utils/stream-transform.js";
|
|
16
16
|
import { palette as p } from "../utils/palette.js";
|
|
17
|
-
import { renderToolCall, createSpinner,
|
|
17
|
+
import { renderToolCall, createSpinner, formatElapsed, SPINNER_FRAMES, } from "../utils/tool-display.js";
|
|
18
18
|
import { renderDiff } from "../utils/diff-renderer.js";
|
|
19
19
|
import { renderBoxFrame } from "../utils/box-frame.js";
|
|
20
20
|
import { getSettings } from "../settings.js";
|
|
21
|
-
import { StdoutWriter } from "../utils/output-writer.js";
|
|
22
21
|
/** Encode a PNG buffer as a terminal inline image escape sequence. */
|
|
23
22
|
function encodeImageForTerminal(data) {
|
|
24
23
|
const b64 = data.toString("base64");
|
|
@@ -68,12 +67,78 @@ function createRenderState() {
|
|
|
68
67
|
};
|
|
69
68
|
}
|
|
70
69
|
export default function activate(ctx) {
|
|
71
|
-
const { bus,
|
|
72
|
-
const writer = new StdoutWriter();
|
|
70
|
+
const { bus, define, compositor } = ctx;
|
|
73
71
|
const s = createRenderState();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
/** Shorthand — get the current agent surface. */
|
|
73
|
+
function out() { return compositor.surface("agent"); }
|
|
74
|
+
/** Capped width for borders, tool lines, and content — keeps everything aligned. */
|
|
75
|
+
function cappedW() { return Math.min(MAX_CONTENT_WIDTH + 2, out().columns); }
|
|
76
|
+
// Gate: other extensions (e.g. overlay) can advise this to suppress
|
|
77
|
+
// TUI rendering of agent output while they own the display.
|
|
78
|
+
define("tui:should-render-agent", () => true);
|
|
79
|
+
function shouldRender() { return ctx.call("tui:should-render-agent"); }
|
|
80
|
+
// ── Advisable rendering handlers ───────────────────────────────
|
|
81
|
+
// Extensions advise these to customize how the TUI renders content.
|
|
82
|
+
// Each handler receives data and returns rendered strings.
|
|
83
|
+
define("tui:response-border", (position, width) => {
|
|
84
|
+
return `${p.dim}${p.accent}${"─".repeat(width)}${p.reset}`;
|
|
85
|
+
});
|
|
86
|
+
define("tui:response-start", () => { });
|
|
87
|
+
define("tui:response-end", (_hadToolCalls) => { });
|
|
88
|
+
define("tui:render-info", (message) => `${p.muted}${message}${p.reset}`);
|
|
89
|
+
define("tui:render-error", (message) => `${p.error}Error: ${message}${p.reset}`);
|
|
90
|
+
define("tui:render-usage", (promptTokens, completionTokens, maxTokens) => {
|
|
91
|
+
const ctxK = (promptTokens / 1000).toFixed(1);
|
|
92
|
+
const maxK = (maxTokens / 1000).toFixed(0);
|
|
93
|
+
const pct = Math.min(100, (promptTokens / maxTokens) * 100).toFixed(0);
|
|
94
|
+
return `${p.dim}⬆ ${promptTokens} ⬇ ${completionTokens} ctx: ${ctxK}k/${maxK}k (${pct}%)${p.reset}`;
|
|
95
|
+
});
|
|
96
|
+
define("tui:render-content-gap", (fromKind, toKind) => fromKind !== toKind ? "\n" : null);
|
|
97
|
+
define("tui:render-tool-complete", (exitCode, elapsed, summary) => {
|
|
98
|
+
const timer = elapsed ? ` ${p.dim}${elapsed}${p.reset}` : "";
|
|
99
|
+
const summaryStr = summary ? ` ${p.dim}${summary}${p.reset}` : "";
|
|
100
|
+
if (exitCode === null)
|
|
101
|
+
return `${p.muted}(timed out)${p.reset}`;
|
|
102
|
+
if (exitCode === 0)
|
|
103
|
+
return `${p.success}✓${p.reset}${summaryStr}${timer}`;
|
|
104
|
+
return `${p.error}✗ exit ${exitCode}${p.reset}${summaryStr}${timer}`;
|
|
105
|
+
});
|
|
106
|
+
define("tui:render-tool-group-summary", (count, rendered, allOk, summaries) => {
|
|
107
|
+
const mark = allOk ? `${p.success}✓${p.reset}` : `${p.error}✗${p.reset}`;
|
|
108
|
+
const summaryStr = summaries.length > 0 ? ` ${p.dim}${summaries.join(", ")}${p.reset}` : "";
|
|
109
|
+
const collapsed = count - rendered;
|
|
110
|
+
if (collapsed > 0) {
|
|
111
|
+
return ` ${p.muted}└${p.reset} ${p.dim}+${collapsed} more${p.reset} ${mark}${summaryStr}`;
|
|
112
|
+
}
|
|
113
|
+
return ` ${p.muted}└${p.reset} ${mark}${summaryStr}`;
|
|
114
|
+
});
|
|
115
|
+
define("tui:render-command-output", (line, _kind) => `${p.dim} ${line}${p.reset}`);
|
|
116
|
+
define("tui:render-spinner", (label, frame, elapsed, hint) => {
|
|
117
|
+
const timer = elapsed ? ` ${p.dim}${elapsed}${p.reset}` : "";
|
|
118
|
+
const hintStr = hint ? ` ${p.dim}${hint}${p.reset}` : "";
|
|
119
|
+
return `${p.accent}${frame} ${label}...${p.reset}${timer}${hintStr}`;
|
|
120
|
+
});
|
|
121
|
+
define("tui:render-user-query", (query, width, modelLabel) => {
|
|
122
|
+
const contentW = width - 4;
|
|
123
|
+
let lines = [];
|
|
124
|
+
for (const raw of query.split("\n")) {
|
|
125
|
+
for (const wrapped of wrapLine(`${p.accent}${raw}${p.reset}`, contentW)) {
|
|
126
|
+
lines.push(wrapped);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const MAX_QUERY_LINES = 20;
|
|
130
|
+
if (lines.length > MAX_QUERY_LINES) {
|
|
131
|
+
const overflow = lines.length - MAX_QUERY_LINES;
|
|
132
|
+
lines = [...lines.slice(0, MAX_QUERY_LINES), `${p.dim}… ${overflow} more lines${p.reset}`];
|
|
133
|
+
}
|
|
134
|
+
return renderBoxFrame(lines, {
|
|
135
|
+
width,
|
|
136
|
+
style: "rounded",
|
|
137
|
+
borderColor: p.accent,
|
|
138
|
+
title: `${p.accent}${p.bold}❯${p.reset}`,
|
|
139
|
+
titleRight: modelLabel,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
77
142
|
// Track backend/model info for display on response border
|
|
78
143
|
let backendInfo = null;
|
|
79
144
|
bus.on("agent:info", (info) => { backendInfo = info; });
|
|
@@ -88,12 +153,16 @@ export default function activate(ctx) {
|
|
|
88
153
|
});
|
|
89
154
|
// ── Event subscriptions ─────────────────────────────────────
|
|
90
155
|
bus.on("agent:query", (e) => {
|
|
156
|
+
if (!shouldRender())
|
|
157
|
+
return;
|
|
91
158
|
s.spinnerStartTime = 0;
|
|
92
159
|
showUserQuery(e.query);
|
|
93
160
|
startAgentResponse();
|
|
94
161
|
startThinkingSpinner();
|
|
95
162
|
});
|
|
96
163
|
bus.on("agent:thinking-chunk", (e) => {
|
|
164
|
+
if (!shouldRender())
|
|
165
|
+
return;
|
|
97
166
|
s.thinkingPending = true;
|
|
98
167
|
if (!s.isThinking) {
|
|
99
168
|
s.isThinking = true;
|
|
@@ -118,6 +187,8 @@ export default function activate(ctx) {
|
|
|
118
187
|
}
|
|
119
188
|
});
|
|
120
189
|
bus.on("agent:response-chunk", (e) => {
|
|
190
|
+
if (!shouldRender())
|
|
191
|
+
return;
|
|
121
192
|
const { blocks } = e;
|
|
122
193
|
// Inject spacing: append \n to text blocks that precede non-text blocks
|
|
123
194
|
for (let i = 0; i < blocks.length; i++) {
|
|
@@ -141,7 +212,7 @@ export default function activate(ctx) {
|
|
|
141
212
|
break;
|
|
142
213
|
case "raw":
|
|
143
214
|
flushForRaw();
|
|
144
|
-
|
|
215
|
+
out().write(block.escape);
|
|
145
216
|
break;
|
|
146
217
|
}
|
|
147
218
|
}
|
|
@@ -150,17 +221,14 @@ export default function activate(ctx) {
|
|
|
150
221
|
let pendingUsage = null;
|
|
151
222
|
bus.on("agent:usage", (e) => { pendingUsage = e; });
|
|
152
223
|
bus.on("agent:response-done", () => {
|
|
224
|
+
if (!shouldRender())
|
|
225
|
+
return;
|
|
153
226
|
s.isThinking = false;
|
|
154
227
|
if (pendingUsage && s.renderer) {
|
|
155
228
|
const { prompt_tokens, completion_tokens } = pendingUsage;
|
|
156
229
|
const maxTokens = backendInfo?.contextWindow ?? 128_000;
|
|
157
|
-
// prompt_tokens of the latest call = current context usage
|
|
158
|
-
// (it includes the full conversation history)
|
|
159
|
-
const ctxK = (prompt_tokens / 1000).toFixed(1);
|
|
160
|
-
const maxK = (maxTokens / 1000).toFixed(0);
|
|
161
|
-
const pct = Math.min(100, (prompt_tokens / maxTokens) * 100).toFixed(0);
|
|
162
230
|
s.renderer.writeLine("");
|
|
163
|
-
s.renderer.writeLine(
|
|
231
|
+
s.renderer.writeLine(ctx.call("tui:render-usage", prompt_tokens, completion_tokens, maxTokens));
|
|
164
232
|
drain();
|
|
165
233
|
pendingUsage = null;
|
|
166
234
|
}
|
|
@@ -173,6 +241,8 @@ export default function activate(ctx) {
|
|
|
173
241
|
// Batch groups: kind → { total, rendered, headerShown }
|
|
174
242
|
let batchGroups = new Map();
|
|
175
243
|
bus.on("agent:tool-batch", (e) => {
|
|
244
|
+
if (!shouldRender())
|
|
245
|
+
return;
|
|
176
246
|
fencedTransform.flush();
|
|
177
247
|
finalizeToolGroup();
|
|
178
248
|
batchGroups = new Map();
|
|
@@ -185,6 +255,8 @@ export default function activate(ctx) {
|
|
|
185
255
|
}
|
|
186
256
|
});
|
|
187
257
|
bus.on("agent:tool-started", (e) => {
|
|
258
|
+
if (!shouldRender())
|
|
259
|
+
return;
|
|
188
260
|
fencedTransform.flush();
|
|
189
261
|
stopCurrentSpinner();
|
|
190
262
|
s.currentToolKind = e.kind;
|
|
@@ -248,6 +320,8 @@ export default function activate(ctx) {
|
|
|
248
320
|
}
|
|
249
321
|
});
|
|
250
322
|
bus.on("agent:tool-completed", (e) => {
|
|
323
|
+
if (!shouldRender())
|
|
324
|
+
return;
|
|
251
325
|
s.toolExitCode = e.exitCode;
|
|
252
326
|
if (e.exitCode !== 0)
|
|
253
327
|
s.toolGroupAllOk = false;
|
|
@@ -265,20 +339,28 @@ export default function activate(ctx) {
|
|
|
265
339
|
startThinkingSpinner();
|
|
266
340
|
}
|
|
267
341
|
});
|
|
268
|
-
bus.on("agent:tool-output-chunk", (e) =>
|
|
269
|
-
|
|
342
|
+
bus.on("agent:tool-output-chunk", (e) => { if (shouldRender())
|
|
343
|
+
writeCommandOutput(e.chunk); });
|
|
344
|
+
bus.on("agent:tool-output", () => { if (shouldRender())
|
|
345
|
+
flushCommandOutput(); });
|
|
270
346
|
bus.on("agent:cancelled", () => {
|
|
347
|
+
if (!shouldRender())
|
|
348
|
+
return;
|
|
271
349
|
s.isThinking = false;
|
|
272
350
|
stopCurrentSpinner();
|
|
273
351
|
showInfo("(cancelled)");
|
|
274
352
|
endAgentResponse();
|
|
275
353
|
});
|
|
276
354
|
bus.on("agent:processing-done", () => {
|
|
355
|
+
if (!shouldRender())
|
|
356
|
+
return;
|
|
277
357
|
s.isThinking = false;
|
|
278
358
|
stopCurrentSpinner();
|
|
279
359
|
endAgentResponse();
|
|
280
360
|
});
|
|
281
361
|
bus.on("agent:error", (e) => {
|
|
362
|
+
if (!shouldRender())
|
|
363
|
+
return;
|
|
282
364
|
stopCurrentSpinner();
|
|
283
365
|
showCollapsedThinking();
|
|
284
366
|
if (!s.renderer)
|
|
@@ -289,6 +371,8 @@ export default function activate(ctx) {
|
|
|
289
371
|
drain();
|
|
290
372
|
});
|
|
291
373
|
bus.on("permission:request", (e) => {
|
|
374
|
+
if (!shouldRender())
|
|
375
|
+
return;
|
|
292
376
|
stopCurrentSpinner();
|
|
293
377
|
flushCommandOutput();
|
|
294
378
|
if (s.renderer) {
|
|
@@ -309,6 +393,8 @@ export default function activate(ctx) {
|
|
|
309
393
|
if (e.key === "\x14")
|
|
310
394
|
toggleThinkingDisplay(); // Ctrl+T
|
|
311
395
|
});
|
|
396
|
+
// Interactive tool UI — stop spinner while tool has control
|
|
397
|
+
bus.on("tool:interactive-start", () => { stopCurrentSpinner(); });
|
|
312
398
|
bus.on("ui:info", (e) => {
|
|
313
399
|
stopCurrentSpinner();
|
|
314
400
|
showInfo(e.message);
|
|
@@ -318,25 +404,27 @@ export default function activate(ctx) {
|
|
|
318
404
|
});
|
|
319
405
|
bus.on("ui:error", (e) => showError(e.message));
|
|
320
406
|
bus.on("ui:suggestion", (e) => {
|
|
321
|
-
|
|
407
|
+
compositor.surface("status").writeLine(`${p.dim}💡 ${e.text}${p.reset}`);
|
|
322
408
|
});
|
|
323
409
|
// ── Rendering functions ─────────────────────────────────────
|
|
324
410
|
function drain() {
|
|
325
411
|
if (!s.renderer)
|
|
326
412
|
return;
|
|
327
413
|
for (const line of s.renderer.drainLines()) {
|
|
328
|
-
|
|
414
|
+
out().write(line + "\n");
|
|
329
415
|
// Track whether we just emitted a blank line (for contentGap dedup).
|
|
330
416
|
// Lines from the renderer are indented (" "), so a blank line is " " or empty.
|
|
331
417
|
lastEmittedLineBlank = line.trimEnd() === "" || line.trimEnd().replace(/\x1b\[[^m]*m/g, "").trim() === "";
|
|
332
418
|
}
|
|
333
419
|
}
|
|
334
420
|
function startAgentResponse() {
|
|
335
|
-
s.renderer = new MarkdownRenderer(
|
|
421
|
+
s.renderer = new MarkdownRenderer(cappedW());
|
|
336
422
|
s.hadToolCalls = false;
|
|
337
|
-
|
|
338
|
-
|
|
423
|
+
const border = ctx.call("tui:response-border", "top", cappedW());
|
|
424
|
+
if (border)
|
|
425
|
+
s.renderer.writeLine(border);
|
|
339
426
|
drain();
|
|
427
|
+
ctx.call("tui:response-start");
|
|
340
428
|
}
|
|
341
429
|
/**
|
|
342
430
|
* Insert an empty line when transitioning between different content kinds
|
|
@@ -345,12 +433,15 @@ export default function activate(ctx) {
|
|
|
345
433
|
*/
|
|
346
434
|
let lastEmittedLineBlank = false;
|
|
347
435
|
function contentGap(kind) {
|
|
348
|
-
if (s.lastContentKind
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
436
|
+
if (s.lastContentKind) {
|
|
437
|
+
const gap = ctx.call("tui:render-content-gap", s.lastContentKind, kind);
|
|
438
|
+
if (gap) {
|
|
439
|
+
if (s.renderer) {
|
|
440
|
+
s.renderer.flush();
|
|
441
|
+
drain();
|
|
442
|
+
}
|
|
443
|
+
out().write(gap);
|
|
352
444
|
}
|
|
353
|
-
writer.write("\n");
|
|
354
445
|
}
|
|
355
446
|
s.lastContentKind = kind;
|
|
356
447
|
}
|
|
@@ -366,48 +457,18 @@ export default function activate(ctx) {
|
|
|
366
457
|
closeToolLine();
|
|
367
458
|
stopCurrentSpinner();
|
|
368
459
|
if (s.renderer) {
|
|
460
|
+
ctx.call("tui:response-end", s.hadToolCalls);
|
|
369
461
|
s.renderer.flush();
|
|
370
|
-
|
|
462
|
+
const border = ctx.call("tui:response-border", "bottom", cappedW());
|
|
463
|
+
if (border)
|
|
464
|
+
s.renderer.writeLine(border);
|
|
371
465
|
drain();
|
|
372
|
-
|
|
466
|
+
out().write("\n");
|
|
373
467
|
s.renderer = null;
|
|
374
468
|
}
|
|
375
469
|
}
|
|
376
470
|
function showUserQuery(query) {
|
|
377
|
-
const
|
|
378
|
-
const contentW = boxW - 4;
|
|
379
|
-
let lines = [];
|
|
380
|
-
for (const raw of query.split("\n")) {
|
|
381
|
-
if (raw.length <= contentW) {
|
|
382
|
-
lines.push(`${p.accent}${raw}${p.reset}`);
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
let remaining = raw;
|
|
386
|
-
while (remaining.length > contentW) {
|
|
387
|
-
let breakAt = remaining.lastIndexOf(" ", contentW);
|
|
388
|
-
if (breakAt <= 0)
|
|
389
|
-
breakAt = contentW;
|
|
390
|
-
lines.push(`${p.accent}${remaining.slice(0, breakAt)}${p.reset}`);
|
|
391
|
-
remaining = remaining.slice(breakAt).trimStart();
|
|
392
|
-
}
|
|
393
|
-
if (remaining)
|
|
394
|
-
lines.push(`${p.accent}${remaining}${p.reset}`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
// Truncate very long queries to keep the response visible
|
|
398
|
-
const MAX_QUERY_LINES = 20;
|
|
399
|
-
if (lines.length > MAX_QUERY_LINES) {
|
|
400
|
-
const overflow = lines.length - MAX_QUERY_LINES;
|
|
401
|
-
lines = [
|
|
402
|
-
...lines.slice(0, MAX_QUERY_LINES),
|
|
403
|
-
`${p.dim}… ${overflow} more lines${p.reset}`,
|
|
404
|
-
];
|
|
405
|
-
}
|
|
406
|
-
// Mode-specific border color and title
|
|
407
|
-
const borderColor = p.accent;
|
|
408
|
-
const title = `${p.accent}${p.bold}❯${p.reset}`;
|
|
409
|
-
// Backend/model label on the right (backend/model, highlighted)
|
|
410
|
-
const model = backendInfo?.model ?? llmClient?.model;
|
|
471
|
+
const model = backendInfo?.model;
|
|
411
472
|
const backend = backendInfo?.name;
|
|
412
473
|
let modelLabel;
|
|
413
474
|
if (backend && model) {
|
|
@@ -419,16 +480,13 @@ export default function activate(ctx) {
|
|
|
419
480
|
else if (backend) {
|
|
420
481
|
modelLabel = `${p.bold}${backend}${p.reset}`;
|
|
421
482
|
}
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
writer.write("\n");
|
|
430
|
-
for (const line of framed) {
|
|
431
|
-
writer.write(line + "\n");
|
|
483
|
+
const querySurface = compositor.surface("query");
|
|
484
|
+
const framed = ctx.call("tui:render-user-query", query, querySurface.columns, modelLabel);
|
|
485
|
+
if (framed.length > 0) {
|
|
486
|
+
querySurface.write("\n");
|
|
487
|
+
for (const line of framed) {
|
|
488
|
+
querySurface.writeLine(line);
|
|
489
|
+
}
|
|
432
490
|
}
|
|
433
491
|
}
|
|
434
492
|
function writeAgentText(text) {
|
|
@@ -439,7 +497,7 @@ export default function activate(ctx) {
|
|
|
439
497
|
s.isThinking = false;
|
|
440
498
|
if (s.showThinkingText && s.renderer) {
|
|
441
499
|
s.renderer.flush();
|
|
442
|
-
const w = Math.min(80,
|
|
500
|
+
const w = Math.min(80, out().columns);
|
|
443
501
|
s.renderer.writeLine(`${p.dim}${"─".repeat(w)}${p.reset}`);
|
|
444
502
|
drain();
|
|
445
503
|
}
|
|
@@ -478,7 +536,7 @@ export default function activate(ctx) {
|
|
|
478
536
|
drain();
|
|
479
537
|
});
|
|
480
538
|
function writeCodeBlock(language, code) {
|
|
481
|
-
ctx.call("render:code-block", language, code,
|
|
539
|
+
ctx.call("render:code-block", language, code, cappedW());
|
|
482
540
|
}
|
|
483
541
|
function flushForRaw() {
|
|
484
542
|
closeToolLine();
|
|
@@ -492,7 +550,7 @@ export default function activate(ctx) {
|
|
|
492
550
|
flushForRaw();
|
|
493
551
|
const escape = encodeImageForTerminal(data);
|
|
494
552
|
if (escape) {
|
|
495
|
-
|
|
553
|
+
out().write(" " + escape + "\n");
|
|
496
554
|
}
|
|
497
555
|
});
|
|
498
556
|
function writeInlineImage(data) {
|
|
@@ -520,7 +578,7 @@ export default function activate(ctx) {
|
|
|
520
578
|
function renderDiffBody(diff, filePath, width) {
|
|
521
579
|
if (diff.isIdentical)
|
|
522
580
|
return [];
|
|
523
|
-
const boxW = Math.min(120, width);
|
|
581
|
+
const boxW = Math.min(120, width - 2); // -2 for writeLine indent
|
|
524
582
|
const contentW = boxW - 4;
|
|
525
583
|
const diffLines = renderDiff(diff, {
|
|
526
584
|
width: contentW,
|
|
@@ -616,12 +674,12 @@ export default function activate(ctx) {
|
|
|
616
674
|
locations: extra?.locations,
|
|
617
675
|
rawInput: extra?.rawInput,
|
|
618
676
|
displayDetail: extra?.displayDetail,
|
|
619
|
-
},
|
|
677
|
+
}, cappedW());
|
|
620
678
|
if (extra?.groupContinuation && lines.length > 0) {
|
|
621
679
|
// Swap the colored kind icon for a muted tree connector,
|
|
622
680
|
// and strip the tool name prefix — show detail only.
|
|
623
681
|
const detail = extra.displayDetail || extractDetail(extra);
|
|
624
|
-
const maxW = Math.max(1,
|
|
682
|
+
const maxW = Math.max(1, cappedW() - 6);
|
|
625
683
|
const text = detail.length > maxW ? detail.slice(0, maxW - 1) + "…" : detail;
|
|
626
684
|
lines[0] = detail
|
|
627
685
|
? `${p.muted}├${p.reset} ${p.dim}${text}${p.reset}`
|
|
@@ -640,7 +698,7 @@ export default function activate(ctx) {
|
|
|
640
698
|
s.toolLineOpen = false;
|
|
641
699
|
}
|
|
642
700
|
else {
|
|
643
|
-
|
|
701
|
+
out().write(` ${batchPrefix}${lines[lines.length - 1]}`);
|
|
644
702
|
s.toolLineOpen = true;
|
|
645
703
|
}
|
|
646
704
|
}
|
|
@@ -653,15 +711,9 @@ export default function activate(ctx) {
|
|
|
653
711
|
return;
|
|
654
712
|
stopCurrentSpinner();
|
|
655
713
|
const elapsed = s.toolStartTime ? formatElapsed(Date.now() - s.toolStartTime) : "";
|
|
656
|
-
const
|
|
657
|
-
const summary = resultDisplay?.summary ? ` ${p.dim}${resultDisplay.summary}${p.reset}` : "";
|
|
658
|
-
const mark = exitCode === null
|
|
659
|
-
? `${p.muted}(timed out)${p.reset}`
|
|
660
|
-
: exitCode === 0
|
|
661
|
-
? `${p.success}✓${p.reset}${summary}${timer}`
|
|
662
|
-
: `${p.error}✗ exit ${exitCode}${p.reset}${summary}${timer}`;
|
|
714
|
+
const mark = ctx.call("tui:render-tool-complete", exitCode, elapsed, resultDisplay?.summary);
|
|
663
715
|
if (s.toolLineOpen && s.commandOutputLineCount === 0) {
|
|
664
|
-
|
|
716
|
+
out().write(` ${mark}\n`);
|
|
665
717
|
s.toolLineOpen = false;
|
|
666
718
|
}
|
|
667
719
|
else {
|
|
@@ -678,7 +730,7 @@ export default function activate(ctx) {
|
|
|
678
730
|
function renderResultBody(body) {
|
|
679
731
|
if (!s.renderer)
|
|
680
732
|
return;
|
|
681
|
-
const lines = ctx.call("render:result-body", body,
|
|
733
|
+
const lines = ctx.call("render:result-body", body, cappedW()) ?? [];
|
|
682
734
|
for (const line of lines) {
|
|
683
735
|
s.renderer.writeLine(line);
|
|
684
736
|
}
|
|
@@ -703,8 +755,11 @@ export default function activate(ctx) {
|
|
|
703
755
|
s.spinner = createSpinner({ startTime: s.spinnerStartTime });
|
|
704
756
|
s.spinnerInterval = setInterval(() => {
|
|
705
757
|
if (s.spinner) {
|
|
706
|
-
const
|
|
707
|
-
|
|
758
|
+
const frame = SPINNER_FRAMES[s.spinner.frame % SPINNER_FRAMES.length];
|
|
759
|
+
s.spinner.frame++;
|
|
760
|
+
const elapsed = formatElapsed(Date.now() - s.spinner.startTime);
|
|
761
|
+
const line = ctx.call("tui:render-spinner", s.spinnerLabel, frame, elapsed, s.spinnerOpts.hint);
|
|
762
|
+
out().write(`\r ${line}\x1b[K`);
|
|
708
763
|
}
|
|
709
764
|
}, 80);
|
|
710
765
|
}
|
|
@@ -714,13 +769,13 @@ export default function activate(ctx) {
|
|
|
714
769
|
s.spinnerInterval = null;
|
|
715
770
|
}
|
|
716
771
|
if (s.spinner) {
|
|
717
|
-
|
|
772
|
+
out().write("\r\x1b[2K");
|
|
718
773
|
s.spinner = null;
|
|
719
774
|
}
|
|
720
775
|
}
|
|
721
776
|
function closeToolLine() {
|
|
722
777
|
if (s.toolLineOpen) {
|
|
723
|
-
|
|
778
|
+
out().write("\n");
|
|
724
779
|
s.toolLineOpen = false;
|
|
725
780
|
}
|
|
726
781
|
}
|
|
@@ -737,20 +792,8 @@ export default function activate(ctx) {
|
|
|
737
792
|
closeToolLine();
|
|
738
793
|
if (!s.renderer)
|
|
739
794
|
startAgentResponse();
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
: `${p.error}✗${p.reset}`;
|
|
743
|
-
const summary = s.toolGroupSummaries.length > 0
|
|
744
|
-
? ` ${p.dim}${s.toolGroupSummaries.join(", ")}${p.reset}`
|
|
745
|
-
: "";
|
|
746
|
-
const collapsed = s.toolGroupCount - s.toolGroupRendered;
|
|
747
|
-
if (collapsed > 0) {
|
|
748
|
-
s.renderer.writeLine(` ${p.muted}└${p.reset} ${p.dim}+${collapsed} more${p.reset} ${mark}${summary}`);
|
|
749
|
-
}
|
|
750
|
-
else {
|
|
751
|
-
// All items visible — close the tree with └ mark + summary
|
|
752
|
-
s.renderer.writeLine(` ${p.muted}└${p.reset} ${mark}${summary}`);
|
|
753
|
-
}
|
|
795
|
+
const groupLine = ctx.call("tui:render-tool-group-summary", s.toolGroupCount, s.toolGroupRendered, s.toolGroupAllOk, s.toolGroupSummaries);
|
|
796
|
+
s.renderer.writeLine(groupLine);
|
|
754
797
|
drain();
|
|
755
798
|
s.toolGroupKind = undefined;
|
|
756
799
|
s.toolGroupCount = 0;
|
|
@@ -758,6 +801,9 @@ export default function activate(ctx) {
|
|
|
758
801
|
s.toolGroupRendered = 0;
|
|
759
802
|
s.toolGroupSummaries = [];
|
|
760
803
|
}
|
|
804
|
+
function renderCommandLine(line) {
|
|
805
|
+
return ctx.call("tui:render-command-output", line, s.currentToolKind);
|
|
806
|
+
}
|
|
761
807
|
function writeCommandOutput(chunk) {
|
|
762
808
|
if (!s.renderer)
|
|
763
809
|
return;
|
|
@@ -770,7 +816,7 @@ export default function activate(ctx) {
|
|
|
770
816
|
s.commandOutputBuffer = lines.pop();
|
|
771
817
|
for (const line of lines) {
|
|
772
818
|
if (s.commandOutputLineCount < maxLines) {
|
|
773
|
-
s.renderer.writeLine(
|
|
819
|
+
s.renderer.writeLine(renderCommandLine(line));
|
|
774
820
|
s.commandOutputLineCount++;
|
|
775
821
|
}
|
|
776
822
|
else {
|
|
@@ -790,7 +836,7 @@ export default function activate(ctx) {
|
|
|
790
836
|
: getSettings().maxCommandOutputLines;
|
|
791
837
|
if (s.commandOutputBuffer) {
|
|
792
838
|
if (s.commandOutputLineCount < maxLines) {
|
|
793
|
-
s.renderer.writeLine(
|
|
839
|
+
s.renderer.writeLine(renderCommandLine(s.commandOutputBuffer));
|
|
794
840
|
s.commandOutputLineCount++;
|
|
795
841
|
}
|
|
796
842
|
else {
|
|
@@ -805,14 +851,22 @@ export default function activate(ctx) {
|
|
|
805
851
|
const tail = s.commandOverflowLines.slice(-FAIL_OVERFLOW_MAX);
|
|
806
852
|
const skipped = s.commandOverflowLines.length - tail.length;
|
|
807
853
|
if (skipped > 0) {
|
|
808
|
-
s.renderer.writeLine(
|
|
854
|
+
s.renderer.writeLine(renderCommandLine(`… ${skipped} lines hidden`));
|
|
809
855
|
}
|
|
810
856
|
for (const line of tail) {
|
|
811
|
-
s.renderer.writeLine(
|
|
857
|
+
s.renderer.writeLine(renderCommandLine(line));
|
|
812
858
|
}
|
|
813
859
|
}
|
|
814
860
|
else if (s.commandOutputOverflow > 0 && maxLines > 0) {
|
|
815
|
-
|
|
861
|
+
// Show last line of output so the user sees the tail (often the most useful part)
|
|
862
|
+
const tail = s.commandOverflowLines[s.commandOverflowLines.length - 1];
|
|
863
|
+
const hidden = tail ? s.commandOutputOverflow - 1 : s.commandOutputOverflow;
|
|
864
|
+
if (hidden > 0) {
|
|
865
|
+
s.renderer.writeLine(renderCommandLine(`… ${hidden} more lines`));
|
|
866
|
+
}
|
|
867
|
+
if (tail) {
|
|
868
|
+
s.renderer.writeLine(renderCommandLine(tail));
|
|
869
|
+
}
|
|
816
870
|
}
|
|
817
871
|
s.commandOutputOverflow = 0;
|
|
818
872
|
s.commandOverflowLines = [];
|
|
@@ -829,7 +883,7 @@ export default function activate(ctx) {
|
|
|
829
883
|
if (diff.isIdentical)
|
|
830
884
|
return;
|
|
831
885
|
contentGap("diff");
|
|
832
|
-
const lines = ctx.call("render:result-body", { kind: "diff", diff, filePath },
|
|
886
|
+
const lines = ctx.call("render:result-body", { kind: "diff", diff, filePath }, cappedW()) ?? [];
|
|
833
887
|
if (!s.renderer)
|
|
834
888
|
startAgentResponse();
|
|
835
889
|
for (const line of lines) {
|
|
@@ -848,7 +902,7 @@ export default function activate(ctx) {
|
|
|
848
902
|
}
|
|
849
903
|
if (!entry.expandedLines) {
|
|
850
904
|
const { filePath, diff } = entry;
|
|
851
|
-
const boxW = Math.min(
|
|
905
|
+
const boxW = Math.min(cappedW() - 2, out().columns - 2); // -2 for writeLine indent
|
|
852
906
|
const contentW = boxW - 4;
|
|
853
907
|
const diffLines = renderDiff(diff, {
|
|
854
908
|
width: contentW,
|
|
@@ -865,16 +919,16 @@ export default function activate(ctx) {
|
|
|
865
919
|
footer: [` ${p.dim}ctrl+o to collapse${p.reset}`],
|
|
866
920
|
});
|
|
867
921
|
}
|
|
868
|
-
|
|
922
|
+
out().write("\n");
|
|
869
923
|
for (const line of entry.expandedLines) {
|
|
870
|
-
|
|
924
|
+
out().write(line + "\n");
|
|
871
925
|
}
|
|
872
926
|
}
|
|
873
927
|
function showFileDiffCached(entry) {
|
|
874
|
-
const lines = ctx.call("render:result-body", { kind: "diff", diff: entry.diff, filePath: entry.filePath },
|
|
875
|
-
|
|
928
|
+
const lines = ctx.call("render:result-body", { kind: "diff", diff: entry.diff, filePath: entry.filePath }, cappedW()) ?? [];
|
|
929
|
+
out().write("\n");
|
|
876
930
|
for (const line of lines) {
|
|
877
|
-
|
|
931
|
+
out().write(line + "\n");
|
|
878
932
|
}
|
|
879
933
|
}
|
|
880
934
|
function toggleThinkingDisplay() {
|
|
@@ -906,7 +960,7 @@ export default function activate(ctx) {
|
|
|
906
960
|
else {
|
|
907
961
|
if (s.renderer) {
|
|
908
962
|
s.renderer.flush();
|
|
909
|
-
const w = Math.min(80,
|
|
963
|
+
const w = Math.min(80, out().columns);
|
|
910
964
|
s.renderer.writeLine(`${p.dim}${"─".repeat(w)}${p.reset}`);
|
|
911
965
|
drain();
|
|
912
966
|
}
|
|
@@ -914,9 +968,10 @@ export default function activate(ctx) {
|
|
|
914
968
|
}
|
|
915
969
|
}
|
|
916
970
|
function showError(message) {
|
|
917
|
-
|
|
971
|
+
const s = compositor.surface("status");
|
|
972
|
+
s.write("\n" + ctx.call("tui:render-error", message) + "\n");
|
|
918
973
|
}
|
|
919
974
|
function showInfo(message) {
|
|
920
|
-
|
|
975
|
+
compositor.surface("status").writeLine(ctx.call("tui:render-info", message));
|
|
921
976
|
}
|
|
922
977
|
}
|