@oh-my-pi/pi-coding-agent 17.2.3 → 17.2.5

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 (375) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/{CHANGELOG-c5hpqt9r.md → CHANGELOG-q2w43aw3.md} +92 -0
  3. package/dist/cli.js +3979 -3548
  4. package/dist/{template-8vdv6xb4.js → template-c2hyaytt.js} +8 -26
  5. package/dist/types/advisor/advise-tool.d.ts +6 -0
  6. package/dist/types/advisor/runtime.d.ts +4 -4
  7. package/dist/types/capability/mcp.d.ts +9 -0
  8. package/dist/types/cli/args.d.ts +4 -1
  9. package/dist/types/cli/browser-relay-cli.d.ts +15 -0
  10. package/dist/types/cli/command-help.d.ts +99 -0
  11. package/dist/types/cli/help-extra.d.ts +2 -0
  12. package/dist/types/cli/models-cli.d.ts +1 -1
  13. package/dist/types/cli/thinking-levels.d.ts +5 -0
  14. package/dist/types/cli/update-cli.d.ts +13 -0
  15. package/dist/types/commands/browser-relay.d.ts +38 -0
  16. package/dist/types/commands/commit.d.ts +3 -0
  17. package/dist/types/commands/complete.d.ts +1 -1
  18. package/dist/types/commands/completions.d.ts +6 -0
  19. package/dist/types/commands/grep.d.ts +3 -0
  20. package/dist/types/commands/join.d.ts +4 -0
  21. package/dist/types/commands/launch-help.d.ts +171 -0
  22. package/dist/types/commands/launch.d.ts +5 -1
  23. package/dist/types/commands/models.d.ts +3 -0
  24. package/dist/types/commands/say.d.ts +10 -0
  25. package/dist/types/commands/worktree.d.ts +3 -0
  26. package/dist/types/config/keybindings.d.ts +6 -1
  27. package/dist/types/config/model-discovery.d.ts +2 -2
  28. package/dist/types/config/models-config-schema-bundle.d.ts +3 -0
  29. package/dist/types/config/models-config-schema.d.ts +2 -0
  30. package/dist/types/config/models-config.d.ts +1 -0
  31. package/dist/types/config/service-tier.d.ts +2 -0
  32. package/dist/types/config/settings-schema.d.ts +70 -22
  33. package/dist/types/cursor-bridge-tools.d.ts +1 -1
  34. package/dist/types/cursor.d.ts +7 -4
  35. package/dist/types/discovery/helpers.d.ts +6 -0
  36. package/dist/types/discovery/omp-extension-roots.d.ts +21 -2
  37. package/dist/types/edit/index.d.ts +3 -3
  38. package/dist/types/edit/modes/replace.d.ts +22 -15
  39. package/dist/types/edit/renderer.d.ts +1 -1
  40. package/dist/types/eval/py/kernel.d.ts +3 -1
  41. package/dist/types/eval/py/spawn-options.d.ts +12 -16
  42. package/dist/types/extensibility/extensions/loader.d.ts +6 -2
  43. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +8 -1
  44. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
  45. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
  46. package/dist/types/extensibility/plugins/marketplace/registry.d.ts +2 -2
  47. package/dist/types/launch/broker.d.ts +1 -1
  48. package/dist/types/launch/client.d.ts +6 -3
  49. package/dist/types/launch/ensure.d.ts +8 -0
  50. package/dist/types/launch/paths.d.ts +3 -2
  51. package/dist/types/launch/protocol.d.ts +2 -2
  52. package/dist/types/lsp/client.d.ts +2 -0
  53. package/dist/types/lsp/mux/daemon.d.ts +15 -0
  54. package/dist/types/lsp/mux/protocol.d.ts +57 -0
  55. package/dist/types/lsp/mux/server.d.ts +18 -0
  56. package/dist/types/lsp/types.d.ts +22 -2
  57. package/dist/types/main.d.ts +2 -0
  58. package/dist/types/mcp/request-id.d.ts +19 -0
  59. package/dist/types/mcp/types.d.ts +14 -0
  60. package/dist/types/memory-backend/index.d.ts +1 -0
  61. package/dist/types/memory-backend/messages.d.ts +15 -0
  62. package/dist/types/mnemopi/embed-client.d.ts +1 -1
  63. package/dist/types/mnemopi/state.d.ts +0 -23
  64. package/dist/types/modes/components/assistant-message.d.ts +2 -0
  65. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  66. package/dist/types/modes/components/model-browser.d.ts +7 -5
  67. package/dist/types/modes/components/model-picker.d.ts +9 -3
  68. package/dist/types/modes/components/read-tool-group.d.ts +11 -0
  69. package/dist/types/modes/components/stripped-tool-calls-placeholder.d.ts +12 -0
  70. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  71. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  72. package/dist/types/modes/interactive-mode.d.ts +1 -0
  73. package/dist/types/modes/types.d.ts +2 -1
  74. package/dist/types/modes/workflow.d.ts +2 -1
  75. package/dist/types/registry/agent-lifecycle.d.ts +14 -5
  76. package/dist/types/registry/agent-registry.d.ts +4 -3
  77. package/dist/types/sdk.d.ts +4 -2
  78. package/dist/types/secrets/index.d.ts +7 -9
  79. package/dist/types/session/agent-session-types.d.ts +3 -1
  80. package/dist/types/session/agent-session.d.ts +6 -1
  81. package/dist/types/session/indexed-session-storage.d.ts +1 -0
  82. package/dist/types/session/model-controls.d.ts +0 -1
  83. package/dist/types/session/session-advisors.d.ts +8 -1
  84. package/dist/types/session/session-context.d.ts +2 -1
  85. package/dist/types/session/session-maintenance.d.ts +2 -0
  86. package/dist/types/session/session-manager.d.ts +16 -5
  87. package/dist/types/session/session-stats.d.ts +10 -0
  88. package/dist/types/session/session-storage.d.ts +15 -3
  89. package/dist/types/system-prompt.d.ts +6 -1
  90. package/dist/types/task/index.d.ts +2 -1
  91. package/dist/types/task/spawn-policy.d.ts +5 -0
  92. package/dist/types/thinking.d.ts +1 -7
  93. package/dist/types/tools/ast-grep.d.ts +1 -1
  94. package/dist/types/tools/browser/registry.d.ts +2 -1
  95. package/dist/types/tools/browser/relay/bridge.d.ts +35 -0
  96. package/dist/types/tools/browser/relay/daemon.d.ts +16 -0
  97. package/dist/types/tools/browser/relay/kind.d.ts +19 -0
  98. package/dist/types/tools/browser/relay/protocol.d.ts +97 -0
  99. package/dist/types/tools/browser/relay/server.d.ts +35 -0
  100. package/dist/types/tools/browser/render.d.ts +1 -0
  101. package/dist/types/tools/browser.d.ts +3 -0
  102. package/dist/types/tools/computer/exposure.d.ts +9 -7
  103. package/dist/types/tools/computer/protocol.d.ts +70 -17
  104. package/dist/types/tools/computer/supervisor.d.ts +23 -10
  105. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  106. package/dist/types/tools/computer/worker.d.ts +42 -7
  107. package/dist/types/tools/computer-renderer.d.ts +7 -5
  108. package/dist/types/tools/computer.d.ts +25 -44
  109. package/dist/types/tools/context.d.ts +3 -0
  110. package/dist/types/tools/default-renderer.d.ts +3 -0
  111. package/dist/types/tools/glob.d.ts +1 -1
  112. package/dist/types/tools/grep.d.ts +1 -1
  113. package/dist/types/tools/render-utils.d.ts +2 -0
  114. package/dist/types/tools/{browser/run-cancellation.d.ts → run-scope.d.ts} +8 -8
  115. package/dist/types/tools/tool-timeouts.d.ts +5 -0
  116. package/dist/types/tools/xdev.d.ts +8 -0
  117. package/dist/types/utils/clipboard.d.ts +2 -2
  118. package/dist/types/web/kagi.d.ts +1 -0
  119. package/dist/types/web/search/index.d.ts +1 -1
  120. package/dist/types/web/search/providers/anthropic.d.ts +1 -0
  121. package/dist/types/web/search/providers/base.d.ts +2 -0
  122. package/dist/types/web/search/providers/brave.d.ts +1 -0
  123. package/dist/types/web/search/providers/browser-page.d.ts +1 -0
  124. package/dist/types/web/search/providers/codex.d.ts +1 -0
  125. package/dist/types/web/search/providers/exa.d.ts +1 -0
  126. package/dist/types/web/search/providers/firecrawl.d.ts +1 -0
  127. package/dist/types/web/search/providers/gemini.d.ts +1 -0
  128. package/dist/types/web/search/providers/jina.d.ts +1 -0
  129. package/dist/types/web/search/providers/kagi.d.ts +1 -0
  130. package/dist/types/web/search/providers/kimi.d.ts +1 -0
  131. package/dist/types/web/search/providers/parallel.d.ts +1 -0
  132. package/dist/types/web/search/providers/perplexity.d.ts +1 -0
  133. package/dist/types/web/search/providers/searxng.d.ts +1 -0
  134. package/dist/types/web/search/providers/tavily.d.ts +1 -0
  135. package/dist/types/web/search/providers/tinyfish.d.ts +1 -0
  136. package/dist/types/web/search/providers/utils.d.ts +6 -9
  137. package/dist/types/web/search/providers/zai.d.ts +1 -0
  138. package/dist/types/web/search/types.d.ts +4 -0
  139. package/package.json +12 -12
  140. package/scripts/legacy-pi-virtual-module.ts +12 -2
  141. package/src/advisor/advise-tool.ts +18 -0
  142. package/src/advisor/runtime.ts +6 -6
  143. package/src/capability/mcp.ts +7 -0
  144. package/src/cli/args.ts +7 -87
  145. package/src/cli/browser-relay-cli.ts +119 -0
  146. package/src/cli/command-help.ts +103 -0
  147. package/src/cli/completion-gen.ts +5 -5
  148. package/src/cli/flag-tables.ts +9 -0
  149. package/src/cli/help-extra.ts +89 -0
  150. package/src/cli/models-cli.ts +13 -16
  151. package/src/cli/setup-cli.ts +13 -21
  152. package/src/cli/thinking-levels.ts +7 -0
  153. package/src/cli/update-cli.ts +26 -7
  154. package/src/cli-commands.ts +170 -33
  155. package/src/cli.ts +17 -5
  156. package/src/collab/host.ts +1 -1
  157. package/src/commands/acp.ts +3 -1
  158. package/src/commands/agents.ts +3 -2
  159. package/src/commands/auth-broker.ts +3 -2
  160. package/src/commands/auth-gateway.ts +3 -2
  161. package/src/commands/bench.ts +2 -3
  162. package/src/commands/browser-relay.ts +53 -0
  163. package/src/commands/cleanse.ts +2 -2
  164. package/src/commands/commit.ts +3 -2
  165. package/src/commands/complete.ts +2 -1
  166. package/src/commands/completions.ts +3 -2
  167. package/src/commands/config.ts +3 -2
  168. package/src/commands/dry-balance.ts +2 -2
  169. package/src/commands/gallery.ts +3 -2
  170. package/src/commands/gc.ts +3 -2
  171. package/src/commands/grep.ts +3 -2
  172. package/src/commands/grievances.ts +3 -2
  173. package/src/commands/install.ts +2 -2
  174. package/src/commands/join.ts +3 -2
  175. package/src/commands/launch-help.ts +116 -0
  176. package/src/commands/launch.ts +7 -191
  177. package/src/commands/models.ts +3 -2
  178. package/src/commands/plugin.ts +3 -2
  179. package/src/commands/read.ts +3 -2
  180. package/src/commands/say.ts +3 -2
  181. package/src/commands/setup.ts +3 -2
  182. package/src/commands/shell.ts +3 -2
  183. package/src/commands/ssh.ts +3 -2
  184. package/src/commands/stats.ts +3 -2
  185. package/src/commands/tiny-models.ts +2 -2
  186. package/src/commands/token.ts +2 -2
  187. package/src/commands/ttsr.ts +2 -2
  188. package/src/commands/update.ts +3 -2
  189. package/src/commands/usage.ts +3 -2
  190. package/src/commands/web-search.ts +3 -2
  191. package/src/commands/worktree.ts +3 -2
  192. package/src/config/keybindings.ts +19 -9
  193. package/src/config/mcp-schema.json +5 -0
  194. package/src/config/model-discovery.ts +30 -13
  195. package/src/config/model-registry.ts +16 -6
  196. package/src/config/models-config-schema-bundle.ts +9 -0
  197. package/src/config/resolve-config-value.ts +2 -1
  198. package/src/config/service-tier.ts +5 -0
  199. package/src/config/settings-schema.ts +73 -21
  200. package/src/cursor-bridge-tools.ts +1 -1
  201. package/src/cursor.ts +10 -14
  202. package/src/discovery/builtin.ts +10 -0
  203. package/src/discovery/helpers.ts +10 -0
  204. package/src/discovery/mcp-json.ts +11 -1
  205. package/src/discovery/omp-extension-roots.ts +79 -20
  206. package/src/discovery/omp-plugins.ts +10 -1
  207. package/src/edit/diff.ts +17 -8
  208. package/src/edit/index.ts +20 -7
  209. package/src/edit/modes/replace.ts +93 -40
  210. package/src/edit/renderer.ts +1 -1
  211. package/src/edit/streaming.ts +14 -17
  212. package/src/eval/py/kernel.ts +2 -1
  213. package/src/eval/py/spawn-options.ts +24 -29
  214. package/src/export/html/template.js +8 -26
  215. package/src/extensibility/extensions/loader.ts +66 -28
  216. package/src/extensibility/extensions/wrapper.ts +1 -0
  217. package/src/extensibility/legacy-pi-ai-shim.ts +22 -1
  218. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -6
  219. package/src/extensibility/plugins/legacy-pi-compat.ts +124 -57
  220. package/src/extensibility/plugins/marketplace/registry.ts +4 -8
  221. package/src/extensibility/skills.ts +11 -0
  222. package/src/extensibility/utils.ts +10 -2
  223. package/src/launch/broker.ts +1 -1
  224. package/src/launch/client.ts +32 -13
  225. package/src/launch/ensure.ts +73 -0
  226. package/src/launch/paths.ts +3 -6
  227. package/src/launch/protocol.ts +2 -2
  228. package/src/lsp/client.ts +23 -2
  229. package/src/lsp/index.ts +8 -0
  230. package/src/lsp/mux/daemon.ts +348 -0
  231. package/src/lsp/mux/protocol.ts +96 -0
  232. package/src/lsp/mux/server.ts +797 -0
  233. package/src/lsp/types.ts +28 -2
  234. package/src/main.ts +31 -24
  235. package/src/mcp/config.ts +1 -0
  236. package/src/mcp/oauth-flow.ts +11 -1
  237. package/src/mcp/request-id.ts +24 -0
  238. package/src/mcp/transports/http.ts +4 -2
  239. package/src/mcp/transports/sse.ts +4 -2
  240. package/src/mcp/transports/stdio.ts +4 -2
  241. package/src/mcp/types.ts +15 -0
  242. package/src/memory-backend/index.ts +1 -0
  243. package/src/memory-backend/messages.ts +19 -0
  244. package/src/mnemopi/embed-client.ts +49 -2
  245. package/src/mnemopi/state.ts +57 -23
  246. package/src/modes/components/agent-hub.ts +1 -1
  247. package/src/modes/components/assistant-message.ts +11 -0
  248. package/src/modes/components/chat-transcript-builder.ts +4 -0
  249. package/src/modes/components/custom-editor.ts +10 -1
  250. package/src/modes/components/model-browser.ts +25 -17
  251. package/src/modes/components/model-picker.ts +10 -6
  252. package/src/modes/components/read-tool-group.ts +36 -0
  253. package/src/modes/components/status-line/component.ts +1 -1
  254. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  255. package/src/modes/components/tool-execution.ts +85 -20
  256. package/src/modes/components/tree-selector.ts +46 -48
  257. package/src/modes/controllers/command-controller.ts +2 -2
  258. package/src/modes/controllers/event-controller.ts +199 -10
  259. package/src/modes/controllers/input-controller.ts +39 -1
  260. package/src/modes/controllers/mcp-command-controller.ts +11 -5
  261. package/src/modes/controllers/selector-controller.ts +47 -6
  262. package/src/modes/interactive-mode.ts +2 -0
  263. package/src/modes/types.ts +6 -1
  264. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  265. package/src/modes/utils/interactive-context-helpers.ts +1 -0
  266. package/src/modes/utils/ui-helpers.ts +6 -10
  267. package/src/modes/workflow.ts +8 -2
  268. package/src/prompts/agents/init.md +1 -1
  269. package/src/prompts/system/plan-mode-active.md +2 -2
  270. package/src/prompts/system/system-prompt.md +26 -52
  271. package/src/prompts/system/workflow-notice.md +3 -3
  272. package/src/prompts/tools/ast-grep.md +1 -1
  273. package/src/prompts/tools/bash.md +1 -1
  274. package/src/prompts/tools/browser.md +1 -0
  275. package/src/prompts/tools/computer.md +21 -21
  276. package/src/prompts/tools/glob.md +1 -1
  277. package/src/prompts/tools/grep.md +1 -1
  278. package/src/prompts/tools/replace.md +5 -4
  279. package/src/prompts/tools/task.md +4 -4
  280. package/src/registry/agent-lifecycle.ts +36 -11
  281. package/src/registry/agent-registry.ts +12 -5
  282. package/src/sdk.ts +102 -35
  283. package/src/secrets/index.ts +16 -18
  284. package/src/session/agent-session-types.ts +3 -1
  285. package/src/session/agent-session.ts +23 -2
  286. package/src/session/agent-storage.ts +6 -4
  287. package/src/session/history-storage.ts +4 -2
  288. package/src/session/indexed-session-storage.ts +9 -0
  289. package/src/session/messages.ts +31 -7
  290. package/src/session/model-controls.ts +0 -1
  291. package/src/session/prewalk.ts +24 -2
  292. package/src/session/session-advisors.ts +21 -3
  293. package/src/session/session-context.ts +16 -12
  294. package/src/session/session-dump-format.ts +1 -1
  295. package/src/session/session-maintenance.ts +41 -4
  296. package/src/session/session-manager.ts +181 -94
  297. package/src/session/session-paths.ts +118 -55
  298. package/src/session/session-stats.ts +48 -3
  299. package/src/session/session-storage.ts +30 -22
  300. package/src/session/session-tools.ts +25 -23
  301. package/src/session/stream-guards.ts +1 -1
  302. package/src/slash-commands/builtin-registry.ts +29 -26
  303. package/src/system-prompt.ts +14 -4
  304. package/src/task/executor.ts +8 -1
  305. package/src/task/index.ts +23 -8
  306. package/src/task/spawn-policy.ts +14 -0
  307. package/src/thinking.ts +2 -8
  308. package/src/tiny/text.ts +12 -0
  309. package/src/tools/ast-grep.ts +10 -4
  310. package/src/tools/browser/cmux/cmux-tab.ts +5 -10
  311. package/src/tools/browser/cmux/rpc.ts +2 -8
  312. package/src/tools/browser/registry.ts +53 -2
  313. package/src/tools/browser/relay/bridge.ts +945 -0
  314. package/src/tools/browser/relay/daemon.ts +117 -0
  315. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  316. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  317. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  318. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  319. package/src/tools/browser/relay/kind.ts +41 -0
  320. package/src/tools/browser/relay/protocol.ts +54 -0
  321. package/src/tools/browser/relay/server.ts +141 -0
  322. package/src/tools/browser/render.ts +4 -1
  323. package/src/tools/browser/shared-daemon.ts +7 -58
  324. package/src/tools/browser/tab-supervisor.ts +5 -4
  325. package/src/tools/browser/tab-worker.ts +33 -12
  326. package/src/tools/browser.ts +26 -1
  327. package/src/tools/computer/exposure.ts +11 -35
  328. package/src/tools/computer/protocol.ts +51 -10
  329. package/src/tools/computer/supervisor.ts +227 -124
  330. package/src/tools/computer/worker-entry.ts +11 -6
  331. package/src/tools/computer/worker.ts +695 -81
  332. package/src/tools/computer-renderer.ts +104 -65
  333. package/src/tools/computer.ts +134 -428
  334. package/src/tools/context.ts +3 -0
  335. package/src/tools/default-renderer.ts +19 -4
  336. package/src/tools/glob.ts +9 -2
  337. package/src/tools/grep.ts +12 -6
  338. package/src/tools/index.ts +10 -6
  339. package/src/tools/inspect-image.ts +32 -6
  340. package/src/tools/render-utils.ts +2 -0
  341. package/src/tools/{browser/run-cancellation.ts → run-scope.ts} +11 -11
  342. package/src/tools/tool-timeouts.ts +1 -0
  343. package/src/tools/xdev.ts +29 -2
  344. package/src/utils/clipboard.ts +40 -14
  345. package/src/utils/command-args.ts +26 -28
  346. package/src/utils/tool-choice.ts +0 -13
  347. package/src/web/kagi.ts +2 -1
  348. package/src/web/search/index.ts +18 -2
  349. package/src/web/search/providers/anthropic.ts +7 -2
  350. package/src/web/search/providers/base.ts +2 -0
  351. package/src/web/search/providers/brave.ts +3 -1
  352. package/src/web/search/providers/browser-page.ts +6 -4
  353. package/src/web/search/providers/codex.ts +4 -1
  354. package/src/web/search/providers/duckduckgo.ts +2 -1
  355. package/src/web/search/providers/ecosia.ts +2 -1
  356. package/src/web/search/providers/exa.ts +4 -2
  357. package/src/web/search/providers/firecrawl.ts +3 -1
  358. package/src/web/search/providers/gemini.ts +8 -2
  359. package/src/web/search/providers/google.ts +2 -1
  360. package/src/web/search/providers/jina.ts +12 -2
  361. package/src/web/search/providers/kagi.ts +3 -0
  362. package/src/web/search/providers/kimi.ts +5 -1
  363. package/src/web/search/providers/mojeek.ts +2 -1
  364. package/src/web/search/providers/parallel.ts +5 -1
  365. package/src/web/search/providers/perplexity.ts +6 -3
  366. package/src/web/search/providers/public.ts +1 -1
  367. package/src/web/search/providers/searxng.ts +11 -5
  368. package/src/web/search/providers/startpage.ts +10 -4
  369. package/src/web/search/providers/synthetic.ts +11 -5
  370. package/src/web/search/providers/tavily.ts +3 -1
  371. package/src/web/search/providers/tinyfish.ts +3 -1
  372. package/src/web/search/providers/utils.ts +12 -10
  373. package/src/web/search/providers/xai.ts +2 -2
  374. package/src/web/search/providers/zai.ts +18 -3
  375. package/src/web/search/types.ts +6 -0
@@ -665,7 +665,6 @@ const bareRequireResolutionCache = new Map<string, Promise<string | null>>();
665
665
  const realpathCache = new Map<string, Promise<string>>();
666
666
  const nativeAddonResolutionCache = new Map<string, Promise<string | null>>();
667
667
  const nativeAddonRequireScanCache = new Map<string, Promise<boolean>>();
668
- const nativeAddonLoaderModulePaths = new Set<string>();
669
668
 
670
669
  function clearLegacyPiResolutionCaches(): void {
671
670
  resolvedSpecifierFallbacks.clear();
@@ -677,7 +676,6 @@ function clearLegacyPiResolutionCaches(): void {
677
676
  bareRequireResolutionCache.clear();
678
677
  nativeAddonResolutionCache.clear();
679
678
  nativeAddonRequireScanCache.clear();
680
- nativeAddonLoaderModulePaths.clear();
681
679
  realpathCache.clear();
682
680
  }
683
681
 
@@ -1076,6 +1074,23 @@ async function resolveSourceModuleFile(basePath: string): Promise<string | null>
1076
1074
  return null;
1077
1075
  }
1078
1076
 
1077
+ async function resolveRelativeCommonJsRequire(specifier: string, importerPath: string): Promise<string | null> {
1078
+ const candidate = path.resolve(path.dirname(importerPath), specifier);
1079
+ try {
1080
+ const stats = await fs.promises.stat(candidate);
1081
+ if (stats.isDirectory()) {
1082
+ const manifest = await readPackageManifest(candidate);
1083
+ if (typeof manifest?.main === "string") {
1084
+ const main = await resolveSourceModuleFile(path.resolve(candidate, manifest.main));
1085
+ if (main) return main;
1086
+ }
1087
+ }
1088
+ } catch {
1089
+ // Missing candidates fall through to extension and index resolution.
1090
+ }
1091
+ return resolveSourceModuleFile(candidate);
1092
+ }
1093
+
1079
1094
  function isPathInsideRoot(rootPath: string, candidatePath: string): boolean {
1080
1095
  const relative = path.relative(rootPath, candidatePath);
1081
1096
  return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
@@ -1255,7 +1270,7 @@ function isBareExtensionDependencySpecifier(specifier: string): boolean {
1255
1270
  return false;
1256
1271
  }
1257
1272
  const packageName = specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
1258
- return Boolean(packageName && !isBuiltin(packageName));
1273
+ return Boolean(packageName && !isBuiltin(specifier));
1259
1274
  }
1260
1275
 
1261
1276
  interface BarePackageSpecifier {
@@ -1319,7 +1334,6 @@ async function readPackageManifestUncached(packageRoot: string): Promise<Record<
1319
1334
  }
1320
1335
 
1321
1336
  type ExtensionModuleKind = "commonjs" | "esm";
1322
- class ExtensionModuleKindConflictError extends Error {}
1323
1337
 
1324
1338
  async function isCommonJsModulePath(
1325
1339
  modulePath: string,
@@ -1440,7 +1454,7 @@ async function resolveNodePackageFallback(
1440
1454
  subpath: string | null,
1441
1455
  manifest: Record<string, unknown>,
1442
1456
  ): Promise<string | null> {
1443
- if (subpath !== null) {
1457
+ if (subpath) {
1444
1458
  return resolvePackageSourceTarget(packageRoot, path.join(packageRoot, subpath));
1445
1459
  }
1446
1460
  for (const field of ["module", "main"]) {
@@ -1476,7 +1490,7 @@ async function resolveNodePackageRequire(specifier: string, importerPath: string
1476
1490
  if (Object.hasOwn(manifest, "exports")) {
1477
1491
  return resolveNodePackageExport(packageRoot, parsed.subpath, manifest, SUPPORTED_PACKAGE_REQUIRE_CONDITIONS);
1478
1492
  }
1479
- if (parsed.subpath !== null) {
1493
+ if (parsed.subpath) {
1480
1494
  return resolvePackageSourceTarget(packageRoot, path.join(packageRoot, parsed.subpath));
1481
1495
  }
1482
1496
  const main = manifest.main;
@@ -1638,6 +1652,9 @@ async function resolveExtensionBareRequire(specifier: string, importerPath: stri
1638
1652
  }
1639
1653
 
1640
1654
  async function resolveExtensionCommonJsRequire(specifier: string, importerPath: string): Promise<string | null> {
1655
+ if (specifier.startsWith(".")) {
1656
+ return resolveRelativeCommonJsRequire(specifier, importerPath);
1657
+ }
1641
1658
  const remappedSpecifier = remapLegacyPiSpecifier(specifier);
1642
1659
  if (remappedSpecifier) {
1643
1660
  try {
@@ -1757,6 +1774,7 @@ const extensionGraphCacheBustResolvedImportModules = new Map<string, Set<string>
1757
1774
  const commonJsModuleSources = new Map<string, string>();
1758
1775
  const commonJsFallbackModulePaths = new Map<string, string>();
1759
1776
  const extensionSynchronousSpecifierTargets = new Map<string, Map<string, string>>();
1777
+ const synchronousModuleSources = new Map<string, string>();
1760
1778
  const commonJsGraphModulePaths = new Set<string>();
1761
1779
  const COMMONJS_REQUIRE_GLOBAL = "__ompLegacyPiRequireGraphModule";
1762
1780
  const commonJsModuleDefinitions = new Map<string, { source: string; filename: string; dirname: string }>();
@@ -1886,6 +1904,7 @@ interface ExtensionModuleGraph {
1886
1904
  readonly modules: Map<string, string>;
1887
1905
  readonly cacheBustResolvedImportModules: Set<string>;
1888
1906
  readonly commonJsPaths: Set<string>;
1907
+ readonly synchronousSourcePaths: Set<string>;
1889
1908
  }
1890
1909
 
1891
1910
  /**
@@ -1901,6 +1920,7 @@ interface ExtensionModuleGraph {
1901
1920
  async function collectExtensionModules(entryRealPath: string): Promise<ExtensionModuleGraph> {
1902
1921
  const modules = new Map<string, string>();
1903
1922
  const commonJsPaths = new Set<string>();
1923
+ const synchronousSourcePaths = new Set<string>();
1904
1924
  const queuedCacheBustResolvedImports = new Map<string, boolean>([[entryRealPath, true]]);
1905
1925
  const queuedModuleKinds = new Map<string, ExtensionModuleKind>([[entryRealPath, "esm"]]);
1906
1926
  const queuedEsmBranchPaths = new Set<string>();
@@ -1949,10 +1969,14 @@ async function collectExtensionModules(entryRealPath: string): Promise<Extension
1949
1969
  let resolvedModuleKind: ExtensionModuleKind | undefined;
1950
1970
  let resolvedEsmBranch = false;
1951
1971
  let requiresNativeAddonRewrite = false;
1972
+ let requiresSynchronousSourceHook = false;
1973
+ let synchronousSourceUpgraded = false;
1952
1974
  const isRequired = reference.kind === "require";
1953
1975
  if (specifier.startsWith(".")) {
1954
- const candidate = Bun.resolveSync(specifier, dir);
1955
- if (hasSourceModuleExtension(candidate)) {
1976
+ const candidate = isRequired
1977
+ ? await resolveRelativeCommonJsRequire(specifier, file)
1978
+ : Bun.resolveSync(specifier, dir);
1979
+ if (candidate && hasSourceModuleExtension(candidate)) {
1956
1980
  const inheritedTargetKind = isRequired
1957
1981
  ? sourceIsCommonJs
1958
1982
  ? "commonjs"
@@ -1966,10 +1990,11 @@ async function collectExtensionModules(entryRealPath: string): Promise<Extension
1966
1990
  const isCommonJsDescendant = isRequired && sourceIsCommonJs && targetIsCommonJs;
1967
1991
  requiresNativeAddonRewrite =
1968
1992
  isRequired && !isCommonJsDescendant && (await moduleRequiresNativeAddon(candidate));
1969
- if (!isRequired || isCommonJsDescendant || requiresNativeAddonRewrite) {
1993
+ if (!isRequired || isCommonJsDescendant || requiresNativeAddonRewrite || !targetIsCommonJs) {
1970
1994
  resolved = await realpathOrSelf(candidate);
1971
1995
  resolvedModuleKind = targetIsCommonJs ? "commonjs" : "esm";
1972
1996
  resolvedEsmBranch = !targetIsCommonJs && esmBranch;
1997
+ requiresSynchronousSourceHook = isRequired && !targetIsCommonJs;
1973
1998
  }
1974
1999
  }
1975
2000
  } else if (specifier.startsWith("#")) {
@@ -1988,10 +2013,11 @@ async function collectExtensionModules(entryRealPath: string): Promise<Extension
1988
2013
  const isCommonJsDescendant = isRequired && sourceIsCommonJs && targetIsCommonJs;
1989
2014
  requiresNativeAddonRewrite =
1990
2015
  isRequired && !isCommonJsDescendant && (await moduleRequiresNativeAddon(candidate));
1991
- if (!isRequired || isCommonJsDescendant || requiresNativeAddonRewrite) {
2016
+ if (!isRequired || isCommonJsDescendant || requiresNativeAddonRewrite || !targetIsCommonJs) {
1992
2017
  resolved = candidate;
1993
2018
  resolvedModuleKind = targetIsCommonJs ? "commonjs" : "esm";
1994
2019
  resolvedEsmBranch = !targetIsCommonJs && esmBranch;
2020
+ requiresSynchronousSourceHook = isRequired && !targetIsCommonJs;
1995
2021
  }
1996
2022
  }
1997
2023
  } else if (
@@ -2022,6 +2048,9 @@ async function collectExtensionModules(entryRealPath: string): Promise<Extension
2022
2048
  : false;
2023
2049
  if (isHookableEntry && dependencyEntry && (!isRequired || (sourceIsCommonJs && isCommonJsEntry))) {
2024
2050
  resolved = await realpathOrSelf(dependencyEntry);
2051
+ } else if (isHookableEntry && dependencyEntry && isRequired && !isCommonJsEntry) {
2052
+ resolved = await realpathOrSelf(dependencyEntry);
2053
+ requiresSynchronousSourceHook = true;
2025
2054
  } else if (isHookableEntry && dependencyEntry && isRequired) {
2026
2055
  requiresNativeAddonRewrite = await moduleRequiresNativeAddon(dependencyEntry);
2027
2056
  if (requiresNativeAddonRewrite) {
@@ -2034,26 +2063,37 @@ async function collectExtensionModules(entryRealPath: string): Promise<Extension
2034
2063
  }
2035
2064
  nextCacheBustResolvedImports = false;
2036
2065
  }
2037
- if (resolved && requiresNativeAddonRewrite) {
2038
- nativeAddonLoaderModulePaths.add(resolved);
2066
+ if (
2067
+ resolved &&
2068
+ (requiresNativeAddonRewrite ||
2069
+ requiresSynchronousSourceHook ||
2070
+ (synchronousSourcePaths.has(file) && resolvedModuleKind === "esm"))
2071
+ ) {
2072
+ synchronousSourceUpgraded = !synchronousSourcePaths.has(resolved);
2073
+ synchronousSourcePaths.add(resolved);
2039
2074
  }
2040
2075
  if (resolved) {
2041
2076
  const queuedCacheBust = queuedCacheBustResolvedImports.get(resolved) ?? false;
2042
2077
  const mergedCacheBust = queuedCacheBust || nextCacheBustResolvedImports;
2043
2078
  queuedCacheBustResolvedImports.set(resolved, mergedCacheBust);
2044
2079
  const queuedModuleKind = queuedModuleKinds.get(resolved);
2045
- if (queuedModuleKind && resolvedModuleKind && queuedModuleKind !== resolvedModuleKind) {
2046
- throw new ExtensionModuleKindConflictError(
2047
- `Conflicting extension module kinds for ${resolved}: ${queuedModuleKind} and ${resolvedModuleKind}`,
2048
- );
2049
- }
2050
- const mergedModuleKind = queuedModuleKind ?? resolvedModuleKind;
2080
+ const mergedEsmBranch = queuedEsmBranchPaths.has(resolved) || resolvedEsmBranch;
2081
+ const mergedModuleKind =
2082
+ queuedModuleKind && resolvedModuleKind && queuedModuleKind !== resolvedModuleKind
2083
+ ? mergedEsmBranch
2084
+ ? "esm"
2085
+ : "commonjs"
2086
+ : (queuedModuleKind ?? resolvedModuleKind);
2051
2087
  if (mergedModuleKind) {
2052
2088
  queuedModuleKinds.set(resolved, mergedModuleKind);
2053
2089
  }
2054
- if (resolvedEsmBranch) {
2090
+ if (mergedEsmBranch) {
2055
2091
  queuedEsmBranchPaths.add(resolved);
2056
2092
  }
2093
+ if (modules.has(resolved) && (queuedModuleKind !== mergedModuleKind || synchronousSourceUpgraded)) {
2094
+ modules.delete(resolved);
2095
+ commonJsPaths.delete(resolved);
2096
+ }
2057
2097
  if (!modules.has(resolved)) {
2058
2098
  queue.push({
2059
2099
  file: resolved,
@@ -2063,22 +2103,22 @@ async function collectExtensionModules(entryRealPath: string): Promise<Extension
2063
2103
  });
2064
2104
  }
2065
2105
  }
2066
- } catch (error) {
2067
- if (error instanceof ExtensionModuleKindConflictError) {
2068
- throw error;
2069
- }
2106
+ } catch {
2070
2107
  // Unresolvable import (e.g. a type-only path); skip it.
2071
2108
  }
2072
2109
  }
2073
2110
  }
2074
2111
  for (const [modulePath, source] of modules) {
2075
- if (commonJsPaths.has(modulePath) || nativeAddonLoaderModulePaths.has(modulePath)) {
2076
- modules.set(modulePath, await rewriteExtensionSpecifiers(source, modulePath, commonJsPaths.has(modulePath)));
2112
+ if (commonJsPaths.has(modulePath)) {
2113
+ modules.set(modulePath, await rewriteExtensionSpecifiers(source, modulePath, true));
2114
+ } else if (synchronousSourcePaths.has(modulePath)) {
2115
+ modules.set(modulePath, await rewriteLegacyExtensionSource(source, modulePath));
2077
2116
  }
2078
2117
  }
2079
2118
  return {
2080
2119
  modules,
2081
2120
  commonJsPaths,
2121
+ synchronousSourcePaths,
2082
2122
  cacheBustResolvedImportModules: new Set(
2083
2123
  [...queuedCacheBustResolvedImports]
2084
2124
  .filter(([modulePath, enabled]) => enabled && modules.has(modulePath))
@@ -2087,6 +2127,15 @@ async function collectExtensionModules(entryRealPath: string): Promise<Extension
2087
2127
  };
2088
2128
  }
2089
2129
 
2130
+ /** Test seam for compiled-binary dependency graph discovery and rewriting. */
2131
+ export async function __collectLegacyPiExtensionSourcesForTests(
2132
+ entryPath: string,
2133
+ ): Promise<ReadonlyMap<string, string>> {
2134
+ const entryRealPath = await realpathOrSelf(path.resolve(entryPath));
2135
+ const graph = await collectExtensionModules(entryRealPath);
2136
+ return graph.modules;
2137
+ }
2138
+
2090
2139
  /**
2091
2140
  * Discovers CommonJS export names Bun normally exposes to ESM importers. The
2092
2141
  * bridge must declare them statically because its default export is synthetic.
@@ -2252,19 +2301,15 @@ async function installExtensionGraphHook(
2252
2301
  entryRealPath: string,
2253
2302
  modules: Map<string, string>,
2254
2303
  commonJsPaths: Set<string>,
2304
+ synchronousSourcePaths: ReadonlySet<string>,
2255
2305
  cacheBustResolvedImportModules: ReadonlySet<string>,
2256
- ): Promise<{ asyncModules: Map<string, string>; syncSourceModules: Map<string, string> }> {
2306
+ ): Promise<{ asyncModules: Map<string, string> }> {
2257
2307
  const asyncModules = new Map<string, string>();
2258
- const syncSourceModules = new Map<string, string>();
2259
2308
  for (const [modulePath, source] of modules) {
2260
- if (commonJsPaths.has(modulePath)) {
2309
+ if (commonJsPaths.has(modulePath) || synchronousSourcePaths.has(modulePath)) {
2261
2310
  continue;
2262
2311
  }
2263
- if (nativeAddonLoaderModulePaths.has(modulePath)) {
2264
- syncSourceModules.set(modulePath, source);
2265
- } else {
2266
- asyncModules.set(modulePath, source);
2267
- }
2312
+ asyncModules.set(modulePath, source);
2268
2313
  }
2269
2314
 
2270
2315
  if (asyncModules.size > 0) {
@@ -2274,24 +2319,35 @@ async function installExtensionGraphHook(
2274
2319
  Bun.plugin({
2275
2320
  name: `omp:legacy-pi-ext:${hookId}`,
2276
2321
  setup(build) {
2277
- build.onLoad({ filter, namespace: "file" }, async args => {
2322
+ build.onLoad({ filter, namespace: "file" }, args => {
2278
2323
  const queryIndex = args.path.indexOf("?mtime=");
2279
2324
  const sourcePath = queryIndex >= 0 ? args.path.slice(0, queryIndex) : args.path;
2280
2325
  const mtimeTag = queryIndex >= 0 ? args.path.slice(queryIndex + "?mtime=".length) : null;
2281
- const cached = asyncModules.get(sourcePath);
2282
- let raw: string;
2283
- if (cached !== undefined) {
2284
- // consume-once: preserves ?mtime edit-pickup for re-imports
2285
- asyncModules.delete(sourcePath);
2286
- raw = cached;
2287
- } else {
2288
- raw = await Bun.file(sourcePath).text();
2326
+ // A later reload can upgrade this module to synchronous loading (a
2327
+ // new `require()` edge reaches it) without re-registering hooks:
2328
+ // this filter keeps matching and `require()` rejects async onLoad
2329
+ // results, so serve the pre-rewritten synchronous source inline.
2330
+ // `ensureExtensionGraphHook` refreshes it on every (re)load.
2331
+ const synchronousSource = synchronousModuleSources.get(sourcePath);
2332
+ if (synchronousSource !== undefined) {
2333
+ return { contents: synchronousSource, loader: getLoader(sourcePath) };
2289
2334
  }
2335
+ const cached = asyncModules.get(sourcePath);
2290
2336
  const resolvedImportMtimeTag = cacheBustResolvedImportModules.has(sourcePath) ? mtimeTag : null;
2291
- return {
2292
- contents: await rewriteLegacyExtensionSource(raw, sourcePath, mtimeTag, resolvedImportMtimeTag),
2293
- loader: getLoader(sourcePath),
2294
- };
2337
+ return (async () => {
2338
+ let raw: string;
2339
+ if (cached !== undefined) {
2340
+ // consume-once: preserves ?mtime edit-pickup for re-imports
2341
+ asyncModules.delete(sourcePath);
2342
+ raw = cached;
2343
+ } else {
2344
+ raw = await Bun.file(sourcePath).text();
2345
+ }
2346
+ return {
2347
+ contents: await rewriteLegacyExtensionSource(raw, sourcePath, mtimeTag, resolvedImportMtimeTag),
2348
+ loader: getLoader(sourcePath),
2349
+ };
2350
+ })();
2295
2351
  });
2296
2352
  },
2297
2353
  });
@@ -2319,28 +2375,26 @@ async function installExtensionGraphHook(
2319
2375
  });
2320
2376
  }
2321
2377
 
2322
- if (syncSourceModules.size > 0) {
2323
- const alternation = [...syncSourceModules.keys()].map(escapeRegExp).join("|");
2378
+ if (synchronousSourcePaths.size > 0) {
2379
+ const alternation = [...synchronousSourcePaths].map(escapeRegExp).join("|");
2324
2380
  const filter = new RegExp(`^(?:${alternation})(?:\\?mtime=\\d+)?$`);
2325
- const hookId = Bun.hash(`${entryRealPath}\0sync-source\0${[...syncSourceModules.keys()].join("\0")}`).toString(
2326
- 36,
2327
- );
2381
+ const hookId = Bun.hash(`${entryRealPath}\0sync-source\0${[...synchronousSourcePaths].join("\0")}`).toString(36);
2328
2382
  Bun.plugin({
2329
2383
  name: `omp:legacy-pi-ext:${hookId}`,
2330
2384
  setup(build) {
2331
2385
  build.onLoad({ filter, namespace: "file" }, args => {
2332
2386
  const queryIndex = args.path.indexOf("?mtime=");
2333
2387
  const sourcePath = queryIndex >= 0 ? args.path.slice(0, queryIndex) : args.path;
2334
- const source = syncSourceModules.get(sourcePath);
2388
+ const source = synchronousModuleSources.get(sourcePath);
2335
2389
  if (source === undefined) {
2336
- throw new Error(`Missing pre-rewritten CommonJS extension source: ${sourcePath}`);
2390
+ throw new Error(`Missing pre-rewritten synchronous extension source: ${sourcePath}`);
2337
2391
  }
2338
2392
  return { contents: source, loader: getLoader(sourcePath) };
2339
2393
  });
2340
2394
  },
2341
2395
  });
2342
2396
  }
2343
- return { asyncModules, syncSourceModules };
2397
+ return { asyncModules };
2344
2398
  }
2345
2399
 
2346
2400
  /**
@@ -2356,6 +2410,7 @@ async function ensureExtensionGraphHook(entryRealPath: string): Promise<{ clear(
2356
2410
  modules: currentModules,
2357
2411
  commonJsPaths,
2358
2412
  cacheBustResolvedImportModules: discoveredCacheBustModules,
2413
+ synchronousSourcePaths,
2359
2414
  } = await collectExtensionModules(entryRealPath);
2360
2415
  let cacheBustResolvedImportModules = extensionGraphCacheBustResolvedImportModules.get(entryRealPath);
2361
2416
  if (!cacheBustResolvedImportModules) {
@@ -2370,6 +2425,15 @@ async function ensureExtensionGraphHook(entryRealPath: string): Promise<{ clear(
2370
2425
  commonJsModuleSources.set(modulePath, await prepareCommonJsDefaultModule(modulePath, source));
2371
2426
  commonJsGraphModulePaths.add(modulePath);
2372
2427
  }
2428
+ if (synchronousSourcePaths.has(modulePath)) {
2429
+ synchronousModuleSources.set(modulePath, source);
2430
+ } else if (synchronousModuleSources.has(modulePath)) {
2431
+ // The path lost its require() edges on this walk, but the permanent
2432
+ // hooks installed while it was synchronous still serve it from this
2433
+ // map — keep the pre-rewritten bytes fresh instead of serving the
2434
+ // stale snapshot from the walk that flagged it.
2435
+ synchronousModuleSources.set(modulePath, await rewriteLegacyExtensionSource(source, modulePath));
2436
+ }
2373
2437
  }
2374
2438
  let hookedModules = extensionGraphHookModules.get(entryRealPath);
2375
2439
  if (!hookedModules) {
@@ -2379,12 +2443,16 @@ async function ensureExtensionGraphHook(entryRealPath: string): Promise<{ clear(
2379
2443
 
2380
2444
  const pendingModules = new Map<string, string>();
2381
2445
  const pendingCommonJsPaths = new Set<string>();
2446
+ const pendingSynchronousSourcePaths = new Set<string>();
2382
2447
  for (const [modulePath, source] of currentModules) {
2383
2448
  if (!hookedModules.has(modulePath)) {
2384
2449
  pendingModules.set(modulePath, source);
2385
2450
  if (commonJsPaths.has(modulePath)) {
2386
2451
  pendingCommonJsPaths.add(modulePath);
2387
2452
  }
2453
+ if (synchronousSourcePaths.has(modulePath)) {
2454
+ pendingSynchronousSourcePaths.add(modulePath);
2455
+ }
2388
2456
  }
2389
2457
  }
2390
2458
  if (pendingModules.size === 0 && commonJsPaths.size === 0) {
@@ -2392,12 +2460,12 @@ async function ensureExtensionGraphHook(entryRealPath: string): Promise<{ clear(
2392
2460
  }
2393
2461
 
2394
2462
  let asyncModules = new Map<string, string>();
2395
- let syncSourceModules = new Map<string, string>();
2396
2463
  if (pendingModules.size > 0) {
2397
- ({ asyncModules, syncSourceModules } = await installExtensionGraphHook(
2464
+ ({ asyncModules } = await installExtensionGraphHook(
2398
2465
  entryRealPath,
2399
2466
  pendingModules,
2400
2467
  pendingCommonJsPaths,
2468
+ pendingSynchronousSourcePaths,
2401
2469
  cacheBustResolvedImportModules,
2402
2470
  ));
2403
2471
  for (const modulePath of pendingModules.keys()) {
@@ -2407,7 +2475,6 @@ async function ensureExtensionGraphHook(entryRealPath: string): Promise<{ clear(
2407
2475
  return {
2408
2476
  clear() {
2409
2477
  asyncModules.clear();
2410
- syncSourceModules.clear();
2411
2478
  for (const modulePath of commonJsPaths) {
2412
2479
  commonJsModuleSources.delete(modulePath);
2413
2480
  commonJsModuleDefinitions.delete(modulePath);
@@ -2,7 +2,7 @@
2
2
  * Registry read/write operations for the marketplace plugin system.
3
3
  *
4
4
  * Two registries:
5
- * - marketplaces.json under getConfigRootDir() — which catalogs the user has added
5
+ * - marketplaces.json at getMarketplacesRegistryPath() — which catalogs the user has added
6
6
  * - installed_plugins.json under getPluginsDir() — which plugins are installed
7
7
  *
8
8
  * Read/write functions accept explicit file paths so callers control the
@@ -15,7 +15,9 @@
15
15
  import * as fs from "node:fs/promises";
16
16
  import * as path from "node:path";
17
17
 
18
- import { getConfigRootDir, getPluginsDir, isEnoent, logger, tryParseJson } from "@oh-my-pi/pi-utils";
18
+ import { getPluginsDir, isEnoent, logger, tryParseJson } from "@oh-my-pi/pi-utils";
19
+
20
+ export { getMarketplacesRegistryPath } from "@oh-my-pi/pi-utils";
19
21
 
20
22
  import type {
21
23
  InstalledPluginEntry,
@@ -24,12 +26,6 @@ import type {
24
26
  MarketplacesRegistry,
25
27
  } from "./types";
26
28
 
27
- // ── Path helpers ─────────────────────────────────────────────────────
28
-
29
- export function getMarketplacesRegistryPath(): string {
30
- return path.join(getConfigRootDir(), "marketplaces.json");
31
- }
32
-
33
29
  export function getInstalledPluginsRegistryPath(): string {
34
30
  return path.join(getPluginsDir(), "installed_plugins.json");
35
31
  }
@@ -302,6 +302,17 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
302
302
 
303
303
  const existing = skillMap.get(skill.name);
304
304
  if (existing) {
305
+ // A skill name claimed by a DEFAULT-path provider (e.g.
306
+ // ~/.claude/skills/<name>) yields to the explicitly configured
307
+ // skills.customDirectories entry — the user's custom dir is the
308
+ // higher-priority source (issue #7190). Only same-source custom
309
+ // duplicates keep first-wins.
310
+ const isCustomExisting = existing.source.startsWith("custom:");
311
+ if (!isCustomExisting) {
312
+ skillMap.set(skill.name, skill);
313
+ realPathSet.add(resolvedPath);
314
+ continue;
315
+ }
305
316
  collisionWarnings.push({
306
317
  skillPath: skill.filePath,
307
318
  message: `name collision: "${skill.name}" already loaded from ${existing.filePath}, skipping this one`,
@@ -1,4 +1,5 @@
1
1
  import * as path from "node:path";
2
+ import { postmortem } from "@oh-my-pi/pi-utils";
2
3
  import { theme } from "../modes/theme/theme";
3
4
  import { expandPath, normalizeLocalScheme } from "../tools/path-utils";
4
5
  import type { HookUIContext } from "./hooks/types";
@@ -115,12 +116,19 @@ function guardedExit(alias: ExitAliasName): (code?: number | string) => never {
115
116
 
116
117
  export async function withHostGuard<T>(fn: () => Promise<T>): Promise<T> {
117
118
  if (hostGuardDepth === 0) {
119
+ // Stamp each throwing replacement with the native primitive it shadows so
120
+ // host-owned shutdown (postmortem's signal/fatal handlers) can still exit
121
+ // through the real exit even while this guard window is open (#6488).
118
122
  hostGuardOriginalProcessExit = process.exit;
119
- process.exit = guardedExit("process.exit") as typeof process.exit;
123
+ const processExitGuard = guardedExit("process.exit") as typeof process.exit;
124
+ Reflect.set(processExitGuard, postmortem.NATIVE_PROCESS_EXIT, hostGuardOriginalProcessExit);
125
+ process.exit = processExitGuard;
120
126
 
121
127
  if (typeof process.reallyExit === "function") {
122
128
  hostGuardOriginalReallyExit = process.reallyExit;
123
- process.reallyExit = guardedExit("process.reallyExit") as typeof process.reallyExit;
129
+ const reallyExitGuard = guardedExit("process.reallyExit") as typeof process.reallyExit;
130
+ Reflect.set(reallyExitGuard, postmortem.NATIVE_PROCESS_EXIT, hostGuardOriginalReallyExit);
131
+ process.reallyExit = reallyExitGuard;
124
132
  }
125
133
 
126
134
  const stdin = process.stdin;
@@ -1124,7 +1124,7 @@ class DaemonBroker {
1124
1124
  }
1125
1125
  }
1126
1126
 
1127
- /** Start the detached per-project daemon broker selected by the CLI worker host. */
1127
+ /** Start the detached project or global daemon broker selected by the CLI worker host. */
1128
1128
  export async function startDaemonBrokerFromEnvironment(): Promise<void> {
1129
1129
  const projectDir = process.env[DAEMON_PROJECT_DIR_ENV];
1130
1130
  const runtimeDir = process.env[DAEMON_RUNTIME_DIR_ENV];
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises";
2
2
  import * as net from "node:net";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
- import { isEexist, isEisdir, isEnoent, postmortem } from "@oh-my-pi/pi-utils";
5
+ import { getGlobalDaemonRuntimeDir, isEexist, isEisdir, isEnoent, postmortem } from "@oh-my-pi/pi-utils";
6
6
  import { hostHasInheritableConsole } from "../eval/py/spawn-options";
7
7
  import { resolveWorkerSpawnCmd, workerEnvFromParent } from "../subprocess/worker-client";
8
8
  import { daemonBrokerEndpoint, daemonRuntimeDir } from "./paths";
@@ -43,8 +43,9 @@ export interface DaemonBrokerClientOptions {
43
43
  idleGraceMs?: number;
44
44
  }
45
45
 
46
- /** Persistent per-process connection to one project's daemon broker. */
46
+ /** Persistent per-process connection to one project or global daemon broker. */
47
47
  export interface DaemonBrokerClient {
48
+ /** Canonical project directory or synthetic directory identifying a global scope. */
48
49
  readonly projectDir: string;
49
50
  request(operation: DaemonOperation, signal?: AbortSignal): Promise<DaemonRpcResult>;
50
51
  close(): void;
@@ -299,7 +300,19 @@ class SocketDaemonClient implements DaemonBrokerClient {
299
300
  const sharedClients = new Map<string, Promise<DaemonBrokerClient>>();
300
301
  let cancelExitCleanup: (() => void) | undefined;
301
302
 
302
- /** Create an independent socket connection to one project's shared daemon broker. */
303
+ function sharedDaemonClient(key: string, create: () => Promise<DaemonBrokerClient>): Promise<DaemonBrokerClient> {
304
+ let pending = sharedClients.get(key);
305
+ if (!pending) {
306
+ pending = create();
307
+ sharedClients.set(key, pending);
308
+ if (!cancelExitCleanup) {
309
+ cancelExitCleanup = postmortem.register("daemon-broker-clients", () => closeDaemonClients());
310
+ }
311
+ }
312
+ return pending;
313
+ }
314
+
315
+ /** Create an independent socket connection to one daemon broker scope. */
303
316
  export async function createDaemonBrokerClient(
304
317
  projectDir: string,
305
318
  options: DaemonBrokerClientOptions = {},
@@ -313,18 +326,24 @@ export async function createDaemonBrokerClient(
313
326
  /** Get the process-shared daemon broker client for one canonical project directory. */
314
327
  export async function daemonClientForProject(projectDir: string): Promise<DaemonBrokerClient> {
315
328
  const canonical = await canonicalProjectDir(projectDir);
316
- let pending = sharedClients.get(canonical);
317
- if (!pending) {
318
- pending = createDaemonBrokerClient(canonical);
319
- sharedClients.set(canonical, pending);
320
- if (!cancelExitCleanup) {
321
- cancelExitCleanup = postmortem.register("daemon-broker-clients", () => closeDaemonClients());
322
- }
323
- }
324
- return pending;
329
+ return sharedDaemonClient(`project:${canonical}`, () => createDaemonBrokerClient(canonical));
330
+ }
331
+
332
+ /** Get the process-shared client that leases one profile-independent, machine-global daemon broker. */
333
+ export async function daemonClientForGlobal(service: string): Promise<DaemonBrokerClient> {
334
+ const runtimeDir = getGlobalDaemonRuntimeDir(service);
335
+ // Canonicalize only after creation so the first caller and later callers
336
+ // derive the same Windows pipe key even when an ancestor is a symlink.
337
+ await fs.mkdir(runtimeDir, { recursive: true, mode: 0o700 });
338
+ const canonical = await fs.realpath(runtimeDir);
339
+ return sharedDaemonClient(`global:${canonical}`, () =>
340
+ createDaemonBrokerClient(canonical, {
341
+ runtimeDir: canonical,
342
+ }),
343
+ );
325
344
  }
326
345
 
327
- /** Close every project broker connection held by this omp process. */
346
+ /** Close every project and machine-global broker connection held by this omp process. */
328
347
  export async function closeDaemonClients(): Promise<void> {
329
348
  const pending = [...sharedClients.values()];
330
349
  sharedClients.clear();
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Quiet daemon-broker ensure helpers shared by broker-owned singleton daemons
3
+ * (shared Chromium, browser relay, LSP mux). Each treats broker/daemon errors
4
+ * as "absent" so ensure loops can retry or adopt a cross-process race winner
5
+ * instead of failing the caller.
6
+ */
7
+ import { logger } from "@oh-my-pi/pi-utils";
8
+ import { throwIfAborted } from "../tools/tool-errors";
9
+ import type { DaemonBrokerClient } from "./client";
10
+ import type { DaemonSnapshot } from "./protocol";
11
+
12
+ const DEFAULT_READY_TIMEOUT_MS = 30_000;
13
+ const STOP_TIMEOUT_MS = 5_000;
14
+
15
+ /** Snapshot a broker daemon, treating "unknown daemon" and broker errors as absent. */
16
+ export async function describeQuietly(
17
+ client: DaemonBrokerClient,
18
+ name: string,
19
+ label: string,
20
+ signal?: AbortSignal,
21
+ ): Promise<DaemonSnapshot | undefined> {
22
+ try {
23
+ const result = await client.request({ op: "describe", name }, signal);
24
+ return result.op === "describe" ? result.daemon : undefined;
25
+ } catch (error) {
26
+ throwIfAborted(signal);
27
+ logger.debug(`${label} describe failed`, {
28
+ name,
29
+ error: error instanceof Error ? error.message : String(error),
30
+ });
31
+ return undefined;
32
+ }
33
+ }
34
+
35
+ /** Block until the daemon reports ready; undefined on timeout or pre-ready exit. */
36
+ export async function waitReady(
37
+ client: DaemonBrokerClient,
38
+ name: string,
39
+ label: string,
40
+ signal?: AbortSignal,
41
+ timeoutMs: number = DEFAULT_READY_TIMEOUT_MS,
42
+ ): Promise<DaemonSnapshot | undefined> {
43
+ try {
44
+ const result = await client.request({ op: "wait", name, for: "ready", timeoutMs }, signal);
45
+ if (result.op !== "wait" || result.timedOut) return undefined;
46
+ return result.daemon;
47
+ } catch (error) {
48
+ throwIfAborted(signal);
49
+ logger.debug(`${label} ready wait failed`, {
50
+ name,
51
+ error: error instanceof Error ? error.message : String(error),
52
+ });
53
+ return undefined;
54
+ }
55
+ }
56
+
57
+ /** Best-effort stop before replacing a wedged or endpoint-less daemon. */
58
+ export async function stopQuietly(
59
+ client: DaemonBrokerClient,
60
+ name: string,
61
+ label: string,
62
+ signal?: AbortSignal,
63
+ ): Promise<void> {
64
+ try {
65
+ await client.request({ op: "stop", name, timeoutMs: STOP_TIMEOUT_MS }, signal);
66
+ } catch (error) {
67
+ throwIfAborted(signal);
68
+ logger.debug(`${label} stop failed`, {
69
+ name,
70
+ error: error instanceof Error ? error.message : String(error),
71
+ });
72
+ }
73
+ }