@esso0428/pi-subagents 0.16.0 → 0.17.1

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.1] - 2026-09-15
11
+
12
+ ### Fixed
13
+ - **The Agents panel now initializes during TUI session startup after restoring persisted agent records**, so openable completed-agent history is visible immediately when a session starts or resumes instead of waiting for the next tool execution.
14
+
15
+ ## [0.17.0] - 2026-09-15
16
+
17
+ ### Changed
18
+ - **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.
19
+
10
20
  ## [0.16.0] - 2026-09-15
11
21
 
12
22
  > **⚠️ 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. It initializes during TUI `session_start`, after the session branch restores completed-agent history, so openable agents are visible immediately when a session starts or resumes. 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,QAuqExC"}
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", {
@@ -513,6 +499,12 @@ export default function (pi) {
513
499
  manager.restoreCompleted(branch
514
500
  .filter((entry) => entry?.type === "custom" && entry?.customType === "subagents:record")
515
501
  .map((entry) => entry.data));
502
+ // Attach the panel during TUI startup, after restored records are present,
503
+ // so terminal agents from the session branch are immediately visible.
504
+ if (ctx.mode === "tui") {
505
+ widget.setUICtx(ctx.ui);
506
+ widget.update();
507
+ }
516
508
  // Guard mirrors the `!scheduler.isActive()` pattern below: session_start
517
509
  // fires once per activation, but a double-bind must not leak listeners.
518
510
  if (!rpcHandle) {
@@ -531,7 +523,6 @@ export default function (pi) {
531
523
  startScheduler(ctx);
532
524
  });
533
525
  pi.on("session_before_switch", () => {
534
- deferredUiRefresh.cancel();
535
526
  manager.clearCompleted(true);
536
527
  scheduler.stop();
537
528
  });
@@ -553,45 +544,23 @@ export default function (pi) {
553
544
  for (const timer of pendingNudges.values())
554
545
  clearTimeout(timer);
555
546
  pendingNudges.clear();
556
- deferredUiRefresh.dispose();
557
- fleet.dispose();
547
+ widget.dispose();
558
548
  manager.dispose();
559
549
  });
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";
550
+ // Live widget: show all agents above the editor. Read live at render time.
551
+ let widgetMode = "all";
566
552
  function getWidgetMode() { return widgetMode; }
567
- const widget = new AgentWidget(manager, agentActivity, getWidgetMode);
553
+ const widget = new AgentWidget(manager, agentActivity, getWidgetMode, {
554
+ canOpenHistory: (record) => canOpenAgentHistory(record, currentCtx?.cwd),
555
+ onOpen: (record, mode) => {
556
+ const ctx = currentCtx;
557
+ if (ctx)
558
+ void viewAgentConversation(ctx, record, mode);
559
+ },
560
+ });
568
561
  function setWidgetMode(m) {
569
- const changed = widgetMode !== m;
570
562
  widgetMode = m;
571
563
  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
564
  }
596
565
  // Project/global default for writing the subagent .output transcript. A custom
597
566
  // agent's `output_transcript` frontmatter overrides this per spawn; when the
@@ -684,10 +653,7 @@ export default function (pi) {
684
653
  }
685
654
  // Grab UI context from first tool execution + clear lingering widget on new turn
686
655
  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();
656
+ widget.setUICtx(ctx.ui);
691
657
  widget.onTurnStart();
692
658
  });
693
659
  /** Format an agent's tool scope: "*" when it has all built-ins, else a comma-separated list. */
@@ -738,7 +704,6 @@ export default function (pi) {
738
704
  setScopeModels: setScopeModelsEnabled,
739
705
  setDisableDefaultAgents: setDisableDefaultAgents,
740
706
  setToolDescriptionMode: setToolDescriptionMode,
741
- setFleetView: setFleetViewEnabled,
742
707
  setWidgetMode: setWidgetMode,
743
708
  setOutputTranscript: setOutputTranscript,
744
709
  }, (event, payload) => pi.events.emit(event, payload));
@@ -1217,9 +1182,6 @@ Terse command-style prompts produce shallow, generic work.
1217
1182
  agentActivity.set(id, bgState);
1218
1183
  widget.ensureTimer();
1219
1184
  widget.update();
1220
- fleet.ensureTimer();
1221
- fleet.update();
1222
- scheduleUiRefresh();
1223
1185
  // Emit created event
1224
1186
  pi.events.emit("subagents:created", {
1225
1187
  id,
@@ -1272,9 +1234,6 @@ Terse command-style prompts produce shallow, generic work.
1272
1234
  agentActivity.set(a.id, fgState);
1273
1235
  widget.ensureTimer();
1274
1236
  widget.update();
1275
- fleet.ensureTimer();
1276
- fleet.update();
1277
- scheduleUiRefresh();
1278
1237
  break;
1279
1238
  }
1280
1239
  }
@@ -1322,7 +1281,6 @@ Terse command-style prompts produce shallow, generic work.
1322
1281
  if (fgId) {
1323
1282
  agentActivity.delete(fgId);
1324
1283
  widget.markFinished(fgId);
1325
- fleet.onAgentFinished(fgId);
1326
1284
  }
1327
1285
  // Get final token count
1328
1286
  const tokenText = formatLifetimeTokens(fgState);
@@ -2091,7 +2049,6 @@ ${systemPrompt}
2091
2049
  scopeModels: isScopeModelsEnabled(),
2092
2050
  disableDefaultAgents: isDefaultsDisabled(),
2093
2051
  toolDescriptionMode: getToolDescriptionMode(),
2094
- fleetView: isFleetViewEnabled(),
2095
2052
  widgetMode: getWidgetMode(),
2096
2053
  outputTranscript: getOutputTranscriptDefault(),
2097
2054
  };
@@ -2159,13 +2116,6 @@ ${systemPrompt}
2159
2116
  currentValue: getOutputTranscriptDefault() ? "on" : "off",
2160
2117
  values: ["on", "off"],
2161
2118
  },
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
2119
  {
2170
2120
  id: "widgetMode",
2171
2121
  label: "Widget",
@@ -2243,11 +2193,6 @@ ${systemPrompt}
2243
2193
  setToolDescriptionMode(value);
2244
2194
  notifyApplied(ctx, `Tool description set to ${value}. Takes effect on next pi session.`);
2245
2195
  }
2246
- else if (id === "fleetView") {
2247
- const enabled = value === "on";
2248
- setFleetViewEnabled(enabled);
2249
- notifyApplied(ctx, `Fleet view ${enabled ? "enabled" : "disabled"}`);
2250
- }
2251
2196
  else if (id === "widgetMode") {
2252
2197
  setWidgetMode(value);
2253
2198
  notifyApplied(ctx, `Widget set to ${value}`);