@esso0428/pi-subagents 0.17.21 → 0.17.23

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,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.17.23] - 2026-10-02
11
+
12
+ ### Fixed
13
+ - **Stopped the agents roster from stealing keys from open modals**: the widget's focus detection used `focused instanceof Editor`, which is always false when the extension resolves its own copy of `@earendil-works/pi-tui` at a different version than pi's host (for example a hoisted peer pinned to an older release). The check silently degraded to "focused is null", so the widget's global `onTerminalInput` listener claimed the keyboard during teardown and transient render states and consumed `↑`/`↓`/`enter`/`escape` from modals such as the `/agents` menus. Focus detection now identifies the prompt editor structurally and fails closed, so the listener only claims input while the editor provably owns it.
14
+
15
+ ## [0.17.22] - 2026-10-01
16
+
17
+ ### Changed
18
+ - **Clarified the wait-group result retrieval flow**: background and wait-group completion notifications, the `Agent` tool description, prompt guidelines, and background spawn results now instruct the parent agent to call `get_subagent_result` once per completed task ID with `wait` omitted or `false`. This restores Pi's native expandable `Get Subagent Result` tool-result UI without reintroducing blocking waits, since custom `sendMessage` notifications cannot render native tool-result cards.
19
+
10
20
  ## [0.17.21] - 2026-10-01
11
21
 
12
22
  ### Fixed
package/README.md CHANGED
@@ -91,7 +91,7 @@ Agent({
91
91
  })
92
92
  ```
93
93
 
94
- `subagent_wait_group` supports `create`, `update`, and `seal`. A sealed group sends one notification after every member reaches a terminal state (`completed`, `steered`, `error`, `stopped`, or `aborted`). If `wait: true` is used without `wait_group`, the extension creates and seals a one-agent implicit group.
94
+ `subagent_wait_group` supports `create`, `update`, and `seal`. A sealed group sends one notification after every member reaches a terminal state (`completed`, `steered`, `error`, `stopped`, or `aborted`). If `wait: true` is used without `wait_group`, the extension creates and seals a one-agent implicit group. After a completion notification, call `get_subagent_result` once per completed task ID with `wait` omitted or `false` to render native expandable result cards without blocking.
95
95
 
96
96
  ### Scheduling
97
97
 
@@ -435,7 +435,7 @@ Check status and retrieve results from a background agent.
435
435
  | `wait` | boolean | no | Deprecated compatibility flag; never blocks |
436
436
  | `verbose` | boolean | no | Include full conversation log |
437
437
 
438
- `get_subagent_result` never blocks. If an agent is still running or queued, the tool returns the current status immediately; wait for the background or wait-group completion notification for the expandable final output.
438
+ `get_subagent_result` never blocks. If an agent is still running or queued, the tool returns the current status immediately. After a background or wait-group completion notification, call it with `wait` omitted or `false` to retrieve the final output through Pi's native expandable tool-result UI.
439
439
 
440
440
  ### `steer_subagent`
441
441
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esso0428/pi-subagents",
3
- "version": "0.17.21",
3
+ "version": "0.17.23",
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/index.ts CHANGED
@@ -321,6 +321,10 @@ export default function (pi: ExtensionAPI) {
321
321
  }
322
322
  }
323
323
 
324
+ const resultRetrievalInstruction =
325
+ "Next action: call get_subagent_result once for each <task-id> above, with wait omitted or false, " +
326
+ "to render native expandable Get Subagent Result tool output. Do not pass wait:true.";
327
+
324
328
  // ---- Individual nudge helper (async join mode) ----
325
329
  function emitIndividualNudge(record: AgentRecord) {
326
330
  if (record.resultConsumed) return; // re-check at send time
@@ -330,7 +334,7 @@ export default function (pi: ExtensionAPI) {
330
334
 
331
335
  pi.sendMessage<NotificationDetails>({
332
336
  customType: "subagent-notification",
333
- content: notification + footer,
337
+ content: `${notification + footer}\n\n${resultRetrievalInstruction}`,
334
338
  display: true,
335
339
  details: buildNotificationDetails(record, 500, agentActivity.get(record.id)),
336
340
  }, { deliverAs: "followUp", triggerTurn: true });
@@ -370,7 +374,7 @@ export default function (pi: ExtensionAPI) {
370
374
 
371
375
  pi.sendMessage<NotificationDetails>({
372
376
  customType: "subagent-notification",
373
- content: `Background agent group completed: ${label}\n\n${notifications}\n\nUse get_subagent_result for full output.`,
377
+ content: `Background agent group completed: ${label}\n\n${notifications}\n\n${resultRetrievalInstruction}`,
374
378
  display: true,
375
379
  details,
376
380
  }, { deliverAs: "followUp", triggerTurn: true });
@@ -408,7 +412,7 @@ export default function (pi: ExtensionAPI) {
408
412
 
409
413
  pi.sendMessage<NotificationDetails>({
410
414
  customType: "subagent-notification",
411
- content: `Background agent wait group completed: ${summary} (group ${groupId}).${consumedNote}\n\n${notifications}\n\nUse get_subagent_result for full output.`,
415
+ content: `Background agent wait group completed: ${summary} (group ${groupId}).${consumedNote}\n\n${notifications}\n\n${resultRetrievalInstruction}`,
412
416
  display: true,
413
417
  details,
414
418
  }, { deliverAs: "followUp", triggerTurn: true });
@@ -900,6 +904,7 @@ If the target is already known, use a direct tool — \`read\` for a known path,
900
904
  - Trust but verify: an agent's summary describes what it intended to do, not necessarily what it did. When an agent writes or edits code, check the actual changes before reporting work as done.
901
905
  - Use run_in_background for work you don't need immediately. You will be notified when it completes — do NOT poll or sleep waiting for it. Continue with other work or respond to the user instead.
902
906
  - For nonblocking grouped notification, set wait: true with run_in_background: true. Omit wait_group for a one-agent implicit group, or create an explicit group with subagent_wait_group and seal it (or set wait_group_done: true on the final Agent call).
907
+ - When a background or wait-group completion notification arrives, call get_subagent_result once per completed task-id with wait omitted or false before summarizing if the user needs the outputs. This preserves native expandable Get Subagent Result UI without blocking.
903
908
  - Foreground vs background: use foreground (default) when you need the agent's results before you can proceed. Use background when you have genuinely independent work to do in parallel.
904
909
  - Use resume with an agent ID to continue a previous agent's work. A new (non-resume) Agent call starts a fresh agent with no memory of prior runs, so the prompt must be self-contained.
905
910
  - Use steer_subagent to send mid-run messages to a running background agent.
@@ -980,6 +985,7 @@ Terse command-style prompts produce shallow, generic work.
980
985
  "For broad codebase exploration or research, spawn Agent with an appropriate subagent_type (e.g. Explore). Otherwise use direct tools (read, grep, find) when the target is already known.",
981
986
  "When an agent runs in the background, you will be notified on completion — do not poll or sleep waiting for it. Continue with other work instead.",
982
987
  "For a nonblocking grouped notification, use wait: true with run_in_background: true; create/update/seal explicit groups with subagent_wait_group.",
988
+ "When a background or wait-group completion notification arrives, call get_subagent_result once per completed task-id with wait omitted or false before summarizing if the user needs the outputs; this preserves native expandable result UI without blocking.",
983
989
  "Trust but verify: an agent's summary describes intent, not outcome. When an agent writes or edits code, check the actual changes before reporting work as done.",
984
990
  ],
985
991
  parameters: Type.Object({
@@ -1447,7 +1453,7 @@ Terse command-style prompts produce shallow, generic work.
1447
1453
  ? `\nWait group: ${effectiveWaitGroupId}${implicitWaitGroupId || waitGroupDone ? " (sealed)" : " (open — seal it with subagent_wait_group)"}.\n` +
1448
1454
  `You will receive one grouped notification when the sealed wait group completes.\n`
1449
1455
  : `\nYou will be notified when this agent completes.\n`) +
1450
- `Use get_subagent_result to retrieve full results, or steer_subagent to send it messages.\n` +
1456
+ `After the completion notification, call get_subagent_result with wait omitted or false to render native expandable results; use steer_subagent to send messages while it is running.\n` +
1451
1457
  `Do not duplicate this agent's work.`,
1452
1458
  { ...detailBase, toolUses: 0, tokens: "", durationMs: 0, status: "background" as const, agentId: id },
1453
1459
  );
@@ -5,7 +5,7 @@
5
5
  * Uses the callback form of setWidget for themed rendering.
6
6
  */
7
7
 
8
- import { Editor, isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
8
+ import { 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";
@@ -238,6 +238,24 @@ export function describeActivity(activeTools: Map<string, string>, responseText?
238
238
  return "thinking…";
239
239
  }
240
240
 
241
+ /**
242
+ * Structural check for pi's prompt editor.
243
+ *
244
+ * `instanceof Editor` is unusable here: an extension can resolve its own copy
245
+ * of `@earendil-works/pi-tui` at a different version than the host's, and the
246
+ * two `Editor` class objects are then unrelated identities, so the check is
247
+ * always false. Modals opened with `ctx.ui.custom()` — including the `/agents`
248
+ * menus — focus a plain `{ render, invalidate, handleInput }` object instead,
249
+ * so the editor's text-buffer methods identify it across pi-tui copies.
250
+ */
251
+ function isPromptEditor(component: unknown): boolean {
252
+ if (typeof component !== "object" || component === null) return false;
253
+ const candidate = component as { getText?: unknown; setText?: unknown; submitValue?: unknown };
254
+ return typeof candidate.getText === "function"
255
+ && typeof candidate.setText === "function"
256
+ && typeof candidate.submitValue === "function";
257
+ }
258
+
241
259
  // ---- Widget manager ----
242
260
 
243
261
  export class AgentWidget {
@@ -373,10 +391,17 @@ export class AgentWidget {
373
391
  return [...running, ...queued, ...finished];
374
392
  }
375
393
 
376
- /** True when pi's prompt editor owns the keyboard. */
394
+ /**
395
+ * True when pi's prompt editor owns the keyboard.
396
+ *
397
+ * Deliberately fails closed: an unknown or absent focus target means the
398
+ * widget does not own the keyboard, so its global terminal listener stays out
399
+ * of the way. The previous `focused == null` fallback claimed input during
400
+ * teardown and transient render states, which let roster navigation swallow
401
+ * ↑/↓/enter/escape from an open modal.
402
+ */
377
403
  private editorHasFocus(): boolean {
378
- const focused = (this.tui as { focusedComponent?: unknown } | undefined)?.focusedComponent;
379
- return focused == null || focused instanceof Editor;
404
+ return isPromptEditor((this.tui as { focusedComponent?: unknown } | undefined)?.focusedComponent);
380
405
  }
381
406
 
382
407
  private selectedIndexOf(records: readonly AgentRecord[]): number {