agent-sh 0.3.0 → 0.4.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 -11
- package/dist/acp-client.d.ts +6 -1
- package/dist/acp-client.js +68 -24
- package/dist/core.js +12 -2
- package/dist/event-bus.d.ts +26 -0
- package/dist/event-bus.js +10 -0
- package/dist/extensions/tui-renderer.d.ts +1 -1
- package/dist/extensions/tui-renderer.js +325 -165
- package/dist/index.js +44 -16
- package/dist/input-handler.d.ts +17 -8
- package/dist/input-handler.js +79 -39
- package/dist/settings.d.ts +11 -0
- package/dist/settings.js +19 -1
- package/dist/shell.js +3 -1
- package/dist/types.d.ts +28 -0
- package/dist/utils/box-frame.js +2 -1
- package/dist/utils/diff-renderer.js +1 -1
- package/dist/utils/frame-renderer.d.ts +26 -0
- package/dist/utils/frame-renderer.js +76 -0
- package/dist/utils/handler-registry.d.ts +41 -0
- package/dist/utils/handler-registry.js +52 -0
- package/dist/utils/line-editor.js +4 -0
- package/dist/utils/markdown.d.ts +15 -6
- package/dist/utils/markdown.js +106 -67
- package/dist/utils/output-writer.d.ts +22 -0
- package/dist/utils/output-writer.js +29 -0
- package/dist/utils/stream-transform.d.ts +70 -0
- package/dist/utils/stream-transform.js +229 -0
- package/dist/utils/tool-display.d.ts +9 -8
- package/dist/utils/tool-display.js +26 -31
- package/examples/extensions/latex-images.ts +142 -0
- package/package.json +10 -2
|
@@ -10,111 +10,190 @@
|
|
|
10
10
|
* silently dropped. Alternative renderers (web UI, logging, minimal)
|
|
11
11
|
* can subscribe to the same events.
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
13
|
+
import { highlight } from "cli-highlight";
|
|
14
|
+
import { MarkdownRenderer, wrapLine } from "../utils/markdown.js";
|
|
15
|
+
import { createFencedBlockTransform } from "../utils/stream-transform.js";
|
|
14
16
|
import { palette as p } from "../utils/palette.js";
|
|
15
|
-
import { renderToolCall,
|
|
17
|
+
import { renderToolCall, createSpinner, renderSpinnerLine, } from "../utils/tool-display.js";
|
|
16
18
|
import { renderDiff } from "../utils/diff-renderer.js";
|
|
17
19
|
import { renderBoxFrame } from "../utils/box-frame.js";
|
|
18
20
|
import { getSettings } from "../settings.js";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
import { StdoutWriter } from "../utils/output-writer.js";
|
|
22
|
+
/** Encode a PNG buffer as a terminal inline image escape sequence. */
|
|
23
|
+
function encodeImageForTerminal(data) {
|
|
24
|
+
const b64 = data.toString("base64");
|
|
25
|
+
if (process.env.TERM_PROGRAM === "iTerm.app" || process.env.TERM_PROGRAM === "WezTerm") {
|
|
26
|
+
return `\x1b]1337;File=inline=1;size=${data.length};preserveAspectRatio=1:${b64}\x07`;
|
|
27
|
+
}
|
|
28
|
+
if (process.env.KITTY_WINDOW_ID || process.env.TERM_PROGRAM === "ghostty") {
|
|
29
|
+
const chunks = [];
|
|
30
|
+
for (let i = 0; i < b64.length; i += 4096) {
|
|
31
|
+
const chunk = b64.slice(i, i + 4096);
|
|
32
|
+
const isLast = i + 4096 >= b64.length;
|
|
33
|
+
chunks.push(i === 0
|
|
34
|
+
? `\x1b_Gf=100,t=d,a=T,m=${isLast ? 0 : 1};${chunk}\x1b\\`
|
|
35
|
+
: `\x1b_Gm=${isLast ? 0 : 1};${chunk}\x1b\\`);
|
|
36
|
+
}
|
|
37
|
+
return chunks.join("");
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
function createRenderState() {
|
|
42
|
+
return {
|
|
43
|
+
renderer: null,
|
|
44
|
+
hadToolCalls: false,
|
|
45
|
+
spinner: null,
|
|
46
|
+
spinnerLabel: "",
|
|
47
|
+
spinnerOpts: {},
|
|
48
|
+
spinnerInterval: null,
|
|
49
|
+
spinnerStartTime: 0,
|
|
50
|
+
lastCommand: "",
|
|
51
|
+
toolLineOpen: false,
|
|
52
|
+
currentToolKind: undefined,
|
|
53
|
+
commandOutputBuffer: "",
|
|
54
|
+
commandOutputLineCount: 0,
|
|
55
|
+
commandOutputOverflow: 0,
|
|
56
|
+
isThinking: false,
|
|
57
|
+
showThinkingText: false,
|
|
58
|
+
thinkingPending: false,
|
|
59
|
+
lastTruncatedDiff: null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export default function activate(ctx) {
|
|
63
|
+
const { bus, getAcpClient, define } = ctx;
|
|
64
|
+
const writer = new StdoutWriter();
|
|
65
|
+
const s = createRenderState();
|
|
66
|
+
// ── Register fenced block transform (code blocks → ContentBlock) ──
|
|
67
|
+
// Nobody is special — tui-renderer uses the same primitive as any extension.
|
|
68
|
+
const fencedTransform = createFencedBlockTransform(bus, {
|
|
69
|
+
open: /^```(\w*)\s*$/,
|
|
70
|
+
close: /^```\s*$/,
|
|
71
|
+
transform(match, content) {
|
|
72
|
+
return { type: "code-block", language: match[1] || "", code: content };
|
|
73
|
+
},
|
|
74
|
+
});
|
|
33
75
|
// ── Event subscriptions ─────────────────────────────────────
|
|
34
76
|
bus.on("agent:query", (e) => {
|
|
35
|
-
spinnerStartTime = 0;
|
|
36
|
-
showUserQuery(e.query);
|
|
77
|
+
s.spinnerStartTime = 0;
|
|
78
|
+
showUserQuery(e.query, e.modeLabel);
|
|
37
79
|
startAgentResponse();
|
|
38
80
|
startThinkingSpinner();
|
|
39
81
|
});
|
|
40
82
|
bus.on("agent:thinking-chunk", (e) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
83
|
+
s.thinkingPending = true;
|
|
84
|
+
if (!s.isThinking) {
|
|
85
|
+
s.isThinking = true;
|
|
86
|
+
if (s.showThinkingText) {
|
|
44
87
|
stopCurrentSpinner();
|
|
45
|
-
if (!renderer)
|
|
88
|
+
if (!s.renderer)
|
|
46
89
|
startAgentResponse();
|
|
47
|
-
renderer.writeLine(`${p.dim}Thinking (ctrl+t to collapse)${p.reset}`);
|
|
90
|
+
s.renderer.writeLine(`${p.dim}Thinking (ctrl+t to collapse)${p.reset}`);
|
|
91
|
+
drain();
|
|
48
92
|
}
|
|
49
93
|
else {
|
|
50
94
|
// Restart spinner with ctrl+t hint now that we know thinking is available
|
|
51
95
|
startThinkingSpinner();
|
|
52
96
|
}
|
|
53
97
|
}
|
|
54
|
-
if (showThinkingText && e.text) {
|
|
55
|
-
|
|
98
|
+
if (s.showThinkingText && e.text) {
|
|
99
|
+
s.thinkingPending = false;
|
|
100
|
+
if (!s.renderer)
|
|
56
101
|
startAgentResponse();
|
|
57
|
-
renderer.push(`${p.dim}${e.text}${p.reset}`);
|
|
58
|
-
|
|
102
|
+
s.renderer.push(`${p.dim}${e.text}${p.reset}`);
|
|
103
|
+
drain();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
bus.on("agent:response-chunk", (e) => {
|
|
107
|
+
if (e.blocks) {
|
|
108
|
+
// Inject spacing: append \n to text blocks that precede non-text blocks
|
|
109
|
+
const blocks = e.blocks;
|
|
110
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
111
|
+
const block = blocks[i];
|
|
112
|
+
const next = blocks[i + 1];
|
|
113
|
+
if (block.type === "text" && next && next.type !== "text") {
|
|
114
|
+
block.text += "\n";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const block of blocks) {
|
|
118
|
+
switch (block.type) {
|
|
119
|
+
case "text":
|
|
120
|
+
if (block.text)
|
|
121
|
+
writeAgentText(block.text);
|
|
122
|
+
break;
|
|
123
|
+
case "code-block":
|
|
124
|
+
writeCodeBlock(block.language, block.code);
|
|
125
|
+
break;
|
|
126
|
+
case "image":
|
|
127
|
+
writeInlineImage(block.data);
|
|
128
|
+
break;
|
|
129
|
+
case "raw":
|
|
130
|
+
flushForRaw();
|
|
131
|
+
writer.write(block.escape);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
writeAgentText(e.text);
|
|
59
138
|
}
|
|
60
139
|
});
|
|
61
|
-
bus.on("agent:response-chunk", (e) => writeAgentText(e.text));
|
|
62
140
|
bus.on("agent:response-done", () => {
|
|
63
|
-
isThinking = false;
|
|
141
|
+
s.isThinking = false;
|
|
64
142
|
endAgentResponse();
|
|
65
143
|
});
|
|
66
144
|
bus.on("agent:tool-call", (e) => {
|
|
67
|
-
lastCommand = e.tool;
|
|
145
|
+
s.lastCommand = e.tool;
|
|
68
146
|
});
|
|
69
147
|
bus.on("agent:tool-started", (e) => {
|
|
148
|
+
fencedTransform.flush();
|
|
70
149
|
stopCurrentSpinner();
|
|
71
|
-
currentToolKind = e.kind;
|
|
150
|
+
s.currentToolKind = e.kind;
|
|
72
151
|
if (e.title === "user_shell") {
|
|
73
|
-
// Minimal annotation — PTY echo will show the output
|
|
74
152
|
closeToolLine();
|
|
75
|
-
if (!renderer)
|
|
153
|
+
if (!s.renderer)
|
|
76
154
|
startAgentResponse();
|
|
77
|
-
renderer.flush();
|
|
155
|
+
s.renderer.flush();
|
|
78
156
|
const cmd = e.rawInput?.command || "";
|
|
79
|
-
renderer.writeLine(`${p.dim}▶ user_shell: ${cmd}${p.reset}`);
|
|
80
|
-
|
|
157
|
+
s.renderer.writeLine(`${p.dim}▶ user_shell: ${cmd}${p.reset}`);
|
|
158
|
+
drain();
|
|
159
|
+
s.hadToolCalls = true;
|
|
81
160
|
}
|
|
82
161
|
else {
|
|
83
|
-
showToolCall(e.title, lastCommand, e);
|
|
162
|
+
showToolCall(e.title, s.lastCommand, e);
|
|
84
163
|
}
|
|
85
|
-
lastCommand = "";
|
|
164
|
+
s.lastCommand = "";
|
|
86
165
|
});
|
|
87
166
|
bus.on("agent:tool-completed", (e) => {
|
|
88
167
|
showToolComplete(e.exitCode);
|
|
89
|
-
currentToolKind = undefined;
|
|
90
|
-
spinnerStartTime = 0;
|
|
168
|
+
s.currentToolKind = undefined;
|
|
169
|
+
s.spinnerStartTime = 0;
|
|
91
170
|
startThinkingSpinner();
|
|
92
171
|
});
|
|
93
172
|
bus.on("agent:tool-output-chunk", (e) => writeCommandOutput(e.chunk));
|
|
94
173
|
bus.on("agent:tool-output", () => flushCommandOutput());
|
|
95
174
|
bus.on("agent:cancelled", () => {
|
|
96
|
-
isThinking = false;
|
|
175
|
+
s.isThinking = false;
|
|
97
176
|
stopCurrentSpinner();
|
|
98
177
|
showInfo("(cancelled)");
|
|
99
178
|
endAgentResponse();
|
|
100
179
|
});
|
|
101
180
|
bus.on("agent:processing-done", () => {
|
|
102
|
-
isThinking = false;
|
|
181
|
+
s.isThinking = false;
|
|
103
182
|
stopCurrentSpinner();
|
|
104
183
|
endAgentResponse();
|
|
105
184
|
});
|
|
106
185
|
bus.on("agent:error", (e) => showError(e.message));
|
|
107
|
-
// Flush rendering state and show inline diff for file writes
|
|
108
186
|
bus.on("permission:request", (e) => {
|
|
109
187
|
stopCurrentSpinner();
|
|
110
188
|
flushCommandOutput();
|
|
111
|
-
renderer
|
|
189
|
+
if (s.renderer) {
|
|
190
|
+
s.renderer.flush();
|
|
191
|
+
drain();
|
|
192
|
+
}
|
|
112
193
|
if (e.kind === "file-write" && e.metadata?.diff) {
|
|
113
194
|
showFileDiff(e.title, e.metadata.diff);
|
|
114
195
|
}
|
|
115
196
|
else {
|
|
116
|
-
// Non-file permission (e.g. tool-call) — end response box
|
|
117
|
-
// so interactive extensions can render their own UI
|
|
118
197
|
endAgentResponse();
|
|
119
198
|
}
|
|
120
199
|
});
|
|
@@ -127,39 +206,46 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
127
206
|
bus.on("ui:info", (e) => showInfo(e.message));
|
|
128
207
|
bus.on("ui:error", (e) => showError(e.message));
|
|
129
208
|
// ── Rendering functions ─────────────────────────────────────
|
|
130
|
-
function
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
catch { }
|
|
209
|
+
function drain() {
|
|
210
|
+
if (!s.renderer)
|
|
211
|
+
return;
|
|
212
|
+
for (const line of s.renderer.drainLines()) {
|
|
213
|
+
writer.write(line + "\n");
|
|
136
214
|
}
|
|
137
215
|
}
|
|
138
216
|
function startAgentResponse() {
|
|
139
|
-
renderer = new MarkdownRenderer();
|
|
140
|
-
hadToolCalls = false;
|
|
141
|
-
renderer.printTopBorder();
|
|
217
|
+
s.renderer = new MarkdownRenderer(writer.columns);
|
|
218
|
+
s.hadToolCalls = false;
|
|
219
|
+
s.renderer.printTopBorder();
|
|
220
|
+
drain();
|
|
221
|
+
}
|
|
222
|
+
function showCollapsedThinking() {
|
|
223
|
+
if (s.thinkingPending && !s.showThinkingText) {
|
|
224
|
+
if (!s.renderer)
|
|
225
|
+
startAgentResponse();
|
|
226
|
+
s.renderer.writeLine(`${p.muted}… thinking${p.reset}`);
|
|
227
|
+
s.thinkingPending = false;
|
|
228
|
+
}
|
|
142
229
|
}
|
|
143
230
|
function endAgentResponse() {
|
|
144
231
|
closeToolLine();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
renderer.
|
|
148
|
-
renderer
|
|
232
|
+
stopCurrentSpinner();
|
|
233
|
+
if (s.renderer) {
|
|
234
|
+
s.renderer.flush();
|
|
235
|
+
s.renderer.printBottomBorder();
|
|
236
|
+
drain();
|
|
237
|
+
s.renderer = null;
|
|
149
238
|
}
|
|
150
239
|
}
|
|
151
|
-
function showUserQuery(query) {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
const contentW = boxW - 4; // inside box padding
|
|
155
|
-
// Wrap long queries to fit within box
|
|
240
|
+
function showUserQuery(query, modeLabel) {
|
|
241
|
+
const boxW = Math.min(84, writer.columns);
|
|
242
|
+
const contentW = boxW - 4;
|
|
156
243
|
const lines = [];
|
|
157
244
|
for (const raw of query.split("\n")) {
|
|
158
245
|
if (raw.length <= contentW) {
|
|
159
246
|
lines.push(`${p.accent}${raw}${p.reset}`);
|
|
160
247
|
}
|
|
161
248
|
else {
|
|
162
|
-
// Simple word wrap
|
|
163
249
|
let remaining = raw;
|
|
164
250
|
while (remaining.length > contentW) {
|
|
165
251
|
let breakAt = remaining.lastIndexOf(" ", contentW);
|
|
@@ -172,82 +258,138 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
172
258
|
lines.push(`${p.accent}${remaining}${p.reset}`);
|
|
173
259
|
}
|
|
174
260
|
}
|
|
261
|
+
// Mode-specific border color and title
|
|
262
|
+
const isExecute = modeLabel === "Execute";
|
|
263
|
+
const borderColor = isExecute ? p.success : p.accent;
|
|
264
|
+
const title = modeLabel
|
|
265
|
+
? `${borderColor}${p.bold} ${modeLabel} ${p.reset}`
|
|
266
|
+
: `${p.accent}${p.bold}❯${p.reset}`;
|
|
175
267
|
const framed = renderBoxFrame(lines, {
|
|
176
268
|
width: boxW,
|
|
177
269
|
style: "rounded",
|
|
178
|
-
borderColor
|
|
179
|
-
title
|
|
270
|
+
borderColor,
|
|
271
|
+
title,
|
|
180
272
|
});
|
|
181
|
-
|
|
273
|
+
writer.write("\n");
|
|
182
274
|
for (const line of framed) {
|
|
183
|
-
|
|
275
|
+
writer.write(line + "\n");
|
|
184
276
|
}
|
|
185
277
|
}
|
|
186
278
|
function writeAgentText(text) {
|
|
187
279
|
closeToolLine();
|
|
188
|
-
const needsGap = hadToolCalls;
|
|
189
|
-
hadToolCalls = false;
|
|
190
|
-
if (isThinking) {
|
|
191
|
-
isThinking = false;
|
|
192
|
-
if (showThinkingText && renderer) {
|
|
193
|
-
renderer.flush();
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
280
|
+
const needsGap = s.hadToolCalls;
|
|
281
|
+
s.hadToolCalls = false;
|
|
282
|
+
if (s.isThinking) {
|
|
283
|
+
s.isThinking = false;
|
|
284
|
+
if (s.showThinkingText && s.renderer) {
|
|
285
|
+
s.renderer.flush();
|
|
286
|
+
const w = Math.min(80, writer.columns);
|
|
287
|
+
s.renderer.writeLine(`${p.dim}${"─".repeat(w)}${p.reset}`);
|
|
288
|
+
drain();
|
|
197
289
|
}
|
|
198
290
|
}
|
|
291
|
+
showCollapsedThinking();
|
|
199
292
|
stopCurrentSpinner();
|
|
200
|
-
if (!renderer)
|
|
293
|
+
if (!s.renderer)
|
|
201
294
|
startAgentResponse();
|
|
202
295
|
if (needsGap)
|
|
203
|
-
|
|
204
|
-
renderer.push(text);
|
|
205
|
-
|
|
296
|
+
writer.write("\n");
|
|
297
|
+
s.renderer.push(text);
|
|
298
|
+
drain();
|
|
299
|
+
}
|
|
300
|
+
define("render:code-block", (language, code, width) => {
|
|
301
|
+
flushForRaw();
|
|
302
|
+
if (language) {
|
|
303
|
+
s.renderer.writeLine(`${p.dim}${language}${p.reset}`);
|
|
304
|
+
}
|
|
305
|
+
let highlighted;
|
|
306
|
+
if (!language) {
|
|
307
|
+
// No language specified — render as plain text to avoid false syntax detection
|
|
308
|
+
highlighted = code;
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
try {
|
|
312
|
+
highlighted = highlight(code, { language });
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
highlighted = `${p.success}${code}${p.reset}`;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const contentWidth = Math.min(90, width - 2);
|
|
319
|
+
for (const line of highlighted.split("\n")) {
|
|
320
|
+
const indented = ` ${line}`;
|
|
321
|
+
const wrapped = wrapLine(indented, contentWidth);
|
|
322
|
+
for (const wl of wrapped) {
|
|
323
|
+
s.renderer.writeLine(wl);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
drain();
|
|
327
|
+
});
|
|
328
|
+
function writeCodeBlock(language, code) {
|
|
329
|
+
ctx.call("render:code-block", language, code, writer.columns);
|
|
330
|
+
}
|
|
331
|
+
function flushForRaw() {
|
|
332
|
+
closeToolLine();
|
|
333
|
+
stopCurrentSpinner();
|
|
334
|
+
if (!s.renderer)
|
|
335
|
+
startAgentResponse();
|
|
336
|
+
s.renderer.flush();
|
|
337
|
+
drain();
|
|
338
|
+
}
|
|
339
|
+
define("render:image", (data) => {
|
|
340
|
+
flushForRaw();
|
|
341
|
+
const escape = encodeImageForTerminal(data);
|
|
342
|
+
if (escape) {
|
|
343
|
+
writer.write(" " + escape + "\n");
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
function writeInlineImage(data) {
|
|
347
|
+
ctx.call("render:image", data);
|
|
206
348
|
}
|
|
207
349
|
function showToolCall(title, command, extra) {
|
|
208
350
|
closeToolLine();
|
|
209
351
|
stopCurrentSpinner();
|
|
210
|
-
if (!renderer)
|
|
352
|
+
if (!s.renderer)
|
|
211
353
|
startAgentResponse();
|
|
212
|
-
|
|
213
|
-
|
|
354
|
+
showCollapsedThinking();
|
|
355
|
+
s.renderer.flush();
|
|
356
|
+
drain();
|
|
214
357
|
const lines = renderToolCall({
|
|
215
358
|
title,
|
|
216
359
|
command: command || undefined,
|
|
217
360
|
kind: extra?.kind,
|
|
218
361
|
locations: extra?.locations,
|
|
219
362
|
rawInput: extra?.rawInput,
|
|
220
|
-
},
|
|
221
|
-
// Write all lines except the last normally, write last without \n
|
|
363
|
+
}, writer.columns);
|
|
222
364
|
for (let i = 0; i < lines.length - 1; i++) {
|
|
223
|
-
renderer.writeLine(lines[i]);
|
|
365
|
+
s.renderer.writeLine(lines[i]);
|
|
224
366
|
}
|
|
367
|
+
drain();
|
|
225
368
|
if (lines.length > 0) {
|
|
226
|
-
|
|
227
|
-
toolLineOpen = true;
|
|
369
|
+
writer.write(` ${lines[lines.length - 1]}`);
|
|
370
|
+
s.toolLineOpen = true;
|
|
228
371
|
}
|
|
229
|
-
hadToolCalls = true;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
commandOutputOverflow = 0;
|
|
372
|
+
s.hadToolCalls = true;
|
|
373
|
+
s.commandOutputLineCount = 0;
|
|
374
|
+
s.commandOutputOverflow = 0;
|
|
233
375
|
}
|
|
234
376
|
function showToolComplete(exitCode) {
|
|
235
|
-
if (!renderer)
|
|
377
|
+
if (!s.renderer)
|
|
236
378
|
return;
|
|
237
379
|
const mark = exitCode === null
|
|
238
380
|
? `${p.muted}(timed out)${p.reset}`
|
|
239
381
|
: exitCode === 0
|
|
240
382
|
? `${p.success}✓${p.reset}`
|
|
241
383
|
: `${p.error}✗ exit ${exitCode}${p.reset}`;
|
|
242
|
-
if (toolLineOpen && commandOutputLineCount === 0) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
toolLineOpen = false;
|
|
384
|
+
if (s.toolLineOpen && s.commandOutputLineCount === 0) {
|
|
385
|
+
writer.write(` ${mark}\n`);
|
|
386
|
+
s.toolLineOpen = false;
|
|
246
387
|
}
|
|
247
388
|
else {
|
|
248
389
|
closeToolLine();
|
|
249
390
|
flushCommandOutput();
|
|
250
|
-
renderer.writeLine(` ${mark}`);
|
|
391
|
+
s.renderer.writeLine(` ${mark}`);
|
|
392
|
+
drain();
|
|
251
393
|
}
|
|
252
394
|
}
|
|
253
395
|
function hasThinkingMode() {
|
|
@@ -255,69 +397,81 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
255
397
|
return !mode || mode.id !== "off";
|
|
256
398
|
}
|
|
257
399
|
function startThinkingSpinner() {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
spinnerStartTime = Date.now();
|
|
400
|
+
if (!s.spinnerStartTime)
|
|
401
|
+
s.spinnerStartTime = Date.now();
|
|
261
402
|
stopCurrentSpinner();
|
|
262
403
|
const thinking = hasThinkingMode();
|
|
263
|
-
|
|
404
|
+
s.spinnerLabel = thinking ? "Thinking" : "Working";
|
|
264
405
|
const hint = thinking
|
|
265
|
-
? (showThinkingText ? "(ctrl+t to collapse)" : "(ctrl+t to expand)")
|
|
406
|
+
? (s.showThinkingText ? "(ctrl+t to collapse)" : "(ctrl+t to expand)")
|
|
266
407
|
: "";
|
|
267
|
-
|
|
408
|
+
s.spinnerOpts = { hint: hint || undefined, startTime: s.spinnerStartTime };
|
|
409
|
+
s.spinner = createSpinner({ startTime: s.spinnerStartTime });
|
|
410
|
+
s.spinnerInterval = setInterval(() => {
|
|
411
|
+
if (s.spinner) {
|
|
412
|
+
const line = renderSpinnerLine(s.spinner, s.spinnerLabel, s.spinnerOpts);
|
|
413
|
+
writer.write(`\r ${line}\x1b[K`);
|
|
414
|
+
}
|
|
415
|
+
}, 80);
|
|
268
416
|
}
|
|
269
417
|
function stopCurrentSpinner() {
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
|
|
418
|
+
if (s.spinnerInterval) {
|
|
419
|
+
clearInterval(s.spinnerInterval);
|
|
420
|
+
s.spinnerInterval = null;
|
|
421
|
+
}
|
|
422
|
+
if (s.spinner) {
|
|
423
|
+
writer.write("\r\x1b[2K");
|
|
424
|
+
s.spinner = null;
|
|
273
425
|
}
|
|
274
426
|
}
|
|
275
427
|
function closeToolLine() {
|
|
276
|
-
if (toolLineOpen) {
|
|
277
|
-
|
|
278
|
-
toolLineOpen = false;
|
|
428
|
+
if (s.toolLineOpen) {
|
|
429
|
+
writer.write("\n");
|
|
430
|
+
s.toolLineOpen = false;
|
|
279
431
|
}
|
|
280
432
|
}
|
|
281
433
|
function writeCommandOutput(chunk) {
|
|
282
|
-
if (!renderer)
|
|
434
|
+
if (!s.renderer)
|
|
283
435
|
return;
|
|
284
436
|
closeToolLine();
|
|
285
|
-
const maxLines = currentToolKind === "read"
|
|
437
|
+
const maxLines = s.currentToolKind === "read"
|
|
286
438
|
? getSettings().readOutputMaxLines
|
|
287
439
|
: getSettings().maxCommandOutputLines;
|
|
288
|
-
commandOutputBuffer += chunk;
|
|
289
|
-
const lines = commandOutputBuffer.split("\n");
|
|
290
|
-
commandOutputBuffer = lines.pop();
|
|
440
|
+
s.commandOutputBuffer += chunk;
|
|
441
|
+
const lines = s.commandOutputBuffer.split("\n");
|
|
442
|
+
s.commandOutputBuffer = lines.pop();
|
|
291
443
|
for (const line of lines) {
|
|
292
|
-
if (commandOutputLineCount < maxLines) {
|
|
293
|
-
renderer.writeLine(`${p.dim} ${line}${p.reset}`);
|
|
294
|
-
commandOutputLineCount++;
|
|
444
|
+
if (s.commandOutputLineCount < maxLines) {
|
|
445
|
+
s.renderer.writeLine(`${p.dim} ${line}${p.reset}`);
|
|
446
|
+
s.commandOutputLineCount++;
|
|
295
447
|
}
|
|
296
448
|
else {
|
|
297
|
-
commandOutputOverflow++;
|
|
449
|
+
s.commandOutputOverflow++;
|
|
298
450
|
}
|
|
299
451
|
}
|
|
452
|
+
drain();
|
|
300
453
|
}
|
|
301
454
|
function flushCommandOutput() {
|
|
302
|
-
if (!renderer)
|
|
455
|
+
if (!s.renderer)
|
|
303
456
|
return;
|
|
304
|
-
const maxLines = currentToolKind === "read"
|
|
457
|
+
const maxLines = s.currentToolKind === "read"
|
|
305
458
|
? getSettings().readOutputMaxLines
|
|
306
459
|
: getSettings().maxCommandOutputLines;
|
|
307
|
-
if (commandOutputBuffer) {
|
|
308
|
-
if (commandOutputLineCount < maxLines) {
|
|
309
|
-
renderer.writeLine(`${p.dim} ${commandOutputBuffer}${p.reset}`);
|
|
310
|
-
commandOutputLineCount++;
|
|
460
|
+
if (s.commandOutputBuffer) {
|
|
461
|
+
if (s.commandOutputLineCount < maxLines) {
|
|
462
|
+
s.renderer.writeLine(`${p.dim} ${s.commandOutputBuffer}${p.reset}`);
|
|
463
|
+
s.commandOutputLineCount++;
|
|
311
464
|
}
|
|
312
465
|
else {
|
|
313
|
-
commandOutputOverflow++;
|
|
466
|
+
s.commandOutputOverflow++;
|
|
314
467
|
}
|
|
315
|
-
commandOutputBuffer = "";
|
|
468
|
+
s.commandOutputBuffer = "";
|
|
316
469
|
}
|
|
317
|
-
if (commandOutputOverflow > 0 && maxLines > 0) {
|
|
318
|
-
renderer.writeLine(`${p.dim} … ${commandOutputOverflow} more lines${p.reset}`);
|
|
470
|
+
if (s.commandOutputOverflow > 0 && maxLines > 0) {
|
|
471
|
+
s.renderer.writeLine(`${p.dim} … ${s.commandOutputOverflow} more lines${p.reset}`);
|
|
319
472
|
}
|
|
320
|
-
commandOutputOverflow = 0;
|
|
473
|
+
s.commandOutputOverflow = 0;
|
|
474
|
+
drain();
|
|
321
475
|
}
|
|
322
476
|
function diffTitle(filePath, diff) {
|
|
323
477
|
const stats = diff.isNewFile
|
|
@@ -328,8 +482,7 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
328
482
|
function showFileDiff(filePath, diff) {
|
|
329
483
|
if (diff.isIdentical)
|
|
330
484
|
return;
|
|
331
|
-
const
|
|
332
|
-
const boxW = Math.min(84, termW);
|
|
485
|
+
const boxW = Math.min(84, writer.columns);
|
|
333
486
|
const contentW = boxW - 4;
|
|
334
487
|
const diffLines = renderDiff(diff, {
|
|
335
488
|
width: contentW,
|
|
@@ -341,10 +494,10 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
341
494
|
const lastLine = diffLines[diffLines.length - 1] ?? "";
|
|
342
495
|
const isTruncated = lastLine.includes("… ");
|
|
343
496
|
if (isTruncated) {
|
|
344
|
-
lastTruncatedDiff = { filePath, diff, expanded: false };
|
|
497
|
+
s.lastTruncatedDiff = { filePath, diff, expanded: false };
|
|
345
498
|
}
|
|
346
499
|
else {
|
|
347
|
-
lastTruncatedDiff = null;
|
|
500
|
+
s.lastTruncatedDiff = null;
|
|
348
501
|
}
|
|
349
502
|
const body = diffLines.length > 1 ? ["", ...diffLines.slice(1), ""] : diffLines;
|
|
350
503
|
const footer = isTruncated
|
|
@@ -357,16 +510,17 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
357
510
|
title: diffTitle(filePath, diff),
|
|
358
511
|
footer,
|
|
359
512
|
});
|
|
360
|
-
if (!renderer)
|
|
513
|
+
if (!s.renderer)
|
|
361
514
|
startAgentResponse();
|
|
362
515
|
for (const line of framed) {
|
|
363
|
-
renderer.writeLine(line);
|
|
516
|
+
s.renderer.writeLine(line);
|
|
364
517
|
}
|
|
518
|
+
drain();
|
|
365
519
|
}
|
|
366
520
|
function expandLastDiff() {
|
|
367
|
-
if (!lastTruncatedDiff)
|
|
521
|
+
if (!s.lastTruncatedDiff)
|
|
368
522
|
return;
|
|
369
|
-
const entry = lastTruncatedDiff;
|
|
523
|
+
const entry = s.lastTruncatedDiff;
|
|
370
524
|
entry.expanded = !entry.expanded;
|
|
371
525
|
if (!entry.expanded) {
|
|
372
526
|
showFileDiffCached(entry);
|
|
@@ -374,8 +528,7 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
374
528
|
}
|
|
375
529
|
if (!entry.expandedLines) {
|
|
376
530
|
const { filePath, diff } = entry;
|
|
377
|
-
const
|
|
378
|
-
const boxW = Math.min(120, termW);
|
|
531
|
+
const boxW = Math.min(120, writer.columns);
|
|
379
532
|
const contentW = boxW - 4;
|
|
380
533
|
const diffLines = renderDiff(diff, {
|
|
381
534
|
width: contentW,
|
|
@@ -392,15 +545,14 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
392
545
|
footer: [` ${p.dim}ctrl+o to collapse${p.reset}`],
|
|
393
546
|
});
|
|
394
547
|
}
|
|
395
|
-
|
|
548
|
+
writer.write("\n");
|
|
396
549
|
for (const line of entry.expandedLines) {
|
|
397
|
-
|
|
550
|
+
writer.write(line + "\n");
|
|
398
551
|
}
|
|
399
552
|
}
|
|
400
553
|
function showFileDiffCached(entry) {
|
|
401
554
|
const { filePath, diff } = entry;
|
|
402
|
-
const
|
|
403
|
-
const boxW = Math.min(84, termW);
|
|
555
|
+
const boxW = Math.min(84, writer.columns);
|
|
404
556
|
const contentW = boxW - 4;
|
|
405
557
|
const diffLines = renderDiff(diff, {
|
|
406
558
|
width: contentW,
|
|
@@ -417,43 +569,51 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
417
569
|
title: diffTitle(filePath, diff),
|
|
418
570
|
footer: [` ${p.dim}ctrl+o to expand${p.reset}`],
|
|
419
571
|
});
|
|
420
|
-
|
|
572
|
+
writer.write("\n");
|
|
421
573
|
for (const line of framed) {
|
|
422
|
-
|
|
574
|
+
writer.write(line + "\n");
|
|
423
575
|
}
|
|
424
576
|
}
|
|
425
577
|
function toggleThinkingDisplay() {
|
|
426
|
-
showThinkingText = !showThinkingText;
|
|
427
|
-
|
|
428
|
-
if (spinner) {
|
|
578
|
+
s.showThinkingText = !s.showThinkingText;
|
|
579
|
+
if (s.spinner) {
|
|
429
580
|
stopCurrentSpinner();
|
|
430
|
-
|
|
581
|
+
if (s.showThinkingText) {
|
|
582
|
+
// Expanding: replace spinner with thinking text header
|
|
583
|
+
if (!s.renderer)
|
|
584
|
+
startAgentResponse();
|
|
585
|
+
s.renderer.writeLine(`${p.dim}Thinking (ctrl+t to collapse)${p.reset}`);
|
|
586
|
+
drain();
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
// Collapsing: restart spinner with updated hint
|
|
590
|
+
startThinkingSpinner();
|
|
591
|
+
}
|
|
431
592
|
return;
|
|
432
593
|
}
|
|
433
|
-
if (!isThinking)
|
|
594
|
+
if (!s.isThinking)
|
|
434
595
|
return;
|
|
435
|
-
if (showThinkingText) {
|
|
436
|
-
// Switch from spinner to streaming text
|
|
596
|
+
if (s.showThinkingText) {
|
|
437
597
|
stopCurrentSpinner();
|
|
438
|
-
if (!renderer)
|
|
598
|
+
if (!s.renderer)
|
|
439
599
|
startAgentResponse();
|
|
440
|
-
renderer.writeLine(`${p.dim}Thinking (ctrl+t to collapse)${p.reset}`);
|
|
600
|
+
s.renderer.writeLine(`${p.dim}Thinking (ctrl+t to collapse)${p.reset}`);
|
|
601
|
+
drain();
|
|
441
602
|
}
|
|
442
603
|
else {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
renderer.writeLine(`${p.dim}${"─".repeat(w)}${p.reset}`);
|
|
604
|
+
if (s.renderer) {
|
|
605
|
+
s.renderer.flush();
|
|
606
|
+
const w = Math.min(80, writer.columns);
|
|
607
|
+
s.renderer.writeLine(`${p.dim}${"─".repeat(w)}${p.reset}`);
|
|
608
|
+
drain();
|
|
449
609
|
}
|
|
450
610
|
startThinkingSpinner();
|
|
451
611
|
}
|
|
452
612
|
}
|
|
453
613
|
function showError(message) {
|
|
454
|
-
|
|
614
|
+
writer.write(`\n${p.error}Error: ${message}${p.reset}\n`);
|
|
455
615
|
}
|
|
456
616
|
function showInfo(message) {
|
|
457
|
-
|
|
617
|
+
writer.write(`${p.muted}${message}${p.reset}\n`);
|
|
458
618
|
}
|
|
459
619
|
}
|