@oh-my-pi/pi-coding-agent 8.1.0 → 8.2.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 (402) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/docs/session.md +111 -46
  3. package/examples/custom-tools/hello/index.ts +1 -1
  4. package/examples/custom-tools/todo/index.ts +3 -4
  5. package/examples/extensions/api-demo.ts +0 -1
  6. package/examples/extensions/chalk-logger.ts +2 -3
  7. package/examples/extensions/hello.ts +0 -1
  8. package/examples/extensions/pirate.ts +0 -1
  9. package/examples/extensions/plan-mode.ts +15 -16
  10. package/examples/extensions/todo.ts +3 -4
  11. package/examples/extensions/tools.ts +1 -2
  12. package/examples/extensions/with-deps/index.ts +0 -1
  13. package/examples/hooks/auto-commit-on-exit.ts +1 -2
  14. package/examples/hooks/confirm-destructive.ts +0 -1
  15. package/examples/hooks/custom-compaction.ts +1 -2
  16. package/examples/hooks/dirty-repo-guard.ts +0 -1
  17. package/examples/hooks/file-trigger.ts +3 -4
  18. package/examples/hooks/git-checkpoint.ts +0 -1
  19. package/examples/hooks/handoff.ts +3 -4
  20. package/examples/hooks/permission-gate.ts +1 -2
  21. package/examples/hooks/protected-paths.ts +1 -2
  22. package/examples/hooks/qna.ts +2 -3
  23. package/examples/hooks/snake.ts +4 -5
  24. package/examples/hooks/status-line.ts +0 -1
  25. package/examples/sdk/01-minimal.ts +2 -3
  26. package/examples/sdk/02-custom-model.ts +2 -3
  27. package/examples/sdk/03-custom-prompt.ts +3 -4
  28. package/examples/sdk/04-skills.ts +2 -3
  29. package/examples/sdk/06-extensions.ts +1 -2
  30. package/examples/sdk/06-hooks.ts +6 -7
  31. package/examples/sdk/07-context-files.ts +0 -1
  32. package/examples/sdk/08-prompt-templates.ts +0 -1
  33. package/examples/sdk/08-slash-commands.ts +0 -1
  34. package/examples/sdk/09-api-keys-and-oauth.ts +0 -1
  35. package/examples/sdk/10-settings.ts +0 -1
  36. package/examples/sdk/11-sessions.ts +0 -1
  37. package/package.json +51 -23
  38. package/scripts/format-prompts.ts +0 -1
  39. package/src/capability/context-file.ts +2 -3
  40. package/src/capability/extension-module.ts +2 -3
  41. package/src/capability/extension.ts +2 -3
  42. package/src/capability/fs.ts +20 -21
  43. package/src/capability/hook.ts +2 -3
  44. package/src/capability/index.ts +15 -16
  45. package/src/capability/instruction.ts +2 -3
  46. package/src/capability/mcp.ts +2 -3
  47. package/src/capability/prompt.ts +2 -3
  48. package/src/capability/rule.ts +2 -3
  49. package/src/capability/settings.ts +1 -2
  50. package/src/capability/skill.ts +2 -3
  51. package/src/capability/slash-command.ts +2 -3
  52. package/src/capability/ssh.ts +2 -3
  53. package/src/capability/system-prompt.ts +2 -3
  54. package/src/capability/tool.ts +2 -3
  55. package/src/cli/args.ts +5 -6
  56. package/src/cli/config-cli.ts +6 -7
  57. package/src/cli/file-processor.ts +19 -17
  58. package/src/cli/jupyter-cli.ts +105 -0
  59. package/src/cli/list-models.ts +10 -11
  60. package/src/cli/plugin-cli.ts +20 -21
  61. package/src/cli/session-picker.ts +2 -3
  62. package/src/cli/setup-cli.ts +2 -3
  63. package/src/cli/stats-cli.ts +2 -3
  64. package/src/cli/update-cli.ts +25 -22
  65. package/src/commit/agentic/agent.ts +21 -23
  66. package/src/commit/agentic/fallback.ts +9 -9
  67. package/src/commit/agentic/index.ts +30 -38
  68. package/src/commit/agentic/state.ts +1 -6
  69. package/src/commit/agentic/tools/analyze-file.ts +15 -15
  70. package/src/commit/agentic/tools/git-file-diff.ts +3 -3
  71. package/src/commit/agentic/tools/git-hunk.ts +7 -7
  72. package/src/commit/agentic/tools/git-overview.ts +5 -5
  73. package/src/commit/agentic/tools/index.ts +14 -14
  74. package/src/commit/agentic/tools/propose-changelog.ts +6 -6
  75. package/src/commit/agentic/tools/propose-commit.ts +8 -8
  76. package/src/commit/agentic/tools/recent-commits.ts +2 -2
  77. package/src/commit/agentic/tools/split-commit.ts +19 -23
  78. package/src/commit/agentic/topo-sort.ts +1 -1
  79. package/src/commit/agentic/trivial.ts +3 -3
  80. package/src/commit/agentic/validation.ts +12 -12
  81. package/src/commit/analysis/conventional.ts +7 -11
  82. package/src/commit/analysis/index.ts +4 -4
  83. package/src/commit/analysis/scope.ts +4 -4
  84. package/src/commit/analysis/summary.ts +7 -9
  85. package/src/commit/analysis/validation.ts +1 -1
  86. package/src/commit/changelog/detect.ts +6 -6
  87. package/src/commit/changelog/generate.ts +7 -9
  88. package/src/commit/changelog/index.ts +13 -13
  89. package/src/commit/changelog/parse.ts +2 -2
  90. package/src/commit/cli.ts +1 -1
  91. package/src/commit/git/diff.ts +3 -3
  92. package/src/commit/git/index.ts +19 -24
  93. package/src/commit/index.ts +1 -1
  94. package/src/commit/map-reduce/index.ts +9 -9
  95. package/src/commit/map-reduce/map-phase.ts +19 -34
  96. package/src/commit/map-reduce/reduce-phase.ts +9 -11
  97. package/src/commit/message.ts +2 -2
  98. package/src/commit/model-selection.ts +3 -7
  99. package/src/commit/pipeline.ts +20 -22
  100. package/src/commit/utils/exclusions.ts +3 -3
  101. package/src/config/file-lock.ts +17 -7
  102. package/src/config/keybindings.ts +6 -8
  103. package/src/config/model-registry.ts +55 -37
  104. package/src/config/model-resolver.ts +18 -19
  105. package/src/config/prompt-templates.ts +11 -11
  106. package/src/config/settings-manager.ts +50 -34
  107. package/src/config.ts +60 -62
  108. package/src/cursor.ts +11 -9
  109. package/src/discovery/agents-md.ts +11 -12
  110. package/src/discovery/builtin.ts +68 -73
  111. package/src/discovery/claude.ts +41 -42
  112. package/src/discovery/cline.ts +11 -12
  113. package/src/discovery/codex.ts +52 -53
  114. package/src/discovery/cursor.ts +9 -10
  115. package/src/discovery/gemini.ts +17 -22
  116. package/src/discovery/github.ts +13 -14
  117. package/src/discovery/helpers.ts +35 -34
  118. package/src/discovery/index.ts +16 -18
  119. package/src/discovery/mcp-json.ts +8 -9
  120. package/src/discovery/ssh.ts +8 -9
  121. package/src/discovery/vscode.ts +4 -5
  122. package/src/discovery/windsurf.ts +6 -7
  123. package/src/exa/company.ts +1 -2
  124. package/src/exa/index.ts +2 -3
  125. package/src/exa/linkedin.ts +1 -2
  126. package/src/exa/mcp-client.ts +14 -16
  127. package/src/exa/render.ts +10 -11
  128. package/src/exa/researcher.ts +1 -2
  129. package/src/exa/search.ts +1 -2
  130. package/src/exa/types.ts +0 -1
  131. package/src/exa/websets.ts +1 -2
  132. package/src/exec/bash-executor.ts +3 -4
  133. package/src/exec/exec.ts +0 -1
  134. package/src/export/custom-share.ts +5 -6
  135. package/src/export/html/index.ts +24 -21
  136. package/src/export/ttsr.ts +2 -3
  137. package/src/extensibility/custom-commands/bundled/review/index.ts +7 -8
  138. package/src/extensibility/custom-commands/loader.ts +17 -14
  139. package/src/extensibility/custom-commands/types.ts +1 -2
  140. package/src/extensibility/custom-tools/loader.ts +10 -11
  141. package/src/extensibility/custom-tools/types.ts +6 -7
  142. package/src/extensibility/custom-tools/wrapper.ts +2 -3
  143. package/src/extensibility/extensions/loader.ts +75 -53
  144. package/src/extensibility/extensions/runner.ts +11 -12
  145. package/src/extensibility/extensions/types.ts +19 -26
  146. package/src/extensibility/extensions/wrapper.ts +3 -4
  147. package/src/extensibility/hooks/index.ts +1 -1
  148. package/src/extensibility/hooks/loader.ts +8 -9
  149. package/src/extensibility/hooks/runner.ts +7 -8
  150. package/src/extensibility/hooks/tool-wrapper.ts +0 -1
  151. package/src/extensibility/hooks/types.ts +10 -17
  152. package/src/extensibility/plugins/doctor.ts +3 -3
  153. package/src/extensibility/plugins/installer.ts +27 -27
  154. package/src/extensibility/plugins/loader.ts +59 -56
  155. package/src/extensibility/plugins/manager.ts +211 -171
  156. package/src/extensibility/plugins/parser.ts +1 -1
  157. package/src/extensibility/plugins/paths.ts +8 -8
  158. package/src/extensibility/skills.ts +63 -60
  159. package/src/extensibility/slash-commands.ts +10 -10
  160. package/src/index.ts +46 -46
  161. package/src/internal-urls/agent-protocol.ts +21 -11
  162. package/src/internal-urls/artifact-protocol.ts +17 -13
  163. package/src/internal-urls/router.ts +1 -2
  164. package/src/internal-urls/rule-protocol.ts +3 -4
  165. package/src/internal-urls/skill-protocol.ts +3 -4
  166. package/src/ipy/executor.ts +14 -10
  167. package/src/ipy/gateway-coordinator.ts +79 -90
  168. package/src/ipy/kernel.ts +32 -30
  169. package/src/ipy/modules.ts +13 -13
  170. package/src/lsp/client.ts +21 -10
  171. package/src/lsp/clients/biome-client.ts +1 -2
  172. package/src/lsp/clients/index.ts +3 -3
  173. package/src/lsp/clients/lsp-linter-client.ts +4 -5
  174. package/src/lsp/config.ts +15 -15
  175. package/src/lsp/edits.ts +4 -5
  176. package/src/lsp/index.ts +43 -44
  177. package/src/lsp/lspmux.ts +8 -8
  178. package/src/lsp/render.ts +10 -16
  179. package/src/lsp/utils.ts +3 -3
  180. package/src/main.ts +55 -34
  181. package/src/mcp/client.ts +2 -3
  182. package/src/mcp/config.ts +5 -6
  183. package/src/mcp/json-rpc.ts +0 -1
  184. package/src/mcp/loader.ts +3 -4
  185. package/src/mcp/manager.ts +17 -18
  186. package/src/mcp/tool-bridge.ts +4 -9
  187. package/src/mcp/tool-cache.ts +2 -3
  188. package/src/mcp/transports/http.ts +2 -4
  189. package/src/mcp/transports/stdio.ts +1 -2
  190. package/src/migrations.ts +60 -49
  191. package/src/modes/components/armin.ts +4 -5
  192. package/src/modes/components/assistant-message.ts +6 -6
  193. package/src/modes/components/bash-execution.ts +7 -8
  194. package/src/modes/components/bordered-loader.ts +3 -3
  195. package/src/modes/components/branch-summary-message.ts +3 -3
  196. package/src/modes/components/compaction-summary-message.ts +3 -3
  197. package/src/modes/components/countdown-timer.ts +0 -1
  198. package/src/modes/components/custom-message.ts +5 -5
  199. package/src/modes/components/diff.ts +1 -1
  200. package/src/modes/components/dynamic-border.ts +2 -2
  201. package/src/modes/components/extensions/extension-dashboard.ts +6 -7
  202. package/src/modes/components/extensions/extension-list.ts +2 -3
  203. package/src/modes/components/extensions/inspector-panel.ts +3 -4
  204. package/src/modes/components/extensions/state-manager.ts +25 -26
  205. package/src/modes/components/extensions/types.ts +1 -2
  206. package/src/modes/components/footer.ts +47 -43
  207. package/src/modes/components/history-search.ts +2 -2
  208. package/src/modes/components/hook-editor.ts +3 -4
  209. package/src/modes/components/hook-input.ts +2 -3
  210. package/src/modes/components/hook-message.ts +5 -5
  211. package/src/modes/components/hook-selector.ts +2 -3
  212. package/src/modes/components/keybinding-hints.ts +2 -3
  213. package/src/modes/components/login-dialog.ts +2 -2
  214. package/src/modes/components/model-selector.ts +12 -12
  215. package/src/modes/components/oauth-selector.ts +2 -2
  216. package/src/modes/components/plugin-settings.ts +20 -20
  217. package/src/modes/components/python-execution.ts +7 -8
  218. package/src/modes/components/queue-mode-selector.ts +3 -3
  219. package/src/modes/components/read-tool-group.ts +2 -2
  220. package/src/modes/components/session-selector.ts +4 -4
  221. package/src/modes/components/settings-defs.ts +77 -69
  222. package/src/modes/components/settings-selector.ts +16 -16
  223. package/src/modes/components/show-images-selector.ts +2 -2
  224. package/src/modes/components/status-line/segments.ts +4 -4
  225. package/src/modes/components/status-line/separators.ts +1 -1
  226. package/src/modes/components/status-line/types.ts +2 -2
  227. package/src/modes/components/status-line-segment-editor.ts +7 -8
  228. package/src/modes/components/status-line.ts +12 -12
  229. package/src/modes/components/theme-selector.ts +8 -7
  230. package/src/modes/components/thinking-selector.ts +4 -4
  231. package/src/modes/components/todo-display.ts +2 -2
  232. package/src/modes/components/todo-reminder.ts +4 -4
  233. package/src/modes/components/tool-execution.ts +11 -16
  234. package/src/modes/components/tree-selector.ts +11 -11
  235. package/src/modes/components/ttsr-notification.ts +5 -5
  236. package/src/modes/components/user-message-selector.ts +1 -1
  237. package/src/modes/components/user-message.ts +1 -1
  238. package/src/modes/components/visual-truncate.ts +0 -1
  239. package/src/modes/components/welcome.ts +4 -4
  240. package/src/modes/controllers/command-controller.ts +46 -47
  241. package/src/modes/controllers/event-controller.ts +16 -20
  242. package/src/modes/controllers/extension-ui-controller.ts +40 -46
  243. package/src/modes/controllers/input-controller.ts +17 -18
  244. package/src/modes/controllers/selector-controller.ts +103 -91
  245. package/src/modes/index.ts +3 -3
  246. package/src/modes/interactive-mode.ts +27 -29
  247. package/src/modes/print-mode.ts +12 -13
  248. package/src/modes/rpc/rpc-client.ts +7 -8
  249. package/src/modes/rpc/rpc-mode.ts +24 -25
  250. package/src/modes/rpc/rpc-types.ts +3 -4
  251. package/src/modes/theme/mermaid-cache.ts +2 -2
  252. package/src/modes/theme/theme.ts +128 -53
  253. package/src/modes/types.ts +10 -10
  254. package/src/modes/utils/ui-helpers.ts +17 -17
  255. package/src/patch/applicator.ts +18 -19
  256. package/src/patch/diff.ts +1 -2
  257. package/src/patch/fuzzy.ts +1 -2
  258. package/src/patch/index.ts +10 -11
  259. package/src/patch/normalize.ts +4 -4
  260. package/src/patch/normative.ts +1 -2
  261. package/src/patch/parser.ts +8 -9
  262. package/src/patch/shared.ts +12 -13
  263. package/src/sdk.ts +60 -63
  264. package/src/session/agent-session.ts +83 -84
  265. package/src/session/agent-storage.ts +11 -11
  266. package/src/session/artifacts.ts +8 -9
  267. package/src/session/auth-storage.ts +25 -29
  268. package/src/session/compaction/branch-summarization.ts +7 -10
  269. package/src/session/compaction/compaction.ts +8 -19
  270. package/src/session/compaction/utils.ts +6 -9
  271. package/src/session/history-storage.ts +10 -10
  272. package/src/session/messages.ts +4 -5
  273. package/src/session/session-manager.ts +76 -65
  274. package/src/session/session-storage.ts +57 -69
  275. package/src/session/storage-migration.ts +2 -3
  276. package/src/session/streaming-output.ts +2 -2
  277. package/src/ssh/connection-manager.ts +43 -50
  278. package/src/ssh/ssh-executor.ts +2 -2
  279. package/src/ssh/sshfs-mount.ts +11 -18
  280. package/src/system-prompt.ts +27 -34
  281. package/src/task/agents.ts +45 -30
  282. package/src/task/commands.ts +6 -7
  283. package/src/task/discovery.ts +39 -76
  284. package/src/task/executor.ts +14 -15
  285. package/src/task/index.ts +33 -36
  286. package/src/task/output-manager.ts +3 -4
  287. package/src/task/parallel.ts +0 -1
  288. package/src/task/render.ts +19 -20
  289. package/src/task/subprocess-tool-registry.ts +1 -2
  290. package/src/task/worker-protocol.ts +3 -3
  291. package/src/task/worker.ts +32 -38
  292. package/src/task/worktree.ts +19 -19
  293. package/src/tools/ask.ts +8 -9
  294. package/src/tools/bash-interceptor.ts +1 -5
  295. package/src/tools/bash.ts +19 -18
  296. package/src/tools/calculator.ts +12 -12
  297. package/src/tools/complete.ts +3 -4
  298. package/src/tools/context.ts +2 -2
  299. package/src/tools/fetch.ts +23 -26
  300. package/src/tools/find.ts +15 -16
  301. package/src/tools/gemini-image.ts +14 -14
  302. package/src/tools/grep.ts +27 -27
  303. package/src/tools/index.ts +78 -56
  304. package/src/tools/list-limit.ts +1 -1
  305. package/src/tools/ls.ts +7 -7
  306. package/src/tools/notebook.ts +5 -5
  307. package/src/tools/output-meta.ts +3 -4
  308. package/src/tools/output-utils.ts +1 -1
  309. package/src/tools/path-utils.ts +5 -5
  310. package/src/tools/python.ts +36 -37
  311. package/src/tools/read.ts +23 -23
  312. package/src/tools/render-utils.ts +8 -9
  313. package/src/tools/renderers.ts +6 -7
  314. package/src/tools/review.ts +8 -11
  315. package/src/tools/ssh.ts +31 -30
  316. package/src/tools/todo-write.ts +13 -13
  317. package/src/tools/tool-errors.ts +3 -3
  318. package/src/tools/tool-result.ts +3 -8
  319. package/src/tools/write.ts +11 -16
  320. package/src/tui/code-cell.ts +3 -9
  321. package/src/tui/file-list.ts +3 -4
  322. package/src/tui/output-block.ts +1 -2
  323. package/src/tui/status-line.ts +2 -3
  324. package/src/tui/tree-list.ts +2 -3
  325. package/src/tui/types.ts +1 -2
  326. package/src/tui/utils.ts +2 -3
  327. package/src/utils/changelog.ts +9 -10
  328. package/src/utils/clipboard.ts +11 -11
  329. package/src/utils/file-mentions.ts +4 -10
  330. package/src/utils/frontmatter.ts +6 -3
  331. package/src/utils/fuzzy.ts +2 -2
  332. package/src/utils/image-convert.ts +1 -1
  333. package/src/utils/image-resize.ts +1 -1
  334. package/src/utils/mime.ts +2 -2
  335. package/src/utils/shell-snapshot.ts +11 -13
  336. package/src/utils/shell.ts +4 -5
  337. package/src/utils/title-generator.ts +8 -9
  338. package/src/utils/tools-manager.ts +23 -23
  339. package/src/vendor/photon/index.js +1099 -1059
  340. package/src/vendor/photon/photon_rs_bg.wasm +0 -0
  341. package/src/web/scrapers/artifacthub.ts +1 -1
  342. package/src/web/scrapers/arxiv.ts +2 -2
  343. package/src/web/scrapers/bluesky.ts +2 -2
  344. package/src/web/scrapers/cheatsh.ts +1 -1
  345. package/src/web/scrapers/chocolatey.ts +2 -2
  346. package/src/web/scrapers/choosealicense.ts +5 -5
  347. package/src/web/scrapers/cisa-kev.ts +1 -1
  348. package/src/web/scrapers/crossref.ts +2 -2
  349. package/src/web/scrapers/devto.ts +3 -3
  350. package/src/web/scrapers/discogs.ts +3 -4
  351. package/src/web/scrapers/discourse.ts +1 -1
  352. package/src/web/scrapers/dockerhub.ts +1 -1
  353. package/src/web/scrapers/fdroid.ts +2 -2
  354. package/src/web/scrapers/firefox-addons.ts +3 -3
  355. package/src/web/scrapers/flathub.ts +1 -1
  356. package/src/web/scrapers/github.ts +3 -3
  357. package/src/web/scrapers/gitlab.ts +4 -4
  358. package/src/web/scrapers/hackernews.ts +2 -2
  359. package/src/web/scrapers/huggingface.ts +1 -1
  360. package/src/web/scrapers/iacr.ts +2 -2
  361. package/src/web/scrapers/index.ts +0 -1
  362. package/src/web/scrapers/jetbrains-marketplace.ts +1 -1
  363. package/src/web/scrapers/lemmy.ts +2 -2
  364. package/src/web/scrapers/maven.ts +2 -2
  365. package/src/web/scrapers/mdn.ts +2 -4
  366. package/src/web/scrapers/metacpan.ts +2 -2
  367. package/src/web/scrapers/musicbrainz.ts +1 -2
  368. package/src/web/scrapers/npm.ts +1 -1
  369. package/src/web/scrapers/nuget.ts +2 -2
  370. package/src/web/scrapers/nvd.ts +3 -3
  371. package/src/web/scrapers/ollama.ts +7 -9
  372. package/src/web/scrapers/opencorporates.ts +2 -2
  373. package/src/web/scrapers/openlibrary.ts +6 -6
  374. package/src/web/scrapers/orcid.ts +0 -1
  375. package/src/web/scrapers/osv.ts +2 -2
  376. package/src/web/scrapers/packagist.ts +1 -1
  377. package/src/web/scrapers/pubmed.ts +1 -2
  378. package/src/web/scrapers/rawg.ts +2 -2
  379. package/src/web/scrapers/readthedocs.ts +1 -2
  380. package/src/web/scrapers/repology.ts +2 -2
  381. package/src/web/scrapers/rfc.ts +1 -1
  382. package/src/web/scrapers/searchcode.ts +2 -2
  383. package/src/web/scrapers/semantic-scholar.ts +1 -1
  384. package/src/web/scrapers/snapcraft.ts +2 -2
  385. package/src/web/scrapers/sourcegraph.ts +1 -1
  386. package/src/web/scrapers/spdx.ts +3 -3
  387. package/src/web/scrapers/spotify.ts +0 -1
  388. package/src/web/scrapers/twitter.ts +1 -1
  389. package/src/web/scrapers/types.ts +1 -2
  390. package/src/web/scrapers/utils.ts +5 -5
  391. package/src/web/scrapers/wikidata.ts +3 -3
  392. package/src/web/scrapers/youtube.ts +9 -14
  393. package/src/web/search/auth.ts +4 -9
  394. package/src/web/search/index.ts +11 -21
  395. package/src/web/search/providers/anthropic.ts +3 -9
  396. package/src/web/search/providers/exa.ts +6 -10
  397. package/src/web/search/providers/perplexity.ts +5 -5
  398. package/src/web/search/render.ts +16 -18
  399. package/scripts/generate-wasm-b64.ts +0 -24
  400. package/src/commit/map-reduce/.map-phase.ts.kate-swp +0 -0
  401. package/src/task/.executor.ts.kate-swp +0 -0
  402. package/src/vendor/photon/photon_rs_bg.wasm.b64.js +0 -1
@@ -1,9 +1,9 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- import { CONFIG_DIR_NAME } from "@oh-my-pi/pi-coding-agent/config";
5
- import { logger } from "@oh-my-pi/pi-utils";
1
+ import * as fs from "node:fs/promises";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { isEnoent, logger } from "@oh-my-pi/pi-utils";
6
5
  import { $ } from "bun";
6
+ import { CONFIG_DIR_NAME } from "../config";
7
7
 
8
8
  export interface SSHConnectionTarget {
9
9
  name: string;
@@ -25,52 +25,44 @@ export interface SSHHostInfo {
25
25
  compatEnabled: boolean;
26
26
  }
27
27
 
28
- const CONTROL_DIR = join(homedir(), CONFIG_DIR_NAME, "ssh-control");
29
- const CONTROL_PATH = join(CONTROL_DIR, "%h.sock");
30
- const HOST_INFO_DIR = join(homedir(), CONFIG_DIR_NAME, "remote-host");
28
+ const CONTROL_DIR = path.join(os.homedir(), CONFIG_DIR_NAME, "ssh-control");
29
+ const CONTROL_PATH = path.join(CONTROL_DIR, "%h.sock");
30
+ const HOST_INFO_DIR = path.join(os.homedir(), CONFIG_DIR_NAME, "remote-host");
31
31
  const HOST_INFO_VERSION = 2;
32
32
 
33
33
  const activeHosts = new Map<string, SSHConnectionTarget>();
34
34
  const pendingConnections = new Map<string, Promise<void>>();
35
35
  const hostInfoCache = new Map<string, SSHHostInfo>();
36
36
 
37
- function ensureControlDir(): void {
38
- if (!existsSync(CONTROL_DIR)) {
39
- mkdirSync(CONTROL_DIR, { recursive: true, mode: 0o700 });
40
- }
37
+ async function ensureControlDir(): Promise<void> {
38
+ await fs.mkdir(CONTROL_DIR, { recursive: true, mode: 0o700 });
41
39
  try {
42
- chmodSync(CONTROL_DIR, 0o700);
40
+ await fs.chmod(CONTROL_DIR, 0o700);
43
41
  } catch (err) {
44
42
  logger.debug("SSH control dir chmod failed", { path: CONTROL_DIR, error: String(err) });
45
43
  }
46
44
  }
47
45
 
48
- function ensureHostInfoDir(): void {
49
- if (!existsSync(HOST_INFO_DIR)) {
50
- mkdirSync(HOST_INFO_DIR, { recursive: true, mode: 0o700 });
51
- }
52
- try {
53
- chmodSync(HOST_INFO_DIR, 0o700);
54
- } catch (err) {
55
- logger.debug("SSH host info dir chmod failed", { path: HOST_INFO_DIR, error: String(err) });
56
- }
57
- }
58
-
59
46
  function sanitizeHostName(name: string): string {
60
47
  const sanitized = name.replace(/[^a-zA-Z0-9._-]+/g, "_");
61
48
  return sanitized.length > 0 ? sanitized : "host";
62
49
  }
63
50
 
64
51
  function getHostInfoPath(name: string): string {
65
- return join(HOST_INFO_DIR, `${sanitizeHostName(name)}.json`);
52
+ return path.join(HOST_INFO_DIR, `${sanitizeHostName(name)}.json`);
66
53
  }
67
54
 
68
- function validateKeyPermissions(keyPath?: string): void {
55
+ async function validateKeyPermissions(keyPath?: string): Promise<void> {
69
56
  if (!keyPath) return;
70
- if (!existsSync(keyPath)) {
71
- throw new Error(`SSH key not found: ${keyPath}`);
57
+ let stats: Awaited<ReturnType<typeof fs.stat>>;
58
+ try {
59
+ stats = await fs.stat(keyPath);
60
+ } catch (err) {
61
+ if (isEnoent(err)) {
62
+ throw new Error(`SSH key not found: ${keyPath}`);
63
+ }
64
+ throw err;
72
65
  }
73
- const stats = statSync(keyPath);
74
66
  if (!stats.isFile()) {
75
67
  throw new Error(`SSH key is not a file: ${keyPath}`);
76
68
  }
@@ -208,31 +200,31 @@ function shouldRefreshHostInfo(host: SSHConnectionTarget, info: SSHHostInfo): bo
208
200
  return false;
209
201
  }
210
202
 
211
- function loadHostInfoFromDisk(host: SSHConnectionTarget): SSHHostInfo | undefined {
203
+ async function loadHostInfoFromDisk(host: SSHConnectionTarget): Promise<SSHHostInfo | undefined> {
212
204
  const path = getHostInfoPath(host.name);
213
- if (!existsSync(path)) return undefined;
214
205
  try {
215
- const raw = readFileSync(path, "utf-8");
206
+ const raw = await fs.readFile(path, "utf-8");
216
207
  const parsed = parseHostInfo(JSON.parse(raw));
217
208
  if (!parsed) return undefined;
218
209
  const resolved = applyCompatOverride(host, parsed);
219
210
  hostInfoCache.set(host.name, resolved);
220
211
  return resolved;
221
212
  } catch (err) {
213
+ if (isEnoent(err)) return undefined;
222
214
  logger.warn("Failed to load SSH host info", { host: host.name, error: String(err) });
223
215
  return undefined;
224
216
  }
225
217
  }
226
218
 
227
- function loadHostInfoFromDiskByName(hostName: string): SSHHostInfo | undefined {
219
+ async function loadHostInfoFromDiskByName(hostName: string): Promise<SSHHostInfo | undefined> {
228
220
  const path = getHostInfoPath(hostName);
229
- if (!existsSync(path)) return undefined;
230
221
  try {
231
- const raw = readFileSync(path, "utf-8");
222
+ const raw = await fs.readFile(path, "utf-8");
232
223
  const parsed = parseHostInfo(JSON.parse(raw));
233
224
  if (!parsed) return undefined;
234
225
  return parsed;
235
226
  } catch (err) {
227
+ if (isEnoent(err)) return undefined;
236
228
  logger.warn("Failed to load SSH host info", { host: hostName, error: String(err) });
237
229
  return undefined;
238
230
  }
@@ -240,7 +232,6 @@ function loadHostInfoFromDiskByName(hostName: string): SSHHostInfo | undefined {
240
232
 
241
233
  async function persistHostInfo(host: SSHConnectionTarget, info: SSHHostInfo): Promise<void> {
242
234
  try {
243
- ensureHostInfoDir();
244
235
  const path = getHostInfoPath(host.name);
245
236
  const payload = { ...info, version: HOST_INFO_VERSION };
246
237
  hostInfoCache.set(host.name, payload);
@@ -252,7 +243,7 @@ async function persistHostInfo(host: SSHConnectionTarget, info: SSHHostInfo): Pr
252
243
 
253
244
  async function probeHostInfo(host: SSHConnectionTarget): Promise<SSHHostInfo> {
254
245
  const command = 'echo "$OSTYPE|$SHELL|$BASH_VERSION" 2>/dev/null || echo "%OS%|%COMSPEC%|"';
255
- const result = await runSshCaptureSync(buildRemoteCommand(host, command));
246
+ const result = await runSshCaptureSync(await buildRemoteCommand(host, command));
256
247
  if (result.exitCode !== 0 && !result.stdout) {
257
248
  logger.debug("SSH host probe failed", { host: host.name, error: result.stderr });
258
249
  const fallback: SSHHostInfo = {
@@ -315,11 +306,11 @@ async function probeHostInfo(host: SSHConnectionTarget): Promise<SSHHostInfo> {
315
306
  const hasBash = !unexpandedPosixVars && (Boolean(bashVersion) || shell === "bash");
316
307
  let compatShell: SSHHostInfo["compatShell"];
317
308
  if (os === "windows" && host.compat !== false) {
318
- const bashProbe = await runSshCaptureSync(buildRemoteCommand(host, 'bash -lc "echo OMP_BASH_OK"'));
309
+ const bashProbe = await runSshCaptureSync(await buildRemoteCommand(host, 'bash -lc "echo OMP_BASH_OK"'));
319
310
  if (bashProbe.exitCode === 0 && bashProbe.stdout.includes("OMP_BASH_OK")) {
320
311
  compatShell = "bash";
321
312
  } else {
322
- const shProbe = await runSshCaptureSync(buildRemoteCommand(host, 'sh -lc "echo OMP_SH_OK"'));
313
+ const shProbe = await runSshCaptureSync(await buildRemoteCommand(host, 'sh -lc "echo OMP_SH_OK"'));
323
314
  if (shProbe.exitCode === 0 && shProbe.stdout.includes("OMP_SH_OK")) {
324
315
  compatShell = "sh";
325
316
  }
@@ -344,18 +335,20 @@ async function probeHostInfo(host: SSHConnectionTarget): Promise<SSHHostInfo> {
344
335
  return info;
345
336
  }
346
337
 
347
- export function getHostInfo(hostName: string): SSHHostInfo | undefined {
348
- return hostInfoCache.get(hostName) ?? loadHostInfoFromDiskByName(hostName);
338
+ export async function getHostInfo(hostName: string): Promise<SSHHostInfo | undefined> {
339
+ const cached = hostInfoCache.get(hostName);
340
+ if (cached) return cached;
341
+ return loadHostInfoFromDiskByName(hostName);
349
342
  }
350
343
 
351
- export function getHostInfoForHost(host: SSHConnectionTarget): SSHHostInfo | undefined {
344
+ export async function getHostInfoForHost(host: SSHConnectionTarget): Promise<SSHHostInfo | undefined> {
352
345
  const cached = hostInfoCache.get(host.name);
353
346
  if (cached) {
354
347
  const resolved = applyCompatOverride(host, cached);
355
348
  if (resolved !== cached) hostInfoCache.set(host.name, resolved);
356
349
  return resolved;
357
350
  }
358
- return loadHostInfoFromDisk(host);
351
+ return await loadHostInfoFromDisk(host);
359
352
  }
360
353
 
361
354
  export async function ensureHostInfo(host: SSHConnectionTarget): Promise<SSHHostInfo> {
@@ -365,7 +358,7 @@ export async function ensureHostInfo(host: SSHConnectionTarget): Promise<SSHHost
365
358
  hostInfoCache.set(host.name, resolved);
366
359
  if (!shouldRefreshHostInfo(host, resolved)) return resolved;
367
360
  }
368
- const fromDisk = loadHostInfoFromDisk(host);
361
+ const fromDisk = await loadHostInfoFromDisk(host);
369
362
  if (fromDisk && !shouldRefreshHostInfo(host, fromDisk)) return fromDisk;
370
363
  await ensureConnection(host);
371
364
  const current = hostInfoCache.get(host.name);
@@ -373,8 +366,8 @@ export async function ensureHostInfo(host: SSHConnectionTarget): Promise<SSHHost
373
366
  return probeHostInfo(host);
374
367
  }
375
368
 
376
- export function buildRemoteCommand(host: SSHConnectionTarget, command: string): string[] {
377
- validateKeyPermissions(host.keyPath);
369
+ export async function buildRemoteCommand(host: SSHConnectionTarget, command: string): Promise<string[]> {
370
+ await validateKeyPermissions(host.keyPath);
378
371
  return [...buildCommonArgs(host), buildSshTarget(host), command];
379
372
  }
380
373
 
@@ -388,14 +381,14 @@ export async function ensureConnection(host: SSHConnectionTarget): Promise<void>
388
381
 
389
382
  const promise = (async () => {
390
383
  ensureSshBinary();
391
- ensureControlDir();
392
- validateKeyPermissions(host.keyPath);
384
+ await ensureControlDir();
385
+ await validateKeyPermissions(host.keyPath);
393
386
 
394
387
  const target = buildSshTarget(host);
395
388
  const check = await runSshSync(["-O", "check", ...buildCommonArgs(host), target]);
396
389
  if (check.exitCode === 0) {
397
390
  activeHosts.set(key, host);
398
- if (!hostInfoCache.has(key) && !loadHostInfoFromDisk(host)) {
391
+ if (!hostInfoCache.has(key) && !(await loadHostInfoFromDisk(host))) {
399
392
  await probeHostInfo(host);
400
393
  }
401
394
  return;
@@ -408,7 +401,7 @@ export async function ensureConnection(host: SSHConnectionTarget): Promise<void>
408
401
  }
409
402
 
410
403
  activeHosts.set(key, host);
411
- if (!hostInfoCache.has(key) && !loadHostInfoFromDisk(host)) {
404
+ if (!hostInfoCache.has(key) && !(await loadHostInfoFromDisk(host))) {
412
405
  await probeHostInfo(host);
413
406
  }
414
407
  })();
@@ -1,5 +1,5 @@
1
- import { OutputSink } from "@oh-my-pi/pi-coding-agent/session/streaming-output";
2
1
  import { cspawn, logger, ptree } from "@oh-my-pi/pi-utils";
2
+ import { OutputSink } from "../session/streaming-output";
3
3
  import { buildRemoteCommand, ensureConnection, ensureHostInfo, type SSHConnectionTarget } from "./connection-manager";
4
4
  import { hasSshfs, mountRemote } from "./sshfs-mount";
5
5
 
@@ -76,7 +76,7 @@ export async function executeSSH(
76
76
  }
77
77
  }
78
78
 
79
- const child = cspawn(["ssh", ...buildRemoteCommand(host, resolvedCommand)], {
79
+ const child = cspawn(["ssh", ...(await buildRemoteCommand(host, resolvedCommand))], {
80
80
  signal: options?.signal,
81
81
  timeout: options?.timeout,
82
82
  });
@@ -1,25 +1,21 @@
1
- import { chmodSync, existsSync, mkdirSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- import { CONFIG_DIR_NAME } from "@oh-my-pi/pi-coding-agent/config";
5
- import { logger } from "@oh-my-pi/pi-utils";
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
6
4
  import { $ } from "bun";
5
+ import { CONFIG_DIR_NAME } from "../config";
7
6
  import { getControlDir, getControlPathTemplate, type SSHConnectionTarget } from "./connection-manager";
8
7
 
9
- const REMOTE_DIR = join(homedir(), CONFIG_DIR_NAME, "remote");
8
+ const REMOTE_DIR = path.join(os.homedir(), CONFIG_DIR_NAME, "remote");
10
9
  const CONTROL_DIR = getControlDir();
11
10
  const CONTROL_PATH = getControlPathTemplate();
12
11
 
13
12
  const mountedPaths = new Set<string>();
14
13
 
15
- function ensureDir(path: string, mode = 0o700): void {
16
- if (!existsSync(path)) {
17
- mkdirSync(path, { recursive: true, mode });
18
- }
14
+ async function ensureDir(path: string, mode = 0o700): Promise<void> {
19
15
  try {
20
- chmodSync(path, mode);
21
- } catch (err) {
22
- logger.debug("SSHFS dir chmod failed", { path, error: String(err) });
16
+ await fs.promises.mkdir(path, { recursive: true, mode });
17
+ } catch {
18
+ await fs.promises.chmod(path, mode).catch(e => void e);
23
19
  }
24
20
  }
25
21
 
@@ -30,7 +26,7 @@ function getMountName(host: SSHConnectionTarget): string {
30
26
  }
31
27
 
32
28
  function getMountPath(host: SSHConnectionTarget): string {
33
- return join(REMOTE_DIR, getMountName(host));
29
+ return path.join(REMOTE_DIR, getMountName(host));
34
30
  }
35
31
 
36
32
  function buildSshTarget(host: SSHConnectionTarget): string {
@@ -95,11 +91,8 @@ export async function isMounted(path: string): Promise<boolean> {
95
91
  export async function mountRemote(host: SSHConnectionTarget, remotePath = "/"): Promise<string | undefined> {
96
92
  if (!hasSshfs()) return undefined;
97
93
 
98
- ensureDir(REMOTE_DIR);
99
- ensureDir(CONTROL_DIR);
100
-
101
94
  const mountPath = getMountPath(host);
102
- ensureDir(mountPath);
95
+ await Promise.all([ensureDir(REMOTE_DIR), ensureDir(CONTROL_DIR), ensureDir(mountPath)]);
103
96
 
104
97
  if (await isMounted(mountPath)) {
105
98
  mountedPaths.add(mountPath);
@@ -1,26 +1,18 @@
1
1
  /**
2
2
  * System prompt construction and project context loading
3
3
  */
4
-
5
- import { existsSync } from "node:fs";
6
- import { homedir } from "node:os";
7
- import { join } from "node:path";
8
- import { contextFileCapability } from "@oh-my-pi/pi-coding-agent/capability/context-file";
9
- import { systemPromptCapability } from "@oh-my-pi/pi-coding-agent/capability/system-prompt";
10
- import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
11
- import type { SkillsSettings } from "@oh-my-pi/pi-coding-agent/config/settings-manager";
12
- import {
13
- type ContextFile,
14
- loadCapability,
15
- type SystemPrompt as SystemPromptFile,
16
- } from "@oh-my-pi/pi-coding-agent/discovery";
17
- import { loadSkills, type Skill } from "@oh-my-pi/pi-coding-agent/extensibility/skills";
18
- import customSystemPromptTemplate from "@oh-my-pi/pi-coding-agent/prompts/system/custom-system-prompt.md" with {
19
- type: "text",
20
- };
21
- import systemPromptTemplate from "@oh-my-pi/pi-coding-agent/prompts/system/system-prompt.md" with { type: "text" };
4
+ import * as os from "node:os";
5
+ import * as path from "node:path";
22
6
  import { $ } from "bun";
23
7
  import chalk from "chalk";
8
+ import { contextFileCapability } from "./capability/context-file";
9
+ import { systemPromptCapability } from "./capability/system-prompt";
10
+ import { renderPromptTemplate } from "./config/prompt-templates";
11
+ import type { SkillsSettings } from "./config/settings-manager";
12
+ import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
13
+ import { loadSkills, type Skill } from "./extensibility/skills";
14
+ import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
15
+ import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
24
16
  import type { ToolName } from "./tools";
25
17
 
26
18
  interface GitContext {
@@ -42,7 +34,7 @@ export async function loadGitContext(cwd: string): Promise<GitContext | null> {
42
34
  .quiet()
43
35
  .text()
44
36
  .catch(() => null)
45
- .then((text) => text?.trim() ?? null);
37
+ .then(text => text?.trim() ?? null);
46
38
 
47
39
  // Check if inside a git repo
48
40
  const isGitRepo = await git("rev-parse", "--is-inside-work-tree");
@@ -86,7 +78,7 @@ function firstNonEmptyLine(value: string | null): string | null {
86
78
  if (!value) return null;
87
79
  const line = value
88
80
  .split("\n")
89
- .map((entry) => entry.trim())
81
+ .map(entry => entry.trim())
90
82
  .filter(Boolean)[0];
91
83
  return line ?? null;
92
84
  }
@@ -94,9 +86,9 @@ function firstNonEmptyLine(value: string | null): string | null {
94
86
  function parseWmicTable(output: string, header: string): string | null {
95
87
  const lines = output
96
88
  .split("\n")
97
- .map((line) => line.trim())
89
+ .map(line => line.trim())
98
90
  .filter(Boolean);
99
- const filtered = lines.filter((line) => line.toLowerCase() !== header.toLowerCase());
91
+ const filtered = lines.filter(line => line.toLowerCase() !== header.toLowerCase());
100
92
  return filtered[0] ?? null;
101
93
  }
102
94
 
@@ -137,8 +129,8 @@ function listAgentsMdFiles(root: string, limit: number): string[] {
137
129
  new Bun.Glob(AGENTS_MD_PATTERN).scanSync({ cwd: root, onlyFiles: true, dot: false, absolute: false }),
138
130
  );
139
131
  const normalized = entries
140
- .map((entry) => normalizePath(entry))
141
- .filter((entry) => entry.length > 0 && !entry.includes("node_modules"))
132
+ .map(entry => normalizePath(entry))
133
+ .filter(entry => entry.length > 0 && !entry.includes("node_modules"))
142
134
  .sort();
143
135
  return normalized.length > limit ? normalized.slice(0, limit) : normalized;
144
136
  } catch {
@@ -274,8 +266,8 @@ async function getCpuModel(): Promise<string | null> {
274
266
  if (lscpu) {
275
267
  const match = lscpu
276
268
  .split("\n")
277
- .map((line) => line.trim())
278
- .find((line) => line.toLowerCase().startsWith("model name:"));
269
+ .map(line => line.trim())
270
+ .find(line => line.toLowerCase().startsWith("model name:"));
279
271
  if (match) return match.split(":").slice(1).join(":").trim();
280
272
  }
281
273
  const cpuInfo = await Bun.file("/proc/cpuinfo")
@@ -370,7 +362,7 @@ function normalizeDesktopValue(value: string): string {
370
362
  if (!trimmed) return "unknown";
371
363
  const parts = trimmed
372
364
  .split(":")
373
- .map((part) => part.trim())
365
+ .map(part => part.trim())
374
366
  .filter(Boolean);
375
367
  return parts[0] ?? trimmed;
376
368
  }
@@ -436,14 +428,15 @@ interface SystemInfoCache {
436
428
  }
437
429
 
438
430
  function getSystemInfoCachePath(): string {
439
- return join(homedir(), ".omp", "system_info.json");
431
+ return path.join(os.homedir(), ".omp", "system_info.json");
440
432
  }
441
433
 
442
434
  async function loadSystemInfoCache(): Promise<SystemInfoCache | null> {
443
435
  try {
444
436
  const cachePath = getSystemInfoCachePath();
445
- if (!existsSync(cachePath)) return null;
446
- const content = await Bun.file(cachePath).json();
437
+ const file = Bun.file(cachePath);
438
+ if (!(await file.exists())) return null;
439
+ const content = await file.json();
447
440
  return content as SystemInfoCache;
448
441
  } catch {
449
442
  return null;
@@ -494,7 +487,7 @@ async function getDiskInfo(): Promise<string | null> {
494
487
  .text()
495
488
  .catch(() => null);
496
489
  if (!output) return null;
497
- const lines = output.split("\n").filter((l) => l.trim() && !l.startsWith("Node"));
490
+ const lines = output.split("\n").filter(l => l.trim() && !l.startsWith("Node"));
498
491
  const disks: string[] = [];
499
492
  for (const line of lines) {
500
493
  const parts = line.split(",");
@@ -590,7 +583,7 @@ export async function loadProjectContextFiles(
590
583
  const result = await loadCapability(contextFileCapability.id, { cwd: resolvedCwd });
591
584
 
592
585
  // Convert ContextFile items and preserve depth info
593
- const files = result.items.map((item) => {
586
+ const files = result.items.map(item => {
594
587
  const contextFile = item as ContextFile;
595
588
  return {
596
589
  path: contextFile.path,
@@ -622,8 +615,8 @@ export async function loadSystemPromptFiles(options: LoadContextFilesOptions = {
622
615
  if (result.items.length === 0) return null;
623
616
 
624
617
  // Combine all SYSTEM.md contents (user-level first, then project-level)
625
- const userLevel = result.items.filter((item) => item.level === "user");
626
- const projectLevel = result.items.filter((item) => item.level === "project");
618
+ const userLevel = result.items.filter(item => item.level === "user");
619
+ const projectLevel = result.items.filter(item => item.level === "project");
627
620
 
628
621
  const parts: string[] = [];
629
622
  for (const item of [...userLevel, ...projectLevel]) {
@@ -3,16 +3,15 @@
3
3
  *
4
4
  * Agents are embedded at build time via Bun's import with { type: "text" }.
5
5
  */
6
-
7
- import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
8
- import { parseAgentFields } from "@oh-my-pi/pi-coding-agent/discovery/helpers";
9
- import exploreMd from "@oh-my-pi/pi-coding-agent/prompts/agents/explore.md" with { type: "text" };
6
+ import { renderPromptTemplate } from "../config/prompt-templates";
7
+ import { parseAgentFields } from "../discovery/helpers";
8
+ import exploreMd from "../prompts/agents/explore.md" with { type: "text" };
10
9
  // Embed agent markdown files at build time
11
- import agentFrontmatterTemplate from "@oh-my-pi/pi-coding-agent/prompts/agents/frontmatter.md" with { type: "text" };
12
- import planMd from "@oh-my-pi/pi-coding-agent/prompts/agents/plan.md" with { type: "text" };
13
- import reviewerMd from "@oh-my-pi/pi-coding-agent/prompts/agents/reviewer.md" with { type: "text" };
14
- import taskMd from "@oh-my-pi/pi-coding-agent/prompts/agents/task.md" with { type: "text" };
15
- import { parseFrontmatter } from "@oh-my-pi/pi-coding-agent/utils/frontmatter";
10
+ import agentFrontmatterTemplate from "../prompts/agents/frontmatter.md" with { type: "text" };
11
+ import planMd from "../prompts/agents/plan.md" with { type: "text" };
12
+ import reviewerMd from "../prompts/agents/reviewer.md" with { type: "text" };
13
+ import taskMd from "../prompts/agents/task.md" with { type: "text" };
14
+ import { parseFrontmatter } from "../utils/frontmatter";
16
15
  import type { AgentDefinition, AgentSource } from "./types";
17
16
 
18
17
  interface AgentFrontmatter {
@@ -69,30 +68,56 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
69
68
  },
70
69
  ];
71
70
 
72
- const EMBEDDED_AGENTS: { name: string; content: string }[] = EMBEDDED_AGENT_DEFS.map((def) => ({
71
+ const EMBEDDED_AGENTS: { name: string; content: string }[] = EMBEDDED_AGENT_DEFS.map(def => ({
73
72
  name: def.fileName,
74
73
  content: buildAgentContent(def),
75
74
  }));
76
75
 
76
+ export class AgentParsingError extends Error {
77
+ constructor(
78
+ error: Error,
79
+ public readonly source?: unknown,
80
+ ) {
81
+ super(`Failed to parse agent: ${error.message}`, { cause: error });
82
+ this.name = "AgentParsingError";
83
+ }
84
+
85
+ toString(): string {
86
+ const details: string[] = [this.message];
87
+ if (this.source !== undefined) {
88
+ details.push(`Source: ${JSON.stringify(this.source)}`);
89
+ }
90
+ if (this.cause && typeof this.cause === "object" && "stack" in this.cause && this.cause.stack) {
91
+ details.push(`Stack:\n${this.cause.stack}`);
92
+ } else if (this.stack) {
93
+ details.push(`Stack:\n${this.stack}`);
94
+ }
95
+ return details.join("\n\n");
96
+ }
97
+ }
98
+
77
99
  /**
78
100
  * Parse an agent from embedded content.
79
101
  */
80
- function parseAgent(fileName: string, content: string, source: AgentSource): AgentDefinition | null {
102
+ export function parseAgent(
103
+ filePath: string,
104
+ content: string,
105
+ source: AgentSource,
106
+ level: "fatal" | "warn" | "off" = "fatal",
107
+ ): AgentDefinition {
81
108
  const { frontmatter, body } = parseFrontmatter(content, {
82
- source: `embedded:${fileName}`,
83
- level: "fatal",
109
+ location: filePath,
110
+ level,
84
111
  });
85
112
  const fields = parseAgentFields(frontmatter);
86
-
87
113
  if (!fields) {
88
- return null;
114
+ throw new AgentParsingError(new Error("Invalid agent fields"), filePath);
89
115
  }
90
-
91
116
  return {
92
117
  ...fields,
93
118
  systemPrompt: body,
94
119
  source,
95
- filePath: `embedded:${fileName}`,
120
+ filePath,
96
121
  };
97
122
  }
98
123
 
@@ -107,25 +132,15 @@ export function loadBundledAgents(): AgentDefinition[] {
107
132
  if (bundledAgentsCache !== null) {
108
133
  return bundledAgentsCache;
109
134
  }
110
-
111
- const agents: AgentDefinition[] = [];
112
-
113
- for (const { name, content } of EMBEDDED_AGENTS) {
114
- const agent = parseAgent(name, content, "bundled");
115
- if (agent) {
116
- agents.push(agent);
117
- }
118
- }
119
-
120
- bundledAgentsCache = agents;
121
- return agents;
135
+ bundledAgentsCache = EMBEDDED_AGENTS.map(({ name, content }) => parseAgent(`embedded:${name}`, content, "bundled"));
136
+ return bundledAgentsCache;
122
137
  }
123
138
 
124
139
  /**
125
140
  * Get a bundled agent by name.
126
141
  */
127
142
  export function getBundledAgent(name: string): AgentDefinition | undefined {
128
- return loadBundledAgents().find((a) => a.name === name);
143
+ return loadBundledAgents().find(a => a.name === name);
129
144
  }
130
145
 
131
146
  /**
@@ -3,14 +3,13 @@
3
3
  *
4
4
  * Commands are embedded at build time via Bun's import with { type: "text" }.
5
5
  */
6
-
7
6
  import * as path from "node:path";
8
- import { type SlashCommand, slashCommandCapability } from "@oh-my-pi/pi-coding-agent/capability/slash-command";
9
- import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
10
- import { loadCapability } from "@oh-my-pi/pi-coding-agent/discovery";
7
+ import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
8
+ import { renderPromptTemplate } from "../config/prompt-templates";
9
+ import { loadCapability } from "../discovery";
11
10
  // Embed command markdown files at build time
12
- import initMd from "@oh-my-pi/pi-coding-agent/prompts/agents/init.md" with { type: "text" };
13
- import { parseFrontmatter } from "@oh-my-pi/pi-coding-agent/utils/frontmatter";
11
+ import initMd from "../prompts/agents/init.md" with { type: "text" };
12
+ import { parseFrontmatter } from "../utils/frontmatter";
14
13
 
15
14
  const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
16
15
  { name: "init.md", content: renderPromptTemplate(initMd) },
@@ -116,7 +115,7 @@ export async function discoverCommands(cwd: string): Promise<WorkflowCommand[]>
116
115
  * Get a command by name.
117
116
  */
118
117
  export function getCommand(commands: WorkflowCommand[], name: string): WorkflowCommand | undefined {
119
- return commands.find((c) => c.name === name);
118
+ return commands.find(c => c.name === name);
120
119
  }
121
120
 
122
121
  /**