@oh-my-pi/pi-coding-agent 0.1.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 (337) hide show
  1. package/CHANGELOG.md +1629 -0
  2. package/README.md +1041 -0
  3. package/docs/compaction.md +403 -0
  4. package/docs/config-usage.md +113 -0
  5. package/docs/custom-tools.md +541 -0
  6. package/docs/extension-loading.md +1004 -0
  7. package/docs/hooks.md +867 -0
  8. package/docs/rpc.md +1040 -0
  9. package/docs/sdk.md +994 -0
  10. package/docs/session-tree-plan.md +441 -0
  11. package/docs/session.md +240 -0
  12. package/docs/skills.md +290 -0
  13. package/docs/theme.md +670 -0
  14. package/docs/tree.md +197 -0
  15. package/docs/tui.md +341 -0
  16. package/examples/README.md +21 -0
  17. package/examples/custom-tools/README.md +124 -0
  18. package/examples/custom-tools/hello/index.ts +20 -0
  19. package/examples/custom-tools/question/index.ts +84 -0
  20. package/examples/custom-tools/subagent/README.md +172 -0
  21. package/examples/custom-tools/subagent/agents/planner.md +37 -0
  22. package/examples/custom-tools/subagent/agents/scout.md +50 -0
  23. package/examples/custom-tools/subagent/agents/worker.md +24 -0
  24. package/examples/custom-tools/subagent/agents.ts +156 -0
  25. package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
  26. package/examples/custom-tools/subagent/commands/implement.md +10 -0
  27. package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
  28. package/examples/custom-tools/subagent/index.ts +1002 -0
  29. package/examples/custom-tools/todo/index.ts +212 -0
  30. package/examples/hooks/README.md +56 -0
  31. package/examples/hooks/auto-commit-on-exit.ts +49 -0
  32. package/examples/hooks/confirm-destructive.ts +59 -0
  33. package/examples/hooks/custom-compaction.ts +116 -0
  34. package/examples/hooks/dirty-repo-guard.ts +52 -0
  35. package/examples/hooks/file-trigger.ts +41 -0
  36. package/examples/hooks/git-checkpoint.ts +53 -0
  37. package/examples/hooks/handoff.ts +150 -0
  38. package/examples/hooks/permission-gate.ts +34 -0
  39. package/examples/hooks/protected-paths.ts +30 -0
  40. package/examples/hooks/qna.ts +119 -0
  41. package/examples/hooks/snake.ts +343 -0
  42. package/examples/hooks/status-line.ts +40 -0
  43. package/examples/sdk/01-minimal.ts +22 -0
  44. package/examples/sdk/02-custom-model.ts +49 -0
  45. package/examples/sdk/03-custom-prompt.ts +44 -0
  46. package/examples/sdk/04-skills.ts +44 -0
  47. package/examples/sdk/05-tools.ts +90 -0
  48. package/examples/sdk/06-hooks.ts +61 -0
  49. package/examples/sdk/07-context-files.ts +36 -0
  50. package/examples/sdk/08-slash-commands.ts +42 -0
  51. package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
  52. package/examples/sdk/10-settings.ts +38 -0
  53. package/examples/sdk/11-sessions.ts +48 -0
  54. package/examples/sdk/12-full-control.ts +95 -0
  55. package/examples/sdk/README.md +154 -0
  56. package/package.json +89 -0
  57. package/src/bun-imports.d.ts +16 -0
  58. package/src/capability/context-file.ts +40 -0
  59. package/src/capability/extension.ts +48 -0
  60. package/src/capability/hook.ts +40 -0
  61. package/src/capability/index.ts +616 -0
  62. package/src/capability/instruction.ts +37 -0
  63. package/src/capability/mcp.ts +52 -0
  64. package/src/capability/prompt.ts +35 -0
  65. package/src/capability/rule.ts +56 -0
  66. package/src/capability/settings.ts +35 -0
  67. package/src/capability/skill.ts +49 -0
  68. package/src/capability/slash-command.ts +40 -0
  69. package/src/capability/system-prompt.ts +35 -0
  70. package/src/capability/tool.ts +38 -0
  71. package/src/capability/types.ts +166 -0
  72. package/src/cli/args.ts +259 -0
  73. package/src/cli/file-processor.ts +121 -0
  74. package/src/cli/list-models.ts +104 -0
  75. package/src/cli/plugin-cli.ts +661 -0
  76. package/src/cli/session-picker.ts +41 -0
  77. package/src/cli/update-cli.ts +274 -0
  78. package/src/cli.ts +10 -0
  79. package/src/config.ts +391 -0
  80. package/src/core/agent-session.ts +2178 -0
  81. package/src/core/auth-storage.ts +258 -0
  82. package/src/core/bash-executor.ts +197 -0
  83. package/src/core/compaction/branch-summarization.ts +315 -0
  84. package/src/core/compaction/compaction.ts +664 -0
  85. package/src/core/compaction/index.ts +7 -0
  86. package/src/core/compaction/utils.ts +153 -0
  87. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  88. package/src/core/custom-commands/index.ts +15 -0
  89. package/src/core/custom-commands/loader.ts +226 -0
  90. package/src/core/custom-commands/types.ts +112 -0
  91. package/src/core/custom-tools/index.ts +22 -0
  92. package/src/core/custom-tools/loader.ts +248 -0
  93. package/src/core/custom-tools/types.ts +185 -0
  94. package/src/core/custom-tools/wrapper.ts +29 -0
  95. package/src/core/exec.ts +139 -0
  96. package/src/core/export-html/index.ts +159 -0
  97. package/src/core/export-html/template.css +774 -0
  98. package/src/core/export-html/template.generated.ts +2 -0
  99. package/src/core/export-html/template.html +45 -0
  100. package/src/core/export-html/template.js +1185 -0
  101. package/src/core/export-html/template.macro.ts +24 -0
  102. package/src/core/file-mentions.ts +54 -0
  103. package/src/core/hooks/index.ts +16 -0
  104. package/src/core/hooks/loader.ts +288 -0
  105. package/src/core/hooks/runner.ts +434 -0
  106. package/src/core/hooks/tool-wrapper.ts +98 -0
  107. package/src/core/hooks/types.ts +770 -0
  108. package/src/core/index.ts +53 -0
  109. package/src/core/logger.ts +112 -0
  110. package/src/core/mcp/client.ts +185 -0
  111. package/src/core/mcp/config.ts +248 -0
  112. package/src/core/mcp/index.ts +45 -0
  113. package/src/core/mcp/loader.ts +99 -0
  114. package/src/core/mcp/manager.ts +235 -0
  115. package/src/core/mcp/tool-bridge.ts +156 -0
  116. package/src/core/mcp/transports/http.ts +316 -0
  117. package/src/core/mcp/transports/index.ts +6 -0
  118. package/src/core/mcp/transports/stdio.ts +252 -0
  119. package/src/core/mcp/types.ts +228 -0
  120. package/src/core/messages.ts +211 -0
  121. package/src/core/model-registry.ts +334 -0
  122. package/src/core/model-resolver.ts +494 -0
  123. package/src/core/plugins/doctor.ts +67 -0
  124. package/src/core/plugins/index.ts +38 -0
  125. package/src/core/plugins/installer.ts +189 -0
  126. package/src/core/plugins/loader.ts +339 -0
  127. package/src/core/plugins/manager.ts +672 -0
  128. package/src/core/plugins/parser.ts +105 -0
  129. package/src/core/plugins/paths.ts +37 -0
  130. package/src/core/plugins/types.ts +190 -0
  131. package/src/core/sdk.ts +900 -0
  132. package/src/core/session-manager.ts +1837 -0
  133. package/src/core/settings-manager.ts +860 -0
  134. package/src/core/skills.ts +352 -0
  135. package/src/core/slash-commands.ts +132 -0
  136. package/src/core/system-prompt.ts +442 -0
  137. package/src/core/timings.ts +25 -0
  138. package/src/core/title-generator.ts +110 -0
  139. package/src/core/tools/ask.ts +193 -0
  140. package/src/core/tools/bash-interceptor.ts +120 -0
  141. package/src/core/tools/bash.ts +91 -0
  142. package/src/core/tools/context.ts +32 -0
  143. package/src/core/tools/edit-diff.ts +487 -0
  144. package/src/core/tools/edit.ts +140 -0
  145. package/src/core/tools/exa/company.ts +59 -0
  146. package/src/core/tools/exa/index.ts +63 -0
  147. package/src/core/tools/exa/linkedin.ts +59 -0
  148. package/src/core/tools/exa/mcp-client.ts +368 -0
  149. package/src/core/tools/exa/render.ts +200 -0
  150. package/src/core/tools/exa/researcher.ts +90 -0
  151. package/src/core/tools/exa/search.ts +338 -0
  152. package/src/core/tools/exa/types.ts +167 -0
  153. package/src/core/tools/exa/websets.ts +248 -0
  154. package/src/core/tools/find.ts +244 -0
  155. package/src/core/tools/grep.ts +584 -0
  156. package/src/core/tools/index.ts +283 -0
  157. package/src/core/tools/ls.ts +142 -0
  158. package/src/core/tools/lsp/client.ts +767 -0
  159. package/src/core/tools/lsp/clients/biome-client.ts +207 -0
  160. package/src/core/tools/lsp/clients/index.ts +49 -0
  161. package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
  162. package/src/core/tools/lsp/config.ts +845 -0
  163. package/src/core/tools/lsp/edits.ts +110 -0
  164. package/src/core/tools/lsp/index.ts +1364 -0
  165. package/src/core/tools/lsp/render.ts +560 -0
  166. package/src/core/tools/lsp/rust-analyzer.ts +145 -0
  167. package/src/core/tools/lsp/types.ts +495 -0
  168. package/src/core/tools/lsp/utils.ts +526 -0
  169. package/src/core/tools/notebook.ts +182 -0
  170. package/src/core/tools/output.ts +198 -0
  171. package/src/core/tools/path-utils.ts +61 -0
  172. package/src/core/tools/read.ts +507 -0
  173. package/src/core/tools/renderers.ts +820 -0
  174. package/src/core/tools/review.ts +275 -0
  175. package/src/core/tools/rulebook.ts +124 -0
  176. package/src/core/tools/task/agents.ts +158 -0
  177. package/src/core/tools/task/artifacts.ts +114 -0
  178. package/src/core/tools/task/commands.ts +157 -0
  179. package/src/core/tools/task/discovery.ts +217 -0
  180. package/src/core/tools/task/executor.ts +531 -0
  181. package/src/core/tools/task/index.ts +548 -0
  182. package/src/core/tools/task/model-resolver.ts +176 -0
  183. package/src/core/tools/task/parallel.ts +38 -0
  184. package/src/core/tools/task/render.ts +502 -0
  185. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  186. package/src/core/tools/task/types.ts +142 -0
  187. package/src/core/tools/truncate.ts +265 -0
  188. package/src/core/tools/web-fetch.ts +2511 -0
  189. package/src/core/tools/web-search/auth.ts +199 -0
  190. package/src/core/tools/web-search/index.ts +583 -0
  191. package/src/core/tools/web-search/providers/anthropic.ts +198 -0
  192. package/src/core/tools/web-search/providers/exa.ts +196 -0
  193. package/src/core/tools/web-search/providers/perplexity.ts +195 -0
  194. package/src/core/tools/web-search/render.ts +372 -0
  195. package/src/core/tools/web-search/types.ts +180 -0
  196. package/src/core/tools/write.ts +63 -0
  197. package/src/core/ttsr.ts +211 -0
  198. package/src/core/utils.ts +187 -0
  199. package/src/discovery/agents-md.ts +75 -0
  200. package/src/discovery/builtin.ts +647 -0
  201. package/src/discovery/claude.ts +623 -0
  202. package/src/discovery/cline.ts +104 -0
  203. package/src/discovery/codex.ts +571 -0
  204. package/src/discovery/cursor.ts +266 -0
  205. package/src/discovery/gemini.ts +368 -0
  206. package/src/discovery/github.ts +120 -0
  207. package/src/discovery/helpers.test.ts +127 -0
  208. package/src/discovery/helpers.ts +249 -0
  209. package/src/discovery/index.ts +84 -0
  210. package/src/discovery/mcp-json.ts +127 -0
  211. package/src/discovery/vscode.ts +99 -0
  212. package/src/discovery/windsurf.ts +219 -0
  213. package/src/index.ts +192 -0
  214. package/src/main.ts +507 -0
  215. package/src/migrations.ts +156 -0
  216. package/src/modes/cleanup.ts +23 -0
  217. package/src/modes/index.ts +48 -0
  218. package/src/modes/interactive/components/armin.ts +382 -0
  219. package/src/modes/interactive/components/assistant-message.ts +86 -0
  220. package/src/modes/interactive/components/bash-execution.ts +199 -0
  221. package/src/modes/interactive/components/bordered-loader.ts +41 -0
  222. package/src/modes/interactive/components/branch-summary-message.ts +42 -0
  223. package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
  224. package/src/modes/interactive/components/custom-editor.ts +122 -0
  225. package/src/modes/interactive/components/diff.ts +147 -0
  226. package/src/modes/interactive/components/dynamic-border.ts +25 -0
  227. package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
  228. package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
  229. package/src/modes/interactive/components/extensions/index.ts +9 -0
  230. package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
  231. package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
  232. package/src/modes/interactive/components/extensions/types.ts +191 -0
  233. package/src/modes/interactive/components/hook-editor.ts +117 -0
  234. package/src/modes/interactive/components/hook-input.ts +64 -0
  235. package/src/modes/interactive/components/hook-message.ts +96 -0
  236. package/src/modes/interactive/components/hook-selector.ts +91 -0
  237. package/src/modes/interactive/components/model-selector.ts +560 -0
  238. package/src/modes/interactive/components/oauth-selector.ts +136 -0
  239. package/src/modes/interactive/components/plugin-settings.ts +481 -0
  240. package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
  241. package/src/modes/interactive/components/session-selector.ts +220 -0
  242. package/src/modes/interactive/components/settings-defs.ts +597 -0
  243. package/src/modes/interactive/components/settings-selector.ts +545 -0
  244. package/src/modes/interactive/components/show-images-selector.ts +45 -0
  245. package/src/modes/interactive/components/status-line/index.ts +4 -0
  246. package/src/modes/interactive/components/status-line/presets.ts +94 -0
  247. package/src/modes/interactive/components/status-line/segments.ts +350 -0
  248. package/src/modes/interactive/components/status-line/separators.ts +55 -0
  249. package/src/modes/interactive/components/status-line/types.ts +81 -0
  250. package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
  251. package/src/modes/interactive/components/status-line.ts +384 -0
  252. package/src/modes/interactive/components/theme-selector.ts +62 -0
  253. package/src/modes/interactive/components/thinking-selector.ts +64 -0
  254. package/src/modes/interactive/components/tool-execution.ts +946 -0
  255. package/src/modes/interactive/components/tree-selector.ts +877 -0
  256. package/src/modes/interactive/components/ttsr-notification.ts +82 -0
  257. package/src/modes/interactive/components/user-message-selector.ts +159 -0
  258. package/src/modes/interactive/components/user-message.ts +18 -0
  259. package/src/modes/interactive/components/visual-truncate.ts +50 -0
  260. package/src/modes/interactive/components/welcome.ts +228 -0
  261. package/src/modes/interactive/interactive-mode.ts +2669 -0
  262. package/src/modes/interactive/theme/dark.json +102 -0
  263. package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
  264. package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
  265. package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
  266. package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
  267. package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
  268. package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
  269. package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
  270. package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
  271. package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
  272. package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
  273. package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
  274. package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
  275. package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
  276. package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
  277. package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
  278. package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
  279. package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
  280. package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
  281. package/src/modes/interactive/theme/defaults/index.ts +67 -0
  282. package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
  283. package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
  284. package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
  285. package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
  286. package/src/modes/interactive/theme/defaults/light-github.json +114 -0
  287. package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
  288. package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
  289. package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
  290. package/src/modes/interactive/theme/defaults/light-one.json +105 -0
  291. package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
  292. package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
  293. package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
  294. package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
  295. package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
  296. package/src/modes/interactive/theme/light.json +99 -0
  297. package/src/modes/interactive/theme/theme-schema.json +424 -0
  298. package/src/modes/interactive/theme/theme.ts +2211 -0
  299. package/src/modes/print-mode.ts +163 -0
  300. package/src/modes/rpc/rpc-client.ts +527 -0
  301. package/src/modes/rpc/rpc-mode.ts +494 -0
  302. package/src/modes/rpc/rpc-types.ts +203 -0
  303. package/src/prompts/architect-plan.md +10 -0
  304. package/src/prompts/branch-summary-preamble.md +3 -0
  305. package/src/prompts/branch-summary.md +28 -0
  306. package/src/prompts/browser.md +71 -0
  307. package/src/prompts/compaction-summary.md +34 -0
  308. package/src/prompts/compaction-turn-prefix.md +16 -0
  309. package/src/prompts/compaction-update-summary.md +41 -0
  310. package/src/prompts/explore.md +82 -0
  311. package/src/prompts/implement-with-critic.md +11 -0
  312. package/src/prompts/implement.md +11 -0
  313. package/src/prompts/init.md +30 -0
  314. package/src/prompts/plan.md +54 -0
  315. package/src/prompts/reviewer.md +81 -0
  316. package/src/prompts/summarization-system.md +3 -0
  317. package/src/prompts/system-prompt.md +27 -0
  318. package/src/prompts/task.md +56 -0
  319. package/src/prompts/title-system.md +8 -0
  320. package/src/prompts/tools/ask.md +24 -0
  321. package/src/prompts/tools/bash.md +23 -0
  322. package/src/prompts/tools/edit.md +9 -0
  323. package/src/prompts/tools/find.md +6 -0
  324. package/src/prompts/tools/grep.md +12 -0
  325. package/src/prompts/tools/lsp.md +14 -0
  326. package/src/prompts/tools/output.md +23 -0
  327. package/src/prompts/tools/read.md +25 -0
  328. package/src/prompts/tools/web-fetch.md +8 -0
  329. package/src/prompts/tools/web-search.md +10 -0
  330. package/src/prompts/tools/write.md +10 -0
  331. package/src/utils/changelog.ts +99 -0
  332. package/src/utils/clipboard.ts +265 -0
  333. package/src/utils/fuzzy.ts +108 -0
  334. package/src/utils/mime.ts +30 -0
  335. package/src/utils/shell-snapshot.ts +218 -0
  336. package/src/utils/shell.ts +364 -0
  337. package/src/utils/tools-manager.ts +265 -0
@@ -0,0 +1,900 @@
1
+ /**
2
+ * SDK for programmatic usage of AgentSession.
3
+ *
4
+ * Provides a factory function and discovery helpers that allow full control
5
+ * over agent configuration, or sensible defaults that match CLI behavior.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // Minimal - everything auto-discovered
10
+ * const session = await createAgentSession();
11
+ *
12
+ * // With custom hooks
13
+ * const session = await createAgentSession({
14
+ * hooks: [
15
+ * ...await discoverHooks(),
16
+ * { factory: myHookFactory },
17
+ * ],
18
+ * });
19
+ *
20
+ * // Full control
21
+ * const session = await createAgentSession({
22
+ * model: myModel,
23
+ * getApiKey: async () => process.env.MY_KEY,
24
+ * tools: [readTool, bashTool],
25
+ * hooks: [],
26
+ * skills: [],
27
+ * sessionFile: false,
28
+ * });
29
+ * ```
30
+ */
31
+
32
+ import { join } from "node:path";
33
+ import { Agent, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
34
+ import type { Model } from "@oh-my-pi/pi-ai";
35
+ import chalk from "chalk";
36
+ // Import discovery to register all providers on startup
37
+ import "../discovery";
38
+ import { loadSync as loadCapability } from "../capability/index";
39
+ import { type Rule, ruleCapability } from "../capability/rule";
40
+ import { getAgentDir, getConfigDirPaths } from "../config";
41
+ import { AgentSession } from "./agent-session";
42
+ import { AuthStorage } from "./auth-storage";
43
+ import {
44
+ type CustomCommandsLoadResult,
45
+ loadCustomCommands as loadCustomCommandsInternal,
46
+ } from "./custom-commands/index";
47
+ import {
48
+ type CustomToolsLoadResult,
49
+ discoverAndLoadCustomTools,
50
+ type LoadedCustomTool,
51
+ wrapCustomTools,
52
+ } from "./custom-tools/index";
53
+ import type { CustomTool } from "./custom-tools/types";
54
+ import { discoverAndLoadHooks, HookRunner, type LoadedHook, wrapToolsWithHooks } from "./hooks/index";
55
+ import type { HookFactory } from "./hooks/types";
56
+ import { logger } from "./logger";
57
+ import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index";
58
+ import { convertToLlm } from "./messages";
59
+ import { ModelRegistry } from "./model-registry";
60
+ import { SessionManager } from "./session-manager";
61
+ import { type CommandsSettings, type Settings, SettingsManager, type SkillsSettings } from "./settings-manager";
62
+ import { loadSkills as loadSkillsInternal, type Skill } from "./skills";
63
+ import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./slash-commands";
64
+ import {
65
+ buildSystemPrompt as buildSystemPromptInternal,
66
+ loadProjectContextFiles as loadContextFilesInternal,
67
+ } from "./system-prompt";
68
+ import { time } from "./timings";
69
+ import { createToolContextStore } from "./tools/context";
70
+ import {
71
+ allTools,
72
+ applyBashInterception,
73
+ bashTool,
74
+ codingTools,
75
+ createBashTool,
76
+ createCodingTools,
77
+ createEditTool,
78
+ createFindTool,
79
+ createGrepTool,
80
+ createLsTool,
81
+ createReadOnlyTools,
82
+ createReadTool,
83
+ createRulebookTool,
84
+ createWriteTool,
85
+ editTool,
86
+ filterRulebookRules,
87
+ findTool,
88
+ grepTool,
89
+ lsTool,
90
+ readOnlyTools,
91
+ readTool,
92
+ type Tool,
93
+ warmupLspServers,
94
+ writeTool,
95
+ } from "./tools/index";
96
+ import { createTtsrManager } from "./ttsr";
97
+
98
+ // Types
99
+
100
+ export interface CreateAgentSessionOptions {
101
+ /** Working directory for project-local discovery. Default: process.cwd() */
102
+ cwd?: string;
103
+ /** Global config directory. Default: ~/.omp/agent */
104
+ agentDir?: string;
105
+
106
+ /** Auth storage for credentials. Default: discoverAuthStorage(agentDir) */
107
+ authStorage?: AuthStorage;
108
+ /** Model registry. Default: discoverModels(authStorage, agentDir) */
109
+ modelRegistry?: ModelRegistry;
110
+
111
+ /** Model to use. Default: from settings, else first available */
112
+ model?: Model<any>;
113
+ /** Thinking level. Default: from settings, else 'off' (clamped to model capabilities) */
114
+ thinkingLevel?: ThinkingLevel;
115
+ /** Models available for cycling (Ctrl+P in interactive mode) */
116
+ scopedModels?: Array<{ model: Model<any>; thinkingLevel: ThinkingLevel }>;
117
+
118
+ /** System prompt. String replaces default, function receives default and returns final. */
119
+ systemPrompt?: string | ((defaultPrompt: string) => string);
120
+
121
+ /** Built-in tools to use. Default: codingTools [read, bash, edit, write] */
122
+ tools?: Tool[];
123
+ /** Custom tools (replaces discovery). */
124
+ customTools?: Array<{ path?: string; tool: CustomTool }>;
125
+ /** Additional custom tool paths to load (merged with discovery). */
126
+ additionalCustomToolPaths?: string[];
127
+
128
+ /** Hooks (replaces discovery). */
129
+ hooks?: Array<{ path?: string; factory: HookFactory }>;
130
+ /** Additional hook paths to load (merged with discovery). */
131
+ additionalHookPaths?: string[];
132
+
133
+ /** Skills. Default: discovered from multiple locations */
134
+ skills?: Skill[];
135
+ /** Context files (AGENTS.md content). Default: discovered walking up from cwd */
136
+ contextFiles?: Array<{ path: string; content: string }>;
137
+ /** Slash commands. Default: discovered from cwd/.omp/commands/ + agentDir/commands/ */
138
+ slashCommands?: FileSlashCommand[];
139
+
140
+ /** Enable MCP server discovery from .mcp.json files. Default: true */
141
+ enableMCP?: boolean;
142
+
143
+ /** Tool names explicitly requested (enables disabled-by-default tools) */
144
+ explicitTools?: string[];
145
+
146
+ /** Session manager. Default: SessionManager.create(cwd) */
147
+ sessionManager?: SessionManager;
148
+
149
+ /** Settings manager. Default: SettingsManager.create(cwd, agentDir) */
150
+ settingsManager?: SettingsManager;
151
+
152
+ /** Whether UI is available (enables interactive tools like ask). Default: false */
153
+ hasUI?: boolean;
154
+ }
155
+
156
+ /** Result from createAgentSession */
157
+ export interface CreateAgentSessionResult {
158
+ /** The created session */
159
+ session: AgentSession;
160
+ /** Custom tools result (for UI context setup in interactive mode) */
161
+ customToolsResult: CustomToolsLoadResult;
162
+ /** MCP manager for server lifecycle management (undefined if MCP disabled) */
163
+ mcpManager?: MCPManager;
164
+ /** Warning if session was restored with a different model than saved */
165
+ modelFallbackMessage?: string;
166
+ /** LSP servers that were warmed up at startup */
167
+ lspServers?: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }>;
168
+ }
169
+
170
+ // Re-exports
171
+
172
+ export type { CustomCommand, CustomCommandFactory } from "./custom-commands/types";
173
+ export type { CustomTool } from "./custom-tools/types";
174
+ export type { HookAPI, HookCommandContext, HookContext, HookFactory } from "./hooks/types";
175
+ export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp/index";
176
+ export type { Settings, SkillsSettings } from "./settings-manager";
177
+ export type { Skill } from "./skills";
178
+ export type { FileSlashCommand } from "./slash-commands";
179
+ export type { Tool } from "./tools/index";
180
+
181
+ export {
182
+ // Pre-built tools (use process.cwd())
183
+ readTool,
184
+ bashTool,
185
+ editTool,
186
+ writeTool,
187
+ grepTool,
188
+ findTool,
189
+ lsTool,
190
+ codingTools,
191
+ readOnlyTools,
192
+ allTools as allBuiltInTools,
193
+ // Tool factories (for custom cwd)
194
+ createCodingTools,
195
+ createReadOnlyTools,
196
+ createReadTool,
197
+ createBashTool,
198
+ createEditTool,
199
+ createWriteTool,
200
+ createGrepTool,
201
+ createFindTool,
202
+ createLsTool,
203
+ };
204
+
205
+ // Helper Functions
206
+
207
+ function getDefaultAgentDir(): string {
208
+ return getAgentDir();
209
+ }
210
+
211
+ // Discovery Functions
212
+
213
+ /**
214
+ * Create an AuthStorage instance with fallback support.
215
+ * Reads from primary path first, then falls back to legacy paths (.pi, .claude).
216
+ */
217
+ export function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): AuthStorage {
218
+ const primaryPath = join(agentDir, "auth.json");
219
+ // Get all auth.json paths (user-level only), excluding the primary
220
+ const allPaths = getConfigDirPaths("auth.json", { project: false });
221
+ const fallbackPaths = allPaths.filter((p) => p !== primaryPath);
222
+
223
+ logger.debug("discoverAuthStorage", { agentDir, primaryPath, allPaths, fallbackPaths });
224
+
225
+ return new AuthStorage(primaryPath, fallbackPaths);
226
+ }
227
+
228
+ /**
229
+ * Create a ModelRegistry with fallback support.
230
+ * Reads from primary path first, then falls back to legacy paths (.pi, .claude).
231
+ */
232
+ export function discoverModels(authStorage: AuthStorage, agentDir: string = getDefaultAgentDir()): ModelRegistry {
233
+ const primaryPath = join(agentDir, "models.json");
234
+ // Get all models.json paths (user-level only), excluding the primary
235
+ const allPaths = getConfigDirPaths("models.json", { project: false });
236
+ const fallbackPaths = allPaths.filter((p) => p !== primaryPath);
237
+
238
+ logger.debug("discoverModels", { primaryPath, fallbackPaths });
239
+
240
+ return new ModelRegistry(authStorage, primaryPath, fallbackPaths);
241
+ }
242
+
243
+ /**
244
+ * Discover hooks from cwd and agentDir.
245
+ */
246
+ export async function discoverHooks(
247
+ cwd?: string,
248
+ _agentDir?: string,
249
+ ): Promise<Array<{ path: string; factory: HookFactory }>> {
250
+ const resolvedCwd = cwd ?? process.cwd();
251
+
252
+ const { hooks, errors } = await discoverAndLoadHooks([], resolvedCwd);
253
+
254
+ // Log errors but don't fail
255
+ for (const { path, error } of errors) {
256
+ console.error(`Failed to load hook "${path}": ${error}`);
257
+ }
258
+
259
+ return hooks.map((h) => ({
260
+ path: h.path,
261
+ factory: createFactoryFromLoadedHook(h),
262
+ }));
263
+ }
264
+
265
+ /**
266
+ * Discover custom tools from cwd and agentDir.
267
+ */
268
+ export async function discoverCustomTools(
269
+ cwd?: string,
270
+ _agentDir?: string,
271
+ ): Promise<Array<{ path: string; tool: CustomTool }>> {
272
+ const resolvedCwd = cwd ?? process.cwd();
273
+
274
+ const { tools, errors } = await discoverAndLoadCustomTools([], resolvedCwd, Object.keys(allTools));
275
+
276
+ // Log errors but don't fail
277
+ for (const { path, error } of errors) {
278
+ console.error(`Failed to load custom tool "${path}": ${error}`);
279
+ }
280
+
281
+ return tools.map((t) => ({
282
+ path: t.path,
283
+ tool: t.tool,
284
+ }));
285
+ }
286
+
287
+ /**
288
+ * Discover skills from cwd and agentDir.
289
+ */
290
+ export function discoverSkills(cwd?: string, _agentDir?: string, settings?: SkillsSettings): Skill[] {
291
+ const { skills } = loadSkillsInternal({
292
+ ...settings,
293
+ cwd: cwd ?? process.cwd(),
294
+ });
295
+ return skills;
296
+ }
297
+
298
+ /**
299
+ * Discover context files (AGENTS.md) walking up from cwd.
300
+ * Returns files sorted by depth (farther from cwd first, so closer files appear last/more prominent).
301
+ */
302
+ export function discoverContextFiles(
303
+ cwd?: string,
304
+ _agentDir?: string,
305
+ ): Array<{ path: string; content: string; depth?: number }> {
306
+ return loadContextFilesInternal({
307
+ cwd: cwd ?? process.cwd(),
308
+ });
309
+ }
310
+
311
+ /**
312
+ * Discover slash commands from cwd and agentDir.
313
+ */
314
+ export function discoverSlashCommands(
315
+ cwd?: string,
316
+ _agentDir?: string,
317
+ _settings?: CommandsSettings,
318
+ ): FileSlashCommand[] {
319
+ return loadSlashCommandsInternal({
320
+ cwd: cwd ?? process.cwd(),
321
+ });
322
+ }
323
+
324
+ /**
325
+ * Discover custom commands (TypeScript slash commands) from cwd and agentDir.
326
+ */
327
+ export async function discoverCustomTSCommands(cwd?: string, agentDir?: string): Promise<CustomCommandsLoadResult> {
328
+ const resolvedCwd = cwd ?? process.cwd();
329
+ const resolvedAgentDir = agentDir ?? getDefaultAgentDir();
330
+
331
+ return loadCustomCommandsInternal({
332
+ cwd: resolvedCwd,
333
+ agentDir: resolvedAgentDir,
334
+ });
335
+ }
336
+
337
+ /**
338
+ * Discover MCP servers from .mcp.json files.
339
+ * Returns the manager and loaded tools.
340
+ */
341
+ export async function discoverMCPServers(cwd?: string): Promise<MCPToolsLoadResult> {
342
+ const resolvedCwd = cwd ?? process.cwd();
343
+ return discoverAndLoadMCPTools(resolvedCwd);
344
+ }
345
+
346
+ // API Key Helpers
347
+
348
+ // System Prompt
349
+
350
+ export interface BuildSystemPromptOptions {
351
+ tools?: Tool[];
352
+ skills?: Skill[];
353
+ contextFiles?: Array<{ path: string; content: string }>;
354
+ cwd?: string;
355
+ appendPrompt?: string;
356
+ }
357
+
358
+ /**
359
+ * Build the default system prompt.
360
+ */
361
+ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): string {
362
+ return buildSystemPromptInternal({
363
+ cwd: options.cwd,
364
+ skills: options.skills,
365
+ contextFiles: options.contextFiles,
366
+ appendSystemPrompt: options.appendPrompt,
367
+ });
368
+ }
369
+
370
+ // Settings
371
+
372
+ /**
373
+ * Load settings from agentDir/settings.json merged with cwd/.omp/settings.json.
374
+ */
375
+ export function loadSettings(cwd?: string, agentDir?: string): Settings {
376
+ const manager = SettingsManager.create(cwd ?? process.cwd(), agentDir ?? getDefaultAgentDir());
377
+ return {
378
+ modelRoles: manager.getModelRoles(),
379
+ defaultThinkingLevel: manager.getDefaultThinkingLevel(),
380
+ queueMode: manager.getQueueMode(),
381
+ theme: manager.getTheme(),
382
+ compaction: manager.getCompactionSettings(),
383
+ retry: manager.getRetrySettings(),
384
+ hideThinkingBlock: manager.getHideThinkingBlock(),
385
+ shellPath: manager.getShellPath(),
386
+ collapseChangelog: manager.getCollapseChangelog(),
387
+ hooks: manager.getHookPaths(),
388
+ customTools: manager.getCustomToolPaths(),
389
+ skills: manager.getSkillsSettings(),
390
+ terminal: { showImages: manager.getShowImages() },
391
+ };
392
+ }
393
+
394
+ // Internal Helpers
395
+
396
+ /**
397
+ * Create a HookFactory from a LoadedHook.
398
+ * This allows mixing discovered hooks with inline hooks.
399
+ */
400
+ function createFactoryFromLoadedHook(loaded: LoadedHook): HookFactory {
401
+ return (api) => {
402
+ for (const [eventType, handlers] of loaded.handlers) {
403
+ for (const handler of handlers) {
404
+ api.on(eventType as any, handler as any);
405
+ }
406
+ }
407
+ };
408
+ }
409
+
410
+ /**
411
+ * Convert hook definitions to LoadedHooks for the HookRunner.
412
+ */
413
+ function createLoadedHooksFromDefinitions(definitions: Array<{ path?: string; factory: HookFactory }>): LoadedHook[] {
414
+ return definitions.map((def) => {
415
+ const handlers = new Map<string, Array<(...args: unknown[]) => Promise<unknown>>>();
416
+ const messageRenderers = new Map<string, any>();
417
+ const commands = new Map<string, any>();
418
+ let sendMessageHandler: (message: any, triggerTurn?: boolean) => void = () => {};
419
+ let appendEntryHandler: (customType: string, data?: any) => void = () => {};
420
+ let newSessionHandler: (options?: any) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false });
421
+ let branchHandler: (entryId: string) => Promise<{ cancelled: boolean }> = async () => ({ cancelled: false });
422
+ let navigateTreeHandler: (targetId: string, options?: any) => Promise<{ cancelled: boolean }> = async () => ({
423
+ cancelled: false,
424
+ });
425
+
426
+ const api = {
427
+ on: (event: string, handler: (...args: unknown[]) => Promise<unknown>) => {
428
+ const list = handlers.get(event) ?? [];
429
+ list.push(handler);
430
+ handlers.set(event, list);
431
+ },
432
+ sendMessage: (message: any, triggerTurn?: boolean) => {
433
+ sendMessageHandler(message, triggerTurn);
434
+ },
435
+ appendEntry: (customType: string, data?: any) => {
436
+ appendEntryHandler(customType, data);
437
+ },
438
+ registerMessageRenderer: (customType: string, renderer: any) => {
439
+ messageRenderers.set(customType, renderer);
440
+ },
441
+ registerCommand: (name: string, options: any) => {
442
+ commands.set(name, { name, ...options });
443
+ },
444
+ newSession: (options?: any) => newSessionHandler(options),
445
+ branch: (entryId: string) => branchHandler(entryId),
446
+ navigateTree: (targetId: string, options?: any) => navigateTreeHandler(targetId, options),
447
+ };
448
+
449
+ def.factory(api as any);
450
+
451
+ return {
452
+ path: def.path ?? "<inline>",
453
+ resolvedPath: def.path ?? "<inline>",
454
+ handlers,
455
+ messageRenderers,
456
+ commands,
457
+ setSendMessageHandler: (handler: (message: any, triggerTurn?: boolean) => void) => {
458
+ sendMessageHandler = handler;
459
+ },
460
+ setAppendEntryHandler: (handler: (customType: string, data?: any) => void) => {
461
+ appendEntryHandler = handler;
462
+ },
463
+ setNewSessionHandler: (handler: (options?: any) => Promise<{ cancelled: boolean }>) => {
464
+ newSessionHandler = handler;
465
+ },
466
+ setBranchHandler: (handler: (entryId: string) => Promise<{ cancelled: boolean }>) => {
467
+ branchHandler = handler;
468
+ },
469
+ setNavigateTreeHandler: (handler: (targetId: string, options?: any) => Promise<{ cancelled: boolean }>) => {
470
+ navigateTreeHandler = handler;
471
+ },
472
+ };
473
+ });
474
+ }
475
+
476
+ // Factory
477
+
478
+ /**
479
+ * Create an AgentSession with the specified options.
480
+ *
481
+ * @example
482
+ * ```typescript
483
+ * // Minimal - uses defaults
484
+ * const { session } = await createAgentSession();
485
+ *
486
+ * // With explicit model
487
+ * import { getModel } from '@oh-my-pi/pi-ai';
488
+ * const { session } = await createAgentSession({
489
+ * model: getModel('anthropic', 'claude-opus-4-5'),
490
+ * thinkingLevel: 'high',
491
+ * });
492
+ *
493
+ * // Continue previous session
494
+ * const { session, modelFallbackMessage } = await createAgentSession({
495
+ * continueSession: true,
496
+ * });
497
+ *
498
+ * // Full control
499
+ * const { session } = await createAgentSession({
500
+ * model: myModel,
501
+ * getApiKey: async () => process.env.MY_KEY,
502
+ * systemPrompt: 'You are helpful.',
503
+ * tools: [readTool, bashTool],
504
+ * hooks: [],
505
+ * skills: [],
506
+ * sessionManager: SessionManager.inMemory(),
507
+ * });
508
+ * ```
509
+ */
510
+ export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
511
+ const cwd = options.cwd ?? process.cwd();
512
+ const agentDir = options.agentDir ?? getDefaultAgentDir();
513
+
514
+ // Use provided or create AuthStorage and ModelRegistry
515
+ const authStorage = options.authStorage ?? discoverAuthStorage(agentDir);
516
+ const modelRegistry = options.modelRegistry ?? discoverModels(authStorage, agentDir);
517
+ time("discoverModels");
518
+
519
+ const settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);
520
+ time("settingsManager");
521
+
522
+ // Initialize discovery system with settings for provider persistence
523
+ const { initializeWithSettings } = await import("../discovery");
524
+ initializeWithSettings(settingsManager);
525
+ time("initializeWithSettings");
526
+
527
+ const sessionManager = options.sessionManager ?? SessionManager.create(cwd);
528
+ time("sessionManager");
529
+
530
+ // Check if session has existing data to restore
531
+ const existingSession = sessionManager.buildSessionContext();
532
+ time("loadSession");
533
+ const hasExistingSession = existingSession.messages.length > 0;
534
+
535
+ let model = options.model;
536
+ let modelFallbackMessage: string | undefined;
537
+
538
+ // If session has data, try to restore model from it
539
+ const defaultModelStr = existingSession.models.default;
540
+ if (!model && hasExistingSession && defaultModelStr) {
541
+ const slashIdx = defaultModelStr.indexOf("/");
542
+ if (slashIdx > 0) {
543
+ const provider = defaultModelStr.slice(0, slashIdx);
544
+ const modelId = defaultModelStr.slice(slashIdx + 1);
545
+ const restoredModel = modelRegistry.find(provider, modelId);
546
+ if (restoredModel && (await modelRegistry.getApiKey(restoredModel))) {
547
+ model = restoredModel;
548
+ }
549
+ if (!model) {
550
+ modelFallbackMessage = `Could not restore model ${defaultModelStr}`;
551
+ }
552
+ }
553
+ }
554
+
555
+ // If still no model, try settings default
556
+ if (!model) {
557
+ const settingsDefaultModel = settingsManager.getModelRole("default");
558
+ if (settingsDefaultModel) {
559
+ const slashIdx = settingsDefaultModel.indexOf("/");
560
+ if (slashIdx > 0) {
561
+ const provider = settingsDefaultModel.slice(0, slashIdx);
562
+ const modelId = settingsDefaultModel.slice(slashIdx + 1);
563
+ const settingsModel = modelRegistry.find(provider, modelId);
564
+ if (settingsModel && (await modelRegistry.getApiKey(settingsModel))) {
565
+ model = settingsModel;
566
+ }
567
+ }
568
+ }
569
+ }
570
+
571
+ // Fall back to first available model with a valid API key
572
+ if (!model) {
573
+ for (const m of modelRegistry.getAll()) {
574
+ if (await modelRegistry.getApiKey(m)) {
575
+ model = m;
576
+ break;
577
+ }
578
+ }
579
+ time("findAvailableModel");
580
+ if (model) {
581
+ if (modelFallbackMessage) {
582
+ modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
583
+ }
584
+ } else {
585
+ // No models available - set message so user knows to /login or configure keys
586
+ modelFallbackMessage = "No models available. Use /login or set an API key environment variable.";
587
+ }
588
+ }
589
+
590
+ let thinkingLevel = options.thinkingLevel;
591
+
592
+ // If session has data, restore thinking level from it
593
+ if (thinkingLevel === undefined && hasExistingSession) {
594
+ thinkingLevel = existingSession.thinkingLevel as ThinkingLevel;
595
+ }
596
+
597
+ // Fall back to settings default
598
+ if (thinkingLevel === undefined) {
599
+ thinkingLevel = settingsManager.getDefaultThinkingLevel() ?? "off";
600
+ }
601
+
602
+ // Clamp to model capabilities
603
+ if (!model || !model.reasoning) {
604
+ thinkingLevel = "off";
605
+ }
606
+
607
+ const skills = options.skills ?? discoverSkills(cwd, agentDir, settingsManager.getSkillsSettings());
608
+ time("discoverSkills");
609
+
610
+ // Discover rules
611
+ const ttsrManager = createTtsrManager(settingsManager.getTtsrSettings());
612
+ const rulesResult = loadCapability<Rule>(ruleCapability.id, { cwd });
613
+ for (const rule of rulesResult.items) {
614
+ if (rule.ttsrTrigger) {
615
+ ttsrManager.addRule(rule);
616
+ }
617
+ }
618
+ time("discoverTtsrRules");
619
+
620
+ // Filter rules for the rulebook (non-TTSR, non-alwaysApply, with descriptions)
621
+ const rulebookRules = filterRulebookRules(rulesResult.items);
622
+ time("filterRulebookRules");
623
+
624
+ const contextFiles = options.contextFiles ?? discoverContextFiles(cwd, agentDir);
625
+ time("discoverContextFiles");
626
+
627
+ // Hook runner - always created (needed for custom command context even without hooks)
628
+ let loadedHooks: LoadedHook[] = [];
629
+ if (options.hooks !== undefined) {
630
+ if (options.hooks.length > 0) {
631
+ loadedHooks = createLoadedHooksFromDefinitions(options.hooks);
632
+ }
633
+ } else {
634
+ // Discover hooks, merging with additional paths
635
+ const configuredPaths = [...settingsManager.getHookPaths(), ...(options.additionalHookPaths ?? [])];
636
+ const { hooks, errors } = await discoverAndLoadHooks(configuredPaths, cwd);
637
+ time("discoverAndLoadHooks");
638
+ for (const { path, error } of errors) {
639
+ console.error(`Failed to load hook "${path}": ${error}`);
640
+ }
641
+ loadedHooks = hooks;
642
+ }
643
+ const hookRunner = new HookRunner(loadedHooks, cwd, sessionManager, modelRegistry);
644
+
645
+ const sessionContext = {
646
+ getSessionFile: () => sessionManager.getSessionFile() ?? null,
647
+ };
648
+ const builtInTools =
649
+ options.tools ??
650
+ createCodingTools(cwd, options.hasUI ?? false, sessionContext, {
651
+ lspFormatOnWrite: settingsManager.getLspFormatOnWrite(),
652
+ lspDiagnosticsOnWrite: settingsManager.getLspDiagnosticsOnWrite(),
653
+ lspDiagnosticsOnEdit: settingsManager.getLspDiagnosticsOnEdit(),
654
+ editFuzzyMatch: settingsManager.getEditFuzzyMatch(),
655
+ });
656
+ time("createCodingTools");
657
+
658
+ let customToolsResult: CustomToolsLoadResult;
659
+ if (options.customTools !== undefined) {
660
+ // Use provided custom tools
661
+ const loadedTools: LoadedCustomTool[] = options.customTools.map((ct) => ({
662
+ path: ct.path ?? "<inline>",
663
+ resolvedPath: ct.path ?? "<inline>",
664
+ tool: ct.tool,
665
+ }));
666
+ customToolsResult = {
667
+ tools: loadedTools,
668
+ errors: [],
669
+ setUIContext: () => {},
670
+ };
671
+ } else {
672
+ // Discover custom tools, merging with additional paths
673
+ const configuredPaths = [...settingsManager.getCustomToolPaths(), ...(options.additionalCustomToolPaths ?? [])];
674
+ customToolsResult = await discoverAndLoadCustomTools(configuredPaths, cwd, Object.keys(allTools));
675
+ time("discoverAndLoadCustomTools");
676
+ for (const { path, error } of customToolsResult.errors) {
677
+ console.error(`Failed to load custom tool "${path}": ${error}`);
678
+ }
679
+ }
680
+
681
+ // Discover MCP tools from .mcp.json files
682
+ let mcpManager: MCPManager | undefined;
683
+ const enableMCP = options.enableMCP ?? true;
684
+ if (enableMCP) {
685
+ const mcpResult = await discoverAndLoadMCPTools(cwd, {
686
+ onConnecting: (serverNames) => {
687
+ if (options.hasUI && serverNames.length > 0) {
688
+ process.stderr.write(chalk.gray(`Connecting to MCP servers: ${serverNames.join(", ")}...\n`));
689
+ }
690
+ },
691
+ enableProjectConfig: settingsManager.getMCPProjectConfigEnabled(),
692
+ // Always filter Exa - we have native integration
693
+ filterExa: true,
694
+ });
695
+ time("discoverAndLoadMCPTools");
696
+ mcpManager = mcpResult.manager;
697
+
698
+ // If we extracted Exa API keys from MCP configs and EXA_API_KEY isn't set, use the first one
699
+ if (mcpResult.exaApiKeys.length > 0 && !process.env.EXA_API_KEY) {
700
+ process.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
701
+ }
702
+
703
+ // Log MCP errors
704
+ for (const { path, error } of mcpResult.errors) {
705
+ console.error(`MCP "${path}": ${error}`);
706
+ }
707
+
708
+ // Merge MCP tools into custom tools result
709
+ if (mcpResult.tools.length > 0) {
710
+ customToolsResult = {
711
+ ...customToolsResult,
712
+ tools: [...customToolsResult.tools, ...mcpResult.tools],
713
+ };
714
+ }
715
+ }
716
+
717
+ // Add specialized Exa web search tools if EXA_API_KEY is available
718
+ const exaSettings = settingsManager.getExaSettings();
719
+ if (exaSettings.enabled && exaSettings.enableSearch) {
720
+ const { getWebSearchTools } = await import("./tools/web-search/index.js");
721
+ const exaWebSearchTools = await getWebSearchTools({
722
+ enableLinkedin: exaSettings.enableLinkedin,
723
+ enableCompany: exaSettings.enableCompany,
724
+ });
725
+ // Filter out the base web_search (already in built-in tools), add specialized Exa tools
726
+ const specializedTools = exaWebSearchTools.filter((t) => t.name !== "web_search");
727
+ if (specializedTools.length > 0) {
728
+ const loadedExaTools: LoadedCustomTool[] = specializedTools.map((tool) => ({
729
+ path: "<exa>",
730
+ resolvedPath: "<exa>",
731
+ tool,
732
+ source: { provider: "builtin", providerName: "builtin", level: "user" },
733
+ }));
734
+ customToolsResult = {
735
+ ...customToolsResult,
736
+ tools: [...customToolsResult.tools, ...loadedExaTools],
737
+ };
738
+ }
739
+ time("getWebSearchTools");
740
+ }
741
+
742
+ let agent: Agent;
743
+ let session: AgentSession;
744
+ const getSessionContext = () => ({
745
+ sessionManager,
746
+ modelRegistry,
747
+ model: agent.state.model,
748
+ isIdle: () => !session.isStreaming,
749
+ hasQueuedMessages: () => session.queuedMessageCount > 0,
750
+ abort: () => {
751
+ session.abort();
752
+ },
753
+ });
754
+ const toolContextStore = createToolContextStore(getSessionContext);
755
+ const wrappedCustomTools = wrapCustomTools(customToolsResult.tools, getSessionContext);
756
+ const baseSetUIContext = customToolsResult.setUIContext;
757
+ customToolsResult = {
758
+ ...customToolsResult,
759
+ setUIContext: (uiContext, hasUI) => {
760
+ toolContextStore.setUIContext(uiContext, hasUI);
761
+ baseSetUIContext(uiContext, hasUI);
762
+ },
763
+ };
764
+
765
+ let allToolsArray: Tool[] = [...builtInTools, ...wrappedCustomTools];
766
+
767
+ // Add rulebook tool if there are rules with descriptions (always enabled, regardless of --tools)
768
+ if (rulebookRules.length > 0) {
769
+ allToolsArray.push(createRulebookTool(rulebookRules));
770
+ }
771
+
772
+ // Filter out hidden tools unless explicitly requested
773
+ if (options.explicitTools) {
774
+ const explicitSet = new Set(options.explicitTools);
775
+ allToolsArray = allToolsArray.filter((tool) => !tool.hidden || explicitSet.has(tool.name));
776
+ } else {
777
+ allToolsArray = allToolsArray.filter((tool) => !tool.hidden);
778
+ }
779
+ time("combineTools");
780
+
781
+ // Apply bash interception to redirect common shell patterns to proper tools (if enabled)
782
+ if (settingsManager.getBashInterceptorEnabled()) {
783
+ allToolsArray = applyBashInterception(allToolsArray);
784
+ }
785
+ time("applyBashInterception");
786
+
787
+ if (hookRunner) {
788
+ allToolsArray = wrapToolsWithHooks(allToolsArray, hookRunner) as Tool[];
789
+ }
790
+
791
+ let systemPrompt: string;
792
+ const defaultPrompt = buildSystemPromptInternal({
793
+ cwd,
794
+ skills,
795
+ contextFiles,
796
+ rulebookRules,
797
+ });
798
+ time("buildSystemPrompt");
799
+
800
+ if (options.systemPrompt === undefined) {
801
+ systemPrompt = defaultPrompt;
802
+ } else if (typeof options.systemPrompt === "string") {
803
+ systemPrompt = buildSystemPromptInternal({
804
+ cwd,
805
+ skills,
806
+ contextFiles,
807
+ rulebookRules,
808
+ customPrompt: options.systemPrompt,
809
+ });
810
+ } else {
811
+ systemPrompt = options.systemPrompt(defaultPrompt);
812
+ }
813
+
814
+ const commandsSettings = settingsManager.getCommandsSettings();
815
+ const slashCommands = options.slashCommands ?? discoverSlashCommands(cwd, agentDir, commandsSettings);
816
+ time("discoverSlashCommands");
817
+
818
+ // Discover custom commands (TypeScript slash commands)
819
+ const customCommandsResult = await loadCustomCommandsInternal({ cwd, agentDir });
820
+ time("discoverCustomCommands");
821
+ for (const { path, error } of customCommandsResult.errors) {
822
+ console.error(`Failed to load custom command "${path}": ${error}`);
823
+ }
824
+
825
+ agent = new Agent({
826
+ initialState: {
827
+ systemPrompt,
828
+ model,
829
+ thinkingLevel,
830
+ tools: allToolsArray,
831
+ },
832
+ convertToLlm,
833
+ transformContext: hookRunner
834
+ ? async (messages) => {
835
+ return hookRunner.emitContext(messages);
836
+ }
837
+ : undefined,
838
+ queueMode: settingsManager.getQueueMode(),
839
+ interruptMode: settingsManager.getInterruptMode(),
840
+ getToolContext: toolContextStore.getContext,
841
+ getApiKey: async () => {
842
+ const currentModel = agent.state.model;
843
+ if (!currentModel) {
844
+ throw new Error("No model selected");
845
+ }
846
+ const key = await modelRegistry.getApiKey(currentModel);
847
+ if (!key) {
848
+ throw new Error(`No API key found for provider "${currentModel.provider}"`);
849
+ }
850
+ return key;
851
+ },
852
+ });
853
+ time("createAgent");
854
+
855
+ // Restore messages if session has existing data
856
+ if (hasExistingSession) {
857
+ agent.replaceMessages(existingSession.messages);
858
+ } else {
859
+ // Save initial model and thinking level for new sessions so they can be restored on resume
860
+ if (model) {
861
+ sessionManager.appendModelChange(`${model.provider}/${model.id}`);
862
+ }
863
+ sessionManager.appendThinkingLevelChange(thinkingLevel);
864
+ }
865
+
866
+ session = new AgentSession({
867
+ agent,
868
+ sessionManager,
869
+ settingsManager,
870
+ scopedModels: options.scopedModels,
871
+ fileCommands: slashCommands,
872
+ hookRunner,
873
+ customTools: customToolsResult.tools,
874
+ customCommands: customCommandsResult.commands,
875
+ skillsSettings: settingsManager.getSkillsSettings(),
876
+ modelRegistry,
877
+ ttsrManager,
878
+ });
879
+ time("createAgentSession");
880
+
881
+ // Warm up LSP servers (connects to detected servers)
882
+ let lspServers: CreateAgentSessionResult["lspServers"];
883
+ if (settingsManager.getLspDiagnosticsOnWrite()) {
884
+ try {
885
+ const result = await warmupLspServers(cwd);
886
+ lspServers = result.servers;
887
+ time("warmupLspServers");
888
+ } catch {
889
+ // Ignore warmup errors
890
+ }
891
+ }
892
+
893
+ return {
894
+ session,
895
+ customToolsResult,
896
+ mcpManager,
897
+ modelFallbackMessage,
898
+ lspServers,
899
+ };
900
+ }