@narumitw/pi-subagents 0.20.0 → 0.26.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/README.md +94 -50
- package/package.json +1 -1
- package/src/agents.ts +8 -4
- package/src/config-ui.ts +566 -205
- package/src/in-process-transport.ts +15 -10
- package/src/persistence.ts +4 -1
- package/src/registry.ts +33 -15
- package/src/settings.ts +113 -0
- package/src/stateful.ts +398 -254
- package/src/subagents.ts +20 -19
- package/src/subprocess-transport.ts +10 -9
- package/src/orchestration.ts +0 -164
package/src/config-ui.ts
CHANGED
|
@@ -1,27 +1,47 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { DynamicBorder, getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import {
|
|
4
|
+
type AutocompleteItem,
|
|
4
5
|
Container,
|
|
5
6
|
Key,
|
|
6
7
|
matchesKey,
|
|
7
8
|
type SelectItem,
|
|
8
9
|
SelectList,
|
|
10
|
+
type SettingItem,
|
|
11
|
+
SettingsList,
|
|
9
12
|
Spacer,
|
|
10
13
|
Text,
|
|
11
14
|
truncateToWidth,
|
|
12
15
|
} from "@earendil-works/pi-tui";
|
|
13
|
-
import {
|
|
16
|
+
import { type CompletionDelivery, discoverAgents } from "./agents.js";
|
|
17
|
+
import type { ManagedAgent } from "./registry.js";
|
|
14
18
|
import {
|
|
15
|
-
hasAnyAgentOverride,
|
|
16
19
|
hasOwn,
|
|
20
|
+
inspectCompletionDeliverySettings,
|
|
17
21
|
readSubagentSettings,
|
|
18
22
|
sameToolSet,
|
|
19
|
-
saveSubagentConfig,
|
|
20
23
|
uniqueToolNames,
|
|
24
|
+
updateAgentToolsSetting,
|
|
25
|
+
updateCompletionDeliverySetting,
|
|
21
26
|
} from "./settings.js";
|
|
27
|
+
import { formatStatefulAgentLine, type StatefulSubagentRuntimeStatus } from "./stateful.js";
|
|
28
|
+
|
|
29
|
+
const SUBCOMMANDS: AutocompleteItem[] = [
|
|
30
|
+
{ value: "settings", label: "settings", description: "Configure completion delivery" },
|
|
31
|
+
{ value: "status", label: "status", description: "Show effective subagent settings" },
|
|
32
|
+
{ value: "help", label: "help", description: "Show subagent settings help" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export interface SubagentSettingsRuntime {
|
|
36
|
+
getCompletionDelivery(): CompletionDelivery;
|
|
37
|
+
setCompletionDelivery(value: CompletionDelivery): void;
|
|
38
|
+
getRuntimeStatus(): StatefulSubagentRuntimeStatus;
|
|
39
|
+
listAgents(includeClosed?: boolean): ManagedAgent[];
|
|
40
|
+
clearAgents(): Promise<number>;
|
|
41
|
+
}
|
|
22
42
|
|
|
23
43
|
export class ToolToggleList {
|
|
24
|
-
private items: { name: string; selected: boolean }[];
|
|
44
|
+
private items: { name: string; displayName: string; selected: boolean }[];
|
|
25
45
|
private cursor = 0;
|
|
26
46
|
private cachedWidth?: number;
|
|
27
47
|
private cachedLines?: string[];
|
|
@@ -29,7 +49,11 @@ export class ToolToggleList {
|
|
|
29
49
|
onCancel?: () => void;
|
|
30
50
|
|
|
31
51
|
constructor(tools: string[], selected: Set<string>) {
|
|
32
|
-
this.items = tools.map((name) => ({
|
|
52
|
+
this.items = tools.map((name) => ({
|
|
53
|
+
name,
|
|
54
|
+
displayName: safeTerminalText(name),
|
|
55
|
+
selected: selected.has(name),
|
|
56
|
+
}));
|
|
33
57
|
}
|
|
34
58
|
|
|
35
59
|
private getSelectedNames(): string[] {
|
|
@@ -65,7 +89,7 @@ export class ToolToggleList {
|
|
|
65
89
|
this.cachedLines = this.items.map((item, i) => {
|
|
66
90
|
const pointer = i === this.cursor ? ">" : " ";
|
|
67
91
|
const check = item.selected ? "✓" : "○";
|
|
68
|
-
return truncateToWidth(`${pointer} ${check} ${item.
|
|
92
|
+
return truncateToWidth(`${pointer} ${check} ${item.displayName}`, width);
|
|
69
93
|
});
|
|
70
94
|
return this.cachedLines;
|
|
71
95
|
}
|
|
@@ -76,218 +100,555 @@ export class ToolToggleList {
|
|
|
76
100
|
}
|
|
77
101
|
}
|
|
78
102
|
|
|
79
|
-
export function registerSubagentConfigCommand(pi: ExtensionAPI) {
|
|
103
|
+
export function registerSubagentConfigCommand(pi: ExtensionAPI, runtime: SubagentSettingsRuntime) {
|
|
104
|
+
registerSubagentPrimaryCommand(pi, runtime);
|
|
80
105
|
pi.registerCommand("subagents:config", {
|
|
81
|
-
description: "Configure
|
|
106
|
+
description: "Configure user tool settings for each subagent",
|
|
82
107
|
handler: async (_args, ctx) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
108
|
+
await showSubagentToolSettings(pi, ctx);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function showSubagentToolSettings(pi: ExtensionAPI, ctx: ExtensionCommandContext) {
|
|
114
|
+
if (ctx.mode !== "tui") {
|
|
115
|
+
if (ctx.hasUI) ctx.ui.notify("/subagents:config requires TUI mode", "info");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Get current settings
|
|
120
|
+
const currentSettings = readSubagentSettings() ?? {};
|
|
121
|
+
const currentAgents = currentSettings.agents ?? {};
|
|
122
|
+
|
|
123
|
+
// Discover agents to show which ones are available
|
|
124
|
+
const discovery = discoverAgents(ctx.cwd, "user", currentSettings);
|
|
125
|
+
const agents = discovery.agents;
|
|
126
|
+
|
|
127
|
+
if (agents.length === 0) {
|
|
128
|
+
ctx.ui.notify("No agents found", "warning");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Loop: agent selection → tool toggle (Esc in tools returns here)
|
|
133
|
+
let selectedAgentIndex = 0;
|
|
134
|
+
while (true) {
|
|
135
|
+
// Step 1: pick an agent to configure
|
|
136
|
+
const agentItems: SelectItem[] = agents.map((a) => {
|
|
137
|
+
const cfg = currentAgents[a.name];
|
|
138
|
+
const hasToolsOverride = cfg ? hasOwn(cfg, "tools") : false;
|
|
139
|
+
const toolSummary = hasToolsOverride
|
|
140
|
+
? cfg?.tools && cfg.tools.length > 0
|
|
141
|
+
? cfg.tools.join(", ")
|
|
142
|
+
: "none"
|
|
143
|
+
: "defaults";
|
|
144
|
+
return {
|
|
145
|
+
value: a.name,
|
|
146
|
+
label: safeTerminalText(a.name),
|
|
147
|
+
description: safeTerminalText(`${a.source} · tools: ${toolSummary}`),
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const agentName = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
152
|
+
const container = new Container();
|
|
153
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
154
|
+
container.addChild(
|
|
155
|
+
new Text(theme.fg("accent", theme.bold("Subagent Tool Configuration")), 1, 0),
|
|
156
|
+
);
|
|
157
|
+
container.addChild(new Spacer(1));
|
|
158
|
+
container.addChild(
|
|
159
|
+
new Text(theme.fg("muted", "Select an agent to configure its allowed tools:"), 1, 0),
|
|
160
|
+
);
|
|
161
|
+
container.addChild(new Spacer(1));
|
|
162
|
+
const selectList = new SelectList(agentItems, Math.min(agentItems.length + 2, 15), {
|
|
163
|
+
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
164
|
+
selectedText: (t: string) => theme.fg("accent", t),
|
|
165
|
+
description: (t: string) => theme.fg("muted", t),
|
|
166
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
167
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
168
|
+
});
|
|
169
|
+
selectList.setSelectedIndex(selectedAgentIndex);
|
|
170
|
+
selectList.onSelectionChange = (item) => {
|
|
171
|
+
selectedAgentIndex = Math.max(
|
|
172
|
+
0,
|
|
173
|
+
agentItems.findIndex((candidate) => candidate.value === item.value),
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
selectList.onSelect = (item) => {
|
|
177
|
+
selectedAgentIndex = Math.max(
|
|
178
|
+
0,
|
|
179
|
+
agentItems.findIndex((candidate) => candidate.value === item.value),
|
|
180
|
+
);
|
|
181
|
+
done(item.value);
|
|
182
|
+
};
|
|
183
|
+
selectList.onCancel = () => done(null);
|
|
184
|
+
container.addChild(selectList);
|
|
185
|
+
container.addChild(
|
|
186
|
+
new Text(theme.fg("dim", "↑↓ navigate · enter select · esc cancel"), 1, 0),
|
|
187
|
+
);
|
|
188
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
189
|
+
return {
|
|
190
|
+
render: (w: number) => container.render(w),
|
|
191
|
+
invalidate: () => container.invalidate(),
|
|
192
|
+
handleInput: (data: string) => {
|
|
193
|
+
selectList.handleInput(data);
|
|
194
|
+
tui.requestRender();
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (!agentName) return;
|
|
200
|
+
|
|
201
|
+
const agent = agents.find((a) => a.name === agentName);
|
|
202
|
+
if (!agent) return;
|
|
203
|
+
|
|
204
|
+
// Step 2: toggle tools for the selected agent
|
|
205
|
+
// Discover without overrides to get original built-in/frontmatter defaults.
|
|
206
|
+
// The main discovery above applies saved overrides, so agent.tools is already
|
|
207
|
+
// overridden — using it for the reset-to-default comparison would match the
|
|
208
|
+
// override against itself and silently delete it on a no-op save.
|
|
209
|
+
const defaultDiscovery = discoverAgents(ctx.cwd, "user");
|
|
210
|
+
const defaultTools = defaultDiscovery.agents.find((a) => a.name === agentName)?.tools;
|
|
211
|
+
const currentAgentSettings = currentAgents[agentName];
|
|
212
|
+
const configuredTools =
|
|
213
|
+
currentAgentSettings && hasOwn(currentAgentSettings, "tools")
|
|
214
|
+
? (currentAgentSettings.tools ?? [])
|
|
215
|
+
: undefined;
|
|
86
216
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
217
|
+
// Get all available tools from pi's registry
|
|
218
|
+
const allTools = uniqueToolNames(pi.getAllTools().map((t) => t.name)).sort((a, b) =>
|
|
219
|
+
a.localeCompare(b),
|
|
220
|
+
);
|
|
221
|
+
const currentTools = uniqueToolNames(configuredTools ?? defaultTools ?? allTools);
|
|
222
|
+
// Sort: currently selected tools first, then rest alphabetically. Preserve
|
|
223
|
+
// unavailable configured tools so saving does not silently drop them.
|
|
224
|
+
const currentSet = new Set(currentTools);
|
|
225
|
+
const selectedFirst = [...currentTools, ...allTools.filter((t) => !currentSet.has(t))];
|
|
90
226
|
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const agents = discovery.agents;
|
|
227
|
+
const selectedTools = await ctx.ui.custom<string[] | null>((tui, theme, _kb, done) => {
|
|
228
|
+
const toggleList = new ToolToggleList(selectedFirst, currentSet);
|
|
94
229
|
|
|
95
|
-
|
|
96
|
-
|
|
230
|
+
const container = new Container();
|
|
231
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
232
|
+
container.addChild(
|
|
233
|
+
new Text(
|
|
234
|
+
theme.fg("accent", theme.bold(`${safeTerminalText(agentName)} tools`)) +
|
|
235
|
+
theme.fg("muted", ` (${agent.source})`),
|
|
236
|
+
1,
|
|
237
|
+
0,
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
container.addChild(new Spacer(1));
|
|
241
|
+
container.addChild(
|
|
242
|
+
new Text(
|
|
243
|
+
theme.fg("muted", "Toggle tools with Enter/Space. S to save, Esc to cancel."),
|
|
244
|
+
1,
|
|
245
|
+
0,
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
container.addChild(new Spacer(1));
|
|
249
|
+
|
|
250
|
+
const listContainer = new Container();
|
|
251
|
+
listContainer.addChild({
|
|
252
|
+
render: (w: number) => toggleList.render(w),
|
|
253
|
+
invalidate: () => toggleList.invalidate(),
|
|
254
|
+
});
|
|
255
|
+
container.addChild(listContainer);
|
|
256
|
+
|
|
257
|
+
container.addChild(new Spacer(1));
|
|
258
|
+
container.addChild(
|
|
259
|
+
new Text(theme.fg("dim", "↑↓ navigate · enter/space toggle · S save · esc cancel"), 1, 0),
|
|
260
|
+
);
|
|
261
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
262
|
+
|
|
263
|
+
toggleList.onDone = (tools) => done(tools);
|
|
264
|
+
toggleList.onCancel = () => done(null);
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
render: (w: number) => container.render(w),
|
|
268
|
+
invalidate: () => container.invalidate(),
|
|
269
|
+
handleInput: (data: string) => {
|
|
270
|
+
toggleList.handleInput(data);
|
|
271
|
+
tui.requestRender();
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// null means user cancelled — loop back to agent selection
|
|
277
|
+
if (selectedTools === null) continue;
|
|
278
|
+
|
|
279
|
+
// Patch only this agent's tool field so forward-compatible settings survive.
|
|
280
|
+
const restoredDefaults =
|
|
281
|
+
defaultTools === undefined
|
|
282
|
+
? sameToolSet(selectedTools, allTools)
|
|
283
|
+
: sameToolSet(selectedTools, defaultTools);
|
|
284
|
+
updateAgentToolsSetting(agentName, restoredDefaults ? undefined : selectedTools);
|
|
285
|
+
const safeAgentName = safeTerminalText(agentName);
|
|
286
|
+
const message = restoredDefaults
|
|
287
|
+
? `${safeAgentName}: defaults restored`
|
|
288
|
+
: `${safeAgentName}: ${selectedTools.length} tool${selectedTools.length !== 1 ? "s" : ""} configured`;
|
|
289
|
+
ctx.ui.notify(message, "info");
|
|
290
|
+
// Saved — exit the loop
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
type ManagerAction = "settings" | "agent-tools" | "agents" | "status" | "help";
|
|
296
|
+
type AgentManagerAction = "back" | "clear";
|
|
297
|
+
|
|
298
|
+
function registerSubagentPrimaryCommand(pi: ExtensionAPI, runtime: SubagentSettingsRuntime) {
|
|
299
|
+
pi.registerCommand("subagents", {
|
|
300
|
+
description: "Manage current-session subagents and user settings",
|
|
301
|
+
getArgumentCompletions(prefix: string): AutocompleteItem[] | null {
|
|
302
|
+
const normalized = prefix.trim().toLowerCase();
|
|
303
|
+
const matches = SUBCOMMANDS.filter((item) => item.value.startsWith(normalized));
|
|
304
|
+
return matches.length > 0 ? matches : null;
|
|
305
|
+
},
|
|
306
|
+
async handler(args, ctx) {
|
|
307
|
+
const subcommand = args.trim().toLowerCase();
|
|
308
|
+
if (!subcommand) {
|
|
309
|
+
await showSubagentManager(pi, ctx, runtime);
|
|
97
310
|
return;
|
|
98
311
|
}
|
|
312
|
+
switch (subcommand) {
|
|
313
|
+
case "settings":
|
|
314
|
+
await showSubagentSettings(ctx, runtime);
|
|
315
|
+
return;
|
|
316
|
+
case "status":
|
|
317
|
+
showSubagentStatus(ctx, runtime);
|
|
318
|
+
return;
|
|
319
|
+
case "help":
|
|
320
|
+
showSubagentHelp(ctx, runtime);
|
|
321
|
+
return;
|
|
322
|
+
default:
|
|
323
|
+
if (ctx.mode === "tui" || ctx.hasUI) {
|
|
324
|
+
ctx.ui.notify(`Unknown /subagents subcommand: ${subcommand}`, "warning");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
}
|
|
99
330
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
331
|
+
async function showSubagentManager(
|
|
332
|
+
pi: ExtensionAPI,
|
|
333
|
+
ctx: ExtensionCommandContext,
|
|
334
|
+
runtime: SubagentSettingsRuntime,
|
|
335
|
+
) {
|
|
336
|
+
if (ctx.mode !== "tui") {
|
|
337
|
+
showSubagentStatus(ctx, runtime);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
while (true) {
|
|
341
|
+
const action = await selectManagerAction(ctx, runtime);
|
|
342
|
+
if (!action) return;
|
|
343
|
+
switch (action) {
|
|
344
|
+
case "settings":
|
|
345
|
+
await showSubagentSettings(ctx, runtime);
|
|
346
|
+
break;
|
|
347
|
+
case "agent-tools":
|
|
348
|
+
await showSubagentToolSettings(pi, ctx);
|
|
349
|
+
break;
|
|
350
|
+
case "agents":
|
|
351
|
+
await showCurrentSessionAgents(ctx, runtime);
|
|
352
|
+
break;
|
|
353
|
+
case "status":
|
|
354
|
+
showSubagentStatus(ctx, runtime);
|
|
355
|
+
break;
|
|
356
|
+
case "help":
|
|
357
|
+
showSubagentHelp(ctx, runtime);
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
118
362
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
363
|
+
async function selectManagerAction(
|
|
364
|
+
ctx: ExtensionCommandContext,
|
|
365
|
+
runtime: SubagentSettingsRuntime,
|
|
366
|
+
): Promise<ManagerAction | null> {
|
|
367
|
+
const status = runtime.getRuntimeStatus();
|
|
368
|
+
const settings = inspectCompletionDeliverySettings();
|
|
369
|
+
const items: SelectItem[] = [
|
|
370
|
+
{
|
|
371
|
+
value: "settings",
|
|
372
|
+
label: "Completion settings",
|
|
373
|
+
description: "Change user completion delivery for this and future sessions",
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
value: "agent-tools",
|
|
377
|
+
label: "Agent tool settings",
|
|
378
|
+
description: "Configure persistent per-agent tool allow-lists",
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
value: "agents",
|
|
382
|
+
label: "Current-session agents",
|
|
383
|
+
description: `${status.activeAgents} active · ${status.retainedAgents} retained`,
|
|
384
|
+
},
|
|
385
|
+
{ value: "status", label: "Status", description: "Show effective runtime and settings state" },
|
|
386
|
+
{ value: "help", label: "Help", description: "Show commands and manual configuration" },
|
|
387
|
+
];
|
|
388
|
+
return ctx.ui.custom<ManagerAction | null>((tui, theme, _keybindings, done) => {
|
|
389
|
+
const container = new Container();
|
|
390
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
391
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("Subagents")), 1, 0));
|
|
392
|
+
container.addChild(new Spacer(1));
|
|
393
|
+
container.addChild(new Text(theme.fg("muted", formatManagerSummary(status, settings)), 1, 0));
|
|
394
|
+
container.addChild(new Spacer(1));
|
|
395
|
+
const selectList = new SelectList(items, Math.min(items.length + 2, 15), {
|
|
396
|
+
selectedPrefix: (text: string) => theme.fg("accent", text),
|
|
397
|
+
selectedText: (text: string) => theme.fg("accent", text),
|
|
398
|
+
description: (text: string) => theme.fg("muted", text),
|
|
399
|
+
scrollInfo: (text: string) => theme.fg("dim", text),
|
|
400
|
+
noMatch: (text: string) => theme.fg("warning", text),
|
|
401
|
+
});
|
|
402
|
+
selectList.onSelect = (item) => done(item.value as ManagerAction);
|
|
403
|
+
selectList.onCancel = () => done(null);
|
|
404
|
+
container.addChild(selectList);
|
|
405
|
+
container.addChild(new Text(theme.fg("dim", "↑↓ navigate · enter select · esc close"), 1, 0));
|
|
406
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
407
|
+
return {
|
|
408
|
+
render: (width: number) => container.render(width),
|
|
409
|
+
invalidate: () => container.invalidate(),
|
|
410
|
+
handleInput(data: string) {
|
|
411
|
+
selectList.handleInput(data);
|
|
412
|
+
tui.requestRender();
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
}
|
|
166
417
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const configuredTools =
|
|
181
|
-
currentAgentSettings && hasOwn(currentAgentSettings, "tools")
|
|
182
|
-
? (currentAgentSettings.tools ?? [])
|
|
183
|
-
: undefined;
|
|
184
|
-
|
|
185
|
-
// Get all available tools from pi's registry
|
|
186
|
-
const allTools = uniqueToolNames(pi.getAllTools().map((t) => t.name)).sort((a, b) =>
|
|
187
|
-
a.localeCompare(b),
|
|
418
|
+
async function showCurrentSessionAgents(
|
|
419
|
+
ctx: ExtensionCommandContext,
|
|
420
|
+
runtime: SubagentSettingsRuntime,
|
|
421
|
+
) {
|
|
422
|
+
while (true) {
|
|
423
|
+
const agents = runtime.listAgents();
|
|
424
|
+
const status = runtime.getRuntimeStatus();
|
|
425
|
+
const action = await ctx.ui.custom<AgentManagerAction | null>(
|
|
426
|
+
(tui, theme, _keybindings, done) => {
|
|
427
|
+
const container = new Container();
|
|
428
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
429
|
+
container.addChild(
|
|
430
|
+
new Text(theme.fg("accent", theme.bold("Current-session Subagents")), 1, 0),
|
|
188
431
|
);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const container = new Container();
|
|
199
|
-
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
200
|
-
container.addChild(
|
|
201
|
-
new Text(
|
|
202
|
-
theme.fg("accent", theme.bold(`${agentName} tools`)) +
|
|
203
|
-
theme.fg("muted", ` (${agent.source})`),
|
|
204
|
-
1,
|
|
205
|
-
0,
|
|
206
|
-
),
|
|
207
|
-
);
|
|
208
|
-
container.addChild(new Spacer(1));
|
|
209
|
-
container.addChild(
|
|
210
|
-
new Text(
|
|
211
|
-
theme.fg("muted", "Toggle tools with Enter/Space. S to save, Esc to cancel."),
|
|
212
|
-
1,
|
|
213
|
-
0,
|
|
214
|
-
),
|
|
215
|
-
);
|
|
216
|
-
container.addChild(new Spacer(1));
|
|
217
|
-
|
|
218
|
-
const listContainer = new Container();
|
|
219
|
-
listContainer.addChild({
|
|
220
|
-
render: (w: number) => toggleList.render(w),
|
|
221
|
-
invalidate: () => toggleList.invalidate(),
|
|
222
|
-
});
|
|
223
|
-
container.addChild(listContainer);
|
|
224
|
-
|
|
225
|
-
container.addChild(new Spacer(1));
|
|
226
|
-
container.addChild(
|
|
227
|
-
new Text(
|
|
228
|
-
theme.fg("dim", "↑↓ navigate · enter/space toggle · S save · esc cancel"),
|
|
229
|
-
1,
|
|
230
|
-
0,
|
|
432
|
+
container.addChild(new Spacer(1));
|
|
433
|
+
container.addChild(
|
|
434
|
+
new Text(
|
|
435
|
+
theme.fg(
|
|
436
|
+
"muted",
|
|
437
|
+
agents.length
|
|
438
|
+
? agents.map(formatStatefulAgentLine).join("\n")
|
|
439
|
+
: formatEmptyRuntime(status),
|
|
231
440
|
),
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
441
|
+
1,
|
|
442
|
+
0,
|
|
443
|
+
),
|
|
444
|
+
);
|
|
445
|
+
container.addChild(new Spacer(1));
|
|
446
|
+
const actions: SelectItem[] = [
|
|
447
|
+
{ value: "back", label: "Back", description: "Return to the Subagents manager" },
|
|
448
|
+
...(agents.length > 0
|
|
449
|
+
? [
|
|
450
|
+
{
|
|
451
|
+
value: "clear",
|
|
452
|
+
label: "Clear current-session agents",
|
|
453
|
+
description: "Close and delete retained agents for this session",
|
|
454
|
+
},
|
|
455
|
+
]
|
|
456
|
+
: []),
|
|
457
|
+
];
|
|
458
|
+
const selectList = new SelectList(actions, Math.min(actions.length + 2, 8), {
|
|
459
|
+
selectedPrefix: (text: string) => theme.fg("accent", text),
|
|
460
|
+
selectedText: (text: string) => theme.fg("accent", text),
|
|
461
|
+
description: (text: string) => theme.fg("muted", text),
|
|
462
|
+
scrollInfo: (text: string) => theme.fg("dim", text),
|
|
463
|
+
noMatch: (text: string) => theme.fg("warning", text),
|
|
246
464
|
});
|
|
465
|
+
selectList.onSelect = (item) => done(item.value as AgentManagerAction);
|
|
466
|
+
selectList.onCancel = () => done(null);
|
|
467
|
+
container.addChild(selectList);
|
|
468
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
469
|
+
return {
|
|
470
|
+
render: (width: number) => container.render(width),
|
|
471
|
+
invalidate: () => container.invalidate(),
|
|
472
|
+
handleInput(data: string) {
|
|
473
|
+
selectList.handleInput(data);
|
|
474
|
+
tui.requestRender();
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
},
|
|
478
|
+
);
|
|
479
|
+
if (!action || action === "back") return;
|
|
480
|
+
const confirmed = await ctx.ui.confirm(
|
|
481
|
+
"Clear current-session subagents?",
|
|
482
|
+
`Close and delete ${agents.length} retained agent${agents.length === 1 ? "" : "s"}?`,
|
|
483
|
+
);
|
|
484
|
+
if (!confirmed) continue;
|
|
485
|
+
const cleared = await runtime.clearAgents();
|
|
486
|
+
ctx.ui.notify(
|
|
487
|
+
`Cleared ${cleared} current-session subagent${cleared === 1 ? "" : "s"}.`,
|
|
488
|
+
"info",
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
247
492
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
493
|
+
async function showSubagentSettings(
|
|
494
|
+
ctx: ExtensionCommandContext,
|
|
495
|
+
runtime: SubagentSettingsRuntime,
|
|
496
|
+
) {
|
|
497
|
+
const snapshot = inspectCompletionDeliverySettings();
|
|
498
|
+
if (ctx.mode !== "tui") {
|
|
499
|
+
if (ctx.hasUI) {
|
|
500
|
+
ctx.ui.notify(
|
|
501
|
+
`User settings apply to this and future sessions. Edit settings manually: ${safeTerminalText(snapshot.path)}`,
|
|
502
|
+
"info",
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (snapshot.error) {
|
|
508
|
+
ctx.ui.notify(
|
|
509
|
+
`Subagent settings cannot be edited: ${safeTerminalText(snapshot.error)}`,
|
|
510
|
+
"error",
|
|
511
|
+
);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
let currentValue = snapshot.value;
|
|
515
|
+
await ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
516
|
+
const items: SettingItem[] = [
|
|
517
|
+
{
|
|
518
|
+
id: "completionDelivery",
|
|
519
|
+
label: "Completion delivery",
|
|
520
|
+
description:
|
|
521
|
+
"User setting applied now and to future sessions. next-turn queues completion; auto-resume requests synthesis after settlement.",
|
|
522
|
+
currentValue,
|
|
523
|
+
values: ["next-turn", "auto-resume"],
|
|
524
|
+
},
|
|
525
|
+
];
|
|
526
|
+
const container = new Container();
|
|
527
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("Subagent User Settings")), 1, 0));
|
|
528
|
+
container.addChild(
|
|
529
|
+
new Text(
|
|
530
|
+
theme.fg("muted", `Applies now and to future sessions\n${safeTerminalText(snapshot.path)}`),
|
|
531
|
+
1,
|
|
532
|
+
0,
|
|
533
|
+
),
|
|
534
|
+
);
|
|
535
|
+
container.addChild(new Spacer(1));
|
|
536
|
+
let settingsList: SettingsList;
|
|
537
|
+
settingsList = new SettingsList(
|
|
538
|
+
items,
|
|
539
|
+
Math.min(items.length + 2, 15),
|
|
540
|
+
getSettingsListTheme(),
|
|
541
|
+
(id, newValue) => {
|
|
542
|
+
if (id !== "completionDelivery") return;
|
|
543
|
+
const previous = currentValue;
|
|
544
|
+
const next = newValue as CompletionDelivery;
|
|
545
|
+
try {
|
|
546
|
+
updateCompletionDeliverySetting(next);
|
|
547
|
+
runtime.setCompletionDelivery(next);
|
|
548
|
+
currentValue = next;
|
|
549
|
+
ctx.ui.notify(
|
|
550
|
+
`User completion delivery set to ${next} for this and future sessions.`,
|
|
551
|
+
"info",
|
|
552
|
+
);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
settingsList.updateValue(id, previous);
|
|
555
|
+
ctx.ui.notify(`Subagent settings were not saved: ${formatError(error)}`, "error");
|
|
276
556
|
}
|
|
557
|
+
tui.requestRender();
|
|
558
|
+
},
|
|
559
|
+
() => done(undefined),
|
|
560
|
+
);
|
|
561
|
+
container.addChild(settingsList);
|
|
562
|
+
return {
|
|
563
|
+
render: (width: number) => container.render(width),
|
|
564
|
+
invalidate: () => container.invalidate(),
|
|
565
|
+
handleInput(data: string) {
|
|
566
|
+
settingsList.handleInput?.(data);
|
|
567
|
+
tui.requestRender();
|
|
568
|
+
},
|
|
569
|
+
};
|
|
570
|
+
});
|
|
571
|
+
}
|
|
277
572
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
573
|
+
function showSubagentStatus(ctx: ExtensionCommandContext, runtime: SubagentSettingsRuntime) {
|
|
574
|
+
if (ctx.mode !== "tui" && !ctx.hasUI) return;
|
|
575
|
+
const snapshot = inspectCompletionDeliverySettings();
|
|
576
|
+
ctx.ui.notify(
|
|
577
|
+
formatStatus(runtime.getRuntimeStatus(), snapshot),
|
|
578
|
+
snapshot.error ? "warning" : "info",
|
|
579
|
+
);
|
|
580
|
+
}
|
|
282
581
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
582
|
+
function showSubagentHelp(ctx: ExtensionCommandContext, runtime: SubagentSettingsRuntime) {
|
|
583
|
+
if (ctx.mode !== "tui" && !ctx.hasUI) return;
|
|
584
|
+
const snapshot = inspectCompletionDeliverySettings();
|
|
585
|
+
const runtimeStatus = runtime.getRuntimeStatus();
|
|
586
|
+
ctx.ui.notify(
|
|
587
|
+
[
|
|
588
|
+
"/subagents — open the current-session manager",
|
|
589
|
+
"/subagents settings — configure user completion delivery",
|
|
590
|
+
"/subagents status — show current-session and user-setting values",
|
|
591
|
+
"/subagents help — show this help",
|
|
592
|
+
"/subagents:config — compatibility route for per-agent user tool settings",
|
|
593
|
+
runtimeStatus.enabled
|
|
594
|
+
? "/subagents:agents list|clear — compatibility route for current-session agents"
|
|
595
|
+
: "/subagents:agents — unavailable while stateful lifecycle tools are disabled",
|
|
596
|
+
`User settings: ${safeTerminalText(snapshot.path)}`,
|
|
597
|
+
].join("\n"),
|
|
598
|
+
"info",
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function formatManagerSummary(
|
|
603
|
+
status: StatefulSubagentRuntimeStatus,
|
|
604
|
+
settings: ReturnType<typeof inspectCompletionDeliverySettings>,
|
|
605
|
+
): string {
|
|
606
|
+
return [
|
|
607
|
+
"Current session",
|
|
608
|
+
`Lifecycle: ${status.enabled ? "enabled" : "disabled"}${status.initialized ? " · initialized" : " · not initialized"}`,
|
|
609
|
+
`Transport: ${status.transport}`,
|
|
610
|
+
`Completion delivery: ${status.completionDelivery}`,
|
|
611
|
+
`Agents: ${status.activeAgents} active · ${status.retainedAgents} retained`,
|
|
612
|
+
"",
|
|
613
|
+
"User settings",
|
|
614
|
+
`Completion source: ${settings.source}`,
|
|
615
|
+
`Path: ${safeTerminalText(settings.path)}`,
|
|
616
|
+
...(settings.error ? [`Warning: ${safeTerminalText(settings.error)}`] : []),
|
|
617
|
+
].join("\n");
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function formatStatus(
|
|
621
|
+
status: StatefulSubagentRuntimeStatus,
|
|
622
|
+
snapshot: ReturnType<typeof inspectCompletionDeliverySettings>,
|
|
623
|
+
): string {
|
|
624
|
+
return [
|
|
625
|
+
"Current session",
|
|
626
|
+
` Lifecycle: ${status.enabled ? "enabled" : "disabled"}`,
|
|
627
|
+
` Runtime: ${status.initialized ? "initialized" : "not initialized"}`,
|
|
628
|
+
` Transport: ${status.transport}`,
|
|
629
|
+
` Completion delivery: ${status.completionDelivery}`,
|
|
630
|
+
` Agents: ${status.activeAgents} active, ${status.retainedAgents} retained`,
|
|
631
|
+
"User settings",
|
|
632
|
+
` Completion source: ${snapshot.source}`,
|
|
633
|
+
` Path: ${safeTerminalText(snapshot.path)}`,
|
|
634
|
+
` Configured completion delivery: ${snapshot.value}`,
|
|
635
|
+
snapshot.error ? ` Warning: ${safeTerminalText(snapshot.error)}` : " Warning: none",
|
|
636
|
+
"User settings persist for future sessions; /subagents settings also applies changes now.",
|
|
637
|
+
"Manual file changes require /reload.",
|
|
638
|
+
].join("\n");
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function formatEmptyRuntime(status: StatefulSubagentRuntimeStatus): string {
|
|
642
|
+
if (!status.enabled) return "Stateful subagents are disabled in user settings.";
|
|
643
|
+
if (!status.initialized) return "Stateful subagents are not initialized for this session.";
|
|
644
|
+
return "No current-session subagents.";
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function safeTerminalText(value: string): string {
|
|
648
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: Escape untrusted terminal controls.
|
|
649
|
+
return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function formatError(error: unknown): string {
|
|
653
|
+
return safeTerminalText(error instanceof Error ? error.message : String(error));
|
|
293
654
|
}
|