@esso0428/pi-subagents 0.17.24 → 0.17.26

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.26] - 2026-10-02
11
+
12
+ ### Fixed
13
+ - **Drew the last agents roster row as `└─` when the roster overflows**: the branch fixup only ran on the path where every row fits, so a clipped or scrolled roster kept `├─` on its final visible entry. The bounded viewport also pads the body with blank lines, so the row is not at the end of the rendered output; both render paths now share a `markLastBranch()` helper that resolves the final visible row explicitly and also blanks the `│` continuation under it.
14
+
15
+ ## [0.17.25] - 2026-10-02
16
+
17
+ ### Fixed
18
+ - **Suspended the roster key listener for the remaining widget-owned dialogs**: v0.17.24 routed `ctx.ui.select`, `ctx.ui.editor`, and `ctx.ui.custom` through `withModal()` but left `ctx.ui.input` and `ctx.ui.confirm` outside it. Those dialogs are reached only after a wrapped modal has already cleared roster navigation, so the listener could still swallow a stray `↓`, and any future path that opens one directly would lose `escape`/`↑`/`↓`/`enter` entirely. Every remaining modal call now runs inside `withModal()` as well.
19
+
10
20
  ## [0.17.24] - 2026-10-02
11
21
 
12
22
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esso0428/pi-subagents",
3
- "version": "0.17.24",
3
+ "version": "0.17.26",
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
@@ -2158,7 +2158,7 @@ Terse command-style prompts produce shallow, generic work.
2158
2158
  }
2159
2159
  } else if (choice === "Delete") {
2160
2160
  if (file) {
2161
- const confirmed = await ctx.ui.confirm("Delete agent", `Delete ${name} from ${file.location} (${file.path})?`);
2161
+ const confirmed = await withModal(() => ctx.ui.confirm("Delete agent", `Delete ${name} from ${file.location} (${file.path})?`));
2162
2162
  if (confirmed) {
2163
2163
  unlinkSync(file.path);
2164
2164
  reloadCustomAgents();
@@ -2166,7 +2166,7 @@ Terse command-style prompts produce shallow, generic work.
2166
2166
  }
2167
2167
  }
2168
2168
  } else if (choice === "Reset to default" && file) {
2169
- const confirmed = await ctx.ui.confirm("Reset to default", `Delete override ${file.path} and restore embedded default?`);
2169
+ const confirmed = await withModal(() => ctx.ui.confirm("Reset to default", `Delete override ${file.path} and restore embedded default?`));
2170
2170
  if (confirmed) {
2171
2171
  unlinkSync(file.path);
2172
2172
  reloadCustomAgents();
@@ -2194,7 +2194,7 @@ Terse command-style prompts produce shallow, generic work.
2194
2194
 
2195
2195
  const targetPath = join(targetDir, `${name}.md`);
2196
2196
  if (existsSync(targetPath)) {
2197
- const overwrite = await ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`);
2197
+ const overwrite = await withModal(() => ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`));
2198
2198
  if (!overwrite) return;
2199
2199
  }
2200
2200
 
@@ -2307,17 +2307,17 @@ Terse command-style prompts produce shallow, generic work.
2307
2307
  }
2308
2308
 
2309
2309
  async function showGenerateWizard(ctx: ExtensionCommandContext, targetDir: string) {
2310
- const description = await ctx.ui.input("Describe what this agent should do");
2310
+ const description = await withModal(() => ctx.ui.input("Describe what this agent should do"));
2311
2311
  if (!description) return;
2312
2312
 
2313
- const name = await ctx.ui.input("Agent name (filename, no spaces)");
2313
+ const name = await withModal(() => ctx.ui.input("Agent name (filename, no spaces)"));
2314
2314
  if (!name) return;
2315
2315
 
2316
2316
  mkdirSync(targetDir, { recursive: true });
2317
2317
 
2318
2318
  const targetPath = join(targetDir, `${name}.md`);
2319
2319
  if (existsSync(targetPath)) {
2320
- const overwrite = await ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`);
2320
+ const overwrite = await withModal(() => ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`));
2321
2321
  if (!overwrite) return;
2322
2322
  }
2323
2323
 
@@ -2384,11 +2384,11 @@ Write the file using the write tool. Only write the file, nothing else.`;
2384
2384
 
2385
2385
  async function showManualWizard(ctx: ExtensionCommandContext, targetDir: string) {
2386
2386
  // 1. Name
2387
- const name = await ctx.ui.input("Agent name (filename, no spaces)");
2387
+ const name = await withModal(() => ctx.ui.input("Agent name (filename, no spaces)"));
2388
2388
  if (!name) return;
2389
2389
 
2390
2390
  // 2. Description
2391
- const description = await ctx.ui.input("Description (one line)");
2391
+ const description = await withModal(() => ctx.ui.input("Description (one line)"));
2392
2392
  if (!description) return;
2393
2393
 
2394
2394
  // 3. Tools
@@ -2403,7 +2403,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
2403
2403
  } else if (toolChoice.startsWith("read-only")) {
2404
2404
  tools = "read, bash, grep, find, ls";
2405
2405
  } else {
2406
- const customTools = await ctx.ui.input("Tools (comma-separated)", BUILTIN_TOOL_NAMES.join(", "));
2406
+ const customTools = await withModal(() => ctx.ui.input("Tools (comma-separated)", BUILTIN_TOOL_NAMES.join(", ")));
2407
2407
  if (!customTools) return;
2408
2408
  tools = customTools;
2409
2409
  }
@@ -2423,7 +2423,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
2423
2423
  else if (modelChoice === "sonnet") modelLine = "\nmodel: anthropic/claude-sonnet-4-6";
2424
2424
  else if (modelChoice === "opus") modelLine = "\nmodel: anthropic/claude-opus-4-6";
2425
2425
  else if (modelChoice === "custom...") {
2426
- const customModel = await ctx.ui.input("Model (provider/modelId)");
2426
+ const customModel = await withModal(() => ctx.ui.input("Model (provider/modelId)"));
2427
2427
  if (customModel) modelLine = `\nmodel: ${customModel}`;
2428
2428
  }
2429
2429
 
@@ -2453,7 +2453,7 @@ ${systemPrompt}
2453
2453
  const targetPath = join(targetDir, `${name}.md`);
2454
2454
 
2455
2455
  if (existsSync(targetPath)) {
2456
- const overwrite = await ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`);
2456
+ const overwrite = await withModal(() => ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`));
2457
2457
  if (!overwrite) return;
2458
2458
  }
2459
2459
 
@@ -2680,7 +2680,7 @@ ${systemPrompt}
2680
2680
 
2681
2681
  // Loop until user enters a valid integer or cancels (Esc / null).
2682
2682
  // Silently trims whitespace; rejects non-numeric input by re-prompting.
2683
- let input: string | undefined = await ctx.ui.input(label, current);
2683
+ let input: string | undefined = await withModal(() => ctx.ui.input(label, current));
2684
2684
  while (input != null) {
2685
2685
  const trimmed = input.trim();
2686
2686
  const n = Number(trimmed);
@@ -2690,7 +2690,7 @@ ${systemPrompt}
2690
2690
  return;
2691
2691
  }
2692
2692
  // Invalid — re-prompt with the user's last entry so they can edit it
2693
- input = await ctx.ui.input(label, trimmed);
2693
+ input = await withModal(() => ctx.ui.input(label, trimmed));
2694
2694
  }
2695
2695
  }
2696
2696
  }
@@ -238,6 +238,21 @@ export function describeActivity(activeTools: Map<string, string>, responseText?
238
238
  return "thinking…";
239
239
  }
240
240
 
241
+ /**
242
+ * Mark the final visible roster row as the tree's last branch.
243
+ *
244
+ * `bodyIndex` is the offset of that row's header line inside the assembled body,
245
+ * which is not simply `body.length - 1`: the bounded viewport pads the body
246
+ * with blank lines, and two-line rows carry a `│` continuation underneath.
247
+ */
248
+ function markLastBranch(body: string[], bodyIndex: number): void {
249
+ const header = body[bodyIndex];
250
+ if (header === undefined) return;
251
+ body[bodyIndex] = header.replace("├─", "└─");
252
+ const continuation = body[bodyIndex + 1];
253
+ if (continuation?.includes("│ ")) body[bodyIndex + 1] = continuation.replace("│ ", " ");
254
+ }
255
+
241
256
  // ---- Widget manager ----
242
257
 
243
258
  export class AgentWidget {
@@ -631,11 +646,7 @@ export class AgentWidget {
631
646
  for (const row of rows) lines.push(...row.lines);
632
647
  if (rows.length > 0) {
633
648
  const lastRow = rows[rows.length - 1];
634
- const lastStart = lines.length - lastRow.lines.length;
635
- lines[lastStart] = lines[lastStart].replace("├─", "└─");
636
- if (lastRow.lines.length === 2) {
637
- lines[lastStart + 1] = lines[lastStart + 1].replace("│ ", " ");
638
- }
649
+ markLastBranch(lines, lines.length - lastRow.lines.length);
639
650
  }
640
651
  } else {
641
652
  // Reserve one line for a directional overflow summary. The viewport is
@@ -675,8 +686,10 @@ export class AgentWidget {
675
686
 
676
687
  let used = 0;
677
688
  let end = start;
689
+ let lastRowLine = -1;
678
690
  const visibleBody: string[] = [];
679
691
  while (end < rows.length && used + heightAt(end) <= contentBudget) {
692
+ lastRowLine = visibleBody.length;
680
693
  visibleBody.push(...rows[end].lines);
681
694
  used += heightAt(end);
682
695
  end++;
@@ -687,6 +700,7 @@ export class AgentWidget {
687
700
  visibleBody.push(rows[start].lines[0]);
688
701
  end = start + 1;
689
702
  }
703
+ markLastBranch(visibleBody, lastRowLine);
690
704
 
691
705
  const hiddenBefore = start;
692
706
  const hiddenAfter = Math.max(0, rows.length - end);