agent-sh 0.15.6 → 0.15.8
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/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/agent/agent-loop.d.ts +3 -0
- package/dist/agent/agent-loop.js +19 -6
- package/dist/agent/events.d.ts +3 -0
- package/dist/agent/extensions/rolling-history/index.js +20 -8
- package/dist/agent/extensions/rolling-history/recall.d.ts +2 -2
- package/dist/agent/extensions/rolling-history/recall.js +17 -7
- package/dist/agent/host-types.d.ts +6 -0
- package/dist/agent/index.js +5 -1
- package/dist/agent/llm-client.d.ts +2 -0
- package/dist/agent/llm-client.js +2 -2
- package/dist/agent/providers/openai-compatible.d.ts +8 -0
- package/dist/agent/providers/openai-compatible.js +9 -2
- package/dist/agent/providers/openrouter.js +11 -1
- package/dist/agent/store.js +6 -1
- package/dist/agent/token-budget.d.ts +2 -1
- package/dist/agent/token-budget.js +6 -1
- package/dist/cli/index.js +1 -1
- package/dist/core/event-bus.d.ts +16 -1
- package/dist/core/event-bus.js +73 -11
- package/dist/core/index.js +18 -0
- package/dist/shell/strategies/bash.js +10 -2
- package/dist/shell/tui-renderer.js +115 -174
- package/dist/utils/executor.js +19 -11
- package/dist/utils/floating-panel.d.ts +1 -0
- package/dist/utils/floating-panel.js +28 -26
- package/dist/utils/markdown.js +19 -21
- package/dist/utils/palette.d.ts +11 -0
- package/dist/utils/palette.js +11 -0
- package/docs/agent.md +13 -11
- package/docs/architecture.md +3 -5
- package/docs/extensions.md +21 -20
- package/docs/library.md +6 -3
- package/docs/troubleshooting.md +2 -2
- package/docs/tui-composition.md +11 -3
- package/docs/usage.md +70 -50
- package/examples/extensions/ashi/package.json +1 -1
- package/examples/extensions/ashi/src/chat/assistant.ts +8 -4
- package/examples/extensions/ashi/src/cli.ts +8 -0
- package/examples/extensions/ashi/src/compaction.ts +4 -7
- package/examples/extensions/ashi/src/frontend.ts +6 -3
- package/examples/extensions/ashi/src/renderers/pi-tui/inline-image.ts +145 -0
- package/examples/extensions/ashi/src/renderers/pi-tui/nodes.ts +51 -1
- package/examples/extensions/ashi/src/schema.ts +8 -2
- package/examples/extensions/ashi/src/user-shell-intents.ts +4 -1
- package/examples/extensions/command-suggest.ts +4 -0
- package/examples/extensions/latex-images.ts +152 -7
- package/examples/extensions/solarized-theme.ts +11 -0
- package/package.json +1 -1
- package/src/agent/agent-loop.ts +19 -6
- package/src/agent/events.ts +1 -0
- package/src/agent/extensions/rolling-history/index.ts +20 -8
- package/src/agent/extensions/rolling-history/recall.ts +28 -7
- package/src/agent/host-types.ts +2 -0
- package/src/agent/index.ts +7 -1
- package/src/agent/llm-client.ts +4 -2
- package/src/agent/providers/openai-compatible.ts +19 -4
- package/src/agent/providers/openrouter.ts +10 -1
- package/src/agent/store.ts +5 -1
- package/src/agent/token-budget.ts +10 -1
- package/src/cli/index.ts +1 -1
- package/src/core/event-bus.ts +67 -12
- package/src/core/index.ts +18 -0
- package/src/shell/strategies/bash.ts +10 -2
- package/src/shell/tui-renderer.ts +130 -207
- package/src/utils/executor.ts +17 -14
- package/src/utils/floating-panel.ts +24 -22
- package/src/utils/markdown.ts +17 -20
- package/src/utils/palette.ts +30 -5
|
@@ -50,7 +50,6 @@ function createRenderState() {
|
|
|
50
50
|
spinnerStartTime: 0,
|
|
51
51
|
openTool: null,
|
|
52
52
|
pendingToolCompletes: new Map(),
|
|
53
|
-
orphanContHeaderKind: undefined,
|
|
54
53
|
currentToolKind: undefined,
|
|
55
54
|
toolStartTime: 0,
|
|
56
55
|
toolExitCode: null,
|
|
@@ -58,12 +57,6 @@ function createRenderState() {
|
|
|
58
57
|
commandOutputLineCount: 0,
|
|
59
58
|
commandOutputOverflow: 0,
|
|
60
59
|
commandOverflowLines: [],
|
|
61
|
-
toolGroupKind: undefined,
|
|
62
|
-
toolGroupCount: 0,
|
|
63
|
-
toolGroupCompletedCount: 0,
|
|
64
|
-
toolGroupAllOk: true,
|
|
65
|
-
toolGroupRendered: 0,
|
|
66
|
-
toolGroupSummaries: [],
|
|
67
60
|
isThinking: false,
|
|
68
61
|
showThinkingText: false,
|
|
69
62
|
thinkingPending: false,
|
|
@@ -113,15 +106,6 @@ export default function activate(ctx) {
|
|
|
113
106
|
return `${p.success}✓${p.reset}${summaryStr}${timer}`;
|
|
114
107
|
return `${p.error}✗ exit ${exitCode}${p.reset}${summaryStr}${timer}`;
|
|
115
108
|
});
|
|
116
|
-
define("tui:render-tool-group-summary", (count, rendered, allOk, summaries) => {
|
|
117
|
-
const mark = allOk ? `${p.success}✓${p.reset}` : `${p.error}✗${p.reset}`;
|
|
118
|
-
const summaryStr = summaries.length > 0 ? ` ${p.dim}${summaries.join(", ")}${p.reset}` : "";
|
|
119
|
-
const collapsed = count - rendered;
|
|
120
|
-
if (collapsed > 0) {
|
|
121
|
-
return ` ${p.muted}└${p.reset} ${p.dim}+${collapsed} more${p.reset} ${mark}${summaryStr}`;
|
|
122
|
-
}
|
|
123
|
-
return ` ${p.muted}└${p.reset} ${mark}${summaryStr}`;
|
|
124
|
-
});
|
|
125
109
|
define("tui:render-command-output", (line, _kind) => `${p.dim} ${line}${p.reset}`);
|
|
126
110
|
define("tui:render-spinner", (label, frame, elapsed, hint) => {
|
|
127
111
|
const timer = elapsed ? ` ${p.dim}${elapsed}${p.reset}` : "";
|
|
@@ -245,33 +229,35 @@ export default function activate(ctx) {
|
|
|
245
229
|
const GROUPABLE_KINDS = new Set(["read", "search"]);
|
|
246
230
|
const GROUP_MAX_VISIBLE = 5;
|
|
247
231
|
const KIND_ICONS = { read: "◆", search: "⌕" };
|
|
248
|
-
// Batch groups: kind → { total, rendered, headerShown }
|
|
249
232
|
let batchGroups = new Map();
|
|
233
|
+
let batchSize = 0;
|
|
234
|
+
/** A lone tool has nothing to interleave with, so deferral applies only to
|
|
235
|
+
* read-only kinds in a multi-tool batch. */
|
|
236
|
+
function isDeferred(kind) {
|
|
237
|
+
return batchSize > 1 && GROUPABLE_KINDS.has(kind) && batchGroups.has(kind);
|
|
238
|
+
}
|
|
250
239
|
bus.on("agent:tool-batch", (e) => {
|
|
251
240
|
if (!shouldRender())
|
|
252
241
|
return;
|
|
253
242
|
fencedTransform.flush();
|
|
254
|
-
|
|
255
|
-
|
|
243
|
+
finalizeAllGroups();
|
|
244
|
+
closeToolLine();
|
|
256
245
|
batchGroups = new Map();
|
|
246
|
+
batchSize = 0;
|
|
257
247
|
for (const group of e.groups) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
rendered: 0,
|
|
261
|
-
headerShown: false,
|
|
262
|
-
});
|
|
248
|
+
batchSize += group.tools.length;
|
|
249
|
+
batchGroups.set(group.kind, { total: group.tools.length, completed: 0, finalized: false, order: [], members: new Map() });
|
|
263
250
|
}
|
|
264
251
|
});
|
|
265
252
|
bus.on("agent:tool-started", (e) => {
|
|
266
253
|
if (!shouldRender())
|
|
267
254
|
return;
|
|
268
255
|
fencedTransform.flush();
|
|
269
|
-
stopCurrentSpinner();
|
|
270
256
|
s.currentToolKind = e.kind;
|
|
271
257
|
s.toolStartTime = Date.now();
|
|
272
|
-
s.orphanContHeaderKind = undefined;
|
|
273
258
|
if (e.title === "user_shell") {
|
|
274
|
-
|
|
259
|
+
stopCurrentSpinner();
|
|
260
|
+
finalizeAllGroups();
|
|
275
261
|
closeToolLine();
|
|
276
262
|
if (!s.renderer)
|
|
277
263
|
startAgentResponse();
|
|
@@ -284,89 +270,57 @@ export default function activate(ctx) {
|
|
|
284
270
|
return;
|
|
285
271
|
}
|
|
286
272
|
const kind = e.kind ?? "execute";
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
showCollapsedThinking();
|
|
297
|
-
contentGap("tool");
|
|
298
|
-
s.renderer.flush();
|
|
299
|
-
drain();
|
|
300
|
-
const icon = KIND_ICONS[kind] ?? "▶";
|
|
301
|
-
s.renderer.writeLine(`${p.warning}${icon}${p.reset} ${kind}`);
|
|
302
|
-
drain();
|
|
303
|
-
group.headerShown = true;
|
|
304
|
-
s.toolGroupKind = kind;
|
|
305
|
-
s.toolGroupCount = 0;
|
|
306
|
-
s.toolGroupCompletedCount = 0;
|
|
307
|
-
s.toolGroupRendered = 0;
|
|
308
|
-
s.toolGroupAllOk = true;
|
|
309
|
-
s.toolGroupSummaries = [];
|
|
310
|
-
}
|
|
311
|
-
s.toolGroupCount++;
|
|
312
|
-
if (s.toolGroupRendered < GROUP_MAX_VISIBLE) {
|
|
313
|
-
showToolCall(e.title, "", { ...e, groupContinuation: true });
|
|
314
|
-
s.toolGroupRendered++;
|
|
315
|
-
}
|
|
316
|
-
if (e.toolCallId) {
|
|
317
|
-
s.pendingToolCompletes.set(e.toolCallId, {
|
|
318
|
-
title: e.title,
|
|
319
|
-
kind,
|
|
320
|
-
displayDetail: e.displayDetail ?? extractDetail(e),
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
// Standalone tool — single in its batch kind, or not groupable
|
|
326
|
-
finalizeToolGroup();
|
|
327
|
-
showToolCall(e.title, "", { ...e });
|
|
273
|
+
if (isDeferred(kind)) {
|
|
274
|
+
const group = batchGroups.get(kind);
|
|
275
|
+
const id = e.toolCallId ?? `${kind}-${group.order.length}`;
|
|
276
|
+
group.order.push(id);
|
|
277
|
+
group.members.set(id, { detail: e.displayDetail ?? extractDetail(e), ok: true, done: false });
|
|
278
|
+
s.hadToolCalls = true;
|
|
279
|
+
if (!s.spinner)
|
|
280
|
+
startThinkingSpinner(); // nothing's drawn yet — stand in until the block renders
|
|
281
|
+
return;
|
|
328
282
|
}
|
|
283
|
+
// Eager: a single-tool turn, or a sequential mutating/streaming tool.
|
|
284
|
+
stopCurrentSpinner();
|
|
285
|
+
showToolCall(e.title, "", { ...e });
|
|
329
286
|
});
|
|
330
287
|
bus.on("agent:tool-completed", (e) => {
|
|
331
288
|
if (!shouldRender())
|
|
332
289
|
return;
|
|
333
290
|
s.toolExitCode = e.exitCode;
|
|
334
|
-
if (e.exitCode !== 0)
|
|
335
|
-
s.toolGroupAllOk = false;
|
|
336
291
|
const resultDisplay = e.resultDisplay;
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
// Finalize as soon as all members return so aggregate lands right
|
|
347
|
-
// after its children, not below out-of-band renders from the next tool.
|
|
348
|
-
const batchGroup = batchGroups.get(s.toolGroupKind);
|
|
349
|
-
if (batchGroup && s.toolGroupCompletedCount >= batchGroup.total) {
|
|
350
|
-
finalizeToolGroup();
|
|
292
|
+
const kind = e.kind ?? "execute";
|
|
293
|
+
const group = batchGroups.get(kind);
|
|
294
|
+
if (isDeferred(kind) && group) {
|
|
295
|
+
const id = e.toolCallId ?? group.order[group.completed];
|
|
296
|
+
const member = id ? group.members.get(id) : undefined;
|
|
297
|
+
if (member) {
|
|
298
|
+
member.done = true;
|
|
299
|
+
member.ok = e.exitCode === 0;
|
|
300
|
+
member.summary = resultDisplay?.summary;
|
|
351
301
|
}
|
|
302
|
+
group.completed++;
|
|
303
|
+
s.currentToolKind = undefined;
|
|
304
|
+
flushReadyGroups();
|
|
305
|
+
if (!s.spinner)
|
|
306
|
+
startThinkingSpinner(); // keep the spinner up for in-flight siblings
|
|
352
307
|
}
|
|
353
308
|
else {
|
|
354
|
-
// Tools that lost the inline slot render as a labeled ⎿. Orphans
|
|
355
|
-
// (group finalized before they returned) reroute via showOrphanedComplete.
|
|
356
309
|
const pending = e.toolCallId ? s.pendingToolCompletes.get(e.toolCallId) : undefined;
|
|
357
310
|
if (pending)
|
|
358
311
|
s.pendingToolCompletes.delete(e.toolCallId);
|
|
359
|
-
|
|
360
|
-
showOrphanedComplete(e.exitCode, resultDisplay, pending.title, pending.kind, pending.displayDetail);
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
showToolComplete(e.exitCode, resultDisplay, pending?.displayDetail ?? pending?.title);
|
|
364
|
-
}
|
|
312
|
+
showToolComplete(e.exitCode, resultDisplay, pending?.displayDetail ?? pending?.title);
|
|
365
313
|
s.currentToolKind = undefined;
|
|
366
314
|
s.spinnerStartTime = 0;
|
|
367
315
|
startThinkingSpinner();
|
|
368
316
|
}
|
|
369
317
|
});
|
|
318
|
+
bus.on("agent:tool-batch-complete", () => {
|
|
319
|
+
if (!shouldRender())
|
|
320
|
+
return;
|
|
321
|
+
// Backstop for a group whose members didn't all complete (e.g. errored).
|
|
322
|
+
finalizeAllGroups();
|
|
323
|
+
});
|
|
370
324
|
bus.on("agent:tool-output-chunk", (e) => { if (shouldRender())
|
|
371
325
|
writeCommandOutput(e.chunk); });
|
|
372
326
|
bus.on("agent:tool-output", () => { if (shouldRender())
|
|
@@ -465,7 +419,7 @@ export default function activate(ctx) {
|
|
|
465
419
|
}
|
|
466
420
|
}
|
|
467
421
|
function endAgentResponse() {
|
|
468
|
-
|
|
422
|
+
finalizeAllGroups();
|
|
469
423
|
closeToolLine();
|
|
470
424
|
stopCurrentSpinner();
|
|
471
425
|
if (s.renderer) {
|
|
@@ -502,7 +456,7 @@ export default function activate(ctx) {
|
|
|
502
456
|
}
|
|
503
457
|
}
|
|
504
458
|
function writeAgentText(text) {
|
|
505
|
-
|
|
459
|
+
finalizeAllGroups();
|
|
506
460
|
closeToolLine();
|
|
507
461
|
s.hadToolCalls = false;
|
|
508
462
|
if (s.isThinking) {
|
|
@@ -526,7 +480,7 @@ export default function activate(ctx) {
|
|
|
526
480
|
flushForRaw();
|
|
527
481
|
contentGap("code");
|
|
528
482
|
if (language) {
|
|
529
|
-
s.renderer.writeLine(`${p.
|
|
483
|
+
s.renderer.writeLine(`${p.mdCodeBlockBorder}${language}${p.reset}`);
|
|
530
484
|
}
|
|
531
485
|
let highlighted;
|
|
532
486
|
try {
|
|
@@ -549,7 +503,10 @@ export default function activate(ctx) {
|
|
|
549
503
|
}
|
|
550
504
|
}
|
|
551
505
|
catch {
|
|
552
|
-
highlighted = code
|
|
506
|
+
highlighted = code
|
|
507
|
+
.split("\n")
|
|
508
|
+
.map((l) => `${p.mdCodeBlock}${l}${p.reset}`)
|
|
509
|
+
.join("\n");
|
|
553
510
|
}
|
|
554
511
|
const contentWidth = Math.min(90, width - 2);
|
|
555
512
|
for (const line of highlighted.split("\n")) {
|
|
@@ -711,9 +668,7 @@ export default function activate(ctx) {
|
|
|
711
668
|
if (!s.renderer)
|
|
712
669
|
startAgentResponse();
|
|
713
670
|
showCollapsedThinking();
|
|
714
|
-
|
|
715
|
-
if (!extra?.groupContinuation)
|
|
716
|
-
contentGap("tool");
|
|
671
|
+
contentGap("tool");
|
|
717
672
|
s.renderer.flush();
|
|
718
673
|
drain();
|
|
719
674
|
const lines = renderToolCall({
|
|
@@ -725,37 +680,19 @@ export default function activate(ctx) {
|
|
|
725
680
|
rawInput: extra?.rawInput,
|
|
726
681
|
displayDetail: extra?.displayDetail,
|
|
727
682
|
}, cappedW(), shellCwd);
|
|
728
|
-
if (extra?.groupContinuation && lines.length > 0) {
|
|
729
|
-
// Swap the colored kind icon for a muted tree connector,
|
|
730
|
-
// and strip the tool name prefix — show detail only.
|
|
731
|
-
const detail = extra.displayDetail || extractDetail(extra);
|
|
732
|
-
const maxW = Math.max(1, cappedW() - 6);
|
|
733
|
-
const text = detail.length > maxW ? detail.slice(0, maxW - 1) + "…" : detail;
|
|
734
|
-
lines[0] = detail
|
|
735
|
-
? `${p.muted}├${p.reset} ${p.dim}${text}${p.reset}`
|
|
736
|
-
: lines[0].replace(/^\x1b\[[^m]*m.\x1b\[0m/, `${p.muted}├${p.reset}`);
|
|
737
|
-
}
|
|
738
|
-
const batchPrefix = "";
|
|
739
683
|
for (let i = 0; i < lines.length - 1; i++) {
|
|
740
684
|
s.renderer.writeLine(lines[i]);
|
|
741
685
|
}
|
|
742
686
|
drain();
|
|
743
687
|
if (lines.length > 0) {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
s.
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
s.openTool = {
|
|
753
|
-
callId: extra.toolCallId,
|
|
754
|
-
title,
|
|
755
|
-
kind: extra.kind,
|
|
756
|
-
displayDetail: extra.displayDetail ?? extractDetail(extra),
|
|
757
|
-
};
|
|
758
|
-
}
|
|
688
|
+
out().write(` ${lines[lines.length - 1]}`);
|
|
689
|
+
if (extra?.toolCallId) {
|
|
690
|
+
s.openTool = {
|
|
691
|
+
callId: extra.toolCallId,
|
|
692
|
+
title,
|
|
693
|
+
kind: extra.kind,
|
|
694
|
+
displayDetail: extra.displayDetail ?? extractDetail(extra),
|
|
695
|
+
};
|
|
759
696
|
}
|
|
760
697
|
}
|
|
761
698
|
s.hadToolCalls = true;
|
|
@@ -783,26 +720,6 @@ export default function activate(ctx) {
|
|
|
783
720
|
if (resultDisplay?.body)
|
|
784
721
|
renderResultBody(resultDisplay.body);
|
|
785
722
|
}
|
|
786
|
-
/** Late completion from a finalized group — re-emit the kind header
|
|
787
|
-
* in muted "(cont.)" form so the ⎿ has a legitimate parent, then
|
|
788
|
-
* render the completion as a normal labeled ⎿. Subsequent orphans
|
|
789
|
-
* of the same kind reuse the existing (cont.) header. */
|
|
790
|
-
function showOrphanedComplete(exitCode, resultDisplay, title, kind, displayDetail) {
|
|
791
|
-
if (s.orphanContHeaderKind !== kind) {
|
|
792
|
-
stopCurrentSpinner();
|
|
793
|
-
closeToolLine();
|
|
794
|
-
flushCommandOutput();
|
|
795
|
-
if (!s.renderer)
|
|
796
|
-
startAgentResponse();
|
|
797
|
-
showCollapsedThinking();
|
|
798
|
-
const icon = (kind && KIND_ICONS[kind]) ?? "▶";
|
|
799
|
-
const label = kind ?? "tool";
|
|
800
|
-
s.renderer.writeLine(`${p.muted}${icon} ${label} (cont.)${p.reset}`);
|
|
801
|
-
drain();
|
|
802
|
-
s.orphanContHeaderKind = kind;
|
|
803
|
-
}
|
|
804
|
-
showToolComplete(exitCode, resultDisplay, displayDetail || title);
|
|
805
|
-
}
|
|
806
723
|
function renderResultBody(body) {
|
|
807
724
|
if (!s.renderer)
|
|
808
725
|
return;
|
|
@@ -849,7 +766,7 @@ export default function activate(ctx) {
|
|
|
849
766
|
function closeToolLine() {
|
|
850
767
|
if (s.openTool) {
|
|
851
768
|
out().write("\n");
|
|
852
|
-
// Stash identity so the completion renders as ⎿ labeled, not
|
|
769
|
+
// Stash identity so the completion renders as ⎿ labeled, not inline ✓.
|
|
853
770
|
s.pendingToolCompletes.set(s.openTool.callId, {
|
|
854
771
|
title: s.openTool.title,
|
|
855
772
|
kind: s.openTool.kind,
|
|
@@ -858,40 +775,64 @@ export default function activate(ctx) {
|
|
|
858
775
|
s.openTool = null;
|
|
859
776
|
}
|
|
860
777
|
}
|
|
861
|
-
/** Render
|
|
862
|
-
* completed
|
|
863
|
-
function
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
const skipAggregate = s.toolGroupCount > 1 && s.toolGroupCompletedCount === 0;
|
|
873
|
-
if (s.toolGroupCount <= 1 || skipAggregate) {
|
|
874
|
-
s.toolGroupKind = undefined;
|
|
875
|
-
s.toolGroupCount = 0;
|
|
876
|
-
s.toolGroupCompletedCount = 0;
|
|
877
|
-
s.toolGroupRendered = 0;
|
|
878
|
-
s.toolGroupAllOk = true;
|
|
879
|
-
s.toolGroupSummaries = [];
|
|
778
|
+
/** Render a deferred group as one contiguous block, skipping a group with no
|
|
779
|
+
* completed members (e.g. all errored before their events fired). */
|
|
780
|
+
function finalizeGroup(kind) {
|
|
781
|
+
const group = batchGroups.get(kind);
|
|
782
|
+
if (!group || group.finalized)
|
|
783
|
+
return;
|
|
784
|
+
group.finalized = true;
|
|
785
|
+
const members = group.order
|
|
786
|
+
.map((id) => group.members.get(id))
|
|
787
|
+
.filter((m) => !!m && m.done);
|
|
788
|
+
if (members.length === 0)
|
|
880
789
|
return;
|
|
881
|
-
}
|
|
882
790
|
stopCurrentSpinner();
|
|
883
791
|
closeToolLine();
|
|
884
792
|
if (!s.renderer)
|
|
885
793
|
startAgentResponse();
|
|
886
|
-
|
|
887
|
-
|
|
794
|
+
showCollapsedThinking();
|
|
795
|
+
contentGap("tool");
|
|
796
|
+
s.renderer.flush();
|
|
888
797
|
drain();
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
798
|
+
const icon = KIND_ICONS[kind] ?? "▶";
|
|
799
|
+
const mark = (m) => ctx.call("tui:render-tool-complete", m.ok ? 0 : 1, "", m.summary);
|
|
800
|
+
if (members.length === 1) {
|
|
801
|
+
const m = members[0];
|
|
802
|
+
s.renderer.writeLine(`${p.warning}${icon}${p.reset} ${kind} ${p.dim}${m.detail}${p.reset} ${mark(m)}`);
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
s.renderer.writeLine(`${p.warning}${icon}${p.reset} ${kind}`);
|
|
806
|
+
const shown = Math.min(members.length, GROUP_MAX_VISIBLE);
|
|
807
|
+
const allShown = members.length <= GROUP_MAX_VISIBLE;
|
|
808
|
+
for (let i = 0; i < shown; i++) {
|
|
809
|
+
const m = members[i];
|
|
810
|
+
const connector = allShown && i === shown - 1 ? "└" : "├";
|
|
811
|
+
s.renderer.writeLine(` ${p.muted}${connector}${p.reset} ${p.dim}${m.detail}${p.reset} ${mark(m)}`);
|
|
812
|
+
}
|
|
813
|
+
if (!allShown) {
|
|
814
|
+
s.renderer.writeLine(` ${p.muted}└${p.reset} ${p.dim}+${members.length - shown} more${p.reset}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
drain();
|
|
818
|
+
}
|
|
819
|
+
function finalizeAllGroups() {
|
|
820
|
+
for (const kind of batchGroups.keys())
|
|
821
|
+
finalizeGroup(kind);
|
|
822
|
+
}
|
|
823
|
+
/** Finalize ready groups in batch order, stopping at the first still in
|
|
824
|
+
* flight — so blocks render in dispatch order, not completion order. */
|
|
825
|
+
function flushReadyGroups() {
|
|
826
|
+
for (const [kind, group] of batchGroups) {
|
|
827
|
+
if (group.finalized)
|
|
828
|
+
continue;
|
|
829
|
+
// Eager (non-groupable) groups never increment `completed`, so one earlier
|
|
830
|
+
// in batch order is a barrier here: a deferred group after it renders at
|
|
831
|
+
// the tool-batch-complete backstop, not incrementally. Order is preserved.
|
|
832
|
+
if (group.completed < group.total)
|
|
833
|
+
break;
|
|
834
|
+
finalizeGroup(kind);
|
|
835
|
+
}
|
|
895
836
|
}
|
|
896
837
|
function renderCommandLine(line) {
|
|
897
838
|
return ctx.call("tui:render-command-output", line, s.currentToolKind);
|
package/dist/utils/executor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3
4
|
import { stripAnsi } from "./ansi.js";
|
|
4
5
|
// Node reports a missing cwd as `spawn <binary> ENOENT` — disambiguate.
|
|
5
6
|
function explainSpawnError(err, cwd) {
|
|
@@ -77,21 +78,23 @@ export function executeCommand(opts) {
|
|
|
77
78
|
return { session, done };
|
|
78
79
|
}
|
|
79
80
|
session.process = child;
|
|
80
|
-
const
|
|
81
|
-
|
|
81
|
+
const handleText = (raw) => {
|
|
82
|
+
if (!raw)
|
|
83
|
+
return;
|
|
82
84
|
const clean = stripAnsi(raw);
|
|
83
|
-
// Accumulate cleaned output for the agent
|
|
84
85
|
session.output += clean;
|
|
85
|
-
// Enforce output cap — truncate from beginning, keep tail
|
|
86
86
|
if (session.output.length > maxOutput) {
|
|
87
87
|
session.output = session.output.slice(-maxOutput);
|
|
88
88
|
session.truncated = true;
|
|
89
89
|
}
|
|
90
|
-
// Real-time streaming callback
|
|
91
90
|
opts.onOutput?.(raw);
|
|
92
91
|
};
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
const outDecoder = new StringDecoder("utf-8");
|
|
93
|
+
const errDecoder = new StringDecoder("utf-8");
|
|
94
|
+
child.stdout?.on("data", (d) => handleText(outDecoder.write(d)));
|
|
95
|
+
child.stderr?.on("data", (d) => handleText(errDecoder.write(d)));
|
|
96
|
+
child.stdout?.on("end", () => handleText(outDecoder.end()));
|
|
97
|
+
child.stderr?.on("end", () => handleText(errDecoder.end()));
|
|
95
98
|
let cancelKill;
|
|
96
99
|
const timer = setTimeout(() => {
|
|
97
100
|
if (!session.done) {
|
|
@@ -169,8 +172,9 @@ export function executeArgv(opts) {
|
|
|
169
172
|
return { session, done };
|
|
170
173
|
}
|
|
171
174
|
session.process = child;
|
|
172
|
-
const
|
|
173
|
-
|
|
175
|
+
const handleText = (raw) => {
|
|
176
|
+
if (!raw)
|
|
177
|
+
return;
|
|
174
178
|
const clean = stripAnsi(raw);
|
|
175
179
|
session.output += clean;
|
|
176
180
|
if (session.output.length > maxOutput) {
|
|
@@ -179,8 +183,12 @@ export function executeArgv(opts) {
|
|
|
179
183
|
}
|
|
180
184
|
opts.onOutput?.(raw);
|
|
181
185
|
};
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
const outDecoder = new StringDecoder("utf-8");
|
|
187
|
+
const errDecoder = new StringDecoder("utf-8");
|
|
188
|
+
child.stdout?.on("data", (d) => handleText(outDecoder.write(d)));
|
|
189
|
+
child.stderr?.on("data", (d) => handleText(errDecoder.write(d)));
|
|
190
|
+
child.stdout?.on("end", () => handleText(outDecoder.end()));
|
|
191
|
+
child.stderr?.on("end", () => handleText(errDecoder.end()));
|
|
184
192
|
const timer = setTimeout(() => {
|
|
185
193
|
if (!session.done && session.process) {
|
|
186
194
|
try {
|
|
@@ -637,6 +637,13 @@ export class FloatingPanel {
|
|
|
637
637
|
this.autocompleteItems = [];
|
|
638
638
|
this.autocompleteIndex = 0;
|
|
639
639
|
}
|
|
640
|
+
moveAutocomplete(delta) {
|
|
641
|
+
const n = this.autocompleteItems.length;
|
|
642
|
+
if (n === 0)
|
|
643
|
+
return;
|
|
644
|
+
this.autocompleteIndex = (this.autocompleteIndex + delta + n) % n;
|
|
645
|
+
this.render();
|
|
646
|
+
}
|
|
640
647
|
// ── Input handling ──────────────────────────────────────────
|
|
641
648
|
handleIntercept(payload) {
|
|
642
649
|
const consumed = { ...payload, consumed: true };
|
|
@@ -746,6 +753,16 @@ export class FloatingPanel {
|
|
|
746
753
|
}
|
|
747
754
|
if (this.handleScroll(data, false))
|
|
748
755
|
return;
|
|
756
|
+
if (data === "\x10" || data === "\x0e") {
|
|
757
|
+
const forward = data === "\x0e";
|
|
758
|
+
if (this.autocompleteActive) {
|
|
759
|
+
this.moveAutocomplete(forward ? 1 : -1);
|
|
760
|
+
}
|
|
761
|
+
else if (forward ? this.editor.historyForward() : this.editor.historyBack()) {
|
|
762
|
+
this.render();
|
|
763
|
+
}
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
749
766
|
const actions = this.editor.feed(data);
|
|
750
767
|
for (const action of actions) {
|
|
751
768
|
switch (action.action) {
|
|
@@ -760,6 +777,7 @@ export class FloatingPanel {
|
|
|
760
777
|
this.editor.pushHistory(query);
|
|
761
778
|
this.editor.clear();
|
|
762
779
|
this.clearAutocomplete();
|
|
780
|
+
this.userScrolled = false;
|
|
763
781
|
// Phase change is the submit handler's call — sync slash commands
|
|
764
782
|
// (e.g. /model, /help) keep the user in input mode.
|
|
765
783
|
this.handlers.call(`${this.prefix}:submit`, query);
|
|
@@ -782,34 +800,18 @@ export class FloatingPanel {
|
|
|
782
800
|
case "shift+tab":
|
|
783
801
|
this.render();
|
|
784
802
|
break;
|
|
785
|
-
case "arrow-up":
|
|
786
|
-
if (this.autocompleteActive)
|
|
787
|
-
this.
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
this.render();
|
|
791
|
-
}
|
|
792
|
-
else {
|
|
793
|
-
const hist = this.editor.historyBack();
|
|
794
|
-
if (hist)
|
|
795
|
-
this.render();
|
|
796
|
-
}
|
|
803
|
+
case "arrow-up":
|
|
804
|
+
if (this.autocompleteActive)
|
|
805
|
+
this.moveAutocomplete(-1);
|
|
806
|
+
else
|
|
807
|
+
this.scrollUp(1);
|
|
797
808
|
break;
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
: this.autocompleteIndex + 1;
|
|
804
|
-
this.render();
|
|
805
|
-
}
|
|
806
|
-
else {
|
|
807
|
-
const hist = this.editor.historyForward();
|
|
808
|
-
if (hist)
|
|
809
|
-
this.render();
|
|
810
|
-
}
|
|
809
|
+
case "arrow-down":
|
|
810
|
+
if (this.autocompleteActive)
|
|
811
|
+
this.moveAutocomplete(1);
|
|
812
|
+
else
|
|
813
|
+
this.scrollDown(1);
|
|
811
814
|
break;
|
|
812
|
-
}
|
|
813
815
|
case "changed":
|
|
814
816
|
case "delete-empty":
|
|
815
817
|
this.updateAutocomplete();
|
package/dist/utils/markdown.js
CHANGED
|
@@ -332,27 +332,25 @@ export class MarkdownRenderer {
|
|
|
332
332
|
renderLine(line) {
|
|
333
333
|
if (line.trim() === "")
|
|
334
334
|
return "";
|
|
335
|
-
// Headings
|
|
336
|
-
const
|
|
337
|
-
if (
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return `${p.bold}${
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
return `${p.bold}${h4[1]}${p.reset}`;
|
|
348
|
-
// Horizontal rule — subtle short separator, not full-width
|
|
335
|
+
// Headings — H3+ keep the `###` marker; H1/H2 don't
|
|
336
|
+
const heading = line.match(/^(#{1,6}) (.+)/);
|
|
337
|
+
if (heading) {
|
|
338
|
+
const level = heading[1].length;
|
|
339
|
+
const text = heading[2];
|
|
340
|
+
if (level === 1)
|
|
341
|
+
return `${p.bold}${p.underline}${p.mdHeading}${text}${p.reset}`;
|
|
342
|
+
if (level === 2)
|
|
343
|
+
return `${p.bold}${p.mdHeading}${text}${p.reset}`;
|
|
344
|
+
return `${p.bold}${p.mdHeading}${"#".repeat(level)} ${text}${p.reset}`;
|
|
345
|
+
}
|
|
346
|
+
// Horizontal rule
|
|
349
347
|
if (/^(-{3,}|_{3,}|\*{3,})\s*$/.test(line)) {
|
|
350
|
-
return ""
|
|
348
|
+
return `${p.mdHr}${"─".repeat(Math.min(this.contentWidth, 80))}${p.reset}`;
|
|
351
349
|
}
|
|
352
350
|
// Blockquote
|
|
353
351
|
const bq = line.match(/^>\s?(.*)/);
|
|
354
352
|
if (bq)
|
|
355
|
-
return `${p.
|
|
353
|
+
return `${p.mdQuoteBorder}│${p.reset} ${p.mdQuote}${p.italic}${this.renderInline(bq[1] || "")}${p.reset}`;
|
|
356
354
|
// Task list (checkbox items) — must come before generic unordered list
|
|
357
355
|
const task = line.match(/^(\s*)[*\-+]\s+\[([ xX])\]\s+(.*)/);
|
|
358
356
|
if (task) {
|
|
@@ -367,21 +365,21 @@ export class MarkdownRenderer {
|
|
|
367
365
|
const ul = line.match(/^(\s*)[*\-+]\s+(.*)/);
|
|
368
366
|
if (ul) {
|
|
369
367
|
const indent = ul[1] || "";
|
|
370
|
-
return `${indent} ${p.
|
|
368
|
+
return `${indent} ${p.mdListBullet}-${p.reset} ${this.renderInline(ul[2] || "")}`;
|
|
371
369
|
}
|
|
372
370
|
// Ordered list
|
|
373
371
|
const ol = line.match(/^(\s*)(\d+)[.)]\s+(.*)/);
|
|
374
372
|
if (ol) {
|
|
375
373
|
const indent = ol[1] || "";
|
|
376
|
-
return `${indent} ${p.
|
|
374
|
+
return `${indent} ${p.mdListBullet}${ol[2]}.${p.reset} ${this.renderInline(ol[3] || "")}`;
|
|
377
375
|
}
|
|
378
376
|
return this.renderInline(line);
|
|
379
377
|
}
|
|
380
378
|
renderInline(text) {
|
|
381
379
|
// Links first — later subs inject `\x1b[…m` whose `[` would be eaten here.
|
|
382
|
-
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, `$1
|
|
380
|
+
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, `${p.mdLink}${p.underline}$1${p.reset} ${p.mdLinkUrl}($2)${p.reset}`);
|
|
383
381
|
// Inline code
|
|
384
|
-
text = text.replace(/`([^`]+)`/g, `${p.
|
|
382
|
+
text = text.replace(/`([^`]+)`/g, `${p.mdCode}$1${p.reset}`);
|
|
385
383
|
// Bold + italic
|
|
386
384
|
text = text.replace(/\*\*\*(.+?)\*\*\*/g, `${p.bold}${p.italic}$1${p.reset}`);
|
|
387
385
|
// Bold
|
|
@@ -391,7 +389,7 @@ export class MarkdownRenderer {
|
|
|
391
389
|
text = text.replace(/\*(.+?)\*/g, `${p.italic}$1${p.reset}`);
|
|
392
390
|
text = text.replace(/(?<!\w)_(.+?)_(?!\w)/g, `${p.italic}$1${p.reset}`);
|
|
393
391
|
// Strikethrough
|
|
394
|
-
text = text.replace(/~~(.+?)~~/g, `${p.
|
|
392
|
+
text = text.replace(/~~(.+?)~~/g, `${p.strikethrough}$1${p.reset}`);
|
|
395
393
|
return text;
|
|
396
394
|
}
|
|
397
395
|
/**
|