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.
Files changed (2) hide show
  1. package/extensions/index.ts +268 -139
  2. package/package.json +1 -1
@@ -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, appendFileSync } from "node:fs";
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
- appendFileSync(filePath, JSON.stringify(data) + "\n", "utf-8");
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 updateSubagentWidget() {
94
- if (!currentCtx) return;
95
- const ctx = currentCtx;
146
+ function renderSubagentCards(theme: any, width: number): string[] {
147
+ if (subagents.length === 0) return [];
96
148
 
97
- // Start flash timer for "working..." dot animation if not already running
98
- if (!flashTimer) {
99
- flashTimer = setInterval(() => {
100
- const hasActive = subagents.some((sa) => sa.status === "running" || sa.status === "created");
101
- if (currentCtx && (hasActive || activeDetailTui)) {
102
- currentCtx.ui.setWidget(
103
- "in-memory-subagent-cards",
104
- buildWidgetFactory(),
105
- { placement: "aboveEditor" }
106
- );
107
- // Also re-render the detail overlay if open
108
- if (activeDetailTui) {
109
- try { activeDetailTui.requestRender(); } catch {}
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
- }, 500);
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
- ctx.ui.setWidget(
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
- buildWidgetFactory(),
258
+ (tui: any, theme: any) => new SubagentCardsWidget(tui, theme),
118
259
  { placement: "aboveEditor" }
119
260
  );
120
261
  }
121
262
 
122
- function buildWidgetFactory() {
123
- return (_tui: any, theme: any) => ({
124
- render(width: number): string[] {
125
- if (subagents.length === 0) return [];
126
-
127
- // Derive cols from columnWidthPercent (all cards share the same value).
128
- const pct = subagents[subagents.length - 1].columnWidthPercent;
129
- const cols = Math.min(3, Math.max(1, Math.round(100 / pct)));
130
- const gap = 1;
131
- const colWidth = Math.floor((width - gap * (cols - 1)) / cols);
132
- const maxContentLines = 4;
133
- const lines: string[] = [""];
134
-
135
- for (let i = 0; i < subagents.length; i += cols) {
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
- // Pad incomplete rows
184
- while (rowCards.length < cols) {
185
- rowCards.push(Array(rowCards[0].length).fill(" ".repeat(colWidth)));
186
- }
278
+ if (!needsAnimation && flashTimer) {
279
+ clearInterval(flashTimer);
280
+ flashTimer = null;
281
+ }
282
+ }
187
283
 
188
- const cardHeight = Math.max(...rowCards.map((c) => c.length));
189
- for (const card of rowCards) {
190
- while (card.length < cardHeight) {
191
- card.push(" ".repeat(colWidth));
192
- }
193
- }
284
+ function requestSubagentRender() {
285
+ widgetRenderVersion++;
286
+ syncAnimationTimer();
194
287
 
195
- for (let row = 0; row < cardHeight; row++) {
196
- lines.push(rowCards.map((card) => card[row]).join(" ".repeat(gap)));
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
- return lines;
201
- },
202
- invalidate() {},
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
- updateSubagentWidget();
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
- updateSubagentWidget();
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
- updateSubagentWidget();
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.messages += ame.delta;
506
- onUpdate?.({
507
- content: [{ type: "text", text: finalText }],
508
- details: {
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, `\n[🔧 ${event.toolName} ⏳]`);
665
+ appendMessage(card, `[🔧 ${event.toolName} ⏳]`);
542
666
  jsonlAppend(jsonlPath, { ...baseLog, toolName: event.toolName, args: event.args });
543
667
  lastEventId = eventId;
544
- onUpdate?.({
545
- content: [
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
- onUpdate?.({
560
- content: [
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, "\n[agent completed]");
577
- updateSubagentWidget();
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
- onUpdate?.({
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
- updateSubagentWidget();
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
- updateSubagentWidget();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagent-in-memory",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "In-process subagent tool for pi with live TUI card widgets, JSONL session logging, and zero system-prompt overhead.",
5
5
  "repository": {
6
6
  "type": "git",