pi-mono-btw 1.7.0 → 1.7.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # pi-mono-btw
2
2
 
3
+ ## 1.7.4
4
+
5
+ ### Patch Changes
6
+
7
+ ### Maintenance
8
+
9
+ - Update pi core imports and peer dependencies to the new `@earendil-works` package scope.
10
+
11
+ ## 1.7.3
12
+
13
+ ### Patch Changes
14
+
15
+ ### Enhanced: btw
16
+
17
+ - Show `/btw` answers in a focused, dismissible panel with `Esc`/`Enter` close, `↑`/`↓` scrolling, and `x` history clearing.
18
+ - Stop injecting completed `/btw` answers into the visible transcript; hide legacy `btw-answer` messages from older versions.
19
+
20
+ ## 1.7.2
21
+
22
+ ### Patch Changes
23
+
24
+ ### Fixed: ask-user-question
25
+
26
+ - Remove unused `StringEnum` import from `@earendil-works/pi-ai`.
27
+
28
+ ## 1.7.1
29
+
30
+ ### Patch Changes
31
+
32
+ ### Fixed: team-mode
33
+
34
+ - Widget no longer mislabels blocked or approval-pending teams as "running smoothly" — blockers and pending approvals are now detected via team summaries.
35
+ - Preserve in-flight work on re-emitted `session_start` events instead of tearing the runtime down and SIGTERM-ing live teammates.
36
+ - Auto-relaunch leaders for `running` teams after a session reset; surface failures as both a team signal and a UI notification.
37
+ - `createTeam` now defaults `repoRoots` to `[process.cwd()]` when the caller passes an empty array.
38
+ - Archive `process.json` into `history/` before a new task reuses the same role slot, so the prior task's final state is no longer silently clobbered.
39
+
40
+ ### Enhanced: team-mode
41
+
42
+ - Durable intent queue for subprocess handoff: `team_spawn_teammate` calls made from a teammate subprocess are written to disk and executed by the main session's `LeaderRuntime` instead of spawning orphaned grand-children.
43
+ - New tool `team_task_create_batch` lets the leader emit the full initial task DAG in one call, removing per-task LLM round-trips during bootstrap.
44
+ - `team_create` / `launchLeader` accept an `awaitBootstrap` option so the user sees the task graph before the tool returns; leader launch retries up to 3 times on transient failures.
45
+ - Persist per-turn debug artifacts (prompt, invocation, stderr, raw event stream) for both leader and teammate subprocesses, exposed via `TeammateSummary.debugArtifacts`.
46
+ - Track `exitCode`, `exitSignal`, `terminationReason`, `stderrTail`, `toolExecutions`, `model` and `modelProvider` on every `TeammateProcess` record.
47
+ - Provider detection now consults pi's `settings.json` and `auth.json` in addition to env vars; default model IDs aligned with the provider/model scheme.
48
+ - `collectPiOutput` supports `AbortSignal` cancellation.
49
+
50
+ ### Tests
51
+
52
+ - New `intent-queue` and `model-config` suites; expanded coverage across `leader-runtime`, `team-manager`, `team-query-tool` and `formatters`.
53
+
3
54
  ## 1.7.0
4
55
 
5
56
  ### Minor Changes
@@ -10,7 +61,22 @@
10
61
 
11
62
  ### Enhanced: team-mode
12
63
 
64
+ - **LLM-driven leader** — replaced the hardcoded `research → synthesis → implementation → verification` state machine with a pi subprocess coordinator that authors the task graph via tool calls
65
+ - **New tool `team_task_create`** so the leader can author tasks at runtime
66
+ - **New tool `team_handoff`** for explicit teammate → teammate context handoffs (replaces regex-scraping of `Handoffs:` output sections)
67
+ - **File-based teammate specs** — drop `.claude/teammates/<role>.md` frontmatter files (`name`, `description`, `needsWorktree`, `hasMemory`, `modelTier`) to extend or override the seven built-in roles
68
+ - **Event-driven leader wakes** — mailbox messages addressed to the leader (or broadcast) trigger a debounced (~200ms) cycle instead of waiting for the 20s polling tick
69
+ - **Templates accept any string** — `fullstack` / `research` / `refactor` remain as built-ins, but unknown template keys are accepted and no-op gracefully
70
+ - **Provider config per team** — per-team model overrides via `/team models`
13
71
  - Reduced leader overhead and parent-session token churn
72
+ - `spawnTeammate` now always appends the full runtime-built context (signals, mailbox, dependencies, team memory) so teammates get the richer snapshot even when the caller's `context` argument is brief
73
+
74
+ ### Breaking changes: team-mode
75
+
76
+ - Removed `LeaderPhase` enum and `currentPhase` field from `TeamRecord` / `TeamSummary`
77
+ - Removed `parseExplicitHandoffs` export and the legacy `Handoffs:` output parser — peer handoffs must go through the `team_handoff` tool
78
+ - Removed the deterministic auto-spawn loop (`ensureBootstrapTasks`) — all task authoring and teammate spawning is now the LLM leader's responsibility
79
+ - Removed `StringEnum` gate on `team_create`'s `template` parameter (now plain string)
14
80
 
15
81
  ### Fixed: review
16
82
 
@@ -20,6 +86,7 @@
20
86
  ### Documentation
21
87
 
22
88
  - Updated root README and sentinel extension README
89
+ - Documented the new file-based teammate spec format and event-driven leader wake in the team-mode README
23
90
 
24
91
  ## 1.6.0
25
92
 
@@ -66,7 +133,6 @@ Replaced the `grep` extension with a new security-focused `sentinel` extension f
66
133
  - ### `multi-edit` — diverge from upstream fork
67
134
 
68
135
  The extension was originally derived from [mitsuhiko/agent-stuff](https://github.com/mitsuhiko/agent-stuff)'s `pi-extensions/multi-edit.ts`. This release rewrites the largest unmodified subsystems so the implementation is structurally distinct from upstream while keeping the public contract intact.
69
-
70
136
  - **Modularized layout** — the 953-line `index.ts` is split into purpose-scoped modules: `types.ts`, `workspace.ts`, `classic.ts`, `patch.ts`, `diff.ts`, and a slim `index.ts` (~180 lines of registration + dispatch wiring).
71
137
  - **New patch engine** — `patch.ts` is now a recursive-descent parser over a `LineCursor` class with `indexOf`-based hunk anchoring. Hunks are stored as `{ oldBlock, newBlock }` raw strings (previously `{ oldLines[], newLines[] }` arrays), letting the applier splice content directly instead of reconstructing line arrays per apply.
72
138
  - **Two-pass diff renderer** — `diff.ts` now walks `diffLines` parts into a typed `Entry[]` stream and makes all gutter / context-collapse decisions in a second pass, replacing the prior single-loop state-flag design.
@@ -88,20 +154,17 @@ Replaced the `grep` extension with a new security-focused `sentinel` extension f
88
154
  ### Minor Changes
89
155
 
90
156
  - ### `multi-edit` — robustness improvements
91
-
92
157
  - **No-op write guard**: skip file write and `context-guard:file-modified` event when new content is identical to what was last read — prevents unnecessary watcher churn
93
158
  - **Early write-access check**: virtual workspace `checkWriteAccess` now validates real-filesystem permissions during the preflight pass so read-only files fail fast before any real file is touched
94
159
  - **Curly-quote normalization**: new `findActualString` helper falls back to normalized quote matching (`"` / `'` ↔ `"` / `'`) when exact `oldText` search fails — the most common class of preflight mismatch
95
160
  - **Atomic batch rollback**: `applyClassicEdits` gains a `rollbackOnError` option that restores all successfully written files when a later edit in the same batch fails
96
161
 
97
162
  ### `ask-user-question` — UX fixes
98
-
99
163
  - **Reliable text capture on submit**: answer is read directly from the editor before it clears itself, fixing a race where the stored value was always empty
100
164
  - **Unified advance logic**: `advanceTab()` and `saveOtherModeText()` helpers replace scattered single-question fast-paths — behaviour is now consistent regardless of form length
101
165
  - **Auto-advance on Enter / Tab**: pressing Enter or Tab in any question (text, radio with "Other", checkbox with "Other") advances to the next tab without requiring a separate click
102
166
 
103
167
  ### `team-mode` — stability fixes
104
-
105
168
  - **Infinite retry loop eliminated**: subprocess guard (`PI_TEAM_SUBPROCESS=1`) prevents spawned pi subprocesses from launching a ghost `LeaderRuntime` that immediately marks in-progress tasks as stalled
106
169
  - **Stall detection grace period**: tasks updated within the last 2 × `LEADER_POLL_MS` (10 s) are skipped by `detectStalledTasks` — eliminates false positives on the spawning cycle
107
170
  - **Circuit breaker**: tasks that stall more than `MAX_TASK_RETRIES` (3) times are permanently cancelled with a clear error signal instead of being silently re-queued
package/README.md CHANGED
@@ -8,8 +8,9 @@ This extension adds Claude Code-style ` /btw ` behavior to pi.
8
8
  - starts a separate model request immediately
9
9
  - does not queue the question into the main agent loop
10
10
  - does not interrupt the current task
11
- - renders answers in a passive widget below the editor while pi keeps working
12
- - stores hidden history as custom session entries (`btw-history`)
11
+ - renders active requests and answers in a focused `/btw` panel while pi keeps working
12
+ - lets the user dismiss the panel with `Esc`/`Enter`, scroll long answers with `↑`/`↓`, and clear displayed history with `x`
13
+ - stores hidden history as custom session entries (`btw-history`) without injecting answers into the visible transcript or main agent context
13
14
 
14
15
  ## Why it is implemented this way
15
16
 
package/index.ts CHANGED
@@ -1,14 +1,14 @@
1
- import { complete, type UserMessage } from "@mariozechner/pi-ai";
2
- import type { ExtensionAPI, ExtensionContext, Theme } from "@mariozechner/pi-coding-agent";
3
- import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
1
+ import { complete, type UserMessage } from "@earendil-works/pi-ai";
2
+ import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
3
+ import { matchesKey, truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
4
4
 
5
- const BTW_ENTRY_TYPE = "btw-history";
6
- const BTW_WIDGET_ID = "btw-widget";
5
+ const BTW_HISTORY_ENTRY_TYPE = "btw-history";
6
+ const LEGACY_BTW_MESSAGE_TYPE = "btw-answer";
7
7
  const COMPLETED_ITEM_TTL_MS = 90_000;
8
8
  const MAX_TRANSCRIPT_CHARS = 14_000;
9
9
  const MAX_TOOL_RESULT_CHARS = 800;
10
- const MAX_RENDER_ITEMS = 2;
11
- const MAX_RENDERED_ANSWER_LINES = 6;
10
+ const MAX_PANEL_HISTORY_ITEMS = 5;
11
+ const MAX_PANEL_BODY_LINES = 18;
12
12
  const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
13
13
 
14
14
  const SIDE_QUESTION_SYSTEM_PROMPT = [
@@ -58,12 +58,14 @@ type BtwRuntime = {
58
58
  items: BtwItem[];
59
59
  spinnerFrame: number;
60
60
  requestRender?: () => void;
61
+ closePanel?: () => void;
62
+ panelOpen?: boolean;
61
63
  spinnerTimer?: ReturnType<typeof setInterval>;
62
64
  expiryTimer?: ReturnType<typeof setTimeout>;
63
65
  };
64
66
 
65
67
  const runtimes = new Map<string, BtwRuntime>();
66
- const pendingPersistence = new Map<string, BtwRecord[]>();
68
+ const pendingHistory = new Map<string, BtwRecord[]>();
67
69
  let nextItemId = 1;
68
70
 
69
71
  function getSessionKey(ctx: ExtensionContext): string {
@@ -236,42 +238,29 @@ async function askSideQuestion(question: string, ctx: ExtensionContext): Promise
236
238
  return answer || "No response received.";
237
239
  }
238
240
 
239
- function ensureWidget(ctx: ExtensionContext, runtime: BtwRuntime) {
240
- if (!ctx.hasUI) return;
241
-
242
- ctx.ui.setWidget(
243
- BTW_WIDGET_ID,
244
- (tui, theme) => {
245
- runtime.requestRender = () => tui.requestRender();
246
- return new BtwWidget(theme, runtime);
247
- },
248
- { placement: "belowEditor" },
249
- );
250
- }
251
-
252
- function persistOrQueue(pi: ExtensionAPI, ctx: ExtensionContext, record: BtwRecord) {
241
+ function persistOrQueueHistory(pi: ExtensionAPI, ctx: ExtensionContext, record: BtwRecord) {
253
242
  if (ctx.isIdle()) {
254
- pi.appendEntry(BTW_ENTRY_TYPE, record);
243
+ pi.appendEntry(BTW_HISTORY_ENTRY_TYPE, record);
255
244
  return;
256
245
  }
257
246
 
258
247
  const key = getSessionKey(ctx);
259
- const queue = pendingPersistence.get(key) ?? [];
248
+ const queue = pendingHistory.get(key) ?? [];
260
249
  queue.push(record);
261
- pendingPersistence.set(key, queue);
250
+ pendingHistory.set(key, queue);
262
251
  }
263
252
 
264
253
  function flushPendingForCurrentSession(pi: ExtensionAPI, ctx: ExtensionContext) {
265
254
  if (!ctx.isIdle()) return;
266
255
 
267
256
  const key = getSessionKey(ctx);
268
- const queue = pendingPersistence.get(key);
257
+ const queue = pendingHistory.get(key);
269
258
  if (!queue || queue.length === 0) return;
270
259
 
271
260
  for (const record of queue) {
272
- pi.appendEntry(BTW_ENTRY_TYPE, record);
261
+ pi.appendEntry(BTW_HISTORY_ENTRY_TYPE, record);
273
262
  }
274
- pendingPersistence.delete(key);
263
+ pendingHistory.delete(key);
275
264
  }
276
265
 
277
266
  function cleanupExpiredItems(runtime: BtwRuntime) {
@@ -321,7 +310,7 @@ async function startBtw(question: string, pi: ExtensionAPI, ctx: ExtensionContex
321
310
  }
322
311
 
323
312
  const runtime = getRuntime(ctx);
324
- ensureWidget(ctx, runtime);
313
+ ensurePanel(ctx, runtime);
325
314
 
326
315
  const item: BtwItem = {
327
316
  id: `btw-${nextItemId++}`,
@@ -342,7 +331,7 @@ async function startBtw(question: string, pi: ExtensionAPI, ctx: ExtensionContex
342
331
  item.answer = answer;
343
332
  item.answeredAt = new Date().toISOString();
344
333
  item.expiresAt = Date.now() + COMPLETED_ITEM_TTL_MS;
345
- persistOrQueue(pi, ctx, {
334
+ persistOrQueueHistory(pi, ctx, {
346
335
  question,
347
336
  answer,
348
337
  askedAt: item.askedAt,
@@ -355,7 +344,7 @@ async function startBtw(question: string, pi: ExtensionAPI, ctx: ExtensionContex
355
344
  item.error = error instanceof Error ? error.message : String(error);
356
345
  item.answeredAt = new Date().toISOString();
357
346
  item.expiresAt = Date.now() + COMPLETED_ITEM_TTL_MS;
358
- persistOrQueue(pi, ctx, {
347
+ persistOrQueueHistory(pi, ctx, {
359
348
  question,
360
349
  error: item.error,
361
350
  askedAt: item.askedAt,
@@ -381,56 +370,123 @@ function extractBtwQuestion(text: string): string | null {
381
370
  return match[1]?.trim() ?? "";
382
371
  }
383
372
 
384
- class BtwWidget {
373
+ function indentWrapped(text: string, width: number, indent = " "): string[] {
374
+ const bodyWidth = Math.max(12, width - indent.length);
375
+ return text.split("\n").flatMap((line) => {
376
+ const wrapped = wrapTextWithAnsi(line.length > 0 ? line : " ", bodyWidth);
377
+ return wrapped.map((part) => indent + part);
378
+ });
379
+ }
380
+
381
+ function ensurePanel(ctx: ExtensionContext, runtime: BtwRuntime) {
382
+ if (!ctx.hasUI || runtime.panelOpen) {
383
+ runtime.requestRender?.();
384
+ return;
385
+ }
386
+
387
+ runtime.panelOpen = true;
388
+ void ctx.ui
389
+ .custom<void>((tui, theme, _keybindings, done) => {
390
+ const close = () => done();
391
+ runtime.requestRender = () => tui.requestRender();
392
+ runtime.closePanel = close;
393
+ return new BtwPanel(theme, runtime, () => tui.requestRender(), close);
394
+ })
395
+ .finally(() => {
396
+ runtime.panelOpen = false;
397
+ runtime.closePanel = undefined;
398
+ runtime.requestRender = undefined;
399
+ });
400
+ }
401
+
402
+ class BtwPanel {
403
+ private scrollOffset = 0;
404
+
385
405
  constructor(
386
406
  private readonly theme: Theme,
387
407
  private readonly runtime: BtwRuntime,
408
+ private readonly requestRender: () => void,
409
+ private readonly close: () => void,
388
410
  ) {}
389
411
 
412
+ handleInput(data: string): void {
413
+ if (matchesKey(data, "escape") || matchesKey(data, "enter") || matchesKey(data, "return")) {
414
+ this.close();
415
+ return;
416
+ }
417
+
418
+ if (data === "x" || data === "X" || matchesKey(data, "x")) {
419
+ this.runtime.items = this.runtime.items.filter((item) => item.state === "loading");
420
+ this.scrollOffset = 0;
421
+ syncRuntimeTimers(this.runtime);
422
+ if (this.runtime.items.length === 0) {
423
+ this.close();
424
+ } else {
425
+ this.requestRender();
426
+ }
427
+ return;
428
+ }
429
+
430
+ if (matchesKey(data, "up")) {
431
+ this.scrollOffset = Math.max(0, this.scrollOffset - 1);
432
+ this.requestRender();
433
+ return;
434
+ }
435
+
436
+ if (matchesKey(data, "down")) {
437
+ this.scrollOffset += 1;
438
+ this.requestRender();
439
+ return;
440
+ }
441
+
442
+ if (matchesKey(data, "home")) {
443
+ this.scrollOffset = 0;
444
+ this.requestRender();
445
+ }
446
+ }
447
+
390
448
  render(width: number): string[] {
391
449
  cleanupExpiredItems(this.runtime);
392
450
  if (this.runtime.items.length === 0) {
393
- return [];
451
+ return [this.theme.fg("dim", "No /btw history."), this.theme.fg("dim", "Esc to close")].map((line) =>
452
+ truncateToWidth(line, width, "...", true),
453
+ );
394
454
  }
395
455
 
396
456
  const innerWidth = Math.max(24, width);
457
+ const latest = this.runtime.items[0]!;
397
458
  const lines: string[] = [];
398
- const activeCount = this.runtime.items.filter((item) => item.state === "loading").length;
399
- const recentCount = this.runtime.items.filter((item) => item.state !== "loading").length;
400
- const summaryParts = [activeCount > 0 ? `${activeCount} running` : undefined, recentCount > 0 ? `${recentCount} recent` : undefined]
401
- .filter(Boolean)
402
- .join(" · ");
403
-
404
- lines.push(this.theme.fg("accent", "BTW") + (summaryParts ? this.theme.fg("dim", ` · ${summaryParts}`) : ""));
405
- lines.push(this.theme.fg("borderMuted", "─".repeat(Math.max(1, innerWidth - 2))));
406
-
407
- for (const item of this.runtime.items.slice(0, MAX_RENDER_ITEMS)) {
408
- const questionLines = wrapTextWithAnsi(this.theme.fg("accent", `Q: ${item.question}`), innerWidth);
409
- lines.push(...questionLines);
410
-
411
- if (item.state === "loading") {
412
- const frame = SPINNER_FRAMES[this.runtime.spinnerFrame] ?? SPINNER_FRAMES[0]!;
413
- lines.push(this.theme.fg("warning", `${frame} Answering with ${item.model}...`));
414
- } else {
415
- const body = item.state === "error" ? this.theme.fg("error", item.error ?? "Unknown error") : item.answer ?? "";
416
- const wrapped = body
417
- .split("\n")
418
- .flatMap((line) => wrapTextWithAnsi(line.length > 0 ? line : " ", innerWidth));
419
- const clipped = wrapped.slice(0, MAX_RENDERED_ANSWER_LINES);
420
- lines.push(...clipped);
421
- if (wrapped.length > clipped.length) {
422
- lines.push(this.theme.fg("dim", `... ${wrapped.length - clipped.length} more line(s)`));
423
- }
424
- }
459
+ lines.push(this.theme.fg("accent", "/btw"));
460
+ lines.push("");
461
+
462
+ for (const item of this.runtime.items.slice(0, MAX_PANEL_HISTORY_ITEMS).reverse()) {
463
+ const question = `/btw ${item.question}`;
464
+ const color = item.id === latest.id ? "accent" : "dim";
465
+ lines.push(...wrapTextWithAnsi(` ${this.theme.fg(color, question)}`, innerWidth));
466
+ }
467
+
468
+ lines.push("");
425
469
 
426
- lines.push("");
470
+ let bodyLines: string[];
471
+ if (latest.state === "loading") {
472
+ const frame = SPINNER_FRAMES[this.runtime.spinnerFrame] ?? SPINNER_FRAMES[0]!;
473
+ bodyLines = [this.theme.fg("warning", ` ${frame} Answering…`)];
474
+ } else if (latest.state === "error") {
475
+ bodyLines = indentWrapped(this.theme.fg("error", latest.error ?? "Unknown error"), innerWidth);
476
+ } else {
477
+ bodyLines = indentWrapped(latest.answer ?? "No response received.", innerWidth);
427
478
  }
428
479
 
429
- if (lines[lines.length - 1] === "") {
430
- lines.pop();
480
+ const maxOffset = Math.max(0, bodyLines.length - MAX_PANEL_BODY_LINES);
481
+ this.scrollOffset = Math.min(this.scrollOffset, maxOffset);
482
+ lines.push(...bodyLines.slice(this.scrollOffset, this.scrollOffset + MAX_PANEL_BODY_LINES));
483
+
484
+ if (bodyLines.length > MAX_PANEL_BODY_LINES) {
485
+ lines.push(this.theme.fg("dim", ` ... ${bodyLines.length - this.scrollOffset - MAX_PANEL_BODY_LINES} more line(s)`));
431
486
  }
432
487
 
433
- lines.push(this.theme.fg("dim", "Use /btw <question> anytime, even while pi is still working."));
488
+ lines.push("");
489
+ lines.push(this.theme.fg("dim", "↑/↓ to scroll · x to clear history · Enter/Esc to close"));
434
490
  return lines.map((line) => truncateToWidth(line, width, "...", true));
435
491
  }
436
492
 
@@ -438,8 +494,13 @@ class BtwWidget {
438
494
  }
439
495
 
440
496
  export default function (pi: ExtensionAPI) {
441
- const attachCurrentSessionWidget = (_event: unknown, ctx: ExtensionContext) => {
442
- ensureWidget(ctx, getRuntime(ctx));
497
+ // Hide answers written by older versions of this extension. New answers are
498
+ // shown in the focused /btw panel only, so users can dismiss them with Esc.
499
+ pi.registerMessageRenderer(LEGACY_BTW_MESSAGE_TYPE, () => {
500
+ return { render: () => [], invalidate: () => {} };
501
+ });
502
+
503
+ const onSessionStart = (_event: unknown, ctx: ExtensionContext) => {
443
504
  flushPendingForCurrentSession(pi, ctx);
444
505
  };
445
506
 
@@ -447,13 +508,18 @@ export default function (pi: ExtensionAPI) {
447
508
  flushPendingForCurrentSession(pi, ctx);
448
509
  };
449
510
 
450
- pi.on("session_start", attachCurrentSessionWidget);
451
- pi.on("session_switch", attachCurrentSessionWidget);
511
+ pi.on("session_start", onSessionStart);
512
+ pi.on("context", (event) => ({
513
+ messages: event.messages.filter(
514
+ (message) => !(message.role === "custom" && (message as { customType?: string }).customType === LEGACY_BTW_MESSAGE_TYPE),
515
+ ),
516
+ }));
452
517
  pi.on("agent_end", flush);
453
518
  pi.on("session_before_switch", flush);
454
519
  pi.on("session_before_fork", flush);
455
520
  pi.on("session_shutdown", (_event, _ctx) => {
456
521
  for (const runtime of runtimes.values()) {
522
+ runtime.closePanel?.();
457
523
  if (runtime.spinnerTimer) clearInterval(runtime.spinnerTimer);
458
524
  if (runtime.expiryTimer) clearTimeout(runtime.expiryTimer);
459
525
  }
@@ -468,6 +534,8 @@ export default function (pi: ExtensionAPI) {
468
534
  return { action: "continue" as const };
469
535
  }
470
536
 
537
+ flushPendingForCurrentSession(pi, ctx);
538
+
471
539
  const question = extractBtwQuestion(event.text);
472
540
  if (question === null) {
473
541
  return { action: "continue" as const };
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "pi-mono-btw",
3
- "version": "1.7.0",
3
+ "version": "1.7.4",
4
4
  "description": "Pi extension that answers side questions while the main agent keeps running",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi-extension"
8
8
  ],
9
9
  "peerDependencies": {
10
- "@mariozechner/pi-ai": "*",
11
- "@mariozechner/pi-coding-agent": "*",
12
- "@mariozechner/pi-tui": "*",
10
+ "@earendil-works/pi-ai": "*",
11
+ "@earendil-works/pi-coding-agent": "*",
12
+ "@earendil-works/pi-tui": "*",
13
13
  "@sinclair/typebox": "*"
14
14
  },
15
15
  "pi": {