agent-sh 0.3.0 → 0.3.1
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/acp-client.js +38 -22
- package/dist/core.js +10 -0
- package/dist/event-bus.d.ts +22 -0
- package/dist/event-bus.js +10 -0
- package/dist/extensions/tui-renderer.d.ts +1 -1
- package/dist/extensions/tui-renderer.js +304 -160
- package/dist/settings.d.ts +11 -0
- package/dist/settings.js +19 -1
- package/dist/types.d.ts +15 -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/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;
|
|
77
|
+
s.spinnerStartTime = 0;
|
|
36
78
|
showUserQuery(e.query);
|
|
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
240
|
function showUserQuery(query) {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
const contentW = boxW - 4; // inside box padding
|
|
155
|
-
// Wrap long queries to fit within box
|
|
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);
|
|
@@ -178,76 +264,126 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
178
264
|
borderColor: p.accent,
|
|
179
265
|
title: `${p.accent}${p.bold}❯${p.reset}`,
|
|
180
266
|
});
|
|
181
|
-
|
|
267
|
+
writer.write("\n");
|
|
182
268
|
for (const line of framed) {
|
|
183
|
-
|
|
269
|
+
writer.write(line + "\n");
|
|
184
270
|
}
|
|
185
271
|
}
|
|
186
272
|
function writeAgentText(text) {
|
|
187
273
|
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
|
-
|
|
274
|
+
const needsGap = s.hadToolCalls;
|
|
275
|
+
s.hadToolCalls = false;
|
|
276
|
+
if (s.isThinking) {
|
|
277
|
+
s.isThinking = false;
|
|
278
|
+
if (s.showThinkingText && s.renderer) {
|
|
279
|
+
s.renderer.flush();
|
|
280
|
+
const w = Math.min(80, writer.columns);
|
|
281
|
+
s.renderer.writeLine(`${p.dim}${"─".repeat(w)}${p.reset}`);
|
|
282
|
+
drain();
|
|
197
283
|
}
|
|
198
284
|
}
|
|
285
|
+
showCollapsedThinking();
|
|
199
286
|
stopCurrentSpinner();
|
|
200
|
-
if (!renderer)
|
|
287
|
+
if (!s.renderer)
|
|
201
288
|
startAgentResponse();
|
|
202
289
|
if (needsGap)
|
|
203
|
-
|
|
204
|
-
renderer.push(text);
|
|
205
|
-
|
|
290
|
+
writer.write("\n");
|
|
291
|
+
s.renderer.push(text);
|
|
292
|
+
drain();
|
|
293
|
+
}
|
|
294
|
+
define("render:code-block", (language, code, width) => {
|
|
295
|
+
flushForRaw();
|
|
296
|
+
if (language) {
|
|
297
|
+
s.renderer.writeLine(`${p.dim}${language}${p.reset}`);
|
|
298
|
+
}
|
|
299
|
+
let highlighted;
|
|
300
|
+
if (!language) {
|
|
301
|
+
// No language specified — render as plain text to avoid false syntax detection
|
|
302
|
+
highlighted = code;
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
try {
|
|
306
|
+
highlighted = highlight(code, { language });
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
highlighted = `${p.success}${code}${p.reset}`;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const contentWidth = Math.min(90, width - 2);
|
|
313
|
+
for (const line of highlighted.split("\n")) {
|
|
314
|
+
const indented = ` ${line}`;
|
|
315
|
+
const wrapped = wrapLine(indented, contentWidth);
|
|
316
|
+
for (const wl of wrapped) {
|
|
317
|
+
s.renderer.writeLine(wl);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
drain();
|
|
321
|
+
});
|
|
322
|
+
function writeCodeBlock(language, code) {
|
|
323
|
+
ctx.call("render:code-block", language, code, writer.columns);
|
|
324
|
+
}
|
|
325
|
+
function flushForRaw() {
|
|
326
|
+
closeToolLine();
|
|
327
|
+
stopCurrentSpinner();
|
|
328
|
+
if (!s.renderer)
|
|
329
|
+
startAgentResponse();
|
|
330
|
+
s.renderer.flush();
|
|
331
|
+
drain();
|
|
332
|
+
}
|
|
333
|
+
define("render:image", (data) => {
|
|
334
|
+
flushForRaw();
|
|
335
|
+
const escape = encodeImageForTerminal(data);
|
|
336
|
+
if (escape) {
|
|
337
|
+
writer.write(" " + escape + "\n");
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
function writeInlineImage(data) {
|
|
341
|
+
ctx.call("render:image", data);
|
|
206
342
|
}
|
|
207
343
|
function showToolCall(title, command, extra) {
|
|
208
344
|
closeToolLine();
|
|
209
345
|
stopCurrentSpinner();
|
|
210
|
-
if (!renderer)
|
|
346
|
+
if (!s.renderer)
|
|
211
347
|
startAgentResponse();
|
|
212
|
-
|
|
213
|
-
|
|
348
|
+
showCollapsedThinking();
|
|
349
|
+
s.renderer.flush();
|
|
350
|
+
drain();
|
|
214
351
|
const lines = renderToolCall({
|
|
215
352
|
title,
|
|
216
353
|
command: command || undefined,
|
|
217
354
|
kind: extra?.kind,
|
|
218
355
|
locations: extra?.locations,
|
|
219
356
|
rawInput: extra?.rawInput,
|
|
220
|
-
},
|
|
221
|
-
// Write all lines except the last normally, write last without \n
|
|
357
|
+
}, writer.columns);
|
|
222
358
|
for (let i = 0; i < lines.length - 1; i++) {
|
|
223
|
-
renderer.writeLine(lines[i]);
|
|
359
|
+
s.renderer.writeLine(lines[i]);
|
|
224
360
|
}
|
|
361
|
+
drain();
|
|
225
362
|
if (lines.length > 0) {
|
|
226
|
-
|
|
227
|
-
toolLineOpen = true;
|
|
363
|
+
writer.write(` ${lines[lines.length - 1]}`);
|
|
364
|
+
s.toolLineOpen = true;
|
|
228
365
|
}
|
|
229
|
-
hadToolCalls = true;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
commandOutputOverflow = 0;
|
|
366
|
+
s.hadToolCalls = true;
|
|
367
|
+
s.commandOutputLineCount = 0;
|
|
368
|
+
s.commandOutputOverflow = 0;
|
|
233
369
|
}
|
|
234
370
|
function showToolComplete(exitCode) {
|
|
235
|
-
if (!renderer)
|
|
371
|
+
if (!s.renderer)
|
|
236
372
|
return;
|
|
237
373
|
const mark = exitCode === null
|
|
238
374
|
? `${p.muted}(timed out)${p.reset}`
|
|
239
375
|
: exitCode === 0
|
|
240
376
|
? `${p.success}✓${p.reset}`
|
|
241
377
|
: `${p.error}✗ exit ${exitCode}${p.reset}`;
|
|
242
|
-
if (toolLineOpen && commandOutputLineCount === 0) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
toolLineOpen = false;
|
|
378
|
+
if (s.toolLineOpen && s.commandOutputLineCount === 0) {
|
|
379
|
+
writer.write(` ${mark}\n`);
|
|
380
|
+
s.toolLineOpen = false;
|
|
246
381
|
}
|
|
247
382
|
else {
|
|
248
383
|
closeToolLine();
|
|
249
384
|
flushCommandOutput();
|
|
250
|
-
renderer.writeLine(` ${mark}`);
|
|
385
|
+
s.renderer.writeLine(` ${mark}`);
|
|
386
|
+
drain();
|
|
251
387
|
}
|
|
252
388
|
}
|
|
253
389
|
function hasThinkingMode() {
|
|
@@ -255,69 +391,81 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
255
391
|
return !mode || mode.id !== "off";
|
|
256
392
|
}
|
|
257
393
|
function startThinkingSpinner() {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
spinnerStartTime = Date.now();
|
|
394
|
+
if (!s.spinnerStartTime)
|
|
395
|
+
s.spinnerStartTime = Date.now();
|
|
261
396
|
stopCurrentSpinner();
|
|
262
397
|
const thinking = hasThinkingMode();
|
|
263
|
-
|
|
398
|
+
s.spinnerLabel = thinking ? "Thinking" : "Working";
|
|
264
399
|
const hint = thinking
|
|
265
|
-
? (showThinkingText ? "(ctrl+t to collapse)" : "(ctrl+t to expand)")
|
|
400
|
+
? (s.showThinkingText ? "(ctrl+t to collapse)" : "(ctrl+t to expand)")
|
|
266
401
|
: "";
|
|
267
|
-
|
|
402
|
+
s.spinnerOpts = { hint: hint || undefined, startTime: s.spinnerStartTime };
|
|
403
|
+
s.spinner = createSpinner({ startTime: s.spinnerStartTime });
|
|
404
|
+
s.spinnerInterval = setInterval(() => {
|
|
405
|
+
if (s.spinner) {
|
|
406
|
+
const line = renderSpinnerLine(s.spinner, s.spinnerLabel, s.spinnerOpts);
|
|
407
|
+
writer.write(`\r ${line}\x1b[K`);
|
|
408
|
+
}
|
|
409
|
+
}, 80);
|
|
268
410
|
}
|
|
269
411
|
function stopCurrentSpinner() {
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
|
|
412
|
+
if (s.spinnerInterval) {
|
|
413
|
+
clearInterval(s.spinnerInterval);
|
|
414
|
+
s.spinnerInterval = null;
|
|
415
|
+
}
|
|
416
|
+
if (s.spinner) {
|
|
417
|
+
writer.write("\r\x1b[2K");
|
|
418
|
+
s.spinner = null;
|
|
273
419
|
}
|
|
274
420
|
}
|
|
275
421
|
function closeToolLine() {
|
|
276
|
-
if (toolLineOpen) {
|
|
277
|
-
|
|
278
|
-
toolLineOpen = false;
|
|
422
|
+
if (s.toolLineOpen) {
|
|
423
|
+
writer.write("\n");
|
|
424
|
+
s.toolLineOpen = false;
|
|
279
425
|
}
|
|
280
426
|
}
|
|
281
427
|
function writeCommandOutput(chunk) {
|
|
282
|
-
if (!renderer)
|
|
428
|
+
if (!s.renderer)
|
|
283
429
|
return;
|
|
284
430
|
closeToolLine();
|
|
285
|
-
const maxLines = currentToolKind === "read"
|
|
431
|
+
const maxLines = s.currentToolKind === "read"
|
|
286
432
|
? getSettings().readOutputMaxLines
|
|
287
433
|
: getSettings().maxCommandOutputLines;
|
|
288
|
-
commandOutputBuffer += chunk;
|
|
289
|
-
const lines = commandOutputBuffer.split("\n");
|
|
290
|
-
commandOutputBuffer = lines.pop();
|
|
434
|
+
s.commandOutputBuffer += chunk;
|
|
435
|
+
const lines = s.commandOutputBuffer.split("\n");
|
|
436
|
+
s.commandOutputBuffer = lines.pop();
|
|
291
437
|
for (const line of lines) {
|
|
292
|
-
if (commandOutputLineCount < maxLines) {
|
|
293
|
-
renderer.writeLine(`${p.dim} ${line}${p.reset}`);
|
|
294
|
-
commandOutputLineCount++;
|
|
438
|
+
if (s.commandOutputLineCount < maxLines) {
|
|
439
|
+
s.renderer.writeLine(`${p.dim} ${line}${p.reset}`);
|
|
440
|
+
s.commandOutputLineCount++;
|
|
295
441
|
}
|
|
296
442
|
else {
|
|
297
|
-
commandOutputOverflow++;
|
|
443
|
+
s.commandOutputOverflow++;
|
|
298
444
|
}
|
|
299
445
|
}
|
|
446
|
+
drain();
|
|
300
447
|
}
|
|
301
448
|
function flushCommandOutput() {
|
|
302
|
-
if (!renderer)
|
|
449
|
+
if (!s.renderer)
|
|
303
450
|
return;
|
|
304
|
-
const maxLines = currentToolKind === "read"
|
|
451
|
+
const maxLines = s.currentToolKind === "read"
|
|
305
452
|
? getSettings().readOutputMaxLines
|
|
306
453
|
: getSettings().maxCommandOutputLines;
|
|
307
|
-
if (commandOutputBuffer) {
|
|
308
|
-
if (commandOutputLineCount < maxLines) {
|
|
309
|
-
renderer.writeLine(`${p.dim} ${commandOutputBuffer}${p.reset}`);
|
|
310
|
-
commandOutputLineCount++;
|
|
454
|
+
if (s.commandOutputBuffer) {
|
|
455
|
+
if (s.commandOutputLineCount < maxLines) {
|
|
456
|
+
s.renderer.writeLine(`${p.dim} ${s.commandOutputBuffer}${p.reset}`);
|
|
457
|
+
s.commandOutputLineCount++;
|
|
311
458
|
}
|
|
312
459
|
else {
|
|
313
|
-
commandOutputOverflow++;
|
|
460
|
+
s.commandOutputOverflow++;
|
|
314
461
|
}
|
|
315
|
-
commandOutputBuffer = "";
|
|
462
|
+
s.commandOutputBuffer = "";
|
|
316
463
|
}
|
|
317
|
-
if (commandOutputOverflow > 0 && maxLines > 0) {
|
|
318
|
-
renderer.writeLine(`${p.dim} … ${commandOutputOverflow} more lines${p.reset}`);
|
|
464
|
+
if (s.commandOutputOverflow > 0 && maxLines > 0) {
|
|
465
|
+
s.renderer.writeLine(`${p.dim} … ${s.commandOutputOverflow} more lines${p.reset}`);
|
|
319
466
|
}
|
|
320
|
-
commandOutputOverflow = 0;
|
|
467
|
+
s.commandOutputOverflow = 0;
|
|
468
|
+
drain();
|
|
321
469
|
}
|
|
322
470
|
function diffTitle(filePath, diff) {
|
|
323
471
|
const stats = diff.isNewFile
|
|
@@ -328,8 +476,7 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
328
476
|
function showFileDiff(filePath, diff) {
|
|
329
477
|
if (diff.isIdentical)
|
|
330
478
|
return;
|
|
331
|
-
const
|
|
332
|
-
const boxW = Math.min(84, termW);
|
|
479
|
+
const boxW = Math.min(84, writer.columns);
|
|
333
480
|
const contentW = boxW - 4;
|
|
334
481
|
const diffLines = renderDiff(diff, {
|
|
335
482
|
width: contentW,
|
|
@@ -341,10 +488,10 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
341
488
|
const lastLine = diffLines[diffLines.length - 1] ?? "";
|
|
342
489
|
const isTruncated = lastLine.includes("… ");
|
|
343
490
|
if (isTruncated) {
|
|
344
|
-
lastTruncatedDiff = { filePath, diff, expanded: false };
|
|
491
|
+
s.lastTruncatedDiff = { filePath, diff, expanded: false };
|
|
345
492
|
}
|
|
346
493
|
else {
|
|
347
|
-
lastTruncatedDiff = null;
|
|
494
|
+
s.lastTruncatedDiff = null;
|
|
348
495
|
}
|
|
349
496
|
const body = diffLines.length > 1 ? ["", ...diffLines.slice(1), ""] : diffLines;
|
|
350
497
|
const footer = isTruncated
|
|
@@ -357,16 +504,17 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
357
504
|
title: diffTitle(filePath, diff),
|
|
358
505
|
footer,
|
|
359
506
|
});
|
|
360
|
-
if (!renderer)
|
|
507
|
+
if (!s.renderer)
|
|
361
508
|
startAgentResponse();
|
|
362
509
|
for (const line of framed) {
|
|
363
|
-
renderer.writeLine(line);
|
|
510
|
+
s.renderer.writeLine(line);
|
|
364
511
|
}
|
|
512
|
+
drain();
|
|
365
513
|
}
|
|
366
514
|
function expandLastDiff() {
|
|
367
|
-
if (!lastTruncatedDiff)
|
|
515
|
+
if (!s.lastTruncatedDiff)
|
|
368
516
|
return;
|
|
369
|
-
const entry = lastTruncatedDiff;
|
|
517
|
+
const entry = s.lastTruncatedDiff;
|
|
370
518
|
entry.expanded = !entry.expanded;
|
|
371
519
|
if (!entry.expanded) {
|
|
372
520
|
showFileDiffCached(entry);
|
|
@@ -374,8 +522,7 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
374
522
|
}
|
|
375
523
|
if (!entry.expandedLines) {
|
|
376
524
|
const { filePath, diff } = entry;
|
|
377
|
-
const
|
|
378
|
-
const boxW = Math.min(120, termW);
|
|
525
|
+
const boxW = Math.min(120, writer.columns);
|
|
379
526
|
const contentW = boxW - 4;
|
|
380
527
|
const diffLines = renderDiff(diff, {
|
|
381
528
|
width: contentW,
|
|
@@ -392,15 +539,14 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
392
539
|
footer: [` ${p.dim}ctrl+o to collapse${p.reset}`],
|
|
393
540
|
});
|
|
394
541
|
}
|
|
395
|
-
|
|
542
|
+
writer.write("\n");
|
|
396
543
|
for (const line of entry.expandedLines) {
|
|
397
|
-
|
|
544
|
+
writer.write(line + "\n");
|
|
398
545
|
}
|
|
399
546
|
}
|
|
400
547
|
function showFileDiffCached(entry) {
|
|
401
548
|
const { filePath, diff } = entry;
|
|
402
|
-
const
|
|
403
|
-
const boxW = Math.min(84, termW);
|
|
549
|
+
const boxW = Math.min(84, writer.columns);
|
|
404
550
|
const contentW = boxW - 4;
|
|
405
551
|
const diffLines = renderDiff(diff, {
|
|
406
552
|
width: contentW,
|
|
@@ -417,43 +563,41 @@ export default function activate({ bus, getAcpClient }) {
|
|
|
417
563
|
title: diffTitle(filePath, diff),
|
|
418
564
|
footer: [` ${p.dim}ctrl+o to expand${p.reset}`],
|
|
419
565
|
});
|
|
420
|
-
|
|
566
|
+
writer.write("\n");
|
|
421
567
|
for (const line of framed) {
|
|
422
|
-
|
|
568
|
+
writer.write(line + "\n");
|
|
423
569
|
}
|
|
424
570
|
}
|
|
425
571
|
function toggleThinkingDisplay() {
|
|
426
|
-
showThinkingText = !showThinkingText;
|
|
427
|
-
|
|
428
|
-
if (spinner) {
|
|
572
|
+
s.showThinkingText = !s.showThinkingText;
|
|
573
|
+
if (s.spinner) {
|
|
429
574
|
stopCurrentSpinner();
|
|
430
575
|
startThinkingSpinner();
|
|
431
576
|
return;
|
|
432
577
|
}
|
|
433
|
-
if (!isThinking)
|
|
578
|
+
if (!s.isThinking)
|
|
434
579
|
return;
|
|
435
|
-
if (showThinkingText) {
|
|
436
|
-
// Switch from spinner to streaming text
|
|
580
|
+
if (s.showThinkingText) {
|
|
437
581
|
stopCurrentSpinner();
|
|
438
|
-
if (!renderer)
|
|
582
|
+
if (!s.renderer)
|
|
439
583
|
startAgentResponse();
|
|
440
|
-
renderer.writeLine(`${p.dim}Thinking (ctrl+t to collapse)${p.reset}`);
|
|
584
|
+
s.renderer.writeLine(`${p.dim}Thinking (ctrl+t to collapse)${p.reset}`);
|
|
585
|
+
drain();
|
|
441
586
|
}
|
|
442
587
|
else {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
renderer.writeLine(`${p.dim}${"─".repeat(w)}${p.reset}`);
|
|
588
|
+
if (s.renderer) {
|
|
589
|
+
s.renderer.flush();
|
|
590
|
+
const w = Math.min(80, writer.columns);
|
|
591
|
+
s.renderer.writeLine(`${p.dim}${"─".repeat(w)}${p.reset}`);
|
|
592
|
+
drain();
|
|
449
593
|
}
|
|
450
594
|
startThinkingSpinner();
|
|
451
595
|
}
|
|
452
596
|
}
|
|
453
597
|
function showError(message) {
|
|
454
|
-
|
|
598
|
+
writer.write(`\n${p.error}Error: ${message}${p.reset}\n`);
|
|
455
599
|
}
|
|
456
600
|
function showInfo(message) {
|
|
457
|
-
|
|
601
|
+
writer.write(`${p.muted}${message}${p.reset}\n`);
|
|
458
602
|
}
|
|
459
603
|
}
|