pi-subagents-lite 1.0.2 → 1.1.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/src/format.ts ADDED
@@ -0,0 +1,173 @@
1
+ /**
2
+ * format.ts — Consolidated display formatting helpers.
3
+ *
4
+ * Single source of truth for all display-formatting functions used across
5
+ * the UI layer. Previously scattered across agent-widget.ts, output-file.ts,
6
+ * and agent-types.ts by historical accident.
7
+ *
8
+ * Pure functions — no module-level state, no side effects.
9
+ */
10
+
11
+ import { getConfig } from "./agent-types.js";
12
+ import type { SubagentType, Theme } from "./types.js";
13
+ import { formatTokens, formatCost } from "./usage.js";
14
+
15
+ /** Max length for a truncated command in tool arg summaries. */
16
+ const MAX_COMMAND_DISPLAY_LENGTH = 100;
17
+
18
+ /** Max length for a truncated string value in default tool arg summaries. */
19
+ const MAX_DEFAULT_STRING_DISPLAY_LENGTH = 200;
20
+
21
+ // ---- Internal helpers (used by buildStatsParts) ----
22
+
23
+ /**
24
+ * Token count with optional context-fill % and compaction-count annotations.
25
+ * Thresholds for percent: <70% dim, 70–85% warning, ≥85% error.
26
+ * Compaction count rendered as `↻ N` in dim.
27
+ *
28
+ * "12.3k" — no annotations
29
+ * "12.3k(45%)" — percent only
30
+ * "12.3k(↻ 2)" — compactions only (e.g. right after compact)
31
+ * "12.3k(45%·↻ 2)" — both
32
+ */
33
+ function formatSessionTokens(
34
+ tokens: number,
35
+ percent: number | null,
36
+ theme: Theme,
37
+ compactions = 0,
38
+ ): string {
39
+ const tokenStr = formatTokens(tokens);
40
+ const annot: string[] = [];
41
+ if (percent !== null) {
42
+ const color = percent >= 85 ? "error" : percent >= 70 ? "warning" : "dim";
43
+ annot.push(theme.fg(color, `${Math.round(percent)}%`));
44
+ }
45
+ if (compactions > 0) {
46
+ annot.push(theme.fg("dim", `↻ ${compactions}`));
47
+ }
48
+ if (annot.length === 0) return tokenStr;
49
+ // Include closing paren in the last annotation's color span to prevent
50
+ // ANSI reset from leaving `)` in default color when wrapped in outer dim.
51
+ const lastIdx = annot.length - 1;
52
+ annot[lastIdx] += ")";
53
+ return `${tokenStr}(${annot.join("·")}`;
54
+ }
55
+
56
+ /** Format turn count with optional max limit: "5≤30⟳" or "5⟳". */
57
+ function formatTurns(turnCount: number, maxTurns?: number | null): string {
58
+ return maxTurns != null ? `${turnCount}≤${maxTurns}⟳ ` : `${turnCount}⟳ `;
59
+ }
60
+
61
+ // ---- Exported formatting functions ----
62
+
63
+ /** Format milliseconds as a compact human-readable duration: "1h 1m 1s", "5m 37s", "10s", "<1s". */
64
+ export function formatMs(ms: number): string {
65
+ if (!Number.isFinite(ms) || ms < 1000) return "<1s";
66
+
67
+ const totalSeconds = Math.floor(ms / 1000);
68
+ const hours = Math.floor(totalSeconds / 3600);
69
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
70
+ const seconds = totalSeconds % 60;
71
+
72
+ const parts: string[] = [];
73
+ if (hours > 0) parts.push(`${hours}h`);
74
+ if (minutes > 0) parts.push(`${minutes}m`);
75
+ if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
76
+
77
+ return parts.join(" ");
78
+ }
79
+
80
+ /**
81
+ * Build common stats parts: toolUses · turns · tokens with context % · cost.
82
+ * Shared by AgentWidget and index.ts for consistent stats display.
83
+ */
84
+ export function buildStatsParts(
85
+ args: {
86
+ toolUses: number;
87
+ turnCount?: number;
88
+ maxTurns?: number;
89
+ tokens: number;
90
+ contextPercent: number | null;
91
+ compactions: number;
92
+ cost?: number;
93
+ },
94
+ theme: Theme,
95
+ ): string[] {
96
+ const parts: string[] = [];
97
+ if (args.toolUses > 0) parts.push(`${args.toolUses}🛠 `);
98
+ if (args.turnCount != null) parts.push(formatTurns(args.turnCount, args.maxTurns));
99
+ if (args.tokens > 0) {
100
+ parts.push(formatSessionTokens(
101
+ args.tokens, args.contextPercent, theme, args.compactions,
102
+ ));
103
+ }
104
+ if (args.cost != null && args.cost > 0) parts.push(formatCost(args.cost));
105
+ return parts;
106
+ }
107
+
108
+ /** Get display name for any agent type (built-in or custom). */
109
+ export function getDisplayName(type: SubagentType): string {
110
+ return getConfig(type).displayName;
111
+ }
112
+
113
+ /**
114
+ * Summarize tool arguments for log-friendly display.
115
+ *
116
+ * Heavy tools (read, write, edit, bash, grep, rg) get compact summaries.
117
+ * Other tools fall back to the default JSON formatting.
118
+ */
119
+ export function summarizeToolArgs(name: string, rawArgs: Record<string, unknown> | undefined): string {
120
+ if (!rawArgs || typeof rawArgs !== "object" || Object.keys(rawArgs).length === 0) return "";
121
+
122
+ switch (name) {
123
+ case "read": {
124
+ // read("/path/to/file") — just the path
125
+ const path = typeof rawArgs.path === "string" ? rawArgs.path : "";
126
+ return `(${JSON.stringify(path)})`;
127
+ }
128
+ case "write": {
129
+ // write("/path/to/file", <N> chars) — path + content size
130
+ const path = typeof rawArgs.file_path === "string" ? rawArgs.file_path : "";
131
+ const content = rawArgs.content;
132
+ const size = typeof content === "string" ? content.length : 0;
133
+ return `(${JSON.stringify(path)}, ${size} chars)`;
134
+ }
135
+ case "edit": {
136
+ // edit("/path/to/file", <N> edits) — path + edit count
137
+ const path = typeof rawArgs.path === "string" ? rawArgs.path : "";
138
+ const edits = rawArgs.edits;
139
+ const editCount = Array.isArray(edits) ? edits.length : 0;
140
+ return `(${JSON.stringify(path)}, ${editCount} edits)`;
141
+ }
142
+ case "bash": {
143
+ // bash("command") — just the command, strip heredoc, truncate long
144
+ const cmd = typeof rawArgs.command === "string" ? rawArgs.command : "";
145
+ // Strip heredoc: truncate at << followed by delimiter
146
+ const heredocIdx = cmd.search(/<<\s*['"]?\w+['"]?/);
147
+ const cleanCmd = heredocIdx >= 0 ? cmd.slice(0, heredocIdx).trim() : cmd.trim();
148
+ // Truncate long commands
149
+ const display = cleanCmd.length > MAX_COMMAND_DISPLAY_LENGTH
150
+ ? cleanCmd.slice(0, MAX_COMMAND_DISPLAY_LENGTH) + "…" : cleanCmd;
151
+ return `(${JSON.stringify(display)})`;
152
+ }
153
+ case "grep":
154
+ case "rg": {
155
+ // grep("pattern", "/path") — pattern + path
156
+ const pattern = typeof rawArgs.pattern === "string" ? rawArgs.pattern : "";
157
+ const path = typeof rawArgs.path === "string" ? rawArgs.path : "";
158
+ return `(${JSON.stringify(pattern)}, ${JSON.stringify(path)})`;
159
+ }
160
+ default: {
161
+ // Default behavior for other tools: single-arg shorthand or JSON dump
162
+ const keys = Object.keys(rawArgs);
163
+ if (keys.length === 1) {
164
+ const val = rawArgs[keys[0]];
165
+ const display = typeof val === "string" && val.length > MAX_DEFAULT_STRING_DISPLAY_LENGTH
166
+ ? JSON.stringify(val.slice(0, MAX_DEFAULT_STRING_DISPLAY_LENGTH) + "...")
167
+ : JSON.stringify(val);
168
+ return `(${display})`;
169
+ }
170
+ return ` ${JSON.stringify(rawArgs)}`;
171
+ }
172
+ }
173
+ }
package/src/index.ts CHANGED
@@ -23,7 +23,6 @@
23
23
  * - session_shutdown: Abort all, dispose manager
24
24
  */
25
25
 
26
- import { Box, Container, Spacer, Text } from "@earendil-works/pi-tui";
27
26
  import { Type } from "@sinclair/typebox";
28
27
  import * as path from "node:path";
29
28
  import type {
@@ -31,38 +30,43 @@ import type {
31
30
  ExtensionCommandContext,
32
31
  ExtensionContext,
33
32
  } from "@earendil-works/pi-coding-agent";
34
- import type { SessionModelOverrides, SubagentsConfig } from "./model-precedence.js";
35
33
  import { DEFAULT_AGENTS } from "./default-agents.js";
36
34
  import { registerAgents, getAvailableTypes, setAgentScanDirs } from "./agent-types.js";
37
35
  import { scanAgentFilesInDir, mergeAgents } from "./agent-discovery.js";
38
36
  import { AgentManager } from "./agent-manager.js";
39
- import { AgentWidget, buildStatsParts, formatMs, getDisplayName, type AgentActivity, type Theme, type UICtx } from "./ui/agent-widget.js";
37
+ import { AgentWidget, type UICtx } from "./ui/agent-widget.js";
40
38
  import { showAgentsMainMenu } from "./menus.js";
41
- import { loadConfig, DEFAULT_CONFIG } from "./config-io.js";
42
- import { executeAgentTool, toolCallListener, backgroundAgentIds, scheduleNudge } from "./tool-execution.js";
43
- import { executeStopAgentTool } from "./stop-agent-tool.js";
44
-
45
- // ============================================================================
46
- // Module-level state
47
- // ============================================================================
48
-
49
- /** Session-only model overrides — not persisted, cleared on session_start. */
50
- export let sessionOverrides: SessionModelOverrides = { default: null };
51
-
52
- /** Config cache — loaded at session_start, updated by /agents menu mutations. */
53
- export let __config: SubagentsConfig = { ...DEFAULT_CONFIG, agent: { ...DEFAULT_CONFIG.agent }, concurrency: { ...DEFAULT_CONFIG.concurrency } };
54
-
55
- /** Agent manager singleton — module-level, no globalThis access. */
56
- export let manager: AgentManager;
57
-
58
- /** Live activity state per agent, keyed by agent ID. Read by AgentWidget and tool-execution. */
59
- export const agentActivity = new Map<string, AgentActivity>();
60
-
61
- /** Live TUI widget showing running/completed agents above the editor. Used by tool-execution. */
62
- export let widget: AgentWidget | undefined;
63
-
64
- /** ExtensionAPI reference — stored at init for execute callbacks. */
65
- export let piInstance: ExtensionAPI;
39
+ import { loadConfig } from "./config-io.js";
40
+ import { executeAgentTool, executeStopAgentTool, toolCallListener, backgroundAgentIds, scheduleNudge } from "./tool-execution.js";
41
+ import { renderAgentToolCall, renderAgentToolResult, renderSubagentResult } from "./renderer.js";
42
+ import {
43
+ __config,
44
+ sessionOverrides,
45
+ agentActivity,
46
+ piInstance,
47
+ setConfig,
48
+ setManager,
49
+ clearManager,
50
+ setWidget,
51
+ setPiInstance,
52
+ resetSessionOverrides,
53
+ resetLastToolsExpanded,
54
+ syncWidgetSettings,
55
+ syncCompactFromToolsExpanded,
56
+ getManager,
57
+ getWidget,
58
+ } from "./state.js";
59
+
60
+ // Re-exports for backward compatibility
61
+ export {
62
+ __config,
63
+ sessionOverrides,
64
+ agentActivity,
65
+ piInstance,
66
+ setShowCostEnabled,
67
+ syncWidgetSettings,
68
+ syncCompactFromToolsExpanded,
69
+ } from "./state.js";
66
70
 
67
71
 
68
72
 
@@ -75,30 +79,37 @@ export let piInstance: ExtensionAPI;
75
79
  * Idempotent — safe to call on every session_start.
76
80
  */
77
81
  function ensureManagerAndWidget(): void {
78
- if (manager) return;
79
-
80
- manager = new AgentManager(
81
- (record) => {
82
- // Only nudge for background (async) agents — sync agents already returned via tool result
83
- if (backgroundAgentIds.has(record.id)) {
84
- scheduleNudge(record.id);
85
- backgroundAgentIds.delete(record.id);
86
- }
87
-
88
- // Mark finished and update widget BEFORE deleting activity —
89
- // renderFinishedLine reads activity for turn count, tokens, etc.
90
- widget?.markFinished(record.id);
91
- widget?.update();
82
+ const currentManager = getManager();
83
+ const currentWidget = getWidget();
84
+ // Create manager if missing
85
+ if (!currentManager) {
86
+ const newManager = new AgentManager(
87
+ (record) => {
88
+ // Only nudge for background (async) agents — sync agents already returned via tool result
89
+ if (backgroundAgentIds.has(record.id)) {
90
+ scheduleNudge(record.id);
91
+ backgroundAgentIds.delete(record.id);
92
+ }
92
93
 
93
- // Remove from live activity tracking
94
- agentActivity.delete(record.id);
95
- },
96
- __config.concurrency,
97
- );
94
+ // Mark finished and update widget BEFORE deleting activity
95
+ // renderFinishedLine reads activity for turn count, tokens, etc.
96
+ getWidget()?.markFinished(record.id);
97
+ getWidget()?.update();
98
+
99
+ // Remove from live activity tracking
100
+ agentActivity.delete(record.id);
101
+ },
102
+ __config.concurrency,
103
+ );
104
+ setManager(newManager);
105
+ }
98
106
 
99
- // Create/replace widget tied to this manager instance
100
- if (!widget) {
101
- widget = new AgentWidget(manager, agentActivity);
107
+ // Create widget if missing (uses existing or newly created manager)
108
+ if (!currentWidget) {
109
+ const newWidget = new AgentWidget(getManager(), agentActivity);
110
+ newWidget.setShowCost(__config.agent.showCost === true);
111
+ setWidget(newWidget);
112
+ syncWidgetSettings();
102
113
  }
103
114
  }
104
115
 
@@ -127,35 +138,12 @@ async function scanAndRegisterAgents(ctx: ExtensionContext): Promise<void> {
127
138
  }
128
139
 
129
140
  async function loadConfigAndRegisterAgents(ctx: ExtensionContext): Promise<void> {
130
- __config = loadConfig();
141
+ setConfig(loadConfig());
131
142
  ensureManagerAndWidget();
132
143
  await scanAndRegisterAgents(ctx);
133
144
  }
134
145
 
135
- // ============================================================================
136
- // UI helpers — stats card rendering (shared by renderResult and message renderer)
137
- // ============================================================================
138
-
139
- /** Format agent display name with optional model: "Agent (mimo-v2.5-pro)" or "Agent". */
140
- function agentNameLabel(d: Record<string, unknown>, theme: Theme): string {
141
- const typeName = getDisplayName((d.type as string) || "");
142
- const modelName = d.modelName as string | undefined;
143
- return modelName ? `${theme.bold(typeName)} (${modelName})` : theme.bold(typeName);
144
- }
145
146
 
146
- /** Build the stats line for an agent result card. Used by both renderers. */
147
- function buildStatsLine(d: Record<string, unknown>, theme: Theme): string {
148
- const parts = buildStatsParts({
149
- toolUses: (d.toolUses as number) ?? 0,
150
- turnCount: d.turnCount as number | undefined,
151
- maxTurns: d.maxTurns as number | undefined,
152
- tokens: (d.tokens as number) ?? 0,
153
- contextPercent: d.contextPercent as number | null,
154
- compactions: (d.compactions as number) ?? 0,
155
- }, theme);
156
- parts.push(formatMs(d.durationMs as number));
157
- return parts.join("·");
158
- }
159
147
 
160
148
  // ============================================================================
161
149
  // Agent tool registration helper — dynamic enum for agent types
@@ -186,51 +174,13 @@ function registerAgentTool(pi: ExtensionAPI): void {
186
174
  }),
187
175
  execute: executeAgentTool,
188
176
 
189
- renderCall(args, theme) {
190
- const typeName = getDisplayName((args.agent as string) || "");
191
- const label = typeName || "Agent";
192
- let text = `▸ ${theme.fg("accent", theme.bold(label))}`;
193
-
194
- // Show model in parens when it differs from the parent model
195
- // _modelOverride is injected by toolCallListener when the resolved
196
- // model differs from the session's parent model
197
- const a = args as Record<string, unknown>;
198
- const modelOverride = a._modelOverride as string | undefined;
199
- if (modelOverride) {
200
- text += ` (${modelOverride})`;
201
- }
202
-
203
- return new Text(text, 0, 0);
204
- },
177
+ renderCall: (args, theme) => renderAgentToolCall(args as Record<string, unknown>, theme),
205
178
 
206
- renderResult(result, options, theme) {
207
- const { expanded } = options as { expanded?: boolean };
208
- const text = result.content[0]?.type === "text" ? result.content[0].text : "";
209
- const d = result.details as Record<string, unknown> | undefined;
210
- const isError = !!(result as any).isError;
211
- const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
212
- const desc = (d?.description as string) || "";
213
-
214
- if (d && d.turnCount != null) {
215
- const namePart = agentNameLabel(d, theme);
216
- const statsLine = buildStatsLine(d, theme);
217
- let lines = `${icon} ${namePart}·${statsLine}\n ${theme.fg("text", desc)}`;
218
- if (expanded && text) {
219
- lines += "\n" + text.split("\n").map(l => ` ${l}`).join("\n");
220
- }
221
- return new Text(lines, 0, 0);
222
- }
223
-
224
- // Minimal card — type name already shown by renderCall
225
- // For background spawns (no stats), use space placeholder — agent isn't done yet
226
- const isBackground = text.includes("running in background") || text.includes("queued");
227
- const prefix = isBackground ? " " : `${icon} `;
228
- if (desc) {
229
- return new Text(`${prefix}${theme.fg("text", desc)}`, 0, 0);
230
- }
231
-
232
- return new Text(`${prefix}${theme.fg("dim", text)}`, 0, 0);
233
- },
179
+ renderResult: (result, options, theme) => renderAgentToolResult(
180
+ result as { content: Array<{ type: string; text?: string }>; details?: Record<string, unknown>; isError?: boolean },
181
+ options as { expanded?: boolean },
182
+ theme,
183
+ ),
234
184
  });
235
185
  }
236
186
 
@@ -240,7 +190,7 @@ function registerAgentTool(pi: ExtensionAPI): void {
240
190
 
241
191
  export default function (pi: ExtensionAPI) {
242
192
  // Store pi for execute callbacks
243
- piInstance = pi;
193
+ setPiInstance(pi);
244
194
 
245
195
  // ========================================================================
246
196
  // Tool registration (stealth schemas — at init time)
@@ -261,55 +211,13 @@ export default function (pi: ExtensionAPI) {
261
211
  });
262
212
 
263
213
  // Message renderer — subagent-result (background agent completion)
264
- pi.registerMessageRenderer("subagent-result", (message, options, theme) => {
265
- const { expanded } = options as { expanded?: boolean };
266
- const d = message.details as Record<string, unknown> | undefined;
267
- const text = (message.content as string)?.trim() || "";
268
-
269
- const inner = new Container();
270
- inner.addChild(new Text(theme.fg("customMessageLabel", "Subagent Result"), 0, 0));
271
- inner.addChild(new Spacer(1));
272
-
273
- if (d && d.turnCount != null) {
274
- const isError = d.status === "error" || d.status === "aborted" || d.status === "stopped";
275
- const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
276
- const desc = (d.description as string) || "";
277
-
278
- const namePart = agentNameLabel(d, theme);
279
- const statsLine = buildStatsLine(d, theme);
280
- let headerLine = `${icon} ${namePart}·${statsLine}\n ${theme.fg("text", desc)}`;
281
- if ((d.outputFile as string)) {
282
- headerLine += `\n ${theme.fg("dim", `tail -f ${d.outputFile}`)}`;
283
- }
284
- inner.addChild(new Text(headerLine, 0, 0));
285
-
286
- if (expanded && text) {
287
- inner.addChild(new Spacer(1));
288
- const resultLines = text.split("\n").map(l => ` ${l}`).join("\n");
289
- inner.addChild(new Text(resultLines, 0, 0));
290
- }
291
- } else {
292
- const desc = (d?.description as string) || "";
293
- let line = `${theme.fg("success", "✓")}`;
294
- if (d?.type) {
295
- line += ` ${agentNameLabel(d, theme)}`;
296
- }
297
- if (desc) line += `\n ${theme.fg("text", desc)}`;
298
- if (d?.outputFile) {
299
- line += `\n ${theme.fg("dim", `tail -f ${d.outputFile}`)}`;
300
- }
301
- inner.addChild(new Text(line, 0, 0));
302
- }
303
-
304
- const box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
305
- box.addChild(inner);
306
-
307
- const outer = new Container();
308
- outer.addChild(new Spacer(1));
309
- outer.addChild(box);
310
- outer.addChild(new Spacer(1));
311
- return outer;
312
- });
214
+ pi.registerMessageRenderer("subagent-result", (message, options, theme) =>
215
+ renderSubagentResult(
216
+ message as { content?: string; details?: Record<string, unknown> },
217
+ options as { expanded?: boolean },
218
+ theme,
219
+ ),
220
+ );
313
221
 
314
222
  // Command registration
315
223
  pi.registerCommand("agents", {
@@ -324,25 +232,65 @@ export default function (pi: ExtensionAPI) {
324
232
  pi.on("tool_call", toolCallListener);
325
233
 
326
234
  pi.on("tool_execution_start", async (_event, ctx) => {
327
- widget?.setUICtx(ctx.ui as unknown as UICtx);
328
- widget?.onTurnStart();
235
+ // Set UI context on first tool execution
236
+ if (!getWidget()) {
237
+ ensureManagerAndWidget();
238
+ }
239
+ getWidget()?.setUICtx(ctx.ui as unknown as UICtx);
240
+ getWidget()?.onTurnStart();
329
241
  });
330
242
 
243
+
244
+
331
245
  // session_start — load config, scan agents, register into registry,
332
246
  // then re-register Agent tool with dynamic agent type enum
247
+ // Listen for ctrl+o keypress to sync compact mode (push-based, no polling)
248
+ let unregisterTerminalInput: (() => void) | undefined;
249
+
333
250
  pi.on("session_start", async (_event: unknown, ctx: ExtensionContext) => {
334
- sessionOverrides = { default: null };
251
+ resetSessionOverrides();
335
252
  agentActivity.clear();
253
+ resetLastToolsExpanded();
336
254
  await loadConfigAndRegisterAgents(ctx);
337
255
  // Re-register with updated agent type list (now includes user/project agents)
338
256
  registerAgentTool(pi);
257
+ // Register ctrl+o listener
258
+ if (ctx.hasUI && !unregisterTerminalInput) {
259
+ unregisterTerminalInput = ctx.ui.onTerminalInput((data: string) => {
260
+ // ctrl+o = 0x0F (15) — toggles tool expansion
261
+ if (data === "\u000f") {
262
+ // Read state after a tick to let the built-in handler process it first
263
+ setTimeout(() => {
264
+ const ui = ctx.ui as unknown as { getToolsExpanded?: () => boolean };
265
+ const expanded = ui.getToolsExpanded?.();
266
+ if (expanded !== undefined) {
267
+ getWidget()?.notifyToolsExpansionChanged(expanded);
268
+ }
269
+ }, 0);
270
+ }
271
+ return undefined; // Don't consume the input
272
+ });
273
+ }
274
+ // Sync compact mode with initial tool expansion state
275
+ syncCompactFromToolsExpanded(false);
339
276
  });
340
277
 
341
- pi.on("session_shutdown", async (_event: unknown) => {
342
- widget?.dispose();
343
- widget = undefined;
344
- if (manager) {
345
- await manager.dispose();
278
+ pi.on("session_shutdown", async (_event: unknown, ctx: ExtensionContext) => {
279
+ // Warn if agents were killed
280
+ const currentManager = getManager();
281
+ if (currentManager) {
282
+ const records = currentManager.listAgents();
283
+ const active = records.filter(r => r.lifecycle.status === "running" || r.lifecycle.status === "queued");
284
+ if (active.length > 0 && ctx.hasUI) {
285
+ ctx.ui.notify(`${active.length} agent(s) killed by reload`, "warning");
286
+ }
287
+ }
288
+ getWidget()?.dispose();
289
+ setWidget(undefined);
290
+ const mgr = getManager();
291
+ if (mgr) {
292
+ await mgr.dispose();
293
+ clearManager();
346
294
  }
347
295
  });
348
296
  }