pi-interactive-shell 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/index.ts +53 -19
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  All notable changes to the `pi-interactive-shell` extension will be documented in this file.
4
4
 
5
+ ## [0.5.1] - 2026-01-22
6
+
7
+ ### Fixed
8
+ - **Prevent overlay stacking** - Starting a new `interactive_shell` session or using `/attach` while an overlay is already open now returns an error instead of causing undefined behavior with stacked/stuck overlays.
9
+
5
10
  ## [0.5.0] - 2026-01-22
6
11
 
7
12
  ### Changed
package/index.ts CHANGED
@@ -8,6 +8,9 @@ import { translateInput } from "./key-encoding.js";
8
8
  import { TOOL_NAME, TOOL_LABEL, TOOL_DESCRIPTION, toolParameters, type ToolParams } from "./tool-schema.js";
9
9
  import { formatDuration, formatDurationMs } from "./types.js";
10
10
 
11
+ // Track whether an overlay is currently open to prevent stacking
12
+ let overlayOpen = false;
13
+
11
14
  export default function interactiveShellExtension(pi: ExtensionAPI) {
12
15
  pi.on("session_shutdown", () => {
13
16
  sessionManager.killAll();
@@ -370,12 +373,24 @@ export default function interactiveShellExtension(pi: ExtensionAPI) {
370
373
  const config = loadConfig(effectiveCwd);
371
374
  const isHandsFree = mode === "hands-free";
372
375
 
376
+ // Prevent starting a new overlay while one is already open
377
+ if (overlayOpen) {
378
+ return {
379
+ content: [{ type: "text", text: "An interactive shell overlay is already open. Wait for it to close or kill the active session before starting a new one." }],
380
+ isError: true,
381
+ details: { error: "overlay_already_open" },
382
+ };
383
+ }
384
+
373
385
  // Generate sessionId early so it's available immediately
374
386
  const generatedSessionId = isHandsFree ? generateSessionId(name) : undefined;
375
387
 
376
388
  // For hands-free mode: non-blocking - return immediately with sessionId
377
389
  // Agent can then query status/output via sessionId and kill when done
378
390
  if (isHandsFree && generatedSessionId) {
391
+ // Mark overlay as open
392
+ overlayOpen = true;
393
+
379
394
  // Start overlay but don't await - it runs in background
380
395
  const overlayPromise = ctx.ui.custom<InteractiveShellResult>(
381
396
  (tui, theme, _kb, done) =>
@@ -421,12 +436,14 @@ export default function interactiveShellExtension(pi: ExtensionAPI) {
421
436
 
422
437
  // Handle overlay completion in background (cleanup when user closes)
423
438
  overlayPromise.then((result) => {
439
+ overlayOpen = false;
424
440
  // Session already handles cleanup via finishWith* methods
425
441
  // This just ensures the promise doesn't cause unhandled rejection
426
442
  if (result.userTookOver) {
427
443
  // User took over - session continues interactively
428
444
  }
429
445
  }).catch(() => {
446
+ overlayOpen = false;
430
447
  // Ignore errors - session cleanup handles this
431
448
  });
432
449
 
@@ -448,6 +465,7 @@ export default function interactiveShellExtension(pi: ExtensionAPI) {
448
465
  }
449
466
 
450
467
  // Interactive mode: blocking - wait for overlay to close
468
+ overlayOpen = true;
451
469
  onUpdate?.({
452
470
  content: [{ type: "text", text: `Opening: ${command}` }],
453
471
  details: {
@@ -457,7 +475,9 @@ export default function interactiveShellExtension(pi: ExtensionAPI) {
457
475
  },
458
476
  });
459
477
 
460
- const result = await ctx.ui.custom<InteractiveShellResult>(
478
+ let result: InteractiveShellResult;
479
+ try {
480
+ result = await ctx.ui.custom<InteractiveShellResult>(
461
481
  (tui, theme, _kb, done) =>
462
482
  new InteractiveShellOverlay(
463
483
  tui,
@@ -532,6 +552,9 @@ export default function interactiveShellExtension(pi: ExtensionAPI) {
532
552
  },
533
553
  },
534
554
  );
555
+ } finally {
556
+ overlayOpen = false;
557
+ }
535
558
 
536
559
  let summary: string;
537
560
  if (result.backgrounded) {
@@ -569,6 +592,12 @@ export default function interactiveShellExtension(pi: ExtensionAPI) {
569
592
  pi.registerCommand("attach", {
570
593
  description: "Reattach to a background shell session",
571
594
  handler: async (args, ctx) => {
595
+ // Prevent reattaching while another overlay is open
596
+ if (overlayOpen) {
597
+ ctx.ui.notify("An overlay is already open. Close it first.", "error");
598
+ return;
599
+ }
600
+
572
601
  const sessions = sessionManager.list();
573
602
 
574
603
  if (sessions.length === 0) {
@@ -601,25 +630,30 @@ export default function interactiveShellExtension(pi: ExtensionAPI) {
601
630
  }
602
631
 
603
632
  const config = loadConfig(ctx.cwd);
604
- await ctx.ui.custom<InteractiveShellResult>(
605
- (tui, theme, _kb, done) =>
606
- new ReattachOverlay(
607
- tui,
608
- theme,
609
- { id: session.id, command: session.command, reason: session.reason, session: session.session },
610
- config,
611
- done,
612
- ),
613
- {
614
- overlay: true,
615
- overlayOptions: {
616
- width: `${config.overlayWidthPercent}%`,
617
- maxHeight: `${config.overlayHeightPercent}%`,
618
- anchor: "center",
619
- margin: 1,
633
+ overlayOpen = true;
634
+ try {
635
+ await ctx.ui.custom<InteractiveShellResult>(
636
+ (tui, theme, _kb, done) =>
637
+ new ReattachOverlay(
638
+ tui,
639
+ theme,
640
+ { id: session.id, command: session.command, reason: session.reason, session: session.session },
641
+ config,
642
+ done,
643
+ ),
644
+ {
645
+ overlay: true,
646
+ overlayOptions: {
647
+ width: `${config.overlayWidthPercent}%`,
648
+ maxHeight: `${config.overlayHeightPercent}%`,
649
+ anchor: "center",
650
+ margin: 1,
651
+ },
620
652
  },
621
- },
622
- );
653
+ );
654
+ } finally {
655
+ overlayOpen = false;
656
+ }
623
657
  },
624
658
  });
625
659
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-interactive-shell",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Run AI coding agents as foreground subagents in pi TUI overlays with hands-free monitoring",
5
5
  "type": "module",
6
6
  "bin": {