@narumitw/pi-subagents 0.53.0 → 1.0.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 +85 -15
- package/package.json +1 -1
- package/src/agents/built-ins.ts +124 -0
- package/src/agents/catalog.ts +224 -0
- package/src/agents/discovery.ts +249 -0
- package/src/agents/types.ts +98 -0
- package/src/agents.ts +47 -670
- package/src/auto-transport.ts +2 -1
- package/src/automation.ts +7 -2
- package/src/capability-router.ts +1 -1
- package/src/completion-delivery.ts +82 -11
- package/src/config-status.ts +2 -2
- package/src/config-ui.ts +8 -8
- package/src/consult-resources.ts +1 -1
- package/src/consult.ts +9 -7
- package/src/create-stateful-transport.ts +2 -1
- package/src/cwd-policy.ts +1 -1
- package/src/execution/budget.ts +56 -0
- package/src/execution/runtime-policy.ts +19 -0
- package/src/execution-plan.ts +1 -1
- package/src/execution-profiles.ts +1 -1
- package/src/execution-ui.ts +1 -1
- package/src/execution.ts +269 -100
- package/src/in-process-transport.ts +3 -2
- package/src/inspect.ts +39 -8
- package/src/limits.ts +1 -0
- package/src/orchestration-metrics.ts +12 -5
- package/src/panel-execution.ts +1 -1
- package/src/panel-planning.ts +1 -1
- package/src/params.ts +3 -1
- package/src/persistence.ts +106 -7
- package/src/registry-types.ts +22 -5
- package/src/registry.ts +205 -37
- package/src/render.ts +1 -1
- package/src/retained-semantic-state.ts +1 -1
- package/src/rpc-transport-metadata.ts +1 -1
- package/src/rpc-transport.ts +2 -1
- package/src/runner.ts +6 -1
- package/src/settings/inspection.ts +275 -0
- package/src/settings/schema.ts +186 -0
- package/src/settings.ts +72 -420
- package/src/spawn-idempotency.ts +1 -1
- package/src/stateful-agent-view.ts +87 -0
- package/src/stateful-config.ts +1 -1
- package/src/stateful-guidance.ts +2 -2
- package/src/stateful-limits.ts +1 -1
- package/src/stateful-prompt.ts +2 -2
- package/src/stateful-render.ts +0 -1
- package/src/stateful-safety.ts +2 -1
- package/src/stateful-tool-params.ts +13 -20
- package/src/stateful.ts +36 -117
- package/src/subagents.ts +8 -9
- package/src/subprocess-transport.ts +2 -6
- package/src/transport-types.ts +1 -1
- package/src/transport-ui.ts +1 -1
- package/src/verification-harness.ts +516 -0
- package/src/verification-receipt.ts +275 -0
- package/src/verified-execution-benchmark.ts +86 -0
- package/src/verified-execution-contract.ts +219 -0
- package/src/work-item-ledger.ts +510 -37
- package/src/work-item-persistence.ts +31 -0
- package/src/workflow-completion-controller.ts +397 -0
- package/src/workflow-plan-compiler.ts +1 -1
- package/src/workflow-plan-patch.ts +1 -1
- package/src/workflow-planning.ts +11 -1
- package/src/workflow-ui.ts +1 -1
package/src/agents.ts
CHANGED
|
@@ -1,674 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as fs from "node:fs";
|
|
6
|
-
import * as path from "node:path";
|
|
7
|
-
import { CONFIG_DIR_NAME, getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import {
|
|
9
|
-
type AgentCapabilityManifest,
|
|
10
|
-
CAPABILITY_MANIFEST_VERSION,
|
|
11
|
-
normalizeCapabilityManifest,
|
|
12
|
-
} from "./capabilities.js";
|
|
13
|
-
|
|
14
|
-
export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
15
|
-
|
|
16
|
-
export type SubagentThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
17
|
-
|
|
18
|
-
export function isThinkingLevel(value: unknown): value is SubagentThinkingLevel {
|
|
19
|
-
return typeof value === "string" && THINKING_LEVELS.includes(value as SubagentThinkingLevel);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type AgentScope = "user" | "project" | "both";
|
|
23
|
-
|
|
24
|
-
export type AgentSource = "built-in" | "user" | "project";
|
|
25
|
-
|
|
26
|
-
export const DEFAULT_PI_TOOL_NAMES = ["read", "bash", "edit", "write"] as const;
|
|
27
|
-
|
|
28
|
-
export function resolveAgentToolNames(tools: readonly string[] | undefined): string[] {
|
|
29
|
-
return [...new Set(tools ?? DEFAULT_PI_TOOL_NAMES)];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface AgentConfig {
|
|
33
|
-
name: string;
|
|
34
|
-
description: string;
|
|
35
|
-
tools?: string[];
|
|
36
|
-
model?: string;
|
|
37
|
-
thinkingLevel?: SubagentThinkingLevel;
|
|
38
|
-
timeoutMs?: number;
|
|
39
|
-
capabilityManifest?: AgentCapabilityManifest;
|
|
40
|
-
systemPrompt: string;
|
|
41
|
-
source: AgentSource;
|
|
42
|
-
filePath: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface SubagentAgentConfig {
|
|
46
|
-
tools?: string[];
|
|
47
|
-
model?: string | null;
|
|
48
|
-
thinkingLevel?: SubagentThinkingLevel | null;
|
|
49
|
-
timeoutMs?: number | null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export type SubagentTransportKind = "subprocess" | "in-process" | "rpc" | "auto";
|
|
53
|
-
|
|
54
|
-
export type CompletionDelivery = "next-turn" | "auto-resume";
|
|
55
|
-
|
|
56
|
-
export const CONSULT_RESOURCE_POLICIES = ["project-context", "none", "all"] as const;
|
|
57
|
-
|
|
58
|
-
export type ConsultResourcePolicy = (typeof CONSULT_RESOURCE_POLICIES)[number];
|
|
59
|
-
|
|
60
|
-
export interface SubagentConsultSettings {
|
|
61
|
-
resources?: ConsultResourcePolicy;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export const CONSULTATION_CWD_POLICIES = ["anywhere", "current-workspace"] as const;
|
|
65
|
-
export type ConsultationCwdPolicy = (typeof CONSULTATION_CWD_POLICIES)[number];
|
|
66
|
-
|
|
67
|
-
export const DELEGATION_CWD_POLICIES = [
|
|
68
|
-
"trusted-targets",
|
|
69
|
-
"current-workspace",
|
|
70
|
-
"anywhere",
|
|
71
|
-
] as const;
|
|
72
|
-
export type DelegationCwdPolicy = (typeof DELEGATION_CWD_POLICIES)[number];
|
|
73
|
-
|
|
74
|
-
export interface SubagentCwdPolicySettings {
|
|
75
|
-
consultation?: ConsultationCwdPolicy;
|
|
76
|
-
delegation?: DelegationCwdPolicy;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface SubagentBlockingSettings {
|
|
80
|
-
enabled?: boolean;
|
|
81
|
-
maxParallelTasks?: number;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export interface SubagentRuntimeSettings {
|
|
85
|
-
enabled?: boolean;
|
|
86
|
-
transport?: SubagentTransportKind;
|
|
87
|
-
completionDelivery?: CompletionDelivery;
|
|
88
|
-
maxAgents?: number;
|
|
89
|
-
maxActiveTurns?: number;
|
|
90
|
-
maxDepth?: number;
|
|
91
|
-
maxChildrenPerAgent?: number;
|
|
92
|
-
maxMailboxMessages?: number;
|
|
93
|
-
maxMailboxMessageBytes?: number;
|
|
94
|
-
idleTtlMs?: number;
|
|
95
|
-
retentionDays?: number;
|
|
96
|
-
maxStoredAgents?: number;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface SubagentSettings {
|
|
100
|
-
agents?: Record<string, SubagentAgentConfig>;
|
|
101
|
-
blocking?: SubagentBlockingSettings;
|
|
102
|
-
stateful?: SubagentRuntimeSettings;
|
|
103
|
-
consult?: SubagentConsultSettings;
|
|
104
|
-
cwdPolicy?: SubagentCwdPolicySettings;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const BUILT_IN_AGENTS: AgentConfig[] = [
|
|
108
|
-
{
|
|
109
|
-
name: "scout",
|
|
110
|
-
description:
|
|
111
|
-
"Read-only codebase reconnaissance; returns concise findings with paths and evidence.",
|
|
112
|
-
tools: ["read", "grep", "find", "ls", "bash"],
|
|
113
|
-
capabilityManifest: builtInManifest(["repository-search", "code-evidence"], "read", [
|
|
114
|
-
"evidence-gathering",
|
|
115
|
-
]),
|
|
116
|
-
source: "built-in",
|
|
117
|
-
filePath: "built-in:scout",
|
|
118
|
-
systemPrompt: [
|
|
119
|
-
"You are a scout subagent. Explore the codebase quickly and report grounded findings.",
|
|
120
|
-
"Do not edit files. Prefer read, grep, find, ls, and safe bash inspection commands.",
|
|
121
|
-
"Return concise bullets with exact file paths, symbols, and open questions.",
|
|
122
|
-
].join("\n"),
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
name: "planner",
|
|
126
|
-
description: "Turns reconnaissance into a lean implementation or migration plan.",
|
|
127
|
-
tools: ["read", "grep", "find", "ls"],
|
|
128
|
-
capabilityManifest: builtInManifest(
|
|
129
|
-
["task-decomposition", "implementation-planning", "migration-planning"],
|
|
130
|
-
"read",
|
|
131
|
-
),
|
|
132
|
-
source: "built-in",
|
|
133
|
-
filePath: "built-in:planner",
|
|
134
|
-
systemPrompt: [
|
|
135
|
-
"You are a planner subagent. Produce executable, verifiable plans only.",
|
|
136
|
-
"Do not modify files. Ground the plan in the repository's actual structure.",
|
|
137
|
-
"Call out assumptions, risks, sequencing, and verification commands.",
|
|
138
|
-
].join("\n"),
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
name: "reviewer",
|
|
142
|
-
description: "Independent code review agent that inspects existing verification evidence.",
|
|
143
|
-
tools: ["read", "grep", "find", "ls", "bash"],
|
|
144
|
-
capabilityManifest: builtInManifest(
|
|
145
|
-
["code-review", "evidence-review", "security-baseline"],
|
|
146
|
-
"read",
|
|
147
|
-
["independent-review"],
|
|
148
|
-
),
|
|
149
|
-
source: "built-in",
|
|
150
|
-
filePath: "built-in:reviewer",
|
|
151
|
-
systemPrompt: [
|
|
152
|
-
"You are a reviewer subagent. Review changes adversarially and assess claims against the code and existing evidence.",
|
|
153
|
-
"Do not edit files or run tests, builds, benchmarks, formatters, or other long-running verification commands.",
|
|
154
|
-
"Inspect code, diffs, test definitions, and existing verification evidence. Recommend any additional commands for the main agent to run.",
|
|
155
|
-
"Report PASS, FAIL, or PARTIAL with evidence, commands inspected, and specific follow-ups.",
|
|
156
|
-
].join("\n"),
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
name: "worker",
|
|
160
|
-
description: "General-purpose implementation worker with the default Pi tool set.",
|
|
161
|
-
capabilityManifest: builtInManifest(
|
|
162
|
-
["implementation", "command-execution", "repository-modification"],
|
|
163
|
-
"write",
|
|
164
|
-
),
|
|
165
|
-
source: "built-in",
|
|
166
|
-
filePath: "built-in:worker",
|
|
167
|
-
systemPrompt: workerSystemPrompt(),
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
name: "general",
|
|
171
|
-
description: "Alias for worker; kept for model-generated subagent names.",
|
|
172
|
-
capabilityManifest: builtInManifest(
|
|
173
|
-
["implementation", "command-execution", "repository-modification"],
|
|
174
|
-
"write",
|
|
175
|
-
),
|
|
176
|
-
source: "built-in",
|
|
177
|
-
filePath: "built-in:general",
|
|
178
|
-
systemPrompt: workerSystemPrompt(),
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
name: "general-purpose",
|
|
182
|
-
description: "Alias for worker; compatible with common subagent naming conventions.",
|
|
183
|
-
capabilityManifest: builtInManifest(
|
|
184
|
-
["implementation", "command-execution", "repository-modification"],
|
|
185
|
-
"write",
|
|
186
|
-
),
|
|
187
|
-
source: "built-in",
|
|
188
|
-
filePath: "built-in:general-purpose",
|
|
189
|
-
systemPrompt: workerSystemPrompt(),
|
|
190
|
-
},
|
|
191
|
-
];
|
|
192
|
-
|
|
193
|
-
export function getBuiltInAgent(name: string): AgentConfig | undefined {
|
|
194
|
-
const agent = BUILT_IN_AGENTS.find((candidate) => candidate.name === name);
|
|
195
|
-
return agent ? structuredClone(agent) : undefined;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function builtInManifest(
|
|
199
|
-
capabilities: string[],
|
|
200
|
-
filesystem: "read" | "write",
|
|
201
|
-
verificationRoles: string[] = [],
|
|
202
|
-
): AgentCapabilityManifest {
|
|
203
|
-
return {
|
|
204
|
-
version: CAPABILITY_MANIFEST_VERSION,
|
|
205
|
-
capabilities,
|
|
206
|
-
modalities: ["text"],
|
|
207
|
-
resultFormats: ["text", "structured-v1", "structured-v2"],
|
|
208
|
-
authority: { filesystem },
|
|
209
|
-
verificationRoles,
|
|
210
|
-
contextStrengths: ["repository"],
|
|
211
|
-
costHint: filesystem === "read" ? "low" : "medium",
|
|
212
|
-
latencyHint: filesystem === "read" ? "low" : "medium",
|
|
213
|
-
limitations: [],
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function workerSystemPrompt(): string {
|
|
218
|
-
return [
|
|
219
|
-
"You are a focused worker subagent running in an isolated Pi process.",
|
|
220
|
-
"Complete the delegated task directly. Keep scope tight and avoid unrelated changes.",
|
|
221
|
-
"When done, summarize files changed, commands run, and any remaining risks.",
|
|
222
|
-
].join("\n");
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export interface AgentDiscoveryResult {
|
|
226
|
-
agents: AgentConfig[];
|
|
227
|
-
projectAgentsDir: string | null;
|
|
228
|
-
omittedAgentDefinitions?: number;
|
|
229
|
-
metadataDiscoveryIncomplete?: boolean;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export interface AgentDiscoveryOptions {
|
|
233
|
-
maxFiles?: number;
|
|
234
|
-
maxFileBytes?: number;
|
|
235
|
-
maxTotalBytes?: number;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
interface LoadedAgents {
|
|
239
|
-
agents: AgentConfig[];
|
|
240
|
-
omittedAgentDefinitions: number;
|
|
241
|
-
metadataDiscoveryIncomplete: boolean;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function readFileBoundedSync(
|
|
245
|
-
filePath: string,
|
|
246
|
-
maxBytes: number | undefined,
|
|
247
|
-
): { content?: string; bytes: number; limited: boolean } {
|
|
248
|
-
if (maxBytes === undefined) {
|
|
249
|
-
try {
|
|
250
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
251
|
-
return { content, bytes: Buffer.byteLength(content), limited: false };
|
|
252
|
-
} catch {
|
|
253
|
-
return { bytes: 0, limited: false };
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const readLimit = Math.max(0, maxBytes);
|
|
258
|
-
let fd: number | undefined;
|
|
259
|
-
try {
|
|
260
|
-
fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK);
|
|
261
|
-
if (!fs.fstatSync(fd).isFile()) return { bytes: 0, limited: false };
|
|
262
|
-
const buffer = Buffer.allocUnsafe(readLimit + 1);
|
|
263
|
-
let offset = 0;
|
|
264
|
-
while (offset < buffer.length) {
|
|
265
|
-
const bytesRead = fs.readSync(fd, buffer, offset, buffer.length - offset, null);
|
|
266
|
-
if (bytesRead === 0) break;
|
|
267
|
-
offset += bytesRead;
|
|
268
|
-
}
|
|
269
|
-
if (offset > readLimit) return { bytes: offset, limited: true };
|
|
270
|
-
return { content: buffer.subarray(0, offset).toString("utf-8"), bytes: offset, limited: false };
|
|
271
|
-
} catch {
|
|
272
|
-
return { bytes: 0, limited: false };
|
|
273
|
-
} finally {
|
|
274
|
-
if (fd !== undefined) fs.closeSync(fd);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function loadAgentsFromDir(
|
|
279
|
-
dir: string,
|
|
280
|
-
source: "user" | "project",
|
|
281
|
-
options: AgentDiscoveryOptions = {},
|
|
282
|
-
): LoadedAgents {
|
|
283
|
-
const agents: AgentConfig[] = [];
|
|
284
|
-
let omittedAgentDefinitions = 0;
|
|
285
|
-
|
|
286
|
-
let entries: fs.Dirent[];
|
|
287
|
-
try {
|
|
288
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
289
|
-
} catch (error) {
|
|
290
|
-
return {
|
|
291
|
-
agents,
|
|
292
|
-
omittedAgentDefinitions,
|
|
293
|
-
metadataDiscoveryIncomplete: (error as NodeJS.ErrnoException).code !== "ENOENT",
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const agentEntries = entries
|
|
298
|
-
.filter((entry) => entry.name.endsWith(".md"))
|
|
299
|
-
.filter((entry) => entry.isFile() || entry.isSymbolicLink());
|
|
300
|
-
let totalBytes = 0;
|
|
301
|
-
|
|
302
|
-
for (const [index, entry] of agentEntries.entries()) {
|
|
303
|
-
if (options.maxFiles !== undefined && index >= options.maxFiles) {
|
|
304
|
-
omittedAgentDefinitions += agentEntries.length - index;
|
|
305
|
-
break;
|
|
306
|
-
}
|
|
307
|
-
const filePath = path.join(dir, entry.name);
|
|
308
|
-
const remainingBytes =
|
|
309
|
-
options.maxTotalBytes === undefined ? undefined : options.maxTotalBytes - totalBytes;
|
|
310
|
-
if (remainingBytes !== undefined && remainingBytes <= 0) {
|
|
311
|
-
omittedAgentDefinitions++;
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
const maxBytes =
|
|
315
|
-
options.maxFileBytes === undefined
|
|
316
|
-
? remainingBytes
|
|
317
|
-
: remainingBytes === undefined
|
|
318
|
-
? options.maxFileBytes
|
|
319
|
-
: Math.min(options.maxFileBytes, remainingBytes);
|
|
320
|
-
const loaded = readFileBoundedSync(filePath, maxBytes);
|
|
321
|
-
totalBytes += Math.min(loaded.bytes, maxBytes ?? loaded.bytes);
|
|
322
|
-
if (loaded.limited || loaded.content === undefined) {
|
|
323
|
-
if (loaded.limited) omittedAgentDefinitions++;
|
|
324
|
-
continue;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const { frontmatter, body } = parseFrontmatter<Record<string, unknown>>(loaded.content);
|
|
328
|
-
|
|
329
|
-
if (typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string") {
|
|
330
|
-
continue;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const hasTools = hasOwn(frontmatter, "tools");
|
|
334
|
-
const rawTools = frontmatter.tools;
|
|
335
|
-
let tools: string[] | undefined;
|
|
336
|
-
if (hasTools) {
|
|
337
|
-
if (rawTools === null) {
|
|
338
|
-
tools = [];
|
|
339
|
-
} else if (Array.isArray(rawTools)) {
|
|
340
|
-
if (!rawTools.every((tool): tool is string => typeof tool === "string")) continue;
|
|
341
|
-
tools = rawTools.map((tool) => tool.trim()).filter(Boolean);
|
|
342
|
-
} else if (typeof rawTools === "string") {
|
|
343
|
-
tools = rawTools
|
|
344
|
-
.split(",")
|
|
345
|
-
.map((tool) => tool.trim())
|
|
346
|
-
.filter(Boolean);
|
|
347
|
-
} else {
|
|
348
|
-
continue;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
agents.push({
|
|
353
|
-
name: frontmatter.name,
|
|
354
|
-
description: frontmatter.description,
|
|
355
|
-
...(hasTools ? { tools: tools ?? [] } : {}),
|
|
356
|
-
model: typeof frontmatter.model === "string" ? frontmatter.model : undefined,
|
|
357
|
-
thinkingLevel: isThinkingLevel(frontmatter.thinkingLevel)
|
|
358
|
-
? frontmatter.thinkingLevel
|
|
359
|
-
: undefined,
|
|
360
|
-
capabilityManifest: normalizeCapabilityManifest(frontmatter.capabilityManifest),
|
|
361
|
-
systemPrompt: body,
|
|
362
|
-
source,
|
|
363
|
-
filePath,
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return { agents, omittedAgentDefinitions, metadataDiscoveryIncomplete: false };
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function isDirectory(p: string): boolean {
|
|
371
|
-
try {
|
|
372
|
-
return fs.statSync(p).isDirectory();
|
|
373
|
-
} catch {
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
379
|
-
let currentDir = cwd;
|
|
380
|
-
while (true) {
|
|
381
|
-
const candidate = path.join(currentDir, CONFIG_DIR_NAME, "agents");
|
|
382
|
-
if (isDirectory(candidate)) return candidate;
|
|
383
|
-
|
|
384
|
-
const parentDir = path.dirname(currentDir);
|
|
385
|
-
if (parentDir === currentDir) return null;
|
|
386
|
-
currentDir = parentDir;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function hasOwn(obj: object, key: PropertyKey): boolean {
|
|
391
|
-
return Object.hasOwn(obj, key);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
export function discoverAgents(
|
|
395
|
-
cwd: string,
|
|
396
|
-
scope: AgentScope,
|
|
397
|
-
config?: SubagentSettings,
|
|
398
|
-
options: AgentDiscoveryOptions = {},
|
|
399
|
-
): AgentDiscoveryResult {
|
|
400
|
-
const userDir = path.join(getAgentDir(), "agents");
|
|
401
|
-
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
402
|
-
|
|
403
|
-
const userLoaded =
|
|
404
|
-
scope === "project"
|
|
405
|
-
? { agents: [], omittedAgentDefinitions: 0, metadataDiscoveryIncomplete: false }
|
|
406
|
-
: loadAgentsFromDir(userDir, "user", options);
|
|
407
|
-
const projectLoaded =
|
|
408
|
-
scope === "user" || !projectAgentsDir
|
|
409
|
-
? { agents: [], omittedAgentDefinitions: 0, metadataDiscoveryIncomplete: false }
|
|
410
|
-
: loadAgentsFromDir(projectAgentsDir, "project", options);
|
|
411
|
-
const userAgents = userLoaded.agents;
|
|
412
|
-
const projectAgents = projectLoaded.agents;
|
|
413
|
-
|
|
414
|
-
const agentMap = new Map<string, AgentConfig>();
|
|
415
|
-
|
|
416
|
-
// Lowest priority: built-ins are always available, then user agents, then
|
|
417
|
-
// trusted project agents if requested. This mirrors the subagent boundary
|
|
418
|
-
// pattern in ./src: stable built-ins plus overridable local definitions.
|
|
419
|
-
for (const agent of BUILT_IN_AGENTS) agentMap.set(agent.name, agent);
|
|
420
|
-
|
|
421
|
-
if (scope === "both") {
|
|
422
|
-
for (const agent of userAgents) agentMap.set(agent.name, agent);
|
|
423
|
-
for (const agent of projectAgents) agentMap.set(agent.name, agent);
|
|
424
|
-
} else if (scope === "user") {
|
|
425
|
-
for (const agent of userAgents) agentMap.set(agent.name, agent);
|
|
426
|
-
} else {
|
|
427
|
-
for (const agent of projectAgents) agentMap.set(agent.name, agent);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Apply user-configured overrides (from /subagents → Agent tool settings) on top of
|
|
431
|
-
// the final resolved agent map, regardless of agent source.
|
|
432
|
-
for (const [name, override] of Object.entries(config?.agents ?? {})) {
|
|
433
|
-
const agent = agentMap.get(name);
|
|
434
|
-
if (!agent) continue;
|
|
435
|
-
|
|
436
|
-
const nextAgent: AgentConfig = { ...agent };
|
|
437
|
-
if (hasOwn(override, "tools")) nextAgent.tools = override.tools;
|
|
438
|
-
if (hasOwn(override, "model")) {
|
|
439
|
-
nextAgent.model = override.model === null ? undefined : override.model;
|
|
440
|
-
}
|
|
441
|
-
if (hasOwn(override, "thinkingLevel")) {
|
|
442
|
-
nextAgent.thinkingLevel =
|
|
443
|
-
override.thinkingLevel === null ? undefined : override.thinkingLevel;
|
|
444
|
-
}
|
|
445
|
-
if (hasOwn(override, "timeoutMs")) {
|
|
446
|
-
nextAgent.timeoutMs = override.timeoutMs === null ? undefined : override.timeoutMs;
|
|
447
|
-
}
|
|
448
|
-
agentMap.set(name, nextAgent);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const omittedAgentDefinitions =
|
|
452
|
-
userLoaded.omittedAgentDefinitions + projectLoaded.omittedAgentDefinitions;
|
|
453
|
-
const metadataDiscoveryIncomplete =
|
|
454
|
-
userLoaded.metadataDiscoveryIncomplete || projectLoaded.metadataDiscoveryIncomplete;
|
|
455
|
-
return {
|
|
456
|
-
agents: Array.from(agentMap.values()),
|
|
457
|
-
projectAgentsDir,
|
|
458
|
-
...(omittedAgentDefinitions > 0 ? { omittedAgentDefinitions } : {}),
|
|
459
|
-
...(metadataDiscoveryIncomplete ? { metadataDiscoveryIncomplete } : {}),
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
export function formatAgentList(
|
|
464
|
-
agents: AgentConfig[],
|
|
465
|
-
maxItems: number,
|
|
466
|
-
): { text: string; remaining: number } {
|
|
467
|
-
if (agents.length === 0) return { text: "none", remaining: 0 };
|
|
468
|
-
const listed = agents.slice(0, maxItems);
|
|
469
|
-
const remaining = agents.length - listed.length;
|
|
470
|
-
return {
|
|
471
|
-
text: listed.map((a) => `${a.name} (${a.source}): ${a.description}`).join("; "),
|
|
472
|
-
remaining,
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
export interface AgentCatalog {
|
|
477
|
-
/** The effective catalog for the default invocation scope. */
|
|
478
|
-
user: AgentDiscoveryResult;
|
|
479
|
-
/** The project-scope catalog; custom project definitions are loaded only after project trust. */
|
|
480
|
-
project?: AgentDiscoveryResult;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
export interface AgentCatalogFormatOptions {
|
|
484
|
-
maxItems?: number;
|
|
485
|
-
maxDescriptionLength?: number;
|
|
486
|
-
maxCharacters?: number;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
export interface AgentCatalogFormatResult {
|
|
490
|
-
text: string;
|
|
491
|
-
omitted: number;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
export const DEFAULT_AGENT_CATALOG_MAX_ITEMS = 32;
|
|
495
|
-
export const DEFAULT_AGENT_CATALOG_MAX_DESCRIPTION_LENGTH = 240;
|
|
496
|
-
export const DEFAULT_AGENT_CATALOG_MAX_CHARACTERS = 6_000;
|
|
497
|
-
export const DEFAULT_AGENT_CATALOG_MAX_FILES_PER_SCOPE = 128;
|
|
498
|
-
export const DEFAULT_AGENT_CATALOG_MAX_FILE_BYTES = 64 * 1024;
|
|
499
|
-
export const DEFAULT_AGENT_CATALOG_MAX_TOTAL_BYTES_PER_SCOPE = 2 * 1024 * 1024;
|
|
500
|
-
|
|
501
|
-
const BUILT_IN_AGENT_ORDER = new Map(BUILT_IN_AGENTS.map((agent, index) => [agent.name, index]));
|
|
502
|
-
|
|
503
|
-
function compareCatalogAgents(left: AgentConfig, right: AgentConfig): number {
|
|
504
|
-
const leftBuiltInOrder = BUILT_IN_AGENT_ORDER.get(left.name);
|
|
505
|
-
const rightBuiltInOrder = BUILT_IN_AGENT_ORDER.get(right.name);
|
|
506
|
-
if (leftBuiltInOrder !== undefined || rightBuiltInOrder !== undefined) {
|
|
507
|
-
if (leftBuiltInOrder === undefined) return 1;
|
|
508
|
-
if (rightBuiltInOrder === undefined) return -1;
|
|
509
|
-
return leftBuiltInOrder - rightBuiltInOrder;
|
|
510
|
-
}
|
|
511
|
-
return left.name.localeCompare(right.name);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function normalizeCatalogDescription(description: string, maxLength: number): string {
|
|
515
|
-
const normalized = description.replace(/\s+/gu, " ").trim();
|
|
516
|
-
if (normalized.length <= maxLength) return normalized;
|
|
517
|
-
const suffix = "…";
|
|
518
|
-
return `${normalized.slice(0, Math.max(0, maxLength - suffix.length)).trimEnd()}${suffix}`;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
type CatalogScope = "user" | "project" | "project-fallback";
|
|
522
|
-
|
|
523
|
-
function catalogAgentLine(
|
|
524
|
-
agent: AgentConfig,
|
|
525
|
-
scope: CatalogScope,
|
|
526
|
-
userNames: ReadonlySet<string>,
|
|
527
|
-
maxDescriptionLength: number,
|
|
528
|
-
): string {
|
|
529
|
-
const scopeLabel =
|
|
530
|
-
scope === "user"
|
|
531
|
-
? 'agentScope: "user"'
|
|
532
|
-
: scope === "project"
|
|
533
|
-
? 'requires agentScope: "project" or "both"'
|
|
534
|
-
: 'requires agentScope: "project" ("both" selects the user definition)';
|
|
535
|
-
const collision =
|
|
536
|
-
scope !== "user" && userNames.has(agent.name)
|
|
537
|
-
? scope === "project"
|
|
538
|
-
? "; overrides the default user definition for project/both"
|
|
539
|
-
: "; scope-specific fallback for the default user override"
|
|
540
|
-
: "";
|
|
541
|
-
return `- ${agent.name} [source: ${agent.source}; ${scopeLabel}${collision}] — ${normalizeCatalogDescription(agent.description, maxDescriptionLength)}`;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* Format the effective agent variants that the parent model can invoke.
|
|
2
|
+
* Compatibility facade for the former mixed agent module.
|
|
546
3
|
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
* read merely to build model-facing metadata.
|
|
4
|
+
* New source modules should import from the cohesive agent boundaries directly.
|
|
5
|
+
* Keep this facade while tests and supported internal entrypoints still depend on the old path.
|
|
550
6
|
*/
|
|
551
|
-
export function formatAgentCatalog(
|
|
552
|
-
catalog: AgentCatalog,
|
|
553
|
-
options: AgentCatalogFormatOptions = {},
|
|
554
|
-
): AgentCatalogFormatResult {
|
|
555
|
-
const maxItems = Math.max(0, options.maxItems ?? DEFAULT_AGENT_CATALOG_MAX_ITEMS);
|
|
556
|
-
const maxDescriptionLength = Math.max(
|
|
557
|
-
1,
|
|
558
|
-
options.maxDescriptionLength ?? DEFAULT_AGENT_CATALOG_MAX_DESCRIPTION_LENGTH,
|
|
559
|
-
);
|
|
560
|
-
const maxCharacters = Math.max(1, options.maxCharacters ?? DEFAULT_AGENT_CATALOG_MAX_CHARACTERS);
|
|
561
|
-
const userDiscoveryIncomplete =
|
|
562
|
-
(catalog.user.omittedAgentDefinitions ?? 0) > 0 ||
|
|
563
|
-
catalog.user.metadataDiscoveryIncomplete === true;
|
|
564
|
-
const projectDiscoveryIncomplete =
|
|
565
|
-
(catalog.project?.omittedAgentDefinitions ?? 0) > 0 ||
|
|
566
|
-
catalog.project?.metadataDiscoveryIncomplete === true;
|
|
567
|
-
const discoveredUserAgents = [...catalog.user.agents].sort(compareCatalogAgents);
|
|
568
|
-
const discoveredProjectScopeAgents = [...(catalog.project?.agents ?? [])].sort(
|
|
569
|
-
compareCatalogAgents,
|
|
570
|
-
);
|
|
571
|
-
const userAgents = userDiscoveryIncomplete ? [] : discoveredUserAgents;
|
|
572
|
-
const projectScopeAgents = projectDiscoveryIncomplete ? [] : discoveredProjectScopeAgents;
|
|
573
|
-
const projectAgents = projectScopeAgents.filter((agent) => agent.source === "project");
|
|
574
|
-
const discoveredUserByName = new Map(discoveredUserAgents.map((agent) => [agent.name, agent]));
|
|
575
|
-
const userByName = new Map(userAgents.map((agent) => [agent.name, agent]));
|
|
576
|
-
const userNames = new Set(userByName.keys());
|
|
577
|
-
const potentialProjectFallbackAgents = discoveredProjectScopeAgents.filter(
|
|
578
|
-
(agent) =>
|
|
579
|
-
agent.source === "built-in" && discoveredUserByName.get(agent.name)?.source === "user",
|
|
580
|
-
);
|
|
581
|
-
const projectFallbackAgents =
|
|
582
|
-
userDiscoveryIncomplete || projectDiscoveryIncomplete ? [] : potentialProjectFallbackAgents;
|
|
583
|
-
const allEntries = [
|
|
584
|
-
...userAgents.map((agent) => ({ agent, scope: "user" as const })),
|
|
585
|
-
...projectAgents.map((agent) => ({ agent, scope: "project" as const })),
|
|
586
|
-
...projectFallbackAgents.map((agent) => ({ agent, scope: "project-fallback" as const })),
|
|
587
|
-
];
|
|
588
|
-
const boundedEntries = allEntries.slice(0, maxItems);
|
|
589
|
-
const suppressedMetadataEntries =
|
|
590
|
-
(userDiscoveryIncomplete ? discoveredUserAgents.length : 0) +
|
|
591
|
-
(projectDiscoveryIncomplete
|
|
592
|
-
? discoveredProjectScopeAgents.filter((agent) => agent.source === "project").length +
|
|
593
|
-
potentialProjectFallbackAgents.length
|
|
594
|
-
: 0);
|
|
595
|
-
const discoveryOmissions =
|
|
596
|
-
(catalog.user.omittedAgentDefinitions ?? 0) +
|
|
597
|
-
(catalog.project?.omittedAgentDefinitions ?? 0) +
|
|
598
|
-
suppressedMetadataEntries;
|
|
599
|
-
const discoveryIncomplete =
|
|
600
|
-
catalog.user.metadataDiscoveryIncomplete === true ||
|
|
601
|
-
catalog.project?.metadataDiscoveryIncomplete === true;
|
|
602
|
-
|
|
603
|
-
const render = (entries: typeof allEntries, omitted: number): string => {
|
|
604
|
-
const lines = [
|
|
605
|
-
"Available agent definitions (metadata only; runtime validation and trust remain authoritative).",
|
|
606
|
-
];
|
|
607
|
-
const userLines = entries
|
|
608
|
-
.filter((entry) => entry.scope === "user")
|
|
609
|
-
.map((entry) => catalogAgentLine(entry.agent, entry.scope, userNames, maxDescriptionLength));
|
|
610
|
-
if (userLines.length > 0) {
|
|
611
|
-
lines.push('Default scope (agentScope: "user"):');
|
|
612
|
-
lines.push(...userLines);
|
|
613
|
-
}
|
|
614
|
-
const projectLines = entries
|
|
615
|
-
.filter((entry) => entry.scope !== "user")
|
|
616
|
-
.map((entry) => catalogAgentLine(entry.agent, entry.scope, userNames, maxDescriptionLength));
|
|
617
|
-
if (projectLines.length > 0) {
|
|
618
|
-
lines.push("Trusted project/scope variants (use the required agentScope shown):");
|
|
619
|
-
lines.push(...projectLines);
|
|
620
|
-
}
|
|
621
|
-
const collisionNames = entries
|
|
622
|
-
.filter((entry) => entry.scope !== "user" && userNames.has(entry.agent.name))
|
|
623
|
-
.map((entry) => entry.agent.name);
|
|
624
|
-
if (collisionNames.length > 0 && projectLines.length > 0) {
|
|
625
|
-
const precedence = entries
|
|
626
|
-
.filter((entry) => entry.scope !== "user" && userNames.has(entry.agent.name))
|
|
627
|
-
.map((entry) =>
|
|
628
|
-
entry.scope === "project"
|
|
629
|
-
? `${entry.agent.name}: user with "user", project with "project"/"both"`
|
|
630
|
-
: `${entry.agent.name}: user with "user"/"both", built-in with "project"`,
|
|
631
|
-
);
|
|
632
|
-
lines.push(`Same-name precedence: ${precedence.join("; ")}.`);
|
|
633
|
-
}
|
|
634
|
-
if (omitted > 0) {
|
|
635
|
-
lines.push(
|
|
636
|
-
`[${omitted} additional agent definition${omitted === 1 ? "" : "s"} omitted due to metadata bounds or incomplete discovery.]`,
|
|
637
|
-
);
|
|
638
|
-
}
|
|
639
|
-
if (discoveryIncomplete) {
|
|
640
|
-
lines.push("[Agent metadata discovery was incomplete; some definitions may be unavailable.]");
|
|
641
|
-
}
|
|
642
|
-
return lines.join("\n");
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
let listedCount = boundedEntries.length;
|
|
646
|
-
let text = render(
|
|
647
|
-
boundedEntries.slice(0, listedCount),
|
|
648
|
-
allEntries.length - listedCount + discoveryOmissions,
|
|
649
|
-
);
|
|
650
|
-
while (text.length > maxCharacters && listedCount > 0) {
|
|
651
|
-
listedCount -= 1;
|
|
652
|
-
text = render(
|
|
653
|
-
boundedEntries.slice(0, listedCount),
|
|
654
|
-
allEntries.length - listedCount + discoveryOmissions,
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
return { text, omitted: allEntries.length - listedCount + discoveryOmissions };
|
|
658
|
-
}
|
|
659
7
|
|
|
660
|
-
export
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
8
|
+
export { getBuiltInAgent } from "./agents/built-ins.js";
|
|
9
|
+
export {
|
|
10
|
+
type AgentCatalog,
|
|
11
|
+
type AgentCatalogFormatOptions,
|
|
12
|
+
type AgentCatalogFormatResult,
|
|
13
|
+
DEFAULT_AGENT_CATALOG_MAX_CHARACTERS,
|
|
14
|
+
DEFAULT_AGENT_CATALOG_MAX_DESCRIPTION_LENGTH,
|
|
15
|
+
DEFAULT_AGENT_CATALOG_MAX_FILE_BYTES,
|
|
16
|
+
DEFAULT_AGENT_CATALOG_MAX_FILES_PER_SCOPE,
|
|
17
|
+
DEFAULT_AGENT_CATALOG_MAX_ITEMS,
|
|
18
|
+
DEFAULT_AGENT_CATALOG_MAX_TOTAL_BYTES_PER_SCOPE,
|
|
19
|
+
discoverAgentCatalog,
|
|
20
|
+
formatAgentCatalog,
|
|
21
|
+
formatAgentList,
|
|
22
|
+
} from "./agents/catalog.js";
|
|
23
|
+
export {
|
|
24
|
+
type AgentDiscoveryOptions,
|
|
25
|
+
type AgentDiscoveryResult,
|
|
26
|
+
discoverAgents,
|
|
27
|
+
} from "./agents/discovery.js";
|
|
28
|
+
export {
|
|
29
|
+
type AgentConfig,
|
|
30
|
+
type AgentScope,
|
|
31
|
+
type AgentSource,
|
|
32
|
+
CONSULT_RESOURCE_POLICIES,
|
|
33
|
+
CONSULTATION_CWD_POLICIES,
|
|
34
|
+
type CompletionDelivery,
|
|
35
|
+
type ConsultationCwdPolicy,
|
|
36
|
+
type ConsultResourcePolicy,
|
|
37
|
+
DEFAULT_PI_TOOL_NAMES,
|
|
38
|
+
DELEGATION_CWD_POLICIES,
|
|
39
|
+
type DelegationCwdPolicy,
|
|
40
|
+
isThinkingLevel,
|
|
41
|
+
resolveAgentToolNames,
|
|
42
|
+
type SubagentAgentConfig,
|
|
43
|
+
type SubagentBlockingSettings,
|
|
44
|
+
type SubagentConsultSettings,
|
|
45
|
+
type SubagentCwdPolicySettings,
|
|
46
|
+
type SubagentRuntimeSettings,
|
|
47
|
+
type SubagentSettings,
|
|
48
|
+
type SubagentThinkingLevel,
|
|
49
|
+
type SubagentTransportKind,
|
|
50
|
+
THINKING_LEVELS,
|
|
51
|
+
} from "./agents/types.js";
|