@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 +12 -0
- package/package.json +1 -1
- package/src/ui/agent-widget.ts +40 -16
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.
|
|
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": {
|
package/src/ui/agent-widget.ts
CHANGED
|
@@ -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.
|
|
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
|
-
/**
|
|
411
|
-
|
|
412
|
-
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
755
|
-
|
|
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
|
|