@gotgenes/pi-subagents 6.3.1 → 6.5.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 +32 -0
- package/docs/architecture/architecture.md +244 -260
- package/docs/plans/0108-extract-agent-type-registry.md +322 -0
- package/docs/plans/0109-extract-settings-manager.md +276 -0
- package/docs/retro/0108-extract-agent-type-registry.md +41 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +16 -13
- package/src/agent-runner.ts +4 -0
- package/src/agent-types.ts +108 -91
- package/src/index.ts +31 -58
- package/src/runtime.ts +0 -6
- package/src/session-config.ts +5 -4
- package/src/settings.ts +94 -46
- package/src/tools/agent-tool.ts +11 -11
- package/src/tools/get-result-tool.ts +3 -1
- package/src/types.ts +0 -3
- package/src/ui/agent-menu.ts +47 -53
- package/src/ui/agent-widget.ts +10 -9
- package/src/ui/conversation-viewer.ts +4 -2
package/src/agent-manager.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { Model } from "@earendil-works/pi-ai";
|
|
|
11
11
|
import type { AgentSession, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { AgentRecord } from "./agent-record.js";
|
|
13
13
|
import type { AgentRunner } from "./agent-runner.js";
|
|
14
|
+
import { AgentTypeRegistry } from "./agent-types.js";
|
|
14
15
|
import { debugLog } from "./debug.js";
|
|
15
16
|
import { buildParentSnapshot } from "./parent-snapshot.js";
|
|
16
17
|
import { subscribeRecordObserver } from "./record-observer.js";
|
|
@@ -30,7 +31,9 @@ export interface AgentManagerOptions {
|
|
|
30
31
|
runner: AgentRunner;
|
|
31
32
|
worktrees: WorktreeManager;
|
|
32
33
|
exec: ShellExec;
|
|
33
|
-
|
|
34
|
+
registry: AgentTypeRegistry;
|
|
35
|
+
/** Injected getter for the concurrency limit — owned by SettingsManager. */
|
|
36
|
+
getMaxConcurrent?: () => number;
|
|
34
37
|
getRunConfig?: () => RunConfig;
|
|
35
38
|
onStart?: OnAgentStart;
|
|
36
39
|
onComplete?: OnAgentComplete;
|
|
@@ -81,7 +84,8 @@ export class AgentManager {
|
|
|
81
84
|
private readonly runner: AgentRunner;
|
|
82
85
|
private readonly worktrees: WorktreeManager;
|
|
83
86
|
private readonly exec: ShellExec;
|
|
84
|
-
private
|
|
87
|
+
private readonly registry: AgentTypeRegistry;
|
|
88
|
+
private readonly _getMaxConcurrent: () => number;
|
|
85
89
|
private getRunConfig?: () => RunConfig;
|
|
86
90
|
|
|
87
91
|
/** Queue of background agents waiting to start. */
|
|
@@ -93,27 +97,25 @@ export class AgentManager {
|
|
|
93
97
|
this.runner = options.runner;
|
|
94
98
|
this.worktrees = options.worktrees;
|
|
95
99
|
this.exec = options.exec;
|
|
100
|
+
this.registry = options.registry;
|
|
96
101
|
this.onComplete = options.onComplete;
|
|
97
102
|
this.onStart = options.onStart;
|
|
98
103
|
this.onCompact = options.onCompact;
|
|
99
104
|
this.getRunConfig = options.getRunConfig;
|
|
100
|
-
this.
|
|
105
|
+
this._getMaxConcurrent = options.getMaxConcurrent ?? (() => DEFAULT_MAX_CONCURRENT);
|
|
101
106
|
// Cleanup completed agents after 10 minutes (but keep sessions for resume)
|
|
102
107
|
this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);
|
|
103
108
|
this.cleanupInterval.unref();
|
|
104
109
|
}
|
|
105
110
|
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Drain the concurrency queue after SettingsManager has updated maxConcurrent.
|
|
113
|
+
* Call this whenever the concurrency limit increases so queued agents can start.
|
|
114
|
+
*/
|
|
115
|
+
notifyConcurrencyChanged(): void {
|
|
110
116
|
this.drainQueue();
|
|
111
117
|
}
|
|
112
118
|
|
|
113
|
-
getMaxConcurrent(): number {
|
|
114
|
-
return this.maxConcurrent;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
119
|
/**
|
|
118
120
|
* Spawn an agent and return its ID immediately (for background use).
|
|
119
121
|
* If the concurrency limit is reached, the agent is queued.
|
|
@@ -140,7 +142,7 @@ export class AgentManager {
|
|
|
140
142
|
const snapshot = buildParentSnapshot(ctx, options.inheritContext);
|
|
141
143
|
const args: SpawnArgs = { snapshot, type, prompt, options };
|
|
142
144
|
|
|
143
|
-
if (options.isBackground && !options.bypassQueue && this.runningBackground >= this.
|
|
145
|
+
if (options.isBackground && !options.bypassQueue && this.runningBackground >= this._getMaxConcurrent()) {
|
|
144
146
|
// Queue it — will be started when a running agent completes
|
|
145
147
|
this.queue.push({ id, args });
|
|
146
148
|
return id;
|
|
@@ -203,6 +205,7 @@ export class AgentManager {
|
|
|
203
205
|
parentSessionFile: options.parentSessionFile,
|
|
204
206
|
parentSessionId: options.parentSessionId,
|
|
205
207
|
signal: record.abortController!.signal,
|
|
208
|
+
registry: this.registry,
|
|
206
209
|
onSessionCreated: (session) => {
|
|
207
210
|
record.session = session;
|
|
208
211
|
// Capture the session file path early so it's available for display
|
|
@@ -279,7 +282,7 @@ export class AgentManager {
|
|
|
279
282
|
|
|
280
283
|
/** Start queued agents up to the concurrency limit. */
|
|
281
284
|
private drainQueue() {
|
|
282
|
-
while (this.queue.length > 0 && this.runningBackground < this.
|
|
285
|
+
while (this.queue.length > 0 && this.runningBackground < this._getMaxConcurrent()) {
|
|
283
286
|
const next = this.queue.shift()!;
|
|
284
287
|
const record = this.agents.get(next.id);
|
|
285
288
|
if (!record || record.status !== "queued") continue;
|
package/src/agent-runner.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
SessionManager,
|
|
13
13
|
SettingsManager,
|
|
14
14
|
} from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import type { AgentConfigLookup } from "./agent-types.js";
|
|
15
16
|
import { extractText } from "./context.js";
|
|
16
17
|
import { detectEnv } from "./env.js";
|
|
17
18
|
import { assembleSessionConfig } from "./session-config.js";
|
|
@@ -91,6 +92,8 @@ export interface RunOptions {
|
|
|
91
92
|
* module-scope `graceTurns` during migration.
|
|
92
93
|
*/
|
|
93
94
|
graceTurns?: number;
|
|
95
|
+
/** Agent config lookup — provides resolveAgentConfig and getToolNamesForType. */
|
|
96
|
+
registry: AgentConfigLookup;
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
export interface RunResult {
|
|
@@ -189,6 +192,7 @@ export async function runAgent(
|
|
|
189
192
|
thinkingLevel: options.thinkingLevel,
|
|
190
193
|
},
|
|
191
194
|
env,
|
|
195
|
+
options.registry,
|
|
192
196
|
);
|
|
193
197
|
|
|
194
198
|
const agentDir = getAgentDir();
|
package/src/agent-types.ts
CHANGED
|
@@ -8,79 +8,132 @@
|
|
|
8
8
|
import { DEFAULT_AGENTS } from "./default-agents.js";
|
|
9
9
|
import type { AgentConfig } from "./types.js";
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
export const BUILTIN_TOOL_NAMES: string[] = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
11
|
+
// ── AgentConfigLookup interface ──────────────────────────────────────────────
|
|
13
12
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Narrow registry interface for consumers that only need config resolution.
|
|
15
|
+
* Prefer this over the full `AgentTypeRegistry` in function signatures (ISP).
|
|
16
|
+
*/
|
|
17
|
+
export interface AgentConfigLookup {
|
|
18
|
+
resolveAgentConfig(type: string): AgentConfig;
|
|
19
|
+
getToolNamesForType(type: string): string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ── AgentTypeRegistry class ──────────────────────────────────────────────────
|
|
16
23
|
|
|
17
24
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
25
|
+
* Injectable registry of all agent configurations (defaults + user-defined).
|
|
26
|
+
*
|
|
27
|
+
* Replaces the module-scoped `agents` Map and its companion free functions.
|
|
28
|
+
* The constructor accepts a `loadUserAgents` callback to defer disk I/O to the
|
|
29
|
+
* call site, keeping this class side-effect-free and easy to test.
|
|
21
30
|
*/
|
|
22
|
-
export
|
|
23
|
-
agents
|
|
31
|
+
export class AgentTypeRegistry implements AgentConfigLookup {
|
|
32
|
+
private agents = new Map<string, AgentConfig>();
|
|
24
33
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
/** The three embedded default agent names. */
|
|
35
|
+
static readonly DEFAULT_AGENT_NAMES = ["general-purpose", "Explore", "Plan"] as const;
|
|
36
|
+
|
|
37
|
+
constructor(private loadUserAgents: () => Map<string, AgentConfig>) {
|
|
38
|
+
this.reload();
|
|
28
39
|
}
|
|
29
40
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Re-scan user agents and rebuild the registry.
|
|
43
|
+
* Starts with DEFAULT_AGENTS, then overlays whatever `loadUserAgents()` returns.
|
|
44
|
+
*/
|
|
45
|
+
reload(): void {
|
|
46
|
+
this.agents.clear();
|
|
47
|
+
for (const [name, config] of DEFAULT_AGENTS) {
|
|
48
|
+
this.agents.set(name, config);
|
|
49
|
+
}
|
|
50
|
+
for (const [name, config] of this.loadUserAgents()) {
|
|
51
|
+
this.agents.set(name, config);
|
|
52
|
+
}
|
|
33
53
|
}
|
|
34
|
-
}
|
|
35
54
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const lower = name.toLowerCase();
|
|
40
|
-
for (const key of agents.keys()) {
|
|
41
|
-
if (key.toLowerCase() === lower) return key;
|
|
55
|
+
/** Resolve a type name case-insensitively. Returns the canonical key or undefined. */
|
|
56
|
+
resolveType(name: string): string | undefined {
|
|
57
|
+
return this.resolveKey(name);
|
|
42
58
|
}
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
59
|
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
/** Get all enabled type names (for spawning and tool descriptions). */
|
|
61
|
+
getAvailableTypes(): string[] {
|
|
62
|
+
return [...this.agents.entries()]
|
|
63
|
+
.filter(([_, config]) => config.enabled !== false)
|
|
64
|
+
.map(([name]) => name);
|
|
65
|
+
}
|
|
50
66
|
|
|
51
|
-
/** Get all
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.map(([name]) => name);
|
|
56
|
-
}
|
|
67
|
+
/** Get all type names including disabled (for UI listing). */
|
|
68
|
+
getAllTypes(): string[] {
|
|
69
|
+
return [...this.agents.keys()];
|
|
70
|
+
}
|
|
57
71
|
|
|
58
|
-
/** Get
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
/** Get names of default agents currently in the registry. */
|
|
73
|
+
getDefaultAgentNames(): string[] {
|
|
74
|
+
return [...this.agents.entries()]
|
|
75
|
+
.filter(([_, config]) => config.isDefault === true)
|
|
76
|
+
.map(([name]) => name);
|
|
77
|
+
}
|
|
62
78
|
|
|
63
|
-
/** Get names of
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
79
|
+
/** Get names of user-defined agents (non-defaults) currently in the registry. */
|
|
80
|
+
getUserAgentNames(): string[] {
|
|
81
|
+
return [...this.agents.entries()]
|
|
82
|
+
.filter(([_, config]) => config.isDefault !== true)
|
|
83
|
+
.map(([name]) => name);
|
|
84
|
+
}
|
|
69
85
|
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.
|
|
75
|
-
}
|
|
86
|
+
/** Check if a type is valid and enabled (case-insensitive). */
|
|
87
|
+
isValidType(type: string): boolean {
|
|
88
|
+
const key = this.resolveKey(type);
|
|
89
|
+
if (!key) return false;
|
|
90
|
+
return this.agents.get(key)?.enabled !== false;
|
|
91
|
+
}
|
|
76
92
|
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
/** Get built-in tool names for a type (case-insensitive). */
|
|
94
|
+
getToolNamesForType(type: string): string[] {
|
|
95
|
+
const key = this.resolveKey(type);
|
|
96
|
+
const raw = key ? this.agents.get(key) : undefined;
|
|
97
|
+
const config = raw?.enabled !== false ? raw : undefined;
|
|
98
|
+
const names = config?.builtinToolNames?.length ? config.builtinToolNames : [...BUILTIN_TOOL_NAMES];
|
|
99
|
+
return names;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Resolve agent config with guaranteed non-null return. Falls back: unknown → general-purpose → absolute fallback. */
|
|
103
|
+
resolveAgentConfig(type: string): AgentConfig {
|
|
104
|
+
const key = this.resolveKey(type);
|
|
105
|
+
const config = key ? this.agents.get(key) : undefined;
|
|
106
|
+
if (config) return config;
|
|
107
|
+
|
|
108
|
+
const gp = this.agents.get("general-purpose");
|
|
109
|
+
if (gp) return gp;
|
|
110
|
+
|
|
111
|
+
// Absolute fallback (should never happen in practice)
|
|
112
|
+
return {
|
|
113
|
+
name: type,
|
|
114
|
+
displayName: "Agent",
|
|
115
|
+
description: "General-purpose agent for complex, multi-step tasks",
|
|
116
|
+
builtinToolNames: BUILTIN_TOOL_NAMES,
|
|
117
|
+
extensions: true,
|
|
118
|
+
skills: true,
|
|
119
|
+
systemPrompt: "",
|
|
120
|
+
promptMode: "append",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private resolveKey(name: string): string | undefined {
|
|
125
|
+
if (this.agents.has(name)) return name;
|
|
126
|
+
const lower = name.toLowerCase();
|
|
127
|
+
for (const key of this.agents.keys()) {
|
|
128
|
+
if (key.toLowerCase() === lower) return key;
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
82
132
|
}
|
|
83
133
|
|
|
134
|
+
/** All known built-in tool names. */
|
|
135
|
+
export const BUILTIN_TOOL_NAMES: string[] = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
136
|
+
|
|
84
137
|
/** Tool names required for memory management. */
|
|
85
138
|
const MEMORY_TOOL_NAMES = ["read", "write", "edit"];
|
|
86
139
|
|
|
@@ -100,39 +153,3 @@ const READONLY_MEMORY_TOOL_NAMES = ["read"];
|
|
|
100
153
|
export function getReadOnlyMemoryToolNames(existingToolNames: Set<string>): string[] {
|
|
101
154
|
return READONLY_MEMORY_TOOL_NAMES.filter(n => !existingToolNames.has(n));
|
|
102
155
|
}
|
|
103
|
-
|
|
104
|
-
/** Get built-in tool names for a type (case-insensitive). */
|
|
105
|
-
export function getToolNamesForType(type: string): string[] {
|
|
106
|
-
const key = resolveKey(type);
|
|
107
|
-
const raw = key ? agents.get(key) : undefined;
|
|
108
|
-
const config = raw?.enabled !== false ? raw : undefined;
|
|
109
|
-
const names = config?.builtinToolNames?.length ? config.builtinToolNames : [...BUILTIN_TOOL_NAMES];
|
|
110
|
-
return names;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Resolve agent config with guaranteed non-null return. Falls back: unknown → general-purpose → absolute fallback. */
|
|
114
|
-
export function resolveAgentConfig(type: string): AgentConfig {
|
|
115
|
-
const key = resolveKey(type);
|
|
116
|
-
const config = key ? agents.get(key) : undefined;
|
|
117
|
-
if (config) {
|
|
118
|
-
return config;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Fallback to general-purpose for unknown types
|
|
122
|
-
const gp = agents.get("general-purpose");
|
|
123
|
-
if (gp) {
|
|
124
|
-
return gp;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Absolute fallback (should never happen in practice)
|
|
128
|
-
return {
|
|
129
|
-
name: type,
|
|
130
|
-
displayName: "Agent",
|
|
131
|
-
description: "General-purpose agent for complex, multi-step tasks",
|
|
132
|
-
builtinToolNames: BUILTIN_TOOL_NAMES,
|
|
133
|
-
extensions: true,
|
|
134
|
-
skills: true,
|
|
135
|
-
systemPrompt: "",
|
|
136
|
-
promptMode: "append",
|
|
137
|
-
};
|
|
138
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
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
|
-
import { getAgentConversation,
|
|
17
|
-
import {
|
|
16
|
+
import { getAgentConversation, resumeAgent, runAgent, steerAgent } from "./agent-runner.js";
|
|
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";
|
|
@@ -23,7 +23,7 @@ import { createNotificationRenderer } from "./renderer.js";
|
|
|
23
23
|
import { createSubagentRuntime } from "./runtime.js";
|
|
24
24
|
import { publishSubagentsService, unpublishSubagentsService } from "./service.js";
|
|
25
25
|
import { createSubagentsService } from "./service-adapter.js";
|
|
26
|
-
import {
|
|
26
|
+
import { SettingsManager } from "./settings.js";
|
|
27
27
|
import { createAgentTool } from "./tools/agent-tool.js";
|
|
28
28
|
import { createGetResultTool } from "./tools/get-result-tool.js";
|
|
29
29
|
import { getModelLabelFromConfig } from "./tools/helpers.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();
|
|
@@ -62,11 +55,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
62
55
|
updateWidget: () => runtime.updateWidget(),
|
|
63
56
|
});
|
|
64
57
|
|
|
58
|
+
// Settings: owns all three in-memory values and handles load/save/emit.
|
|
59
|
+
const settings = new SettingsManager({
|
|
60
|
+
emit: (event, payload) => pi.events.emit(event, payload),
|
|
61
|
+
cwd: process.cwd(),
|
|
62
|
+
});
|
|
63
|
+
settings.load();
|
|
64
|
+
|
|
65
65
|
// Background completion: emit lifecycle event and delegate to notification system
|
|
66
66
|
const manager = new AgentManager({
|
|
67
67
|
runner: { run: runAgent, resume: resumeAgent },
|
|
68
68
|
worktrees: new GitWorktreeManager(process.cwd()),
|
|
69
69
|
exec: (cmd, args, opts) => pi.exec(cmd, args, opts),
|
|
70
|
+
registry,
|
|
70
71
|
onComplete: (record) => {
|
|
71
72
|
// Emit lifecycle event based on terminal status
|
|
72
73
|
const isError = record.status === "error" || record.status === "stopped" || record.status === "aborted";
|
|
@@ -111,7 +112,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
111
112
|
compactionCount: record.compactionCount,
|
|
112
113
|
});
|
|
113
114
|
},
|
|
114
|
-
|
|
115
|
+
getMaxConcurrent: () => settings.maxConcurrent,
|
|
116
|
+
getRunConfig: () => settings,
|
|
115
117
|
});
|
|
116
118
|
|
|
117
119
|
// Typed service published via Symbol.for() for cross-extension access.
|
|
@@ -137,7 +139,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
137
139
|
pi.on("session_shutdown", () => lifecycle.handleSessionShutdown());
|
|
138
140
|
|
|
139
141
|
// Live widget: show running agents above editor
|
|
140
|
-
runtime.widget = new AgentWidget(manager, runtime.agentActivity);
|
|
142
|
+
runtime.widget = new AgentWidget(manager, runtime.agentActivity, registry);
|
|
141
143
|
|
|
142
144
|
// Grab UI context from first tool execution + clear lingering widget on new turn
|
|
143
145
|
const toolStart = new ToolStartHandler(runtime);
|
|
@@ -145,17 +147,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
145
147
|
|
|
146
148
|
/** Build the full type list text dynamically from the unified registry. */
|
|
147
149
|
const buildTypeListText = () => {
|
|
148
|
-
const defaultNames = getDefaultAgentNames();
|
|
149
|
-
const userNames = getUserAgentNames();
|
|
150
|
+
const defaultNames = registry.getDefaultAgentNames();
|
|
151
|
+
const userNames = registry.getUserAgentNames();
|
|
150
152
|
|
|
151
153
|
const defaultDescs = defaultNames.map((name) => {
|
|
152
|
-
const cfg = resolveAgentConfig(name);
|
|
154
|
+
const cfg = registry.resolveAgentConfig(name);
|
|
153
155
|
const modelSuffix = cfg.model ? ` (${getModelLabelFromConfig(cfg.model)})` : "";
|
|
154
156
|
return `- ${name}: ${cfg.description}${modelSuffix}`;
|
|
155
157
|
});
|
|
156
158
|
|
|
157
159
|
const customDescs = userNames.map((name) => {
|
|
158
|
-
const cfg = resolveAgentConfig(name);
|
|
160
|
+
const cfg = registry.resolveAgentConfig(name);
|
|
159
161
|
return `- ${name}: ${cfg.description}`;
|
|
160
162
|
});
|
|
161
163
|
|
|
@@ -170,18 +172,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
170
172
|
|
|
171
173
|
const typeListText = buildTypeListText();
|
|
172
174
|
|
|
173
|
-
// Apply persisted settings on startup and emit `subagents:settings_loaded`.
|
|
174
|
-
// Global + project merged; missing → defaults; corrupt file emits a warning
|
|
175
|
-
// to stderr and falls back to defaults.
|
|
176
|
-
applyAndEmitLoaded(
|
|
177
|
-
{
|
|
178
|
-
setMaxConcurrent: (n) => manager.setMaxConcurrent(n),
|
|
179
|
-
setDefaultMaxTurns: (n) => { runtime.defaultMaxTurns = normalizeMaxTurns(n); },
|
|
180
|
-
setGraceTurns: (n) => { runtime.graceTurns = Math.max(1, n); },
|
|
181
|
-
},
|
|
182
|
-
(event, payload) => pi.events.emit(event, payload),
|
|
183
|
-
);
|
|
184
|
-
|
|
185
175
|
// ---- Agent tool ----
|
|
186
176
|
|
|
187
177
|
pi.registerTool(defineTool(createAgentTool({
|
|
@@ -190,7 +180,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
190
180
|
spawnAndWait: (ctx, type, prompt, opts) => manager.spawnAndWait(ctx, type, prompt, opts),
|
|
191
181
|
resume: (id, prompt, signal) => manager.resume(id, prompt, signal),
|
|
192
182
|
getRecord: (id) => manager.getRecord(id),
|
|
193
|
-
getMaxConcurrent: () =>
|
|
183
|
+
getMaxConcurrent: () => settings.maxConcurrent,
|
|
194
184
|
listAgents: () => manager.listAgents(),
|
|
195
185
|
},
|
|
196
186
|
widget: {
|
|
@@ -201,11 +191,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
201
191
|
},
|
|
202
192
|
agentActivity: runtime.agentActivity,
|
|
203
193
|
emitEvent: (name, data) => pi.events.emit(name, data),
|
|
204
|
-
|
|
194
|
+
registry,
|
|
205
195
|
typeListText,
|
|
206
|
-
availableTypesText: getAvailableTypes().join(", "),
|
|
196
|
+
availableTypesText: registry.getAvailableTypes().join(", "),
|
|
207
197
|
agentDir: getAgentDir(),
|
|
208
|
-
|
|
198
|
+
settings,
|
|
209
199
|
})));
|
|
210
200
|
|
|
211
201
|
// ---- get_subagent_result tool ----
|
|
@@ -214,6 +204,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
214
204
|
getRecord: (id) => manager.getRecord(id),
|
|
215
205
|
cancelNudge: (key) => notifications.cancelNudge(key),
|
|
216
206
|
getConversation: (session) => getAgentConversation(session),
|
|
207
|
+
registry,
|
|
217
208
|
})));
|
|
218
209
|
|
|
219
210
|
// ---- steer_subagent tool ----
|
|
@@ -231,38 +222,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
231
222
|
listAgents: () => manager.listAgents(),
|
|
232
223
|
getRecord: (id) => manager.getRecord(id),
|
|
233
224
|
spawnAndWait: (ctx, type, prompt, opts) => manager.spawnAndWait(ctx, type, prompt, opts),
|
|
234
|
-
|
|
235
|
-
setMaxConcurrent: (n) => manager.setMaxConcurrent(n),
|
|
225
|
+
notifyConcurrencyChanged: () => manager.notifyConcurrencyChanged(),
|
|
236
226
|
},
|
|
237
|
-
|
|
227
|
+
registry,
|
|
238
228
|
agentActivity: runtime.agentActivity,
|
|
239
|
-
getModelLabel: (type,
|
|
240
|
-
const cfg = resolveAgentConfig(type);
|
|
229
|
+
getModelLabel: (type, modelRegistry) => {
|
|
230
|
+
const cfg = registry.resolveAgentConfig(type);
|
|
241
231
|
if (!cfg.model) return 'inherit';
|
|
242
|
-
if (
|
|
243
|
-
const resolved = resolveModel(cfg.model,
|
|
232
|
+
if (modelRegistry) {
|
|
233
|
+
const resolved = resolveModel(cfg.model, modelRegistry);
|
|
244
234
|
if (typeof resolved === 'string') return 'inherit';
|
|
245
235
|
}
|
|
246
236
|
return getModelLabelFromConfig(cfg.model);
|
|
247
237
|
},
|
|
248
|
-
|
|
249
|
-
maxConcurrent: manager.getMaxConcurrent(),
|
|
250
|
-
defaultMaxTurns: runtime.defaultMaxTurns ?? 0,
|
|
251
|
-
graceTurns: runtime.graceTurns,
|
|
252
|
-
}),
|
|
253
|
-
getDefaultMaxTurns: () => runtime.defaultMaxTurns,
|
|
254
|
-
getGraceTurns: () => runtime.graceTurns,
|
|
255
|
-
setDefaultMaxTurns: (n) => {
|
|
256
|
-
runtime.defaultMaxTurns = normalizeMaxTurns(n);
|
|
257
|
-
},
|
|
258
|
-
setGraceTurns: (n) => {
|
|
259
|
-
runtime.graceTurns = Math.max(1, n);
|
|
260
|
-
},
|
|
261
|
-
saveSettings: (settings, successMsg) => saveAndEmitChanged(
|
|
262
|
-
settings,
|
|
263
|
-
successMsg,
|
|
264
|
-
(event, payload) => pi.events.emit(event, payload),
|
|
265
|
-
),
|
|
238
|
+
settings,
|
|
266
239
|
emitEvent: (name, data) => pi.events.emit(name, data),
|
|
267
240
|
personalAgentsDir: join(getAgentDir(), 'agents'),
|
|
268
241
|
projectAgentsDir: join(process.cwd(), '.pi', 'agents'),
|
package/src/runtime.ts
CHANGED
|
@@ -24,12 +24,6 @@ export interface RunConfig {
|
|
|
24
24
|
* Tests construct a fresh runtime per test for full isolation.
|
|
25
25
|
*/
|
|
26
26
|
export class SubagentRuntime {
|
|
27
|
-
// ── Execution config (was module-scope in agent-runner.ts) ──────────────
|
|
28
|
-
/** Default max turns for all agents. undefined = unlimited. */
|
|
29
|
-
defaultMaxTurns: number | undefined = undefined;
|
|
30
|
-
/** Additional turns allowed after the soft-limit steer message. */
|
|
31
|
-
graceTurns: number = 5;
|
|
32
|
-
|
|
33
27
|
// ── Session state (was closure-scoped in index.ts) ───────────────────────
|
|
34
28
|
/** Active Pi session context — set on session_start, cleared on session_shutdown. */
|
|
35
29
|
currentCtx: { pi: unknown; ctx: unknown } | undefined = undefined;
|
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
|