@fitchmultz/pi-subagents 0.0.0-stage → 0.44.4

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 (342) hide show
  1. package/CHANGELOG.md +1645 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1529 -2
  4. package/agents/context-builder.md +97 -0
  5. package/agents/debugger.md +51 -0
  6. package/agents/delegate.md +13 -0
  7. package/agents/fixer.md +37 -0
  8. package/agents/oracle.md +71 -0
  9. package/agents/planner.md +72 -0
  10. package/agents/researcher.md +51 -0
  11. package/agents/reviewer-claude.md +69 -0
  12. package/agents/reviewer-gpt.md +68 -0
  13. package/agents/reviewer-ponytail.md +82 -0
  14. package/agents/reviewer-security.md +81 -0
  15. package/agents/reviewer.md +69 -0
  16. package/agents/scout.md +66 -0
  17. package/agents/ui-designer.md +51 -0
  18. package/agents/watcher.md +26 -0
  19. package/agents/worker.md +46 -0
  20. package/agents/writer.md +36 -0
  21. package/dist/agents/agent-management.js +923 -0
  22. package/dist/agents/agent-scope.js +7 -0
  23. package/dist/agents/agent-selection.js +20 -0
  24. package/dist/agents/agent-serializer.js +100 -0
  25. package/dist/agents/agents.js +752 -0
  26. package/dist/agents/chain-serializer.js +367 -0
  27. package/dist/agents/frontmatter.js +21 -0
  28. package/dist/agents/identity.js +29 -0
  29. package/dist/agents/skills.js +570 -0
  30. package/dist/extension/build-info.js +1 -0
  31. package/dist/extension/compact-tools.js +112 -0
  32. package/dist/extension/config.js +22 -0
  33. package/dist/extension/control-notices.js +29 -0
  34. package/dist/extension/doctor.js +143 -0
  35. package/dist/extension/fanout-child.js +326 -0
  36. package/dist/extension/index.js +577 -0
  37. package/dist/extension/schemas.js +416 -0
  38. package/dist/extension/tool-input.js +20 -0
  39. package/dist/extension/tool-result.js +14 -0
  40. package/dist/history/canonical-result.js +81 -0
  41. package/dist/history/database.js +72 -0
  42. package/dist/history/index.js +194 -0
  43. package/dist/history/ingest.js +447 -0
  44. package/dist/history/queries.js +350 -0
  45. package/dist/history/store.js +216 -0
  46. package/dist/history/types.js +4 -0
  47. package/dist/history/worker.js +461 -0
  48. package/dist/intercom/intercom-bridge.js +112 -0
  49. package/dist/intercom/live-intercom.js +94 -0
  50. package/dist/intercom/result-intercom.js +373 -0
  51. package/dist/pi-intercom/agent-dir.js +10 -0
  52. package/dist/pi-intercom/broker/broker.js +426 -0
  53. package/dist/pi-intercom/broker/client.js +492 -0
  54. package/dist/pi-intercom/broker/framing.js +65 -0
  55. package/dist/pi-intercom/broker/paths.js +42 -0
  56. package/dist/pi-intercom/broker/pid.js +66 -0
  57. package/dist/pi-intercom/broker/spawn.js +221 -0
  58. package/dist/pi-intercom/config.js +102 -0
  59. package/dist/pi-intercom/index.js +2942 -0
  60. package/dist/pi-intercom/reply-tracker.js +182 -0
  61. package/dist/pi-intercom/session-targets.js +128 -0
  62. package/dist/pi-intercom/subagent-live-events.js +132 -0
  63. package/dist/pi-intercom/topics.js +157 -0
  64. package/dist/pi-intercom/types.js +163 -0
  65. package/dist/pi-intercom/ui/compose.js +269 -0
  66. package/dist/pi-intercom/ui/inline-message.js +80 -0
  67. package/dist/pi-intercom/ui/session-list.js +124 -0
  68. package/dist/runs/background/async-control.js +95 -0
  69. package/dist/runs/background/async-execution.js +676 -0
  70. package/dist/runs/background/async-job-tracker.js +383 -0
  71. package/dist/runs/background/async-result-file.js +58 -0
  72. package/dist/runs/background/async-resume.js +305 -0
  73. package/dist/runs/background/async-status.js +442 -0
  74. package/dist/runs/background/completion-dedupe.js +69 -0
  75. package/dist/runs/background/completion-delivery.js +245 -0
  76. package/dist/runs/background/notify.js +71 -0
  77. package/dist/runs/background/parallel-groups.js +55 -0
  78. package/dist/runs/background/result-watcher.js +426 -0
  79. package/dist/runs/background/run-id-resolver.js +64 -0
  80. package/dist/runs/background/run-status.js +382 -0
  81. package/dist/runs/background/stale-run-reconciler.js +335 -0
  82. package/dist/runs/background/subagent-runner-launcher.js +55 -0
  83. package/dist/runs/background/subagent-runner.js +1872 -0
  84. package/dist/runs/background/top-level-async.js +5 -0
  85. package/dist/runs/foreground/chain-clarify.js +1152 -0
  86. package/dist/runs/foreground/clarify-invocation.js +72 -0
  87. package/dist/runs/foreground/execution-input.js +423 -0
  88. package/dist/runs/foreground/foreground-control.js +817 -0
  89. package/dist/runs/foreground/question-control.js +77 -0
  90. package/dist/runs/foreground/run-async-path.js +205 -0
  91. package/dist/runs/foreground/subagent-executor.js +442 -0
  92. package/dist/runs/foreground/subagent-params.js +165 -0
  93. package/dist/runs/foreground/wait-run.js +139 -0
  94. package/dist/runs/shared/acceptance-contract.js +253 -0
  95. package/dist/runs/shared/acceptance-evaluation.js +256 -0
  96. package/dist/runs/shared/acceptance-finalization.js +280 -0
  97. package/dist/runs/shared/acceptance-reports.js +147 -0
  98. package/dist/runs/shared/acceptance.js +4 -0
  99. package/dist/runs/shared/chain-outputs.js +110 -0
  100. package/dist/runs/shared/child-attempt.js +693 -0
  101. package/dist/runs/shared/child-execution-cwd.js +96 -0
  102. package/dist/runs/shared/child-observations.js +114 -0
  103. package/dist/runs/shared/claude-code.js +199 -0
  104. package/dist/runs/shared/completion-guard.js +31 -0
  105. package/dist/runs/shared/dynamic-fanout.js +274 -0
  106. package/dist/runs/shared/history-index.js +72 -0
  107. package/dist/runs/shared/mcp-direct-tool-allowlist.js +334 -0
  108. package/dist/runs/shared/model-fallback.js +185 -0
  109. package/dist/runs/shared/mutating-tool-guard.js +170 -0
  110. package/dist/runs/shared/native-finalization.js +101 -0
  111. package/dist/runs/shared/native-usage.js +153 -0
  112. package/dist/runs/shared/nested-events.js +700 -0
  113. package/dist/runs/shared/nested-path.js +48 -0
  114. package/dist/runs/shared/nested-render.js +108 -0
  115. package/dist/runs/shared/parallel-utils.js +61 -0
  116. package/dist/runs/shared/parent-receipts.js +68 -0
  117. package/dist/runs/shared/parent-usage.js +121 -0
  118. package/dist/runs/shared/pi-args.js +310 -0
  119. package/dist/runs/shared/pi-spawn.js +52 -0
  120. package/dist/runs/shared/run-history-queries.js +45 -0
  121. package/dist/runs/shared/run-history.js +103 -0
  122. package/dist/runs/shared/run-id-ambiguity.js +4 -0
  123. package/dist/runs/shared/run-records.js +679 -0
  124. package/dist/runs/shared/session-cwd-preload.js +33 -0
  125. package/dist/runs/shared/single-output.js +222 -0
  126. package/dist/runs/shared/streaming-text.js +15 -0
  127. package/dist/runs/shared/structured-output.js +55 -0
  128. package/dist/runs/shared/subagent-control.js +179 -0
  129. package/dist/runs/shared/subagent-prompt-runtime.js +197 -0
  130. package/dist/runs/shared/subagent-tool-loop-guard.js +66 -0
  131. package/dist/runs/shared/supervisor-questions.js +320 -0
  132. package/dist/runs/shared/workflow-graph.js +214 -0
  133. package/dist/runs/shared/workflow-policy.js +84 -0
  134. package/dist/runs/shared/worktree.js +525 -0
  135. package/dist/shared/agent-context-policy.js +126 -0
  136. package/dist/shared/artifacts.js +96 -0
  137. package/dist/shared/atomic-json.js +13 -0
  138. package/dist/shared/cooperative.js +20 -0
  139. package/dist/shared/execution-cwd.js +14 -0
  140. package/dist/shared/file-coalescer.js +21 -0
  141. package/dist/shared/fork-context.js +49 -0
  142. package/dist/shared/formatters.js +85 -0
  143. package/dist/shared/journal-reader.js +407 -0
  144. package/dist/shared/lazy-tools.js +32 -0
  145. package/dist/shared/model-info.js +68 -0
  146. package/dist/shared/native-session.js +15 -0
  147. package/dist/shared/native-tui.js +18 -0
  148. package/dist/shared/native-typebox.js +20 -0
  149. package/dist/shared/post-exit-stdio-guard.js +197 -0
  150. package/dist/shared/prompt-sections.js +8 -0
  151. package/dist/shared/session-entries.js +52 -0
  152. package/dist/shared/session-identity.js +14 -0
  153. package/dist/shared/settings.js +254 -0
  154. package/dist/shared/status-format.js +111 -0
  155. package/dist/shared/temp-root.js +114 -0
  156. package/dist/shared/types.js +207 -0
  157. package/dist/shared/ui-mode.js +3 -0
  158. package/dist/shared/utils.js +334 -0
  159. package/dist/slash/prompt-template-bridge.js +298 -0
  160. package/dist/slash/slash-bridge.js +116 -0
  161. package/dist/slash/slash-commands.js +607 -0
  162. package/dist/slash/slash-live-state.js +246 -0
  163. package/dist/tui/action-hints.js +75 -0
  164. package/dist/tui/agent-history.js +197 -0
  165. package/dist/tui/agent-view.js +1656 -0
  166. package/dist/tui/render.js +1515 -0
  167. package/docs/intercom.md +579 -0
  168. package/docs/performance.md +121 -0
  169. package/package.json +101 -4
  170. package/prompts/gather-context-and-clarify.md +14 -0
  171. package/prompts/parallel-cleanup.md +62 -0
  172. package/prompts/parallel-context-build.md +56 -0
  173. package/prompts/parallel-handoff-plan.md +62 -0
  174. package/prompts/parallel-research.md +51 -0
  175. package/prompts/parallel-review.md +59 -0
  176. package/prompts/review-loop.md +42 -0
  177. package/scripts/build.mjs +134 -0
  178. package/scripts/check-compat.mjs +48 -0
  179. package/scripts/compat-host.mjs +19 -0
  180. package/scripts/linux-smoke.sh +51 -0
  181. package/scripts/local-install-smoke.mjs +66 -0
  182. package/scripts/native-package-smoke.mjs +82 -0
  183. package/scripts/package-smoke.mjs +205 -0
  184. package/scripts/prepare.mjs +67 -0
  185. package/scripts/real-pi-smoke.mjs +415 -0
  186. package/scripts/run-tests.mjs +149 -0
  187. package/skills/pi-intercom/SKILL.md +113 -0
  188. package/skills/pi-intercom/evals/evals.json +29 -0
  189. package/skills/pi-intercom/evals/trigger-evals.json +47 -0
  190. package/skills/pi-intercom/references/peer-sessions.md +69 -0
  191. package/skills/pi-subagents/SKILL.md +76 -0
  192. package/skills/pi-subagents/evals/evals.json +29 -0
  193. package/skills/pi-subagents/evals/trigger-evals.json +42 -0
  194. package/skills/pi-subagents/references/full-orchestration-guide.md +863 -0
  195. package/src/agents/agent-management.ts +813 -0
  196. package/src/agents/agent-scope.ts +7 -0
  197. package/src/agents/agent-selection.ts +23 -0
  198. package/src/agents/agent-serializer.ts +99 -0
  199. package/src/agents/agents.ts +916 -0
  200. package/src/agents/chain-serializer.ts +329 -0
  201. package/src/agents/frontmatter.ts +21 -0
  202. package/src/agents/identity.ts +30 -0
  203. package/src/agents/skills.ts +637 -0
  204. package/src/extension/build-info.ts +2 -0
  205. package/src/extension/compact-tools.ts +114 -0
  206. package/src/extension/config.ts +23 -0
  207. package/src/extension/control-notices.ts +47 -0
  208. package/src/extension/doctor.ts +171 -0
  209. package/src/extension/fanout-child.ts +312 -0
  210. package/src/extension/index.ts +649 -0
  211. package/src/extension/schemas.ts +444 -0
  212. package/src/extension/tool-input.ts +19 -0
  213. package/src/extension/tool-result.ts +16 -0
  214. package/src/history/canonical-result.ts +62 -0
  215. package/src/history/database.ts +59 -0
  216. package/src/history/index.ts +152 -0
  217. package/src/history/ingest.ts +332 -0
  218. package/src/history/queries.ts +241 -0
  219. package/src/history/store.ts +203 -0
  220. package/src/history/types.ts +166 -0
  221. package/src/history/worker.ts +317 -0
  222. package/src/intercom/intercom-bridge.ts +123 -0
  223. package/src/intercom/live-intercom.ts +104 -0
  224. package/src/intercom/result-intercom.ts +429 -0
  225. package/src/pi-intercom/agent-dir.ts +9 -0
  226. package/src/pi-intercom/broker/broker.ts +463 -0
  227. package/src/pi-intercom/broker/client.ts +558 -0
  228. package/src/pi-intercom/broker/framing.ts +77 -0
  229. package/src/pi-intercom/broker/paths.ts +47 -0
  230. package/src/pi-intercom/broker/pid.ts +59 -0
  231. package/src/pi-intercom/broker/spawn.ts +248 -0
  232. package/src/pi-intercom/config.ts +142 -0
  233. package/src/pi-intercom/index.ts +2929 -0
  234. package/src/pi-intercom/reply-tracker.ts +211 -0
  235. package/src/pi-intercom/session-targets.ts +172 -0
  236. package/src/pi-intercom/subagent-live-events.ts +133 -0
  237. package/src/pi-intercom/topics.ts +125 -0
  238. package/src/pi-intercom/types.ts +271 -0
  239. package/src/pi-intercom/ui/compose.ts +289 -0
  240. package/src/pi-intercom/ui/inline-message.ts +92 -0
  241. package/src/pi-intercom/ui/session-list.ts +139 -0
  242. package/src/runs/background/async-control.ts +89 -0
  243. package/src/runs/background/async-execution.ts +850 -0
  244. package/src/runs/background/async-job-tracker.ts +385 -0
  245. package/src/runs/background/async-result-file.ts +62 -0
  246. package/src/runs/background/async-resume.ts +333 -0
  247. package/src/runs/background/async-status.ts +481 -0
  248. package/src/runs/background/completion-dedupe.ts +79 -0
  249. package/src/runs/background/completion-delivery.ts +195 -0
  250. package/src/runs/background/notify.ts +123 -0
  251. package/src/runs/background/parallel-groups.ts +53 -0
  252. package/src/runs/background/result-watcher.ts +423 -0
  253. package/src/runs/background/run-id-resolver.ts +69 -0
  254. package/src/runs/background/run-status.ts +384 -0
  255. package/src/runs/background/stale-run-reconciler.ts +369 -0
  256. package/src/runs/background/subagent-runner-launcher.ts +56 -0
  257. package/src/runs/background/subagent-runner.ts +2053 -0
  258. package/src/runs/background/top-level-async.ts +13 -0
  259. package/src/runs/foreground/chain-clarify.ts +1340 -0
  260. package/src/runs/foreground/clarify-invocation.ts +77 -0
  261. package/src/runs/foreground/execution-input.ts +467 -0
  262. package/src/runs/foreground/foreground-control.ts +821 -0
  263. package/src/runs/foreground/question-control.ts +70 -0
  264. package/src/runs/foreground/run-async-path.ts +243 -0
  265. package/src/runs/foreground/subagent-executor.ts +501 -0
  266. package/src/runs/foreground/subagent-params.ts +320 -0
  267. package/src/runs/foreground/wait-run.ts +121 -0
  268. package/src/runs/shared/acceptance-contract.ts +257 -0
  269. package/src/runs/shared/acceptance-evaluation.ts +275 -0
  270. package/src/runs/shared/acceptance-finalization.ts +353 -0
  271. package/src/runs/shared/acceptance-reports.ts +152 -0
  272. package/src/runs/shared/acceptance.ts +33 -0
  273. package/src/runs/shared/chain-outputs.ts +114 -0
  274. package/src/runs/shared/child-attempt.ts +623 -0
  275. package/src/runs/shared/child-execution-cwd.ts +91 -0
  276. package/src/runs/shared/child-observations.ts +99 -0
  277. package/src/runs/shared/claude-code.ts +243 -0
  278. package/src/runs/shared/completion-guard.ts +35 -0
  279. package/src/runs/shared/dynamic-fanout.ts +295 -0
  280. package/src/runs/shared/history-index.ts +69 -0
  281. package/src/runs/shared/mcp-direct-tool-allowlist.ts +376 -0
  282. package/src/runs/shared/model-fallback.ts +219 -0
  283. package/src/runs/shared/mutating-tool-guard.ts +194 -0
  284. package/src/runs/shared/native-finalization.ts +128 -0
  285. package/src/runs/shared/native-usage.ts +113 -0
  286. package/src/runs/shared/nested-events.ts +752 -0
  287. package/src/runs/shared/nested-path.ts +52 -0
  288. package/src/runs/shared/nested-render.ts +119 -0
  289. package/src/runs/shared/parallel-utils.ts +153 -0
  290. package/src/runs/shared/parent-receipts.ts +59 -0
  291. package/src/runs/shared/parent-usage.ts +121 -0
  292. package/src/runs/shared/pi-args.ts +350 -0
  293. package/src/runs/shared/pi-spawn.ts +52 -0
  294. package/src/runs/shared/run-history-queries.ts +44 -0
  295. package/src/runs/shared/run-history.ts +93 -0
  296. package/src/runs/shared/run-id-ambiguity.ts +4 -0
  297. package/src/runs/shared/run-records.ts +607 -0
  298. package/src/runs/shared/session-cwd-preload.ts +27 -0
  299. package/src/runs/shared/single-output.ts +267 -0
  300. package/src/runs/shared/streaming-text.ts +18 -0
  301. package/src/runs/shared/structured-output.ts +68 -0
  302. package/src/runs/shared/subagent-control.ts +219 -0
  303. package/src/runs/shared/subagent-prompt-runtime.ts +201 -0
  304. package/src/runs/shared/subagent-tool-loop-guard.ts +89 -0
  305. package/src/runs/shared/supervisor-questions.ts +372 -0
  306. package/src/runs/shared/workflow-graph.ts +220 -0
  307. package/src/runs/shared/workflow-policy.ts +125 -0
  308. package/src/runs/shared/worktree.ts +635 -0
  309. package/src/shared/agent-context-policy.ts +184 -0
  310. package/src/shared/artifacts.ts +95 -0
  311. package/src/shared/atomic-json.ts +16 -0
  312. package/src/shared/cooperative.ts +21 -0
  313. package/src/shared/execution-cwd.ts +14 -0
  314. package/src/shared/file-coalescer.ts +29 -0
  315. package/src/shared/fork-context.ts +68 -0
  316. package/src/shared/formatters.ts +84 -0
  317. package/src/shared/journal-reader.ts +333 -0
  318. package/src/shared/lazy-tools.ts +36 -0
  319. package/src/shared/model-info.ts +81 -0
  320. package/src/shared/native-session.ts +8 -0
  321. package/src/shared/native-tui.ts +10 -0
  322. package/src/shared/native-typebox.ts +14 -0
  323. package/src/shared/post-exit-stdio-guard.ts +204 -0
  324. package/src/shared/prompt-sections.ts +10 -0
  325. package/src/shared/session-entries.ts +49 -0
  326. package/src/shared/session-identity.ts +18 -0
  327. package/src/shared/settings.ts +384 -0
  328. package/src/shared/status-format.ts +123 -0
  329. package/src/shared/temp-root.ts +107 -0
  330. package/src/shared/types.ts +1259 -0
  331. package/src/shared/ui-mode.ts +5 -0
  332. package/src/shared/utils.ts +342 -0
  333. package/src/slash/prompt-template-bridge.ts +408 -0
  334. package/src/slash/slash-bridge.ts +178 -0
  335. package/src/slash/slash-commands.ts +617 -0
  336. package/src/slash/slash-live-state.ts +292 -0
  337. package/src/tui/action-hints.ts +69 -0
  338. package/src/tui/agent-history.ts +210 -0
  339. package/src/tui/agent-view.ts +1323 -0
  340. package/src/tui/render.ts +1550 -0
  341. package/tsconfig.build.json +12 -0
  342. package/tsconfig.json +19 -0
@@ -0,0 +1,916 @@
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 { DefaultPackageManager, SettingsManager } from "@earendil-works/pi-coding-agent";
10
+ import type { AcceptanceInput, OutputMode } from "../shared/types.ts";
11
+ import { getAgentDir } from "../shared/utils.ts";
12
+ import { KNOWN_FIELDS } from "./agent-serializer.ts";
13
+ import { parseChain, parseJsonChain } from "./chain-serializer.ts";
14
+ import { mergeAgentsForScope } from "./agent-selection.ts";
15
+ import { parseFrontmatter } from "./frontmatter.ts";
16
+ import { buildRuntimeName, parsePackageName } from "./identity.ts";
17
+ export { buildRuntimeName, frontmatterNameForConfig, parsePackageName } from "./identity.ts";
18
+
19
+ export type AgentScope = "user" | "project" | "both";
20
+
21
+ export type AgentSource = "builtin" | "package" | "user" | "project";
22
+ type SystemPromptMode = "append" | "replace";
23
+ export type AgentDefaultContext = "fresh" | "fork";
24
+
25
+ export function defaultSystemPromptMode(_name: string): SystemPromptMode {
26
+ return "append";
27
+ }
28
+
29
+ export function defaultInheritProjectContext(_name: string): boolean {
30
+ return true;
31
+ }
32
+
33
+ export function defaultInheritSkills(): boolean {
34
+ return true;
35
+ }
36
+
37
+ interface BuiltinAgentOverrideConfig {
38
+ model?: string | false;
39
+ fallbackModels?: string[] | false;
40
+ thinking?: string | false;
41
+ systemPromptMode?: SystemPromptMode;
42
+ inheritProjectContext?: boolean;
43
+ inheritSkills?: boolean;
44
+ defaultContext?: AgentDefaultContext | false;
45
+ disabled?: boolean;
46
+ systemPrompt?: string;
47
+ skills?: string[] | false;
48
+ tools?: string[] | false;
49
+ allowSubagents?: boolean;
50
+ maxExecutionTimeMs?: number | false;
51
+ maxTokens?: number | false;
52
+ completionGuard?: boolean;
53
+ }
54
+
55
+ export interface AgentConfig {
56
+ name: string;
57
+ localName?: string;
58
+ packageName?: string;
59
+ description: string;
60
+ tools?: string[];
61
+ mcpDirectTools?: string[];
62
+ allowSubagents?: boolean;
63
+ model?: string;
64
+ fallbackModels?: string[];
65
+ thinking?: string;
66
+ systemPromptMode: SystemPromptMode;
67
+ inheritProjectContext: boolean;
68
+ inheritSkills: boolean;
69
+ defaultContext?: AgentDefaultContext;
70
+ systemPrompt: string;
71
+ source: AgentSource;
72
+ filePath: string;
73
+ skills?: string[];
74
+ extensions?: string[];
75
+ output?: string;
76
+ defaultReads?: string[];
77
+ defaultProgress?: boolean;
78
+ interactive?: boolean;
79
+ maxSubagentDepth?: number;
80
+ maxExecutionTimeMs?: number;
81
+ maxTokens?: number;
82
+ completionGuard?: boolean;
83
+ disabled?: boolean;
84
+ extraFields?: Record<string, string>;
85
+ }
86
+
87
+ interface SubagentSettings {
88
+ overrides: Record<string, BuiltinAgentOverrideConfig>;
89
+ disableBuiltins?: boolean;
90
+ }
91
+
92
+ const EMPTY_SUBAGENT_SETTINGS: SubagentSettings = { overrides: {} };
93
+
94
+ export interface ChainStepConfig {
95
+ agent?: string;
96
+ task?: string;
97
+ phase?: string;
98
+ label?: string;
99
+ as?: string;
100
+ outputSchema?: string | Record<string, unknown>;
101
+ output?: string | false;
102
+ outputMode?: OutputMode;
103
+ reads?: string[] | false;
104
+ model?: string;
105
+ skills?: string[] | false;
106
+ progress?: boolean;
107
+ parallel?: unknown;
108
+ expand?: unknown;
109
+ collect?: unknown;
110
+ concurrency?: number;
111
+ failFast?: boolean;
112
+ worktree?: boolean;
113
+ acceptance?: AcceptanceInput;
114
+ }
115
+
116
+ export interface ChainConfig {
117
+ name: string;
118
+ localName?: string;
119
+ packageName?: string;
120
+ description: string;
121
+ source: AgentSource;
122
+ filePath: string;
123
+ steps: ChainStepConfig[];
124
+ extraFields?: Record<string, string>;
125
+ }
126
+
127
+ export interface ChainDiscoveryDiagnostic {
128
+ source: "user" | "project";
129
+ filePath: string;
130
+ error: string;
131
+ }
132
+
133
+ export interface AgentDiscoveryDiagnostic extends Omit<ChainDiscoveryDiagnostic, "source"> {
134
+ source: "user" | "project" | "package";
135
+ }
136
+
137
+ export interface AgentDiscoveryOptions {
138
+ projectTrusted?: boolean;
139
+ }
140
+
141
+ interface AgentDiscoveryResult {
142
+ agents: AgentConfig[];
143
+ projectAgentsDir: string | null;
144
+ }
145
+
146
+ function getUserChainDir(): string {
147
+ return path.join(getAgentDir(), "chains");
148
+ }
149
+
150
+ function splitToolList(rawTools: string[] | undefined): { tools?: string[]; mcpDirectTools?: string[] } {
151
+ const mcpDirectTools: string[] = [];
152
+ const tools: string[] = [];
153
+ for (const tool of rawTools ?? []) {
154
+ if (tool.startsWith("mcp:")) {
155
+ mcpDirectTools.push(tool.slice(4));
156
+ } else {
157
+ tools.push(tool);
158
+ }
159
+ }
160
+ return {
161
+ ...(tools.length > 0 ? { tools } : {}),
162
+ ...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
163
+ };
164
+ }
165
+
166
+ function hasProjectPiSubagentResource(dir: string): boolean {
167
+ const piDir = path.join(dir, ".pi");
168
+ return fs.existsSync(path.join(piDir, "settings.json"))
169
+ || isDirectory(path.join(piDir, "agents"))
170
+ || isDirectory(path.join(piDir, "chains"));
171
+ }
172
+
173
+ function findNearestProjectRoot(cwd: string): string | null {
174
+ let currentDir = cwd;
175
+ const homeDir = path.resolve(os.homedir());
176
+ const startedAtHome = path.resolve(cwd) === homeDir;
177
+ while (true) {
178
+ const resolvedCurrent = path.resolve(currentDir);
179
+ // Like Pi, ~/.pi is project config only for a home cwd, never for projects below it.
180
+ if (resolvedCurrent === homeDir && !startedAtHome) return null;
181
+ const hasProjectPi = hasProjectPiSubagentResource(currentDir);
182
+ const hasProjectAgents = resolvedCurrent !== homeDir && isDirectory(path.join(currentDir, ".agents"));
183
+ if (hasProjectPi || hasProjectAgents) return currentDir;
184
+
185
+ const parentDir = path.dirname(currentDir);
186
+ if (parentDir === currentDir) return null;
187
+ currentDir = parentDir;
188
+ }
189
+ }
190
+
191
+ function getUserAgentSettingsPath(): string {
192
+ return path.join(getAgentDir(), "settings.json");
193
+ }
194
+
195
+ function getProjectAgentSettingsPath(cwd: string, options: AgentDiscoveryOptions = {}): string | null {
196
+ if (options.projectTrusted === false) return null;
197
+ const projectRoot = findNearestProjectRoot(cwd);
198
+ return projectRoot ? path.join(projectRoot, ".pi", "settings.json") : null;
199
+ }
200
+
201
+ function readSettingsFileStrict(filePath: string): Record<string, unknown> {
202
+ if (!fs.existsSync(filePath)) return {};
203
+ let raw: string;
204
+ try {
205
+ raw = fs.readFileSync(filePath, "utf-8");
206
+ } catch (error) {
207
+ const message = error instanceof Error ? error.message : String(error);
208
+ throw new Error(`Failed to read settings file '${filePath}': ${message}`, { cause: error });
209
+ }
210
+
211
+ let parsed: unknown;
212
+ try {
213
+ parsed = JSON.parse(raw);
214
+ } catch (error) {
215
+ const message = error instanceof Error ? error.message : String(error);
216
+ throw new Error(`Failed to parse settings file '${filePath}': ${message}`, { cause: error });
217
+ }
218
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
219
+ throw new Error(`Settings file '${filePath}' must contain a JSON object.`);
220
+ }
221
+ return parsed as Record<string, unknown>;
222
+ }
223
+
224
+ function parseOverrideStringArrayOrFalse(
225
+ value: unknown,
226
+ meta: { filePath: string; name: string; field: string },
227
+ ): string[] | false | undefined {
228
+ if (value === undefined) return undefined;
229
+ if (value === false) return false;
230
+ if (!Array.isArray(value)) {
231
+ throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid '${meta.field}'; expected an array of strings or false.`);
232
+ }
233
+
234
+ const items: string[] = [];
235
+ for (const item of value) {
236
+ if (typeof item !== "string") {
237
+ throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid '${meta.field}'; expected an array of strings or false.`);
238
+ }
239
+ const trimmed = item.trim();
240
+ if (trimmed) items.push(trimmed);
241
+ }
242
+ return items;
243
+ }
244
+
245
+ function parseBuiltinOverrideEntry(
246
+ name: string,
247
+ value: unknown,
248
+ filePath: string,
249
+ ): BuiltinAgentOverrideConfig | undefined {
250
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
251
+ throw new Error(`Builtin override '${name}' in '${filePath}' must be an object.`);
252
+ }
253
+
254
+ const input = value as Record<string, unknown>;
255
+ const override: BuiltinAgentOverrideConfig = {};
256
+
257
+ if ("model" in input) {
258
+ if (typeof input.model === "string" || input.model === false) override.model = input.model;
259
+ else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'model'; expected a string or false.`);
260
+ }
261
+
262
+ if ("thinking" in input) {
263
+ if (typeof input.thinking === "string" || input.thinking === false) override.thinking = input.thinking;
264
+ else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'thinking'; expected a string or false.`);
265
+ }
266
+
267
+ if ("systemPromptMode" in input) {
268
+ if (input.systemPromptMode === "append" || input.systemPromptMode === "replace") {
269
+ override.systemPromptMode = input.systemPromptMode;
270
+ } else {
271
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPromptMode'; expected 'append' or 'replace'.`);
272
+ }
273
+ }
274
+
275
+ if ("inheritProjectContext" in input) {
276
+ if (typeof input.inheritProjectContext === "boolean") {
277
+ override.inheritProjectContext = input.inheritProjectContext;
278
+ } else {
279
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'inheritProjectContext'; expected a boolean.`);
280
+ }
281
+ }
282
+
283
+ if ("inheritSkills" in input) {
284
+ if (typeof input.inheritSkills === "boolean") {
285
+ override.inheritSkills = input.inheritSkills;
286
+ } else {
287
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'inheritSkills'; expected a boolean.`);
288
+ }
289
+ }
290
+
291
+ if ("defaultContext" in input) {
292
+ if (input.defaultContext === "fresh" || input.defaultContext === "fork" || input.defaultContext === false) {
293
+ override.defaultContext = input.defaultContext;
294
+ } else {
295
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'defaultContext'; expected 'fresh', 'fork', or false.`);
296
+ }
297
+ }
298
+
299
+ if ("disabled" in input) {
300
+ if (typeof input.disabled === "boolean") {
301
+ override.disabled = input.disabled;
302
+ } else {
303
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'disabled'; expected a boolean.`);
304
+ }
305
+ }
306
+
307
+ if ("allowSubagents" in input) {
308
+ if (typeof input.allowSubagents === "boolean") {
309
+ override.allowSubagents = input.allowSubagents;
310
+ } else {
311
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'allowSubagents'; expected a boolean.`);
312
+ }
313
+ }
314
+
315
+ if ("maxExecutionTimeMs" in input) {
316
+ if (input.maxExecutionTimeMs === false || (typeof input.maxExecutionTimeMs === "number" && Number.isInteger(input.maxExecutionTimeMs) && input.maxExecutionTimeMs >= 1)) {
317
+ override.maxExecutionTimeMs = input.maxExecutionTimeMs;
318
+ } else {
319
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'maxExecutionTimeMs'; expected an integer >= 1 or false.`);
320
+ }
321
+ }
322
+
323
+ if ("maxTokens" in input) {
324
+ if (input.maxTokens === false || (typeof input.maxTokens === "number" && Number.isInteger(input.maxTokens) && input.maxTokens >= 1)) {
325
+ override.maxTokens = input.maxTokens;
326
+ } else {
327
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'maxTokens'; expected an integer >= 1 or false.`);
328
+ }
329
+ }
330
+
331
+ if ("completionGuard" in input) {
332
+ if (typeof input.completionGuard === "boolean") {
333
+ override.completionGuard = input.completionGuard;
334
+ } else {
335
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'completionGuard'; expected a boolean.`);
336
+ }
337
+ }
338
+
339
+ if ("systemPrompt" in input) {
340
+ if (typeof input.systemPrompt === "string") override.systemPrompt = input.systemPrompt;
341
+ else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPrompt'; expected a string.`);
342
+ }
343
+
344
+ const fallbackModels = parseOverrideStringArrayOrFalse(input.fallbackModels, { filePath, name, field: "fallbackModels" });
345
+ if (fallbackModels !== undefined) override.fallbackModels = fallbackModels;
346
+
347
+ const skills = parseOverrideStringArrayOrFalse(input.skills, { filePath, name, field: "skills" });
348
+ if (skills !== undefined) override.skills = skills;
349
+
350
+ const tools = parseOverrideStringArrayOrFalse(input.tools, { filePath, name, field: "tools" });
351
+ if (tools !== undefined) override.tools = tools;
352
+
353
+ return Object.keys(override).length > 0 ? override : undefined;
354
+ }
355
+
356
+ function readSubagentSettings(filePath: string | null): SubagentSettings {
357
+ if (!filePath) return EMPTY_SUBAGENT_SETTINGS;
358
+ const settings = readSettingsFileStrict(filePath);
359
+ const subagents = settings.subagents;
360
+ if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return EMPTY_SUBAGENT_SETTINGS;
361
+
362
+ const subagentsObject = subagents as Record<string, unknown>;
363
+ let disableBuiltins: boolean | undefined;
364
+ if ("disableBuiltins" in subagentsObject) {
365
+ if (typeof subagentsObject.disableBuiltins === "boolean") {
366
+ disableBuiltins = subagentsObject.disableBuiltins;
367
+ } else {
368
+ throw new Error(`Subagent settings in '${filePath}' have invalid 'disableBuiltins'; expected a boolean.`);
369
+ }
370
+ }
371
+
372
+ const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
373
+ const agentOverrides = subagentsObject.agentOverrides;
374
+ if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) {
375
+ return { overrides: parsed, disableBuiltins };
376
+ }
377
+ for (const [name, value] of Object.entries(agentOverrides)) {
378
+ const override = parseBuiltinOverrideEntry(name, value, filePath);
379
+ if (override) parsed[name] = override;
380
+ }
381
+ return { overrides: parsed, disableBuiltins };
382
+ }
383
+
384
+ function applyBuiltinOverride(
385
+ agent: AgentConfig,
386
+ override: BuiltinAgentOverrideConfig,
387
+ ): AgentConfig {
388
+ const next: AgentConfig = { ...agent };
389
+
390
+ if (override.model !== undefined) next.model = override.model === false ? undefined : override.model;
391
+ if (override.fallbackModels !== undefined) {
392
+ next.fallbackModels = override.fallbackModels === false ? undefined : [...override.fallbackModels];
393
+ }
394
+ if (override.thinking !== undefined) next.thinking = override.thinking === false ? undefined : override.thinking;
395
+ if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode;
396
+ if (override.inheritProjectContext !== undefined) next.inheritProjectContext = override.inheritProjectContext;
397
+ if (override.inheritSkills !== undefined) next.inheritSkills = override.inheritSkills;
398
+ if (override.defaultContext !== undefined) next.defaultContext = override.defaultContext === false ? undefined : override.defaultContext;
399
+ if (override.disabled !== undefined) next.disabled = override.disabled;
400
+ if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
401
+ if (override.skills !== undefined) next.skills = override.skills === false ? undefined : [...override.skills];
402
+ if (override.tools !== undefined) {
403
+ const { tools, mcpDirectTools } = splitToolList(override.tools === false ? [] : override.tools);
404
+ next.tools = tools;
405
+ next.mcpDirectTools = mcpDirectTools;
406
+ }
407
+ if (override.allowSubagents !== undefined) next.allowSubagents = override.allowSubagents;
408
+ if (override.maxExecutionTimeMs !== undefined) next.maxExecutionTimeMs = override.maxExecutionTimeMs === false ? undefined : override.maxExecutionTimeMs;
409
+ if (override.maxTokens !== undefined) next.maxTokens = override.maxTokens === false ? undefined : override.maxTokens;
410
+ if (override.completionGuard !== undefined) next.completionGuard = override.completionGuard;
411
+
412
+ return next;
413
+ }
414
+
415
+ function applyBuiltinOverrides(
416
+ builtinAgents: AgentConfig[],
417
+ userSettings: SubagentSettings,
418
+ projectSettings: SubagentSettings,
419
+ projectSettingsPath: string | null,
420
+ ): AgentConfig[] {
421
+ return builtinAgents.map((agent) => {
422
+ const userOverride = userSettings.overrides[agent.name];
423
+ let next = userOverride
424
+ ? applyBuiltinOverride(agent, userOverride)
425
+ : userSettings.disableBuiltins === true
426
+ ? applyBuiltinOverride(agent, { disabled: true })
427
+ : agent;
428
+ if (projectSettingsPath) {
429
+ const projectOverride = projectSettings.overrides[agent.name];
430
+ if (projectOverride) next = applyBuiltinOverride({ ...next, disabled: false }, projectOverride);
431
+ else if (projectSettings.disableBuiltins !== undefined) next = applyBuiltinOverride(next, { disabled: projectSettings.disableBuiltins });
432
+ }
433
+ return next;
434
+ });
435
+ }
436
+
437
+ function listFilesRecursive(dir: string, predicate: (fileName: string) => boolean): string[] {
438
+ const files: string[] = [];
439
+ if (!fs.existsSync(dir)) return files;
440
+
441
+ let entries: fs.Dirent[];
442
+ try {
443
+ entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
444
+ } catch {
445
+ return files;
446
+ }
447
+
448
+ for (const entry of entries) {
449
+ const filePath = path.join(dir, entry.name);
450
+ if (entry.isDirectory()) {
451
+ if (!entry.name.startsWith(".")) files.push(...listFilesRecursive(filePath, predicate));
452
+ continue;
453
+ }
454
+ if (!entry.isFile() && !entry.isSymbolicLink()) continue;
455
+ if (!predicate(entry.name)) continue;
456
+ files.push(filePath);
457
+ }
458
+ return files;
459
+ }
460
+
461
+ function isInAgentSkillSubtree(dir: string, filePath: string): boolean {
462
+ return path.relative(dir, filePath).split(path.sep)[0] === "skills";
463
+ }
464
+
465
+ const reportedAgentDiagnostics = new Map<string, { filePath: string; error: string }>();
466
+ const loggedAgentDiagnostics = new Set<string>();
467
+ const MAX_LOGGED_AGENT_DIAGNOSTICS = 1_000;
468
+
469
+ function reportAgentDiagnostic(filePath: string, message: string): void {
470
+ const key = `${filePath}\0${message}`;
471
+ if (!reportedAgentDiagnostics.has(key) && reportedAgentDiagnostics.size >= MAX_LOGGED_AGENT_DIAGNOSTICS) {
472
+ const oldest = reportedAgentDiagnostics.keys().next().value;
473
+ if (oldest !== undefined) reportedAgentDiagnostics.delete(oldest);
474
+ }
475
+ reportedAgentDiagnostics.set(key, { filePath, error: message });
476
+ if (loggedAgentDiagnostics.has(key)) return;
477
+ if (loggedAgentDiagnostics.size >= MAX_LOGGED_AGENT_DIAGNOSTICS) {
478
+ const oldest = loggedAgentDiagnostics.values().next().value;
479
+ if (oldest !== undefined) loggedAgentDiagnostics.delete(oldest);
480
+ }
481
+ loggedAgentDiagnostics.add(key);
482
+ console.error(`Invalid agent definition '${filePath}: ${message}'`);
483
+ }
484
+
485
+ function pathIsInside(dir: string, filePath: string): boolean {
486
+ const relative = path.relative(dir, filePath);
487
+ return relative !== "" && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
488
+ }
489
+
490
+ function clearAgentDiagnosticsForDirs(dirs: string[]): void {
491
+ for (const [key, entry] of reportedAgentDiagnostics) {
492
+ if (dirs.some((dir) => pathIsInside(dir, entry.filePath))) reportedAgentDiagnostics.delete(key);
493
+ }
494
+ }
495
+
496
+ function agentDiagnosticsForDir(dir: string, source: AgentDiscoveryDiagnostic["source"]): AgentDiscoveryDiagnostic[] {
497
+ return [...reportedAgentDiagnostics.values()]
498
+ .filter((entry) => pathIsInside(dir, entry.filePath))
499
+ .map((entry) => ({ ...entry, source }));
500
+ }
501
+
502
+ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
503
+ const agents: AgentConfig[] = [];
504
+
505
+ for (const filePath of listFilesRecursive(dir, (fileName) => fileName.endsWith(".md") && !fileName.endsWith(".chain.md") && fileName !== "SKILL.template.md" && fileName !== "AGENTS.md")) {
506
+ if (isInAgentSkillSubtree(dir, filePath)) continue;
507
+ let content: string;
508
+ try {
509
+ content = fs.readFileSync(filePath, "utf-8");
510
+ } catch (error) {
511
+ reportAgentDiagnostic(filePath, `cannot read file: ${error instanceof Error ? error.message : String(error)}`);
512
+ continue;
513
+ }
514
+
515
+ const { frontmatter, body } = parseFrontmatter(content);
516
+
517
+ if (!frontmatter.name || !frontmatter.description) {
518
+ reportAgentDiagnostic(filePath, "frontmatter must include name and description");
519
+ continue;
520
+ }
521
+
522
+ const localName = frontmatter.name;
523
+ const parsedPackage = parsePackageName(frontmatter.package, `Agent '${localName}' package`);
524
+ if (parsedPackage.error) {
525
+ reportAgentDiagnostic(filePath, parsedPackage.error);
526
+ continue;
527
+ }
528
+ const packageName = parsedPackage.packageName;
529
+ const runtimeName = buildRuntimeName(localName, packageName);
530
+
531
+ const rawTools = frontmatter.tools
532
+ ?.split(",")
533
+ .map((t) => t.trim())
534
+ .filter(Boolean);
535
+
536
+ const mcpDirectTools: string[] = [];
537
+ const tools: string[] = [];
538
+ if (rawTools) {
539
+ for (const tool of rawTools) {
540
+ if (tool.startsWith("mcp:")) {
541
+ mcpDirectTools.push(tool.slice(4));
542
+ } else {
543
+ tools.push(tool);
544
+ }
545
+ }
546
+ }
547
+
548
+ const defaultReads = frontmatter.defaultReads
549
+ ?.split(",")
550
+ .map((f) => f.trim())
551
+ .filter(Boolean);
552
+
553
+ const skillStr = frontmatter.skill || frontmatter.skills;
554
+ const skills = skillStr
555
+ ?.split(",")
556
+ .map((s) => s.trim())
557
+ .filter(Boolean);
558
+ const fallbackModels = frontmatter.fallbackModels
559
+ ?.split(",")
560
+ .map((model) => model.trim())
561
+ .filter(Boolean);
562
+ const systemPromptMode = frontmatter.systemPromptMode === "replace"
563
+ ? "replace"
564
+ : frontmatter.systemPromptMode === "append"
565
+ ? "append"
566
+ : defaultSystemPromptMode(localName);
567
+ const inheritProjectContext = frontmatter.inheritProjectContext === "true"
568
+ ? true
569
+ : frontmatter.inheritProjectContext === "false"
570
+ ? false
571
+ : defaultInheritProjectContext(localName);
572
+ const inheritSkills = frontmatter.inheritSkills === "true"
573
+ ? true
574
+ : frontmatter.inheritSkills === "false"
575
+ ? false
576
+ : defaultInheritSkills();
577
+ const defaultContext = frontmatter.defaultContext === "fork"
578
+ ? "fork" as const
579
+ : frontmatter.defaultContext === "fresh"
580
+ ? "fresh" as const
581
+ : undefined;
582
+
583
+ let extensions: string[] | undefined;
584
+ if (frontmatter.extensions !== undefined) {
585
+ extensions = frontmatter.extensions
586
+ .split(",")
587
+ .map((e) => e.trim())
588
+ .filter(Boolean);
589
+ }
590
+
591
+ const extraFields: Record<string, string> = {};
592
+ for (const [key, value] of Object.entries(frontmatter)) {
593
+ if (!KNOWN_FIELDS.has(key)) extraFields[key] = value;
594
+ }
595
+
596
+ const booleanFields = ["allowSubagents", "inheritProjectContext", "inheritSkills", "defaultProgress", "interactive", "completionGuard"] as const;
597
+ const invalidBoolean = booleanFields.find((field) => frontmatter[field] !== undefined && frontmatter[field] !== "true" && frontmatter[field] !== "false");
598
+ if (invalidBoolean) {
599
+ reportAgentDiagnostic(filePath, `${invalidBoolean} must be true or false`);
600
+ continue;
601
+ }
602
+ if (frontmatter.systemPromptMode !== undefined && frontmatter.systemPromptMode !== "append" && frontmatter.systemPromptMode !== "replace") {
603
+ reportAgentDiagnostic(filePath, "systemPromptMode must be append or replace");
604
+ continue;
605
+ }
606
+ if (frontmatter.defaultContext !== undefined && frontmatter.defaultContext !== "fresh" && frontmatter.defaultContext !== "fork") {
607
+ reportAgentDiagnostic(filePath, "defaultContext must be fresh or fork");
608
+ continue;
609
+ }
610
+ const parsedMaxSubagentDepth = Number(frontmatter.maxSubagentDepth);
611
+ const parsedMaxExecutionTimeMs = Number(frontmatter.maxExecutionTimeMs);
612
+ const parsedMaxTokens = Number(frontmatter.maxTokens);
613
+ if (frontmatter.maxSubagentDepth !== undefined && (!Number.isInteger(parsedMaxSubagentDepth) || parsedMaxSubagentDepth < 0)) {
614
+ reportAgentDiagnostic(filePath, "maxSubagentDepth must be an integer >= 0");
615
+ continue;
616
+ }
617
+ if (frontmatter.maxExecutionTimeMs !== undefined && (!Number.isInteger(parsedMaxExecutionTimeMs) || parsedMaxExecutionTimeMs < 1)) {
618
+ reportAgentDiagnostic(filePath, "maxExecutionTimeMs must be an integer >= 1");
619
+ continue;
620
+ }
621
+ if (frontmatter.maxTokens !== undefined && (!Number.isInteger(parsedMaxTokens) || parsedMaxTokens < 1)) {
622
+ reportAgentDiagnostic(filePath, "maxTokens must be an integer >= 1");
623
+ continue;
624
+ }
625
+ const completionGuard = frontmatter.completionGuard === "false"
626
+ ? false
627
+ : frontmatter.completionGuard === "true"
628
+ ? true
629
+ : undefined;
630
+
631
+ agents.push({
632
+ name: runtimeName,
633
+ localName,
634
+ packageName,
635
+ description: frontmatter.description,
636
+ tools: tools.length > 0 ? tools : undefined,
637
+ mcpDirectTools: mcpDirectTools.length > 0 ? mcpDirectTools : undefined,
638
+ allowSubagents: frontmatter.allowSubagents === "true",
639
+ model: frontmatter.model,
640
+ fallbackModels: fallbackModels && fallbackModels.length > 0 ? fallbackModels : undefined,
641
+ thinking: frontmatter.thinking,
642
+ systemPromptMode,
643
+ inheritProjectContext,
644
+ inheritSkills,
645
+ defaultContext,
646
+ systemPrompt: body,
647
+ source,
648
+ filePath,
649
+ skills: skills && skills.length > 0 ? skills : undefined,
650
+ extensions,
651
+ output: frontmatter.output,
652
+ defaultReads: defaultReads && defaultReads.length > 0 ? defaultReads : undefined,
653
+ defaultProgress: frontmatter.defaultProgress === "true",
654
+ interactive: frontmatter.interactive === "true",
655
+ maxSubagentDepth:
656
+ Number.isInteger(parsedMaxSubagentDepth) && parsedMaxSubagentDepth >= 0
657
+ ? parsedMaxSubagentDepth
658
+ : 0,
659
+ maxExecutionTimeMs:
660
+ Number.isInteger(parsedMaxExecutionTimeMs) && parsedMaxExecutionTimeMs >= 1
661
+ ? parsedMaxExecutionTimeMs
662
+ : undefined,
663
+ maxTokens:
664
+ Number.isInteger(parsedMaxTokens) && parsedMaxTokens >= 1
665
+ ? parsedMaxTokens
666
+ : undefined,
667
+ completionGuard,
668
+ extraFields: Object.keys(extraFields).length > 0 ? extraFields : undefined,
669
+ });
670
+ }
671
+
672
+ return agents;
673
+ }
674
+
675
+ function loadChainsFromDir(dir: string, source: "user" | "project"): { chains: ChainConfig[]; diagnostics: ChainDiscoveryDiagnostic[] } {
676
+ const chains: ChainConfig[] = [];
677
+ const diagnostics: ChainDiscoveryDiagnostic[] = [];
678
+
679
+ for (const filePath of listFilesRecursive(dir, (fileName) => fileName.endsWith(".chain.md") || fileName.endsWith(".chain.json"))) {
680
+ let content: string;
681
+ try {
682
+ content = fs.readFileSync(filePath, "utf-8");
683
+ } catch {
684
+ continue;
685
+ }
686
+
687
+ try {
688
+ const chain = filePath.endsWith(".chain.json") ? parseJsonChain(content, source, filePath) : parseChain(content, source, filePath);
689
+ chains.push(chain);
690
+ } catch (error) {
691
+ diagnostics.push({ source, filePath, error: error instanceof Error ? error.message : String(error) });
692
+ continue;
693
+ }
694
+ }
695
+
696
+ return { chains, diagnostics };
697
+ }
698
+
699
+ function isDirectory(p: string): boolean {
700
+ try {
701
+ return fs.statSync(p).isDirectory();
702
+ } catch {
703
+ return false;
704
+ }
705
+ }
706
+
707
+ function resolveNearestProjectAgentDirs(cwd: string, options: AgentDiscoveryOptions = {}): { readDirs: string[]; preferredDir: string | null } {
708
+ if (options.projectTrusted === false) return { readDirs: [], preferredDir: null };
709
+ const projectRoot = findNearestProjectRoot(cwd);
710
+ if (!projectRoot) return { readDirs: [], preferredDir: null };
711
+
712
+ const legacyDir = path.join(projectRoot, ".agents");
713
+ const preferredDir = path.join(projectRoot, ".pi", "agents");
714
+ const readDirs: string[] = [];
715
+ if (isDirectory(legacyDir)) readDirs.push(legacyDir);
716
+ if (isDirectory(preferredDir)) readDirs.push(preferredDir);
717
+
718
+ return {
719
+ readDirs,
720
+ preferredDir,
721
+ };
722
+ }
723
+
724
+ function resolveNearestProjectChainDirs(cwd: string, options: AgentDiscoveryOptions = {}): { readDirs: string[]; preferredDir: string | null } {
725
+ if (options.projectTrusted === false) return { readDirs: [], preferredDir: null };
726
+ const projectRoot = findNearestProjectRoot(cwd);
727
+ if (!projectRoot) return { readDirs: [], preferredDir: null };
728
+
729
+ const preferredDir = path.join(projectRoot, ".pi", "chains");
730
+ return {
731
+ readDirs: isDirectory(preferredDir) ? [preferredDir] : [],
732
+ preferredDir,
733
+ };
734
+ }
735
+ const BUILTIN_AGENTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "agents");
736
+
737
+ let lastPackageManager: { key: string; manager: DefaultPackageManager; settingsManager: SettingsManager } | undefined;
738
+
739
+ function loadConfiguredPackageAgents(cwd: string, scope: AgentScope, options: AgentDiscoveryOptions): { dirs: string[]; agents: AgentConfig[] } {
740
+ const projectRoot = findNearestProjectRoot(cwd) ?? cwd;
741
+ const agentDir = getAgentDir();
742
+ const projectTrusted = options.projectTrusted !== false;
743
+ const settings = {
744
+ global: scope === "project" ? undefined : JSON.stringify(readSettingsFileStrict(path.join(agentDir, "settings.json"))),
745
+ project: scope === "user" || !projectTrusted ? undefined : JSON.stringify(readSettingsFileStrict(path.join(projectRoot, ".pi", "settings.json"))),
746
+ };
747
+ const key = JSON.stringify([agentDir, projectRoot, scope, projectTrusted, settings]);
748
+ if (lastPackageManager?.key !== key) {
749
+ const settingsManager = SettingsManager.fromStorage({
750
+ withLock(settingsScope, read) { read(settings[settingsScope]); },
751
+ }, { projectTrusted });
752
+ const errors = settingsManager.drainErrors();
753
+ if (errors.length) throw errors[0]!.error;
754
+ lastPackageManager = { key, manager: new DefaultPackageManager({ cwd: projectRoot, agentDir, settingsManager }), settingsManager };
755
+ }
756
+ const { manager, settingsManager } = lastPackageManager;
757
+ const configuredPackages = manager.listConfiguredPackages();
758
+ const identities = new Map<string, { user?: typeof configuredPackages[number]; project?: typeof configuredPackages[number] }>();
759
+ for (const configured of configuredPackages) {
760
+ const npm = configured.source.startsWith("npm:");
761
+ const git = /^(?:git:|https?:\/\/|ssh:\/\/)/.test(configured.source.trim());
762
+ const userRoot = scope === "both" && configured.scope === "project" && (npm || git)
763
+ ? manager.getInstalledPath(configured.source, "user") : undefined;
764
+ const gitBase = configured.scope === "user" ? path.join(agentDir, "git") : path.join(projectRoot, ".pi", "git");
765
+ // Pi treats unparseable Git-like names as local paths, not managed Git identities.
766
+ const kind = npm ? "npm" : git && (
767
+ (configured.installedPath && pathIsInside(gitBase, configured.installedPath))
768
+ || (userRoot && pathIsInside(path.join(agentDir, "git"), userRoot))
769
+ ) ? "git" : "local";
770
+ // Let Pi normalize remote names/URLs and ignore versions/refs; local paths keep their declaring scope.
771
+ const root = kind === "local" ? configured.installedPath : userRoot ?? configured.installedPath;
772
+ if (!root) continue;
773
+ const identity = `${kind}:${fs.realpathSync(root)}`;
774
+ const entries = identities.get(identity) ?? {};
775
+ if (configured.scope === "project") entries.project = configured;
776
+ else entries.user ??= configured;
777
+ identities.set(identity, entries);
778
+ }
779
+ const selected = new Set<typeof configuredPackages[number]>();
780
+ for (const { user, project } of identities.values()) {
781
+ const declaration = settingsManager.getProjectSettings().packages?.findLast(pkg => (typeof pkg === "string" ? pkg : pkg.source) === project?.source);
782
+ const delta = typeof declaration === "object" && declaration.autoload === false;
783
+ const entry = delta && user ? user : project ?? user;
784
+ if (entry) selected.add(entry);
785
+ }
786
+ const dirs: string[] = [];
787
+ const seenDirs = new Set<string>();
788
+ for (const configured of configuredPackages.filter(entry => selected.has(entry))) {
789
+ if (scope !== "both" && configured.scope !== scope) continue;
790
+ if (configured.scope === "project" && options.projectTrusted === false) continue;
791
+ const root = configured.installedPath;
792
+ if (!root || !fs.existsSync(path.join(root, "package.json"))) continue;
793
+ const manifest = readSettingsFileStrict(path.join(root, "package.json"));
794
+ const subagents = manifest.subagents;
795
+ if (subagents === undefined) continue;
796
+ if (!subagents || typeof subagents !== "object" || !("agents" in subagents) || !Array.isArray(subagents.agents)) {
797
+ throw new Error(`Package '${root}' subagents.agents must be an array of relative directories.`);
798
+ }
799
+ const realRoot = fs.realpathSync(root);
800
+ for (const entry of subagents.agents) {
801
+ if (typeof entry !== "string" || !entry || path.isAbsolute(entry) || entry.split(/[\\/]/).includes("..")) {
802
+ throw new Error(`Package '${root}' subagents.agents must contain relative directories inside the package.`);
803
+ }
804
+ const dir = path.resolve(root, entry);
805
+ const realDir = isDirectory(dir) ? fs.realpathSync(dir) : undefined;
806
+ if (!realDir || !pathIsInside(realRoot, realDir)) {
807
+ throw new Error(`Package '${root}' subagents.agents directory is missing or outside the package: ${entry}`);
808
+ }
809
+ if (!seenDirs.has(realDir)) dirs.push(dir);
810
+ seenDirs.add(realDir);
811
+ }
812
+ }
813
+ clearAgentDiagnosticsForDirs(dirs);
814
+ return { dirs, agents: dirs.flatMap(dir => loadAgentsFromDir(dir, "package")) };
815
+ }
816
+
817
+ export function discoverAgents(cwd: string, scope: AgentScope, options: AgentDiscoveryOptions = {}): AgentDiscoveryResult {
818
+ const userDirOld = path.join(getAgentDir(), "agents");
819
+ const userDirNew = path.join(os.homedir(), ".agents");
820
+ const { readDirs: projectAgentDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(cwd, options);
821
+ const userSettingsPath = getUserAgentSettingsPath();
822
+ const projectSettingsPath = getProjectAgentSettingsPath(cwd, options);
823
+ const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
824
+ const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
825
+ clearAgentDiagnosticsForDirs([
826
+ ...(scope === "project" ? [] : [userDirOld, userDirNew]),
827
+ ...(scope === "user" ? [] : projectAgentDirs),
828
+ ]);
829
+
830
+ const builtinAgents = applyBuiltinOverrides(
831
+ loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
832
+ userSettings,
833
+ projectSettings,
834
+ projectSettingsPath,
835
+ );
836
+
837
+ const userAgentsOld = scope === "project" ? [] : loadAgentsFromDir(userDirOld, "user");
838
+ const userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user");
839
+ const userAgents = [...userAgentsOld, ...userAgentsNew];
840
+
841
+ const projectAgents = scope === "user" ? [] : projectAgentDirs.flatMap((dir) => loadAgentsFromDir(dir, "project"));
842
+ const { agents: packageAgents } = loadConfiguredPackageAgents(cwd, scope, options);
843
+ const agents = mergeAgentsForScope(scope, userAgents, projectAgents, [...builtinAgents, ...packageAgents])
844
+ .filter((agent) => agent.disabled !== true);
845
+
846
+ return { agents, projectAgentsDir };
847
+ }
848
+
849
+ export function discoverAgentsAll(cwd: string, options: AgentDiscoveryOptions = {}, scope: AgentScope = "both"): {
850
+ builtin: AgentConfig[];
851
+ package: AgentConfig[];
852
+ user: AgentConfig[];
853
+ project: AgentConfig[];
854
+ chains: ChainConfig[];
855
+ chainDiagnostics: ChainDiscoveryDiagnostic[];
856
+ agentDiagnostics: AgentDiscoveryDiagnostic[];
857
+ userDir: string;
858
+ projectDir: string | null;
859
+ userChainDir: string;
860
+ projectChainDir: string | null;
861
+ userSettingsPath: string;
862
+ projectSettingsPath: string | null;
863
+ } {
864
+ const userDirOld = path.join(getAgentDir(), "agents");
865
+ const userDirNew = path.join(os.homedir(), ".agents");
866
+ const userChainDir = getUserChainDir();
867
+ const { readDirs: projectDirs, preferredDir: projectDir } = resolveNearestProjectAgentDirs(cwd, options);
868
+ const { readDirs: projectChainDirs, preferredDir: projectChainDir } = resolveNearestProjectChainDirs(cwd, options);
869
+ const userSettingsPath = getUserAgentSettingsPath();
870
+ const projectSettingsPath = getProjectAgentSettingsPath(cwd, options);
871
+ const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
872
+ const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
873
+ clearAgentDiagnosticsForDirs([
874
+ ...(scope === "project" ? [] : [userDirOld, userDirNew]),
875
+ ...(scope === "user" ? [] : projectDirs),
876
+ ]);
877
+
878
+ const builtin = applyBuiltinOverrides(
879
+ loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
880
+ userSettings,
881
+ projectSettings,
882
+ projectSettingsPath,
883
+ );
884
+ const user = scope === "project" ? [] : [
885
+ ...loadAgentsFromDir(userDirOld, "user"),
886
+ ...loadAgentsFromDir(userDirNew, "user"),
887
+ ];
888
+ const project = (scope === "user" ? [] : projectDirs).flatMap((dir) => loadAgentsFromDir(dir, "project"));
889
+
890
+ const projectChains: ChainConfig[] = [];
891
+ const projectChainDiagnostics: ChainDiscoveryDiagnostic[] = [];
892
+ for (const dir of scope === "user" ? [] : projectChainDirs) {
893
+ const loaded = loadChainsFromDir(dir, "project");
894
+ projectChainDiagnostics.push(...loaded.diagnostics);
895
+ projectChains.push(...loaded.chains);
896
+ }
897
+ const userChains = scope === "project" ? { chains: [], diagnostics: [] } : loadChainsFromDir(userChainDir, "user");
898
+ const chains = [
899
+ ...userChains.chains,
900
+ ...projectChains,
901
+ ];
902
+ const chainDiagnostics = [
903
+ ...userChains.diagnostics,
904
+ ...projectChainDiagnostics,
905
+ ];
906
+ const packageProfiles = loadConfiguredPackageAgents(cwd, scope, options);
907
+ const agentDiagnostics = [
908
+ ...(scope === "project" ? [] : [...agentDiagnosticsForDir(userDirOld, "user"), ...agentDiagnosticsForDir(userDirNew, "user")]),
909
+ ...(scope === "user" ? [] : projectDirs.flatMap((dir) => agentDiagnosticsForDir(dir, "project"))),
910
+ ...packageProfiles.dirs.flatMap(dir => agentDiagnosticsForDir(dir, "package")),
911
+ ];
912
+
913
+ const userDir = userDirOld;
914
+
915
+ return { builtin, package: packageProfiles.agents, user, project, chains, chainDiagnostics, agentDiagnostics, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
916
+ }