@gotgenes/pi-subagents 6.16.1 → 6.16.3
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 +17 -0
- package/docs/architecture/architecture.md +22 -18
- package/docs/plans/0146-narrow-ui-context.md +319 -0
- package/docs/retro/0146-narrow-ui-context.md +70 -0
- package/docs/retro/0148-split-agent-widget-rendering.md +39 -0
- package/package.json +1 -1
- package/src/index.ts +19 -13
- package/src/tools/get-result-tool.ts +11 -14
- package/src/tools/steer-tool.ts +12 -15
- package/src/ui/agent-config-editor.ts +63 -67
- package/src/ui/agent-creation-wizard.ts +50 -39
- package/src/ui/agent-menu.ts +118 -74
package/src/ui/agent-menu.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { AgentSpawnConfig } from "../agent-manager.js";
|
|
3
1
|
import { AgentTypeRegistry } from "../agent-types.js";
|
|
4
2
|
import type { ModelRegistry } from "../model-resolver.js";
|
|
3
|
+
import type { ParentSnapshot } from "../parent-snapshot.js";
|
|
5
4
|
import type { AgentConfig, AgentRecord } from "../types.js";
|
|
6
5
|
import type { AgentActivityTracker } from "./agent-activity-tracker.js";
|
|
7
6
|
import { createAgentConfigEditor } from "./agent-config-editor.js";
|
|
@@ -16,7 +15,12 @@ export interface AgentMenuManager {
|
|
|
16
15
|
listAgents: () => AgentRecord[];
|
|
17
16
|
getRecord: (id: string) => AgentRecord | undefined;
|
|
18
17
|
/** Used by generate wizard to spawn an agent that writes the .md file. */
|
|
19
|
-
spawnAndWait: (
|
|
18
|
+
spawnAndWait: (
|
|
19
|
+
parentSnapshot: ParentSnapshot,
|
|
20
|
+
type: string,
|
|
21
|
+
prompt: string,
|
|
22
|
+
opts: { description: string; maxTurns: number },
|
|
23
|
+
) => Promise<AgentRecord>;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
/** Narrow settings interface required by the agent menu. */
|
|
@@ -52,35 +56,58 @@ export interface AgentMenuDeps {
|
|
|
52
56
|
|
|
53
57
|
// ---- Narrow UI context types ----
|
|
54
58
|
|
|
59
|
+
/** Narrow UI interface — only the ctx.ui methods menu handlers actually call. */
|
|
60
|
+
export interface MenuUI {
|
|
61
|
+
select(title: string, options: string[]): Promise<string | undefined>;
|
|
62
|
+
confirm(title: string, message: string): Promise<boolean>;
|
|
63
|
+
input(title: string, defaultValue?: string): Promise<string | undefined>;
|
|
64
|
+
notify(message: string, level: "info" | "warning" | "error"): void;
|
|
65
|
+
editor(title: string, content: string): Promise<string | undefined>;
|
|
66
|
+
custom<R>(component: any, options?: any): Promise<R>;
|
|
67
|
+
}
|
|
68
|
+
|
|
55
69
|
// ---- Factory ----
|
|
56
70
|
|
|
57
71
|
/**
|
|
58
72
|
* Create the `/agents` command handler.
|
|
59
73
|
* Returns a function suitable for `pi.registerCommand("agents", { handler })`.
|
|
60
74
|
*/
|
|
61
|
-
export function createAgentsMenuHandler(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
export function createAgentsMenuHandler({
|
|
76
|
+
manager,
|
|
77
|
+
registry,
|
|
78
|
+
agentActivity,
|
|
79
|
+
getModelLabel,
|
|
80
|
+
settings,
|
|
81
|
+
fileOps,
|
|
82
|
+
personalAgentsDir,
|
|
83
|
+
projectAgentsDir,
|
|
84
|
+
}: AgentMenuDeps) {
|
|
85
|
+
const editor = createAgentConfigEditor(
|
|
86
|
+
fileOps,
|
|
87
|
+
registry,
|
|
88
|
+
personalAgentsDir,
|
|
89
|
+
projectAgentsDir,
|
|
90
|
+
);
|
|
68
91
|
|
|
69
92
|
const wizard = createAgentCreationWizard({
|
|
70
|
-
fileOps
|
|
71
|
-
manager
|
|
72
|
-
registry
|
|
73
|
-
personalAgentsDir
|
|
74
|
-
projectAgentsDir
|
|
93
|
+
fileOps,
|
|
94
|
+
manager,
|
|
95
|
+
registry,
|
|
96
|
+
personalAgentsDir,
|
|
97
|
+
projectAgentsDir,
|
|
75
98
|
});
|
|
76
99
|
|
|
77
|
-
async function showAgentsMenu(
|
|
78
|
-
|
|
79
|
-
|
|
100
|
+
async function showAgentsMenu(
|
|
101
|
+
ui: MenuUI,
|
|
102
|
+
modelRegistry: ModelRegistry,
|
|
103
|
+
parentSnapshot: ParentSnapshot,
|
|
104
|
+
) {
|
|
105
|
+
registry.reload();
|
|
106
|
+
const allNames = registry.getAllTypes();
|
|
80
107
|
|
|
81
108
|
const options: string[] = [];
|
|
82
109
|
|
|
83
|
-
const agents =
|
|
110
|
+
const agents = manager.listAgents();
|
|
84
111
|
if (agents.length > 0) {
|
|
85
112
|
const running = agents.filter(
|
|
86
113
|
(a) => a.status === "running" || a.status === "queued",
|
|
@@ -108,30 +135,30 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
108
135
|
: "";
|
|
109
136
|
|
|
110
137
|
if (noAgentsMsg) {
|
|
111
|
-
|
|
138
|
+
ui.notify(noAgentsMsg, "info");
|
|
112
139
|
}
|
|
113
140
|
|
|
114
|
-
const choice = await
|
|
141
|
+
const choice = await ui.select("Agents", options);
|
|
115
142
|
if (!choice) return;
|
|
116
143
|
|
|
117
144
|
if (choice.startsWith("Running agents (")) {
|
|
118
|
-
await showRunningAgents(
|
|
119
|
-
await showAgentsMenu(
|
|
145
|
+
await showRunningAgents(ui);
|
|
146
|
+
await showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
120
147
|
} else if (choice.startsWith("Agent types (")) {
|
|
121
|
-
await showAllAgentsList(
|
|
122
|
-
await showAgentsMenu(
|
|
148
|
+
await showAllAgentsList(ui, modelRegistry);
|
|
149
|
+
await showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
123
150
|
} else if (choice === "Create new agent") {
|
|
124
|
-
await wizard.showCreateWizard(
|
|
151
|
+
await wizard.showCreateWizard(ui, parentSnapshot);
|
|
125
152
|
} else if (choice === "Settings") {
|
|
126
|
-
await showSettings(
|
|
127
|
-
await showAgentsMenu(
|
|
153
|
+
await showSettings(ui);
|
|
154
|
+
await showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
128
155
|
}
|
|
129
156
|
}
|
|
130
157
|
|
|
131
|
-
async function showAllAgentsList(
|
|
132
|
-
const allNames =
|
|
158
|
+
async function showAllAgentsList(ui: MenuUI, modelRegistry: ModelRegistry) {
|
|
159
|
+
const allNames = registry.getAllTypes();
|
|
133
160
|
if (allNames.length === 0) {
|
|
134
|
-
|
|
161
|
+
ui.notify("No agents.", "info");
|
|
135
162
|
return;
|
|
136
163
|
}
|
|
137
164
|
|
|
@@ -144,9 +171,9 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
144
171
|
};
|
|
145
172
|
|
|
146
173
|
const entries = allNames.map((name) => {
|
|
147
|
-
const cfg =
|
|
174
|
+
const cfg = registry.resolveAgentConfig(name);
|
|
148
175
|
const disabled = cfg.enabled === false;
|
|
149
|
-
const model =
|
|
176
|
+
const model = getModelLabel(name, modelRegistry);
|
|
150
177
|
const indicator = sourceIndicator(cfg);
|
|
151
178
|
const prefix = `${indicator}${name} · ${model}`;
|
|
152
179
|
const desc = disabled ? "(disabled)" : cfg.description;
|
|
@@ -155,10 +182,12 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
155
182
|
const maxPrefix = Math.max(...entries.map((e) => e.prefix.length));
|
|
156
183
|
|
|
157
184
|
const hasCustom = allNames.some((n) => {
|
|
158
|
-
const c =
|
|
185
|
+
const c = registry.resolveAgentConfig(n);
|
|
159
186
|
return !c.isDefault && c.enabled !== false;
|
|
160
187
|
});
|
|
161
|
-
const hasDisabled = allNames.some(
|
|
188
|
+
const hasDisabled = allNames.some(
|
|
189
|
+
(n) => registry.resolveAgentConfig(n).enabled === false,
|
|
190
|
+
);
|
|
162
191
|
const legendParts: string[] = [];
|
|
163
192
|
if (hasCustom) legendParts.push("• = project ◦ = global");
|
|
164
193
|
if (hasDisabled) legendParts.push("✕ = disabled");
|
|
@@ -169,47 +198,47 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
169
198
|
);
|
|
170
199
|
if (legend) options.push(legend);
|
|
171
200
|
|
|
172
|
-
const choice = await
|
|
201
|
+
const choice = await ui.select("Agent types", options);
|
|
173
202
|
if (!choice) return;
|
|
174
203
|
|
|
175
204
|
const agentName = choice
|
|
176
205
|
.split(" · ")[0]
|
|
177
206
|
.replace(/^[•◦✕\s]+/, "")
|
|
178
207
|
.trim();
|
|
179
|
-
if (
|
|
180
|
-
await editor.showAgentDetail(
|
|
181
|
-
await showAllAgentsList(
|
|
208
|
+
if (registry.resolveType(agentName) != null) {
|
|
209
|
+
await editor.showAgentDetail(ui, agentName);
|
|
210
|
+
await showAllAgentsList(ui, modelRegistry);
|
|
182
211
|
}
|
|
183
212
|
}
|
|
184
213
|
|
|
185
|
-
async function showRunningAgents(
|
|
186
|
-
const agents =
|
|
214
|
+
async function showRunningAgents(ui: MenuUI) {
|
|
215
|
+
const agents = manager.listAgents();
|
|
187
216
|
if (agents.length === 0) {
|
|
188
|
-
|
|
217
|
+
ui.notify("No agents.", "info");
|
|
189
218
|
return;
|
|
190
219
|
}
|
|
191
220
|
|
|
192
221
|
const options = agents.map((a) => {
|
|
193
|
-
const dn = getDisplayName(a.type,
|
|
222
|
+
const dn = getDisplayName(a.type, registry);
|
|
194
223
|
const dur = formatDuration(a.startedAt, a.completedAt);
|
|
195
224
|
return `${dn} (${a.description}) · ${a.toolUses} tools · ${a.status} · ${dur}`;
|
|
196
225
|
});
|
|
197
226
|
|
|
198
|
-
const choice = await
|
|
227
|
+
const choice = await ui.select("Running agents", options);
|
|
199
228
|
if (!choice) return;
|
|
200
229
|
|
|
201
230
|
const idx = options.indexOf(choice);
|
|
202
231
|
if (idx < 0) return;
|
|
203
232
|
const record = agents[idx];
|
|
204
233
|
|
|
205
|
-
await viewAgentConversation(
|
|
206
|
-
await showRunningAgents(
|
|
234
|
+
await viewAgentConversation(ui, record);
|
|
235
|
+
await showRunningAgents(ui);
|
|
207
236
|
}
|
|
208
237
|
|
|
209
|
-
async function viewAgentConversation(
|
|
238
|
+
async function viewAgentConversation(ui: MenuUI, record: AgentRecord) {
|
|
210
239
|
const session = record.session;
|
|
211
240
|
if (!session) {
|
|
212
|
-
|
|
241
|
+
ui.notify(
|
|
213
242
|
`Agent is ${record.status === "queued" ? "queued" : "expired"} — no session available.`,
|
|
214
243
|
"info",
|
|
215
244
|
);
|
|
@@ -219,11 +248,19 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
219
248
|
const { ConversationViewer, VIEWPORT_HEIGHT_PCT } = await import(
|
|
220
249
|
"./conversation-viewer.js"
|
|
221
250
|
);
|
|
222
|
-
const activity =
|
|
251
|
+
const activity = agentActivity.get(record.id);
|
|
223
252
|
|
|
224
|
-
await
|
|
253
|
+
await ui.custom<undefined>(
|
|
225
254
|
(tui: any, theme: any, _keybindings: any, done: any) => {
|
|
226
|
-
return new ConversationViewer({
|
|
255
|
+
return new ConversationViewer({
|
|
256
|
+
tui,
|
|
257
|
+
session,
|
|
258
|
+
record,
|
|
259
|
+
activity,
|
|
260
|
+
theme,
|
|
261
|
+
done,
|
|
262
|
+
registry,
|
|
263
|
+
});
|
|
227
264
|
},
|
|
228
265
|
{
|
|
229
266
|
overlay: true,
|
|
@@ -236,61 +273,68 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
236
273
|
);
|
|
237
274
|
}
|
|
238
275
|
|
|
239
|
-
async function showSettings(
|
|
240
|
-
const choice = await
|
|
241
|
-
`Max concurrency (current: ${
|
|
242
|
-
`Default max turns (current: ${
|
|
243
|
-
`Grace turns (current: ${
|
|
276
|
+
async function showSettings(ui: MenuUI) {
|
|
277
|
+
const choice = await ui.select("Settings", [
|
|
278
|
+
`Max concurrency (current: ${settings.maxConcurrent})`,
|
|
279
|
+
`Default max turns (current: ${settings.defaultMaxTurns ?? "unlimited"})`,
|
|
280
|
+
`Grace turns (current: ${settings.graceTurns})`,
|
|
244
281
|
]);
|
|
245
282
|
if (!choice) return;
|
|
246
283
|
|
|
247
284
|
if (choice.startsWith("Max concurrency")) {
|
|
248
|
-
const val = await
|
|
285
|
+
const val = await ui.input(
|
|
249
286
|
"Max concurrent background agents",
|
|
250
|
-
String(
|
|
287
|
+
String(settings.maxConcurrent),
|
|
251
288
|
);
|
|
252
289
|
if (val) {
|
|
253
290
|
const n = parseInt(val, 10);
|
|
254
291
|
if (n >= 1) {
|
|
255
|
-
const toast =
|
|
256
|
-
|
|
292
|
+
const toast = settings.applyMaxConcurrent(n);
|
|
293
|
+
ui.notify(toast.message, toast.level);
|
|
257
294
|
} else {
|
|
258
|
-
|
|
295
|
+
ui.notify("Must be a positive integer.", "warning");
|
|
259
296
|
}
|
|
260
297
|
}
|
|
261
298
|
} else if (choice.startsWith("Default max turns")) {
|
|
262
|
-
const val = await
|
|
299
|
+
const val = await ui.input(
|
|
263
300
|
"Default max turns before wrap-up (0 = unlimited)",
|
|
264
|
-
String(
|
|
301
|
+
String(settings.defaultMaxTurns ?? 0),
|
|
265
302
|
);
|
|
266
303
|
if (val) {
|
|
267
304
|
const n = parseInt(val, 10);
|
|
268
305
|
if (n >= 0) {
|
|
269
|
-
const toast =
|
|
270
|
-
|
|
306
|
+
const toast = settings.applyDefaultMaxTurns(n);
|
|
307
|
+
ui.notify(toast.message, toast.level);
|
|
271
308
|
} else {
|
|
272
|
-
|
|
309
|
+
ui.notify("Must be 0 (unlimited) or a positive integer.", "warning");
|
|
273
310
|
}
|
|
274
311
|
}
|
|
275
312
|
} else if (choice.startsWith("Grace turns")) {
|
|
276
|
-
const val = await
|
|
313
|
+
const val = await ui.input(
|
|
277
314
|
"Grace turns after wrap-up steer",
|
|
278
|
-
String(
|
|
315
|
+
String(settings.graceTurns),
|
|
279
316
|
);
|
|
280
317
|
if (val) {
|
|
281
318
|
const n = parseInt(val, 10);
|
|
282
319
|
if (n >= 1) {
|
|
283
|
-
const toast =
|
|
284
|
-
|
|
320
|
+
const toast = settings.applyGraceTurns(n);
|
|
321
|
+
ui.notify(toast.message, toast.level);
|
|
285
322
|
} else {
|
|
286
|
-
|
|
323
|
+
ui.notify("Must be a positive integer.", "warning");
|
|
287
324
|
}
|
|
288
325
|
}
|
|
289
326
|
}
|
|
290
327
|
}
|
|
291
328
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
329
|
+
return async ({
|
|
330
|
+
ui,
|
|
331
|
+
modelRegistry,
|
|
332
|
+
parentSnapshot,
|
|
333
|
+
}: {
|
|
334
|
+
ui: MenuUI;
|
|
335
|
+
modelRegistry: ModelRegistry;
|
|
336
|
+
parentSnapshot: ParentSnapshot;
|
|
337
|
+
}) => {
|
|
338
|
+
await showAgentsMenu(ui, modelRegistry, parentSnapshot);
|
|
295
339
|
};
|
|
296
340
|
}
|