@oh-my-pi/pi-coding-agent 8.0.20 → 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 (421) hide show
  1. package/CHANGELOG.md +125 -0
  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 +54 -23
  38. package/scripts/format-prompts.ts +0 -1
  39. package/src/capability/context-file.ts +3 -4
  40. package/src/capability/extension-module.ts +3 -4
  41. package/src/capability/extension.ts +3 -4
  42. package/src/capability/fs.ts +20 -21
  43. package/src/capability/hook.ts +3 -4
  44. package/src/capability/index.ts +15 -16
  45. package/src/capability/instruction.ts +3 -4
  46. package/src/capability/mcp.ts +3 -4
  47. package/src/capability/prompt.ts +3 -4
  48. package/src/capability/rule.ts +3 -4
  49. package/src/capability/settings.ts +2 -3
  50. package/src/capability/skill.ts +3 -4
  51. package/src/capability/slash-command.ts +3 -4
  52. package/src/capability/ssh.ts +3 -4
  53. package/src/capability/system-prompt.ts +3 -4
  54. package/src/capability/tool.ts +3 -4
  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 -25
  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 +307 -0
  66. package/src/commit/agentic/fallback.ts +96 -0
  67. package/src/commit/agentic/index.ts +351 -0
  68. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  69. package/src/commit/agentic/prompts/session-user.md +26 -0
  70. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  71. package/src/commit/agentic/prompts/system.md +40 -0
  72. package/src/commit/agentic/state.ts +69 -0
  73. package/src/commit/agentic/tools/analyze-file.ts +131 -0
  74. package/src/commit/agentic/tools/git-file-diff.ts +194 -0
  75. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  76. package/src/commit/agentic/tools/git-overview.ts +84 -0
  77. package/src/commit/agentic/tools/index.ts +56 -0
  78. package/src/commit/agentic/tools/propose-changelog.ts +128 -0
  79. package/src/commit/agentic/tools/propose-commit.ts +154 -0
  80. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  81. package/src/commit/agentic/tools/split-commit.ts +280 -0
  82. package/src/commit/agentic/topo-sort.ts +44 -0
  83. package/src/commit/agentic/trivial.ts +51 -0
  84. package/src/commit/agentic/validation.ts +200 -0
  85. package/src/commit/analysis/conventional.ts +165 -0
  86. package/src/commit/analysis/index.ts +4 -0
  87. package/src/commit/analysis/scope.ts +242 -0
  88. package/src/commit/analysis/summary.ts +112 -0
  89. package/src/commit/analysis/validation.ts +66 -0
  90. package/src/commit/changelog/detect.ts +36 -0
  91. package/src/commit/changelog/generate.ts +110 -0
  92. package/src/commit/changelog/index.ts +233 -0
  93. package/src/commit/changelog/parse.ts +44 -0
  94. package/src/commit/cli.ts +93 -0
  95. package/src/commit/git/diff.ts +148 -0
  96. package/src/commit/git/errors.ts +11 -0
  97. package/src/commit/git/index.ts +212 -0
  98. package/src/commit/git/operations.ts +53 -0
  99. package/src/commit/index.ts +5 -0
  100. package/src/commit/map-reduce/index.ts +63 -0
  101. package/src/commit/map-reduce/map-phase.ts +178 -0
  102. package/src/commit/map-reduce/reduce-phase.ts +145 -0
  103. package/src/commit/map-reduce/utils.ts +9 -0
  104. package/src/commit/message.ts +11 -0
  105. package/src/commit/model-selection.ts +80 -0
  106. package/src/commit/pipeline.ts +240 -0
  107. package/src/commit/prompts/analysis-system.md +155 -0
  108. package/src/commit/prompts/analysis-user.md +41 -0
  109. package/src/commit/prompts/changelog-system.md +56 -0
  110. package/src/commit/prompts/changelog-user.md +19 -0
  111. package/src/commit/prompts/file-observer-system.md +26 -0
  112. package/src/commit/prompts/file-observer-user.md +9 -0
  113. package/src/commit/prompts/reduce-system.md +60 -0
  114. package/src/commit/prompts/reduce-user.md +17 -0
  115. package/src/commit/prompts/summary-retry.md +4 -0
  116. package/src/commit/prompts/summary-system.md +52 -0
  117. package/src/commit/prompts/summary-user.md +13 -0
  118. package/src/commit/prompts/types-description.md +2 -0
  119. package/src/commit/types.ts +109 -0
  120. package/src/commit/utils/exclusions.ts +42 -0
  121. package/src/config/file-lock.ts +121 -0
  122. package/src/config/keybindings.ts +6 -8
  123. package/src/config/model-registry.ts +65 -38
  124. package/src/config/model-resolver.ts +18 -19
  125. package/src/config/prompt-templates.ts +11 -11
  126. package/src/config/settings-manager.ts +141 -50
  127. package/src/config.ts +64 -66
  128. package/src/cursor.ts +11 -9
  129. package/src/discovery/agents-md.ts +11 -12
  130. package/src/discovery/builtin.ts +68 -73
  131. package/src/discovery/claude.ts +41 -42
  132. package/src/discovery/cline.ts +11 -12
  133. package/src/discovery/codex.ts +52 -53
  134. package/src/discovery/cursor.ts +9 -10
  135. package/src/discovery/gemini.ts +17 -22
  136. package/src/discovery/github.ts +13 -14
  137. package/src/discovery/helpers.ts +35 -34
  138. package/src/discovery/index.ts +22 -24
  139. package/src/discovery/mcp-json.ts +8 -9
  140. package/src/discovery/ssh.ts +8 -9
  141. package/src/discovery/vscode.ts +4 -5
  142. package/src/discovery/windsurf.ts +6 -7
  143. package/src/exa/company.ts +1 -2
  144. package/src/exa/index.ts +2 -3
  145. package/src/exa/linkedin.ts +1 -2
  146. package/src/exa/mcp-client.ts +14 -16
  147. package/src/exa/render.ts +10 -11
  148. package/src/exa/researcher.ts +1 -2
  149. package/src/exa/search.ts +1 -2
  150. package/src/exa/types.ts +0 -1
  151. package/src/exa/websets.ts +1 -2
  152. package/src/exec/bash-executor.ts +3 -4
  153. package/src/exec/exec.ts +0 -1
  154. package/src/export/custom-share.ts +5 -6
  155. package/src/export/html/index.ts +24 -21
  156. package/src/export/ttsr.ts +2 -3
  157. package/src/extensibility/custom-commands/bundled/review/index.ts +7 -8
  158. package/src/extensibility/custom-commands/loader.ts +18 -15
  159. package/src/extensibility/custom-commands/types.ts +2 -3
  160. package/src/extensibility/custom-tools/loader.ts +11 -12
  161. package/src/extensibility/custom-tools/types.ts +7 -8
  162. package/src/extensibility/custom-tools/wrapper.ts +2 -3
  163. package/src/extensibility/extensions/loader.ts +76 -54
  164. package/src/extensibility/extensions/runner.ts +11 -12
  165. package/src/extensibility/extensions/types.ts +20 -27
  166. package/src/extensibility/extensions/wrapper.ts +3 -4
  167. package/src/extensibility/hooks/index.ts +1 -1
  168. package/src/extensibility/hooks/loader.ts +9 -10
  169. package/src/extensibility/hooks/runner.ts +7 -8
  170. package/src/extensibility/hooks/tool-wrapper.ts +0 -1
  171. package/src/extensibility/hooks/types.ts +11 -18
  172. package/src/extensibility/plugins/doctor.ts +3 -3
  173. package/src/extensibility/plugins/installer.ts +27 -27
  174. package/src/extensibility/plugins/loader.ts +59 -56
  175. package/src/extensibility/plugins/manager.ts +211 -171
  176. package/src/extensibility/plugins/parser.ts +1 -1
  177. package/src/extensibility/plugins/paths.ts +8 -8
  178. package/src/extensibility/skills.ts +63 -60
  179. package/src/extensibility/slash-commands.ts +10 -10
  180. package/src/index.ts +54 -54
  181. package/src/internal-urls/agent-protocol.ts +21 -11
  182. package/src/internal-urls/artifact-protocol.ts +17 -13
  183. package/src/internal-urls/router.ts +1 -2
  184. package/src/internal-urls/rule-protocol.ts +3 -4
  185. package/src/internal-urls/skill-protocol.ts +3 -4
  186. package/src/ipy/executor.ts +109 -9
  187. package/src/ipy/gateway-coordinator.ts +79 -90
  188. package/src/ipy/kernel.ts +32 -30
  189. package/src/ipy/modules.ts +13 -13
  190. package/src/lsp/client.ts +21 -10
  191. package/src/lsp/clients/biome-client.ts +1 -2
  192. package/src/lsp/clients/index.ts +3 -3
  193. package/src/lsp/clients/lsp-linter-client.ts +4 -5
  194. package/src/lsp/config.ts +15 -15
  195. package/src/lsp/edits.ts +4 -5
  196. package/src/lsp/index.ts +43 -44
  197. package/src/lsp/lspmux.ts +8 -8
  198. package/src/lsp/render.ts +99 -61
  199. package/src/lsp/utils.ts +3 -3
  200. package/src/main.ts +71 -37
  201. package/src/mcp/client.ts +2 -3
  202. package/src/mcp/config.ts +5 -6
  203. package/src/mcp/json-rpc.ts +0 -1
  204. package/src/mcp/loader.ts +6 -7
  205. package/src/mcp/manager.ts +17 -18
  206. package/src/mcp/tool-bridge.ts +4 -9
  207. package/src/mcp/tool-cache.ts +2 -3
  208. package/src/mcp/transports/http.ts +2 -4
  209. package/src/mcp/transports/stdio.ts +1 -2
  210. package/src/migrations.ts +63 -52
  211. package/src/modes/components/armin.ts +4 -5
  212. package/src/modes/components/assistant-message.ts +33 -5
  213. package/src/modes/components/bash-execution.ts +7 -8
  214. package/src/modes/components/bordered-loader.ts +3 -3
  215. package/src/modes/components/branch-summary-message.ts +3 -3
  216. package/src/modes/components/compaction-summary-message.ts +3 -3
  217. package/src/modes/components/countdown-timer.ts +0 -1
  218. package/src/modes/components/custom-message.ts +5 -5
  219. package/src/modes/components/diff.ts +1 -1
  220. package/src/modes/components/dynamic-border.ts +2 -2
  221. package/src/modes/components/extensions/extension-dashboard.ts +6 -7
  222. package/src/modes/components/extensions/extension-list.ts +2 -3
  223. package/src/modes/components/extensions/inspector-panel.ts +3 -4
  224. package/src/modes/components/extensions/state-manager.ts +25 -26
  225. package/src/modes/components/extensions/types.ts +1 -2
  226. package/src/modes/components/footer.ts +47 -43
  227. package/src/modes/components/history-search.ts +2 -2
  228. package/src/modes/components/hook-editor.ts +3 -4
  229. package/src/modes/components/hook-input.ts +2 -3
  230. package/src/modes/components/hook-message.ts +5 -5
  231. package/src/modes/components/hook-selector.ts +2 -3
  232. package/src/modes/components/keybinding-hints.ts +2 -3
  233. package/src/modes/components/login-dialog.ts +2 -2
  234. package/src/modes/components/model-selector.ts +12 -12
  235. package/src/modes/components/oauth-selector.ts +2 -2
  236. package/src/modes/components/plugin-settings.ts +20 -20
  237. package/src/modes/components/python-execution.ts +7 -8
  238. package/src/modes/components/queue-mode-selector.ts +3 -3
  239. package/src/modes/components/read-tool-group.ts +2 -2
  240. package/src/modes/components/session-selector.ts +4 -4
  241. package/src/modes/components/settings-defs.ts +77 -69
  242. package/src/modes/components/settings-selector.ts +16 -16
  243. package/src/modes/components/show-images-selector.ts +2 -2
  244. package/src/modes/components/status-line/segments.ts +4 -4
  245. package/src/modes/components/status-line/separators.ts +1 -1
  246. package/src/modes/components/status-line/types.ts +2 -2
  247. package/src/modes/components/status-line-segment-editor.ts +7 -8
  248. package/src/modes/components/status-line.ts +12 -12
  249. package/src/modes/components/theme-selector.ts +8 -7
  250. package/src/modes/components/thinking-selector.ts +4 -4
  251. package/src/modes/components/todo-display.ts +2 -2
  252. package/src/modes/components/todo-reminder.ts +4 -4
  253. package/src/modes/components/tool-execution.ts +16 -19
  254. package/src/modes/components/tree-selector.ts +12 -12
  255. package/src/modes/components/ttsr-notification.ts +5 -5
  256. package/src/modes/components/user-message-selector.ts +1 -1
  257. package/src/modes/components/user-message.ts +1 -1
  258. package/src/modes/components/visual-truncate.ts +0 -1
  259. package/src/modes/components/welcome.ts +4 -4
  260. package/src/modes/controllers/command-controller.ts +46 -47
  261. package/src/modes/controllers/event-controller.ts +16 -20
  262. package/src/modes/controllers/extension-ui-controller.ts +40 -46
  263. package/src/modes/controllers/input-controller.ts +17 -18
  264. package/src/modes/controllers/selector-controller.ts +103 -91
  265. package/src/modes/index.ts +3 -3
  266. package/src/modes/interactive-mode.ts +31 -31
  267. package/src/modes/print-mode.ts +12 -13
  268. package/src/modes/rpc/rpc-client.ts +7 -8
  269. package/src/modes/rpc/rpc-mode.ts +24 -28
  270. package/src/modes/rpc/rpc-types.ts +3 -4
  271. package/src/modes/theme/mermaid-cache.ts +89 -0
  272. package/src/modes/theme/theme.ts +130 -53
  273. package/src/modes/types.ts +10 -10
  274. package/src/modes/utils/ui-helpers.ts +17 -17
  275. package/src/patch/applicator.ts +18 -19
  276. package/src/patch/diff.ts +1 -2
  277. package/src/patch/fuzzy.ts +1 -2
  278. package/src/patch/index.ts +11 -18
  279. package/src/patch/normalize.ts +4 -4
  280. package/src/patch/normative.ts +1 -2
  281. package/src/patch/parser.ts +8 -9
  282. package/src/patch/shared.ts +43 -16
  283. package/src/prompts/tools/task.md +2 -0
  284. package/src/sdk.ts +100 -65
  285. package/src/session/agent-session.ts +84 -85
  286. package/src/session/agent-storage.ts +43 -39
  287. package/src/session/artifacts.ts +32 -10
  288. package/src/session/auth-storage.ts +50 -39
  289. package/src/session/compaction/branch-summarization.ts +7 -10
  290. package/src/session/compaction/compaction.ts +8 -19
  291. package/src/session/compaction/utils.ts +6 -9
  292. package/src/session/history-storage.ts +10 -10
  293. package/src/session/messages.ts +4 -5
  294. package/src/session/session-manager.ts +76 -65
  295. package/src/session/session-storage.ts +57 -69
  296. package/src/session/storage-migration.ts +14 -56
  297. package/src/session/streaming-output.ts +2 -2
  298. package/src/ssh/connection-manager.ts +43 -50
  299. package/src/ssh/ssh-executor.ts +2 -2
  300. package/src/ssh/sshfs-mount.ts +11 -18
  301. package/src/system-prompt.ts +28 -35
  302. package/src/task/agents.ts +45 -30
  303. package/src/task/commands.ts +6 -7
  304. package/src/task/discovery.ts +39 -76
  305. package/src/task/executor.ts +14 -15
  306. package/src/task/index.ts +40 -34
  307. package/src/task/output-manager.ts +93 -0
  308. package/src/task/parallel.ts +0 -1
  309. package/src/task/render.ts +24 -30
  310. package/src/task/subprocess-tool-registry.ts +1 -2
  311. package/src/task/worker-protocol.ts +3 -3
  312. package/src/task/worker.ts +33 -39
  313. package/src/task/worktree.ts +19 -19
  314. package/src/tools/ask.ts +41 -20
  315. package/src/tools/bash-interceptor.ts +1 -5
  316. package/src/tools/bash.ts +91 -97
  317. package/src/tools/calculator.ts +49 -47
  318. package/src/tools/complete.ts +4 -5
  319. package/src/tools/context.ts +2 -2
  320. package/src/tools/fetch.ts +84 -124
  321. package/src/tools/find.ts +94 -98
  322. package/src/tools/gemini-image.ts +14 -14
  323. package/src/tools/grep.ts +100 -116
  324. package/src/tools/index.ts +80 -55
  325. package/src/tools/list-limit.ts +1 -1
  326. package/src/tools/ls.ts +44 -70
  327. package/src/tools/notebook.ts +51 -67
  328. package/src/tools/output-meta.ts +3 -4
  329. package/src/tools/output-utils.ts +2 -2
  330. package/src/tools/path-utils.ts +5 -5
  331. package/src/tools/python.ts +104 -217
  332. package/src/tools/read.ts +92 -33
  333. package/src/tools/render-utils.ts +8 -23
  334. package/src/tools/renderers.ts +6 -7
  335. package/src/tools/review.ts +8 -11
  336. package/src/tools/ssh.ts +69 -49
  337. package/src/tools/todo-write.ts +37 -25
  338. package/src/tools/tool-errors.ts +3 -3
  339. package/src/tools/tool-result.ts +3 -8
  340. package/src/tools/write.ts +99 -75
  341. package/src/tui/code-cell.ts +109 -0
  342. package/src/tui/file-list.ts +47 -0
  343. package/src/tui/index.ts +11 -0
  344. package/src/tui/output-block.ts +72 -0
  345. package/src/tui/status-line.ts +39 -0
  346. package/src/tui/tree-list.ts +55 -0
  347. package/src/tui/types.ts +16 -0
  348. package/src/tui/utils.ts +48 -0
  349. package/src/utils/changelog.ts +9 -10
  350. package/src/utils/clipboard.ts +11 -11
  351. package/src/utils/file-mentions.ts +4 -10
  352. package/src/utils/frontmatter.ts +6 -3
  353. package/src/utils/fuzzy.ts +2 -2
  354. package/src/utils/image-convert.ts +1 -1
  355. package/src/utils/image-resize.ts +1 -1
  356. package/src/utils/mime.ts +2 -2
  357. package/src/utils/shell-snapshot.ts +11 -13
  358. package/src/utils/shell.ts +4 -5
  359. package/src/utils/title-generator.ts +8 -9
  360. package/src/utils/tools-manager.ts +23 -23
  361. package/src/vendor/photon/index.js +1099 -1059
  362. package/src/vendor/photon/photon_rs_bg.wasm +0 -0
  363. package/src/web/scrapers/artifacthub.ts +1 -1
  364. package/src/web/scrapers/arxiv.ts +2 -2
  365. package/src/web/scrapers/bluesky.ts +2 -2
  366. package/src/web/scrapers/cheatsh.ts +1 -1
  367. package/src/web/scrapers/chocolatey.ts +2 -2
  368. package/src/web/scrapers/choosealicense.ts +5 -5
  369. package/src/web/scrapers/cisa-kev.ts +1 -1
  370. package/src/web/scrapers/crossref.ts +2 -2
  371. package/src/web/scrapers/devto.ts +3 -3
  372. package/src/web/scrapers/discogs.ts +3 -4
  373. package/src/web/scrapers/discourse.ts +1 -1
  374. package/src/web/scrapers/dockerhub.ts +1 -1
  375. package/src/web/scrapers/fdroid.ts +2 -2
  376. package/src/web/scrapers/firefox-addons.ts +3 -3
  377. package/src/web/scrapers/flathub.ts +1 -1
  378. package/src/web/scrapers/github.ts +3 -3
  379. package/src/web/scrapers/gitlab.ts +4 -4
  380. package/src/web/scrapers/hackernews.ts +2 -2
  381. package/src/web/scrapers/huggingface.ts +1 -1
  382. package/src/web/scrapers/iacr.ts +2 -2
  383. package/src/web/scrapers/index.ts +0 -1
  384. package/src/web/scrapers/jetbrains-marketplace.ts +1 -1
  385. package/src/web/scrapers/lemmy.ts +2 -2
  386. package/src/web/scrapers/maven.ts +2 -2
  387. package/src/web/scrapers/mdn.ts +2 -4
  388. package/src/web/scrapers/metacpan.ts +2 -2
  389. package/src/web/scrapers/musicbrainz.ts +1 -2
  390. package/src/web/scrapers/npm.ts +1 -1
  391. package/src/web/scrapers/nuget.ts +2 -2
  392. package/src/web/scrapers/nvd.ts +3 -3
  393. package/src/web/scrapers/ollama.ts +7 -9
  394. package/src/web/scrapers/opencorporates.ts +2 -2
  395. package/src/web/scrapers/openlibrary.ts +6 -6
  396. package/src/web/scrapers/orcid.ts +0 -1
  397. package/src/web/scrapers/osv.ts +2 -2
  398. package/src/web/scrapers/packagist.ts +1 -1
  399. package/src/web/scrapers/pubmed.ts +1 -2
  400. package/src/web/scrapers/rawg.ts +2 -2
  401. package/src/web/scrapers/readthedocs.ts +1 -2
  402. package/src/web/scrapers/repology.ts +2 -2
  403. package/src/web/scrapers/rfc.ts +1 -1
  404. package/src/web/scrapers/searchcode.ts +2 -2
  405. package/src/web/scrapers/semantic-scholar.ts +1 -1
  406. package/src/web/scrapers/snapcraft.ts +2 -2
  407. package/src/web/scrapers/sourcegraph.ts +1 -1
  408. package/src/web/scrapers/spdx.ts +3 -3
  409. package/src/web/scrapers/spotify.ts +0 -1
  410. package/src/web/scrapers/twitter.ts +1 -1
  411. package/src/web/scrapers/types.ts +1 -2
  412. package/src/web/scrapers/utils.ts +5 -5
  413. package/src/web/scrapers/wikidata.ts +3 -3
  414. package/src/web/scrapers/youtube.ts +9 -14
  415. package/src/web/search/auth.ts +5 -10
  416. package/src/web/search/index.ts +11 -21
  417. package/src/web/search/providers/anthropic.ts +3 -9
  418. package/src/web/search/providers/exa.ts +6 -10
  419. package/src/web/search/providers/perplexity.ts +5 -5
  420. package/src/web/search/render.ts +129 -175
  421. package/tsconfig.json +0 -42
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Model resolution, scoping, and initial selection
3
3
  */
4
-
5
4
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
6
5
  import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
7
- import { isValidThinkingLevel } from "@oh-my-pi/pi-coding-agent/cli/args";
8
6
  import chalk from "chalk";
7
+ import { isValidThinkingLevel } from "../cli/args";
9
8
  import type { ModelRegistry } from "./model-registry";
10
9
 
11
10
  /** Default model IDs for each known provider */
@@ -84,7 +83,7 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
84
83
  const provider = modelPattern.substring(0, slashIndex);
85
84
  const modelId = modelPattern.substring(slashIndex + 1);
86
85
  const providerMatch = availableModels.find(
87
- (m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase(),
86
+ m => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase(),
88
87
  );
89
88
  if (providerMatch) {
90
89
  return providerMatch;
@@ -93,14 +92,14 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
93
92
  }
94
93
 
95
94
  // Check for exact ID match (case-insensitive)
96
- const exactMatch = availableModels.find((m) => m.id.toLowerCase() === modelPattern.toLowerCase());
95
+ const exactMatch = availableModels.find(m => m.id.toLowerCase() === modelPattern.toLowerCase());
97
96
  if (exactMatch) {
98
97
  return exactMatch;
99
98
  }
100
99
 
101
100
  // No exact match - fall back to partial matching
102
101
  const matches = availableModels.filter(
103
- (m) =>
102
+ m =>
104
103
  m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||
105
104
  m.name?.toLowerCase().includes(modelPattern.toLowerCase()),
106
105
  );
@@ -110,8 +109,8 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
110
109
  }
111
110
 
112
111
  // Separate into aliases and dated versions
113
- const aliases = matches.filter((m) => isAlias(m.id));
114
- const datedVersions = matches.filter((m) => !isAlias(m.id));
112
+ const aliases = matches.filter(m => isAlias(m.id));
113
+ const datedVersions = matches.filter(m => !isAlias(m.id));
115
114
 
116
115
  if (aliases.length > 0) {
117
116
  // Prefer alias - if multiple aliases, pick the one that sorts highest
@@ -226,7 +225,7 @@ export async function resolveModelScope(patterns: string[], modelRegistry: Model
226
225
 
227
226
  // Match against "provider/modelId" format OR just model ID
228
227
  // This allows "*sonnet*" to match without requiring "anthropic/*sonnet*"
229
- const matchingModels = availableModels.filter((m) => {
228
+ const matchingModels = availableModels.filter(m => {
230
229
  const fullId = `${m.provider}/${m.id}`;
231
230
  const glob = new Bun.Glob(globPattern.toLowerCase());
232
231
  return glob.match(fullId.toLowerCase()) || glob.match(m.id.toLowerCase());
@@ -238,7 +237,7 @@ export async function resolveModelScope(patterns: string[], modelRegistry: Model
238
237
  }
239
238
 
240
239
  for (const model of matchingModels) {
241
- if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {
240
+ if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) {
242
241
  scopedModels.push({ model, thinkingLevel, explicitThinkingLevel });
243
242
  }
244
243
  }
@@ -257,7 +256,7 @@ export async function resolveModelScope(patterns: string[], modelRegistry: Model
257
256
  }
258
257
 
259
258
  // Avoid duplicates
260
- if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {
259
+ if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) {
261
260
  scopedModels.push({ model, thinkingLevel, explicitThinkingLevel });
262
261
  }
263
262
  }
@@ -343,7 +342,7 @@ export async function findInitialModel(options: {
343
342
  // Try to find a default model from known providers
344
343
  for (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {
345
344
  const defaultId = defaultModelPerProvider[provider];
346
- const match = availableModels.find((m) => m.provider === provider && m.id === defaultId);
345
+ const match = availableModels.find(m => m.provider === provider && m.id === defaultId);
347
346
  if (match) {
348
347
  return { model: match, thinkingLevel: "off", fallbackMessage: undefined };
349
348
  }
@@ -405,7 +404,7 @@ export async function restoreModelFromSession(
405
404
  let fallbackModel: Model<Api> | undefined;
406
405
  for (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {
407
406
  const defaultId = defaultModelPerProvider[provider];
408
- const match = availableModels.find((m) => m.provider === provider && m.id === defaultId);
407
+ const match = availableModels.find(m => m.provider === provider && m.id === defaultId);
409
408
  if (match) {
410
409
  fallbackModel = match;
411
410
  break;
@@ -450,7 +449,7 @@ export async function findSmolModel(
450
449
  if (savedModel) {
451
450
  const parsed = parseModelString(savedModel);
452
451
  if (parsed) {
453
- const match = availableModels.find((m) => m.provider === parsed.provider && m.id === parsed.id);
452
+ const match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
454
453
  if (match) return match;
455
454
  }
456
455
  }
@@ -458,15 +457,15 @@ export async function findSmolModel(
458
457
  // 2. Try priority chain
459
458
  for (const pattern of SMOL_MODEL_PRIORITY) {
460
459
  // Try exact match with provider prefix
461
- const providerMatch = availableModels.find((m) => `${m.provider}/${m.id}`.toLowerCase() === pattern);
460
+ const providerMatch = availableModels.find(m => `${m.provider}/${m.id}`.toLowerCase() === pattern);
462
461
  if (providerMatch) return providerMatch;
463
462
 
464
463
  // Try exact match first
465
- const exactMatch = availableModels.find((m) => m.id.toLowerCase() === pattern);
464
+ const exactMatch = availableModels.find(m => m.id.toLowerCase() === pattern);
466
465
  if (exactMatch) return exactMatch;
467
466
 
468
467
  // Try fuzzy match (substring)
469
- const fuzzyMatch = availableModels.find((m) => m.id.toLowerCase().includes(pattern));
468
+ const fuzzyMatch = availableModels.find(m => m.id.toLowerCase().includes(pattern));
470
469
  if (fuzzyMatch) return fuzzyMatch;
471
470
  }
472
471
 
@@ -493,7 +492,7 @@ export async function findSlowModel(
493
492
  if (savedModel) {
494
493
  const parsed = parseModelString(savedModel);
495
494
  if (parsed) {
496
- const match = availableModels.find((m) => m.provider === parsed.provider && m.id === parsed.id);
495
+ const match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
497
496
  if (match) return match;
498
497
  }
499
498
  }
@@ -501,11 +500,11 @@ export async function findSlowModel(
501
500
  // 2. Try priority chain
502
501
  for (const pattern of SLOW_MODEL_PRIORITY) {
503
502
  // Try exact match first
504
- const exactMatch = availableModels.find((m) => m.id.toLowerCase() === pattern.toLowerCase());
503
+ const exactMatch = availableModels.find(m => m.id.toLowerCase() === pattern.toLowerCase());
505
504
  if (exactMatch) return exactMatch;
506
505
 
507
506
  // Try fuzzy match (substring)
508
- const fuzzyMatch = availableModels.find((m) => m.id.toLowerCase().includes(pattern.toLowerCase()));
507
+ const fuzzyMatch = availableModels.find(m => m.id.toLowerCase().includes(pattern.toLowerCase()));
509
508
  if (fuzzyMatch) return fuzzyMatch;
510
509
  }
511
510
 
@@ -1,8 +1,8 @@
1
- import { join, resolve } from "node:path";
2
- import { CONFIG_DIR_NAME, getPromptsDir } from "@oh-my-pi/pi-coding-agent/config";
3
- import { parseFrontmatter } from "@oh-my-pi/pi-coding-agent/utils/frontmatter";
1
+ import * as path from "node:path";
4
2
  import { logger } from "@oh-my-pi/pi-utils";
5
3
  import Handlebars from "handlebars";
4
+ import { CONFIG_DIR_NAME, getPromptsDir } from "../config";
5
+ import { parseFrontmatter } from "../utils/frontmatter";
6
6
 
7
7
  /**
8
8
  * Represents a prompt template loaded from a markdown file
@@ -44,7 +44,7 @@ handlebars.registerHelper(
44
44
  const suffix = (options.hash.suffix as string) ?? "";
45
45
  const rawSeparator = (options.hash.join as string) ?? "\n";
46
46
  const separator = rawSeparator.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
47
- return context.map((item) => `${prefix}${options.fn(item)}${suffix}`).join(separator);
47
+ return context.map(item => `${prefix}${options.fn(item)}${suffix}`).join(separator);
48
48
  },
49
49
  );
50
50
 
@@ -126,7 +126,7 @@ handlebars.registerHelper(
126
126
  const headers = headersStr?.split("|") ?? [];
127
127
  const separator = headers.map(() => "---").join(" | ");
128
128
  const headerRow = headers.length > 0 ? `| ${headers.join(" | ")} |\n| ${separator} |\n` : "";
129
- const rows = context.map((item) => `| ${options.fn(item).trim()} |`).join("\n");
129
+ const rows = context.map(item => `| ${options.fn(item).trim()} |`).join("\n");
130
130
  return headerRow + rows;
131
131
  },
132
132
  );
@@ -235,7 +235,7 @@ function optimizePromptLayout(input: string): string {
235
235
  // normalize NBSP -> space
236
236
  s = s.replace(/\u00A0/g, " ");
237
237
 
238
- const lines = s.split("\n").map((line) => {
238
+ const lines = s.split("\n").map(line => {
239
239
  // 2) remove trailing whitespace (spaces/tabs) per line
240
240
  let l = line.replace(/[ \t]+$/g, "");
241
241
 
@@ -382,7 +382,7 @@ async function loadTemplatesFromDir(
382
382
  entries.sort((a, b) => a.split("/").length - b.split("/").length);
383
383
 
384
384
  for (const entry of entries) {
385
- const fullPath = join(dir, entry);
385
+ const fullPath = path.join(dir, entry);
386
386
  const file = Bun.file(fullPath);
387
387
 
388
388
  try {
@@ -409,7 +409,7 @@ async function loadTemplatesFromDir(
409
409
  // Get description from frontmatter or first non-empty line
410
410
  let description = String(frontmatter.description || "");
411
411
  if (!description) {
412
- const firstLine = body.split("\n").find((line) => line.trim());
412
+ const firstLine = body.split("\n").find(line => line.trim());
413
413
  if (firstLine) {
414
414
  // Truncate if too long
415
415
  description = firstLine.slice(0, 60);
@@ -461,11 +461,11 @@ export async function loadPromptTemplates(options: LoadPromptTemplatesOptions =
461
461
 
462
462
  // 1. Load global templates from agentDir/prompts/
463
463
  // Note: if agentDir is provided, it should be the agent dir, not the prompts dir
464
- const globalPromptsDir = options.agentDir ? join(options.agentDir, "prompts") : resolvedAgentDir;
464
+ const globalPromptsDir = options.agentDir ? path.join(options.agentDir, "prompts") : resolvedAgentDir;
465
465
  templates.push(...(await loadTemplatesFromDir(globalPromptsDir, "user")));
466
466
 
467
467
  // 2. Load project templates from cwd/{CONFIG_DIR_NAME}/prompts/
468
- const projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, "prompts");
468
+ const projectPromptsDir = path.resolve(resolvedCwd, CONFIG_DIR_NAME, "prompts");
469
469
  templates.push(...(await loadTemplatesFromDir(projectPromptsDir, "project")));
470
470
 
471
471
  return templates;
@@ -482,7 +482,7 @@ export function expandPromptTemplate(text: string, templates: PromptTemplate[]):
482
482
  const templateName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
483
483
  const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1);
484
484
 
485
- const template = templates.find((t) => t.name === templateName);
485
+ const template = templates.find(t => t.name === templateName);
486
486
  if (template) {
487
487
  const args = parseCommandArgs(argsString);
488
488
  const argsText = args.join(" ");
@@ -1,11 +1,13 @@
1
- import { rename } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { type Settings as SettingsItem, settingsCapability } from "@oh-my-pi/pi-coding-agent/capability/settings";
4
- import { getAgentDbPath, getAgentDir } from "@oh-my-pi/pi-coding-agent/config";
5
- import { loadCapability } from "@oh-my-pi/pi-coding-agent/discovery";
6
- import type { SymbolPreset } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
7
- import { AgentStorage } from "@oh-my-pi/pi-coding-agent/session/agent-storage";
8
- import { logger } from "@oh-my-pi/pi-utils";
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { isEnoent, logger } from "@oh-my-pi/pi-utils";
4
+ import { YAML } from "bun";
5
+ import { type Settings as SettingsItem, settingsCapability } from "../capability/settings";
6
+ import { getAgentDbPath, getAgentDir } from "../config";
7
+ import { loadCapability } from "../discovery";
8
+ import type { SymbolPreset } from "../modes/theme/theme";
9
+ import { AgentStorage } from "../session/agent-storage";
10
+ import { withFileLock } from "./file-lock";
9
11
 
10
12
  export interface CompactionSettings {
11
13
  enabled?: boolean; // default: true
@@ -117,6 +119,15 @@ export interface PythonSettings {
117
119
  sharedGateway?: boolean;
118
120
  }
119
121
 
122
+ export interface CommitSettings {
123
+ mapReduceEnabled?: boolean;
124
+ mapReduceMinFiles?: number;
125
+ mapReduceMaxFileTokens?: number;
126
+ mapReduceTimeoutMs?: number;
127
+ mapReduceMaxConcurrency?: number;
128
+ changelogMaxDiffChars?: number;
129
+ }
130
+
120
131
  export interface EditSettings {
121
132
  fuzzyMatch?: boolean; // default: true (accept high-confidence fuzzy matches for whitespace/indentation)
122
133
  fuzzyThreshold?: number; // default: 0.95 (similarity threshold for fuzzy matching)
@@ -191,6 +202,7 @@ export interface Settings {
191
202
  interruptMode?: "immediate" | "wait";
192
203
  theme?: string;
193
204
  symbolPreset?: SymbolPreset; // default: uses theme's preset or "unicode"
205
+ colorBlindMode?: boolean; // default: false (use blue instead of green for diff additions)
194
206
  compaction?: CompactionSettings;
195
207
  branchSummary?: BranchSummarySettings;
196
208
  retry?: RetrySettings;
@@ -214,6 +226,7 @@ export interface Settings {
214
226
  mcp?: MCPSettings;
215
227
  lsp?: LspSettings;
216
228
  python?: PythonSettings;
229
+ commit?: CommitSettings;
217
230
  edit?: EditSettings;
218
231
  ttsr?: TtsrSettings;
219
232
  todoCompletion?: TodoCompletionSettings;
@@ -330,7 +343,7 @@ function normalizeBashInterceptorSettings(
330
343
  patterns = DEFAULT_BASH_INTERCEPTOR_RULES;
331
344
  } else if (Array.isArray(rawPatterns)) {
332
345
  patterns = rawPatterns
333
- .map((rule) => normalizeBashInterceptorRule(rule))
346
+ .map(rule => normalizeBashInterceptorRule(rule))
334
347
  .filter((rule): rule is BashInterceptorRule => rule !== null);
335
348
  } else {
336
349
  patterns = DEFAULT_BASH_INTERCEPTOR_RULES;
@@ -359,7 +372,7 @@ function hasNerdFonts(): boolean {
359
372
  const termProgram = (process.env.TERM_PROGRAM || "").toLowerCase();
360
373
  const term = (process.env.TERM || "").toLowerCase();
361
374
  const nerdTerms = ["iterm", "wezterm", "kitty", "ghostty", "alacritty"];
362
- cachedNerdFonts = nerdTerms.some((candidate) => termProgram.includes(candidate) || term.includes(candidate));
375
+ cachedNerdFonts = nerdTerms.some(candidate => termProgram.includes(candidate) || term.includes(candidate));
363
376
  return cachedNerdFonts;
364
377
  }
365
378
 
@@ -424,8 +437,10 @@ function deepMergeSettings(base: Settings, overrides: Settings): Settings {
424
437
  }
425
438
 
426
439
  export class SettingsManager {
427
- /** SQLite storage for persisted settings (null for in-memory mode) */
440
+ /** SQLite storage for auth/cache (null for in-memory mode) */
428
441
  private storage: AgentStorage | null;
442
+ /** Path to config.yml (null for in-memory mode) */
443
+ private configPath: string | null;
429
444
  private cwd: string | null;
430
445
  private globalSettings: Settings;
431
446
  private overrides: Settings;
@@ -434,7 +449,8 @@ export class SettingsManager {
434
449
 
435
450
  /**
436
451
  * Private constructor - use static factory methods instead.
437
- * @param storage - SQLite storage instance for persistence, or null for in-memory mode
452
+ * @param storage - SQLite storage instance for auth/cache, or null for in-memory mode
453
+ * @param configPath - Path to config.yml for persistence, or null for in-memory mode
438
454
  * @param cwd - Current working directory for project settings discovery
439
455
  * @param initialSettings - Initial global settings to use
440
456
  * @param persist - Whether to persist settings changes to storage
@@ -442,12 +458,14 @@ export class SettingsManager {
442
458
  */
443
459
  private constructor(
444
460
  storage: AgentStorage | null,
461
+ configPath: string | null,
445
462
  cwd: string | null,
446
463
  initialSettings: Settings,
447
464
  persist: boolean,
448
465
  projectSettings: Settings,
449
466
  ) {
450
467
  this.storage = storage;
468
+ this.configPath = configPath;
451
469
  this.cwd = cwd;
452
470
  this.persist = persist;
453
471
  this.globalSettings = initialSettings;
@@ -479,14 +497,17 @@ export class SettingsManager {
479
497
  }
480
498
 
481
499
  /**
482
- * Create a SettingsManager that loads from persistent SQLite storage.
500
+ * Create a SettingsManager that loads from persistent config.yml.
483
501
  * @param cwd - Current working directory for project settings discovery
484
- * @param agentDir - Agent directory containing agent.db
502
+ * @param agentDir - Agent directory containing config.yml
485
503
  * @returns Configured SettingsManager with merged global and user settings
486
504
  */
487
505
  static async create(cwd: string = process.cwd(), agentDir: string = getAgentDir()): Promise<SettingsManager> {
488
- const storage = AgentStorage.open(getAgentDbPath(agentDir));
489
- await SettingsManager.migrateLegacySettingsFile(storage, agentDir);
506
+ const configPath = path.join(agentDir, "config.yml");
507
+ const storage = await AgentStorage.open(getAgentDbPath(agentDir));
508
+
509
+ // Migrate from legacy storage if config.yml doesn't exist
510
+ await SettingsManager.migrateToYaml(storage, agentDir, configPath);
490
511
 
491
512
  // Use capability API to load user-level settings from all providers
492
513
  const result = await loadCapability(settingsCapability.id, { cwd });
@@ -499,14 +520,14 @@ export class SettingsManager {
499
520
  }
500
521
  }
501
522
 
502
- // Load persisted settings from agent.db (legacy settings.json is migrated separately)
503
- const storedSettings = SettingsManager.loadFromStorage(storage);
523
+ // Load persisted settings from config.yml
524
+ const storedSettings = await SettingsManager.loadFromYaml(configPath);
504
525
  globalSettings = deepMergeSettings(globalSettings, storedSettings);
505
526
 
506
527
  // Load project settings before construction (constructor is sync)
507
528
  const projectSettings = await SettingsManager.loadProjectSettingsStatic(cwd);
508
529
 
509
- return new SettingsManager(storage, cwd, globalSettings, true, projectSettings);
530
+ return new SettingsManager(storage, configPath, cwd, globalSettings, true, projectSettings);
510
531
  }
511
532
 
512
533
  /**
@@ -515,7 +536,7 @@ export class SettingsManager {
515
536
  * @returns SettingsManager that won't persist changes to disk
516
537
  */
517
538
  static inMemory(settings: Partial<Settings> = {}): SettingsManager {
518
- return new SettingsManager(null, null, settings, false, {});
539
+ return new SettingsManager(null, null, null, settings, false, {});
519
540
  }
520
541
 
521
542
  /**
@@ -534,41 +555,89 @@ export class SettingsManager {
534
555
  }
535
556
 
536
557
  /**
537
- * Load settings from SQLite storage, applying any schema migrations.
538
- * @param storage - AgentStorage instance, or null for in-memory mode
539
- * @returns Parsed and migrated settings, or empty object if storage is null/empty
558
+ * Load settings from config.yml, applying any schema migrations.
559
+ * @param configPath - Path to config.yml, or null for in-memory mode
560
+ * @returns Parsed and migrated settings, or empty object if file doesn't exist
540
561
  */
541
- private static loadFromStorage(storage: AgentStorage | null): Settings {
542
- if (!storage) {
562
+ private static async loadFromYaml(configPath: string | null): Promise<Settings> {
563
+ if (!configPath) {
543
564
  return {};
544
565
  }
545
- const settings = storage.getSettings();
546
- if (!settings) {
566
+ try {
567
+ const content = await Bun.file(configPath).text();
568
+ const parsed = YAML.parse(content);
569
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
570
+ return {};
571
+ }
572
+ return SettingsManager.migrateSettings(parsed as Record<string, unknown>);
573
+ } catch (error) {
574
+ if (isEnoent(error)) return {};
575
+ logger.warn("SettingsManager failed to load config.yml", { path: configPath, error: String(error) });
547
576
  return {};
548
577
  }
549
- return SettingsManager.migrateSettings(settings as Record<string, unknown>);
550
578
  }
551
579
 
552
- private static async migrateLegacySettingsFile(storage: AgentStorage, agentDir: string): Promise<void> {
553
- const settingsPath = join(agentDir, "settings.json");
554
- const settingsFile = Bun.file(settingsPath);
555
- if (!(await settingsFile.exists())) return;
556
- if (storage.getSettings() !== null) return;
557
-
580
+ /**
581
+ * Migrate settings from legacy sources to config.yml.
582
+ * Migration order: settings.json -> agent.db -> config.yml
583
+ * Only migrates if config.yml doesn't exist.
584
+ */
585
+ private static async migrateToYaml(storage: AgentStorage, agentDir: string, configPath: string): Promise<void> {
558
586
  try {
559
- const parsed = JSON.parse(await settingsFile.text());
560
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
587
+ await Bun.file(configPath).text();
588
+ return;
589
+ } catch (err) {
590
+ if (!isEnoent(err)) {
591
+ logger.warn("SettingsManager failed to check config.yml", { path: configPath, error: String(err) });
561
592
  return;
562
593
  }
563
- const migrated = SettingsManager.migrateSettings(parsed as Record<string, unknown>);
564
- storage.saveSettings(migrated);
594
+ }
595
+
596
+ let settings: Settings = {};
597
+ let migrated = false;
598
+
599
+ // 1. Try to migrate from settings.json (oldest legacy format)
600
+ const settingsJsonPath = path.join(agentDir, "settings.json");
601
+ try {
602
+ const parsed = JSON.parse(await Bun.file(settingsJsonPath).text());
603
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
604
+ settings = deepMergeSettings(settings, SettingsManager.migrateSettings(parsed));
605
+ migrated = true;
606
+ // Backup settings.json
607
+ try {
608
+ await fs.rename(settingsJsonPath, `${settingsJsonPath}.bak`);
609
+ } catch (error) {
610
+ logger.warn("SettingsManager failed to backup settings.json", { error: String(error) });
611
+ }
612
+ }
613
+ } catch (error) {
614
+ if (!isEnoent(error)) {
615
+ logger.warn("SettingsManager failed to read settings.json", { error: String(error) });
616
+ }
617
+ }
618
+
619
+ // 2. Migrate from agent.db settings table
620
+ try {
621
+ const dbSettings = storage.getSettings();
622
+ if (dbSettings) {
623
+ settings = deepMergeSettings(
624
+ settings,
625
+ SettingsManager.migrateSettings(dbSettings as Record<string, unknown>),
626
+ );
627
+ migrated = true;
628
+ }
629
+ } catch (error) {
630
+ logger.warn("SettingsManager failed to read agent.db settings", { error: String(error) });
631
+ }
632
+
633
+ // 3. Write merged settings to config.yml if we found any
634
+ if (migrated && Object.keys(settings).length > 0) {
565
635
  try {
566
- await rename(settingsPath, `${settingsPath}.bak`);
636
+ await Bun.write(configPath, YAML.stringify(settings, null, 2));
637
+ logger.debug("SettingsManager migrated settings to config.yml", { path: configPath });
567
638
  } catch (error) {
568
- logger.warn("SettingsManager failed to backup settings.json", { error: String(error) });
639
+ logger.warn("SettingsManager failed to write config.yml", { path: configPath, error: String(error) });
569
640
  }
570
- } catch (error) {
571
- logger.warn("SettingsManager failed to migrate settings.json", { error: String(error) });
572
641
  }
573
642
  }
574
643
 
@@ -620,22 +689,24 @@ export class SettingsManager {
620
689
  }
621
690
 
622
691
  /**
623
- * Persist current global settings to SQLite storage and rebuild merged settings.
624
- * Merges with any concurrent changes in storage before saving.
692
+ * Persist current global settings to config.yml and rebuild merged settings.
693
+ * Uses file locking to prevent concurrent write races.
625
694
  */
626
695
  private async save(): Promise<void> {
627
- if (this.persist && this.storage) {
696
+ if (this.persist && this.configPath) {
697
+ const configPath = this.configPath;
628
698
  try {
629
- const currentSettings = this.storage.getSettings() ?? {};
630
- const mergedSettings = deepMergeSettings(currentSettings, this.globalSettings);
631
- this.globalSettings = mergedSettings;
632
- this.storage.saveSettings(this.globalSettings);
699
+ await withFileLock(configPath, async () => {
700
+ const currentSettings = await SettingsManager.loadFromYaml(configPath);
701
+ const mergedSettings = deepMergeSettings(currentSettings, this.globalSettings);
702
+ this.globalSettings = mergedSettings;
703
+ await Bun.write(configPath, YAML.stringify(this.globalSettings, null, 2));
704
+ });
633
705
  } catch (error) {
634
706
  logger.warn("SettingsManager save failed", { error: String(error) });
635
707
  }
636
708
  }
637
709
 
638
- // Always re-merge to update active settings (needed for both file and inMemory modes)
639
710
  const projectSettings = await this.loadProjectSettings();
640
711
  this.rebuildSettings(projectSettings);
641
712
  }
@@ -724,6 +795,15 @@ export class SettingsManager {
724
795
  await this.save();
725
796
  }
726
797
 
798
+ getColorBlindMode(): boolean {
799
+ return this.settings.colorBlindMode ?? false;
800
+ }
801
+
802
+ async setColorBlindMode(enabled: boolean): Promise<void> {
803
+ this.globalSettings.colorBlindMode = enabled;
804
+ await this.save();
805
+ }
806
+
727
807
  getDefaultThinkingLevel(): "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | undefined {
728
808
  return this.settings.defaultThinkingLevel;
729
809
  }
@@ -800,6 +880,17 @@ export class SettingsManager {
800
880
  };
801
881
  }
802
882
 
883
+ getCommitSettings(): Required<CommitSettings> {
884
+ return {
885
+ mapReduceEnabled: this.settings.commit?.mapReduceEnabled ?? true,
886
+ mapReduceMinFiles: this.settings.commit?.mapReduceMinFiles ?? 4,
887
+ mapReduceMaxFileTokens: this.settings.commit?.mapReduceMaxFileTokens ?? 50_000,
888
+ mapReduceTimeoutMs: this.settings.commit?.mapReduceTimeoutMs ?? 120_000,
889
+ mapReduceMaxConcurrency: this.settings.commit?.mapReduceMaxConcurrency ?? 5,
890
+ changelogMaxDiffChars: this.settings.commit?.changelogMaxDiffChars ?? 120_000,
891
+ };
892
+ }
893
+
803
894
  getRetryMaxRetries(): number {
804
895
  return this.settings.retry?.maxRetries ?? 3;
805
896
  }