@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,3 +1,7 @@
1
+ import { logger } from "@oh-my-pi/pi-utils";
2
+ import { OutputSink } from "../session/streaming-output";
3
+ import { time } from "../utils/timings";
4
+ import { shutdownSharedGateway } from "./gateway-coordinator";
1
5
  import {
2
6
  checkPythonKernelAvailability,
3
7
  type KernelDisplayOutput,
@@ -5,9 +9,12 @@ import {
5
9
  type KernelExecuteResult,
6
10
  type PreludeHelper,
7
11
  PythonKernel,
8
- } from "@oh-my-pi/pi-coding-agent/ipy/kernel";
9
- import { OutputSink } from "@oh-my-pi/pi-coding-agent/session/streaming-output";
10
- import { logger } from "@oh-my-pi/pi-utils";
12
+ } from "./kernel";
13
+
14
+ const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
15
+ const MAX_KERNEL_SESSIONS = 4;
16
+ const CLEANUP_INTERVAL_MS = 30 * 1000; // 30 seconds
17
+
11
18
  export type PythonKernelMode = "session" | "per-call";
12
19
 
13
20
  export interface PythonExecutorOptions {
@@ -77,10 +84,60 @@ interface KernelSession {
77
84
 
78
85
  const kernelSessions = new Map<string, KernelSession>();
79
86
  let cachedPreludeDocs: PreludeHelper[] | null = null;
87
+ let cleanupTimer: NodeJS.Timeout | null = null;
88
+
89
+ function startCleanupTimer(): void {
90
+ if (cleanupTimer) return;
91
+ cleanupTimer = setInterval(() => {
92
+ void cleanupIdleSessions();
93
+ }, CLEANUP_INTERVAL_MS);
94
+ cleanupTimer.unref();
95
+ }
96
+
97
+ function stopCleanupTimer(): void {
98
+ if (cleanupTimer) {
99
+ clearInterval(cleanupTimer);
100
+ cleanupTimer = null;
101
+ }
102
+ }
103
+
104
+ async function cleanupIdleSessions(): Promise<void> {
105
+ const now = Date.now();
106
+ const toDispose: KernelSession[] = [];
107
+
108
+ for (const session of kernelSessions.values()) {
109
+ if (session.dead || now - session.lastUsedAt > IDLE_TIMEOUT_MS) {
110
+ toDispose.push(session);
111
+ }
112
+ }
113
+
114
+ if (toDispose.length > 0) {
115
+ logger.debug("Cleaning up idle kernel sessions", { count: toDispose.length });
116
+ await Promise.allSettled(toDispose.map(session => disposeKernelSession(session)));
117
+ }
118
+
119
+ if (kernelSessions.size === 0) {
120
+ stopCleanupTimer();
121
+ }
122
+ }
123
+
124
+ async function evictOldestSession(): Promise<void> {
125
+ let oldest: KernelSession | null = null;
126
+ for (const session of kernelSessions.values()) {
127
+ if (!oldest || session.lastUsedAt < oldest.lastUsedAt) {
128
+ oldest = session;
129
+ }
130
+ }
131
+ if (oldest) {
132
+ logger.debug("Evicting oldest kernel session", { id: oldest.id });
133
+ await disposeKernelSession(oldest);
134
+ }
135
+ }
80
136
 
81
137
  export async function disposeAllKernelSessions(): Promise<void> {
138
+ stopCleanupTimer();
82
139
  const sessions = Array.from(kernelSessions.values());
83
- await Promise.allSettled(sessions.map((session) => disposeKernelSession(session)));
140
+ await Promise.allSettled(sessions.map(session => disposeKernelSession(session)));
84
141
  }
85
142
 
86
143
  async function ensureKernelAvailable(cwd: string): Promise<void> {
@@ -98,6 +155,7 @@ export async function warmPythonEnvironment(
98
155
  ): Promise<{ ok: boolean; reason?: string; docs: PreludeHelper[] }> {
99
156
  try {
100
157
  await ensureKernelAvailable(cwd);
158
+ time("warmPython:ensureKernelAvailable");
101
159
  } catch (err: unknown) {
102
160
  const reason = err instanceof Error ? err.message : String(err);
103
161
  cachedPreludeDocs = [];
@@ -111,10 +169,11 @@ export async function warmPythonEnvironment(
111
169
  const docs = await withKernelSession(
112
170
  resolvedSessionId,
113
171
  cwd,
114
- async (kernel) => kernel.introspectPrelude(),
172
+ async kernel => kernel.introspectPrelude(),
115
173
  useSharedGateway,
116
174
  sessionFile,
117
175
  );
176
+ time("warmPython:withKernelSession");
118
177
  cachedPreludeDocs = docs;
119
178
  return { ok: true, docs };
120
179
  } catch (err: unknown) {
@@ -132,12 +191,36 @@ export function resetPreludeDocsCache(): void {
132
191
  cachedPreludeDocs = null;
133
192
  }
134
193
 
194
+ function isResourceExhaustionError(error: unknown): boolean {
195
+ const message = error instanceof Error ? error.message : String(error);
196
+ return (
197
+ message.includes("Too many open files") ||
198
+ message.includes("EMFILE") ||
199
+ message.includes("ENFILE") ||
200
+ message.includes("resource temporarily unavailable")
201
+ );
202
+ }
203
+
204
+ async function recoverFromResourceExhaustion(): Promise<void> {
205
+ logger.warn("Resource exhaustion detected, recovering by restarting shared gateway");
206
+ stopCleanupTimer();
207
+ const sessions = Array.from(kernelSessions.values());
208
+ for (const session of sessions) {
209
+ if (session.heartbeatTimer) {
210
+ clearInterval(session.heartbeatTimer);
211
+ }
212
+ kernelSessions.delete(session.id);
213
+ }
214
+ await shutdownSharedGateway();
215
+ }
216
+
135
217
  async function createKernelSession(
136
218
  sessionId: string,
137
219
  cwd: string,
138
220
  useSharedGateway?: boolean,
139
221
  sessionFile?: string,
140
222
  artifactsDir?: string,
223
+ isRetry?: boolean,
141
224
  ): Promise<KernelSession> {
142
225
  const env: Record<string, string> | undefined =
143
226
  sessionFile || artifactsDir
@@ -146,7 +229,19 @@ async function createKernelSession(
146
229
  ...(artifactsDir ? { ARTIFACTS: artifactsDir } : {}),
147
230
  }
148
231
  : undefined;
149
- const kernel = await PythonKernel.start({ cwd, useSharedGateway, env });
232
+
233
+ let kernel: PythonKernel;
234
+ try {
235
+ kernel = await PythonKernel.start({ cwd, useSharedGateway, env });
236
+ time("createKernelSession:PythonKernel.start");
237
+ } catch (err) {
238
+ if (!isRetry && isResourceExhaustionError(err)) {
239
+ await recoverFromResourceExhaustion();
240
+ return createKernelSession(sessionId, cwd, useSharedGateway, sessionFile, artifactsDir, true);
241
+ }
242
+ throw err;
243
+ }
244
+
150
245
  const session: KernelSession = {
151
246
  id: sessionId,
152
247
  kernel,
@@ -218,8 +313,13 @@ async function withKernelSession<T>(
218
313
  ): Promise<T> {
219
314
  let session = kernelSessions.get(sessionId);
220
315
  if (!session) {
316
+ // Evict oldest session if at capacity
317
+ if (kernelSessions.size >= MAX_KERNEL_SESSIONS) {
318
+ await evictOldestSession();
319
+ }
221
320
  session = await createKernelSession(sessionId, cwd, useSharedGateway, sessionFile, artifactsDir);
222
321
  kernelSessions.set(sessionId, session);
322
+ startCleanupTimer();
223
323
  }
224
324
 
225
325
  const run = async (): Promise<T> => {
@@ -266,8 +366,8 @@ async function executeWithKernel(
266
366
  const result = await kernel.execute(code, {
267
367
  signal: options?.signal,
268
368
  timeoutMs: options?.timeoutMs,
269
- onChunk: (text) => sink.push(text),
270
- onDisplay: (output) => void displayOutputs.push(output),
369
+ onChunk: text => sink.push(text),
370
+ onDisplay: output => void displayOutputs.push(output),
271
371
  });
272
372
 
273
373
  if (result.cancelled) {
@@ -351,7 +451,7 @@ export async function executePython(code: string, options?: PythonExecutorOption
351
451
  return await withKernelSession(
352
452
  sessionId,
353
453
  cwd,
354
- async (kernel) => executeWithKernel(kernel, code, options),
454
+ async kernel => executeWithKernel(kernel, code, options),
355
455
  useSharedGateway,
356
456
  sessionFile,
357
457
  artifactsDir,
@@ -1,22 +1,12 @@
1
- import {
2
- closeSync,
3
- existsSync,
4
- mkdirSync,
5
- openSync,
6
- readFileSync,
7
- renameSync,
8
- statSync,
9
- unlinkSync,
10
- utimesSync,
11
- writeFileSync,
12
- } from "node:fs";
1
+ import * as fs from "node:fs";
13
2
  import { createServer } from "node:net";
14
- import { delimiter, join } from "node:path";
15
- import { getAgentDir } from "@oh-my-pi/pi-coding-agent/config";
16
- import { getShellConfig, killProcessTree } from "@oh-my-pi/pi-coding-agent/utils/shell";
17
- import { getOrCreateSnapshot } from "@oh-my-pi/pi-coding-agent/utils/shell-snapshot";
18
- import { logger } from "@oh-my-pi/pi-utils";
3
+ import * as path from "node:path";
4
+ import { isEnoent, logger } from "@oh-my-pi/pi-utils";
19
5
  import type { Subprocess } from "bun";
6
+ import { getAgentDir } from "../config";
7
+ import { getShellConfig, killProcessTree } from "../utils/shell";
8
+ import { getOrCreateSnapshot } from "../utils/shell-snapshot";
9
+ import { time } from "../utils/timings";
20
10
 
21
11
  const GATEWAY_DIR_NAME = "python-gateway";
22
12
  const GATEWAY_INFO_FILE = "gateway.json";
@@ -114,13 +104,13 @@ const CASE_INSENSITIVE_ENV = process.platform === "win32";
114
104
  const ACTIVE_ENV_ALLOWLIST = CASE_INSENSITIVE_ENV ? WINDOWS_ENV_ALLOWLIST : DEFAULT_ENV_ALLOWLIST;
115
105
 
116
106
  const NORMALIZED_ALLOWLIST = new Map(
117
- Array.from(ACTIVE_ENV_ALLOWLIST, (key) => [CASE_INSENSITIVE_ENV ? key.toUpperCase() : key, key] as const),
107
+ Array.from(ACTIVE_ENV_ALLOWLIST, key => [CASE_INSENSITIVE_ENV ? key.toUpperCase() : key, key] as const),
118
108
  );
119
109
  const NORMALIZED_DENYLIST = new Set(
120
- Array.from(DEFAULT_ENV_DENYLIST, (key) => (CASE_INSENSITIVE_ENV ? key.toUpperCase() : key)),
110
+ Array.from(DEFAULT_ENV_DENYLIST, key => (CASE_INSENSITIVE_ENV ? key.toUpperCase() : key)),
121
111
  );
122
112
  const NORMALIZED_ALLOW_PREFIXES = CASE_INSENSITIVE_ENV
123
- ? DEFAULT_ENV_ALLOW_PREFIXES.map((prefix) => prefix.toUpperCase())
113
+ ? DEFAULT_ENV_ALLOW_PREFIXES.map(prefix => prefix.toUpperCase())
124
114
  : DEFAULT_ENV_ALLOW_PREFIXES;
125
115
 
126
116
  function normalizeEnvKey(key: string): string {
@@ -129,7 +119,7 @@ function normalizeEnvKey(key: string): string {
129
119
 
130
120
  function resolvePathKey(env: Record<string, string | undefined>): string {
131
121
  if (!CASE_INSENSITIVE_ENV) return "PATH";
132
- const match = Object.keys(env).find((candidate) => candidate.toLowerCase() === "path");
122
+ const match = Object.keys(env).find(candidate => candidate.toLowerCase() === "path");
133
123
  return match ?? "PATH";
134
124
  }
135
125
 
@@ -166,7 +156,7 @@ function filterEnv(env: Record<string, string | undefined>): Record<string, stri
166
156
  filtered[canonicalKey] = value;
167
157
  continue;
168
158
  }
169
- if (NORMALIZED_ALLOW_PREFIXES.some((prefix) => normalizedKey.startsWith(prefix))) {
159
+ if (NORMALIZED_ALLOW_PREFIXES.some(prefix => normalizedKey.startsWith(prefix))) {
170
160
  filtered[key] = value;
171
161
  }
172
162
  }
@@ -175,7 +165,7 @@ function filterEnv(env: Record<string, string | undefined>): Record<string, stri
175
165
 
176
166
  async function resolveVenvPath(cwd: string): Promise<string | null> {
177
167
  if (process.env.VIRTUAL_ENV) return process.env.VIRTUAL_ENV;
178
- const candidates = [join(cwd, ".venv"), join(cwd, "venv")];
168
+ const candidates = [path.join(cwd, ".venv"), path.join(cwd, "venv")];
179
169
  for (const candidate of candidates) {
180
170
  if (await Bun.file(candidate).exists()) {
181
171
  return candidate;
@@ -189,12 +179,12 @@ async function resolvePythonRuntime(cwd: string, baseEnv: Record<string, string
189
179
  const venvPath = env.VIRTUAL_ENV ?? (await resolveVenvPath(cwd));
190
180
  if (venvPath) {
191
181
  env.VIRTUAL_ENV = venvPath;
192
- const binDir = process.platform === "win32" ? join(venvPath, "Scripts") : join(venvPath, "bin");
193
- const pythonCandidate = join(binDir, process.platform === "win32" ? "python.exe" : "python");
182
+ const binDir = process.platform === "win32" ? path.join(venvPath, "Scripts") : path.join(venvPath, "bin");
183
+ const pythonCandidate = path.join(binDir, process.platform === "win32" ? "python.exe" : "python");
194
184
  if (await Bun.file(pythonCandidate).exists()) {
195
185
  const pathKey = resolvePathKey(env);
196
186
  const currentPath = env[pathKey];
197
- env[pathKey] = currentPath ? `${binDir}${delimiter}${currentPath}` : binDir;
187
+ env[pathKey] = currentPath ? `${binDir}${path.delimiter}${currentPath}` : binDir;
198
188
  return { pythonPath: pythonCandidate, env, venvPath };
199
189
  }
200
190
  }
@@ -232,33 +222,29 @@ async function allocatePort(): Promise<number> {
232
222
  }
233
223
 
234
224
  function getGatewayDir(): string {
235
- return join(getAgentDir(), GATEWAY_DIR_NAME);
225
+ return path.join(getAgentDir(), GATEWAY_DIR_NAME);
236
226
  }
237
227
 
238
228
  function getGatewayInfoPath(): string {
239
- return join(getGatewayDir(), GATEWAY_INFO_FILE);
229
+ return path.join(getGatewayDir(), GATEWAY_INFO_FILE);
240
230
  }
241
231
 
242
232
  function getGatewayLockPath(): string {
243
- return join(getGatewayDir(), GATEWAY_LOCK_FILE);
233
+ return path.join(getGatewayDir(), GATEWAY_LOCK_FILE);
244
234
  }
245
235
 
246
- function writeLockInfo(lockPath: string, fd: number): void {
236
+ async function writeLockInfo(lockPath: string): Promise<void> {
247
237
  const payload: GatewayLockInfo = { pid: process.pid, startedAt: Date.now() };
248
238
  try {
249
- writeFileSync(fd, JSON.stringify(payload));
239
+ await Bun.write(lockPath, JSON.stringify(payload));
250
240
  } catch {
251
- try {
252
- writeFileSync(lockPath, JSON.stringify(payload));
253
- } catch {
254
- // Ignore lock write failures
255
- }
241
+ // Ignore lock write failures
256
242
  }
257
243
  }
258
244
 
259
- function readLockInfo(lockPath: string): GatewayLockInfo | null {
245
+ async function readLockInfo(lockPath: string): Promise<GatewayLockInfo | null> {
260
246
  try {
261
- const raw = readFileSync(lockPath, "utf-8");
247
+ const raw = await Bun.file(lockPath).text();
262
248
  const parsed = JSON.parse(raw) as Partial<GatewayLockInfo>;
263
249
  if (typeof parsed.pid === "number" && Number.isFinite(parsed.pid)) {
264
250
  return { pid: parsed.pid, startedAt: typeof parsed.startedAt === "number" ? parsed.startedAt : 0 };
@@ -269,36 +255,41 @@ function readLockInfo(lockPath: string): GatewayLockInfo | null {
269
255
  return null;
270
256
  }
271
257
 
272
- function ensureGatewayDir(): void {
258
+ async function ensureGatewayDir(): Promise<void> {
273
259
  const dir = getGatewayDir();
274
- if (!existsSync(dir)) {
275
- mkdirSync(dir, { recursive: true });
276
- }
260
+ await fs.promises.mkdir(dir, { recursive: true });
277
261
  }
278
262
 
279
263
  async function withGatewayLock<T>(handler: () => Promise<T>): Promise<T> {
280
- ensureGatewayDir();
264
+ await ensureGatewayDir();
281
265
  const lockPath = getGatewayLockPath();
282
266
  const start = Date.now();
283
267
  while (true) {
268
+ let fd: fs.promises.FileHandle | undefined;
284
269
  try {
285
- const fd = openSync(lockPath, "wx");
286
- const heartbeat = setInterval(() => {
287
- try {
288
- const now = new Date();
289
- utimesSync(lockPath, now, now);
290
- } catch {
291
- // Ignore heartbeat errors
270
+ fd = await fs.promises.open(lockPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL);
271
+ let heartbeatRunning = true;
272
+ const heartbeat = (async () => {
273
+ while (heartbeatRunning) {
274
+ await Bun.sleep(GATEWAY_LOCK_HEARTBEAT_MS);
275
+ if (!heartbeatRunning) break;
276
+ try {
277
+ const now = new Date();
278
+ await fs.promises.utimes(lockPath, now, now);
279
+ } catch {
280
+ // Ignore heartbeat errors
281
+ }
292
282
  }
293
- }, GATEWAY_LOCK_HEARTBEAT_MS);
283
+ })();
294
284
  try {
295
- writeLockInfo(lockPath, fd);
285
+ await writeLockInfo(lockPath);
296
286
  return await handler();
297
287
  } finally {
298
- clearInterval(heartbeat);
288
+ heartbeatRunning = false;
289
+ void heartbeat.catch(() => {}); // Don't await - let it die naturally
299
290
  try {
300
- closeSync(fd);
301
- unlinkSync(lockPath);
291
+ await fd.close();
292
+ await fs.promises.unlink(lockPath);
302
293
  } catch {
303
294
  // Ignore lock cleanup errors
304
295
  }
@@ -308,15 +299,15 @@ async function withGatewayLock<T>(handler: () => Promise<T>): Promise<T> {
308
299
  if (error.code === "EEXIST") {
309
300
  let removedStale = false;
310
301
  try {
311
- const stat = statSync(lockPath);
312
- const lockInfo = readLockInfo(lockPath);
302
+ const lockStat = await fs.promises.stat(lockPath);
303
+ const lockInfo = await readLockInfo(lockPath);
313
304
  const lockPid = lockInfo?.pid;
314
- const lockAgeMs = lockInfo?.startedAt ? Date.now() - lockInfo.startedAt : Date.now() - stat.mtimeMs;
305
+ const lockAgeMs = lockInfo?.startedAt ? Date.now() - lockInfo.startedAt : Date.now() - lockStat.mtimeMs;
315
306
  const staleByTime = lockAgeMs > GATEWAY_LOCK_STALE_MS;
316
307
  const staleByPid = lockPid !== undefined && !isPidRunning(lockPid);
317
308
  const staleByMissingPid = lockPid === undefined && staleByTime;
318
309
  if (staleByPid || staleByMissingPid) {
319
- unlinkSync(lockPath);
310
+ await fs.promises.unlink(lockPath);
320
311
  removedStale = true;
321
312
  logger.warn("Removed stale shared gateway lock", { path: lockPath, pid: lockPid });
322
313
  }
@@ -336,14 +327,12 @@ async function withGatewayLock<T>(handler: () => Promise<T>): Promise<T> {
336
327
  }
337
328
  }
338
329
 
339
- function readGatewayInfo(): GatewayInfo | null {
330
+ async function readGatewayInfo(): Promise<GatewayInfo | null> {
340
331
  const infoPath = getGatewayInfoPath();
341
- if (!existsSync(infoPath)) return null;
342
-
343
332
  try {
344
- const content = readFileSync(infoPath, "utf-8");
333
+ const content = await Bun.file(infoPath).text();
345
334
  const parsed = JSON.parse(content) as Partial<GatewayInfo>;
346
- if (!parsed || typeof parsed !== "object") return null;
335
+
347
336
  if (typeof parsed.url !== "string" || typeof parsed.pid !== "number" || typeof parsed.startedAt !== "number") {
348
337
  return null;
349
338
  }
@@ -354,26 +343,25 @@ function readGatewayInfo(): GatewayInfo | null {
354
343
  pythonPath: typeof parsed.pythonPath === "string" ? parsed.pythonPath : undefined,
355
344
  venvPath: typeof parsed.venvPath === "string" || parsed.venvPath === null ? parsed.venvPath : undefined,
356
345
  };
357
- } catch {
346
+ } catch (err) {
347
+ if (isEnoent(err)) return null;
358
348
  return null;
359
349
  }
360
350
  }
361
351
 
362
- function writeGatewayInfo(info: GatewayInfo): void {
352
+ async function writeGatewayInfo(info: GatewayInfo): Promise<void> {
363
353
  const infoPath = getGatewayInfoPath();
364
354
  const tempPath = `${infoPath}.tmp`;
365
- writeFileSync(tempPath, JSON.stringify(info, null, 2));
366
- renameSync(tempPath, infoPath);
355
+ await Bun.write(tempPath, JSON.stringify(info, null, 2));
356
+ await fs.promises.rename(tempPath, infoPath);
367
357
  }
368
358
 
369
- function clearGatewayInfo(): void {
359
+ async function clearGatewayInfo(): Promise<void> {
370
360
  const infoPath = getGatewayInfoPath();
371
- if (existsSync(infoPath)) {
372
- try {
373
- unlinkSync(infoPath);
374
- } catch {
375
- // Ignore errors on cleanup
376
- }
361
+ try {
362
+ await fs.promises.unlink(infoPath);
363
+ } catch {
364
+ // Ignore errors on cleanup (file may not exist)
377
365
  }
378
366
  }
379
367
 
@@ -388,12 +376,9 @@ function isPidRunning(pid: number): boolean {
388
376
 
389
377
  async function isGatewayHealthy(url: string): Promise<boolean> {
390
378
  try {
391
- const controller = new AbortController();
392
- const timeout = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);
393
379
  const response = await fetch(`${url}/api/kernelspecs`, {
394
- signal: controller.signal,
380
+ signal: AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS),
395
381
  });
396
- clearTimeout(timeout);
397
382
  return response.ok;
398
383
  } catch {
399
384
  return false;
@@ -497,9 +482,12 @@ export async function acquireSharedGateway(cwd: string): Promise<AcquireResult |
497
482
 
498
483
  try {
499
484
  return await withGatewayLock(async () => {
500
- const existingInfo = readGatewayInfo();
485
+ time("acquireSharedGateway:lockAcquired");
486
+ const existingInfo = await readGatewayInfo();
487
+ time("acquireSharedGateway:readInfo");
501
488
  if (existingInfo) {
502
489
  if (await isGatewayAlive(existingInfo)) {
490
+ time("acquireSharedGateway:isAlive");
503
491
  localGatewayUrl = existingInfo.url;
504
492
  isCoordinatorInitialized = true;
505
493
  logger.debug("Reusing global Python gateway", { url: existingInfo.url });
@@ -510,10 +498,11 @@ export async function acquireSharedGateway(cwd: string): Promise<AcquireResult |
510
498
  if (isPidRunning(existingInfo.pid)) {
511
499
  await killGateway(existingInfo.pid, "stale");
512
500
  }
513
- clearGatewayInfo();
501
+ await clearGatewayInfo();
514
502
  }
515
503
 
516
504
  const { url, pid, pythonPath, venvPath } = await startGatewayProcess(cwd);
505
+ time("acquireSharedGateway:startGateway");
517
506
  const info: GatewayInfo = {
518
507
  url,
519
508
  pid,
@@ -521,7 +510,7 @@ export async function acquireSharedGateway(cwd: string): Promise<AcquireResult |
521
510
  pythonPath,
522
511
  venvPath,
523
512
  };
524
- writeGatewayInfo(info);
513
+ await writeGatewayInfo(info);
525
514
  isCoordinatorInitialized = true;
526
515
  logger.debug("Started global Python gateway", { url, pid });
527
516
  return { url, isShared: true };
@@ -538,13 +527,13 @@ export async function releaseSharedGateway(): Promise<void> {
538
527
  if (!isCoordinatorInitialized) return;
539
528
  }
540
529
 
541
- export function getSharedGatewayUrl(): string | null {
530
+ export async function getSharedGatewayUrl(): Promise<string | null> {
542
531
  if (localGatewayUrl) return localGatewayUrl;
543
- return readGatewayInfo()?.url ?? null;
532
+ return (await readGatewayInfo())?.url ?? null;
544
533
  }
545
534
 
546
- export function isSharedGatewayActive(): boolean {
547
- return getGatewayStatus().active;
535
+ export async function isSharedGatewayActive(): Promise<boolean> {
536
+ return (await getGatewayStatus()).active;
548
537
  }
549
538
 
550
539
  export interface GatewayStatus {
@@ -556,8 +545,8 @@ export interface GatewayStatus {
556
545
  venvPath: string | null;
557
546
  }
558
547
 
559
- export function getGatewayStatus(): GatewayStatus {
560
- const info = readGatewayInfo();
548
+ export async function getGatewayStatus(): Promise<GatewayStatus> {
549
+ const info = await readGatewayInfo();
561
550
  if (!info) {
562
551
  return {
563
552
  active: false,
@@ -582,12 +571,12 @@ export function getGatewayStatus(): GatewayStatus {
582
571
  export async function shutdownSharedGateway(): Promise<void> {
583
572
  try {
584
573
  await withGatewayLock(async () => {
585
- const info = readGatewayInfo();
574
+ const info = await readGatewayInfo();
586
575
  if (!info) return;
587
576
  if (isPidRunning(info.pid)) {
588
577
  await killGateway(info.pid, "shutdown");
589
578
  }
590
- clearGatewayInfo();
579
+ await clearGatewayInfo();
591
580
  });
592
581
  } catch (err) {
593
582
  logger.warn("Failed to shutdown shared gateway", {