@esso0428/pi-subagents 0.16.0 → 0.17.0

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,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.17.0] - 2026-09-15
11
+
12
+ ### Changed
13
+ - **The above-editor Agents panel is now the single agent navigator.** It replaces the duplicate FleetView roster, uses focus-gated `↑`/`↓` navigation with `Enter` to open live or read-only history viewers, and removes FleetView's duplicate timer and deferred redraw path.
14
+
10
15
  ## [0.16.0] - 2026-09-15
11
16
 
12
17
  > **⚠️ Breaking: pi 0.80.8 is now the minimum supported host version.** The package peer dependencies require `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` `>=0.80.8`; upgrade pi before installing this release. This floor matches the `ModelRuntime` API used by the runner and avoids installing a release against the pre-0.80.8 session-creation contract.
package/README.md CHANGED
@@ -14,8 +14,7 @@ https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
14
14
 
15
15
  - **Claude Code look & feel** — same tool names, calling conventions, and UI patterns (`Agent`, `get_subagent_result`, `steer_subagent`) — feels native
16
16
  - **Parallel background agents** — spawn multiple agents that run concurrently with automatic queuing (configurable concurrency limit, default 4) and smart group join (consolidated notifications)
17
- - **Live widget UI** — persistent above-editor widget with animated spinners, live tool activity, token counts, and colored status icons. Configurable via `/agents → Settings → Widget`: `all` (every agent), `background` (default — hides foreground runs, which already render inline as the `Agent` tool result), or `off`
18
- - **FleetView** — Claude Code-style navigable list of `main` + every running subagent rendered below the editor (earliest-launched first). Press `↓` (or `←`) at an empty prompt to jump in, `↑`/`↓`/wheel to move the selection, `Enter` to open the selected agent's live, auto-updating conversation, `Esc` to return. The selected row stays visible while the bounded list window moves through the full roster. Finished agents linger briefly before dropping out, and a viewer stays open through completion so you can read the final output. Toggle via `/agents → Settings → Fleet view`
17
+ - **Agents panel UI** — persistent above-editor widget with animated spinners, live tool activity, token counts, colored status icons, and one focus-gated navigator. It shows every agent by default; press `↓` at an empty prompt to activate the panel, `↑`/`↓` to select, `Enter` to open a live or read-only history viewer, and `Esc` to return. Configure via `/agents → Settings → Widget`: `all`, `background`, or `off`
19
18
  - **Conversation viewer** — select any agent in `/agents` to open a live-scrolling overlay of its full conversation (auto-follows new content, scroll up to pause). Steer a running agent inline by pressing `Enter` to open a composer, typing, then `Enter` to send (`Esc` or an empty submit returns) — the message appears as a user message and redirects the agent after its current tool. Stop a still-running agent by pressing `x` (then `x` again to confirm) — both work for background agents too
20
19
  - **Custom agent types** — define agents in `.pi/agents/<name>.md` or `.agents/agents/<name>.md` (project) or globally, with YAML frontmatter: custom system prompts, model selection, thinking levels, tool restrictions
21
20
  - **Mid-run steering** — inject messages into running agents to redirect their work without restarting
@@ -95,7 +94,7 @@ Restrictions:
95
94
 
96
95
  ## UI
97
96
 
98
- The extension renders a persistent widget above the editor showing active agents. By default it shows background runs only (`widgetMode: background`) — foreground agents already render inline as the `Agent` tool result, so the widget would otherwise double-render them. Switch to `all` (every agent) or `off` (hide the widget) via `/agents → Settings → Widget`:
97
+ The extension renders a persistent Agents widget above the editor. By default it shows all agents (`widgetMode: all`), including foreground and background runs. Switch to `background` or `off` via `/agents → Settings → Widget`:
99
98
 
100
99
  ```
101
100
  ● Agents
@@ -112,20 +111,19 @@ The token field is annotated with two optional signals inside parens:
112
111
  - **`NN%`** — context-window utilization (color-coded: <70% dim, 70–85% warning, ≥85% error). Omitted when the model has no declared `contextWindow`, or briefly right after compaction.
113
112
  - **`⇊N`** — number of times the session has compacted, when > 0. Stays dim; the percent's color carries urgency.
114
113
 
115
- ### FleetView
114
+ ### Agents panel navigator
116
115
 
117
- While subagents are running, a Claude Code-style navigable list renders **below** the editor:
116
+ While agents are running or have openable history, the single Agents panel renders **above** the editor:
118
117
 
119
118
  ```
120
- esc to interrupt · ← for agents · ↓ to manage
121
-
122
- ● main
123
- ○ general-purpose Sleep then report 1 11s · ↓ 13.1k tokens
124
- ○ general-purpose Sleep then report 2 11s · ↓ 13.1k tokens
125
- ↓ 3 more
119
+ ● Agents ↑↓ select · enter view · esc back
120
+ ├─ ● Agent Refactor auth module · ↻5≤30 · 5 tool uses · 12.3s
121
+ │ ⎿ editing 2 files…
122
+ ├─ ○ Explore Find auth files · ↻3 · 4.1s
123
+ └─ +1 more (↓ 1 more; 1 finished)
126
124
  ```
127
125
 
128
- The list is ordered earliest-launched first, and only shows agents you can actually open (pending/queued agents with no session yet appear once they start). At an **empty prompt**, press `↓` (or `←`) to move focus from the prompt into the list — the selected row is marked `●`, the rest `○`. `↑`/`↓` move the selection, `Enter` opens the selected agent's live conversation overlay (it auto-updates as the agent works), and `Esc` (or `↑` above `main`) returns to the prompt. Selecting `main` returns to the normal view. Inside the overlay, press `Enter` to steer the running agent — type a message and `Enter` to send it (`Esc` or an empty submit returns), and it redirects the agent the same way the `steer_subagent` tool does. A viewer stays open when its agent finishes so you can read the final output, and finished agents linger in the list for a few seconds before dropping out. Typing anything at a non-empty prompt behaves normally — the list only captures arrow keys when the prompt is empty. Disable it entirely via `/agents → Settings → Fleet view`.
126
+ At an **empty prompt**, press `↓` to activate the panel. The selected row is marked `●`; `↑`/`↓` move through the full bounded roster, `Enter` opens a running agent live or a terminal agent's read-only history. A queued agent remains selectable, but has no live viewer until it starts; `Esc` returns to the prompt. `↑` while the first row is selected also returns to the prompt. Pressing `↑` at an inactive prompt, typing in a non-empty prompt, or using `j`/`k`/`←` behaves normally; the panel only captures the focus-gated arrow sequence described above. Disable it entirely via `/agents → Settings → Widget → off`.
129
127
 
130
128
  Individual agent results render Claude Code-style in the conversation:
131
129
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAc,KAAK,YAAY,EAA0F,MAAM,iCAAiC,CAAC;AACxK,OAAO,EAAE,SAAS,EAAiE,MAAM,wBAAwB,CAAC;AAmBlH,OAAO,EAcL,KAAK,KAAK,EAEX,MAAM,sBAAsB,CAAC;AA8C9B,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GACvB,SAAS,CAKX;AAkKD,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,QAqtExC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAc,KAAK,YAAY,EAA0F,MAAM,iCAAiC,CAAC;AACxK,OAAO,EAAE,SAAS,EAAiE,MAAM,wBAAwB,CAAC;AAmBlH,OAAO,EAcL,KAAK,KAAK,EAEX,MAAM,sBAAsB,CAAC;AA4C9B,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GACvB,SAAS,CAKX;AAkKD,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,QAiqExC"}
package/dist/index.js CHANGED
@@ -31,8 +31,6 @@ import { resolveStorePath, ScheduleStore } from "./schedule-store.js";
31
31
  import { applyAndEmitLoaded, saveAndEmitChanged } from "./settings.js";
32
32
  import { getStatusNote } from "./status-note.js";
33
33
  import { AgentWidget, buildInvocationTags, describeActivity, fgPreservingNestedStyles, formatDuration, formatMs, formatTokens, formatTurns, getDisplayName, getPromptModeLabel, SPINNER, } from "./ui/agent-widget.js";
34
- import { createDeferredUiRefresh } from "./ui/deferred-ui-refresh.js";
35
- import { FleetList } from "./ui/fleet-list.js";
36
34
  import { showSchedulesMenu } from "./ui/schedule-menu.js";
37
35
  import { addUsage, getLifetimeTotal, getSessionContextPercent } from "./usage.js";
38
36
  // ---- Shared helpers ----
@@ -320,17 +318,14 @@ export default function (pi) {
320
318
  function sendIndividualNudge(record) {
321
319
  agentActivity.delete(record.id);
322
320
  widget.markFinished(record.id);
323
- fleet.onAgentFinished(record.id);
324
321
  scheduleNudge(record.id, () => emitIndividualNudge(record));
325
322
  widget.update();
326
- scheduleUiRefresh();
327
323
  }
328
324
  // ---- Group join manager ----
329
325
  const groupJoin = new GroupJoinManager((records, partial) => {
330
326
  for (const r of records) {
331
327
  agentActivity.delete(r.id);
332
328
  widget.markFinished(r.id);
333
- fleet.onAgentFinished(r.id);
334
329
  }
335
330
  const groupKey = `group:${records.map(r => r.id).join(",")}`;
336
331
  scheduleNudge(groupKey, () => {
@@ -338,7 +333,6 @@ export default function (pi) {
338
333
  const unconsumed = records.filter(r => !r.resultConsumed);
339
334
  if (unconsumed.length === 0) {
340
335
  widget.update();
341
- scheduleUiRefresh();
342
336
  return;
343
337
  }
344
338
  const notifications = unconsumed.map(r => formatTaskNotification(r, 300)).join('\n\n');
@@ -358,7 +352,6 @@ export default function (pi) {
358
352
  }, { deliverAs: "followUp", triggerTurn: true });
359
353
  });
360
354
  widget.update();
361
- scheduleUiRefresh();
362
355
  }, 30_000);
363
356
  /** Helper: build event data for lifecycle events from an AgentRecord. */
364
357
  function buildEventData(record) {
@@ -409,16 +402,13 @@ export default function (pi) {
409
402
  if (record.resultConsumed) {
410
403
  agentActivity.delete(record.id);
411
404
  widget.markFinished(record.id);
412
- fleet.onAgentFinished(record.id);
413
405
  widget.update();
414
- scheduleUiRefresh();
415
406
  return;
416
407
  }
417
408
  // If this agent is pending batch finalization (debounce window still open),
418
409
  // don't send an individual nudge — finalizeBatch will pick it up retroactively.
419
410
  if (currentBatchAgents.some(a => a.id === record.id)) {
420
411
  widget.update();
421
- scheduleUiRefresh();
422
412
  return;
423
413
  }
424
414
  const result = groupJoin.onAgentComplete(record);
@@ -428,7 +418,6 @@ export default function (pi) {
428
418
  // 'held' → do nothing, group will fire later
429
419
  // 'delivered' → group callback already fired
430
420
  widget.update();
431
- scheduleUiRefresh();
432
421
  }, undefined, (record) => {
433
422
  // Emit started event when agent transitions to running (including from queue)
434
423
  pi.events.emit("subagents:started", {
@@ -438,9 +427,6 @@ export default function (pi) {
438
427
  });
439
428
  widget.ensureTimer();
440
429
  widget.update();
441
- fleet.ensureTimer();
442
- fleet.update();
443
- scheduleUiRefresh();
444
430
  }, (record, info) => {
445
431
  // Emit compacted event when agent's session compacts (preserves count on record).
446
432
  pi.events.emit("subagents:compacted", {
@@ -531,7 +517,6 @@ export default function (pi) {
531
517
  startScheduler(ctx);
532
518
  });
533
519
  pi.on("session_before_switch", () => {
534
- deferredUiRefresh.cancel();
535
520
  manager.clearCompleted(true);
536
521
  scheduler.stop();
537
522
  });
@@ -553,45 +538,23 @@ export default function (pi) {
553
538
  for (const timer of pendingNudges.values())
554
539
  clearTimeout(timer);
555
540
  pendingNudges.clear();
556
- deferredUiRefresh.dispose();
557
- fleet.dispose();
541
+ widget.dispose();
558
542
  manager.dispose();
559
543
  });
560
- // Live widget: show running agents above editor.
561
- // widgetMode (default "background") selects what the widget shows: "all" =
562
- // every agent; "background" = hide foreground (they already render inline as
563
- // the Agent tool result, so showing them here too is a duplicate, #118), keep
564
- // everything else; "off" = hide the widget entirely. Read live at render time.
565
- let widgetMode = "background";
544
+ // Live widget: show all agents above the editor. Read live at render time.
545
+ let widgetMode = "all";
566
546
  function getWidgetMode() { return widgetMode; }
567
- const widget = new AgentWidget(manager, agentActivity, getWidgetMode);
547
+ const widget = new AgentWidget(manager, agentActivity, getWidgetMode, {
548
+ canOpenHistory: (record) => canOpenAgentHistory(record, currentCtx?.cwd),
549
+ onOpen: (record, mode) => {
550
+ const ctx = currentCtx;
551
+ if (ctx)
552
+ void viewAgentConversation(ctx, record, mode);
553
+ },
554
+ });
568
555
  function setWidgetMode(m) {
569
- const changed = widgetMode !== m;
570
556
  widgetMode = m;
571
557
  widget.update();
572
- if (changed)
573
- scheduleUiRefresh();
574
- }
575
- // Claude Code-style FleetView: navigable list of main + subagents below the editor.
576
- const fleet = new FleetList(manager, agentActivity, () => currentCtx?.cwd, pi, () => currentCtx);
577
- // One render-only scheduler is shared by both widgets. The callback resolves
578
- // the live TUI target at fire time because session switches invalidate the
579
- // previous widget context; both widgets use the same TUI, so one successful
580
- // request is sufficient.
581
- const deferredUiRefresh = createDeferredUiRefresh(() => {
582
- if (widget.requestUiRefresh(true))
583
- return;
584
- fleet.requestUiRefresh(true);
585
- });
586
- const scheduleUiRefresh = () => deferredUiRefresh.schedule();
587
- let fleetViewEnabled = true;
588
- function isFleetViewEnabled() { return fleetViewEnabled; }
589
- function setFleetViewEnabled(b) {
590
- const changed = fleetViewEnabled !== b;
591
- fleetViewEnabled = b;
592
- fleet.setEnabled(b);
593
- if (changed)
594
- scheduleUiRefresh();
595
558
  }
596
559
  // Project/global default for writing the subagent .output transcript. A custom
597
560
  // agent's `output_transcript` frontmatter overrides this per spawn; when the
@@ -684,10 +647,7 @@ export default function (pi) {
684
647
  }
685
648
  // Grab UI context from first tool execution + clear lingering widget on new turn
686
649
  pi.on("tool_execution_start", async (_event, ctx) => {
687
- const widgetContextChanged = widget.setUICtx(ctx.ui);
688
- const fleetContextChanged = fleet.setUICtx(ctx.ui);
689
- if (widgetContextChanged || fleetContextChanged)
690
- scheduleUiRefresh();
650
+ widget.setUICtx(ctx.ui);
691
651
  widget.onTurnStart();
692
652
  });
693
653
  /** Format an agent's tool scope: "*" when it has all built-ins, else a comma-separated list. */
@@ -738,7 +698,6 @@ export default function (pi) {
738
698
  setScopeModels: setScopeModelsEnabled,
739
699
  setDisableDefaultAgents: setDisableDefaultAgents,
740
700
  setToolDescriptionMode: setToolDescriptionMode,
741
- setFleetView: setFleetViewEnabled,
742
701
  setWidgetMode: setWidgetMode,
743
702
  setOutputTranscript: setOutputTranscript,
744
703
  }, (event, payload) => pi.events.emit(event, payload));
@@ -1217,9 +1176,6 @@ Terse command-style prompts produce shallow, generic work.
1217
1176
  agentActivity.set(id, bgState);
1218
1177
  widget.ensureTimer();
1219
1178
  widget.update();
1220
- fleet.ensureTimer();
1221
- fleet.update();
1222
- scheduleUiRefresh();
1223
1179
  // Emit created event
1224
1180
  pi.events.emit("subagents:created", {
1225
1181
  id,
@@ -1272,9 +1228,6 @@ Terse command-style prompts produce shallow, generic work.
1272
1228
  agentActivity.set(a.id, fgState);
1273
1229
  widget.ensureTimer();
1274
1230
  widget.update();
1275
- fleet.ensureTimer();
1276
- fleet.update();
1277
- scheduleUiRefresh();
1278
1231
  break;
1279
1232
  }
1280
1233
  }
@@ -1322,7 +1275,6 @@ Terse command-style prompts produce shallow, generic work.
1322
1275
  if (fgId) {
1323
1276
  agentActivity.delete(fgId);
1324
1277
  widget.markFinished(fgId);
1325
- fleet.onAgentFinished(fgId);
1326
1278
  }
1327
1279
  // Get final token count
1328
1280
  const tokenText = formatLifetimeTokens(fgState);
@@ -2091,7 +2043,6 @@ ${systemPrompt}
2091
2043
  scopeModels: isScopeModelsEnabled(),
2092
2044
  disableDefaultAgents: isDefaultsDisabled(),
2093
2045
  toolDescriptionMode: getToolDescriptionMode(),
2094
- fleetView: isFleetViewEnabled(),
2095
2046
  widgetMode: getWidgetMode(),
2096
2047
  outputTranscript: getOutputTranscriptDefault(),
2097
2048
  };
@@ -2159,13 +2110,6 @@ ${systemPrompt}
2159
2110
  currentValue: getOutputTranscriptDefault() ? "on" : "off",
2160
2111
  values: ["on", "off"],
2161
2112
  },
2162
- {
2163
- id: "fleetView",
2164
- label: "Fleet view",
2165
- description: "Claude Code-style main+subagents list below the editor (↓/← to navigate, Enter to view)",
2166
- currentValue: isFleetViewEnabled() ? "on" : "off",
2167
- values: ["on", "off"],
2168
- },
2169
2113
  {
2170
2114
  id: "widgetMode",
2171
2115
  label: "Widget",
@@ -2243,11 +2187,6 @@ ${systemPrompt}
2243
2187
  setToolDescriptionMode(value);
2244
2188
  notifyApplied(ctx, `Tool description set to ${value}. Takes effect on next pi session.`);
2245
2189
  }
2246
- else if (id === "fleetView") {
2247
- const enabled = value === "on";
2248
- setFleetViewEnabled(enabled);
2249
- notifyApplied(ctx, `Fleet view ${enabled ? "enabled" : "disabled"}`);
2250
- }
2251
2190
  else if (id === "widgetMode") {
2252
2191
  setWidgetMode(value);
2253
2192
  notifyApplied(ctx, `Widget set to ${value}`);