@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
@@ -13,7 +13,7 @@
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
15
 
16
- import { existsSync, readFileSync } from "node:fs";
16
+ import * as fs from "node:fs";
17
17
  import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
18
18
  import type {
19
19
  AssistantMessage,
@@ -26,21 +26,23 @@ import type {
26
26
  UsageReport,
27
27
  } from "@oh-my-pi/pi-ai";
28
28
  import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai";
29
- import type { Rule } from "@oh-my-pi/pi-coding-agent/capability/rule";
30
- import { getAgentDbPath } from "@oh-my-pi/pi-coding-agent/config";
31
- import type { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
32
- import { parseModelString } from "@oh-my-pi/pi-coding-agent/config/model-resolver";
29
+ import { abortableSleep, isEnoent, logger } from "@oh-my-pi/pi-utils";
30
+ import { YAML } from "bun";
31
+ import type { Rule } from "../capability/rule";
32
+ import { getAgentDbPath } from "../config";
33
+ import type { ModelRegistry } from "../config/model-registry";
34
+ import { parseModelString } from "../config/model-resolver";
33
35
  import {
34
36
  expandPromptTemplate,
35
37
  type PromptTemplate,
36
38
  parseCommandArgs,
37
39
  renderPromptTemplate,
38
- } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
39
- import type { SettingsManager, SkillsSettings } from "@oh-my-pi/pi-coding-agent/config/settings-manager";
40
- import { type BashResult, executeBash as executeBashCommand } from "@oh-my-pi/pi-coding-agent/exec/bash-executor";
41
- import { exportSessionToHtml } from "@oh-my-pi/pi-coding-agent/export/html/index";
42
- import type { TtsrManager } from "@oh-my-pi/pi-coding-agent/export/ttsr";
43
- import type { LoadedCustomCommand } from "@oh-my-pi/pi-coding-agent/extensibility/custom-commands/index";
40
+ } from "../config/prompt-templates";
41
+ import type { SettingsManager, SkillsSettings } from "../config/settings-manager";
42
+ import { type BashResult, executeBash as executeBashCommand } from "../exec/bash-executor";
43
+ import { exportSessionToHtml } from "../export/html";
44
+ import type { TtsrManager } from "../export/ttsr";
45
+ import type { LoadedCustomCommand } from "../extensibility/custom-commands";
44
46
  import type {
45
47
  ExtensionCommandContext,
46
48
  ExtensionRunner,
@@ -52,23 +54,21 @@ import type {
52
54
  TreePreparation,
53
55
  TurnEndEvent,
54
56
  TurnStartEvent,
55
- } from "@oh-my-pi/pi-coding-agent/extensibility/extensions";
56
- import type { CompactOptions, ContextUsage } from "@oh-my-pi/pi-coding-agent/extensibility/extensions/types";
57
- import type { HookCommandContext } from "@oh-my-pi/pi-coding-agent/extensibility/hooks/types";
58
- import type { Skill, SkillWarning } from "@oh-my-pi/pi-coding-agent/extensibility/skills";
59
- import { expandSlashCommand, type FileSlashCommand } from "@oh-my-pi/pi-coding-agent/extensibility/slash-commands";
60
- import { executePython as executePythonCommand, type PythonResult } from "@oh-my-pi/pi-coding-agent/ipy/executor";
61
- import { theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
62
- import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "@oh-my-pi/pi-coding-agent/patch";
63
- import ttsrInterruptTemplate from "@oh-my-pi/pi-coding-agent/prompts/system/ttsr-interrupt.md" with { type: "text" };
64
- import { closeAllConnections } from "@oh-my-pi/pi-coding-agent/ssh/connection-manager";
65
- import { unmountAll } from "@oh-my-pi/pi-coding-agent/ssh/sshfs-mount";
66
- import { outputMeta } from "@oh-my-pi/pi-coding-agent/tools/output-meta";
67
- import { resolveToCwd } from "@oh-my-pi/pi-coding-agent/tools/path-utils";
68
- import type { TodoItem } from "@oh-my-pi/pi-coding-agent/tools/todo-write";
69
- import { extractFileMentions, generateFileMentionMessages } from "@oh-my-pi/pi-coding-agent/utils/file-mentions";
70
- import { abortableSleep, logger } from "@oh-my-pi/pi-utils";
71
- import { YAML } from "bun";
57
+ } from "../extensibility/extensions";
58
+ import type { CompactOptions, ContextUsage } from "../extensibility/extensions/types";
59
+ import type { HookCommandContext } from "../extensibility/hooks/types";
60
+ import type { Skill, SkillWarning } from "../extensibility/skills";
61
+ import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
62
+ import { executePython as executePythonCommand, type PythonResult } from "../ipy/executor";
63
+ import { theme } from "../modes/theme/theme";
64
+ import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../patch";
65
+ import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" };
66
+ import { closeAllConnections } from "../ssh/connection-manager";
67
+ import { unmountAll } from "../ssh/sshfs-mount";
68
+ import { outputMeta } from "../tools/output-meta";
69
+ import { resolveToCwd } from "../tools/path-utils";
70
+ import type { TodoItem } from "../tools/todo-write";
71
+ import { extractFileMentions, generateFileMentionMessages } from "../utils/file-mentions";
72
72
  import {
73
73
  type CompactionResult,
74
74
  calculateContextTokens,
@@ -78,7 +78,7 @@ import {
78
78
  generateBranchSummary,
79
79
  prepareCompaction,
80
80
  shouldCompact,
81
- } from "./compaction/index";
81
+ } from "./compaction";
82
82
  import {
83
83
  type BashExecutionMessage,
84
84
  type BranchSummaryMessage,
@@ -214,9 +214,9 @@ const noOpUIContext: ExtensionUIContext = {
214
214
  get theme() {
215
215
  return theme;
216
216
  },
217
- getAllThemes: () => [],
218
- getTheme: () => undefined,
219
- setTheme: (_theme) => ({ success: false, error: "UI not available" }),
217
+ getAllThemes: () => Promise.resolve([]),
218
+ getTheme: () => Promise.resolve(undefined),
219
+ setTheme: _theme => Promise.resolve({ success: false, error: "UI not available" }),
220
220
  setFooter: () => {},
221
221
  setHeader: () => {},
222
222
  setEditorComponent: () => {},
@@ -542,7 +542,7 @@ export class AgentSession {
542
542
  private _getTtsrInjectionContent(): string | undefined {
543
543
  if (this._pendingTtsrInjections.length === 0) return undefined;
544
544
  const content = this._pendingTtsrInjections
545
- .map((r) => renderPromptTemplate(ttsrInterruptTemplate, { name: r.name, path: r.path, content: r.content }))
545
+ .map(r => renderPromptTemplate(ttsrInterruptTemplate, { name: r.name, path: r.path, content: r.content }))
546
546
  .join("\n\n");
547
547
  this._pendingTtsrInjections = [];
548
548
  return content;
@@ -553,10 +553,10 @@ export class AgentSession {
553
553
  if (message.role !== "user") return "";
554
554
  const content = message.content;
555
555
  if (typeof content === "string") return content;
556
- const textBlocks = content.filter((c) => c.type === "text");
557
- const text = textBlocks.map((c) => (c as TextContent).text).join("");
556
+ const textBlocks = content.filter(c => c.type === "text");
557
+ const text = textBlocks.map(c => (c as TextContent).text).join("");
558
558
  if (text.length > 0) return text;
559
- const hasImages = content.some((c) => c.type === "image");
559
+ const hasImages = content.some(c => c.type === "image");
560
560
  return hasImages ? "[Image]" : "";
561
561
  }
562
562
 
@@ -578,7 +578,7 @@ export class AgentSession {
578
578
  this._streamingEditFileCache.clear();
579
579
  }
580
580
 
581
- private _preCacheStreamingEditFile(event: AgentEvent): void {
581
+ private async _preCacheStreamingEditFile(event: AgentEvent): Promise<void> {
582
582
  if (!this.settingsManager.getEditStreamingAbort()) return;
583
583
  if (event.type !== "message_update") return;
584
584
  const assistantEvent = event.assistantMessageEvent;
@@ -606,13 +606,11 @@ export class AgentSession {
606
606
  if (this._streamingEditFileCache.has(resolvedPath)) return;
607
607
 
608
608
  try {
609
- if (existsSync(resolvedPath)) {
610
- const rawText = readFileSync(resolvedPath, "utf8");
611
- const { text } = stripBom(rawText);
612
- this._streamingEditFileCache.set(resolvedPath, normalizeToLF(text));
613
- }
609
+ const rawText = fs.readFileSync(resolvedPath, "utf-8");
610
+ const { text } = stripBom(rawText);
611
+ this._streamingEditFileCache.set(resolvedPath, normalizeToLF(text));
614
612
  } catch {
615
- // Don't cache on read errors - let the edit tool handle them
613
+ // Don't cache on read errors (including ENOENT) - let the edit tool handle them
616
614
  }
617
615
  }
618
616
 
@@ -655,7 +653,7 @@ export class AgentSession {
655
653
  const normalizedDiff = normalizeDiff(diffForCheck.replace(/\r/g, ""));
656
654
  if (!normalizedDiff) return;
657
655
  const lines = normalizedDiff.split("\n");
658
- const hasChangeLine = lines.some((line) => line.startsWith("+") || line.startsWith("-"));
656
+ const hasChangeLine = lines.some(line => line.startsWith("+") || line.startsWith("-"));
659
657
  if (!hasChangeLine) return;
660
658
 
661
659
  const lineCount = lines.length;
@@ -666,13 +664,13 @@ export class AgentSession {
666
664
  const rename = typeof args.rename === "string" ? args.rename : undefined;
667
665
 
668
666
  const removedLines = lines
669
- .filter((line) => line.startsWith("-") && !line.startsWith("--- "))
670
- .map((line) => line.slice(1));
667
+ .filter(line => line.startsWith("-") && !line.startsWith("--- "))
668
+ .map(line => line.slice(1));
671
669
  if (removedLines.length > 0) {
672
670
  const resolvedPath = resolveToCwd(path, this.sessionManager.getCwd());
673
671
  const cachedContent = this._streamingEditFileCache.get(resolvedPath);
674
672
  if (cachedContent !== undefined) {
675
- const missing = removedLines.find((line) => !cachedContent.includes(normalizeToLF(line)));
673
+ const missing = removedLines.find(line => !cachedContent.includes(normalizeToLF(line)));
676
674
  if (missing) {
677
675
  this._streamingEditAbortTriggered = true;
678
676
  logger.warn("Streaming edit aborted due to patch preview failure", {
@@ -701,10 +699,9 @@ export class AgentSession {
701
699
  ): Promise<void> {
702
700
  if (this._streamingEditAbortTriggered) return;
703
701
  try {
704
- if (!(await Bun.file(resolvedPath).exists())) return;
705
702
  const { text } = stripBom(await Bun.file(resolvedPath).text());
706
703
  const normalizedContent = normalizeToLF(text);
707
- const missing = removedLines.find((line) => !normalizedContent.includes(normalizeToLF(line)));
704
+ const missing = removedLines.find(line => !normalizedContent.includes(normalizeToLF(line)));
708
705
  if (missing) {
709
706
  this._streamingEditAbortTriggered = true;
710
707
  logger.warn("Streaming edit aborted due to patch preview failure", {
@@ -714,8 +711,12 @@ export class AgentSession {
714
711
  });
715
712
  this.agent.abort();
716
713
  }
717
- } catch {
718
- // Ignore errors during async fallback
714
+ } catch (err) {
715
+ // Ignore ENOENT (file not found) - let the edit tool handle missing files
716
+ // Also ignore other errors during async fallback
717
+ if (!isEnoent(err)) {
718
+ // Log unexpected errors but don't abort
719
+ }
719
720
  }
720
721
  }
721
722
 
@@ -885,7 +886,7 @@ export class AgentSession {
885
886
  * Returns the names of tools currently set on the agent.
886
887
  */
887
888
  getActiveToolNames(): string[] {
888
- return this.agent.state.tools.map((t) => t.name);
889
+ return this.agent.state.tools.map(t => t.name);
889
890
  }
890
891
 
891
892
  /**
@@ -1178,7 +1179,7 @@ export class AgentSession {
1178
1179
  hasQueuedMessages: () => this.queuedMessageCount > 0,
1179
1180
  getContextUsage: () => this.getContextUsage(),
1180
1181
  waitForIdle: () => this.agent.waitForIdle(),
1181
- newSession: async (options) => {
1182
+ newSession: async options => {
1182
1183
  const success = await this.newSession({ parentSession: options?.parentSession });
1183
1184
  if (!success) {
1184
1185
  return { cancelled: true };
@@ -1188,7 +1189,7 @@ export class AgentSession {
1188
1189
  }
1189
1190
  return { cancelled: false };
1190
1191
  },
1191
- branch: async (entryId) => {
1192
+ branch: async entryId => {
1192
1193
  const result = await this.branch(entryId);
1193
1194
  return { cancelled: result.cancelled };
1194
1195
  },
@@ -1196,7 +1197,7 @@ export class AgentSession {
1196
1197
  const result = await this.navigateTree(targetId, { summarize: options?.summarize });
1197
1198
  return { cancelled: result.cancelled };
1198
1199
  },
1199
- compact: async (instructionsOrOptions) => {
1200
+ compact: async instructionsOrOptions => {
1200
1201
  const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
1201
1202
  const options =
1202
1203
  instructionsOrOptions && typeof instructionsOrOptions === "object" ? instructionsOrOptions : undefined;
@@ -1218,7 +1219,7 @@ export class AgentSession {
1218
1219
  const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1);
1219
1220
 
1220
1221
  // Find matching command
1221
- const loaded = this._customCommands.find((c) => c.command.name === commandName);
1222
+ const loaded = this._customCommands.find(c => c.command.name === commandName);
1222
1223
  if (!loaded) return null;
1223
1224
 
1224
1225
  // Get command context from extension runner (includes session control methods)
@@ -1605,10 +1606,10 @@ export class AgentSession {
1605
1606
  const parsed = parseModelString(roleModelStr);
1606
1607
  let match: Model<any> | undefined;
1607
1608
  if (parsed) {
1608
- match = availableModels.find((m) => m.provider === parsed.provider && m.id === parsed.id);
1609
+ match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
1609
1610
  }
1610
1611
  if (!match) {
1611
- match = availableModels.find((m) => m.id.toLowerCase() === roleModelStr.toLowerCase());
1612
+ match = availableModels.find(m => m.id.toLowerCase() === roleModelStr.toLowerCase());
1612
1613
  }
1613
1614
  if (!match) continue;
1614
1615
 
@@ -1619,8 +1620,8 @@ export class AgentSession {
1619
1620
 
1620
1621
  const lastRole = this.sessionManager.getLastModelChangeRole();
1621
1622
  let currentIndex = lastRole
1622
- ? roleModels.findIndex((entry) => entry.role === lastRole)
1623
- : roleModels.findIndex((entry) => modelsAreEqual(entry.model, currentModel));
1623
+ ? roleModels.findIndex(entry => entry.role === lastRole)
1624
+ : roleModels.findIndex(entry => modelsAreEqual(entry.model, currentModel));
1624
1625
  if (currentIndex === -1) currentIndex = 0;
1625
1626
 
1626
1627
  const nextIndex = (currentIndex + 1) % roleModels.length;
@@ -1639,7 +1640,7 @@ export class AgentSession {
1639
1640
  if (this._scopedModels.length <= 1) return undefined;
1640
1641
 
1641
1642
  const currentModel = this.model;
1642
- let currentIndex = this._scopedModels.findIndex((sm) => modelsAreEqual(sm.model, currentModel));
1643
+ let currentIndex = this._scopedModels.findIndex(sm => modelsAreEqual(sm.model, currentModel));
1643
1644
 
1644
1645
  if (currentIndex === -1) currentIndex = 0;
1645
1646
  const len = this._scopedModels.length;
@@ -1668,7 +1669,7 @@ export class AgentSession {
1668
1669
  if (availableModels.length <= 1) return undefined;
1669
1670
 
1670
1671
  const currentModel = this.model;
1671
- let currentIndex = availableModels.findIndex((m) => modelsAreEqual(m, currentModel));
1672
+ let currentIndex = availableModels.findIndex(m => modelsAreEqual(m, currentModel));
1672
1673
 
1673
1674
  if (currentIndex === -1) currentIndex = 0;
1674
1675
  const len = availableModels.length;
@@ -1898,7 +1899,7 @@ export class AgentSession {
1898
1899
  this.agent.replaceMessages(sessionContext.messages);
1899
1900
 
1900
1901
  // Get the saved compaction entry for the hook
1901
- const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary) as
1902
+ const savedCompactionEntry = newEntries.find(e => e.type === "compaction" && e.summary === summary) as
1902
1903
  | CompactionEntry
1903
1904
  | undefined;
1904
1905
 
@@ -1975,7 +1976,7 @@ export class AgentSession {
1975
1976
  // The error shouldn't trigger another compaction since we already compacted.
1976
1977
  // Example: opus fails → switch to codex → compact → switch back to opus → opus error
1977
1978
  // is still in context but shouldn't trigger compaction again.
1978
- const compactionEntry = this.sessionManager.getBranch().find((e) => e.type === "compaction");
1979
+ const compactionEntry = this.sessionManager.getBranch().find(e => e.type === "compaction");
1979
1980
  const errorIsFromBeforeCompaction =
1980
1981
  compactionEntry && assistantMessage.timestamp < new Date(compactionEntry.timestamp).getTime();
1981
1982
 
@@ -2022,22 +2023,20 @@ export class AgentSession {
2022
2023
  if (!sessionFile) return;
2023
2024
 
2024
2025
  const todoPath = `${sessionFile.slice(0, -6)}/todos.json`;
2025
- const file = Bun.file(todoPath);
2026
- if (!(await file.exists())) {
2027
- this._todoReminderCount = 0;
2028
- return;
2029
- }
2030
2026
 
2031
2027
  let todos: TodoItem[];
2032
2028
  try {
2033
- const data = await file.json();
2029
+ const data = await Bun.file(todoPath).json();
2034
2030
  todos = data?.todos ?? [];
2035
- } catch {
2031
+ } catch (err) {
2032
+ if (isEnoent(err)) {
2033
+ this._todoReminderCount = 0;
2034
+ }
2036
2035
  return;
2037
2036
  }
2038
2037
 
2039
2038
  // Check for incomplete todos
2040
- const incomplete = todos.filter((t) => t.status !== "completed");
2039
+ const incomplete = todos.filter(t => t.status !== "completed");
2041
2040
  if (incomplete.length === 0) {
2042
2041
  this._todoReminderCount = 0;
2043
2042
  return;
@@ -2045,7 +2044,7 @@ export class AgentSession {
2045
2044
 
2046
2045
  // Build reminder message
2047
2046
  this._todoReminderCount++;
2048
- const todoList = incomplete.map((t) => `- ${t.content}`).join("\n");
2047
+ const todoList = incomplete.map(t => `- ${t.content}`).join("\n");
2049
2048
  const reminder =
2050
2049
  `<system_reminder>\n` +
2051
2050
  `You stopped with ${incomplete.length} incomplete todo item(s):\n${todoList}\n\n` +
@@ -2094,10 +2093,10 @@ export class AgentSession {
2094
2093
 
2095
2094
  const parsed = parseModelString(roleModelStr);
2096
2095
  if (parsed) {
2097
- return availableModels.find((m) => m.provider === parsed.provider && m.id === parsed.id);
2096
+ return availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
2098
2097
  }
2099
2098
  const roleLower = roleModelStr.toLowerCase();
2100
- return availableModels.find((m) => m.id.toLowerCase() === roleLower);
2099
+ return availableModels.find(m => m.id.toLowerCase() === roleLower);
2101
2100
  }
2102
2101
 
2103
2102
  private _getCompactionModelCandidates(availableModels: Model<any>[]): Model<any>[] {
@@ -2295,7 +2294,7 @@ export class AgentSession {
2295
2294
  this.agent.replaceMessages(sessionContext.messages);
2296
2295
 
2297
2296
  // Get the saved compaction entry for the hook
2298
- const savedCompactionEntry = newEntries.find((e) => e.type === "compaction" && e.summary === summary) as
2297
+ const savedCompactionEntry = newEntries.find(e => e.type === "compaction" && e.summary === summary) as
2299
2298
  | CompactionEntry
2300
2299
  | undefined;
2301
2300
 
@@ -2822,7 +2821,7 @@ export class AgentSession {
2822
2821
  const provider = defaultModelStr.slice(0, slashIdx);
2823
2822
  const modelId = defaultModelStr.slice(slashIdx + 1);
2824
2823
  const availableModels = this._modelRegistry.getAvailable();
2825
- const match = availableModels.find((m) => m.provider === provider && m.id === modelId);
2824
+ const match = availableModels.find(m => m.provider === provider && m.id === modelId);
2826
2825
  if (match) {
2827
2826
  this.agent.setModel(match);
2828
2827
  }
@@ -3026,7 +3025,7 @@ export class AgentSession {
3026
3025
  ? targetEntry.content
3027
3026
  : targetEntry.content
3028
3027
  .filter((c): c is { type: "text"; text: string } => c.type === "text")
3029
- .map((c) => c.text)
3028
+ .map(c => c.text)
3030
3029
  .join("");
3031
3030
  } else {
3032
3031
  // Non-user message: leaf = selected node
@@ -3092,7 +3091,7 @@ export class AgentSession {
3092
3091
  if (Array.isArray(content)) {
3093
3092
  return content
3094
3093
  .filter((c): c is { type: "text"; text: string } => c.type === "text")
3095
- .map((c) => c.text)
3094
+ .map(c => c.text)
3096
3095
  .join("");
3097
3096
  }
3098
3097
  return "";
@@ -3103,9 +3102,9 @@ export class AgentSession {
3103
3102
  */
3104
3103
  getSessionStats(): SessionStats {
3105
3104
  const state = this.state;
3106
- const userMessages = state.messages.filter((m) => m.role === "user").length;
3107
- const assistantMessages = state.messages.filter((m) => m.role === "assistant").length;
3108
- const toolResults = state.messages.filter((m) => m.role === "toolResult").length;
3105
+ const userMessages = state.messages.filter(m => m.role === "user").length;
3106
+ const assistantMessages = state.messages.filter(m => m.role === "assistant").length;
3107
+ const toolResults = state.messages.filter(m => m.role === "toolResult").length;
3109
3108
 
3110
3109
  let toolCalls = 0;
3111
3110
  let totalInput = 0;
@@ -3125,7 +3124,7 @@ export class AgentSession {
3125
3124
  for (const message of state.messages) {
3126
3125
  if (message.role === "assistant") {
3127
3126
  const assistantMsg = message as AssistantMessage;
3128
- toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
3127
+ toolCalls += assistantMsg.content.filter(c => c.type === "toolCall").length;
3129
3128
  totalInput += assistantMsg.usage.input;
3130
3129
  totalOutput += assistantMsg.usage.output;
3131
3130
  totalCacheRead += assistantMsg.usage.cacheRead;
@@ -3193,7 +3192,7 @@ export class AgentSession {
3193
3192
  const authStorage = this._modelRegistry.authStorage;
3194
3193
  if (!authStorage.fetchUsageReports) return null;
3195
3194
  return authStorage.fetchUsageReports({
3196
- baseUrlResolver: (provider) => this._modelRegistry.getProviderBaseUrl?.(provider),
3195
+ baseUrlResolver: provider => this._modelRegistry.getProviderBaseUrl?.(provider),
3197
3196
  });
3198
3197
  }
3199
3198
 
@@ -3274,7 +3273,7 @@ export class AgentSession {
3274
3273
  const lastAssistant = this.messages
3275
3274
  .slice()
3276
3275
  .reverse()
3277
- .find((m) => {
3276
+ .find(m => {
3278
3277
  if (m.role !== "assistant") return false;
3279
3278
  const msg = m as AssistantMessage;
3280
3279
  // Skip aborted messages with no content
@@ -1,9 +1,9 @@
1
1
  import { Database } from "bun:sqlite";
2
- import { chmodSync, existsSync, mkdirSync } from "node:fs";
3
- import { dirname } from "node:path";
4
- import { getAgentDbPath } from "@oh-my-pi/pi-coding-agent/config";
5
- import type { Settings } from "@oh-my-pi/pi-coding-agent/config/settings-manager";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
6
4
  import { logger } from "@oh-my-pi/pi-utils";
5
+ import { getAgentDbPath } from "../config";
6
+ import type { Settings } from "../config/settings-manager";
7
7
  import type { AuthCredential } from "./auth-storage";
8
8
 
9
9
  /** Prepared SQLite statement type from bun:sqlite */
@@ -116,8 +116,6 @@ export class AgentStorage {
116
116
  private static instances = new Map<string, AgentStorage>();
117
117
 
118
118
  private listSettingsStmt: Statement;
119
- private insertSettingStmt: Statement;
120
- private deleteSettingsStmt: Statement;
121
119
  private getCacheStmt: Statement;
122
120
  private upsertCacheStmt: Statement;
123
121
  private deleteExpiredCacheStmt: Statement;
@@ -137,10 +135,6 @@ export class AgentStorage {
137
135
  this.hardenPermissions(dbPath);
138
136
 
139
137
  this.listSettingsStmt = this.db.prepare("SELECT key, value FROM settings");
140
- this.insertSettingStmt = this.db.prepare(
141
- "INSERT INTO settings (key, value, updated_at) VALUES (?, ?, unixepoch())",
142
- );
143
- this.deleteSettingsStmt = this.db.prepare("DELETE FROM settings");
144
138
 
145
139
  this.getCacheStmt = this.db.prepare("SELECT value FROM cache WHERE key = ? AND expires_at > unixepoch()");
146
140
  this.upsertCacheStmt = this.db.prepare(
@@ -197,8 +191,8 @@ CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY);
197
191
 
198
192
  const settingsInfo = this.db.prepare("PRAGMA table_info(settings)").all() as Array<{ name?: string }>;
199
193
  const hasSettingsTable = settingsInfo.length > 0;
200
- const hasKey = settingsInfo.some((column) => column.name === "key");
201
- const hasValue = settingsInfo.some((column) => column.name === "value");
194
+ const hasKey = settingsInfo.some(column => column.name === "key");
195
+ const hasValue = settingsInfo.some(column => column.name === "value");
202
196
 
203
197
  if (!hasSettingsTable) {
204
198
  this.db.exec(`
@@ -264,20 +258,43 @@ CREATE TABLE settings (
264
258
 
265
259
  /**
266
260
  * Returns singleton instance for the given database path, creating if needed.
261
+ * Retries on SQLITE_BUSY with exponential backoff.
267
262
  * @param dbPath - Path to the SQLite database file (defaults to config path)
268
263
  * @returns AgentStorage instance for the given path
269
264
  */
270
- static open(dbPath: string = getAgentDbPath()): AgentStorage {
265
+ static async open(dbPath: string = getAgentDbPath()): Promise<AgentStorage> {
271
266
  const existing = AgentStorage.instances.get(dbPath);
272
267
  if (existing) return existing;
273
- const storage = new AgentStorage(dbPath);
274
- AgentStorage.instances.set(dbPath, storage);
275
- return storage;
268
+
269
+ const maxRetries = 3;
270
+ const baseDelayMs = 100;
271
+ let lastError: Error | undefined;
272
+
273
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
274
+ try {
275
+ const storage = new AgentStorage(dbPath);
276
+ AgentStorage.instances.set(dbPath, storage);
277
+ return storage;
278
+ } catch (err) {
279
+ const isSqliteBusy = err && typeof err === "object" && (err as { code?: string }).code === "SQLITE_BUSY";
280
+ if (!isSqliteBusy) {
281
+ throw err;
282
+ }
283
+ lastError = err as Error;
284
+ const delayMs = baseDelayMs * 2 ** attempt;
285
+ await Bun.sleep(delayMs);
286
+ }
287
+ }
288
+
289
+ throw lastError ?? new Error("Failed to open database after retries");
276
290
  }
277
291
 
278
292
  /**
279
- * Retrieves all settings from storage.
293
+ * Retrieves all settings from storage (legacy, for migration only).
294
+ * Settings are now stored in config.yml. This method is only used
295
+ * during migration from agent.db to config.yml.
280
296
  * @returns Settings object, or null if no settings are stored
297
+ * @deprecated Use config.yml instead. This is only for migration.
281
298
  */
282
299
  getSettings(): Settings | null {
283
300
  const rows = (this.listSettingsStmt.all() as SettingsRow[]) ?? [];
@@ -297,26 +314,13 @@ CREATE TABLE settings (
297
314
  }
298
315
 
299
316
  /**
300
- * Atomically replaces all settings in storage.
301
- * Uses delete-then-insert within a transaction for consistency.
302
- * @param settings - Settings object to persist
317
+ * @deprecated Settings are now stored in config.yml, not agent.db.
318
+ * This method is kept for backward compatibility but does nothing.
303
319
  */
304
320
  saveSettings(settings: Settings): void {
305
- const entries = Object.entries(settings).filter(([, value]) => value !== undefined);
306
- const replace = this.db.transaction((rows: Array<[string, unknown]>) => {
307
- this.deleteSettingsStmt.run();
308
- for (const [key, value] of rows) {
309
- const serialized = JSON.stringify(value);
310
- if (serialized === undefined) continue;
311
- this.insertSettingStmt.run(key, serialized);
312
- }
321
+ logger.warn("AgentStorage.saveSettings is deprecated - settings are now stored in config.yml", {
322
+ keys: Object.keys(settings),
313
323
  });
314
-
315
- try {
316
- replace(entries);
317
- } catch (error) {
318
- logger.error("AgentStorage failed to save settings", { error: String(error) });
319
- }
320
324
  }
321
325
 
322
326
  /**
@@ -480,20 +484,20 @@ CREATE TABLE settings (
480
484
  * @param dbPath - Path to the database file
481
485
  */
482
486
  private ensureDir(dbPath: string): void {
483
- mkdirSync(dirname(dbPath), { recursive: true });
487
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
484
488
  }
485
489
 
486
490
  private hardenPermissions(dbPath: string): void {
487
- const dir = dirname(dbPath);
491
+ const dir = path.dirname(dbPath);
488
492
  try {
489
- chmodSync(dir, 0o700);
493
+ fs.chmodSync(dir, 0o700);
490
494
  } catch (error) {
491
495
  logger.warn("AgentStorage failed to chmod agent dir", { path: dir, error: String(error) });
492
496
  }
493
497
 
494
- if (!existsSync(dbPath)) return;
498
+ if (!fs.existsSync(dbPath)) return;
495
499
  try {
496
- chmodSync(dbPath, 0o600);
500
+ fs.chmodSync(dbPath, 0o600);
497
501
  } catch (error) {
498
502
  logger.warn("AgentStorage failed to chmod db file", { path: dbPath, error: String(error) });
499
503
  }
@@ -4,9 +4,8 @@
4
4
  * Artifacts are stored in a directory alongside the session file,
5
5
  * accessible via artifact:// URLs or the $ARTIFACTS environment variable.
6
6
  */
7
-
8
- import { mkdir, readdir } from "node:fs/promises";
9
- import { join } from "node:path";
7
+ import * as fs from "node:fs/promises";
8
+ import * as path from "node:path";
10
9
 
11
10
  /**
12
11
  * Manages artifact storage for a session.
@@ -18,6 +17,7 @@ export class ArtifactManager {
18
17
  #nextId = 0;
19
18
  readonly #dir: string;
20
19
  #dirCreated = false;
20
+ #initialized = false;
21
21
 
22
22
  /**
23
23
  * @param sessionFile Path to the session .jsonl file
@@ -37,9 +37,31 @@ export class ArtifactManager {
37
37
 
38
38
  async #ensureDir(): Promise<void> {
39
39
  if (!this.#dirCreated) {
40
- await mkdir(this.#dir, { recursive: true });
40
+ await fs.mkdir(this.#dir, { recursive: true });
41
41
  this.#dirCreated = true;
42
42
  }
43
+ if (!this.#initialized) {
44
+ await this.#scanExistingIds();
45
+ this.#initialized = true;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Scan existing artifact files to find the next available ID.
51
+ * This ensures we don't overwrite artifacts when resuming a session.
52
+ */
53
+ async #scanExistingIds(): Promise<void> {
54
+ const files = await this.listFiles();
55
+ let maxId = -1;
56
+ for (const file of files) {
57
+ // Files are named: {id}.{toolType}.log
58
+ const match = file.match(/^(\d+)\..*\.log$/);
59
+ if (match) {
60
+ const id = parseInt(match[1], 10);
61
+ if (id > maxId) maxId = id;
62
+ }
63
+ }
64
+ this.#nextId = maxId + 1;
43
65
  }
44
66
 
45
67
  /**
@@ -58,8 +80,8 @@ export class ArtifactManager {
58
80
  async allocatePath(toolType: string): Promise<{ id: string; path: string }> {
59
81
  await this.#ensureDir();
60
82
  const id = String(this.allocateId());
61
- const filename = `${id}.${toolType}.txt`;
62
- return { id, path: join(this.#dir, filename) };
83
+ const filename = `${id}.${toolType}.log`;
84
+ return { id, path: path.join(this.#dir, filename) };
63
85
  }
64
86
 
65
87
  /**
@@ -81,7 +103,7 @@ export class ArtifactManager {
81
103
  */
82
104
  async exists(id: string): Promise<boolean> {
83
105
  const files = await this.listFiles();
84
- return files.some((f) => f.startsWith(`${id}.`));
106
+ return files.some(f => f.startsWith(`${id}.`));
85
107
  }
86
108
 
87
109
  /**
@@ -90,7 +112,7 @@ export class ArtifactManager {
90
112
  */
91
113
  async listFiles(): Promise<string[]> {
92
114
  try {
93
- return await readdir(this.#dir);
115
+ return await fs.readdir(this.#dir);
94
116
  } catch {
95
117
  return [];
96
118
  }
@@ -104,7 +126,7 @@ export class ArtifactManager {
104
126
  */
105
127
  async getPath(id: string): Promise<string | null> {
106
128
  const files = await this.listFiles();
107
- const match = files.find((f) => f.startsWith(`${id}.`));
108
- return match ? join(this.#dir, match) : null;
129
+ const match = files.find(f => f.startsWith(`${id}.`));
130
+ return match ? path.join(this.#dir, match) : null;
109
131
  }
110
132
  }