@pi-agents/orchid 0.1.0-beta.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.
Files changed (163) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE +21 -0
  3. package/README.md +246 -0
  4. package/agents/AGENTS-MANIFEST.md +42 -0
  5. package/agents/brain.md +42 -0
  6. package/agents/context-builder.md +46 -0
  7. package/agents/delegate.md +12 -0
  8. package/agents/dev-1.md +42 -0
  9. package/agents/oracle.md +73 -0
  10. package/agents/planner.md +55 -0
  11. package/agents/researcher.md +52 -0
  12. package/agents/reviewer.md +79 -0
  13. package/agents/scout.md +50 -0
  14. package/agents/tester.md +45 -0
  15. package/agents/worker.md +55 -0
  16. package/extensions/ralph.ts +1 -0
  17. package/extensions/reviewer-extension.ts +125 -0
  18. package/extensions/task-orchestrator.ts +28 -0
  19. package/package.json +63 -0
  20. package/prompts/gather-context-and-clarify.md +13 -0
  21. package/prompts/parallel-cleanup.md +59 -0
  22. package/prompts/parallel-context-build.md +53 -0
  23. package/prompts/parallel-handoff-plan.md +59 -0
  24. package/prompts/parallel-research.md +50 -0
  25. package/prompts/parallel-review.md +54 -0
  26. package/prompts/review-loop.md +41 -0
  27. package/skills/orchid/SKILL.md +214 -0
  28. package/skills/orchid/orchid-cleanup/SKILL.md +122 -0
  29. package/skills/orchid/orchid-converge/SKILL.md +124 -0
  30. package/skills/orchid/orchid-decompose/SKILL.md +201 -0
  31. package/skills/orchid/orchid-doctor/SKILL.md +162 -0
  32. package/skills/orchid/orchid-investigate/SKILL.md +102 -0
  33. package/skills/orchid/orchid-launch/SKILL.md +147 -0
  34. package/skills/ralph/SKILL.md +73 -0
  35. package/skills/subagents/pi-subagents/SKILL.md +813 -0
  36. package/src/index.ts +7 -0
  37. package/src/orchestrator/abort.ts +534 -0
  38. package/src/orchestrator/agent-bridge-extension.ts +1020 -0
  39. package/src/orchestrator/agent-host.ts +954 -0
  40. package/src/orchestrator/cleanup.ts +776 -0
  41. package/src/orchestrator/config-loader.ts +1412 -0
  42. package/src/orchestrator/config-schema.ts +690 -0
  43. package/src/orchestrator/config.ts +81 -0
  44. package/src/orchestrator/context-window.ts +66 -0
  45. package/src/orchestrator/diagnostic-reports.ts +475 -0
  46. package/src/orchestrator/diagnostics.ts +394 -0
  47. package/src/orchestrator/discovery.ts +1833 -0
  48. package/src/orchestrator/engine-worker.ts +415 -0
  49. package/src/orchestrator/engine.ts +5940 -0
  50. package/src/orchestrator/execution.ts +3104 -0
  51. package/src/orchestrator/extension.ts +5934 -0
  52. package/src/orchestrator/formatting.ts +785 -0
  53. package/src/orchestrator/git.ts +88 -0
  54. package/src/orchestrator/index.ts +28 -0
  55. package/src/orchestrator/lane-runner.ts +1787 -0
  56. package/src/orchestrator/mailbox.ts +780 -0
  57. package/src/orchestrator/merge.ts +3414 -0
  58. package/src/orchestrator/messages.ts +1062 -0
  59. package/src/orchestrator/migrations.ts +278 -0
  60. package/src/orchestrator/naming.ts +117 -0
  61. package/src/orchestrator/path-resolver.ts +275 -0
  62. package/src/orchestrator/persistence.ts +2625 -0
  63. package/src/orchestrator/process-registry.ts +452 -0
  64. package/src/orchestrator/quality-gate.ts +1085 -0
  65. package/src/orchestrator/resume.ts +3488 -0
  66. package/src/orchestrator/sessions.ts +57 -0
  67. package/src/orchestrator/settings-loader.ts +136 -0
  68. package/src/orchestrator/settings-tui.ts +2208 -0
  69. package/src/orchestrator/sidecar-telemetry.ts +267 -0
  70. package/src/orchestrator/supervisor.ts +4548 -0
  71. package/src/orchestrator/task-executor-core.ts +675 -0
  72. package/src/orchestrator/tmux-compat.ts +37 -0
  73. package/src/orchestrator/tool-allowlist-constants.ts +37 -0
  74. package/src/orchestrator/types.ts +4465 -0
  75. package/src/orchestrator/verification.ts +547 -0
  76. package/src/orchestrator/waves.ts +1564 -0
  77. package/src/orchestrator/workspace.ts +707 -0
  78. package/src/orchestrator/worktree.ts +2725 -0
  79. package/src/ralph/index.ts +825 -0
  80. package/src/subagents/agents/agent-management.ts +648 -0
  81. package/src/subagents/agents/agent-scope.ts +6 -0
  82. package/src/subagents/agents/agent-selection.ts +23 -0
  83. package/src/subagents/agents/agent-serializer.ts +86 -0
  84. package/src/subagents/agents/agents.ts +832 -0
  85. package/src/subagents/agents/chain-serializer.ts +137 -0
  86. package/src/subagents/agents/frontmatter.ts +29 -0
  87. package/src/subagents/agents/identity.ts +30 -0
  88. package/src/subagents/agents/skills.ts +632 -0
  89. package/src/subagents/extension/config.ts +16 -0
  90. package/src/subagents/extension/control-notices.ts +92 -0
  91. package/src/subagents/extension/doctor.ts +199 -0
  92. package/src/subagents/extension/fanout-child.ts +170 -0
  93. package/src/subagents/extension/index.ts +573 -0
  94. package/src/subagents/extension/schemas.ts +168 -0
  95. package/src/subagents/intercom/intercom-bridge.ts +379 -0
  96. package/src/subagents/intercom/result-intercom.ts +377 -0
  97. package/src/subagents/runs/background/async-execution.ts +712 -0
  98. package/src/subagents/runs/background/async-job-tracker.ts +310 -0
  99. package/src/subagents/runs/background/async-resume.ts +345 -0
  100. package/src/subagents/runs/background/async-status.ts +325 -0
  101. package/src/subagents/runs/background/completion-dedupe.ts +63 -0
  102. package/src/subagents/runs/background/notify.ts +108 -0
  103. package/src/subagents/runs/background/parallel-groups.ts +45 -0
  104. package/src/subagents/runs/background/result-watcher.ts +307 -0
  105. package/src/subagents/runs/background/run-id-resolver.ts +83 -0
  106. package/src/subagents/runs/background/run-status.ts +269 -0
  107. package/src/subagents/runs/background/stale-run-reconciler.ts +336 -0
  108. package/src/subagents/runs/background/subagent-runner.ts +1808 -0
  109. package/src/subagents/runs/background/top-level-async.ts +13 -0
  110. package/src/subagents/runs/foreground/chain-clarify.ts +1333 -0
  111. package/src/subagents/runs/foreground/chain-execution.ts +938 -0
  112. package/src/subagents/runs/foreground/execution.ts +918 -0
  113. package/src/subagents/runs/foreground/subagent-executor.ts +2527 -0
  114. package/src/subagents/runs/shared/completion-guard.ts +147 -0
  115. package/src/subagents/runs/shared/long-running-guard.ts +175 -0
  116. package/src/subagents/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
  117. package/src/subagents/runs/shared/model-fallback.ts +103 -0
  118. package/src/subagents/runs/shared/nested-events.ts +819 -0
  119. package/src/subagents/runs/shared/nested-path.ts +52 -0
  120. package/src/subagents/runs/shared/nested-render.ts +115 -0
  121. package/src/subagents/runs/shared/parallel-utils.ts +109 -0
  122. package/src/subagents/runs/shared/pi-args.ts +220 -0
  123. package/src/subagents/runs/shared/pi-spawn.ts +115 -0
  124. package/src/subagents/runs/shared/run-history.ts +60 -0
  125. package/src/subagents/runs/shared/single-output.ts +164 -0
  126. package/src/subagents/runs/shared/subagent-control.ts +226 -0
  127. package/src/subagents/runs/shared/subagent-prompt-runtime.ts +170 -0
  128. package/src/subagents/runs/shared/worktree.ts +577 -0
  129. package/src/subagents/shared/artifacts.ts +98 -0
  130. package/src/subagents/shared/atomic-json.ts +16 -0
  131. package/src/subagents/shared/file-coalescer.ts +40 -0
  132. package/src/subagents/shared/fork-context.ts +76 -0
  133. package/src/subagents/shared/formatters.ts +133 -0
  134. package/src/subagents/shared/jsonl-writer.ts +81 -0
  135. package/src/subagents/shared/model-info.ts +78 -0
  136. package/src/subagents/shared/post-exit-stdio-guard.ts +85 -0
  137. package/src/subagents/shared/session-identity.ts +10 -0
  138. package/src/subagents/shared/session-tokens.ts +44 -0
  139. package/src/subagents/shared/settings.ts +397 -0
  140. package/src/subagents/shared/status-format.ts +49 -0
  141. package/src/subagents/shared/types.ts +822 -0
  142. package/src/subagents/shared/utils.ts +450 -0
  143. package/src/subagents/slash/prompt-template-bridge.ts +397 -0
  144. package/src/subagents/slash/slash-bridge.ts +174 -0
  145. package/src/subagents/slash/slash-commands.ts +528 -0
  146. package/src/subagents/slash/slash-live-state.ts +292 -0
  147. package/src/subagents/tui/render-helpers.ts +80 -0
  148. package/src/subagents/tui/render.ts +1358 -0
  149. package/templates/agents/local/supervisor.md +33 -0
  150. package/templates/agents/local/task-merger.md +27 -0
  151. package/templates/agents/local/task-reviewer.md +30 -0
  152. package/templates/agents/local/task-worker.md +34 -0
  153. package/templates/agents/supervisor-routing.md +92 -0
  154. package/templates/agents/supervisor.md +229 -0
  155. package/templates/agents/task-merger.md +214 -0
  156. package/templates/agents/task-reviewer.md +260 -0
  157. package/templates/agents/task-worker-segment.md +44 -0
  158. package/templates/agents/task-worker.md +557 -0
  159. package/templates/tasks/CONTEXT.md +30 -0
  160. package/templates/tasks/EXAMPLE-001-hello-world/PROMPT.md +98 -0
  161. package/templates/tasks/EXAMPLE-001-hello-world/STATUS.md +73 -0
  162. package/templates/tasks/EXAMPLE-002-parallel-smoke/PROMPT.md +97 -0
  163. package/templates/tasks/EXAMPLE-002-parallel-smoke/STATUS.md +73 -0
@@ -0,0 +1,832 @@
1
+ /**
2
+ * Agent discovery and configuration
3
+ */
4
+
5
+ import * as fs from "node:fs";
6
+ import * as os from "node:os";
7
+ import * as path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import type { OutputMode } from "../shared/types.ts";
10
+ import { getAgentDir } from "../shared/utils.ts";
11
+ import { KNOWN_FIELDS } from "./agent-serializer.ts";
12
+ import { parseChain } from "./chain-serializer.ts";
13
+ import { mergeAgentsForScope } from "./agent-selection.ts";
14
+ import { parseFrontmatter } from "./frontmatter.ts";
15
+ import { buildRuntimeName, parsePackageName } from "./identity.ts";
16
+ export { buildRuntimeName, frontmatterNameForConfig, parsePackageName } from "./identity.ts";
17
+
18
+ export type AgentScope = "user" | "project" | "both";
19
+
20
+ export type AgentSource = "builtin" | "user" | "project";
21
+ type SystemPromptMode = "append" | "replace";
22
+ export type AgentDefaultContext = "fresh" | "fork";
23
+
24
+ export function defaultSystemPromptMode(name: string): SystemPromptMode {
25
+ return name === "delegate" ? "append" : "replace";
26
+ }
27
+
28
+ export function defaultInheritProjectContext(name: string): boolean {
29
+ return name === "delegate";
30
+ }
31
+
32
+ export function defaultInheritSkills(): boolean {
33
+ return false;
34
+ }
35
+
36
+ export interface BuiltinAgentOverrideBase {
37
+ model?: string;
38
+ fallbackModels?: string[];
39
+ thinking?: string;
40
+ systemPromptMode: SystemPromptMode;
41
+ inheritProjectContext: boolean;
42
+ inheritSkills: boolean;
43
+ defaultContext?: AgentDefaultContext;
44
+ disabled?: boolean;
45
+ systemPrompt: string;
46
+ skills?: string[];
47
+ tools?: string[];
48
+ mcpDirectTools?: string[];
49
+ completionGuard?: boolean;
50
+ }
51
+
52
+ interface BuiltinAgentOverrideConfig {
53
+ model?: string | false;
54
+ fallbackModels?: string[] | false;
55
+ thinking?: string | false;
56
+ systemPromptMode?: SystemPromptMode;
57
+ inheritProjectContext?: boolean;
58
+ inheritSkills?: boolean;
59
+ defaultContext?: AgentDefaultContext | false;
60
+ disabled?: boolean;
61
+ systemPrompt?: string;
62
+ skills?: string[] | false;
63
+ tools?: string[] | false;
64
+ completionGuard?: boolean;
65
+ }
66
+
67
+ interface BuiltinAgentOverrideInfo {
68
+ scope: "user" | "project";
69
+ path: string;
70
+ base: BuiltinAgentOverrideBase;
71
+ }
72
+
73
+ export interface AgentConfig {
74
+ name: string;
75
+ localName?: string;
76
+ packageName?: string;
77
+ description: string;
78
+ tools?: string[];
79
+ mcpDirectTools?: string[];
80
+ model?: string;
81
+ fallbackModels?: string[];
82
+ thinking?: string;
83
+ systemPromptMode: SystemPromptMode;
84
+ inheritProjectContext: boolean;
85
+ inheritSkills: boolean;
86
+ defaultContext?: AgentDefaultContext;
87
+ systemPrompt: string;
88
+ source: AgentSource;
89
+ filePath: string;
90
+ skills?: string[];
91
+ extensions?: string[];
92
+ output?: string;
93
+ defaultReads?: string[];
94
+ defaultProgress?: boolean;
95
+ interactive?: boolean;
96
+ maxSubagentDepth?: number;
97
+ completionGuard?: boolean;
98
+ disabled?: boolean;
99
+ extraFields?: Record<string, string>;
100
+ override?: BuiltinAgentOverrideInfo;
101
+ }
102
+
103
+ interface SubagentSettings {
104
+ overrides: Record<string, BuiltinAgentOverrideConfig>;
105
+ disableBuiltins?: boolean;
106
+ }
107
+
108
+ const EMPTY_SUBAGENT_SETTINGS: SubagentSettings = { overrides: {} };
109
+
110
+ export interface ChainStepConfig {
111
+ agent: string;
112
+ task: string;
113
+ output?: string | false;
114
+ outputMode?: OutputMode;
115
+ reads?: string[] | false;
116
+ model?: string;
117
+ skills?: string[] | false;
118
+ progress?: boolean;
119
+ }
120
+
121
+ export interface ChainConfig {
122
+ name: string;
123
+ localName?: string;
124
+ packageName?: string;
125
+ description: string;
126
+ source: AgentSource;
127
+ filePath: string;
128
+ steps: ChainStepConfig[];
129
+ extraFields?: Record<string, string>;
130
+ }
131
+
132
+ interface AgentDiscoveryResult {
133
+ agents: AgentConfig[];
134
+ projectAgentsDir: string | null;
135
+ }
136
+
137
+ function getUserChainDir(): string {
138
+ return path.join(getAgentDir(), "chains");
139
+ }
140
+
141
+ function splitToolList(rawTools: string[] | undefined): { tools?: string[]; mcpDirectTools?: string[] } {
142
+ const mcpDirectTools: string[] = [];
143
+ const tools: string[] = [];
144
+ for (const tool of rawTools ?? []) {
145
+ if (tool.startsWith("mcp:")) {
146
+ mcpDirectTools.push(tool.slice(4));
147
+ } else {
148
+ tools.push(tool);
149
+ }
150
+ }
151
+ return {
152
+ ...(tools.length > 0 ? { tools } : {}),
153
+ ...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
154
+ };
155
+ }
156
+
157
+ function joinToolList(config: Pick<AgentConfig, "tools" | "mcpDirectTools">): string[] | undefined {
158
+ const joined = [
159
+ ...(config.tools ?? []),
160
+ ...(config.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`),
161
+ ];
162
+ return joined.length > 0 ? joined : undefined;
163
+ }
164
+
165
+ function arraysEqual(a: string[] | undefined, b: string[] | undefined): boolean {
166
+ if (!a && !b) return true;
167
+ if (!a || !b) return false;
168
+ if (a.length !== b.length) return false;
169
+ for (let i = 0; i < a.length; i++) {
170
+ if (a[i] !== b[i]) return false;
171
+ }
172
+ return true;
173
+ }
174
+
175
+ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
176
+ return {
177
+ model: agent.model,
178
+ fallbackModels: agent.fallbackModels ? [...agent.fallbackModels] : undefined,
179
+ thinking: agent.thinking,
180
+ systemPromptMode: agent.systemPromptMode,
181
+ inheritProjectContext: agent.inheritProjectContext,
182
+ inheritSkills: agent.inheritSkills,
183
+ defaultContext: agent.defaultContext,
184
+ disabled: agent.disabled,
185
+ systemPrompt: agent.systemPrompt,
186
+ skills: agent.skills ? [...agent.skills] : undefined,
187
+ tools: agent.tools ? [...agent.tools] : undefined,
188
+ mcpDirectTools: agent.mcpDirectTools ? [...agent.mcpDirectTools] : undefined,
189
+ completionGuard: agent.completionGuard,
190
+ };
191
+ }
192
+
193
+ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentOverrideConfig {
194
+ return {
195
+ ...(override.model !== undefined ? { model: override.model } : {}),
196
+ ...(override.fallbackModels !== undefined
197
+ ? { fallbackModels: override.fallbackModels === false ? false : [...override.fallbackModels] }
198
+ : {}),
199
+ ...(override.thinking !== undefined ? { thinking: override.thinking } : {}),
200
+ ...(override.systemPromptMode !== undefined ? { systemPromptMode: override.systemPromptMode } : {}),
201
+ ...(override.inheritProjectContext !== undefined ? { inheritProjectContext: override.inheritProjectContext } : {}),
202
+ ...(override.inheritSkills !== undefined ? { inheritSkills: override.inheritSkills } : {}),
203
+ ...(override.defaultContext !== undefined ? { defaultContext: override.defaultContext } : {}),
204
+ ...(override.disabled !== undefined ? { disabled: override.disabled } : {}),
205
+ ...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
206
+ ...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
207
+ ...(override.tools !== undefined ? { tools: override.tools === false ? false : [...override.tools] } : {}),
208
+ ...(override.completionGuard !== undefined ? { completionGuard: override.completionGuard } : {}),
209
+ };
210
+ }
211
+
212
+ function findNearestProjectRoot(cwd: string): string | null {
213
+ let currentDir = cwd;
214
+ while (true) {
215
+ if (isDirectory(path.join(currentDir, ".pi")) || isDirectory(path.join(currentDir, ".agents"))) {
216
+ return currentDir;
217
+ }
218
+
219
+ const parentDir = path.dirname(currentDir);
220
+ if (parentDir === currentDir) return null;
221
+ currentDir = parentDir;
222
+ }
223
+ }
224
+
225
+ function getUserAgentSettingsPath(): string {
226
+ return path.join(getAgentDir(), "settings.json");
227
+ }
228
+
229
+ function getProjectAgentSettingsPath(cwd: string): string | null {
230
+ const projectRoot = findNearestProjectRoot(cwd);
231
+ return projectRoot ? path.join(projectRoot, ".pi", "settings.json") : null;
232
+ }
233
+
234
+ function readSettingsFileStrict(filePath: string): Record<string, unknown> {
235
+ if (!fs.existsSync(filePath)) return {};
236
+ let raw: string;
237
+ try {
238
+ raw = fs.readFileSync(filePath, "utf-8");
239
+ } catch (error) {
240
+ const message = error instanceof Error ? error.message : String(error);
241
+ throw new Error(`Failed to read settings file '${filePath}': ${message}`, { cause: error });
242
+ }
243
+
244
+ let parsed: unknown;
245
+ try {
246
+ parsed = JSON.parse(raw);
247
+ } catch (error) {
248
+ const message = error instanceof Error ? error.message : String(error);
249
+ throw new Error(`Failed to parse settings file '${filePath}': ${message}`, { cause: error });
250
+ }
251
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
252
+ throw new Error(`Settings file '${filePath}' must contain a JSON object.`);
253
+ }
254
+ return parsed as Record<string, unknown>;
255
+ }
256
+
257
+ function writeSettingsFile(filePath: string, settings: Record<string, unknown>): void {
258
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
259
+ fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
260
+ }
261
+
262
+ function parseOverrideStringArrayOrFalse(
263
+ value: unknown,
264
+ meta: { filePath: string; name: string; field: string },
265
+ ): string[] | false | undefined {
266
+ if (value === undefined) return undefined;
267
+ if (value === false) return false;
268
+ if (!Array.isArray(value)) {
269
+ throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid '${meta.field}'; expected an array of strings or false.`);
270
+ }
271
+
272
+ const items: string[] = [];
273
+ for (const item of value) {
274
+ if (typeof item !== "string") {
275
+ throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid '${meta.field}'; expected an array of strings or false.`);
276
+ }
277
+ const trimmed = item.trim();
278
+ if (trimmed) items.push(trimmed);
279
+ }
280
+ return items;
281
+ }
282
+
283
+ function parseBuiltinOverrideEntry(
284
+ name: string,
285
+ value: unknown,
286
+ filePath: string,
287
+ ): BuiltinAgentOverrideConfig | undefined {
288
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
289
+ throw new Error(`Builtin override '${name}' in '${filePath}' must be an object.`);
290
+ }
291
+
292
+ const input = value as Record<string, unknown>;
293
+ const override: BuiltinAgentOverrideConfig = {};
294
+
295
+ if ("model" in input) {
296
+ if (typeof input.model === "string" || input.model === false) override.model = input.model;
297
+ else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'model'; expected a string or false.`);
298
+ }
299
+
300
+ if ("thinking" in input) {
301
+ if (typeof input.thinking === "string" || input.thinking === false) override.thinking = input.thinking;
302
+ else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'thinking'; expected a string or false.`);
303
+ }
304
+
305
+ if ("systemPromptMode" in input) {
306
+ if (input.systemPromptMode === "append" || input.systemPromptMode === "replace") {
307
+ override.systemPromptMode = input.systemPromptMode;
308
+ } else {
309
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPromptMode'; expected 'append' or 'replace'.`);
310
+ }
311
+ }
312
+
313
+ if ("inheritProjectContext" in input) {
314
+ if (typeof input.inheritProjectContext === "boolean") {
315
+ override.inheritProjectContext = input.inheritProjectContext;
316
+ } else {
317
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'inheritProjectContext'; expected a boolean.`);
318
+ }
319
+ }
320
+
321
+ if ("inheritSkills" in input) {
322
+ if (typeof input.inheritSkills === "boolean") {
323
+ override.inheritSkills = input.inheritSkills;
324
+ } else {
325
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'inheritSkills'; expected a boolean.`);
326
+ }
327
+ }
328
+
329
+ if ("defaultContext" in input) {
330
+ if (input.defaultContext === "fresh" || input.defaultContext === "fork" || input.defaultContext === false) {
331
+ override.defaultContext = input.defaultContext;
332
+ } else {
333
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'defaultContext'; expected 'fresh', 'fork', or false.`);
334
+ }
335
+ }
336
+
337
+ if ("disabled" in input) {
338
+ if (typeof input.disabled === "boolean") {
339
+ override.disabled = input.disabled;
340
+ } else {
341
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'disabled'; expected a boolean.`);
342
+ }
343
+ }
344
+
345
+ if ("completionGuard" in input) {
346
+ if (typeof input.completionGuard === "boolean") {
347
+ override.completionGuard = input.completionGuard;
348
+ } else {
349
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'completionGuard'; expected a boolean.`);
350
+ }
351
+ }
352
+
353
+ if ("systemPrompt" in input) {
354
+ if (typeof input.systemPrompt === "string") override.systemPrompt = input.systemPrompt;
355
+ else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPrompt'; expected a string.`);
356
+ }
357
+
358
+ const fallbackModels = parseOverrideStringArrayOrFalse(input.fallbackModels, { filePath, name, field: "fallbackModels" });
359
+ if (fallbackModels !== undefined) override.fallbackModels = fallbackModels;
360
+
361
+ const skills = parseOverrideStringArrayOrFalse(input.skills, { filePath, name, field: "skills" });
362
+ if (skills !== undefined) override.skills = skills;
363
+
364
+ const tools = parseOverrideStringArrayOrFalse(input.tools, { filePath, name, field: "tools" });
365
+ if (tools !== undefined) override.tools = tools;
366
+
367
+ return Object.keys(override).length > 0 ? override : undefined;
368
+ }
369
+
370
+ function readSubagentSettings(filePath: string | null): SubagentSettings {
371
+ if (!filePath) return EMPTY_SUBAGENT_SETTINGS;
372
+ const settings = readSettingsFileStrict(filePath);
373
+ const subagents = settings.subagents;
374
+ if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return EMPTY_SUBAGENT_SETTINGS;
375
+
376
+ const subagentsObject = subagents as Record<string, unknown>;
377
+ let disableBuiltins: boolean | undefined;
378
+ if ("disableBuiltins" in subagentsObject) {
379
+ if (typeof subagentsObject.disableBuiltins === "boolean") {
380
+ disableBuiltins = subagentsObject.disableBuiltins;
381
+ } else {
382
+ throw new Error(`Subagent settings in '${filePath}' have invalid 'disableBuiltins'; expected a boolean.`);
383
+ }
384
+ }
385
+
386
+ const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
387
+ const agentOverrides = subagentsObject.agentOverrides;
388
+ if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) {
389
+ return { overrides: parsed, disableBuiltins };
390
+ }
391
+ for (const [name, value] of Object.entries(agentOverrides)) {
392
+ const override = parseBuiltinOverrideEntry(name, value, filePath);
393
+ if (override) parsed[name] = override;
394
+ }
395
+ return { overrides: parsed, disableBuiltins };
396
+ }
397
+
398
+ function applyBuiltinOverride(
399
+ agent: AgentConfig,
400
+ override: BuiltinAgentOverrideConfig,
401
+ meta: { scope: "user" | "project"; path: string },
402
+ ): AgentConfig {
403
+ const next: AgentConfig = {
404
+ ...agent,
405
+ override: { ...meta, base: cloneOverrideBase(agent) },
406
+ };
407
+
408
+ if (override.model !== undefined) next.model = override.model === false ? undefined : override.model;
409
+ if (override.fallbackModels !== undefined) {
410
+ next.fallbackModels = override.fallbackModels === false ? undefined : [...override.fallbackModels];
411
+ }
412
+ if (override.thinking !== undefined) next.thinking = override.thinking === false ? undefined : override.thinking;
413
+ if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
414
+ if (override.inheritProjectContext !== undefined) next.inheritProjectContext = override.inheritProjectContext;
415
+ if (override.inheritSkills !== undefined) next.inheritSkills = override.inheritSkills;
416
+ if (override.defaultContext !== undefined) next.defaultContext = override.defaultContext === false ? undefined : override.defaultContext;
417
+ if (override.disabled !== undefined) next.disabled = override.disabled;
418
+ if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
419
+ if (override.skills !== undefined) next.skills = override.skills === false ? undefined : [...override.skills];
420
+ if (override.tools !== undefined) {
421
+ const { tools, mcpDirectTools } = splitToolList(override.tools === false ? [] : override.tools);
422
+ next.tools = tools;
423
+ next.mcpDirectTools = mcpDirectTools;
424
+ }
425
+ if (override.completionGuard !== undefined) next.completionGuard = override.completionGuard;
426
+
427
+ return next;
428
+ }
429
+
430
+ function applyBuiltinOverrides(
431
+ builtinAgents: AgentConfig[],
432
+ userSettings: SubagentSettings,
433
+ projectSettings: SubagentSettings,
434
+ userSettingsPath: string,
435
+ projectSettingsPath: string | null,
436
+ ): AgentConfig[] {
437
+ const projectBulkDisabled = projectSettings.disableBuiltins === true && projectSettingsPath !== null;
438
+ const userBulkDisabled = projectSettings.disableBuiltins === undefined && userSettings.disableBuiltins === true;
439
+
440
+ return builtinAgents.map((agent) => {
441
+ const projectOverride = projectSettings.overrides[agent.name];
442
+ if (projectOverride && projectSettingsPath) {
443
+ return applyBuiltinOverride(agent, projectOverride, { scope: "project", path: projectSettingsPath });
444
+ }
445
+
446
+ if (projectBulkDisabled && projectSettingsPath) {
447
+ return applyBuiltinOverride(agent, { disabled: true }, { scope: "project", path: projectSettingsPath });
448
+ }
449
+
450
+ const userOverride = userSettings.overrides[agent.name];
451
+ if (userOverride) {
452
+ return applyBuiltinOverride(agent, userOverride, { scope: "user", path: userSettingsPath });
453
+ }
454
+
455
+ if (userBulkDisabled) {
456
+ return applyBuiltinOverride(agent, { disabled: true }, { scope: "user", path: userSettingsPath });
457
+ }
458
+
459
+ return agent;
460
+ });
461
+ }
462
+
463
+ export function buildBuiltinOverrideConfig(
464
+ base: BuiltinAgentOverrideBase,
465
+ draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "completionGuard">,
466
+ ): BuiltinAgentOverrideConfig | undefined {
467
+ const override: BuiltinAgentOverrideConfig = {};
468
+
469
+ if (draft.model !== base.model) override.model = draft.model ?? false;
470
+ if (!arraysEqual(draft.fallbackModels, base.fallbackModels)) override.fallbackModels = draft.fallbackModels ? [...draft.fallbackModels] : false;
471
+ if (draft.thinking !== base.thinking) override.thinking = draft.thinking ?? false;
472
+ if (draft.systemPromptMode !== base.systemPromptMode) override.systemPromptMode = draft.systemPromptMode;
473
+ if (draft.inheritProjectContext !== base.inheritProjectContext) override.inheritProjectContext = draft.inheritProjectContext;
474
+ if (draft.inheritSkills !== base.inheritSkills) override.inheritSkills = draft.inheritSkills;
475
+ if (draft.defaultContext !== base.defaultContext) override.defaultContext = draft.defaultContext ?? false;
476
+ if (draft.disabled !== base.disabled) override.disabled = draft.disabled ?? false;
477
+ if (draft.systemPrompt !== base.systemPrompt) override.systemPrompt = draft.systemPrompt;
478
+ if (!arraysEqual(draft.skills, base.skills)) override.skills = draft.skills ? [...draft.skills] : false;
479
+
480
+ const baseTools = joinToolList(base);
481
+ const draftTools = joinToolList(draft);
482
+ if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false;
483
+ if ((draft.completionGuard !== false) !== (base.completionGuard !== false)) {
484
+ override.completionGuard = draft.completionGuard !== false;
485
+ }
486
+
487
+ return Object.keys(override).length > 0 ? override : undefined;
488
+ }
489
+
490
+ export function saveBuiltinAgentOverride(
491
+ cwd: string,
492
+ name: string,
493
+ scope: "user" | "project",
494
+ override: BuiltinAgentOverrideConfig,
495
+ ): string {
496
+ const filePath = scope === "project" ? getProjectAgentSettingsPath(cwd) : getUserAgentSettingsPath();
497
+ if (!filePath) throw new Error("Project override is not available here. No project config root was found.");
498
+
499
+ const settings = readSettingsFileStrict(filePath);
500
+ const subagents = settings.subagents && typeof settings.subagents === "object" && !Array.isArray(settings.subagents)
501
+ ? { ...(settings.subagents as Record<string, unknown>) }
502
+ : {};
503
+ const agentOverrides = subagents.agentOverrides && typeof subagents.agentOverrides === "object" && !Array.isArray(subagents.agentOverrides)
504
+ ? { ...(subagents.agentOverrides as Record<string, unknown>) }
505
+ : {};
506
+
507
+ agentOverrides[name] = cloneOverrideValue(override);
508
+ subagents.agentOverrides = agentOverrides;
509
+ settings.subagents = subagents;
510
+ writeSettingsFile(filePath, settings);
511
+ return filePath;
512
+ }
513
+
514
+ export function removeBuiltinAgentOverride(cwd: string, name: string, scope: "user" | "project"): string {
515
+ const filePath = scope === "project" ? getProjectAgentSettingsPath(cwd) : getUserAgentSettingsPath();
516
+ if (!filePath) throw new Error("Project override is not available here. No project config root was found.");
517
+ if (!fs.existsSync(filePath)) return filePath;
518
+
519
+ const settings = readSettingsFileStrict(filePath);
520
+ const subagents = settings.subagents;
521
+ if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return filePath;
522
+ const nextSubagents = { ...(subagents as Record<string, unknown>) };
523
+ const agentOverrides = nextSubagents.agentOverrides;
524
+ if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) return filePath;
525
+
526
+ const nextOverrides = { ...(agentOverrides as Record<string, unknown>) };
527
+ delete nextOverrides[name];
528
+ if (Object.keys(nextOverrides).length > 0) nextSubagents.agentOverrides = nextOverrides;
529
+ else delete nextSubagents.agentOverrides;
530
+
531
+ if (Object.keys(nextSubagents).length > 0) settings.subagents = nextSubagents;
532
+ else delete settings.subagents;
533
+
534
+ writeSettingsFile(filePath, settings);
535
+ return filePath;
536
+ }
537
+
538
+ function listMarkdownFilesRecursive(dir: string, predicate: (fileName: string) => boolean): string[] {
539
+ const files: string[] = [];
540
+ if (!fs.existsSync(dir)) return files;
541
+
542
+ let entries: fs.Dirent[];
543
+ try {
544
+ entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
545
+ } catch {
546
+ return files;
547
+ }
548
+
549
+ for (const entry of entries) {
550
+ const filePath = path.join(dir, entry.name);
551
+ if (entry.isDirectory()) {
552
+ files.push(...listMarkdownFilesRecursive(filePath, predicate));
553
+ continue;
554
+ }
555
+ if (!entry.isFile() && !entry.isSymbolicLink()) continue;
556
+ if (!predicate(entry.name)) continue;
557
+ files.push(filePath);
558
+ }
559
+ return files;
560
+ }
561
+
562
+ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
563
+ const agents: AgentConfig[] = [];
564
+
565
+ for (const filePath of listMarkdownFilesRecursive(dir, (fileName) => fileName.endsWith(".md") && !fileName.endsWith(".chain.md"))) {
566
+ let content: string;
567
+ try {
568
+ content = fs.readFileSync(filePath, "utf-8");
569
+ } catch {
570
+ continue;
571
+ }
572
+
573
+ const { frontmatter, body } = parseFrontmatter(content);
574
+
575
+ if (!frontmatter.name || !frontmatter.description) {
576
+ continue;
577
+ }
578
+
579
+ const localName = frontmatter.name;
580
+ const parsedPackage = parsePackageName(frontmatter.package, `Agent '${localName}' package`);
581
+ if (parsedPackage.error) continue;
582
+ const packageName = parsedPackage.packageName;
583
+ const runtimeName = buildRuntimeName(localName, packageName);
584
+
585
+ const rawTools = frontmatter.tools
586
+ ?.split(",")
587
+ .map((t) => t.trim())
588
+ .filter(Boolean);
589
+
590
+ const mcpDirectTools: string[] = [];
591
+ const tools: string[] = [];
592
+ if (rawTools) {
593
+ for (const tool of rawTools) {
594
+ if (tool.startsWith("mcp:")) {
595
+ mcpDirectTools.push(tool.slice(4));
596
+ } else {
597
+ tools.push(tool);
598
+ }
599
+ }
600
+ }
601
+
602
+ const defaultReads = frontmatter.defaultReads
603
+ ?.split(",")
604
+ .map((f) => f.trim())
605
+ .filter(Boolean);
606
+
607
+ const skillStr = frontmatter.skill || frontmatter.skills;
608
+ const skills = skillStr
609
+ ?.split(",")
610
+ .map((s) => s.trim())
611
+ .filter(Boolean);
612
+ const fallbackModels = frontmatter.fallbackModels
613
+ ?.split(",")
614
+ .map((model) => model.trim())
615
+ .filter(Boolean);
616
+ const systemPromptMode = frontmatter.systemPromptMode === "replace"
617
+ ? "replace"
618
+ : frontmatter.systemPromptMode === "append"
619
+ ? "append"
620
+ : defaultSystemPromptMode(localName);
621
+ const inheritProjectContext = frontmatter.inheritProjectContext === "true"
622
+ ? true
623
+ : frontmatter.inheritProjectContext === "false"
624
+ ? false
625
+ : defaultInheritProjectContext(localName);
626
+ const inheritSkills = frontmatter.inheritSkills === "true"
627
+ ? true
628
+ : frontmatter.inheritSkills === "false"
629
+ ? false
630
+ : defaultInheritSkills();
631
+ const defaultContext = frontmatter.defaultContext === "fork"
632
+ ? "fork" as const
633
+ : frontmatter.defaultContext === "fresh"
634
+ ? "fresh" as const
635
+ : undefined;
636
+
637
+ let extensions: string[] | undefined;
638
+ if (frontmatter.extensions !== undefined) {
639
+ extensions = frontmatter.extensions
640
+ .split(",")
641
+ .map((e) => e.trim())
642
+ .filter(Boolean);
643
+ }
644
+
645
+ const extraFields: Record<string, string> = {};
646
+ for (const [key, value] of Object.entries(frontmatter)) {
647
+ if (!KNOWN_FIELDS.has(key)) extraFields[key] = value;
648
+ }
649
+
650
+ const parsedMaxSubagentDepth = Number(frontmatter.maxSubagentDepth);
651
+ const completionGuard = frontmatter.completionGuard === "false"
652
+ ? false
653
+ : frontmatter.completionGuard === "true"
654
+ ? true
655
+ : undefined;
656
+
657
+ agents.push({
658
+ name: runtimeName,
659
+ localName,
660
+ packageName,
661
+ description: frontmatter.description,
662
+ tools: tools.length > 0 ? tools : undefined,
663
+ mcpDirectTools: mcpDirectTools.length > 0 ? mcpDirectTools : undefined,
664
+ model: frontmatter.model,
665
+ fallbackModels: fallbackModels && fallbackModels.length > 0 ? fallbackModels : undefined,
666
+ thinking: frontmatter.thinking,
667
+ systemPromptMode,
668
+ inheritProjectContext,
669
+ inheritSkills,
670
+ defaultContext,
671
+ systemPrompt: body,
672
+ source,
673
+ filePath,
674
+ skills: skills && skills.length > 0 ? skills : undefined,
675
+ extensions,
676
+ output: frontmatter.output,
677
+ defaultReads: defaultReads && defaultReads.length > 0 ? defaultReads : undefined,
678
+ defaultProgress: frontmatter.defaultProgress === "true",
679
+ interactive: frontmatter.interactive === "true",
680
+ maxSubagentDepth:
681
+ Number.isInteger(parsedMaxSubagentDepth) && parsedMaxSubagentDepth >= 0
682
+ ? parsedMaxSubagentDepth
683
+ : undefined,
684
+ completionGuard,
685
+ extraFields: Object.keys(extraFields).length > 0 ? extraFields : undefined,
686
+ });
687
+ }
688
+
689
+ return agents;
690
+ }
691
+
692
+ function loadChainsFromDir(dir: string, source: AgentSource): ChainConfig[] {
693
+ const chains: ChainConfig[] = [];
694
+
695
+ for (const filePath of listMarkdownFilesRecursive(dir, (fileName) => fileName.endsWith(".chain.md"))) {
696
+ let content: string;
697
+ try {
698
+ content = fs.readFileSync(filePath, "utf-8");
699
+ } catch {
700
+ continue;
701
+ }
702
+
703
+ try {
704
+ chains.push(parseChain(content, source, filePath));
705
+ } catch {
706
+ continue;
707
+ }
708
+ }
709
+
710
+ return chains;
711
+ }
712
+
713
+ function isDirectory(p: string): boolean {
714
+ try {
715
+ return fs.statSync(p).isDirectory();
716
+ } catch {
717
+ return false;
718
+ }
719
+ }
720
+
721
+ function resolveNearestProjectAgentDirs(cwd: string): { readDirs: string[]; preferredDir: string | null } {
722
+ const projectRoot = findNearestProjectRoot(cwd);
723
+ if (!projectRoot) return { readDirs: [], preferredDir: null };
724
+
725
+ const legacyDir = path.join(projectRoot, ".agents");
726
+ const preferredDir = path.join(projectRoot, ".pi", "agents");
727
+ const readDirs: string[] = [];
728
+ if (isDirectory(legacyDir)) readDirs.push(legacyDir);
729
+ if (isDirectory(preferredDir)) readDirs.push(preferredDir);
730
+
731
+ return {
732
+ readDirs,
733
+ preferredDir,
734
+ };
735
+ }
736
+
737
+ function resolveNearestProjectChainDirs(cwd: string): { readDirs: string[]; preferredDir: string | null } {
738
+ const projectRoot = findNearestProjectRoot(cwd);
739
+ if (!projectRoot) return { readDirs: [], preferredDir: null };
740
+
741
+ const preferredDir = path.join(projectRoot, ".pi", "chains");
742
+ return {
743
+ readDirs: isDirectory(preferredDir) ? [preferredDir] : [],
744
+ preferredDir,
745
+ };
746
+ }
747
+ const BUILTIN_AGENTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "agents");
748
+
749
+ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
750
+ const userDirOld = path.join(getAgentDir(), "agents");
751
+ const userDirNew = path.join(os.homedir(), ".agents");
752
+ const { readDirs: projectAgentDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(cwd);
753
+ const userSettingsPath = getUserAgentSettingsPath();
754
+ const projectSettingsPath = getProjectAgentSettingsPath(cwd);
755
+ const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
756
+ const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
757
+
758
+ const builtinAgents = applyBuiltinOverrides(
759
+ loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
760
+ userSettings,
761
+ projectSettings,
762
+ userSettingsPath,
763
+ projectSettingsPath,
764
+ );
765
+
766
+ const userAgentsOld = scope === "project" ? [] : loadAgentsFromDir(userDirOld, "user");
767
+ const userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user");
768
+ const userAgents = [...userAgentsOld, ...userAgentsNew];
769
+
770
+ const projectAgents = scope === "user" ? [] : projectAgentDirs.flatMap((dir) => loadAgentsFromDir(dir, "project"));
771
+ const agents = mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents)
772
+ .filter((agent) => agent.disabled !== true);
773
+
774
+ return { agents, projectAgentsDir };
775
+ }
776
+
777
+ export function discoverAgentsAll(cwd: string): {
778
+ builtin: AgentConfig[];
779
+ user: AgentConfig[];
780
+ project: AgentConfig[];
781
+ chains: ChainConfig[];
782
+ userDir: string;
783
+ projectDir: string | null;
784
+ userChainDir: string;
785
+ projectChainDir: string | null;
786
+ userSettingsPath: string;
787
+ projectSettingsPath: string | null;
788
+ } {
789
+ const userDirOld = path.join(getAgentDir(), "agents");
790
+ const userDirNew = path.join(os.homedir(), ".agents");
791
+ const userChainDir = getUserChainDir();
792
+ const { readDirs: projectDirs, preferredDir: projectDir } = resolveNearestProjectAgentDirs(cwd);
793
+ const { readDirs: projectChainDirs, preferredDir: projectChainDir } = resolveNearestProjectChainDirs(cwd);
794
+ const userSettingsPath = getUserAgentSettingsPath();
795
+ const projectSettingsPath = getProjectAgentSettingsPath(cwd);
796
+ const userSettings = readSubagentSettings(userSettingsPath);
797
+ const projectSettings = readSubagentSettings(projectSettingsPath);
798
+
799
+ const builtin = applyBuiltinOverrides(
800
+ loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
801
+ userSettings,
802
+ projectSettings,
803
+ userSettingsPath,
804
+ projectSettingsPath,
805
+ );
806
+ const user = [
807
+ ...loadAgentsFromDir(userDirOld, "user"),
808
+ ...loadAgentsFromDir(userDirNew, "user"),
809
+ ];
810
+ const projectMap = new Map<string, AgentConfig>();
811
+ for (const dir of projectDirs) {
812
+ for (const agent of loadAgentsFromDir(dir, "project")) {
813
+ projectMap.set(agent.name, agent);
814
+ }
815
+ }
816
+ const project = Array.from(projectMap.values());
817
+
818
+ const chainMap = new Map<string, ChainConfig>();
819
+ for (const dir of projectChainDirs) {
820
+ for (const chain of loadChainsFromDir(dir, "project")) {
821
+ chainMap.set(chain.name, chain);
822
+ }
823
+ }
824
+ const chains = [
825
+ ...loadChainsFromDir(userChainDir, "user"),
826
+ ...Array.from(chainMap.values()),
827
+ ];
828
+
829
+ const userDir = process.env.PI_CODING_AGENT_DIR ? userDirOld : fs.existsSync(userDirNew) ? userDirNew : userDirOld;
830
+
831
+ return { builtin, user, project, chains, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
832
+ }