@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/tools/glob.ts CHANGED
@@ -13,6 +13,7 @@ import { splitMemoryGlobPattern } from "../internal-urls/memory-protocol";
13
13
  import type { Theme } from "../modes/theme/theme";
14
14
  import globDescription from "../prompts/tools/glob.md" with { type: "text" };
15
15
  import { type TruncationResult, truncateHead } from "../session/streaming-output";
16
+ import { isScoutSpawnable } from "../task/spawn-policy";
16
17
  import { Ellipsis, fileHyperlink, renderFileList, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
17
18
  import type { ToolSession } from ".";
18
19
  import { applyListLimit } from "./list-limit";
@@ -106,7 +107,14 @@ export class GlobTool implements AgentTool<typeof findSchema, GlobToolDetails> {
106
107
  readonly approval = "read" as const;
107
108
  readonly loadMode = "essential";
108
109
  readonly label = "Glob";
109
- readonly description: string;
110
+ get description(): string {
111
+ return prompt.render(globDescription, {
112
+ scoutAvailable: isScoutSpawnable(
113
+ this.session.settings.get("task.disabledAgents") as string[] | undefined,
114
+ this.session.getSessionSpawns?.() ?? "*",
115
+ ),
116
+ });
117
+ }
110
118
  readonly parameters = findSchema;
111
119
 
112
120
  readonly examples: readonly ToolExample<typeof findSchema.infer>[] = [
@@ -138,7 +146,6 @@ export class GlobTool implements AgentTool<typeof findSchema, GlobToolDetails> {
138
146
  ) {
139
147
  this.#customOps = options?.operations;
140
148
  this.#rootPathAlias = options?.rootPathAlias === true;
141
- this.description = prompt.render(globDescription);
142
149
  }
143
150
 
144
151
  async execute(
package/src/tools/grep.ts CHANGED
@@ -22,6 +22,7 @@ import type { InternalResource, ResolveContext } from "../internal-urls/types";
22
22
  import type { Theme } from "../modes/theme/theme";
23
23
  import grepDescription from "../prompts/tools/grep.md" with { type: "text" };
24
24
  import { DEFAULT_MAX_COLUMN, type TruncationResult, truncateHead, truncateLine } from "../session/streaming-output";
25
+ import { isScoutSpawnable } from "../task/spawn-policy";
25
26
  import {
26
27
  Ellipsis,
27
28
  fileHyperlink,
@@ -909,7 +910,17 @@ export class GrepTool implements AgentTool<typeof searchSchema, GrepToolDetails>
909
910
  readonly label = "Grep";
910
911
  readonly loadMode = "discoverable";
911
912
  readonly summary = "Grep file contents using ripgrep (fast regex search)";
912
- readonly description: string;
913
+ get description(): string {
914
+ const displayMode = resolveFileDisplayMode(this.session);
915
+ return prompt.render(grepDescription, {
916
+ IS_HL_MODE: displayMode.hashLines,
917
+ IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
918
+ scoutAvailable: isScoutSpawnable(
919
+ this.session.settings.get("task.disabledAgents") as string[] | undefined,
920
+ this.session.getSessionSpawns?.() ?? "*",
921
+ ),
922
+ });
923
+ }
913
924
  readonly parameters = searchSchema;
914
925
  readonly strict = true;
915
926
 
@@ -924,11 +935,6 @@ export class GrepTool implements AgentTool<typeof searchSchema, GrepToolDetails>
924
935
  this.#contextOverride = context !== undefined ? Math.max(0, Math.floor(context)) : undefined;
925
936
  const total = options?.totalMatchLimit;
926
937
  this.#totalMatchLimit = total !== undefined ? Math.max(1, Math.floor(total)) : undefined;
927
- const displayMode = resolveFileDisplayMode(session);
928
- this.description = prompt.render(grepDescription, {
929
- IS_HL_MODE: displayMode.hashLines,
930
- IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
931
- });
932
938
  }
933
939
 
934
940
  async execute(
@@ -667,8 +667,12 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
667
667
  // Ordinary sessions use xd:// for discoverable built-ins, custom tools, and
668
668
  // MCP tools. Structured children must expose only their host-provided names,
669
669
  // so never allocate a registry that later SDK assembly could populate.
670
+ // The transport rides read/write, so a session granted no write tool never
671
+ // allocates xd:// state — its tools are exposed top-level directly instead
672
+ // of auto-granting a write transport the session was denied.
670
673
  // Explicitly requested built-ins retain their top-level presentation.
671
- const xdevEnabled = !restrictToolNames && session.settings.get("tools.xdev");
674
+ const xdevEnabled =
675
+ !restrictToolNames && session.settings.get("tools.xdev") && tools.some(tool => tool.name === "write");
672
676
  const mountBuiltinTools = requestedTools === undefined;
673
677
  if (xdevEnabled) {
674
678
  const mountedNames = new Set<string>();
@@ -686,14 +690,14 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
686
690
  };
687
691
  tools = kept;
688
692
  }
689
- // The xd:// transport rides read/write: `read xd://` lists+documents devices,
690
- // `write xd://<tool>` executes them. Staged previews from deferrable tools
691
- // (e.g. ast_edit) also resolve through a `write` to xd://resolve/reject. Retain
692
- // both whenever any device is mounted or a deferrable tool can stage one.
693
+ // Staged previews from deferrable tools (e.g. ast_edit) resolve through a
694
+ // `write` to xd://resolve/reject, so retain write whenever one can stage.
695
+ // xd:// mounting itself never registers write: sessions without a granted
696
+ // write tool skip mounting entirely (see xdevEnabled above).
693
697
  const xdevMounted = (session.xdev?.mountedNames.size ?? 0) > 0;
694
698
  if (
695
699
  !restrictToolNames &&
696
- (tools.some(tool => tool.deferrable === true) || xdevMounted) &&
700
+ tools.some(tool => tool.deferrable === true) &&
697
701
  !tools.some(tool => tool.name === "write")
698
702
  ) {
699
703
  const writeTool = await logger.time("createTools:write", BUILTIN_TOOLS.write, session);
@@ -12,9 +12,15 @@ import { prompt } from "@oh-my-pi/pi-utils";
12
12
  import { type } from "arktype";
13
13
  import { extractTextContent } from "../commit/utils";
14
14
 
15
- import { expandRoleAlias, getModelMatchPreferences, resolveModelFromString } from "../config/model-resolver";
15
+ import {
16
+ expandRoleAlias,
17
+ extractExplicitThinkingSelector,
18
+ getModelMatchPreferences,
19
+ resolveModelFromString,
20
+ } from "../config/model-resolver";
16
21
  import inspectImageDescription from "../prompts/tools/inspect-image.md" with { type: "text" };
17
22
  import inspectImageSystemPromptTemplate from "../prompts/tools/inspect-image-system.md" with { type: "text" };
23
+ import { concreteThinkingLevel, resolveThinkingLevelForModel, toReasoningEffort } from "../thinking";
18
24
  import {
19
25
  ImageInputTooLargeError,
20
26
  type LoadedImageInput,
@@ -163,11 +169,17 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
163
169
  };
164
170
 
165
171
  const activeModelPattern = this.session.getActiveModelString?.() ?? this.session.getModelString?.();
166
- const model =
167
- resolvePattern("@vision") ??
168
- resolvePattern("@default") ??
169
- resolvePattern(activeModelPattern) ??
170
- availableModels[0];
172
+ let model: Model<Api> | undefined;
173
+ let selectedPattern: string | undefined;
174
+ for (const pattern of ["@vision", "@default", activeModelPattern]) {
175
+ const resolved = resolvePattern(pattern);
176
+ if (resolved) {
177
+ model = resolved;
178
+ selectedPattern = pattern;
179
+ break;
180
+ }
181
+ }
182
+ model ??= availableModels[0];
171
183
  if (!model) {
172
184
  throw new ToolError("Unable to resolve a model for inspect_image.");
173
185
  }
@@ -233,6 +245,19 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
233
245
  return `inspect_image request timed out after ${seconds}s. Increase inspect_image.timeoutMs (currently ${timeoutMs}ms; 0 disables) or check the vision model provider.`;
234
246
  };
235
247
 
248
+ // Honor the thinking effort configured on the resolved model role
249
+ // (e.g. `modelRoles.vision: <model>:high`). Without it the oneshot sent a
250
+ // suppressed/zero thinking budget, which thinking-only models (Gemini 3.x)
251
+ // reject with HTTP 400 ("Budget 0 is invalid. This model only works in
252
+ // thinking mode.").
253
+ const configuredThinking = concreteThinkingLevel(
254
+ extractExplicitThinkingSelector(selectedPattern, this.session.settings, {
255
+ isLiteralModelId: (provider, id) =>
256
+ availableModels.some(candidate => candidate.provider === provider && candidate.id === id),
257
+ }),
258
+ );
259
+ const reasoning = toReasoningEffort(resolveThinkingLevelForModel(model, configuredThinking));
260
+
236
261
  let response: AssistantMessage;
237
262
  try {
238
263
  response = await instrumentedCompleteSimple(
@@ -253,6 +278,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
253
278
  {
254
279
  apiKey: modelRegistry.resolver(model, this.session.getSessionId?.() ?? undefined),
255
280
  signal: effectiveSignal,
281
+ reasoning,
256
282
  },
257
283
  { telemetry, oneshotKind: "inspect_image", completeImpl: this.completeImageRequest },
258
284
  );
@@ -61,6 +61,8 @@ export const PREVIEW_LIMITS = {
61
61
  OUTPUT_COLLAPSED: 3,
62
62
  /** Output preview lines in expanded view */
63
63
  OUTPUT_EXPANDED: 10,
64
+ /** Computer script lines shown in collapsed view */
65
+ COMPUTER_CODE_COLLAPSED: 10,
64
66
  /** Max hunks shown when collapsed (edit tool) */
65
67
  DIFF_COLLAPSED_HUNKS: 8,
66
68
  /** Max diff lines shown when collapsed (edit tool) */
@@ -1,10 +1,10 @@
1
- import { untilAborted } from "@oh-my-pi/pi-utils";
2
- import { ToolError, throwIfAborted } from "../tool-errors";
1
+ import { untilAborted } from "@oh-my-pi/pi-utils/abortable";
2
+ import { ToolError, throwIfAborted } from "./tool-errors";
3
3
 
4
4
  /**
5
5
  * Marks a run-scoped promise as observed without changing its behavior for awaited callers.
6
6
  *
7
- * Browser run teardown aborts can reject promises created for evaluated code after user code
7
+ * Run teardown aborts can reject promises created for evaluated code after user code
8
8
  * has stopped observing them (for example fire-and-forget `wait()`/facade calls). In 16.3.0
9
9
  * those zero-consumer rejections reached the process-level `unhandledRejection` handler and
10
10
  * killed every subagent sharing the process (issues #4499/#4672). Attaching a no-op rejection
@@ -33,8 +33,8 @@ export interface WaitPredicateOptions {
33
33
  /**
34
34
  * Effective `wait(predicate)` deadline for a given cell budget. Always strictly below
35
35
  * the cell budget so the named `wait(predicate) timed out` error wins the race against
36
- * the opaque whole-cell "Browser code execution timed out". `0`/`Infinity` ("disable")
37
- * map to the largest bounded deadline; negative/NaN garbage falls back to the default.
36
+ * the opaque whole-cell execution timeout. `0`/`Infinity` ("disable") map to the largest
37
+ * bounded deadline; negative/NaN garbage falls back to the default.
38
38
  */
39
39
  export function resolvePredicateTimeout(cellTimeoutMs: number, explicit?: number): number {
40
40
  const budgetBound = Math.max(1, cellTimeoutMs - CELL_BUDGET_SLACK_MS);
@@ -44,15 +44,15 @@ export function resolvePredicateTimeout(cellTimeoutMs: number, explicit?: number
44
44
  }
45
45
 
46
46
  /**
47
- * Run-scoped `wait()` helper for evaluated browser code, honoring the owning run's
48
- * cancellation signal.
47
+ * Run-scoped `wait()` helper for evaluated code (browser and computer workers), honoring
48
+ * the owning run's cancellation signal.
49
49
  *
50
50
  * - `wait(ms)` sleeps for `ms` milliseconds.
51
51
  * - `wait(fn, { timeout?, interval? })` polls `fn` (sync or async) until it returns a
52
52
  * truthy value and resolves with that value; throws a named `ToolError` on timeout
53
53
  * instead of stalling into the whole-cell deadline. Predicate errors propagate.
54
54
  */
55
- export function waitForBrowserRun(
55
+ export function waitForRun(
56
56
  msOrPredicate: number | (() => unknown),
57
57
  signal: AbortSignal,
58
58
  opts?: WaitPredicateOptions,
@@ -86,8 +86,8 @@ export function waitForBrowserRun(
86
86
  return markHandled(promise);
87
87
  }
88
88
 
89
- /** Binds a long-lived browser facade to one evaluated run's abort signal. */
90
- export function bindBrowserRunFacade<T extends object>(target: T, signal: AbortSignal): T {
89
+ /** Binds a long-lived scope facade (page/tab/desktop objects) to one evaluated run's abort signal. */
90
+ export function bindRunFacade<T extends object>(target: T, signal: AbortSignal): T {
91
91
  const cache = new Map<PropertyKey, unknown>();
92
92
  return new Proxy(target, {
93
93
  get(current, prop) {
@@ -121,7 +121,7 @@ export function bindBrowserRunFacade<T extends object>(target: T, signal: AbortS
121
121
  // brand-check internal slots that a Proxy cannot forward, and reading a
122
122
  // signal needs no abort gating anyway.
123
123
  if (value instanceof AbortSignal) return value;
124
- const wrapped = bindBrowserRunFacade(value, signal);
124
+ const wrapped = bindRunFacade(value, signal);
125
125
  cache.set(prop, wrapped);
126
126
  return wrapped;
127
127
  }
@@ -11,6 +11,7 @@ export const TOOL_TIMEOUTS = {
11
11
  bash: { default: 300, min: 1, max: 3600 },
12
12
  eval: { default: 30, min: 1, max: 3600 },
13
13
  browser: { default: 30, min: 1, max: 300 },
14
+ computer: { default: 120, min: 1, max: 300 },
14
15
  ssh: { default: 60, min: 1, max: 3600 },
15
16
  fetch: { default: 20, min: 1, max: 45 },
16
17
  lsp: { default: 20, min: 5, max: 300 },
package/src/tools/xdev.ts CHANGED
@@ -36,6 +36,7 @@ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
36
36
  import { XD_URL_PREFIX } from "../internal-urls/xd-protocol";
37
37
  import type { Theme } from "../modes/theme/theme";
38
38
  import { truncateHeadBytes } from "../session/streaming-output";
39
+ import { resolveToolTier, type ToolTier } from "./approval";
39
40
  import { renderDefaultToolExecution } from "./default-renderer";
40
41
  import type { Tool } from "./index";
41
42
  import { replaceTabs } from "./render-utils";
@@ -88,6 +89,13 @@ export interface XdevDispatch {
88
89
  mode: "help" | "execute";
89
90
  /** Validated inner args, kept for renderer delegation on result rebuilds. */
90
91
  args?: Record<string, unknown>;
92
+ /**
93
+ * Approval tier of the wrapped tool for {@link args} (`read` = no workspace
94
+ * mutation). Absent for `help` dispatches and calls whose tier could not be
95
+ * resolved. Consumed by the prewalk coordinator to skip read-only device
96
+ * calls when deciding the model hand-off (issue #7312).
97
+ */
98
+ tier?: ToolTier;
91
99
  /** Details object returned by the wrapped tool, when executed. */
92
100
  inner?: unknown;
93
101
  }
@@ -415,7 +423,18 @@ export async function dispatchXdevTool(
415
423
  }
416
424
 
417
425
  const validated = parseDeviceArgs(canonical as AiTool, content, toolCallId, () => renderDocs(canonical));
418
- xdev = { ...xdev, args: validated };
426
+ // Record the wrapped tool's approval tier so the prewalk coordinator can
427
+ // tell a read-only device call (e.g. `lsp` navigation) from a real
428
+ // workspace mutation without re-decoding the payload. Best-effort: a
429
+ // throwing approval leaves the tier absent (prewalk then declines to
430
+ // switch), unlike the write gate which fails closed to `exec`.
431
+ let tier: ToolTier | undefined;
432
+ try {
433
+ tier = resolveToolTier(canonical, validated);
434
+ } catch {
435
+ tier = undefined;
436
+ }
437
+ xdev = { ...xdev, args: validated, tier };
419
438
  const innerOnUpdate: AgentToolUpdateCallback | undefined = onUpdate
420
439
  ? partial =>
421
440
  onUpdate({
@@ -425,7 +444,15 @@ export async function dispatchXdevTool(
425
444
  })
426
445
  : undefined;
427
446
  const executable = state.decorateExecution?.(canonical) ?? canonical;
428
- const result = await executable.execute(toolCallId, validated as never, signal, innerOnUpdate, context);
447
+ const executionContext = context
448
+ ? {
449
+ ...context,
450
+ xdevTierResolved: (effectiveTier: ToolTier) => {
451
+ xdev = { ...xdev, tier: effectiveTier };
452
+ },
453
+ }
454
+ : undefined;
455
+ const result = await executable.execute(toolCallId, validated as never, signal, innerOnUpdate, executionContext);
429
456
  return { result, xdev: { ...xdev, inner: result.details } };
430
457
  } catch (error) {
431
458
  if (
@@ -1,8 +1,14 @@
1
- import type { ClipboardImage } from "@oh-my-pi/pi-natives";
2
- import * as native from "@oh-my-pi/pi-natives";
3
- import { logger } from "@oh-my-pi/pi-utils";
1
+ import {
2
+ type ClipboardImage,
3
+ copyToClipboard as nativeCopyToClipboard,
4
+ readImageFromClipboard as nativeReadImageFromClipboard,
5
+ } from "@oh-my-pi/pi-natives/clipboard";
6
+ import * as logger from "@oh-my-pi/pi-utils/logger";
7
+ import { SUPPORTED_IMAGE_MIME_TYPES } from "@oh-my-pi/pi-utils/mime";
4
8
  import MAC_FILE_URL_SCRIPT from "./mac-file-urls.applescript" with { type: "text" };
5
9
 
10
+ type SpawnCaptureOptions = { input?: string; timeoutMs?: number };
11
+
6
12
  /**
7
13
  * Run a subprocess and capture its stdout without blocking the event loop.
8
14
  *
@@ -10,15 +16,20 @@ import MAC_FILE_URL_SCRIPT from "./mac-file-urls.applescript" with { type: "text
10
16
  * path all shell out to CLI clipboard tools. The synchronous `execSync` API
11
17
  * parks the render loop until the child exits or the timeout fires, so a hung
12
18
  * clipboard daemon freezes the TUI for the full 2000ms budget (#4235). This
13
- * helper mirrors the previous semantics — read stdout as UTF-8, throw on
14
- * non-zero exit or timeout, forward optional stdin — but yields to the event
15
- * loop while the child runs.
19
+ * helper mirrors the previous semantics — capture stdout, throw on non-zero
20
+ * exit or timeout, forward optional stdin — but yields to the event loop while
21
+ * the child runs.
16
22
  *
17
23
  * @throws Error when the child fails to spawn, is killed by the timeout, or
18
- * exits with a non-zero status. Callers rely on this to fall through to the
19
- * outer catch and return an empty string / empty list.
24
+ * exits with a non-zero status. Callers rely on this to use platform
25
+ * fallbacks or report an empty clipboard.
20
26
  */
21
- async function spawnCapture(cmd: string[], options: { input?: string; timeoutMs?: number } = {}): Promise<string> {
27
+ async function spawnCapture(cmd: string[], options: SpawnCaptureOptions & { encoding: "bytes" }): Promise<Uint8Array>;
28
+ async function spawnCapture(cmd: string[], options?: SpawnCaptureOptions): Promise<string>;
29
+ async function spawnCapture(
30
+ cmd: string[],
31
+ options: SpawnCaptureOptions & { encoding?: "bytes" } = {},
32
+ ): Promise<string | Uint8Array> {
22
33
  const timeoutMs = options.timeoutMs ?? 2000;
23
34
  const proc = Bun.spawn(cmd, {
24
35
  stdout: "pipe",
@@ -31,7 +42,9 @@ async function spawnCapture(cmd: string[], options: { input?: string; timeoutMs?
31
42
  proc.kill();
32
43
  }, timeoutMs);
33
44
  try {
34
- const stdout = await new Response(proc.stdout).text();
45
+ const response = new Response(proc.stdout);
46
+ const stdout =
47
+ options.encoding === "bytes" ? new Uint8Array(await response.arrayBuffer()) : await response.text();
35
48
  await proc.exited;
36
49
  if (timedOut) {
37
50
  throw new Error(`${cmd[0]} timed out after ${timeoutMs}ms`);
@@ -125,7 +138,7 @@ export async function copyToClipboard(text: string): Promise<void> {
125
138
  }
126
139
  }
127
140
 
128
- await native.copyToClipboard(text);
141
+ await nativeCopyToClipboard(text);
129
142
  } catch {
130
143
  // Ignore — clipboard copy is best-effort
131
144
  }
@@ -255,7 +268,7 @@ async function readTextViaPowerShell(): Promise<string | null> {
255
268
  * because terminal clipboard paths can leave image payloads invisible to the
256
269
  * native bridge.
257
270
  *
258
- * @returns PNG payload or null when no image is available.
271
+ * @returns A supported image payload or null when no image is available.
259
272
  */
260
273
  export async function readImageFromClipboard(): Promise<ClipboardImage | null> {
261
274
  if (process.env.TERMUX_VERSION) {
@@ -272,7 +285,7 @@ export async function readImageFromClipboard(): Promise<ClipboardImage | null> {
272
285
 
273
286
  if (process.platform === "win32") {
274
287
  try {
275
- const image = await native.readImageFromClipboard();
288
+ const image = await nativeReadImageFromClipboard();
276
289
  if (image) return image;
277
290
  } catch (err) {
278
291
  logger.warn("clipboard: native Windows image read failed", { error: String(err) });
@@ -280,11 +293,24 @@ export async function readImageFromClipboard(): Promise<ClipboardImage | null> {
280
293
  return await readImageViaPowerShell();
281
294
  }
282
295
 
296
+ if (process.platform === "linux" && process.env.WAYLAND_DISPLAY) {
297
+ try {
298
+ const offeredMimeTypes = new Set((await spawnCapture(["wl-paste", "--list-types"])).split(/\r?\n/));
299
+ for (const mimeType of SUPPORTED_IMAGE_MIME_TYPES) {
300
+ if (!offeredMimeTypes.has(mimeType)) continue;
301
+ const data = await spawnCapture(["wl-paste", "--type", mimeType], { encoding: "bytes" });
302
+ if (data.byteLength > 0) return { data, mimeType };
303
+ }
304
+ } catch {
305
+ // Fall through when wl-clipboard is absent or no advertised image payload can be read.
306
+ }
307
+ }
308
+
283
309
  if (!hasDisplay()) {
284
310
  return null;
285
311
  }
286
312
 
287
- return (await native.readImageFromClipboard()) ?? null;
313
+ return (await nativeReadImageFromClipboard()) ?? null;
288
314
  }
289
315
 
290
316
  /**
@@ -39,38 +39,36 @@ export function parseCommandArgs(argsString: string): string[] {
39
39
  * containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.
40
40
  */
41
41
  export function substituteArgs(content: string, args: string[]): string {
42
- let result = content;
43
-
44
- // Replace $1, $2, etc. with positional args FIRST (before wildcards)
45
- // This prevents wildcard replacement values containing $<digit> patterns from being re-substituted
46
- result = result.replace(/\$(\d+)/g, (_, num) => {
47
- const index = parseInt(num, 10) - 1;
48
- return args[index] ?? "";
49
- });
50
-
51
- result = result.replace(/\$@\[(\d+)(?::(\d*)?)?\]/g, (_, startRaw: string, lengthRaw?: string) => {
52
- const start = Number.parseInt(startRaw, 10);
53
- if (!Number.isFinite(start) || start < 1) return "";
54
- const startIndex = start - 1;
55
- if (startIndex >= args.length) return "";
42
+ const allArgs = args.join(" ");
56
43
 
57
- if (lengthRaw === undefined || lengthRaw === "") {
58
- return args.slice(startIndex).join(" ");
59
- }
44
+ return content.replace(
45
+ /\$@\[(\d+)(?::(\d*)?)?\]|\$ARGUMENTS|\$@|\$(\d+)/g,
46
+ (match, startRaw?: string, lengthRaw?: string, positionalNum?: string) => {
47
+ if (positionalNum !== undefined) {
48
+ const index = Number.parseInt(positionalNum, 10) - 1;
49
+ return args[index] ?? "";
50
+ }
60
51
 
61
- const length = Number.parseInt(lengthRaw, 10);
62
- if (!Number.isFinite(length) || length <= 0) return "";
63
- return args.slice(startIndex, startIndex + length).join(" ");
64
- });
52
+ if (startRaw !== undefined) {
53
+ const start = Number.parseInt(startRaw, 10);
54
+ if (!Number.isFinite(start) || start < 1) return "";
55
+ const startIndex = start - 1;
56
+ if (startIndex >= args.length) return "";
65
57
 
66
- // Pre-compute all args joined (optimization)
67
- const allArgs = args.join(" ");
58
+ if (lengthRaw === undefined || lengthRaw === "") {
59
+ return args.slice(startIndex).join(" ");
60
+ }
68
61
 
69
- // Replace $ARGUMENTS with all args joined (new syntax, aligns with Claude, Codex, OpenCode)
70
- result = result.replaceAll("$ARGUMENTS", allArgs);
62
+ const length = Number.parseInt(lengthRaw, 10);
63
+ if (!Number.isFinite(length) || length <= 0) return "";
64
+ return args.slice(startIndex, startIndex + length).join(" ");
65
+ }
71
66
 
72
- // Replace $@ with all args joined (existing syntax)
73
- result = result.replaceAll("$@", allArgs);
67
+ if (match === "$ARGUMENTS" || match === "$@") {
68
+ return allArgs;
69
+ }
74
70
 
75
- return result;
71
+ return match;
72
+ },
73
+ );
76
74
  }
@@ -12,19 +12,6 @@ export function buildNamedToolChoice(toolName: string, model?: Model<Api>): Tool
12
12
  return { type: "tool", name: toolName };
13
13
  }
14
14
 
15
- if (toolName === "computer" && model.supportsComputerUse === true) {
16
- if (
17
- model.api === "openai-codex-responses" ||
18
- model.api === "openai-responses" ||
19
- model.api === "azure-openai-responses"
20
- ) {
21
- return { type: "computer" };
22
- }
23
- return undefined;
24
- }
25
- // Models without native computer support receive the tool as a regular
26
- // function tool, so the generic named-function forcing below applies.
27
-
28
15
  if (
29
16
  model.api === "openai-codex-responses" ||
30
17
  model.api === "openai-responses" ||
package/src/web/kagi.ts CHANGED
@@ -156,6 +156,7 @@ export interface KagiSearchOptions {
156
156
  recency?: "day" | "week" | "month" | "year";
157
157
  sessionId?: string;
158
158
  signal?: AbortSignal;
159
+ timeoutMs?: number;
159
160
  fetch?: FetchImpl;
160
161
  }
161
162
 
@@ -253,7 +254,7 @@ export async function searchWithKagi(
253
254
  Accept: "application/json",
254
255
  },
255
256
  body,
256
- signal: withHardTimeout(options.signal),
257
+ signal: withHardTimeout(options.signal, options.timeoutMs),
257
258
  });
258
259
 
259
260
  if (!res.ok) {
@@ -29,8 +29,13 @@ import {
29
29
  } from "./provider";
30
30
  import { applyQueryConstraints, parseSearchQuery } from "./query";
31
31
  import { renderSearchCall, renderSearchResult, type SearchRenderDetails } from "./render";
32
- import type { SearchProviderId, SearchResponse } from "./types";
33
- import { SearchProviderError } from "./types";
32
+ import {
33
+ DEFAULT_WEB_SEARCH_TIMEOUT_SECONDS,
34
+ MAX_WEB_SEARCH_TIMEOUT_SECONDS,
35
+ SearchProviderError,
36
+ type SearchProviderId,
37
+ type SearchResponse,
38
+ } from "./types";
34
39
 
35
40
  /** Web search tool parameters schema */
36
41
  export const webSearchSchema = type({
@@ -164,6 +169,16 @@ async function executeSearch(
164
169
  geminiModel = undefined;
165
170
  }
166
171
 
172
+ let timeoutMs = DEFAULT_WEB_SEARCH_TIMEOUT_SECONDS * 1_000;
173
+ try {
174
+ const configuredSeconds = settings.get("providers.webSearchTimeoutSeconds");
175
+ if (Number.isFinite(configuredSeconds) && configuredSeconds > 0) {
176
+ timeoutMs = Math.ceil(Math.min(configuredSeconds, MAX_WEB_SEARCH_TIMEOUT_SECONDS) * 1_000);
177
+ }
178
+ } catch {
179
+ // Preserve the default for one-shot callers that do not initialize Settings.
180
+ }
181
+
167
182
  const failures: Array<{ provider: Pick<SearchProvider, "id" | "label">; error: unknown }> = [];
168
183
  let availableProviderCount = 0;
169
184
  let lastProvider: Pick<SearchProvider, "id" | "label"> | undefined;
@@ -196,6 +211,7 @@ async function executeSearch(
196
211
  numSearchResults: params.num_search_results,
197
212
  temperature: params.temperature,
198
213
  signal,
214
+ timeoutMs,
199
215
  authStorage,
200
216
  modelRegistry,
201
217
  sessionId,
@@ -19,6 +19,7 @@ import {
19
19
  withAuth,
20
20
  wrapFetchForCch,
21
21
  } from "@oh-my-pi/pi-ai";
22
+ import { hasOpus47ApiRestrictions } from "@oh-my-pi/pi-catalog/identity/family";
22
23
  import { $env } from "@oh-my-pi/pi-utils";
23
24
  import type {
24
25
  AnthropicApiResponse,
@@ -97,6 +98,7 @@ export interface AnthropicSearchParams {
97
98
  max_tokens?: number;
98
99
  temperature?: number;
99
100
  signal?: AbortSignal;
101
+ timeoutMs?: number;
100
102
  fetch?: FetchImpl;
101
103
  }
102
104
 
@@ -152,6 +154,7 @@ async function callSearch(
152
154
  temperature?: number,
153
155
  signal?: AbortSignal,
154
156
  fetchImpl: FetchImpl = fetch,
157
+ timeoutMs?: number,
155
158
  ): Promise<AnthropicApiResponse> {
156
159
  const url = buildAnthropicUrl(auth);
157
160
  const headers = buildAnthropicSearchHeaders(auth);
@@ -176,7 +179,8 @@ async function callSearch(
176
179
  body.metadata = { user_id: metadataUserId };
177
180
  }
178
181
 
179
- if (temperature !== undefined) {
182
+ // Opus 4.7+, Sonnet 5+, and Fable/Mythos 5 reject sampling parameters with a 400.
183
+ if (temperature !== undefined && !hasOpus47ApiRestrictions(model)) {
180
184
  body.temperature = temperature;
181
185
  }
182
186
 
@@ -191,7 +195,7 @@ async function callSearch(
191
195
  method: "POST",
192
196
  headers,
193
197
  body: JSON.stringify(body),
194
- signal: withHardTimeout(signal),
198
+ signal: withHardTimeout(signal, timeoutMs),
195
199
  });
196
200
 
197
201
  if (!response.ok) {
@@ -367,6 +371,7 @@ export async function searchAnthropic(
367
371
  params.temperature,
368
372
  params.signal,
369
373
  params.fetch,
374
+ params.timeoutMs,
370
375
  );
371
376
  },
372
377
  {
@@ -47,6 +47,8 @@ export interface SearchParams {
47
47
  recency?: "day" | "week" | "month" | "year";
48
48
  systemPrompt: string;
49
49
  signal?: AbortSignal;
50
+ /** Hard timeout for this provider's search transport, in milliseconds. */
51
+ timeoutMs?: number;
50
52
  fetch?: FetchImpl;
51
53
  maxOutputTokens?: number;
52
54
  numSearchResults?: number;
@@ -52,6 +52,7 @@ export interface BraveSearchParams {
52
52
  recency?: "day" | "week" | "month" | "year";
53
53
  parsedQuery?: StructuredQuery;
54
54
  signal?: AbortSignal;
55
+ timeoutMs?: number;
55
56
  fetch?: FetchImpl;
56
57
  }
57
58
 
@@ -113,7 +114,7 @@ async function callBraveSearch(
113
114
  Accept: "application/json",
114
115
  "X-Subscription-Token": apiKey,
115
116
  },
116
- signal: withHardTimeout(params.signal),
117
+ signal: withHardTimeout(params.signal, params.timeoutMs),
117
118
  });
118
119
 
119
120
  if (!response.ok) {
@@ -173,6 +174,7 @@ export class BraveProvider extends SearchProvider {
173
174
  recency: params.recency,
174
175
  parsedQuery: params.parsedQuery,
175
176
  signal: params.signal,
177
+ timeoutMs: params.timeoutMs,
176
178
  fetch: params.fetch,
177
179
  });
178
180
  }