@ferris1225/pi-subagents 0.29.0 → 0.32.2

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/src/widget.ts CHANGED
@@ -14,10 +14,12 @@ import {
14
14
  deriveActivityState,
15
15
  formatElapsed,
16
16
  formatUsageCompact,
17
+ isRunActiveStatus,
17
18
  monitor,
18
19
  statusIcon,
19
20
  statusLabel,
20
21
  } from "./monitor.ts";
22
+ import { announceRecoveryRecords } from "./recovery.ts";
21
23
  import type { SubagentRuntime } from "./runtime.ts";
22
24
 
23
25
  /** Features whose one-time announcement is still pending (keyed by config
@@ -80,6 +82,9 @@ async function announceNewFeatures(
80
82
 
81
83
  export function registerWidget(pi: ExtensionAPI, runtime: SubagentRuntime): void {
82
84
  pi.on("session_start", async (_e, ctx) => {
85
+ // Recovery paths survive the old runtime and are shown again in the next
86
+ // UI-capable session before any transient widget state is rebuilt.
87
+ await announceRecoveryRecords(runtime.configPath, ctx);
83
88
  if (ctx.mode !== "tui") return;
84
89
  await announceNewFeatures(ctx, runtime);
85
90
 
@@ -89,7 +94,7 @@ export function registerWidget(pi: ExtensionAPI, runtime: SubagentRuntime): void
89
94
  const unsub = monitor.subscribe(() => tui.requestRender());
90
95
  // Tick once a second so elapsed time stays live while runs are active.
91
96
  const timer = setInterval(() => {
92
- if (monitor.getRuns().some((r) => r.status === "queued" || r.status === "running")) {
97
+ if (monitor.getRuns().some((r) => isRunActiveStatus(r.status))) {
93
98
  tui.requestRender();
94
99
  }
95
100
  }, 1000);
@@ -110,7 +115,7 @@ export function registerWidget(pi: ExtensionAPI, runtime: SubagentRuntime): void
110
115
  const isChain = Boolean(r.groupId);
111
116
  const chainContinues = isChain && runs[idx + 1]?.groupId === r.groupId;
112
117
  const activity =
113
- r.activity && (r.status === "running" || r.status === "queued") ? r.activity : undefined;
118
+ r.activity && isRunActiveStatus(r.status) ? r.activity : undefined;
114
119
  const hasActivity = activity !== undefined;
115
120
  const icon = statusIcon(r.status, theme);
116
121
  // Chain-internal runs (auto-fix worker/reviewer) are child nodes under
@@ -126,21 +131,33 @@ export function registerWidget(pi: ExtensionAPI, runtime: SubagentRuntime): void
126
131
  // uses the accent color, everything else is quiet.
127
132
  if (!isChain && lines.length > 0) lines.push("");
128
133
  const nodeBranch = isChain ? (chainContinues ? "├─ " : "└─ ") : "";
129
- const left = `${dim(nodeBranch)}${icon} ${dim(`#${r.id}`)} ${isChain ? name : theme.fg("accent", theme.bold(name))}`;
134
+ // Content label (task-derived) trails the agent name so concurrent
135
+ // same-agent runs read as what they do, not just their run id. Chain
136
+ // nodes already carry a distinguishing relationLabel.
137
+ const labelPart = !isChain && r.label ? ` ${dim(`· ${r.label}`)}` : "";
138
+ const left = `${dim(nodeBranch)}${icon} ${dim(`#${r.id}`)} ${isChain ? name : theme.fg("accent", theme.bold(name))}${labelPart}`;
130
139
 
131
140
  // Right side: full model ref (provider/model), token usage (in/out +
132
141
  // cache read/write), tool count, elapsed, and the soft activity-state
133
142
  // annotation (idle / long-running). Trailing the header with a single
134
143
  // " · " chain keeps the row compact (no center gap); compactLine
135
144
  // clips on overflow, never the right side on its own.
136
- const model = r.model ?? "?";
145
+ const model = r.modelFallbackFrom
146
+ ? `${r.model ?? "?"} (pool fallback from ${r.modelFallbackFrom})`
147
+ : (r.model ?? "?");
137
148
  const usage = formatUsageCompact(r.usage);
138
149
  const tools = r.toolCount ? `${r.toolCount} tool${r.toolCount === 1 ? "" : "s"}` : "";
139
150
  const elapsed = formatElapsed(r, now);
140
151
  // The round outcome summary leads the metadata so a finished chain
141
152
  // row reads as what it did ("fail · src/index.ts · render()",
142
153
  // "pass", "src/index.ts · tests/monitor.test.ts").
143
- const metaParts = [r.summary, model, usage, tools, elapsed].filter(Boolean);
154
+ const isolation = r.isolation === "worktree" ? `worktree ${r.integrationStatus ?? "active"}` : undefined;
155
+ const relation = r.forkedFromRunId !== undefined
156
+ ? `fork of #${r.forkedFromRunId}`
157
+ : (r.forkChildRunIds?.length ?? 0) > 0
158
+ ? `forks ${r.forkChildRunIds!.map((id) => `#${id}`).join(",")}`
159
+ : undefined;
160
+ const metaParts = [r.summary, relation, isolation, model, usage, tools, elapsed].filter(Boolean);
144
161
  // Running is conveyed by the icon + elapsed; spell out the label only for
145
162
  // the other states (ready / done / stopped) so they are unambiguous.
146
163
  if (r.status !== "running") metaParts.push(statusLabel(r.status));