@gotgenes/pi-subagents 6.3.1 → 6.4.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/CHANGELOG.md +16 -0
- package/docs/architecture/architecture.md +234 -263
- package/docs/plans/0108-extract-agent-type-registry.md +322 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +5 -0
- package/src/agent-runner.ts +4 -0
- package/src/agent-types.ts +108 -91
- package/src/index.ts +16 -21
- package/src/session-config.ts +5 -4
- package/src/tools/agent-tool.ts +8 -8
- package/src/tools/get-result-tool.ts +3 -1
- package/src/types.ts +0 -3
- package/src/ui/agent-menu.ts +23 -25
- package/src/ui/agent-widget.ts +10 -9
- package/src/ui/conversation-viewer.ts +4 -2
package/src/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { join } from "node:path";
|
|
|
14
14
|
import { defineTool, type ExtensionAPI, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import { AgentManager } from "./agent-manager.js";
|
|
16
16
|
import { getAgentConversation, normalizeMaxTurns, resumeAgent, runAgent, steerAgent } from "./agent-runner.js";
|
|
17
|
-
import {
|
|
17
|
+
import { AgentTypeRegistry } from "./agent-types.js";
|
|
18
18
|
import { loadCustomAgents } from "./custom-agents.js";
|
|
19
19
|
import { SessionLifecycleHandler, ToolStartHandler } from "./handlers/index.js";
|
|
20
20
|
import { type ModelRegistry, resolveModel } from "./model-resolver.js";
|
|
@@ -40,14 +40,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
40
40
|
// ---- Register custom notification renderer ----
|
|
41
41
|
pi.registerMessageRenderer<NotificationDetails>("subagent-notification", createNotificationRenderer());
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
const reloadCustomAgents = () => {
|
|
45
|
-
const userAgents = loadCustomAgents(process.cwd());
|
|
46
|
-
registerAgents(userAgents);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
// Initial load
|
|
50
|
-
reloadCustomAgents();
|
|
43
|
+
const registry = new AgentTypeRegistry(() => loadCustomAgents(process.cwd()));
|
|
51
44
|
|
|
52
45
|
// ---- Runtime: all mutable extension state in one place ----
|
|
53
46
|
const runtime = createSubagentRuntime();
|
|
@@ -67,6 +60,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
67
60
|
runner: { run: runAgent, resume: resumeAgent },
|
|
68
61
|
worktrees: new GitWorktreeManager(process.cwd()),
|
|
69
62
|
exec: (cmd, args, opts) => pi.exec(cmd, args, opts),
|
|
63
|
+
registry,
|
|
70
64
|
onComplete: (record) => {
|
|
71
65
|
// Emit lifecycle event based on terminal status
|
|
72
66
|
const isError = record.status === "error" || record.status === "stopped" || record.status === "aborted";
|
|
@@ -137,7 +131,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
137
131
|
pi.on("session_shutdown", () => lifecycle.handleSessionShutdown());
|
|
138
132
|
|
|
139
133
|
// Live widget: show running agents above editor
|
|
140
|
-
runtime.widget = new AgentWidget(manager, runtime.agentActivity);
|
|
134
|
+
runtime.widget = new AgentWidget(manager, runtime.agentActivity, registry);
|
|
141
135
|
|
|
142
136
|
// Grab UI context from first tool execution + clear lingering widget on new turn
|
|
143
137
|
const toolStart = new ToolStartHandler(runtime);
|
|
@@ -145,17 +139,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
145
139
|
|
|
146
140
|
/** Build the full type list text dynamically from the unified registry. */
|
|
147
141
|
const buildTypeListText = () => {
|
|
148
|
-
const defaultNames = getDefaultAgentNames();
|
|
149
|
-
const userNames = getUserAgentNames();
|
|
142
|
+
const defaultNames = registry.getDefaultAgentNames();
|
|
143
|
+
const userNames = registry.getUserAgentNames();
|
|
150
144
|
|
|
151
145
|
const defaultDescs = defaultNames.map((name) => {
|
|
152
|
-
const cfg = resolveAgentConfig(name);
|
|
146
|
+
const cfg = registry.resolveAgentConfig(name);
|
|
153
147
|
const modelSuffix = cfg.model ? ` (${getModelLabelFromConfig(cfg.model)})` : "";
|
|
154
148
|
return `- ${name}: ${cfg.description}${modelSuffix}`;
|
|
155
149
|
});
|
|
156
150
|
|
|
157
151
|
const customDescs = userNames.map((name) => {
|
|
158
|
-
const cfg = resolveAgentConfig(name);
|
|
152
|
+
const cfg = registry.resolveAgentConfig(name);
|
|
159
153
|
return `- ${name}: ${cfg.description}`;
|
|
160
154
|
});
|
|
161
155
|
|
|
@@ -201,9 +195,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
201
195
|
},
|
|
202
196
|
agentActivity: runtime.agentActivity,
|
|
203
197
|
emitEvent: (name, data) => pi.events.emit(name, data),
|
|
204
|
-
|
|
198
|
+
registry,
|
|
205
199
|
typeListText,
|
|
206
|
-
availableTypesText: getAvailableTypes().join(", "),
|
|
200
|
+
availableTypesText: registry.getAvailableTypes().join(", "),
|
|
207
201
|
agentDir: getAgentDir(),
|
|
208
202
|
getDefaultMaxTurns: () => runtime.defaultMaxTurns,
|
|
209
203
|
})));
|
|
@@ -214,6 +208,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
214
208
|
getRecord: (id) => manager.getRecord(id),
|
|
215
209
|
cancelNudge: (key) => notifications.cancelNudge(key),
|
|
216
210
|
getConversation: (session) => getAgentConversation(session),
|
|
211
|
+
registry,
|
|
217
212
|
})));
|
|
218
213
|
|
|
219
214
|
// ---- steer_subagent tool ----
|
|
@@ -234,13 +229,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
234
229
|
getMaxConcurrent: () => manager.getMaxConcurrent(),
|
|
235
230
|
setMaxConcurrent: (n) => manager.setMaxConcurrent(n),
|
|
236
231
|
},
|
|
237
|
-
|
|
232
|
+
registry,
|
|
238
233
|
agentActivity: runtime.agentActivity,
|
|
239
|
-
getModelLabel: (type,
|
|
240
|
-
const cfg = resolveAgentConfig(type);
|
|
234
|
+
getModelLabel: (type, modelRegistry) => {
|
|
235
|
+
const cfg = registry.resolveAgentConfig(type);
|
|
241
236
|
if (!cfg.model) return 'inherit';
|
|
242
|
-
if (
|
|
243
|
-
const resolved = resolveModel(cfg.model,
|
|
237
|
+
if (modelRegistry) {
|
|
238
|
+
const resolved = resolveModel(cfg.model, modelRegistry);
|
|
244
239
|
if (typeof resolved === 'string') return 'inherit';
|
|
245
240
|
}
|
|
246
241
|
return getModelLabelFromConfig(cfg.model);
|
package/src/session-config.ts
CHANGED
|
@@ -11,10 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
|
+
type AgentConfigLookup,
|
|
14
15
|
getMemoryToolNames,
|
|
15
16
|
getReadOnlyMemoryToolNames,
|
|
16
|
-
getToolNamesForType,
|
|
17
|
-
resolveAgentConfig,
|
|
18
17
|
} from "./agent-types.js";
|
|
19
18
|
import { buildMemoryBlock, buildReadOnlyMemoryBlock } from "./memory.js";
|
|
20
19
|
import { buildAgentPrompt, type PromptExtras } from "./prompts.js";
|
|
@@ -140,14 +139,16 @@ function resolveDefaultModel(
|
|
|
140
139
|
* @param ctx Narrow context from the parent session.
|
|
141
140
|
* @param options Per-call overrides (cwd, isolated, model, thinkingLevel).
|
|
142
141
|
* @param env Pre-resolved environment info from `detectEnv()`.
|
|
142
|
+
* @param registry Agent config lookup — provides resolveAgentConfig and getToolNamesForType.
|
|
143
143
|
*/
|
|
144
144
|
export function assembleSessionConfig(
|
|
145
145
|
type: SubagentType,
|
|
146
146
|
ctx: AssemblerContext,
|
|
147
147
|
options: AssemblerOptions,
|
|
148
148
|
env: EnvInfo,
|
|
149
|
+
registry: AgentConfigLookup,
|
|
149
150
|
): SessionConfig {
|
|
150
|
-
const agentConfig = resolveAgentConfig(type);
|
|
151
|
+
const agentConfig = registry.resolveAgentConfig(type);
|
|
151
152
|
|
|
152
153
|
const effectiveCwd = options.cwd ?? ctx.cwd;
|
|
153
154
|
|
|
@@ -166,7 +167,7 @@ export function assembleSessionConfig(
|
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
|
|
169
|
-
let toolNames = getToolNamesForType(type);
|
|
170
|
+
let toolNames = registry.getToolNamesForType(type);
|
|
170
171
|
|
|
171
172
|
// Persistent memory: detect write capability and branch accordingly.
|
|
172
173
|
// Account for disallowedTools — a tool in the base set but on the denylist
|
package/src/tools/agent-tool.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
import type { SpawnOptions } from "../agent-manager.js";
|
|
5
5
|
import { normalizeMaxTurns } from "../agent-runner.js";
|
|
6
|
-
import {
|
|
6
|
+
import { AgentTypeRegistry } from "../agent-types.js";
|
|
7
7
|
import { resolveAgentInvocationConfig } from "../invocation-config.js";
|
|
8
8
|
import { resolveInvocationModel } from "../model-resolver.js";
|
|
9
9
|
|
|
@@ -108,7 +108,7 @@ export interface AgentToolDeps {
|
|
|
108
108
|
widget: AgentToolWidget;
|
|
109
109
|
agentActivity: Map<string, AgentActivity>;
|
|
110
110
|
emitEvent: (name: string, data: unknown) => void;
|
|
111
|
-
|
|
111
|
+
registry: AgentTypeRegistry;
|
|
112
112
|
typeListText: string;
|
|
113
113
|
availableTypesText: string;
|
|
114
114
|
agentDir: string;
|
|
@@ -207,7 +207,7 @@ Guidelines:
|
|
|
207
207
|
|
|
208
208
|
renderCall(args: Record<string, unknown>, theme: any) {
|
|
209
209
|
const displayName = args.subagent_type
|
|
210
|
-
? getDisplayName(args.subagent_type as string)
|
|
210
|
+
? getDisplayName(args.subagent_type as string, deps.registry)
|
|
211
211
|
: "Agent";
|
|
212
212
|
const desc = (args.description as string) ?? "";
|
|
213
213
|
return new Text(
|
|
@@ -327,17 +327,17 @@ Guidelines:
|
|
|
327
327
|
deps.widget.setUICtx(ctx.ui as UICtx);
|
|
328
328
|
|
|
329
329
|
// Reload custom agents so new .pi/agents/*.md files are picked up without restart
|
|
330
|
-
deps.
|
|
330
|
+
deps.registry.reload();
|
|
331
331
|
|
|
332
332
|
const rawType = params.subagent_type as SubagentType;
|
|
333
|
-
const resolved = resolveType(rawType);
|
|
333
|
+
const resolved = deps.registry.resolveType(rawType);
|
|
334
334
|
const subagentType = resolved ?? "general-purpose";
|
|
335
335
|
const fellBack = resolved === undefined;
|
|
336
336
|
|
|
337
|
-
const displayName = getDisplayName(subagentType);
|
|
337
|
+
const displayName = getDisplayName(subagentType, deps.registry);
|
|
338
338
|
|
|
339
339
|
// Get agent config for invocation resolution
|
|
340
|
-
const customConfig = resolveAgentConfig(subagentType);
|
|
340
|
+
const customConfig = deps.registry.resolveAgentConfig(subagentType);
|
|
341
341
|
|
|
342
342
|
const resolvedConfig = resolveAgentInvocationConfig(customConfig, params);
|
|
343
343
|
|
|
@@ -375,7 +375,7 @@ Guidelines:
|
|
|
375
375
|
runInBackground,
|
|
376
376
|
isolation,
|
|
377
377
|
};
|
|
378
|
-
const modeLabel = getPromptModeLabel(subagentType);
|
|
378
|
+
const modeLabel = getPromptModeLabel(subagentType, deps.registry);
|
|
379
379
|
const { tags: invocationTags } = buildInvocationTags(agentInvocation);
|
|
380
380
|
const agentTags = modeLabel ? [modeLabel, ...invocationTags] : invocationTags;
|
|
381
381
|
const detailBase = {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import type { AgentConfigLookup } from "../agent-types.js";
|
|
3
4
|
import type { AgentRecord } from "../types.js";
|
|
4
5
|
import { formatDuration, getDisplayName } from "../ui/agent-widget.js";
|
|
5
6
|
import { getSessionContextPercent } from "../usage.js";
|
|
@@ -10,6 +11,7 @@ export interface GetResultDeps {
|
|
|
10
11
|
getRecord: (id: string) => AgentRecord | undefined;
|
|
11
12
|
cancelNudge: (key: string) => void;
|
|
12
13
|
getConversation: (session: AgentSession) => string | undefined;
|
|
14
|
+
registry: AgentConfigLookup;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
/** Create the get_subagent_result tool definition (without Pi SDK wrapper). */
|
|
@@ -57,7 +59,7 @@ export function createGetResultTool(deps: GetResultDeps) {
|
|
|
57
59
|
await record.promise;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
const displayName = getDisplayName(record.type);
|
|
62
|
+
const displayName = getDisplayName(record.type, deps.registry);
|
|
61
63
|
const duration = formatDuration(record.startedAt, record.completedAt);
|
|
62
64
|
const tokens = formatLifetimeTokens(record);
|
|
63
65
|
const contextPercent = getSessionContextPercent(record.session);
|
package/src/types.ts
CHANGED
|
@@ -12,9 +12,6 @@ export type { ThinkingLevel };
|
|
|
12
12
|
/** Agent type: any string name (built-in defaults or user-defined). */
|
|
13
13
|
export type SubagentType = string;
|
|
14
14
|
|
|
15
|
-
/** Names of the three embedded default agents. */
|
|
16
|
-
export const DEFAULT_AGENT_NAMES = ["general-purpose", "Explore", "Plan"] as const;
|
|
17
|
-
|
|
18
15
|
/** Memory scope for persistent agent memory. */
|
|
19
16
|
export type MemoryScope = "user" | "project" | "local";
|
|
20
17
|
|
package/src/ui/agent-menu.ts
CHANGED
|
@@ -4,10 +4,8 @@ import { join } from "node:path";
|
|
|
4
4
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import type { SpawnOptions } from "../agent-manager.js";
|
|
6
6
|
import {
|
|
7
|
+
AgentTypeRegistry,
|
|
7
8
|
BUILTIN_TOOL_NAMES,
|
|
8
|
-
getAllTypes,
|
|
9
|
-
resolveAgentConfig,
|
|
10
|
-
resolveType,
|
|
11
9
|
} from "../agent-types.js";
|
|
12
10
|
import type { ModelRegistry } from "../model-resolver.js";
|
|
13
11
|
import type { AgentConfig, AgentRecord } from "../types.js";
|
|
@@ -28,7 +26,7 @@ export interface AgentMenuManager {
|
|
|
28
26
|
|
|
29
27
|
export interface AgentMenuDeps {
|
|
30
28
|
manager: AgentMenuManager;
|
|
31
|
-
|
|
29
|
+
registry: AgentTypeRegistry;
|
|
32
30
|
agentActivity: Map<string, AgentActivity>;
|
|
33
31
|
/** Resolve model label for a given agent type + registry. */
|
|
34
32
|
getModelLabel: (type: string, registry?: ModelRegistry) => string;
|
|
@@ -72,8 +70,8 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
72
70
|
}
|
|
73
71
|
|
|
74
72
|
async function showAgentsMenu(ctx: ExtensionContext) {
|
|
75
|
-
deps.
|
|
76
|
-
const allNames = getAllTypes();
|
|
73
|
+
deps.registry.reload();
|
|
74
|
+
const allNames = deps.registry.getAllTypes();
|
|
77
75
|
|
|
78
76
|
const options: string[] = [];
|
|
79
77
|
|
|
@@ -126,7 +124,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
126
124
|
}
|
|
127
125
|
|
|
128
126
|
async function showAllAgentsList(ctx: ExtensionContext) {
|
|
129
|
-
const allNames = getAllTypes();
|
|
127
|
+
const allNames = deps.registry.getAllTypes();
|
|
130
128
|
if (allNames.length === 0) {
|
|
131
129
|
ctx.ui.notify("No agents.", "info");
|
|
132
130
|
return;
|
|
@@ -141,7 +139,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
141
139
|
};
|
|
142
140
|
|
|
143
141
|
const entries = allNames.map((name) => {
|
|
144
|
-
const cfg = resolveAgentConfig(name);
|
|
142
|
+
const cfg = deps.registry.resolveAgentConfig(name);
|
|
145
143
|
const disabled = cfg.enabled === false;
|
|
146
144
|
const model = deps.getModelLabel(name, ctx.modelRegistry);
|
|
147
145
|
const indicator = sourceIndicator(cfg);
|
|
@@ -152,10 +150,10 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
152
150
|
const maxPrefix = Math.max(...entries.map((e) => e.prefix.length));
|
|
153
151
|
|
|
154
152
|
const hasCustom = allNames.some((n) => {
|
|
155
|
-
const c = resolveAgentConfig(n);
|
|
153
|
+
const c = deps.registry.resolveAgentConfig(n);
|
|
156
154
|
return !c.isDefault && c.enabled !== false;
|
|
157
155
|
});
|
|
158
|
-
const hasDisabled = allNames.some((n) => resolveAgentConfig(n).enabled === false);
|
|
156
|
+
const hasDisabled = allNames.some((n) => deps.registry.resolveAgentConfig(n).enabled === false);
|
|
159
157
|
const legendParts: string[] = [];
|
|
160
158
|
if (hasCustom) legendParts.push("• = project ◦ = global");
|
|
161
159
|
if (hasDisabled) legendParts.push("✕ = disabled");
|
|
@@ -173,7 +171,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
173
171
|
.split(" · ")[0]
|
|
174
172
|
.replace(/^[•◦✕\s]+/, "")
|
|
175
173
|
.trim();
|
|
176
|
-
if (resolveType(agentName) != null) {
|
|
174
|
+
if (deps.registry.resolveType(agentName) != null) {
|
|
177
175
|
await showAgentDetail(ctx, agentName);
|
|
178
176
|
await showAllAgentsList(ctx);
|
|
179
177
|
}
|
|
@@ -187,7 +185,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
187
185
|
}
|
|
188
186
|
|
|
189
187
|
const options = agents.map((a) => {
|
|
190
|
-
const dn = getDisplayName(a.type);
|
|
188
|
+
const dn = getDisplayName(a.type, deps.registry);
|
|
191
189
|
const dur = formatDuration(a.startedAt, a.completedAt);
|
|
192
190
|
return `${dn} (${a.description}) · ${a.toolUses} tools · ${a.status} · ${dur}`;
|
|
193
191
|
});
|
|
@@ -220,7 +218,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
220
218
|
|
|
221
219
|
await ctx.ui.custom<undefined>(
|
|
222
220
|
(tui: any, theme: any, _keybindings: any, done: any) => {
|
|
223
|
-
return new ConversationViewer(tui, session, record, activity, theme, done);
|
|
221
|
+
return new ConversationViewer(tui, session, record, activity, theme, done, deps.registry);
|
|
224
222
|
},
|
|
225
223
|
{
|
|
226
224
|
overlay: true,
|
|
@@ -234,11 +232,11 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
234
232
|
}
|
|
235
233
|
|
|
236
234
|
async function showAgentDetail(ctx: ExtensionContext, name: string) {
|
|
237
|
-
if (resolveType(name) == null) {
|
|
235
|
+
if (deps.registry.resolveType(name) == null) {
|
|
238
236
|
ctx.ui.notify(`Agent config not found for "${name}".`, "warning");
|
|
239
237
|
return;
|
|
240
238
|
}
|
|
241
|
-
const cfg = resolveAgentConfig(name);
|
|
239
|
+
const cfg = deps.registry.resolveAgentConfig(name);
|
|
242
240
|
|
|
243
241
|
const file = findAgentFile(name);
|
|
244
242
|
const isDefault = cfg.isDefault === true;
|
|
@@ -266,7 +264,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
266
264
|
if (edited !== undefined && edited !== content) {
|
|
267
265
|
const { writeFileSync } = await import("node:fs");
|
|
268
266
|
writeFileSync(file.path, edited, "utf-8");
|
|
269
|
-
deps.
|
|
267
|
+
deps.registry.reload();
|
|
270
268
|
ctx.ui.notify(`Updated ${file.path}`, "info");
|
|
271
269
|
}
|
|
272
270
|
} else if (choice === "Delete") {
|
|
@@ -277,7 +275,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
277
275
|
);
|
|
278
276
|
if (confirmed) {
|
|
279
277
|
unlinkSync(file.path);
|
|
280
|
-
deps.
|
|
278
|
+
deps.registry.reload();
|
|
281
279
|
ctx.ui.notify(`Deleted ${file.path}`, "info");
|
|
282
280
|
}
|
|
283
281
|
}
|
|
@@ -288,7 +286,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
288
286
|
);
|
|
289
287
|
if (confirmed) {
|
|
290
288
|
unlinkSync(file.path);
|
|
291
|
-
deps.
|
|
289
|
+
deps.registry.reload();
|
|
292
290
|
ctx.ui.notify(`Restored default ${name}`, "info");
|
|
293
291
|
}
|
|
294
292
|
} else if (choice.startsWith("Eject")) {
|
|
@@ -347,7 +345,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
347
345
|
|
|
348
346
|
const { writeFileSync } = await import("node:fs");
|
|
349
347
|
writeFileSync(targetPath, content, "utf-8");
|
|
350
|
-
deps.
|
|
348
|
+
deps.registry.reload();
|
|
351
349
|
ctx.ui.notify(`Ejected ${name} to ${targetPath}`, "info");
|
|
352
350
|
}
|
|
353
351
|
|
|
@@ -362,7 +360,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
362
360
|
const updated = content.replace(/^---\n/, "---\nenabled: false\n");
|
|
363
361
|
const { writeFileSync } = await import("node:fs");
|
|
364
362
|
writeFileSync(file.path, updated, "utf-8");
|
|
365
|
-
deps.
|
|
363
|
+
deps.registry.reload();
|
|
366
364
|
ctx.ui.notify(`Disabled ${name} (${file.path})`, "info");
|
|
367
365
|
return;
|
|
368
366
|
}
|
|
@@ -381,7 +379,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
381
379
|
const targetPath = join(targetDir, `${name}.md`);
|
|
382
380
|
const { writeFileSync } = await import("node:fs");
|
|
383
381
|
writeFileSync(targetPath, "---\nenabled: false\n---\n", "utf-8");
|
|
384
|
-
deps.
|
|
382
|
+
deps.registry.reload();
|
|
385
383
|
ctx.ui.notify(`Disabled ${name} (${targetPath})`, "info");
|
|
386
384
|
}
|
|
387
385
|
|
|
@@ -395,11 +393,11 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
|
|
|
395
393
|
|
|
396
394
|
if (updated.trim() === "---\n---" || updated.trim() === "---\n---\n") {
|
|
397
395
|
unlinkSync(file.path);
|
|
398
|
-
deps.
|
|
396
|
+
deps.registry.reload();
|
|
399
397
|
ctx.ui.notify(`Enabled ${name} (removed ${file.path})`, "info");
|
|
400
398
|
} else {
|
|
401
399
|
writeFileSync(file.path, updated, "utf-8");
|
|
402
|
-
deps.
|
|
400
|
+
deps.registry.reload();
|
|
403
401
|
ctx.ui.notify(`Enabled ${name} (${file.path})`, "info");
|
|
404
402
|
}
|
|
405
403
|
}
|
|
@@ -501,7 +499,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
|
|
|
501
499
|
return;
|
|
502
500
|
}
|
|
503
501
|
|
|
504
|
-
deps.
|
|
502
|
+
deps.registry.reload();
|
|
505
503
|
|
|
506
504
|
if (existsSync(targetPath)) {
|
|
507
505
|
ctx.ui.notify(`Created ${targetPath}`, "info");
|
|
@@ -604,7 +602,7 @@ ${systemPrompt}
|
|
|
604
602
|
|
|
605
603
|
const { writeFileSync } = await import("node:fs");
|
|
606
604
|
writeFileSync(targetPath, content, "utf-8");
|
|
607
|
-
deps.
|
|
605
|
+
deps.registry.reload();
|
|
608
606
|
ctx.ui.notify(`Created ${targetPath}`, "info");
|
|
609
607
|
}
|
|
610
608
|
|
package/src/ui/agent-widget.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
9
9
|
import type { AgentManager } from "../agent-manager.js";
|
|
10
|
-
import {
|
|
10
|
+
import { type AgentConfigLookup, AgentTypeRegistry } from "../agent-types.js";
|
|
11
11
|
import type { AgentInvocation, SubagentType } from "../types.js";
|
|
12
12
|
import { getLifetimeTotal, getSessionContextPercent, type LifetimeUsage, type SessionLike } from "../usage.js";
|
|
13
13
|
|
|
@@ -143,14 +143,14 @@ export function formatDuration(startedAt: number, completedAt?: number): string
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
/** Get display name for any agent type (built-in or custom). */
|
|
146
|
-
export function getDisplayName(type: SubagentType): string {
|
|
147
|
-
const config = resolveAgentConfig(type);
|
|
146
|
+
export function getDisplayName(type: SubagentType, registry: AgentConfigLookup): string {
|
|
147
|
+
const config = registry.resolveAgentConfig(type);
|
|
148
148
|
return config.displayName ?? config.name;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/** Short label for prompt mode: "twin" for append, nothing for replace (the default). */
|
|
152
|
-
export function getPromptModeLabel(type: SubagentType): string | undefined {
|
|
153
|
-
const config = resolveAgentConfig(type);
|
|
152
|
+
export function getPromptModeLabel(type: SubagentType, registry: AgentConfigLookup): string | undefined {
|
|
153
|
+
const config = registry.resolveAgentConfig(type);
|
|
154
154
|
return config.promptMode === "append" ? "twin" : undefined;
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -225,6 +225,7 @@ export class AgentWidget {
|
|
|
225
225
|
constructor(
|
|
226
226
|
private manager: AgentManager,
|
|
227
227
|
private agentActivity: Map<string, AgentActivity>,
|
|
228
|
+
private registry: AgentTypeRegistry,
|
|
228
229
|
) {}
|
|
229
230
|
|
|
230
231
|
/** Set the UI context (grabbed from first tool execution). */
|
|
@@ -275,8 +276,8 @@ export class AgentWidget {
|
|
|
275
276
|
|
|
276
277
|
/** Render a finished agent line. */
|
|
277
278
|
private renderFinishedLine(a: { id: string; type: SubagentType; status: string; description: string; toolUses: number; startedAt: number; completedAt?: number; error?: string }, theme: Theme): string {
|
|
278
|
-
const name = getDisplayName(a.type);
|
|
279
|
-
const modeLabel = getPromptModeLabel(a.type);
|
|
279
|
+
const name = getDisplayName(a.type, this.registry);
|
|
280
|
+
const modeLabel = getPromptModeLabel(a.type, this.registry);
|
|
280
281
|
const duration = formatMs((a.completedAt ?? Date.now()) - a.startedAt);
|
|
281
282
|
|
|
282
283
|
let icon: string;
|
|
@@ -345,8 +346,8 @@ export class AgentWidget {
|
|
|
345
346
|
|
|
346
347
|
const runningLines: string[][] = []; // each entry is [header, activity]
|
|
347
348
|
for (const a of running) {
|
|
348
|
-
const name = getDisplayName(a.type);
|
|
349
|
-
const modeLabel = getPromptModeLabel(a.type);
|
|
349
|
+
const name = getDisplayName(a.type, this.registry);
|
|
350
|
+
const modeLabel = getPromptModeLabel(a.type, this.registry);
|
|
350
351
|
const modeTag = modeLabel ? ` ${theme.fg("dim", `(${modeLabel})`)}` : "";
|
|
351
352
|
const elapsed = formatMs(Date.now() - a.startedAt);
|
|
352
353
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { type Component, matchesKey, type TUI, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
10
|
+
import type { AgentConfigLookup } from "../agent-types.js";
|
|
10
11
|
import { extractText } from "../context.js";
|
|
11
12
|
import type { AgentRecord } from "../types.js";
|
|
12
13
|
import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
|
|
@@ -33,6 +34,7 @@ export class ConversationViewer implements Component {
|
|
|
33
34
|
private activity: AgentActivity | undefined,
|
|
34
35
|
private theme: Theme,
|
|
35
36
|
private done: (result: undefined) => void,
|
|
37
|
+
private registry: AgentConfigLookup,
|
|
36
38
|
) {
|
|
37
39
|
this.unsubscribe = session.subscribe(() => {
|
|
38
40
|
if (this.closed) return;
|
|
@@ -91,8 +93,8 @@ export class ConversationViewer implements Component {
|
|
|
91
93
|
|
|
92
94
|
// Header
|
|
93
95
|
lines.push(hrTop);
|
|
94
|
-
const name = getDisplayName(this.record.type);
|
|
95
|
-
const modeLabel = getPromptModeLabel(this.record.type);
|
|
96
|
+
const name = getDisplayName(this.record.type, this.registry);
|
|
97
|
+
const modeLabel = getPromptModeLabel(this.record.type, this.registry);
|
|
96
98
|
const modeTag = modeLabel ? ` ${th.fg("dim", `(${modeLabel})`)}` : "";
|
|
97
99
|
const statusIcon = this.record.status === "running"
|
|
98
100
|
? th.fg("accent", "●")
|