pi-subagent-in-memory 0.1.4 → 0.1.5
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/extensions/index.ts +268 -139
- package/package.json +1 -1
package/extensions/index.ts
CHANGED
|
@@ -41,13 +41,34 @@ import { renderCard, type CardTheme } from "./tui-draw.ts";
|
|
|
41
41
|
|
|
42
42
|
import { visibleWidth, truncateToWidth, wrapTextWithAnsi, matchesKey, Key } from "@mariozechner/pi-tui";
|
|
43
43
|
import type { Focusable } from "@mariozechner/pi-tui";
|
|
44
|
-
import { mkdirSync, writeFileSync
|
|
44
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
45
|
+
import { appendFile } from "node:fs/promises";
|
|
45
46
|
import { join } from "node:path";
|
|
46
47
|
import { randomUUID } from "node:crypto";
|
|
47
48
|
|
|
48
49
|
// ── JSONL event logger ──────────────────────────────────────────
|
|
50
|
+
const jsonlWriteQueues = new Map<string, Promise<void>>();
|
|
51
|
+
|
|
49
52
|
function jsonlAppend(filePath: string, data: Record<string, any>) {
|
|
50
|
-
|
|
53
|
+
const line = JSON.stringify(data) + "\n";
|
|
54
|
+
const prev = jsonlWriteQueues.get(filePath) ?? Promise.resolve();
|
|
55
|
+
const next = prev.catch(() => {}).then(() => appendFile(filePath, line, "utf-8"));
|
|
56
|
+
jsonlWriteQueues.set(filePath, next);
|
|
57
|
+
return next;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function flushJsonl(filePath: string) {
|
|
61
|
+
const pending = jsonlWriteQueues.get(filePath);
|
|
62
|
+
if (!pending) return;
|
|
63
|
+
try {
|
|
64
|
+
await pending;
|
|
65
|
+
} catch {
|
|
66
|
+
// Best-effort logging; tool execution should not fail because log flushing failed.
|
|
67
|
+
} finally {
|
|
68
|
+
if (jsonlWriteQueues.get(filePath) === pending) {
|
|
69
|
+
jsonlWriteQueues.delete(filePath);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
51
72
|
}
|
|
52
73
|
|
|
53
74
|
// ── Subagent card state ─────────────────────────────────────────
|
|
@@ -73,6 +94,11 @@ const CARD_THEMES: CardTheme[] = [
|
|
|
73
94
|
{ bg: "\x1b[48;2;15;55;30m", br: "\x1b[38;2;50;185;100m" },
|
|
74
95
|
];
|
|
75
96
|
|
|
97
|
+
const MAX_CARD_MESSAGE_CHARS = 16_000;
|
|
98
|
+
const MAX_PARTIAL_UPDATE_CHARS = 4_000;
|
|
99
|
+
const PARTIAL_UPDATE_INTERVAL_MS = 200;
|
|
100
|
+
const WIDGET_ANIMATION_INTERVAL_MS = 500;
|
|
101
|
+
|
|
76
102
|
function formatElapsed(startedAt: number, endedAt?: number): string {
|
|
77
103
|
const elapsed = Math.floor(((endedAt ?? Date.now()) - startedAt) / 1000);
|
|
78
104
|
const m = Math.floor(elapsed / 60);
|
|
@@ -80,127 +106,199 @@ function formatElapsed(startedAt: number, endedAt?: number): string {
|
|
|
80
106
|
return m > 0 ? `${m}m ${s}s` : `${s}s`;
|
|
81
107
|
}
|
|
82
108
|
|
|
109
|
+
function hasActiveSubagents() {
|
|
110
|
+
return subagents.some((sa) => sa.status === "running" || sa.status === "created");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function trimCardMessages(card: SubagentCard) {
|
|
114
|
+
if (card.messages.length <= MAX_CARD_MESSAGE_CHARS) return;
|
|
115
|
+
let trimmed = card.messages.slice(-MAX_CARD_MESSAGE_CHARS);
|
|
116
|
+
const firstNewline = trimmed.indexOf("\n");
|
|
117
|
+
if (firstNewline >= 0) {
|
|
118
|
+
trimmed = trimmed.slice(firstNewline + 1);
|
|
119
|
+
}
|
|
120
|
+
card.messages = `…\n${trimmed}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function appendMessage(card: SubagentCard, msg: string) {
|
|
124
|
+
card.messages += (card.messages ? "\n" : "") + msg;
|
|
125
|
+
trimCardMessages(card);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function appendMessageChunk(card: SubagentCard, chunk: string) {
|
|
129
|
+
card.messages += chunk;
|
|
130
|
+
trimCardMessages(card);
|
|
131
|
+
}
|
|
132
|
+
|
|
83
133
|
// ── Shared state — single instance across all nesting levels ────
|
|
84
134
|
const subagents: SubagentCard[] = [];
|
|
85
135
|
let currentCtx: { ui: any } | null = null;
|
|
86
136
|
let mainSessionId = "unknown";
|
|
87
137
|
let subagentCount = 0;
|
|
88
138
|
let flashTimer: ReturnType<typeof setInterval> | null = null;
|
|
139
|
+
let widgetTui: any = null;
|
|
140
|
+
let widgetMounted = false;
|
|
141
|
+
let widgetRenderVersion = 0;
|
|
89
142
|
|
|
90
143
|
// Track open detail overlay so we can trigger re-renders
|
|
91
144
|
let activeDetailTui: any = null;
|
|
92
145
|
|
|
93
|
-
function
|
|
94
|
-
if (
|
|
95
|
-
const ctx = currentCtx;
|
|
146
|
+
function renderSubagentCards(theme: any, width: number): string[] {
|
|
147
|
+
if (subagents.length === 0) return [];
|
|
96
148
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
149
|
+
// Derive cols from columnWidthPercent (all cards share the same value).
|
|
150
|
+
const pct = subagents[subagents.length - 1].columnWidthPercent;
|
|
151
|
+
const cols = Math.min(3, Math.max(1, Math.round(100 / pct)));
|
|
152
|
+
const gap = 1;
|
|
153
|
+
const colWidth = Math.floor((width - gap * (cols - 1)) / cols);
|
|
154
|
+
const maxContentLines = 4;
|
|
155
|
+
const lines: string[] = [""];
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < subagents.length; i += cols) {
|
|
158
|
+
const rowCards = subagents.slice(i, i + cols).map((sa, idx) => {
|
|
159
|
+
const cardTheme = CARD_THEMES[(i + idx) % CARD_THEMES.length];
|
|
160
|
+
|
|
161
|
+
const titleText = `${sa.title} [${sa.modelLabel}]`;
|
|
162
|
+
const innerW = colWidth - 4;
|
|
163
|
+
|
|
164
|
+
const allText = sa.prompt || "…";
|
|
165
|
+
const contentLines = allText.split("\n");
|
|
166
|
+
const trimmedLines = contentLines.map((l) =>
|
|
167
|
+
visibleWidth(l) > innerW ? truncateToWidth(l, innerW - 1) + "…" : l
|
|
168
|
+
);
|
|
169
|
+
const visible = trimmedLines.slice(0, maxContentLines);
|
|
170
|
+
const content = visible.join("\n") + (contentLines.length > maxContentLines ? "\n…" : "");
|
|
171
|
+
|
|
172
|
+
let statusRaw: string;
|
|
173
|
+
if (sa.status === "created") {
|
|
174
|
+
statusRaw = "⏳ started";
|
|
175
|
+
} else if (sa.status === "running") {
|
|
176
|
+
const dotPhase = Math.floor(Date.now() / 2000) % 3;
|
|
177
|
+
statusRaw = "⚡ working" + ".".repeat(dotPhase + 1);
|
|
178
|
+
} else if (sa.status === "completed") {
|
|
179
|
+
statusRaw = "✅ finished";
|
|
180
|
+
} else {
|
|
181
|
+
statusRaw = "❌ error";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const STATUS_WIDTH = 14;
|
|
185
|
+
const visPad = Math.max(0, STATUS_WIDTH - visibleWidth(statusRaw));
|
|
186
|
+
const elapsed = formatElapsed(sa.startedAt, sa.endedAt);
|
|
187
|
+
const footer = `${statusRaw}${" ".repeat(visPad)} ${elapsed}`;
|
|
188
|
+
|
|
189
|
+
return renderCard({
|
|
190
|
+
title: titleText,
|
|
191
|
+
badge: `#${sa.num}`,
|
|
192
|
+
content,
|
|
193
|
+
footer,
|
|
194
|
+
footerRight: `Ctrl+${sa.num}`,
|
|
195
|
+
colWidth,
|
|
196
|
+
theme,
|
|
197
|
+
cardTheme,
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
while (rowCards.length < cols) {
|
|
202
|
+
rowCards.push(Array(rowCards[0].length).fill(" ".repeat(colWidth)));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const cardHeight = Math.max(...rowCards.map((c) => c.length));
|
|
206
|
+
for (const card of rowCards) {
|
|
207
|
+
while (card.length < cardHeight) {
|
|
208
|
+
card.push(" ".repeat(colWidth));
|
|
111
209
|
}
|
|
112
|
-
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (let row = 0; row < cardHeight; row++) {
|
|
213
|
+
lines.push(rowCards.map((card) => card[row]).join(" ".repeat(gap)));
|
|
214
|
+
}
|
|
113
215
|
}
|
|
114
216
|
|
|
115
|
-
|
|
217
|
+
return lines;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
class SubagentCardsWidget {
|
|
221
|
+
private cachedWidth = -1;
|
|
222
|
+
private cachedVersion = -1;
|
|
223
|
+
private cachedLines: string[] = [];
|
|
224
|
+
|
|
225
|
+
constructor(
|
|
226
|
+
private tui: any,
|
|
227
|
+
private theme: any,
|
|
228
|
+
) {
|
|
229
|
+
widgetTui = tui;
|
|
230
|
+
widgetMounted = true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
render(width: number): string[] {
|
|
234
|
+
if (width === this.cachedWidth && this.cachedVersion === widgetRenderVersion) {
|
|
235
|
+
return this.cachedLines;
|
|
236
|
+
}
|
|
237
|
+
this.cachedWidth = width;
|
|
238
|
+
this.cachedVersion = widgetRenderVersion;
|
|
239
|
+
this.cachedLines = renderSubagentCards(this.theme, width);
|
|
240
|
+
return this.cachedLines;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
invalidate(): void {
|
|
244
|
+
this.cachedWidth = -1;
|
|
245
|
+
this.cachedVersion = -1;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
dispose(): void {
|
|
249
|
+
if (widgetTui === this.tui) widgetTui = null;
|
|
250
|
+
widgetMounted = false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function ensureSubagentWidget() {
|
|
255
|
+
if (!currentCtx || widgetMounted) return;
|
|
256
|
+
currentCtx.ui.setWidget(
|
|
116
257
|
"in-memory-subagent-cards",
|
|
117
|
-
|
|
258
|
+
(tui: any, theme: any) => new SubagentCardsWidget(tui, theme),
|
|
118
259
|
{ placement: "aboveEditor" }
|
|
119
260
|
);
|
|
120
261
|
}
|
|
121
262
|
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const rowCards = subagents.slice(i, i + cols).map((sa, idx) => {
|
|
137
|
-
const cardTheme = CARD_THEMES[(i + idx) % CARD_THEMES.length];
|
|
138
|
-
|
|
139
|
-
const titleText = `${sa.title} [${sa.modelLabel}]`;
|
|
140
|
-
const innerW = colWidth - 4;
|
|
141
|
-
|
|
142
|
-
// Show the prompt as content
|
|
143
|
-
const allText = sa.prompt || "…";
|
|
144
|
-
const contentLines = allText.split("\n");
|
|
145
|
-
const trimmedLines = contentLines.map((l) =>
|
|
146
|
-
visibleWidth(l) > innerW ? truncateToWidth(l, innerW - 1) + "…" : l
|
|
147
|
-
);
|
|
148
|
-
const visible = trimmedLines.slice(0, maxContentLines);
|
|
149
|
-
const content = visible.join("\n") + (contentLines.length > maxContentLines ? "\n…" : "");
|
|
150
|
-
|
|
151
|
-
// Status indicator with animated dots for "working"
|
|
152
|
-
// All status strings are padded to the same visible width so the
|
|
153
|
-
// elapsed-time counter stays in a fixed column.
|
|
154
|
-
let statusRaw: string;
|
|
155
|
-
if (sa.status === "created") {
|
|
156
|
-
statusRaw = "⏳ started";
|
|
157
|
-
} else if (sa.status === "running") {
|
|
158
|
-
const dotPhase = Math.floor(Date.now() / 2000) % 3;
|
|
159
|
-
statusRaw = "⚡ working" + ".".repeat(dotPhase + 1);
|
|
160
|
-
} else if (sa.status === "completed") {
|
|
161
|
-
statusRaw = "✅ finished";
|
|
162
|
-
} else {
|
|
163
|
-
statusRaw = "❌ error";
|
|
164
|
-
}
|
|
165
|
-
// Pad to fixed visible width (longest is "⚡ working..." or "✅ finished")
|
|
166
|
-
const STATUS_WIDTH = 14;
|
|
167
|
-
const visPad = Math.max(0, STATUS_WIDTH - visibleWidth(statusRaw));
|
|
168
|
-
const elapsed = formatElapsed(sa.startedAt, sa.endedAt);
|
|
169
|
-
const footer = `${statusRaw}${" ".repeat(visPad)} ${elapsed}`;
|
|
170
|
-
|
|
171
|
-
return renderCard({
|
|
172
|
-
title: titleText,
|
|
173
|
-
badge: `#${sa.num}`,
|
|
174
|
-
content,
|
|
175
|
-
footer,
|
|
176
|
-
footerRight: `Ctrl+${sa.num}`,
|
|
177
|
-
colWidth,
|
|
178
|
-
theme,
|
|
179
|
-
cardTheme,
|
|
180
|
-
});
|
|
181
|
-
});
|
|
263
|
+
function syncAnimationTimer() {
|
|
264
|
+
const needsAnimation = hasActiveSubagents() || !!activeDetailTui;
|
|
265
|
+
if (needsAnimation && !flashTimer) {
|
|
266
|
+
flashTimer = setInterval(() => {
|
|
267
|
+
if (!hasActiveSubagents() && !activeDetailTui) {
|
|
268
|
+
syncAnimationTimer();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
widgetRenderVersion++;
|
|
272
|
+
try { widgetTui?.requestRender(); } catch {}
|
|
273
|
+
try { activeDetailTui?.requestRender(); } catch {}
|
|
274
|
+
}, WIDGET_ANIMATION_INTERVAL_MS);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
182
277
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
278
|
+
if (!needsAnimation && flashTimer) {
|
|
279
|
+
clearInterval(flashTimer);
|
|
280
|
+
flashTimer = null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
187
283
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
card.push(" ".repeat(colWidth));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
284
|
+
function requestSubagentRender() {
|
|
285
|
+
widgetRenderVersion++;
|
|
286
|
+
syncAnimationTimer();
|
|
194
287
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
288
|
+
if (!currentCtx) return;
|
|
289
|
+
if (subagents.length === 0) {
|
|
290
|
+
if (widgetMounted) {
|
|
291
|
+
currentCtx.ui.setWidget("in-memory-subagent-cards", undefined);
|
|
292
|
+
widgetMounted = false;
|
|
293
|
+
widgetTui = null;
|
|
294
|
+
}
|
|
295
|
+
try { activeDetailTui?.requestRender(); } catch {}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
199
298
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
});
|
|
299
|
+
ensureSubagentWidget();
|
|
300
|
+
try { widgetTui?.requestRender(); } catch {}
|
|
301
|
+
try { activeDetailTui?.requestRender(); } catch {}
|
|
204
302
|
}
|
|
205
303
|
|
|
206
304
|
// ── Detail overlay component ────────────────────────────────────
|
|
@@ -310,6 +408,8 @@ class SubagentDetailOverlay implements Focusable {
|
|
|
310
408
|
invalidate(): void {}
|
|
311
409
|
dispose(): void {
|
|
312
410
|
activeDetailTui = null;
|
|
411
|
+
syncAnimationTimer();
|
|
412
|
+
requestSubagentRender();
|
|
313
413
|
}
|
|
314
414
|
}
|
|
315
415
|
|
|
@@ -349,11 +449,6 @@ const SubagentParams = Type.Object({
|
|
|
349
449
|
|
|
350
450
|
type SubagentParamsType = Static<typeof SubagentParams>;
|
|
351
451
|
|
|
352
|
-
// ── Helper: append to card messages ─────────────────────────────
|
|
353
|
-
function appendMessage(card: SubagentCard, msg: string) {
|
|
354
|
-
card.messages += (card.messages ? "\n" : "") + msg;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
452
|
// ── Core execution logic ────────────────────────────────────────
|
|
358
453
|
async function executeSubagent(
|
|
359
454
|
toolCallId: string,
|
|
@@ -443,7 +538,7 @@ async function executeSubagent(
|
|
|
443
538
|
startedAt: Date.now(),
|
|
444
539
|
};
|
|
445
540
|
subagents.push(card);
|
|
446
|
-
|
|
541
|
+
requestSubagentRender();
|
|
447
542
|
|
|
448
543
|
onUpdate?.({
|
|
449
544
|
content: [{ type: "text", text: `Subagent session created: ${session.sessionId}` }],
|
|
@@ -464,7 +559,7 @@ async function executeSubagent(
|
|
|
464
559
|
card.status = "error";
|
|
465
560
|
card.endedAt = Date.now();
|
|
466
561
|
appendMessage(card, "[aborted]");
|
|
467
|
-
|
|
562
|
+
requestSubagentRender();
|
|
468
563
|
};
|
|
469
564
|
|
|
470
565
|
try {
|
|
@@ -472,6 +567,34 @@ async function executeSubagent(
|
|
|
472
567
|
let finalText = "";
|
|
473
568
|
let textDeltaBuffer = "";
|
|
474
569
|
let toolcallDeltaBuffer = "";
|
|
570
|
+
let lastPartialUpdateAt = 0;
|
|
571
|
+
let lastPartialUpdateText = "";
|
|
572
|
+
|
|
573
|
+
const buildPartialText = (extraLine?: string) => {
|
|
574
|
+
let text = finalText;
|
|
575
|
+
if (text.length > MAX_PARTIAL_UPDATE_CHARS) {
|
|
576
|
+
text = `…${text.slice(-MAX_PARTIAL_UPDATE_CHARS)}`;
|
|
577
|
+
}
|
|
578
|
+
if (extraLine) {
|
|
579
|
+
text = text ? `${text}\n${extraLine}` : extraLine;
|
|
580
|
+
}
|
|
581
|
+
return text || "...";
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const emitPartialUpdate = (details: Record<string, any>, extraLine?: string, force = false) => {
|
|
585
|
+
const now = Date.now();
|
|
586
|
+
if (!force && now - lastPartialUpdateAt < PARTIAL_UPDATE_INTERVAL_MS) return;
|
|
587
|
+
|
|
588
|
+
const text = buildPartialText(extraLine);
|
|
589
|
+
if (!force && text === lastPartialUpdateText) return;
|
|
590
|
+
|
|
591
|
+
lastPartialUpdateAt = now;
|
|
592
|
+
lastPartialUpdateText = text;
|
|
593
|
+
onUpdate?.({
|
|
594
|
+
content: [{ type: "text", text }],
|
|
595
|
+
details,
|
|
596
|
+
});
|
|
597
|
+
};
|
|
475
598
|
|
|
476
599
|
session.subscribe((event) => {
|
|
477
600
|
const updateData: Record<string, any> = {
|
|
@@ -487,7 +610,7 @@ async function executeSubagent(
|
|
|
487
610
|
case "agent_start":
|
|
488
611
|
card.status = "running";
|
|
489
612
|
appendMessage(card, "[agent started]");
|
|
490
|
-
|
|
613
|
+
requestSubagentRender();
|
|
491
614
|
jsonlAppend(jsonlPath, baseLog);
|
|
492
615
|
lastEventId = eventId;
|
|
493
616
|
onUpdate?.({
|
|
@@ -502,13 +625,10 @@ async function executeSubagent(
|
|
|
502
625
|
textDeltaBuffer += ame.delta;
|
|
503
626
|
finalText += ame.delta;
|
|
504
627
|
// Append delta text to messages for live view
|
|
505
|
-
card
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
...updateData,
|
|
510
|
-
data: { assistantMessageEventType: ame.type, delta: ame.delta },
|
|
511
|
-
},
|
|
628
|
+
appendMessageChunk(card, ame.delta);
|
|
629
|
+
emitPartialUpdate({
|
|
630
|
+
...updateData,
|
|
631
|
+
data: { assistantMessageEventType: ame.type, textLength: finalText.length },
|
|
512
632
|
});
|
|
513
633
|
} else if (ame.type === "text_end") {
|
|
514
634
|
if (textDeltaBuffer) {
|
|
@@ -518,6 +638,10 @@ async function executeSubagent(
|
|
|
518
638
|
jsonlAppend(jsonlPath, { ...baseLog, data: { assistantMessageEventType: ame.type } });
|
|
519
639
|
}
|
|
520
640
|
lastEventId = eventId;
|
|
641
|
+
emitPartialUpdate({
|
|
642
|
+
...updateData,
|
|
643
|
+
data: { assistantMessageEventType: ame.type, textLength: finalText.length },
|
|
644
|
+
}, undefined, true);
|
|
521
645
|
} else if (ame.type === "toolcall_delta") {
|
|
522
646
|
if ("delta" in ame) toolcallDeltaBuffer += (ame as any).delta ?? "";
|
|
523
647
|
} else if (ame.type === "toolcall_end") {
|
|
@@ -538,43 +662,38 @@ async function executeSubagent(
|
|
|
538
662
|
}
|
|
539
663
|
|
|
540
664
|
case "tool_execution_start":
|
|
541
|
-
appendMessage(card,
|
|
665
|
+
appendMessage(card, `[🔧 ${event.toolName} ⏳]`);
|
|
542
666
|
jsonlAppend(jsonlPath, { ...baseLog, toolName: event.toolName, args: event.args });
|
|
543
667
|
lastEventId = eventId;
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
{ type: "text", text: finalText + `\n[Tool: ${event.toolName}]` },
|
|
547
|
-
],
|
|
548
|
-
details: {
|
|
668
|
+
emitPartialUpdate(
|
|
669
|
+
{
|
|
549
670
|
...updateData,
|
|
550
671
|
data: { toolName: event.toolName, args: event.args },
|
|
551
672
|
},
|
|
552
|
-
|
|
673
|
+
`[Tool: ${event.toolName}]`,
|
|
674
|
+
true,
|
|
675
|
+
);
|
|
553
676
|
break;
|
|
554
677
|
|
|
555
678
|
case "tool_execution_end":
|
|
556
679
|
appendMessage(card, `[🔧 ${event.toolName} ${event.isError ? "❌" : "✅"}]`);
|
|
557
680
|
jsonlAppend(jsonlPath, { ...baseLog, toolName: event.toolName, isError: event.isError, result: event.result });
|
|
558
681
|
lastEventId = eventId;
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
{
|
|
562
|
-
type: "text",
|
|
563
|
-
text: finalText + `\n[Tool: ${event.toolName} ${event.isError ? "❌" : "✅"}]`,
|
|
564
|
-
},
|
|
565
|
-
],
|
|
566
|
-
details: {
|
|
682
|
+
emitPartialUpdate(
|
|
683
|
+
{
|
|
567
684
|
...updateData,
|
|
568
685
|
data: { toolName: event.toolName, isError: event.isError },
|
|
569
686
|
},
|
|
570
|
-
|
|
687
|
+
`[Tool: ${event.toolName} ${event.isError ? "❌" : "✅"}]`,
|
|
688
|
+
true,
|
|
689
|
+
);
|
|
571
690
|
break;
|
|
572
691
|
|
|
573
692
|
case "agent_end":
|
|
574
693
|
card.status = "completed";
|
|
575
694
|
card.endedAt = Date.now();
|
|
576
|
-
appendMessage(card, "
|
|
577
|
-
|
|
695
|
+
appendMessage(card, "[agent completed]");
|
|
696
|
+
requestSubagentRender();
|
|
578
697
|
jsonlAppend(jsonlPath, { ...baseLog, finalTextLength: finalText.length });
|
|
579
698
|
resolve(finalText || "Subagent completed with no text output.");
|
|
580
699
|
break;
|
|
@@ -585,10 +704,7 @@ async function executeSubagent(
|
|
|
585
704
|
case "message_end":
|
|
586
705
|
jsonlAppend(jsonlPath, baseLog);
|
|
587
706
|
lastEventId = eventId;
|
|
588
|
-
|
|
589
|
-
content: [{ type: "text", text: finalText || "..." }],
|
|
590
|
-
details: updateData,
|
|
591
|
-
});
|
|
707
|
+
emitPartialUpdate(updateData);
|
|
592
708
|
break;
|
|
593
709
|
|
|
594
710
|
default:
|
|
@@ -613,13 +729,14 @@ async function executeSubagent(
|
|
|
613
729
|
card.status = "error";
|
|
614
730
|
card.endedAt = Date.now();
|
|
615
731
|
appendMessage(card, `[error: ${err?.message ?? String(err)}]`);
|
|
616
|
-
|
|
732
|
+
requestSubagentRender();
|
|
617
733
|
reject(err);
|
|
618
734
|
});
|
|
619
735
|
});
|
|
620
736
|
|
|
621
737
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
622
738
|
session.dispose();
|
|
739
|
+
await flushJsonl(jsonlPath);
|
|
623
740
|
|
|
624
741
|
const resultPath = join(outDir, "result.md");
|
|
625
742
|
writeFileSync(resultPath, result, "utf-8");
|
|
@@ -631,6 +748,7 @@ async function executeSubagent(
|
|
|
631
748
|
} catch (err: any) {
|
|
632
749
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
633
750
|
session.dispose();
|
|
751
|
+
await flushJsonl(jsonlPath);
|
|
634
752
|
|
|
635
753
|
const errorMsg = err?.message ?? String(err);
|
|
636
754
|
const errorPath = join(outDir, "error.md");
|
|
@@ -681,13 +799,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
681
799
|
currentCtx = ctx;
|
|
682
800
|
mainSessionId = ctx.sessionManager.getSessionId?.() ?? `session-${Date.now()}`;
|
|
683
801
|
subagentCount = 0;
|
|
684
|
-
|
|
802
|
+
subagents.length = 0;
|
|
803
|
+
activeDetailTui = null;
|
|
804
|
+
widgetMounted = false;
|
|
805
|
+
widgetTui = null;
|
|
806
|
+
if (flashTimer) { clearInterval(flashTimer); flashTimer = null; }
|
|
807
|
+
ctx.ui.setWidget("in-memory-subagent-cards", undefined);
|
|
808
|
+
requestSubagentRender();
|
|
685
809
|
});
|
|
686
810
|
|
|
687
811
|
pi.registerCommand("in-memory-clear-widgets", {
|
|
688
812
|
description: "Clear all in-memory subagent card widgets",
|
|
689
813
|
handler: async (_args, ctx) => {
|
|
690
814
|
subagents.length = 0;
|
|
815
|
+
activeDetailTui = null;
|
|
816
|
+
widgetMounted = false;
|
|
817
|
+
widgetTui = null;
|
|
691
818
|
if (flashTimer) { clearInterval(flashTimer); flashTimer = null; }
|
|
692
819
|
ctx.ui.setWidget("in-memory-subagent-cards", undefined);
|
|
693
820
|
ctx.ui.notify("In-memory subagent widgets cleared", "info");
|
|
@@ -722,6 +849,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
722
849
|
);
|
|
723
850
|
|
|
724
851
|
activeDetailTui = null;
|
|
852
|
+
syncAnimationTimer();
|
|
853
|
+
requestSubagentRender();
|
|
725
854
|
},
|
|
726
855
|
});
|
|
727
856
|
}
|
package/package.json
CHANGED