@esso0428/pi-subagents 0.17.26 → 0.17.28

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
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.17.28] - 2026-10-02
11
+
12
+ ### Fixed
13
+ - **Stopped the agents widget from stealing arrow keys from `/settings` and other extensions' menus**: v0.17.24 replaced the widget's focus probe with "no modal of ours is open", which is strictly weaker than the check it removed. pi routes terminal input to extension listeners *before* the focused component, and it swaps the prompt editor out for every dialog while `getEditorText()` still reports the detached, empty editor — so the empty-prompt gate passed and the widget consumed `↑`/`↓`/`enter`/`escape` from pi's own `/settings`, from other extensions' selectors such as `qa-ask`, and from our `/agents` menus. Focus detection is restored to `focused == null || focused instanceof Editor`, kept alongside the `withModal()` tracking added in v0.17.25 so our own dialogs are additionally guaranteed priority.
14
+
15
+ Roster `↑`/`↓` navigation is consequently inert in installations where this extension resolves its own copy of `@earendil-works/pi-tui` at a different version than pi's host, because the two `Editor` classes are then unrelated objects. That is deliberate: `SettingsList` (what `/settings` focuses) is itself a pi-tui class instance, so no structural probe can tell it apart from the editor, and `setEditorComponent()` lets any extension wrap the editor (`pi-tmux-cursor-focus` subclasses it under tmux), so class identity is the only test that can. Inert is the safe failure — it disables this one feature instead of every other menu in the host.
16
+
17
+ ## [0.17.27] - 2026-10-02
18
+
19
+ ### Fixed
20
+ - **Stopped the agents widget from drawing two tree terminators at once**: v0.17.26 taught the clipped render path to mark the final visible row as `└─`, but the footer's stable-height placeholder for the "scrolled to the end with rows hidden above" case was itself a bare `└─`, so both appeared stacked. That placeholder now reserves the row with blank indentation instead.
21
+
10
22
  ## [0.17.26] - 2026-10-02
11
23
 
12
24
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esso0428/pi-subagents",
3
- "version": "0.17.26",
3
+ "version": "0.17.28",
4
4
  "description": "A pi extension that brings smart Claude Code-style autonomous sub-agents to pi, with npm:pi-subagents-style JSON agent overrides.",
5
5
  "author": "ESSO0428",
6
6
  "repository": {
@@ -5,7 +5,7 @@
5
5
  * Uses the callback form of setWidget for themed rendering.
6
6
  */
7
7
 
8
- import { isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
8
+ import { Editor, isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
9
9
  import type { AgentManager } from "../agent-manager.js";
10
10
  import { getConfig } from "../agent-types.js";
11
11
  import type { AgentInvocation, AgentRecord, SubagentType, WidgetMode } from "../types.js";
@@ -394,22 +394,44 @@ export class AgentWidget {
394
394
  * Track an AgentWidget-owned modal (the `/agents` menus and friends).
395
395
  *
396
396
  * The widget's terminal listener runs before the focused component, so while
397
- * a modal is open the listener must not claim ↑/↓/enter/escape. Whether pi's
398
- * prompt editor currently owns the keyboard is deliberately not consulted:
399
- * pi can replace it through `ctx.ui.setEditorComponent()` (for example
400
- * pi-tmux-cursor-focus wraps it under tmux), and the extension may also
401
- * resolve its own copy of `@earendil-works/pi-tui` at a different version
402
- * than the host's. Neither `instanceof` nor a structural probe can identify
403
- * the editor reliably in that environment, and a wrong answer silently
404
- * disables roster navigation entirely.
397
+ * a modal is open the listener must not claim ↑/↓/enter/escape.
405
398
  */
406
399
  setModalOpen(open: boolean): void {
407
400
  this.modalDepth = open ? this.modalDepth + 1 : Math.max(0, this.modalDepth - 1);
408
401
  }
409
402
 
410
- /** True when no AgentWidget-owned modal is holding the keyboard. */
411
- private keyboardAvailable(): boolean {
412
- return this.modalDepth === 0;
403
+ /**
404
+ * True when pi's prompt editor owns the keyboard.
405
+ *
406
+ * Both halves of this are load-bearing, and both were arrived at the hard way.
407
+ *
408
+ * A terminal-input listener runs BEFORE the focused component, and pi swaps
409
+ * the prompt editor out for every dialog while `getEditorText()` keeps
410
+ * reporting the detached — empty — editor. Without this guard the widget eats
411
+ * ↑/↓/enter/escape from `/settings`, from other extensions' pickers, and from
412
+ * pi's own menus (upstream issue #123). Only the prompt editor may claim keys.
413
+ *
414
+ * `focused == null` errs toward the editor so list activation keeps working.
415
+ * `focused instanceof Editor` is deliberately NOT replaced with a structural
416
+ * probe: pi's own `/settings` focuses `SettingsList`, a pi-tui class instance,
417
+ * so no shape test separates it from the editor, and another extension can
418
+ * wrap the editor through `setEditorComponent()` (pi-tmux-cursor-focus does
419
+ * exactly that whenever pi runs under tmux), so class identity is the only
420
+ * test that can.
421
+ *
422
+ * That leaves one known failure mode, which is why this is conservative
423
+ * rather than clever: if this extension resolves its own copy of
424
+ * `@earendil-works/pi-tui` at a different version than the host's, the two
425
+ * `Editor` classes are unrelated objects, the check is always false, and
426
+ * roster ↑/↓ stays inert. Inert is the safe failure — it breaks this one
427
+ * feature instead of every other menu in the host. Aligning the peer so the
428
+ * check works is a dependency-graph change in the host project, not something
429
+ * to paper over here.
430
+ */
431
+ private editorHasFocus(): boolean {
432
+ if (this.modalDepth > 0) return false;
433
+ const focused = (this.tui as { focusedComponent?: unknown } | undefined)?.focusedComponent;
434
+ return focused == null || focused instanceof Editor;
413
435
  }
414
436
 
415
437
  private selectedIndexOf(records: readonly AgentRecord[]): number {
@@ -451,7 +473,7 @@ export class AgentWidget {
451
473
  if (records.length === 0 || !ui) return false;
452
474
 
453
475
  if (!this.navigationActive) {
454
- if (direction !== 1 || !this.keyboardAvailable() || (ui.getEditorText?.() ?? "") !== "") return false;
476
+ if (direction !== 1 || !this.editorHasFocus() || (ui.getEditorText?.() ?? "") !== "") return false;
455
477
  this.activate(records);
456
478
  return true;
457
479
  }
@@ -479,7 +501,7 @@ export class AgentWidget {
479
501
  /** Handle terminal input before it reaches the focused prompt editor. */
480
502
  handleKey(data: string): { consume?: boolean; data?: string } | undefined {
481
503
  if (!this.uiCtx || isKeyRelease(data)) return undefined;
482
- if (!this.keyboardAvailable()) {
504
+ if (!this.editorHasFocus()) {
483
505
  if (this.navigationActive) this.deactivate();
484
506
  return undefined;
485
507
  }
@@ -751,8 +773,10 @@ export class AgentWidget {
751
773
  } else if (!showTopMore) {
752
774
  lines.push(truncate(theme.fg("dim", "└─") + ` ${theme.fg("dim", summary)}`));
753
775
  } else {
754
- // Keep a stable footer row when the selected viewport reaches the end.
755
- lines.push(truncate(theme.fg("dim", "└─")));
776
+ // Scrolled to the end with rows hidden above. The final visible roster
777
+ // row already carries the tree terminator, so this row only reserves
778
+ // height for a stable footer — repeating `└─` would draw two of them.
779
+ lines.push(" ");
756
780
  }
757
781
  }
758
782