@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
package/src/sdk.ts CHANGED
@@ -17,6 +17,7 @@ import type {
17
17
  Model,
18
18
  ModelUsageHealth,
19
19
  ProviderSessionState,
20
+ ServiceTier,
20
21
  SimpleStreamOptions,
21
22
  } from "@oh-my-pi/pi-ai";
22
23
  import { resolveApiKeyOnce } from "@oh-my-pi/pi-ai/auth-retry";
@@ -66,6 +67,7 @@ import { CursorExecHandlers, type CursorMcpResourceAdapter } from "./cursor";
66
67
  import { createBridgeEditTool, createBridgeGrepFactory } from "./cursor-bridge-tools";
67
68
  import "./discovery";
68
69
  import { initializeWithSettings } from "./discovery";
70
+ import { withOmpExtensionRootScope } from "./discovery/omp-extension-roots";
69
71
  import { disposeAllJuliaKernelSessions, disposeJuliaKernelSessionsByOwner } from "./eval/jl/executor";
70
72
  import { disposeVmContextsByOwner } from "./eval/js/context-manager";
71
73
  import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./eval/py/executor";
@@ -101,6 +103,7 @@ import {
101
103
  import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./extensibility/slash-commands";
102
104
  import type { HindsightSessionState } from "./hindsight/state";
103
105
  import { LocalProtocolHandler, type LocalProtocolOptions } from "./internal-urls";
106
+ import { setSharedLspEnabled } from "./lsp/client";
104
107
  import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
105
108
  import {
106
109
  deduplicateMCPToolsByName,
@@ -168,6 +171,7 @@ import {
168
171
  } from "./system-prompt";
169
172
  import { AgentOutputManager } from "./task/output-manager";
170
173
  import { wrapStreamFnWithProviderConcurrency } from "./task/provider-concurrency";
174
+ import { isScoutSpawnable } from "./task/spawn-policy";
171
175
  import type { StructuredSubagentSchemaMode } from "./task/types";
172
176
  import {
173
177
  AUTO_THINKING,
@@ -378,6 +382,8 @@ export interface CreateAgentSessionOptions {
378
382
  thinkingLevel?: ConfiguredThinkingLevel;
379
383
  /** Hard ceiling on the session's thinking effort (e.g. a task spawn's `task.maxEffort`-capped hint); retry-fallback recovery re-clamps to it. */
380
384
  thinkingLevelCeiling?: Effort;
385
+ /** OpenAI service-tier override for this session. `null` omits `service_tier`. */
386
+ openAIServiceTier?: ServiceTier | null;
381
387
  /** Models available for cycling (Ctrl+P in interactive mode) */
382
388
  scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
383
389
  /** Prewalk from the starting model to a fast/cheap target at the first edit/write once the todo list exists. */
@@ -695,12 +701,15 @@ export async function discoverSessionExtensionPaths(
695
701
  cwd: string,
696
702
  settings: Settings,
697
703
  ): Promise<string[]> {
698
- if (options.disableExtensionDiscovery) {
699
- return options.additionalExtensionPaths ?? [];
700
- }
701
- const configuredPaths = [...(options.additionalExtensionPaths ?? []), ...(settings.get("extensions") ?? [])];
702
- const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
703
- return discoverExtensionPaths(configuredPaths, cwd, disabledExtensionIds);
704
+ const configuredPaths = options.disableExtensionDiscovery
705
+ ? (options.additionalExtensionPaths ?? [])
706
+ : [...(options.additionalExtensionPaths ?? []), ...(settings.get("extensions") ?? [])];
707
+ const disabledExtensionIds = options.disableExtensionDiscovery
708
+ ? undefined
709
+ : (settings.get("disabledExtensions") ?? []);
710
+ return discoverExtensionPaths(configuredPaths, cwd, disabledExtensionIds, {
711
+ ambient: !options.disableExtensionDiscovery,
712
+ });
704
713
  }
705
714
 
706
715
  /**
@@ -778,9 +787,11 @@ export async function discoverSkills(
778
787
  export async function discoverContextFiles(
779
788
  cwd?: string,
780
789
  _agentDir?: string,
790
+ disabledExtensions?: string[],
781
791
  ): Promise<Array<{ path: string; content: string; depth?: number }>> {
782
792
  return await loadContextFilesInternal({
783
793
  cwd: cwd ?? getProjectDir(),
794
+ disabledExtensions,
784
795
  });
785
796
  }
786
797
 
@@ -1214,6 +1225,13 @@ export function createAutoLearnCaptureRunner(
1214
1225
  * ```
1215
1226
  */
1216
1227
  export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
1228
+ const rootMode = options.disableExtensionDiscovery ? "explicit-only" : "merge";
1229
+ return await withOmpExtensionRootScope(options.additionalExtensionPaths ?? [], rootMode, () =>
1230
+ createAgentSessionScoped(options),
1231
+ );
1232
+ }
1233
+
1234
+ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Promise<CreateAgentSessionResult> {
1217
1235
  const cwd = options.cwd ?? getProjectDir();
1218
1236
  const agentDir = options.agentDir ?? getAgentDir();
1219
1237
  const eventBus = options.eventBus ?? new EventBus();
@@ -1277,14 +1295,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1277
1295
  ? Promise.resolve(options.contextFiles)
1278
1296
  : logger.time("discoverContextFiles", discoverContextFiles, cwd, agentDir);
1279
1297
  contextFilesPromise.catch(() => {});
1280
- const activeRepoContextPromise = logger.time("resolveActiveRepoContext", async () => {
1298
+ const resolveRepoContext = async (repoCwd: string) => {
1281
1299
  try {
1282
- return await resolveActiveRepoContext(cwd);
1300
+ return await resolveActiveRepoContext(repoCwd);
1283
1301
  } catch (err) {
1284
1302
  logger.debug("Failed to resolve active repo context", { err: String(err) });
1285
1303
  return null;
1286
1304
  }
1287
- });
1305
+ };
1306
+ const activeRepoContextPromise = logger.time("resolveActiveRepoContext", resolveRepoContext, cwd);
1288
1307
  activeRepoContextPromise.catch(() => {});
1289
1308
  const watchdogFilesPromise = logger.time("discoverWatchdogFiles", () => discoverWatchdogFiles(cwd, agentDir));
1290
1309
  watchdogFilesPromise.catch(() => {});
@@ -1377,11 +1396,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1377
1396
  // headless run on an unwritable default config root pays for a feature it
1378
1397
  // does not use.
1379
1398
  const needsPlaceholderKey = secretEntriesNeedPlaceholderKey([...envEntries, ...fileEntries]);
1399
+ const explicitAgentDir = options.agentDir;
1380
1400
  const placeholderKey = needsPlaceholderKey
1381
- ? await getSecretPlaceholderKey(agentDir)
1382
- : await getExistingSecretPlaceholderKey(agentDir);
1401
+ ? await getSecretPlaceholderKey(explicitAgentDir)
1402
+ : await getExistingSecretPlaceholderKey(explicitAgentDir);
1383
1403
  if (allEntries.length > 0) {
1384
- obfuscator = new SecretObfuscator(allEntries, placeholderKey ?? (() => getSecretPlaceholderKeySync(agentDir)));
1404
+ obfuscator = new SecretObfuscator(
1405
+ allEntries,
1406
+ placeholderKey ?? (() => getSecretPlaceholderKeySync(explicitAgentDir)),
1407
+ );
1385
1408
  }
1386
1409
  if (obfuscator?.hasSecrets() !== true && placeholderKey !== undefined) {
1387
1410
  // No configured entry produced an active secret (e.g. only ignored short
@@ -1591,7 +1614,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1591
1614
  }
1592
1615
  return result;
1593
1616
  };
1594
- const [contextFiles, resolvedWorkspaceTree, watchdogFiles, activeRepoContext, discoveredAdvisors] =
1617
+ const [initialContextFiles, resolvedWorkspaceTree, watchdogFiles, initialActiveRepoContext, discoveredAdvisors] =
1595
1618
  await Promise.all([
1596
1619
  contextFilesPromise,
1597
1620
  raceWithDeadline("buildWorkspaceTree", workspaceTreePromise),
@@ -1599,6 +1622,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1599
1622
  activeRepoContextPromise,
1600
1623
  advisorConfigsPromise,
1601
1624
  ]);
1625
+ let contextFiles = initialContextFiles;
1602
1626
 
1603
1627
  let agent: Agent;
1604
1628
  let session!: AgentSession;
@@ -1633,14 +1657,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1633
1657
  const agentKind = (options.taskDepth ?? 0) > 0 || options.parentTaskPrefix ? ("sub" as const) : ("main" as const);
1634
1658
  let registeredAgentRef: AgentRef | undefined;
1635
1659
  /**
1636
- * Forget the agent ref on teardown — unless the agent is being parked (or is
1637
- * already parked). Parking disposes the session but keeps the ref addressable
1638
- * (history://, revive); only process teardown / explicit kill unregisters.
1660
+ * Forget the agent ref on teardown — unless it is a retained terminal ref.
1661
+ * Parking disposes the session but keeps the ref addressable (history://,
1662
+ * revive); a hard kill leaves it as a terminal `aborted` tombstone. Both are
1663
+ * detached (session === null) by the time dispose runs, per the AgentRef
1664
+ * invariant, so preserving them never keeps a disposed session reachable — an
1665
+ * aborted ref that still holds a live session is a bug and is unregistered
1666
+ * rather than handed to ensureLive. Only process teardown / a plain release
1667
+ * unregisters.
1639
1668
  */
1640
1669
  const unregisterUnlessParked = (): void => {
1641
1670
  const ref = registeredAgentRef;
1642
1671
  if (!ref || agentRegistry.get(resolvedAgentId) !== ref) return;
1643
- if (ref.status === "parked") return;
1672
+ if (ref.status === "parked" || (ref.status === "aborted" && !ref.session)) return;
1644
1673
  if (AgentLifecycleManager.global().isParking(resolvedAgentId, ref)) return;
1645
1674
  agentRegistry.unregister(resolvedAgentId, ref);
1646
1675
  };
@@ -2650,7 +2679,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2650
2679
  // a real tool.
2651
2680
  //
2652
2681
  // It must be a `replace`-mode instance. `PiEditExecArgs` carries
2653
- // `old_text`/`new_text` pairs, which is exactly `replace`'s schema and
2682
+ // `old_string`/`new_string` replacements, which is exactly `replace`'s schema and
2654
2683
  // nothing else's — under the default `hashline` mode the frame's args do
2655
2684
  // not match the tool's parameters at all. The registry instance follows
2656
2685
  // the session's configured mode, so the bridge builds its own.
@@ -2704,10 +2733,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2704
2733
 
2705
2734
  // Existing staged/device paths need write registered before active-set assembly.
2706
2735
  // Deferred MCP also registers it now, but refresh activates it only after a server connects.
2736
+ // xd:// mounts never register write: xdev state only exists when the session
2737
+ // already granted a write tool (see createTools), so mounting rides that grant.
2707
2738
  const hasDeferrableTools = Array.from(toolRegistry.values()).some(tool => tool.deferrable === true);
2708
- const hasXdevTools = (toolSession.xdev?.mountedNames.size ?? 0) > 0;
2709
2739
  const planModeAvailable = settings.get("plan.enabled");
2710
- if (!restrictToolNames && (hasDeferrableTools || hasXdevTools || planModeAvailable || deferMCPDiscoveryForUI)) {
2740
+ if (!restrictToolNames && (hasDeferrableTools || planModeAvailable || deferMCPDiscoveryForUI)) {
2711
2741
  await ensureWriteRegistered();
2712
2742
  }
2713
2743
 
@@ -2783,6 +2813,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2783
2813
  tools: Map<string, AgentTool>,
2784
2814
  ): Promise<BuildSystemPromptResult> => {
2785
2815
  toolContextStore.setToolNames(toolNames);
2816
+ const promptCwd = sessionManager.getCwd();
2817
+ const activeRepoContext = hasSession
2818
+ ? await logger.time("resolveActiveRepoContext", resolveRepoContext, promptCwd)
2819
+ : initialActiveRepoContext;
2820
+ if (hasSession && options.contextFiles === undefined) {
2821
+ contextFiles = await logger.time("discoverContextFiles", discoverContextFiles, promptCwd, agentDir, [
2822
+ ...(settings.get("disabledExtensions") ?? []),
2823
+ ]);
2824
+ toolSession.contextFiles = contextFiles;
2825
+ session.setAdvisorContextPrompt(formatAdvisorContextPrompt(contextFiles));
2826
+ }
2786
2827
  const memoryBackend = restrictToolNames ? undefined : await resolveMemoryBackend(settings);
2787
2828
  const memoryInstructions = memoryBackend
2788
2829
  ? await memoryBackend.buildDeveloperInstructions(agentDir, settings, session)
@@ -2839,8 +2880,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2839
2880
  }
2840
2881
  }
2841
2882
  let appendPrompt: string | undefined = appendParts.length > 0 ? appendParts.join("\n\n") : undefined;
2842
- // Owned/in-band tool dialects (non-native) require the catalog as `# Tool:`
2843
- // sections; native tool calling lets the compact name list suffice.
2883
+ // Owned/in-band tool dialects (non-native) require the full functions-
2884
+ // namespace catalog; native tool calling lets the compact name list suffice.
2844
2885
  const nativeTools = resolveDialect(settings.get("tools.format"), agent?.state.model ?? model) === undefined;
2845
2886
  const promptTools = projectSystemPromptToolMetadata(
2846
2887
  tools,
@@ -2852,7 +2893,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2852
2893
  : options.appendSystemPrompt;
2853
2894
  }
2854
2895
  const defaultPrompt = await buildSystemPromptInternal({
2855
- cwd,
2896
+ cwd: promptCwd,
2856
2897
  additionalWorkspaceRoots: sessionManager.getAdditionalDirectories(),
2857
2898
  xdevTools: toolSession.xdev ? xdevEntries(toolSession.xdev) : [],
2858
2899
  xdevDocs: toolSession.xdev
@@ -2874,6 +2915,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2874
2915
  eagerTasksAlways,
2875
2916
  taskBatch: settings.get("task.batch"),
2876
2917
  taskMaxConcurrency: settings.get("task.maxConcurrency"),
2918
+ scoutAvailable: isScoutSpawnable(
2919
+ settings.get("task.disabledAgents") as string[] | undefined,
2920
+ options.spawns ?? "*",
2921
+ ),
2877
2922
  taskIrcEnabled: !restrictToolNames && isIrcEnabled(settings, options.taskDepth ?? 0),
2878
2923
  autoQaEnabled: !restrictToolNames && isAutoQaEnabled(settings),
2879
2924
  secretsEnabled,
@@ -2953,6 +2998,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2953
2998
  const xdevReadAvailable =
2954
2999
  builtInRegistryToolNames.has("read") &&
2955
3000
  (explicitlyRequestedToolNameSet === undefined || explicitlyRequestedToolNameSet.has("read"));
3001
+ const xdevWriteAvailable =
3002
+ builtInRegistryToolNames.has("write") &&
3003
+ (explicitlyRequestedToolNameSet === undefined || explicitlyRequestedToolNameSet.has("write"));
2956
3004
  const initialRequestedActiveToolNames = options.toolNames
2957
3005
  ? requestedActiveToolNames
2958
3006
  : requestedActiveToolNames.filter(name => !defaultInactiveToolNames.has(name));
@@ -2998,23 +3046,23 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2998
3046
 
2999
3047
  // Partition the initial enabled set for the xd:// transport. Tool instances
3000
3048
  // remain in the canonical map; only presentation names move between layers.
3049
+ // Mounting requires both transport halves in the granted set (`read xd://`
3050
+ // discovers, `write xd://<tool>` executes); a session without either keeps
3051
+ // every tool top-level instead of auto-granting the missing transport.
3001
3052
  if (toolSession.xdev) {
3002
3053
  const topLevelToolNames: string[] = [];
3003
3054
  const mountedNames: string[] = [];
3004
3055
  for (const name of initialToolNames) {
3005
3056
  const tool = toolRegistry.get(name);
3006
3057
  const explicitlyRequested = explicitlyRequestedToolNameSet?.has(name) === true;
3007
- if (tool && xdevReadAvailable && !explicitlyRequested && isMountableUnderXdev(tool))
3058
+ if (tool && xdevReadAvailable && xdevWriteAvailable && !explicitlyRequested && isMountableUnderXdev(tool))
3008
3059
  mountedNames.push(name);
3009
3060
  else topLevelToolNames.push(name);
3010
3061
  }
3011
- const writeTransportAvailable = mountedNames.length === 0 || (await ensureWriteRegistered());
3012
3062
  toolSession.xdev.mountedNames.clear();
3013
- if (writeTransportAvailable) {
3014
- for (const name of mountedNames) toolSession.xdev.mountedNames.add(name);
3015
- initialToolNames = topLevelToolNames;
3016
- if (mountedNames.length > 0 && !initialToolNames.includes("write")) initialToolNames.push("write");
3017
- }
3063
+ for (const name of mountedNames) toolSession.xdev.mountedNames.add(name);
3064
+ initialToolNames = topLevelToolNames;
3065
+ if (mountedNames.length > 0 && !initialToolNames.includes("write")) initialToolNames.push("write");
3018
3066
  }
3019
3067
 
3020
3068
  setActiveToolNames(initialToolNames);
@@ -3104,13 +3152,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3104
3152
  const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
3105
3153
  const preferOpenAICodexWebsockets =
3106
3154
  openaiWebsocketSetting === "on" ? true : openaiWebsocketSetting === "off" ? false : undefined;
3107
- const initialServiceTierByFamily = hasServiceTierEntry
3155
+ const configuredServiceTierByFamily = hasServiceTierEntry
3108
3156
  ? (existingSession.serviceTier ?? {})
3109
3157
  : buildServiceTierByFamily(
3110
3158
  settings.get("tier.openai"),
3111
3159
  settings.get("tier.anthropic"),
3112
3160
  settings.get("tier.google"),
3113
3161
  );
3162
+ const initialServiceTierByFamily = { ...configuredServiceTierByFamily };
3163
+ if (options.openAIServiceTier === null) {
3164
+ delete initialServiceTierByFamily.openai;
3165
+ } else if (options.openAIServiceTier !== undefined) {
3166
+ initialServiceTierByFamily.openai = options.openAIServiceTier;
3167
+ }
3114
3168
 
3115
3169
  // One-shot launch-latency marker: fired the first time the loop dispatches
3116
3170
  // a chat request to the provider transport. See onFirstChatDispatch.
@@ -3213,6 +3267,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3213
3267
  // Restore messages if session has existing data
3214
3268
  if (hasExistingSession) {
3215
3269
  agent.replaceMessages(existingSession.messages);
3270
+ if (options.openAIServiceTier !== undefined) {
3271
+ sessionManager.appendServiceTierChange(
3272
+ Object.keys(initialServiceTierByFamily).length > 0 ? initialServiceTierByFamily : null,
3273
+ );
3274
+ }
3216
3275
  } else {
3217
3276
  // Save initial model, thinking level, and service tier for new sessions so they can be restored on resume.
3218
3277
  if (model) {
@@ -3223,8 +3282,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3223
3282
  // classification persists its concrete effort once a real user turn runs.
3224
3283
  sessionManager.appendThinkingLevelChange(effectiveThinkingLevel);
3225
3284
  }
3226
- if (Object.keys(initialServiceTierByFamily).length > 0) {
3227
- sessionManager.appendServiceTierChange(initialServiceTierByFamily);
3285
+ if (options.openAIServiceTier !== undefined || Object.keys(initialServiceTierByFamily).length > 0) {
3286
+ sessionManager.appendServiceTierChange(
3287
+ Object.keys(initialServiceTierByFamily).length > 0 ? initialServiceTierByFamily : null,
3288
+ );
3228
3289
  }
3229
3290
  }
3230
3291
 
@@ -3271,8 +3332,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3271
3332
  .map(tool => new ExtensionToolWrapper(wrapToolWithMetaNotice(tool), extensionRunner) as Tool);
3272
3333
 
3273
3334
  const advisorWatchdogPrompts = [...watchdogFiles];
3274
- if (activeRepoContext) {
3275
- advisorWatchdogPrompts.push(formatActiveRepoWatchdogPrompt(activeRepoContext));
3335
+ if (initialActiveRepoContext) {
3336
+ advisorWatchdogPrompts.push(formatActiveRepoWatchdogPrompt(initialActiveRepoContext));
3276
3337
  }
3277
3338
  const advisorWatchdogPrompt = advisorWatchdogPrompts.length > 0 ? advisorWatchdogPrompts.join("\n\n") : undefined;
3278
3339
  // Hand the advisor the same project context files (AGENTS.md, etc.) the
@@ -3302,6 +3363,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3302
3363
  initialAdvisorCosts,
3303
3364
  settings,
3304
3365
  autoApprove: options.autoApprove,
3366
+ scoutAllowedBySpawnPolicy: isScoutSpawnable(undefined, options.spawns ?? "*"),
3305
3367
  evalKernelOwnerId,
3306
3368
  // Defined only for top-level sessions (creation is gated above).
3307
3369
  // AgentSession uses this to decide whether it may dispose the global
@@ -3504,6 +3566,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3504
3566
  }
3505
3567
  }
3506
3568
 
3569
+ // Broker-shared language servers: one server per project, multiplexed
3570
+ // across omp instances by the LSP mux daemon. Session-level because the
3571
+ // flag lives in module state consulted on every client cold-start.
3572
+ setSharedLspEnabled(enableLsp && settings.get("lsp.shared"));
3573
+
3507
3574
  // Start LSP warmup in the background so startup does not block on language server initialization.
3508
3575
  // With `lsp.lazy` (the default) the warmup is skipped: recognized servers are still discovered and
3509
3576
  // surfaced in the UI as "available", but cold-start on first use — the lsp tool or an edit/write
@@ -2,7 +2,7 @@ import * as crypto from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { SENSITIVE_TOKEN_RE } from "@oh-my-pi/pi-ai/providers/transform-messages";
5
- import { getAgentDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
5
+ import { getSecretPlaceholderKeyPath, isEnoent, logger } from "@oh-my-pi/pi-utils";
6
6
  import { YAML } from "bun";
7
7
  import { regexHasUnresolvableShortMatchFallback, type SecretEntry, sanitizeSecretFriendlyName } from "./obfuscator";
8
8
  import { compileSecretRegex } from "./regex";
@@ -11,17 +11,15 @@ const PLACEHOLDER_KEY_RE = /^[A-Za-z0-9_-]{43}$/;
11
11
  const cachedPlaceholderKeys = new Map<string, string>();
12
12
 
13
13
  /**
14
- * Per-install secret key for the placeholder digest. Persisted under the agent
15
- * config directory and never sent to a provider, so model-visible placeholders
16
- * cannot be reversed by dictionary-hashing candidate secrets. Stable across
17
- * sessions so persisted transcripts deobfuscate consistently. Defaults to
18
- * `getAgentDir()` the same directory `createAgentSession()` passes as
19
- * `agentDir` so a caller relying on the default reads/writes the identical
20
- * key file live sessions use, per `~/.omp/agent/secret-placeholder.key` in
21
- * docs/secrets.md.
14
+ * Per-install secret key for the placeholder digest. Persisted under XDG state
15
+ * and never sent to a provider, so model-visible placeholders cannot be reversed
16
+ * by dictionary-hashing candidate secrets. Stable across sessions so persisted
17
+ * transcripts deobfuscate consistently. Defaults to `getSecretPlaceholderKeyPath()`
18
+ * — `$XDG_STATE_HOME/omp/secret-placeholder.key` (or `~/.omp/agent/secret-placeholder.key`
19
+ * without XDG), per docs/secrets.md.
22
20
  */
23
- export async function getSecretPlaceholderKey(keyDir: string = getAgentDir()): Promise<string> {
24
- const keyPath = path.join(keyDir, "secret-placeholder.key");
21
+ export async function getSecretPlaceholderKey(keyDir?: string): Promise<string> {
22
+ const keyPath = keyDir ? path.join(keyDir, "secret-placeholder.key") : getSecretPlaceholderKeyPath();
25
23
  const cached = cachedPlaceholderKeys.get(keyPath);
26
24
  if (cached !== undefined) return cached;
27
25
 
@@ -32,7 +30,7 @@ export async function getSecretPlaceholderKey(keyDir: string = getAgentDir()): P
32
30
  }
33
31
 
34
32
  const generated = crypto.randomBytes(32).toString("base64url");
35
- await fs.promises.mkdir(keyDir, { recursive: true });
33
+ await fs.promises.mkdir(path.dirname(keyPath), { recursive: true });
36
34
  try {
37
35
  await fs.promises.writeFile(keyPath, generated, { flag: "wx", mode: 0o600 });
38
36
  cachedPlaceholderKeys.set(keyPath, generated);
@@ -53,8 +51,8 @@ export async function getSecretPlaceholderKey(keyDir: string = getAgentDir()): P
53
51
  }
54
52
 
55
53
  /** Return an existing placeholder key for redaction without creating a new key file. */
56
- export async function getExistingSecretPlaceholderKey(keyDir: string = getAgentDir()): Promise<string | undefined> {
57
- const keyPath = path.join(keyDir, "secret-placeholder.key");
54
+ export async function getExistingSecretPlaceholderKey(keyDir?: string): Promise<string | undefined> {
55
+ const keyPath = keyDir ? path.join(keyDir, "secret-placeholder.key") : getSecretPlaceholderKeyPath();
58
56
  const cached = cachedPlaceholderKeys.get(keyPath);
59
57
  if (cached !== undefined) return cached;
60
58
  // Redaction-only: this key is loaded solely to redact an existing key file from
@@ -85,8 +83,8 @@ let ephemeralSyncPlaceholderKey: string | undefined;
85
83
  * throws: an unreadable or unwritable key file degrades to a process-ephemeral
86
84
  * key (with a warning) instead of breaking the session.
87
85
  */
88
- export function getSecretPlaceholderKeySync(keyDir: string = getAgentDir()): string {
89
- const keyPath = path.join(keyDir, "secret-placeholder.key");
86
+ export function getSecretPlaceholderKeySync(keyDir?: string): string {
87
+ const keyPath = keyDir ? path.join(keyDir, "secret-placeholder.key") : getSecretPlaceholderKeyPath();
90
88
  const cached = cachedPlaceholderKeys.get(keyPath);
91
89
  if (cached !== undefined) return cached;
92
90
  try {
@@ -100,7 +98,7 @@ export function getSecretPlaceholderKeySync(keyDir: string = getAgentDir()): str
100
98
  }
101
99
  const generated = crypto.randomBytes(32).toString("base64url");
102
100
  try {
103
- fs.mkdirSync(keyDir, { recursive: true });
101
+ fs.mkdirSync(path.dirname(keyPath), { recursive: true });
104
102
  fs.writeFileSync(keyPath, generated, { flag: "wx", mode: 0o600 });
105
103
  cachedPlaceholderKeys.set(keyPath, generated);
106
104
  return generated;
@@ -205,7 +203,7 @@ export function collectEnvSecrets(): SecretEntry[] {
205
203
  * API keys) that are NOT configured via secrets.yml or the environment. Without
206
204
  * these, such a token in a tool result falls through to pi-ai's irreversible
207
205
  * provider-boundary redaction (`[openai_token_redacted]`); the model then echoes
208
- * that placeholder into edit-tool `old_text`, which can never match the real
206
+ * that placeholder into edit-tool `old_string`, which can never match the real
209
207
  * bytes on disk (issue #6968). Routing the same shapes through the obfuscator
210
208
  * mints reversible keyed placeholders that `deobfuscateToolArguments` restores
211
209
  * before tool execution, keeping exact-match edits working while the credential
@@ -109,6 +109,8 @@ export interface AgentSessionConfig {
109
109
  agent: Agent;
110
110
  sessionManager: SessionManager;
111
111
  settings: Settings;
112
+ /** Whether the session spawn policy permits the read-only `scout` subagent. Defaults to true. */
113
+ scoutAllowedBySpawnPolicy?: boolean;
112
114
  /** Whether the caller explicitly requested yolo/auto-approve behavior for this session. */
113
115
  autoApprove?: boolean;
114
116
  /** Models to cycle through with Ctrl+P (from --models flag). */
@@ -232,7 +234,7 @@ export interface AgentSessionConfig {
232
234
  /**
233
235
  * Build the `replace`-mode `edit` a Cursor `pi_edit` frame needs, against the
234
236
  * advisor-scoped tool session. The advisor's ordinary instance follows the
235
- * configured `edit.mode` and rejects the frame's `old_text`/`new_text` pairs.
237
+ * configured `edit.mode` and rejects the frame's `old_string`/`new_string` args.
236
238
  */
237
239
  advisorCreateEditTool?(): AgentTool | undefined;
238
240
  /**
@@ -518,6 +518,7 @@ export class AgentSession {
518
518
  // Agent identity (registry id) used for IRC routing and job ownership.
519
519
  #agentId: string | undefined;
520
520
  #agentKind: "main" | "sub" = "main";
521
+ #scoutAllowedBySpawnPolicy = true;
521
522
  #providerSessionId: string | undefined;
522
523
  #freshProviderSessionId: string | undefined;
523
524
  #inheritedProviderPromptCacheKey: string | undefined;
@@ -1237,6 +1238,7 @@ export class AgentSession {
1237
1238
  this.#loopGuards = new LoopGuards(streamGuardsHost);
1238
1239
  this.#agentId = config.agentId;
1239
1240
  this.#agentKind = config.agentKind ?? "main";
1241
+ this.#scoutAllowedBySpawnPolicy = config.scoutAllowedBySpawnPolicy ?? true;
1240
1242
  this.#providerSessionId = config.providerSessionId;
1241
1243
  this.#inheritedProviderPromptCacheKey =
1242
1244
  config.providerPromptCacheKeySource === "fork" ? this.agent.promptCacheKey : undefined;
@@ -1382,6 +1384,7 @@ export class AgentSession {
1382
1384
  extensionRunner: this.#extensionRunner,
1383
1385
  sideStreamFn: this.#sideStreamFn,
1384
1386
  providerSessionState: this.#providerSessionState,
1387
+ preferWebsockets: this.#preferWebsockets,
1385
1388
  model: () => this.model,
1386
1389
  thinkingLevel: () => this.thinkingLevel,
1387
1390
  isDisposed: () => this.#isDisposed,
@@ -1417,6 +1420,7 @@ export class AgentSession {
1417
1420
  syncTodoPhasesFromBranch: () => this.#todo.syncFromBranch(),
1418
1421
  resetAdvisorRuntimes: () => this.#advisors.resetAllRuntimes(),
1419
1422
  rebaseAfterCompaction: () => this.#stats.rebaseAfterCompaction(),
1423
+ recordAnchoredHistoryRewrite: tokensRemoved => this.#stats.recordAnchoredHistoryRewrite(tokensRemoved),
1420
1424
  getContextBreakdown: options => this.getContextBreakdown(options),
1421
1425
  getContextUsage: options => this.getContextUsage(options),
1422
1426
  shake: (mode, options) => this.shake(mode, options),
@@ -4634,6 +4638,11 @@ export class AgentSession {
4634
4638
  };
4635
4639
  }
4636
4640
 
4641
+ #isScoutAvailable(): boolean {
4642
+ const disabledAgents = this.settings.get("task.disabledAgents") as string[] | undefined;
4643
+ return this.#scoutAllowedBySpawnPolicy && !disabledAgents?.includes("scout");
4644
+ }
4645
+
4637
4646
  async #buildPlanModeMessage(): Promise<CustomMessage | null> {
4638
4647
  const state = this.#planModeState;
4639
4648
  if (!state?.enabled) return null;
@@ -4657,6 +4666,7 @@ export class AgentSession {
4657
4666
  isHashlineEditMode: this.#resolveActiveEditMode() === "hashline",
4658
4667
  reentry: state.reentry ?? false,
4659
4668
  iterative: state.workflow === "iterative",
4669
+ scoutAvailable: this.#isScoutAvailable(),
4660
4670
  });
4661
4671
 
4662
4672
  return {
@@ -4788,7 +4798,10 @@ export class AgentSession {
4788
4798
  keywordNotices.push({
4789
4799
  role: "custom",
4790
4800
  customType: "workflow-notice",
4791
- content: renderWorkflowNotice({ taskBatch: this.settings.get("task.batch") }),
4801
+ content: renderWorkflowNotice({
4802
+ taskBatch: this.settings.get("task.batch"),
4803
+ scoutAvailable: this.#isScoutAvailable(),
4804
+ }),
4792
4805
  display: false,
4793
4806
  attribution: "user",
4794
4807
  timestamp,
@@ -6333,7 +6346,6 @@ export class AgentSession {
6333
6346
  selector?: string;
6334
6347
  thinkingLevel?: ThinkingLevel;
6335
6348
  persist?: boolean;
6336
- currentContextTokens?: number;
6337
6349
  },
6338
6350
  ): Promise<{ switched: boolean }> {
6339
6351
  return this.#models.setModel(model, role, options);
@@ -8719,6 +8731,15 @@ export class AgentSession {
8719
8731
  return this.#advisors.applyAdvisorConfigs(advisors, sharedInstructions);
8720
8732
  }
8721
8733
 
8734
+ /**
8735
+ * Refresh the project context prompt advisor sessions run against after
8736
+ * context files change on `/reload-plugins`. Rebuilds live advisor runtimes so
8737
+ * they stop evaluating turns against stale `AGENTS.md` instructions.
8738
+ */
8739
+ setAdvisorContextPrompt(contextPrompt: string | undefined): void {
8740
+ this.#advisors.setContextPrompt(contextPrompt);
8741
+ }
8742
+
8722
8743
  /**
8723
8744
  * Whether the advisor setting is enabled for this session.
8724
8745
  */
@@ -8,7 +8,7 @@ import {
8
8
  SqliteAuthCredentialStore,
9
9
  type StoredAuthCredential,
10
10
  } from "@oh-my-pi/pi-ai";
11
- import { AsyncDrain, getAgentDbPath, getStatsDbPath, isRecord, logger } from "@oh-my-pi/pi-utils";
11
+ import { AsyncDrain, getAgentDbPath, getDbBusyTimeoutMs, getStatsDbPath, isRecord, logger } from "@oh-my-pi/pi-utils";
12
12
  import type { RawSettings as Settings } from "../config/settings";
13
13
 
14
14
  /** Row shape for settings table queries */
@@ -199,8 +199,10 @@ ON CONFLICT(model_key) DO UPDATE SET
199
199
  // Install the busy handler BEFORE any lock-taking statement (incl.
200
200
  // `PRAGMA journal_mode=WAL`, which acquires an exclusive lock during WAL
201
201
  // recovery). Without this, concurrent omp startups can crash here with
202
- // `SQLITE_BUSY` / `SQLITE_BUSY_RECOVERY`. See issue #2421.
203
- this.#db.run("PRAGMA busy_timeout = 5000");
202
+ // `SQLITE_BUSY` / `SQLITE_BUSY_RECOVERY`. See issue #2421. Headless
203
+ // hosts bound the wait so lock contention cannot freeze the protocol
204
+ // loop for the full interactive timeout.
205
+ this.#db.run(`PRAGMA busy_timeout = ${getDbBusyTimeoutMs()}`);
204
206
  this.#db.run(`
205
207
  PRAGMA journal_mode=WAL;
206
208
  PRAGMA synchronous=NORMAL;
@@ -571,7 +573,7 @@ FROM model_usage_legacy
571
573
  async backfillModelPerfFromStats(statsDbPath: string): Promise<number> {
572
574
  const statsDb = new Database(statsDbPath, { readonly: true });
573
575
  try {
574
- statsDb.run("PRAGMA busy_timeout = 5000");
576
+ statsDb.run(`PRAGMA busy_timeout = ${getDbBusyTimeoutMs()}`);
575
577
  const select = statsDb.prepare(
576
578
  `SELECT rowid, timestamp, provider, model, output_tokens, duration, ttft
577
579
  FROM messages
@@ -1,7 +1,7 @@
1
1
  import { Database, type Statement } from "bun:sqlite";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
- import { AsyncDrain, getHistoryDbPath, logger } from "@oh-my-pi/pi-utils";
4
+ import { AsyncDrain, getDbBusyTimeoutMs, getHistoryDbPath, logger } from "@oh-my-pi/pi-utils";
5
5
 
6
6
  export interface HistoryEntry {
7
7
  id: number;
@@ -51,7 +51,9 @@ export class HistoryStorage {
51
51
  this.#db = new Database(dbPath);
52
52
 
53
53
  // Install the busy handler BEFORE any lock-taking statement. See #2421.
54
- this.#db.run("PRAGMA busy_timeout = 5000");
54
+ // Headless hosts bound the wait so lock contention cannot freeze the
55
+ // protocol loop for the full interactive timeout.
56
+ this.#db.run(`PRAGMA busy_timeout = ${getDbBusyTimeoutMs()}`);
55
57
 
56
58
  const hasFts = this.#db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='history_fts'").get();
57
59
  this.#db.run(`
@@ -511,6 +511,15 @@ class IndexedSessionStorageWriter implements SessionStorageWriter {
511
511
  return next;
512
512
  }
513
513
 
514
+ appendSync(line: string): void {
515
+ if (this.#closed) throw new Error("Writer closed");
516
+ if (this.#error) throw this.#error;
517
+ // Local index is updated immediately; remote publish stays ordered on the
518
+ // path queue. Callers that need remote durability still await append()/flush().
519
+ const mtimeMs = this.#storage._appendForWriter(this.#path, line);
520
+ void this.#trackPromise(this.#storage._queueAppend(this.#path, line, mtimeMs, () => this.#error));
521
+ }
522
+
514
523
  async append(line: string): Promise<void> {
515
524
  if (this.#closed) throw new Error("Writer closed");
516
525
  if (this.#error) throw this.#error;