@oh-my-pi/pi-coding-agent 17.2.4 → 17.2.6

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 (348) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/{CHANGELOG-bpmhv26t.md → CHANGELOG-gs76k6wc.md} +69 -0
  3. package/dist/cli.js +4438 -3962
  4. package/dist/{template-8vdv6xb4.js → template-c2hyaytt.js} +8 -26
  5. package/dist/types/cli/args.d.ts +4 -1
  6. package/dist/types/cli/browser-relay-cli.d.ts +15 -0
  7. package/dist/types/cli/command-help.d.ts +99 -0
  8. package/dist/types/cli/gc-cli.d.ts +1 -0
  9. package/dist/types/cli/help-extra.d.ts +2 -0
  10. package/dist/types/cli/thinking-levels.d.ts +5 -0
  11. package/dist/types/cli/update-cli.d.ts +13 -0
  12. package/dist/types/commands/browser-relay.d.ts +38 -0
  13. package/dist/types/commands/commit.d.ts +3 -0
  14. package/dist/types/commands/complete.d.ts +1 -1
  15. package/dist/types/commands/completions.d.ts +6 -0
  16. package/dist/types/commands/grep.d.ts +3 -0
  17. package/dist/types/commands/join.d.ts +4 -0
  18. package/dist/types/commands/launch-help.d.ts +171 -0
  19. package/dist/types/commands/launch.d.ts +5 -1
  20. package/dist/types/commands/models.d.ts +3 -0
  21. package/dist/types/commands/say.d.ts +10 -0
  22. package/dist/types/commands/worktree.d.ts +3 -0
  23. package/dist/types/config/keybindings.d.ts +5 -0
  24. package/dist/types/config/model-discovery.d.ts +2 -2
  25. package/dist/types/config/models-config-schema-bundle.d.ts +3 -0
  26. package/dist/types/config/models-config-schema.d.ts +2 -0
  27. package/dist/types/config/models-config.d.ts +1 -0
  28. package/dist/types/config/service-tier.d.ts +2 -0
  29. package/dist/types/config/settings-schema.d.ts +69 -21
  30. package/dist/types/cursor-bridge-tools.d.ts +1 -1
  31. package/dist/types/cursor.d.ts +7 -4
  32. package/dist/types/edit/index.d.ts +3 -3
  33. package/dist/types/edit/modes/replace.d.ts +22 -15
  34. package/dist/types/edit/renderer.d.ts +1 -1
  35. package/dist/types/eval/py/kernel.d.ts +3 -1
  36. package/dist/types/eval/py/spawn-options.d.ts +12 -16
  37. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +8 -1
  38. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
  39. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
  40. package/dist/types/launch/broker.d.ts +1 -1
  41. package/dist/types/launch/client.d.ts +15 -4
  42. package/dist/types/launch/ensure.d.ts +8 -0
  43. package/dist/types/launch/paths.d.ts +1 -1
  44. package/dist/types/launch/protocol.d.ts +19 -2
  45. package/dist/types/lsp/client.d.ts +2 -0
  46. package/dist/types/lsp/mux/daemon.d.ts +15 -0
  47. package/dist/types/lsp/mux/protocol.d.ts +57 -0
  48. package/dist/types/lsp/mux/server.d.ts +18 -0
  49. package/dist/types/lsp/types.d.ts +22 -2
  50. package/dist/types/main.d.ts +2 -0
  51. package/dist/types/mnemopi/embed-client.d.ts +1 -1
  52. package/dist/types/mnemopi/state.d.ts +0 -23
  53. package/dist/types/modes/components/assistant-message.d.ts +2 -0
  54. package/dist/types/modes/components/btw-panel.d.ts +3 -0
  55. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  56. package/dist/types/modes/components/read-tool-group.d.ts +2 -0
  57. package/dist/types/modes/components/stripped-tool-calls-placeholder.d.ts +12 -0
  58. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  59. package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
  60. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  61. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  62. package/dist/types/modes/interactive-mode.d.ts +5 -1
  63. package/dist/types/modes/types.d.ts +4 -1
  64. package/dist/types/modes/workflow.d.ts +2 -1
  65. package/dist/types/sdk.d.ts +3 -1
  66. package/dist/types/secrets/index.d.ts +1 -1
  67. package/dist/types/security/contracts/schemas.d.ts +405 -403
  68. package/dist/types/session/agent-session-types.d.ts +8 -1
  69. package/dist/types/session/agent-session.d.ts +26 -2
  70. package/dist/types/session/indexed-session-storage.d.ts +1 -0
  71. package/dist/types/session/launch-completion.d.ts +10 -0
  72. package/dist/types/session/session-advisors.d.ts +1 -1
  73. package/dist/types/session/session-context.d.ts +2 -1
  74. package/dist/types/session/session-entries.d.ts +15 -1
  75. package/dist/types/session/session-maintenance.d.ts +1 -0
  76. package/dist/types/session/session-manager.d.ts +23 -11
  77. package/dist/types/session/session-stats.d.ts +10 -0
  78. package/dist/types/session/session-storage.d.ts +15 -3
  79. package/dist/types/session/yield-queue.d.ts +6 -1
  80. package/dist/types/system-prompt.d.ts +4 -1
  81. package/dist/types/task/index.d.ts +2 -1
  82. package/dist/types/task/spawn-policy.d.ts +5 -0
  83. package/dist/types/thinking.d.ts +1 -7
  84. package/dist/types/tools/ast-grep.d.ts +1 -1
  85. package/dist/types/tools/browser/registry.d.ts +2 -1
  86. package/dist/types/tools/browser/relay/bridge.d.ts +35 -0
  87. package/dist/types/tools/browser/relay/daemon.d.ts +16 -0
  88. package/dist/types/tools/browser/relay/kind.d.ts +19 -0
  89. package/dist/types/tools/browser/relay/protocol.d.ts +97 -0
  90. package/dist/types/tools/browser/relay/server.d.ts +35 -0
  91. package/dist/types/tools/browser/render.d.ts +1 -0
  92. package/dist/types/tools/browser.d.ts +3 -0
  93. package/dist/types/tools/computer/exposure.d.ts +9 -7
  94. package/dist/types/tools/computer/protocol.d.ts +70 -17
  95. package/dist/types/tools/computer/supervisor.d.ts +23 -10
  96. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  97. package/dist/types/tools/computer/worker.d.ts +42 -7
  98. package/dist/types/tools/computer-renderer.d.ts +7 -5
  99. package/dist/types/tools/computer.d.ts +25 -44
  100. package/dist/types/tools/context.d.ts +3 -0
  101. package/dist/types/tools/glob.d.ts +1 -1
  102. package/dist/types/tools/grep.d.ts +1 -1
  103. package/dist/types/tools/index.d.ts +9 -0
  104. package/dist/types/tools/render-utils.d.ts +2 -0
  105. package/dist/types/tools/{browser/run-cancellation.d.ts → run-scope.d.ts} +8 -8
  106. package/dist/types/tools/tool-timeouts.d.ts +5 -0
  107. package/dist/types/tools/xdev.d.ts +8 -0
  108. package/dist/types/utils/clipboard.d.ts +2 -2
  109. package/dist/types/web/kagi.d.ts +1 -0
  110. package/dist/types/web/search/index.d.ts +1 -1
  111. package/dist/types/web/search/providers/anthropic.d.ts +1 -0
  112. package/dist/types/web/search/providers/base.d.ts +2 -0
  113. package/dist/types/web/search/providers/brave.d.ts +1 -0
  114. package/dist/types/web/search/providers/browser-page.d.ts +1 -0
  115. package/dist/types/web/search/providers/codex.d.ts +1 -0
  116. package/dist/types/web/search/providers/exa.d.ts +1 -0
  117. package/dist/types/web/search/providers/firecrawl.d.ts +1 -0
  118. package/dist/types/web/search/providers/gemini.d.ts +1 -0
  119. package/dist/types/web/search/providers/jina.d.ts +1 -0
  120. package/dist/types/web/search/providers/kagi.d.ts +1 -0
  121. package/dist/types/web/search/providers/kimi.d.ts +1 -0
  122. package/dist/types/web/search/providers/parallel.d.ts +1 -0
  123. package/dist/types/web/search/providers/perplexity.d.ts +1 -0
  124. package/dist/types/web/search/providers/searxng.d.ts +1 -0
  125. package/dist/types/web/search/providers/tavily.d.ts +1 -0
  126. package/dist/types/web/search/providers/tinyfish.d.ts +1 -0
  127. package/dist/types/web/search/providers/utils.d.ts +6 -9
  128. package/dist/types/web/search/providers/zai.d.ts +1 -0
  129. package/dist/types/web/search/types.d.ts +4 -0
  130. package/package.json +12 -12
  131. package/src/cli/args.ts +7 -87
  132. package/src/cli/browser-relay-cli.ts +119 -0
  133. package/src/cli/command-help.ts +103 -0
  134. package/src/cli/completion-gen.ts +5 -5
  135. package/src/cli/flag-tables.ts +9 -0
  136. package/src/cli/gc-cli.ts +641 -21
  137. package/src/cli/help-extra.ts +89 -0
  138. package/src/cli/setup-cli.ts +13 -21
  139. package/src/cli/thinking-levels.ts +7 -0
  140. package/src/cli/update-cli.ts +26 -7
  141. package/src/cli-commands.ts +170 -33
  142. package/src/cli.ts +17 -5
  143. package/src/commands/acp.ts +3 -1
  144. package/src/commands/agents.ts +3 -2
  145. package/src/commands/auth-broker.ts +3 -2
  146. package/src/commands/auth-gateway.ts +3 -2
  147. package/src/commands/bench.ts +2 -3
  148. package/src/commands/browser-relay.ts +53 -0
  149. package/src/commands/cleanse.ts +2 -2
  150. package/src/commands/commit.ts +3 -2
  151. package/src/commands/complete.ts +2 -1
  152. package/src/commands/completions.ts +3 -2
  153. package/src/commands/config.ts +3 -2
  154. package/src/commands/dry-balance.ts +2 -2
  155. package/src/commands/gallery.ts +3 -2
  156. package/src/commands/gc.ts +3 -2
  157. package/src/commands/grep.ts +3 -2
  158. package/src/commands/grievances.ts +3 -2
  159. package/src/commands/install.ts +2 -2
  160. package/src/commands/join.ts +3 -2
  161. package/src/commands/launch-help.ts +116 -0
  162. package/src/commands/launch.ts +7 -191
  163. package/src/commands/models.ts +3 -2
  164. package/src/commands/plugin.ts +3 -2
  165. package/src/commands/read.ts +3 -2
  166. package/src/commands/say.ts +3 -2
  167. package/src/commands/setup.ts +3 -2
  168. package/src/commands/shell.ts +3 -2
  169. package/src/commands/ssh.ts +3 -2
  170. package/src/commands/stats.ts +3 -2
  171. package/src/commands/tiny-models.ts +2 -2
  172. package/src/commands/token.ts +2 -2
  173. package/src/commands/ttsr.ts +2 -2
  174. package/src/commands/update.ts +3 -2
  175. package/src/commands/usage.ts +3 -2
  176. package/src/commands/web-search.ts +3 -2
  177. package/src/commands/worktree.ts +3 -2
  178. package/src/config/keybindings.ts +5 -0
  179. package/src/config/model-discovery.ts +44 -27
  180. package/src/config/model-registry.ts +19 -13
  181. package/src/config/models-config-schema-bundle.ts +9 -0
  182. package/src/config/resolve-config-value.ts +2 -1
  183. package/src/config/service-tier.ts +5 -0
  184. package/src/config/settings-schema.ts +72 -20
  185. package/src/config/settings.ts +1 -1
  186. package/src/cursor-bridge-tools.ts +1 -1
  187. package/src/cursor.ts +10 -14
  188. package/src/edit/diff.ts +17 -8
  189. package/src/edit/index.ts +20 -7
  190. package/src/edit/modes/replace.ts +93 -40
  191. package/src/edit/renderer.ts +1 -1
  192. package/src/edit/streaming.ts +14 -17
  193. package/src/eval/py/kernel.ts +2 -1
  194. package/src/eval/py/spawn-options.ts +24 -29
  195. package/src/export/html/index.ts +10 -3
  196. package/src/export/html/template.js +8 -26
  197. package/src/export/share.ts +4 -0
  198. package/src/extensibility/extensions/wrapper.ts +1 -0
  199. package/src/extensibility/legacy-pi-ai-shim.ts +22 -1
  200. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -6
  201. package/src/extensibility/plugins/legacy-pi-compat.ts +124 -57
  202. package/src/extensibility/skills.ts +11 -0
  203. package/src/extensibility/utils.ts +10 -2
  204. package/src/launch/broker.ts +223 -7
  205. package/src/launch/client.ts +192 -21
  206. package/src/launch/ensure.ts +73 -0
  207. package/src/launch/paths.ts +1 -1
  208. package/src/launch/protocol.ts +54 -2
  209. package/src/lsp/client.ts +23 -2
  210. package/src/lsp/index.ts +8 -0
  211. package/src/lsp/mux/daemon.ts +348 -0
  212. package/src/lsp/mux/protocol.ts +96 -0
  213. package/src/lsp/mux/server.ts +797 -0
  214. package/src/lsp/types.ts +28 -2
  215. package/src/main.ts +20 -8
  216. package/src/mcp/config-writer.ts +1 -1
  217. package/src/mcp/oauth-flow.ts +11 -1
  218. package/src/mnemopi/embed-client.ts +49 -2
  219. package/src/mnemopi/state.ts +57 -23
  220. package/src/modes/components/assistant-message.ts +11 -0
  221. package/src/modes/components/btw-panel.ts +41 -4
  222. package/src/modes/components/chat-transcript-builder.ts +4 -0
  223. package/src/modes/components/custom-editor.ts +9 -0
  224. package/src/modes/components/read-tool-group.ts +10 -0
  225. package/src/modes/components/status-line/component.ts +1 -1
  226. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  227. package/src/modes/components/tool-execution.ts +8 -0
  228. package/src/modes/components/tree-selector.ts +46 -48
  229. package/src/modes/controllers/btw-controller.ts +55 -7
  230. package/src/modes/controllers/command-controller.ts +31 -0
  231. package/src/modes/controllers/event-controller.ts +17 -0
  232. package/src/modes/controllers/input-controller.ts +63 -8
  233. package/src/modes/controllers/mcp-command-controller.ts +11 -5
  234. package/src/modes/controllers/selector-controller.ts +20 -0
  235. package/src/modes/interactive-mode.ts +18 -2
  236. package/src/modes/types.ts +9 -1
  237. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  238. package/src/modes/utils/interactive-context-helpers.ts +1 -0
  239. package/src/modes/utils/ui-helpers.ts +6 -10
  240. package/src/modes/workflow.ts +8 -2
  241. package/src/prompts/agents/init.md +1 -1
  242. package/src/prompts/session/launch-completion.md +1 -0
  243. package/src/prompts/system/plan-mode-active.md +2 -2
  244. package/src/prompts/system/system-prompt.md +26 -52
  245. package/src/prompts/system/workflow-notice.md +3 -3
  246. package/src/prompts/tools/ast-grep.md +1 -1
  247. package/src/prompts/tools/bash.md +1 -1
  248. package/src/prompts/tools/browser.md +1 -0
  249. package/src/prompts/tools/computer.md +21 -21
  250. package/src/prompts/tools/glob.md +1 -1
  251. package/src/prompts/tools/grep.md +1 -1
  252. package/src/prompts/tools/replace.md +5 -4
  253. package/src/prompts/tools/task.md +4 -4
  254. package/src/sdk.ts +49 -6
  255. package/src/secrets/index.ts +1 -1
  256. package/src/security/contracts/schemas.ts +205 -183
  257. package/src/security/contracts/validation.ts +5 -1
  258. package/src/security/store.ts +2 -2
  259. package/src/session/agent-session-types.ts +9 -1
  260. package/src/session/agent-session.ts +204 -15
  261. package/src/session/indexed-session-storage.ts +9 -0
  262. package/src/session/launch-completion.ts +37 -0
  263. package/src/session/prewalk.ts +24 -2
  264. package/src/session/session-advisors.ts +2 -2
  265. package/src/session/session-context.ts +42 -12
  266. package/src/session/session-dump-format.ts +1 -1
  267. package/src/session/session-entries.ts +17 -1
  268. package/src/session/session-maintenance.ts +38 -4
  269. package/src/session/session-manager.ts +123 -67
  270. package/src/session/session-paths.ts +118 -55
  271. package/src/session/session-stats.ts +48 -3
  272. package/src/session/session-storage.ts +30 -22
  273. package/src/session/stream-guards.ts +1 -1
  274. package/src/session/yield-queue.ts +121 -16
  275. package/src/slash-commands/builtin-registry.ts +37 -24
  276. package/src/system-prompt.ts +8 -3
  277. package/src/task/index.ts +23 -8
  278. package/src/task/spawn-policy.ts +14 -0
  279. package/src/thinking.ts +2 -8
  280. package/src/tools/ast-grep.ts +10 -4
  281. package/src/tools/browser/cmux/cmux-tab.ts +97 -14
  282. package/src/tools/browser/cmux/rpc.ts +2 -8
  283. package/src/tools/browser/registry.ts +53 -2
  284. package/src/tools/browser/relay/bridge.ts +945 -0
  285. package/src/tools/browser/relay/daemon.ts +117 -0
  286. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  287. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  288. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  289. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  290. package/src/tools/browser/relay/kind.ts +41 -0
  291. package/src/tools/browser/relay/protocol.ts +54 -0
  292. package/src/tools/browser/relay/server.ts +141 -0
  293. package/src/tools/browser/render.ts +4 -1
  294. package/src/tools/browser/shared-daemon.ts +7 -58
  295. package/src/tools/browser/tab-supervisor.ts +5 -4
  296. package/src/tools/browser/tab-worker.ts +33 -12
  297. package/src/tools/browser.ts +26 -1
  298. package/src/tools/computer/exposure.ts +11 -35
  299. package/src/tools/computer/protocol.ts +51 -10
  300. package/src/tools/computer/supervisor.ts +227 -124
  301. package/src/tools/computer/worker-entry.ts +11 -6
  302. package/src/tools/computer/worker.ts +695 -81
  303. package/src/tools/computer-renderer.ts +104 -65
  304. package/src/tools/computer.ts +134 -428
  305. package/src/tools/context.ts +3 -0
  306. package/src/tools/glob.ts +9 -2
  307. package/src/tools/grep.ts +12 -6
  308. package/src/tools/hub/launch.ts +122 -6
  309. package/src/tools/index.ts +9 -0
  310. package/src/tools/inspect-image.ts +32 -6
  311. package/src/tools/render-utils.ts +2 -0
  312. package/src/tools/{browser/run-cancellation.ts → run-scope.ts} +11 -11
  313. package/src/tools/tool-timeouts.ts +1 -0
  314. package/src/tools/xdev.ts +29 -2
  315. package/src/utils/clipboard.ts +40 -14
  316. package/src/utils/command-args.ts +26 -28
  317. package/src/utils/tool-choice.ts +0 -13
  318. package/src/web/kagi.ts +2 -1
  319. package/src/web/search/index.ts +18 -2
  320. package/src/web/search/providers/anthropic.ts +4 -1
  321. package/src/web/search/providers/base.ts +2 -0
  322. package/src/web/search/providers/brave.ts +3 -1
  323. package/src/web/search/providers/browser-page.ts +6 -4
  324. package/src/web/search/providers/codex.ts +4 -1
  325. package/src/web/search/providers/duckduckgo.ts +2 -1
  326. package/src/web/search/providers/ecosia.ts +2 -1
  327. package/src/web/search/providers/exa.ts +4 -2
  328. package/src/web/search/providers/firecrawl.ts +3 -1
  329. package/src/web/search/providers/gemini.ts +8 -2
  330. package/src/web/search/providers/google.ts +2 -1
  331. package/src/web/search/providers/jina.ts +12 -2
  332. package/src/web/search/providers/kagi.ts +3 -0
  333. package/src/web/search/providers/kimi.ts +5 -1
  334. package/src/web/search/providers/mojeek.ts +2 -1
  335. package/src/web/search/providers/parallel.ts +5 -1
  336. package/src/web/search/providers/perplexity.ts +6 -3
  337. package/src/web/search/providers/public.ts +1 -1
  338. package/src/web/search/providers/searxng.ts +11 -5
  339. package/src/web/search/providers/startpage.ts +10 -4
  340. package/src/web/search/providers/synthetic.ts +11 -5
  341. package/src/web/search/providers/tavily.ts +3 -1
  342. package/src/web/search/providers/tinyfish.ts +3 -1
  343. package/src/web/search/providers/utils.ts +12 -10
  344. package/src/web/search/providers/xai.ts +2 -2
  345. package/src/web/search/providers/zai.ts +18 -3
  346. package/src/web/search/types.ts +6 -0
  347. package/dist/types/config/file-lock.d.ts +0 -29
  348. package/src/config/file-lock.ts +0 -164
@@ -204,7 +204,7 @@
204
204
  containsActive.set(node, has);
205
205
  }
206
206
 
207
- // Stack: [node, indent, justBranched, showConnector, isLast, gutters, isVirtualRootChild]
207
+ // Stack: [node, indent, showConnector, isLast, gutters, isVirtualRootChild]
208
208
  const stack = [];
209
209
 
210
210
  // Add roots (prioritize branch containing active leaf)
@@ -213,11 +213,11 @@
213
213
  );
214
214
  for (let i = orderedRoots.length - 1; i >= 0; i--) {
215
215
  const isLast = i === orderedRoots.length - 1;
216
- stack.push([orderedRoots[i], multipleRoots ? 1 : 0, multipleRoots, multipleRoots, isLast, [], multipleRoots]);
216
+ stack.push([orderedRoots[i], multipleRoots ? 1 : 0, multipleRoots, isLast, [], multipleRoots]);
217
217
  }
218
218
 
219
219
  while (stack.length > 0) {
220
- const [node, indent, justBranched, showConnector, isLast, gutters, isVirtualRootChild] = stack.pop();
220
+ const [node, indent, showConnector, isLast, gutters, isVirtualRootChild] = stack.pop();
221
221
 
222
222
  result.push({ node, indent, showConnector, isLast, gutters, isVirtualRootChild, multipleRoots });
223
223
 
@@ -229,18 +229,10 @@
229
229
  Number(containsActive.get(b)) - Number(containsActive.get(a))
230
230
  );
231
231
 
232
- // Calculate child indent (matches tree-selector.ts)
233
- let childIndent;
234
- if (multipleChildren) {
235
- // Parent branches: children get +1
236
- childIndent = indent + 1;
237
- } else if (justBranched && indent > 0) {
238
- // First generation after a branch: +1 for visual grouping
239
- childIndent = indent + 1;
240
- } else {
241
- // Single-child chain: stay flat
242
- childIndent = indent;
243
- }
232
+ // Real branch points add visual depth, and a virtual root's direct
233
+ // children (the session roots) nest one level under the shared
234
+ // column-0 root. Linear continuations otherwise stay aligned.
235
+ const childIndent = multipleChildren || isVirtualRootChild ? indent + 1 : indent;
244
236
 
245
237
  // Build gutters for children
246
238
  const connectorDisplayed = showConnector && !isVirtualRootChild;
@@ -253,7 +245,7 @@
253
245
  // Add children in reverse order for stack
254
246
  for (let i = orderedChildren.length - 1; i >= 0; i--) {
255
247
  const childIsLast = i === orderedChildren.length - 1;
256
- stack.push([orderedChildren[i], childIndent, multipleChildren, multipleChildren, childIsLast, childGutters, false]);
248
+ stack.push([orderedChildren[i], childIndent, multipleChildren, childIsLast, childGutters, false]);
257
249
  }
258
250
  }
259
251
 
@@ -268,13 +260,6 @@
268
260
  const displayIndent = multipleRoots ? Math.max(0, indent - 1) : indent;
269
261
  const connector = showConnector && !isVirtualRootChild ? (isLast ? '└─ ' : '├─ ') : '';
270
262
  const connectorPosition = connector ? displayIndent - 1 : -1;
271
- // Chain rows (no connector of their own) under a last-sibling (`└─`)
272
- // branch stay anchored by a vertical drawn one level right of the
273
- // suppressed gutter — below the branch head's content — never in the
274
- // `└─` corner column itself (#2298, #2325). Chains under `├─` heads
275
- // are already anchored by the sibling line (`show: true` gutter).
276
- const nearestGutter = !connector ? gutters[gutters.length - 1] : undefined;
277
- const chainAnchorLevel = nearestGutter && !nearestGutter.show ? nearestGutter.position + 1 : -1;
278
263
 
279
264
  const totalChars = displayIndent * 3;
280
265
  const prefixChars = [];
@@ -287,9 +272,6 @@
287
272
  // Standard tree semantics: `│` only while more siblings continue
288
273
  // below (`show`), space below a `└─`.
289
274
  prefixChars.push(posInLevel === 0 && gutter.show ? '│' : ' ');
290
- } else if (level === chainAnchorLevel) {
291
- // Chain anchor for rows under a `└─` branch head.
292
- prefixChars.push(posInLevel === 0 ? '│' : ' ');
293
275
  } else if (connector && level === connectorPosition) {
294
276
  if (posInLevel === 0) {
295
277
  prefixChars.push(isLast ? '└' : '├');
@@ -223,6 +223,7 @@ function collectShareRegexSecretValues(o: SecretObfuscator, data: SessionData):
223
223
  if (!header) return;
224
224
  add(header.title);
225
225
  add(header.cwd);
226
+ for (const previousSessionFile of header.previousSessionFiles ?? []) add(previousSessionFile);
226
227
  };
227
228
 
228
229
  addHeader(data.header);
@@ -246,6 +247,9 @@ function redactShareHeader(
246
247
  ...header,
247
248
  title: header.title === undefined ? undefined : o.obfuscate(header.title, sharedRegexSecretValues),
248
249
  cwd: o.obfuscate(header.cwd, sharedRegexSecretValues),
250
+ previousSessionFiles: header.previousSessionFiles?.map(previousSessionFile =>
251
+ o.obfuscate(previousSessionFile, sharedRegexSecretValues),
252
+ ),
249
253
  };
250
254
  }
251
255
 
@@ -239,6 +239,7 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
239
239
  // short-circuit above.
240
240
  const resolvedArgs = approvalArgs(effectiveParams, context);
241
241
  const resolved = resolveApproval(this.tool, resolvedArgs, approvalMode, userPolicies);
242
+ context?.xdevTierResolved?.(resolved.tier);
242
243
  if (resolved.policy === "deny") {
243
244
  throw new Error(
244
245
  `Tool "${this.tool.name}" is blocked by user policy.\n` +
@@ -19,7 +19,15 @@
19
19
  * `types.ts` via the `export *` below — pi-ai still exports both as types,
20
20
  * only the runtime `Type` builder and `StringEnum()` helper were removed.
21
21
  */
22
- import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
22
+ import {
23
+ type Api,
24
+ type AssistantMessage,
25
+ type AssistantMessageEventStream,
26
+ type Context,
27
+ type Model,
28
+ type SimpleStreamOptions,
29
+ streamSimple,
30
+ } from "@oh-my-pi/pi-ai";
23
31
  import type { Effort } from "@oh-my-pi/pi-catalog/effort";
24
32
  import { clampThinkingLevelForModel } from "@oh-my-pi/pi-catalog/model-thinking";
25
33
  import {
@@ -121,6 +129,19 @@ export { calculateCost, getBundledModel, getBundledModels, getBundledProviders,
121
129
  export const getModel = getBundledModel;
122
130
  export const getModels = getBundledModels;
123
131
 
132
+ /**
133
+ * Stream OpenAI Responses through the historical simple-options contract.
134
+ *
135
+ * Legacy `/compat` callers pass {@link SimpleStreamOptions}; routing through
136
+ * `streamSimple` preserves option normalization before provider dispatch.
137
+ */
138
+ export function streamSimpleOpenAIResponses(
139
+ model: Model<"openai-responses">,
140
+ context: Context,
141
+ options?: SimpleStreamOptions,
142
+ ): AssistantMessageEventStream {
143
+ return streamSimple(model, context, options);
144
+ }
124
145
  /**
125
146
  * Compatibility re-exports for runtime helpers that upstream
126
147
  * `@earendil-works/pi-ai` exposed from its package root but omp's
@@ -1415,12 +1415,13 @@ export function getPackageDir(): string {
1415
1415
  return getOmpPackageDir() ?? (isCompiledBinary() ? path.dirname(process.execPath) : process.cwd());
1416
1416
  }
1417
1417
 
1418
- // Legacy pi's `@earendil-works/pi-coding-agent` re-exported `estimateTokens`
1419
- // and `compact` from its package root (via `./core/compaction/index.ts`). In
1420
- // omp both live in `@oh-my-pi/pi-agent-core/compaction`, and the coding-agent
1421
- // barrel below does not forward them, so legacy extensions importing either
1422
- // fail Bun's static export check during validation (issues #6583, #7174).
1423
- export { compact, estimateTokens } from "@oh-my-pi/pi-agent-core/compaction";
1418
+ // Legacy pi's `@earendil-works/pi-coding-agent` re-exported `estimateTokens`,
1419
+ // `compact`, and `serializeConversation` from its package root (via
1420
+ // `./core/compaction/index.ts`). In omp they live in
1421
+ // `@oh-my-pi/pi-agent-core/compaction`, and the coding-agent barrel below does
1422
+ // not forward them, so legacy extensions importing them fail Bun's static
1423
+ // export check during validation (issues #6583, #7174, #7403).
1424
+ export { compact, estimateTokens, serializeConversation } from "@oh-my-pi/pi-agent-core/compaction";
1424
1425
 
1425
1426
  // Same barrel gap for two more legacy package-root exports: pi re-exported the
1426
1427
  // `CONFIG_DIR_NAME` constant and the CLI parser `parseArgs`. In omp
@@ -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);
@@ -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;