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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/{CHANGELOG-bpmhv26t.md → CHANGELOG-gs76k6wc.md} +69 -0
  3. package/dist/cli.js +4438 -3962
  4. package/dist/{template-8vdv6xb4.js → template-c2hyaytt.js} +8 -26
  5. package/dist/types/cli/args.d.ts +4 -1
  6. package/dist/types/cli/browser-relay-cli.d.ts +15 -0
  7. package/dist/types/cli/command-help.d.ts +99 -0
  8. package/dist/types/cli/gc-cli.d.ts +1 -0
  9. package/dist/types/cli/help-extra.d.ts +2 -0
  10. package/dist/types/cli/thinking-levels.d.ts +5 -0
  11. package/dist/types/cli/update-cli.d.ts +13 -0
  12. package/dist/types/commands/browser-relay.d.ts +38 -0
  13. package/dist/types/commands/commit.d.ts +3 -0
  14. package/dist/types/commands/complete.d.ts +1 -1
  15. package/dist/types/commands/completions.d.ts +6 -0
  16. package/dist/types/commands/grep.d.ts +3 -0
  17. package/dist/types/commands/join.d.ts +4 -0
  18. package/dist/types/commands/launch-help.d.ts +171 -0
  19. package/dist/types/commands/launch.d.ts +5 -1
  20. package/dist/types/commands/models.d.ts +3 -0
  21. package/dist/types/commands/say.d.ts +10 -0
  22. package/dist/types/commands/worktree.d.ts +3 -0
  23. package/dist/types/config/keybindings.d.ts +5 -0
  24. package/dist/types/config/model-discovery.d.ts +2 -2
  25. package/dist/types/config/models-config-schema-bundle.d.ts +3 -0
  26. package/dist/types/config/models-config-schema.d.ts +2 -0
  27. package/dist/types/config/models-config.d.ts +1 -0
  28. package/dist/types/config/service-tier.d.ts +2 -0
  29. package/dist/types/config/settings-schema.d.ts +69 -21
  30. package/dist/types/cursor-bridge-tools.d.ts +1 -1
  31. package/dist/types/cursor.d.ts +7 -4
  32. package/dist/types/edit/index.d.ts +3 -3
  33. package/dist/types/edit/modes/replace.d.ts +22 -15
  34. package/dist/types/edit/renderer.d.ts +1 -1
  35. package/dist/types/eval/py/kernel.d.ts +3 -1
  36. package/dist/types/eval/py/spawn-options.d.ts +12 -16
  37. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +8 -1
  38. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
  39. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
  40. package/dist/types/launch/broker.d.ts +1 -1
  41. package/dist/types/launch/client.d.ts +15 -4
  42. package/dist/types/launch/ensure.d.ts +8 -0
  43. package/dist/types/launch/paths.d.ts +1 -1
  44. package/dist/types/launch/protocol.d.ts +19 -2
  45. package/dist/types/lsp/client.d.ts +2 -0
  46. package/dist/types/lsp/mux/daemon.d.ts +15 -0
  47. package/dist/types/lsp/mux/protocol.d.ts +57 -0
  48. package/dist/types/lsp/mux/server.d.ts +18 -0
  49. package/dist/types/lsp/types.d.ts +22 -2
  50. package/dist/types/main.d.ts +2 -0
  51. package/dist/types/mnemopi/embed-client.d.ts +1 -1
  52. package/dist/types/mnemopi/state.d.ts +0 -23
  53. package/dist/types/modes/components/assistant-message.d.ts +2 -0
  54. package/dist/types/modes/components/btw-panel.d.ts +3 -0
  55. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  56. package/dist/types/modes/components/read-tool-group.d.ts +2 -0
  57. package/dist/types/modes/components/stripped-tool-calls-placeholder.d.ts +12 -0
  58. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  59. package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
  60. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  61. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  62. package/dist/types/modes/interactive-mode.d.ts +5 -1
  63. package/dist/types/modes/types.d.ts +4 -1
  64. package/dist/types/modes/workflow.d.ts +2 -1
  65. package/dist/types/sdk.d.ts +3 -1
  66. package/dist/types/secrets/index.d.ts +1 -1
  67. package/dist/types/security/contracts/schemas.d.ts +405 -403
  68. package/dist/types/session/agent-session-types.d.ts +8 -1
  69. package/dist/types/session/agent-session.d.ts +26 -2
  70. package/dist/types/session/indexed-session-storage.d.ts +1 -0
  71. package/dist/types/session/launch-completion.d.ts +10 -0
  72. package/dist/types/session/session-advisors.d.ts +1 -1
  73. package/dist/types/session/session-context.d.ts +2 -1
  74. package/dist/types/session/session-entries.d.ts +15 -1
  75. package/dist/types/session/session-maintenance.d.ts +1 -0
  76. package/dist/types/session/session-manager.d.ts +23 -11
  77. package/dist/types/session/session-stats.d.ts +10 -0
  78. package/dist/types/session/session-storage.d.ts +15 -3
  79. package/dist/types/session/yield-queue.d.ts +6 -1
  80. package/dist/types/system-prompt.d.ts +4 -1
  81. package/dist/types/task/index.d.ts +2 -1
  82. package/dist/types/task/spawn-policy.d.ts +5 -0
  83. package/dist/types/thinking.d.ts +1 -7
  84. package/dist/types/tools/ast-grep.d.ts +1 -1
  85. package/dist/types/tools/browser/registry.d.ts +2 -1
  86. package/dist/types/tools/browser/relay/bridge.d.ts +35 -0
  87. package/dist/types/tools/browser/relay/daemon.d.ts +16 -0
  88. package/dist/types/tools/browser/relay/kind.d.ts +19 -0
  89. package/dist/types/tools/browser/relay/protocol.d.ts +97 -0
  90. package/dist/types/tools/browser/relay/server.d.ts +35 -0
  91. package/dist/types/tools/browser/render.d.ts +1 -0
  92. package/dist/types/tools/browser.d.ts +3 -0
  93. package/dist/types/tools/computer/exposure.d.ts +9 -7
  94. package/dist/types/tools/computer/protocol.d.ts +70 -17
  95. package/dist/types/tools/computer/supervisor.d.ts +23 -10
  96. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  97. package/dist/types/tools/computer/worker.d.ts +42 -7
  98. package/dist/types/tools/computer-renderer.d.ts +7 -5
  99. package/dist/types/tools/computer.d.ts +25 -44
  100. package/dist/types/tools/context.d.ts +3 -0
  101. package/dist/types/tools/glob.d.ts +1 -1
  102. package/dist/types/tools/grep.d.ts +1 -1
  103. package/dist/types/tools/index.d.ts +9 -0
  104. package/dist/types/tools/render-utils.d.ts +2 -0
  105. package/dist/types/tools/{browser/run-cancellation.d.ts → run-scope.d.ts} +8 -8
  106. package/dist/types/tools/tool-timeouts.d.ts +5 -0
  107. package/dist/types/tools/xdev.d.ts +8 -0
  108. package/dist/types/utils/clipboard.d.ts +2 -2
  109. package/dist/types/web/kagi.d.ts +1 -0
  110. package/dist/types/web/search/index.d.ts +1 -1
  111. package/dist/types/web/search/providers/anthropic.d.ts +1 -0
  112. package/dist/types/web/search/providers/base.d.ts +2 -0
  113. package/dist/types/web/search/providers/brave.d.ts +1 -0
  114. package/dist/types/web/search/providers/browser-page.d.ts +1 -0
  115. package/dist/types/web/search/providers/codex.d.ts +1 -0
  116. package/dist/types/web/search/providers/exa.d.ts +1 -0
  117. package/dist/types/web/search/providers/firecrawl.d.ts +1 -0
  118. package/dist/types/web/search/providers/gemini.d.ts +1 -0
  119. package/dist/types/web/search/providers/jina.d.ts +1 -0
  120. package/dist/types/web/search/providers/kagi.d.ts +1 -0
  121. package/dist/types/web/search/providers/kimi.d.ts +1 -0
  122. package/dist/types/web/search/providers/parallel.d.ts +1 -0
  123. package/dist/types/web/search/providers/perplexity.d.ts +1 -0
  124. package/dist/types/web/search/providers/searxng.d.ts +1 -0
  125. package/dist/types/web/search/providers/tavily.d.ts +1 -0
  126. package/dist/types/web/search/providers/tinyfish.d.ts +1 -0
  127. package/dist/types/web/search/providers/utils.d.ts +6 -9
  128. package/dist/types/web/search/providers/zai.d.ts +1 -0
  129. package/dist/types/web/search/types.d.ts +4 -0
  130. package/package.json +12 -12
  131. package/src/cli/args.ts +7 -87
  132. package/src/cli/browser-relay-cli.ts +119 -0
  133. package/src/cli/command-help.ts +103 -0
  134. package/src/cli/completion-gen.ts +5 -5
  135. package/src/cli/flag-tables.ts +9 -0
  136. package/src/cli/gc-cli.ts +641 -21
  137. package/src/cli/help-extra.ts +89 -0
  138. package/src/cli/setup-cli.ts +13 -21
  139. package/src/cli/thinking-levels.ts +7 -0
  140. package/src/cli/update-cli.ts +26 -7
  141. package/src/cli-commands.ts +170 -33
  142. package/src/cli.ts +17 -5
  143. package/src/commands/acp.ts +3 -1
  144. package/src/commands/agents.ts +3 -2
  145. package/src/commands/auth-broker.ts +3 -2
  146. package/src/commands/auth-gateway.ts +3 -2
  147. package/src/commands/bench.ts +2 -3
  148. package/src/commands/browser-relay.ts +53 -0
  149. package/src/commands/cleanse.ts +2 -2
  150. package/src/commands/commit.ts +3 -2
  151. package/src/commands/complete.ts +2 -1
  152. package/src/commands/completions.ts +3 -2
  153. package/src/commands/config.ts +3 -2
  154. package/src/commands/dry-balance.ts +2 -2
  155. package/src/commands/gallery.ts +3 -2
  156. package/src/commands/gc.ts +3 -2
  157. package/src/commands/grep.ts +3 -2
  158. package/src/commands/grievances.ts +3 -2
  159. package/src/commands/install.ts +2 -2
  160. package/src/commands/join.ts +3 -2
  161. package/src/commands/launch-help.ts +116 -0
  162. package/src/commands/launch.ts +7 -191
  163. package/src/commands/models.ts +3 -2
  164. package/src/commands/plugin.ts +3 -2
  165. package/src/commands/read.ts +3 -2
  166. package/src/commands/say.ts +3 -2
  167. package/src/commands/setup.ts +3 -2
  168. package/src/commands/shell.ts +3 -2
  169. package/src/commands/ssh.ts +3 -2
  170. package/src/commands/stats.ts +3 -2
  171. package/src/commands/tiny-models.ts +2 -2
  172. package/src/commands/token.ts +2 -2
  173. package/src/commands/ttsr.ts +2 -2
  174. package/src/commands/update.ts +3 -2
  175. package/src/commands/usage.ts +3 -2
  176. package/src/commands/web-search.ts +3 -2
  177. package/src/commands/worktree.ts +3 -2
  178. package/src/config/keybindings.ts +5 -0
  179. package/src/config/model-discovery.ts +44 -27
  180. package/src/config/model-registry.ts +19 -13
  181. package/src/config/models-config-schema-bundle.ts +9 -0
  182. package/src/config/resolve-config-value.ts +2 -1
  183. package/src/config/service-tier.ts +5 -0
  184. package/src/config/settings-schema.ts +72 -20
  185. package/src/config/settings.ts +1 -1
  186. package/src/cursor-bridge-tools.ts +1 -1
  187. package/src/cursor.ts +10 -14
  188. package/src/edit/diff.ts +17 -8
  189. package/src/edit/index.ts +20 -7
  190. package/src/edit/modes/replace.ts +93 -40
  191. package/src/edit/renderer.ts +1 -1
  192. package/src/edit/streaming.ts +14 -17
  193. package/src/eval/py/kernel.ts +2 -1
  194. package/src/eval/py/spawn-options.ts +24 -29
  195. package/src/export/html/index.ts +10 -3
  196. package/src/export/html/template.js +8 -26
  197. package/src/export/share.ts +4 -0
  198. package/src/extensibility/extensions/wrapper.ts +1 -0
  199. package/src/extensibility/legacy-pi-ai-shim.ts +22 -1
  200. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -6
  201. package/src/extensibility/plugins/legacy-pi-compat.ts +124 -57
  202. package/src/extensibility/skills.ts +11 -0
  203. package/src/extensibility/utils.ts +10 -2
  204. package/src/launch/broker.ts +223 -7
  205. package/src/launch/client.ts +192 -21
  206. package/src/launch/ensure.ts +73 -0
  207. package/src/launch/paths.ts +1 -1
  208. package/src/launch/protocol.ts +54 -2
  209. package/src/lsp/client.ts +23 -2
  210. package/src/lsp/index.ts +8 -0
  211. package/src/lsp/mux/daemon.ts +348 -0
  212. package/src/lsp/mux/protocol.ts +96 -0
  213. package/src/lsp/mux/server.ts +797 -0
  214. package/src/lsp/types.ts +28 -2
  215. package/src/main.ts +20 -8
  216. package/src/mcp/config-writer.ts +1 -1
  217. package/src/mcp/oauth-flow.ts +11 -1
  218. package/src/mnemopi/embed-client.ts +49 -2
  219. package/src/mnemopi/state.ts +57 -23
  220. package/src/modes/components/assistant-message.ts +11 -0
  221. package/src/modes/components/btw-panel.ts +41 -4
  222. package/src/modes/components/chat-transcript-builder.ts +4 -0
  223. package/src/modes/components/custom-editor.ts +9 -0
  224. package/src/modes/components/read-tool-group.ts +10 -0
  225. package/src/modes/components/status-line/component.ts +1 -1
  226. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  227. package/src/modes/components/tool-execution.ts +8 -0
  228. package/src/modes/components/tree-selector.ts +46 -48
  229. package/src/modes/controllers/btw-controller.ts +55 -7
  230. package/src/modes/controllers/command-controller.ts +31 -0
  231. package/src/modes/controllers/event-controller.ts +17 -0
  232. package/src/modes/controllers/input-controller.ts +63 -8
  233. package/src/modes/controllers/mcp-command-controller.ts +11 -5
  234. package/src/modes/controllers/selector-controller.ts +20 -0
  235. package/src/modes/interactive-mode.ts +18 -2
  236. package/src/modes/types.ts +9 -1
  237. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  238. package/src/modes/utils/interactive-context-helpers.ts +1 -0
  239. package/src/modes/utils/ui-helpers.ts +6 -10
  240. package/src/modes/workflow.ts +8 -2
  241. package/src/prompts/agents/init.md +1 -1
  242. package/src/prompts/session/launch-completion.md +1 -0
  243. package/src/prompts/system/plan-mode-active.md +2 -2
  244. package/src/prompts/system/system-prompt.md +26 -52
  245. package/src/prompts/system/workflow-notice.md +3 -3
  246. package/src/prompts/tools/ast-grep.md +1 -1
  247. package/src/prompts/tools/bash.md +1 -1
  248. package/src/prompts/tools/browser.md +1 -0
  249. package/src/prompts/tools/computer.md +21 -21
  250. package/src/prompts/tools/glob.md +1 -1
  251. package/src/prompts/tools/grep.md +1 -1
  252. package/src/prompts/tools/replace.md +5 -4
  253. package/src/prompts/tools/task.md +4 -4
  254. package/src/sdk.ts +49 -6
  255. package/src/secrets/index.ts +1 -1
  256. package/src/security/contracts/schemas.ts +205 -183
  257. package/src/security/contracts/validation.ts +5 -1
  258. package/src/security/store.ts +2 -2
  259. package/src/session/agent-session-types.ts +9 -1
  260. package/src/session/agent-session.ts +204 -15
  261. package/src/session/indexed-session-storage.ts +9 -0
  262. package/src/session/launch-completion.ts +37 -0
  263. package/src/session/prewalk.ts +24 -2
  264. package/src/session/session-advisors.ts +2 -2
  265. package/src/session/session-context.ts +42 -12
  266. package/src/session/session-dump-format.ts +1 -1
  267. package/src/session/session-entries.ts +17 -1
  268. package/src/session/session-maintenance.ts +38 -4
  269. package/src/session/session-manager.ts +123 -67
  270. package/src/session/session-paths.ts +118 -55
  271. package/src/session/session-stats.ts +48 -3
  272. package/src/session/session-storage.ts +30 -22
  273. package/src/session/stream-guards.ts +1 -1
  274. package/src/session/yield-queue.ts +121 -16
  275. package/src/slash-commands/builtin-registry.ts +37 -24
  276. package/src/system-prompt.ts +8 -3
  277. package/src/task/index.ts +23 -8
  278. package/src/task/spawn-policy.ts +14 -0
  279. package/src/thinking.ts +2 -8
  280. package/src/tools/ast-grep.ts +10 -4
  281. package/src/tools/browser/cmux/cmux-tab.ts +97 -14
  282. package/src/tools/browser/cmux/rpc.ts +2 -8
  283. package/src/tools/browser/registry.ts +53 -2
  284. package/src/tools/browser/relay/bridge.ts +945 -0
  285. package/src/tools/browser/relay/daemon.ts +117 -0
  286. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  287. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  288. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  289. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  290. package/src/tools/browser/relay/kind.ts +41 -0
  291. package/src/tools/browser/relay/protocol.ts +54 -0
  292. package/src/tools/browser/relay/server.ts +141 -0
  293. package/src/tools/browser/render.ts +4 -1
  294. package/src/tools/browser/shared-daemon.ts +7 -58
  295. package/src/tools/browser/tab-supervisor.ts +5 -4
  296. package/src/tools/browser/tab-worker.ts +33 -12
  297. package/src/tools/browser.ts +26 -1
  298. package/src/tools/computer/exposure.ts +11 -35
  299. package/src/tools/computer/protocol.ts +51 -10
  300. package/src/tools/computer/supervisor.ts +227 -124
  301. package/src/tools/computer/worker-entry.ts +11 -6
  302. package/src/tools/computer/worker.ts +695 -81
  303. package/src/tools/computer-renderer.ts +104 -65
  304. package/src/tools/computer.ts +134 -428
  305. package/src/tools/context.ts +3 -0
  306. package/src/tools/glob.ts +9 -2
  307. package/src/tools/grep.ts +12 -6
  308. package/src/tools/hub/launch.ts +122 -6
  309. package/src/tools/index.ts +9 -0
  310. package/src/tools/inspect-image.ts +32 -6
  311. package/src/tools/render-utils.ts +2 -0
  312. package/src/tools/{browser/run-cancellation.ts → run-scope.ts} +11 -11
  313. package/src/tools/tool-timeouts.ts +1 -0
  314. package/src/tools/xdev.ts +29 -2
  315. package/src/utils/clipboard.ts +40 -14
  316. package/src/utils/command-args.ts +26 -28
  317. package/src/utils/tool-choice.ts +0 -13
  318. package/src/web/kagi.ts +2 -1
  319. package/src/web/search/index.ts +18 -2
  320. package/src/web/search/providers/anthropic.ts +4 -1
  321. package/src/web/search/providers/base.ts +2 -0
  322. package/src/web/search/providers/brave.ts +3 -1
  323. package/src/web/search/providers/browser-page.ts +6 -4
  324. package/src/web/search/providers/codex.ts +4 -1
  325. package/src/web/search/providers/duckduckgo.ts +2 -1
  326. package/src/web/search/providers/ecosia.ts +2 -1
  327. package/src/web/search/providers/exa.ts +4 -2
  328. package/src/web/search/providers/firecrawl.ts +3 -1
  329. package/src/web/search/providers/gemini.ts +8 -2
  330. package/src/web/search/providers/google.ts +2 -1
  331. package/src/web/search/providers/jina.ts +12 -2
  332. package/src/web/search/providers/kagi.ts +3 -0
  333. package/src/web/search/providers/kimi.ts +5 -1
  334. package/src/web/search/providers/mojeek.ts +2 -1
  335. package/src/web/search/providers/parallel.ts +5 -1
  336. package/src/web/search/providers/perplexity.ts +6 -3
  337. package/src/web/search/providers/public.ts +1 -1
  338. package/src/web/search/providers/searxng.ts +11 -5
  339. package/src/web/search/providers/startpage.ts +10 -4
  340. package/src/web/search/providers/synthetic.ts +11 -5
  341. package/src/web/search/providers/tavily.ts +3 -1
  342. package/src/web/search/providers/tinyfish.ts +3 -1
  343. package/src/web/search/providers/utils.ts +12 -10
  344. package/src/web/search/providers/xai.ts +2 -2
  345. package/src/web/search/providers/zai.ts +18 -3
  346. package/src/web/search/types.ts +6 -0
  347. package/dist/types/config/file-lock.d.ts +0 -29
  348. package/src/config/file-lock.ts +0 -164
@@ -121,10 +121,14 @@ function parseHtmlResults(html: string): ParsedResult[] {
121
121
  * any failure (network, non-OK status, challenge shell, markup drift) yields
122
122
  * `undefined` and the caller falls back to a direct GET.
123
123
  */
124
- async function fetchFormInputs(fetchImpl: FetchImpl, signal: AbortSignal): Promise<Record<string, string> | undefined> {
124
+ async function fetchFormInputs(
125
+ fetchImpl: FetchImpl,
126
+ signal: AbortSignal,
127
+ timeoutMs?: number,
128
+ ): Promise<Record<string, string> | undefined> {
125
129
  let page: LoadedHtmlPage;
126
130
  try {
127
- page = await browserFetch(STARTPAGE_HOME_URL, { fetch: fetchImpl, signal });
131
+ page = await browserFetch(STARTPAGE_HOME_URL, { fetch: fetchImpl, signal, timeoutMs });
128
132
  } catch (error) {
129
133
  if (signal.aborted) throw error;
130
134
  return undefined;
@@ -135,7 +139,7 @@ async function fetchFormInputs(fetchImpl: FetchImpl, signal: AbortSignal): Promi
135
139
 
136
140
  async function callStartpageHtml(params: SearchParams): Promise<string> {
137
141
  const fetchImpl = params.fetch ?? fetch;
138
- const signal = withHardTimeout(params.signal);
142
+ const signal = withHardTimeout(params.signal, params.timeoutMs);
139
143
  const withDate = params.recency ? RECENCY_TO_STARTPAGE_WITH_DATE[params.recency] : undefined;
140
144
  // Startpage proxies Google, so the operator set works inline; rebuild via
141
145
  // the shared scraper formatter to canonicalize aliases (domain: → site:,
@@ -143,7 +147,7 @@ async function callStartpageHtml(params: SearchParams): Promise<string> {
143
147
  // queries pass through byte-identical.
144
148
  const query = formatScraperQuery(params.query, params.parsedQuery);
145
149
 
146
- const formInputs = await fetchFormInputs(fetchImpl, signal);
150
+ const formInputs = await fetchFormInputs(fetchImpl, signal, params.timeoutMs);
147
151
  let page: LoadedHtmlPage;
148
152
  if (formInputs) {
149
153
  const form = new URLSearchParams(formInputs);
@@ -152,6 +156,7 @@ async function callStartpageHtml(params: SearchParams): Promise<string> {
152
156
  page = await browserFetch(STARTPAGE_SEARCH_URL, {
153
157
  fetch: fetchImpl,
154
158
  signal,
159
+ timeoutMs: params.timeoutMs,
155
160
  referer: STARTPAGE_HOME_URL,
156
161
  init: { method: "POST", body: form.toString() },
157
162
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
@@ -163,6 +168,7 @@ async function callStartpageHtml(params: SearchParams): Promise<string> {
163
168
  page = await browserFetch(url.href, {
164
169
  fetch: fetchImpl,
165
170
  signal,
171
+ timeoutMs: params.timeoutMs,
166
172
  referer: STARTPAGE_HOME_URL,
167
173
  });
168
174
  }
@@ -43,6 +43,7 @@ async function callSyntheticSearch(
43
43
  query: string,
44
44
  signal?: AbortSignal,
45
45
  fetchImpl: FetchImpl = fetch,
46
+ timeoutMs?: number,
46
47
  ): Promise<SyntheticSearchResponse> {
47
48
  const response = await fetchImpl(SYNTHETIC_SEARCH_URL, {
48
49
  method: "POST",
@@ -51,7 +52,7 @@ async function callSyntheticSearch(
51
52
  Authorization: `Bearer ${apiKey}`,
52
53
  },
53
54
  body: JSON.stringify({ query }),
54
- signal: withHardTimeout(signal),
55
+ signal: withHardTimeout(signal, timeoutMs),
55
56
  });
56
57
 
57
58
  if (!response.ok) {
@@ -80,10 +81,15 @@ export async function searchSynthetic(params: SearchParamsWithFetch): Promise<Se
80
81
  : params.query;
81
82
 
82
83
  const fetchImpl = params.fetch;
83
- const data = await withAuth(keyOrResolver, key => callSyntheticSearch(key, query, params.signal, fetchImpl), {
84
- signal: params.signal,
85
- missingKeyMessage: "Synthetic credentials not found. Set SYNTHETIC_API_KEY or login with 'omp /login synthetic'.",
86
- });
84
+ const data = await withAuth(
85
+ keyOrResolver,
86
+ key => callSyntheticSearch(key, query, params.signal, fetchImpl, params.timeoutMs),
87
+ {
88
+ signal: params.signal,
89
+ missingKeyMessage:
90
+ "Synthetic credentials not found. Set SYNTHETIC_API_KEY or login with 'omp /login synthetic'.",
91
+ },
92
+ );
87
93
  const sources: SearchSource[] = [];
88
94
 
89
95
  for (const result of data.results ?? []) {
@@ -30,6 +30,7 @@ export interface TavilySearchParams {
30
30
  /** `before:` upper bound, ISO `YYYY-MM-DD`, mapped to `end_date`. */
31
31
  end_date?: string;
32
32
  signal?: AbortSignal;
33
+ timeoutMs?: number;
33
34
  fetch?: FetchImpl;
34
35
  }
35
36
 
@@ -120,7 +121,7 @@ async function callTavilySearch(apiKey: string, params: TavilySearchParams): Pro
120
121
  Authorization: `Bearer ${apiKey}`,
121
122
  },
122
123
  body: JSON.stringify(buildRequestBody(params)),
123
- signal: withHardTimeout(params.signal),
124
+ signal: withHardTimeout(params.signal, params.timeoutMs),
124
125
  });
125
126
 
126
127
  if (!response.ok) {
@@ -189,6 +190,7 @@ export async function searchTavily(params: SearchParams): Promise<SearchResponse
189
190
  num_results: params.numSearchResults ?? params.limit,
190
191
  recency: params.recency,
191
192
  signal: params.signal,
193
+ timeoutMs: params.timeoutMs,
192
194
  fetch: params.fetch,
193
195
  };
194
196
  if (parsed.hasDirectives) {
@@ -34,6 +34,7 @@ export interface TinyFishSearchParams {
34
34
  recency?: SearchParams["recency"];
35
35
  page?: number;
36
36
  signal?: AbortSignal;
37
+ timeoutMs?: number;
37
38
  fetch?: FetchImpl;
38
39
  }
39
40
 
@@ -78,7 +79,7 @@ async function callTinyFishSearch(apiKey: string, params: TinyFishSearchParams):
78
79
  Accept: "application/json",
79
80
  "X-API-Key": apiKey,
80
81
  },
81
- signal: withHardTimeout(params.signal),
82
+ signal: withHardTimeout(params.signal, params.timeoutMs),
82
83
  });
83
84
 
84
85
  if (!response.ok) {
@@ -117,6 +118,7 @@ export async function searchTinyFish(params: SearchParams): Promise<SearchRespon
117
118
  num_results: pageSize,
118
119
  recency: params.recency,
119
120
  signal: params.signal,
121
+ timeoutMs: params.timeoutMs,
120
122
  fetch: params.fetch,
121
123
  };
122
124
  const keyOrResolver: ApiKey = params.authStorage.resolver("tinyfish", {
@@ -1,5 +1,10 @@
1
1
  import type { AgentStorage } from "../../../session/agent-storage";
2
- import { SearchProviderError, type SearchProviderId, type SearchSource } from "../../../web/search/types";
2
+ import {
3
+ DEFAULT_WEB_SEARCH_TIMEOUT_SECONDS,
4
+ SearchProviderError,
5
+ type SearchProviderId,
6
+ type SearchSource,
7
+ } from "../../../web/search/types";
3
8
  import { dateToAgeSeconds } from "../utils";
4
9
 
5
10
  /**
@@ -44,16 +49,13 @@ export function findCredential(
44
49
  }
45
50
 
46
51
  /**
47
- * Default hard ceiling for a single web-search round-trip. 60s tolerates
48
- * legitimate slow LLM-mediated responses (anthropic web_search_20250305,
49
- * perplexity, gemini, codex) while still guaranteeing the session unfreezes
50
- * within a minute if Bun's `AbortSignal` fails to propagate on Windows.
51
- *
52
- * Pure search APIs (brave, exa, jina, tavily, searxng, synthetic, zai)
53
- * settle far faster in practice; reusing the same ceiling keeps the wiring
54
- * uniform without compromising correctness.
52
+ * The 60-second default tolerates legitimate slow LLM-mediated responses
53
+ * (Anthropic web_search_20250305, Perplexity, Gemini, Codex) while bounding
54
+ * Windows stalls when Bun's `AbortSignal` fails to propagate. Callers may
55
+ * configure a longer provider deadline, capped at five minutes by the
56
+ * dispatcher; pure search APIs typically settle far faster.
55
57
  */
56
- export const SEARCH_HARD_TIMEOUT_MS = 60_000;
58
+ export const SEARCH_HARD_TIMEOUT_MS = DEFAULT_WEB_SEARCH_TIMEOUT_SECONDS * 1_000;
57
59
 
58
60
  /**
59
61
  * Compose a caller-supplied {@link AbortSignal} with a hard timeout so an
@@ -14,7 +14,7 @@ const XAI_WEB_SEARCH_MODEL = "grok-4.5";
14
14
  // grok-4.5 defaults reasoning.effort to "high"; xAI documents "low" for
15
15
  // latency-sensitive agentic use and simple tool calling
16
16
  // (docs.x.ai/developers/model-capabilities/text/reasoning). Web search is
17
- // exactly that and runs under a 60s hard timeout, so pin the search calls low.
17
+ // latency-sensitive, so pin these calls low regardless of their configured timeout.
18
18
  const XAI_WEB_SEARCH_REASONING_EFFORT = "low";
19
19
  const DEFAULT_NUM_RESULTS = 10;
20
20
  const MAX_NUM_RESULTS = 30;
@@ -139,7 +139,7 @@ async function postXAIResponses(
139
139
  Authorization: `Bearer ${apiKey}`,
140
140
  },
141
141
  body: JSON.stringify(body),
142
- signal: withHardTimeout(params.signal),
142
+ signal: withHardTimeout(params.signal, params.timeoutMs),
143
143
  });
144
144
  }
145
145
 
@@ -38,6 +38,7 @@ export interface ZaiSearchParams {
38
38
  query: string;
39
39
  num_results?: number;
40
40
  signal?: AbortSignal;
41
+ timeoutMs?: number;
41
42
  fetch?: FetchImpl;
42
43
  authStorage: AuthStorage;
43
44
  sessionId?: string;
@@ -121,6 +122,7 @@ async function postZaiMcp(
121
122
  signal: AbortSignal | undefined,
122
123
  fetchImpl: FetchImpl,
123
124
  expectResponse: boolean,
125
+ timeoutMs?: number,
124
126
  ): Promise<ZaiMcpPostResult> {
125
127
  const headers: Record<string, string> = {
126
128
  Authorization: `Bearer ${apiKey}`,
@@ -144,7 +146,7 @@ async function postZaiMcp(
144
146
  method: "POST",
145
147
  headers,
146
148
  body: JSON.stringify(body),
147
- signal: withHardTimeout(signal),
149
+ signal: withHardTimeout(signal, timeoutMs),
148
150
  });
149
151
 
150
152
  if (!response.ok) {
@@ -211,6 +213,7 @@ async function callZaiTool(
211
213
  args: Record<string, unknown>,
212
214
  signal: AbortSignal | undefined,
213
215
  fetchImpl: FetchImpl,
216
+ timeoutMs?: number,
214
217
  ): Promise<unknown> {
215
218
  const initialized = await postZaiMcp(
216
219
  apiKey,
@@ -224,12 +227,22 @@ async function callZaiTool(
224
227
  signal,
225
228
  fetchImpl,
226
229
  true,
230
+ timeoutMs,
227
231
  );
228
232
  if (initialized.parsed !== undefined) {
229
233
  readJsonRpcPayload(initialized.parsed);
230
234
  }
231
235
 
232
- await postZaiMcp(apiKey, "notifications/initialized", {}, initialized.sessionId, signal, fetchImpl, false);
236
+ await postZaiMcp(
237
+ apiKey,
238
+ "notifications/initialized",
239
+ {},
240
+ initialized.sessionId,
241
+ signal,
242
+ fetchImpl,
243
+ false,
244
+ timeoutMs,
245
+ );
233
246
 
234
247
  const toolCall = await postZaiMcp(
235
248
  apiKey,
@@ -242,6 +255,7 @@ async function callZaiTool(
242
255
  signal,
243
256
  fetchImpl,
244
257
  true,
258
+ timeoutMs,
245
259
  );
246
260
  const payload = readJsonRpcPayload(toolCall.parsed);
247
261
  const resultRecord = isRecord(payload.result) ? payload.result : null;
@@ -279,7 +293,7 @@ async function callZaiSearch(apiKey: string, params: ZaiSearchParams): Promise<u
279
293
  let lastError: unknown;
280
294
  for (let i = 0; i < attempts.length; i++) {
281
295
  try {
282
- return await callZaiTool(apiKey, attempts[i], params.signal, fetchImpl);
296
+ return await callZaiTool(apiKey, attempts[i], params.signal, fetchImpl, params.timeoutMs);
283
297
  } catch (error) {
284
298
  lastError = error;
285
299
  const isLastAttempt = i === attempts.length - 1;
@@ -429,6 +443,7 @@ export class ZaiProvider extends SearchProvider {
429
443
  query: parsed.hasDirectives ? formatQuery(parsed, ZAI_QUERY_SYNTAX) : params.query,
430
444
  num_results: params.numSearchResults ?? params.limit,
431
445
  signal: params.signal,
446
+ timeoutMs: params.timeoutMs,
432
447
  authStorage: params.authStorage,
433
448
  sessionId: params.sessionId,
434
449
  fetch: fetchOverride,
@@ -89,6 +89,12 @@ export const SEARCH_PROVIDER_OPTIONS = [
89
89
  },
90
90
  ] as const;
91
91
 
92
+ /** Default hard timeout for each web-search provider transport. */
93
+ export const DEFAULT_WEB_SEARCH_TIMEOUT_SECONDS = 60;
94
+
95
+ /** Maximum configurable hard timeout for each web-search provider transport. */
96
+ export const MAX_WEB_SEARCH_TIMEOUT_SECONDS = 300;
97
+
92
98
  /** Supported web search providers (every option except `auto`). */
93
99
  export type SearchProviderId = Exclude<(typeof SEARCH_PROVIDER_OPTIONS)[number]["value"], "auto">;
94
100
 
@@ -1,29 +0,0 @@
1
- export interface FileLockOptions {
2
- staleMs?: number;
3
- retries?: number;
4
- retryDelayMs?: number;
5
- }
6
- interface LockInfo {
7
- pid: number;
8
- timestamp: number;
9
- token: string;
10
- }
11
- declare function getLockPath(filePath: string): string;
12
- declare function readLockInfo(lockPath: string): Promise<LockInfo | null>;
13
- declare function isLockStale(lockPath: string, staleMs: number): Promise<boolean>;
14
- declare function tryAcquireLock(lockPath: string): Promise<string | null>;
15
- declare function releaseLock(lockPath: string, expectedToken?: string): Promise<void>;
16
- export declare function withFileLock<T>(filePath: string, fn: () => Promise<T>, options?: FileLockOptions): Promise<T>;
17
- /**
18
- * Test-only handles for the internal lock primitives. These are NOT part of
19
- * the public API — they exist so the contract tests can validate token-keyed
20
- * release semantics and the mkdir-race window without re-implementing them.
21
- */
22
- export declare const __internalsForTesting: {
23
- tryAcquireLock: typeof tryAcquireLock;
24
- releaseLock: typeof releaseLock;
25
- readLockInfo: typeof readLockInfo;
26
- isLockStale: typeof isLockStale;
27
- getLockPath: typeof getLockPath;
28
- };
29
- export {};
@@ -1,164 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import * as fs from "node:fs/promises";
3
- import { isEnoent, logger } from "@oh-my-pi/pi-utils";
4
-
5
- export interface FileLockOptions {
6
- staleMs?: number;
7
- retries?: number;
8
- retryDelayMs?: number;
9
- }
10
-
11
- const DEFAULT_OPTIONS: Required<FileLockOptions> = {
12
- staleMs: 10_000,
13
- retries: 50,
14
- retryDelayMs: 100,
15
- };
16
-
17
- interface LockInfo {
18
- pid: number;
19
- timestamp: number;
20
- token: string;
21
- }
22
-
23
- function getLockPath(filePath: string): string {
24
- return `${filePath}.lock`;
25
- }
26
-
27
- async function writeLockInfo(lockPath: string, token: string): Promise<void> {
28
- const info: LockInfo = { pid: process.pid, timestamp: Date.now(), token };
29
- await Bun.write(`${lockPath}/info`, JSON.stringify(info));
30
- }
31
-
32
- async function readLockInfo(lockPath: string): Promise<LockInfo | null> {
33
- try {
34
- const content = await fs.readFile(`${lockPath}/info`, "utf-8");
35
- return JSON.parse(content) as LockInfo;
36
- } catch {
37
- return null;
38
- }
39
- }
40
-
41
- function isProcessAlive(pid: number): boolean {
42
- try {
43
- process.kill(pid, 0);
44
- return true;
45
- } catch {
46
- return false;
47
- }
48
- }
49
-
50
- async function isLockStale(lockPath: string, staleMs: number): Promise<boolean> {
51
- const info = await readLockInfo(lockPath);
52
- if (info) {
53
- if (!isProcessAlive(info.pid)) return true;
54
- if (Date.now() - info.timestamp > staleMs) return true;
55
- return false;
56
- }
57
-
58
- // No info file. Either the lock holder is between mkdir and writeLockInfo
59
- // (fresh dir, do not reap) or the dir was already removed (also do not
60
- // reap — there is nothing to clean up, and an unguarded fs.rm here would
61
- // race with another contender's successful mkdir and wipe their dir).
62
- try {
63
- const stat = await fs.stat(lockPath);
64
- return Date.now() - stat.mtimeMs > staleMs;
65
- } catch (err) {
66
- if (isEnoent(err)) return false;
67
- throw err;
68
- }
69
- }
70
-
71
- async function tryAcquireLock(lockPath: string): Promise<string | null> {
72
- try {
73
- await fs.mkdir(lockPath);
74
- const token = randomUUID();
75
- await writeLockInfo(lockPath, token);
76
- return token;
77
- } catch (error) {
78
- if ((error as NodeJS.ErrnoException).code === "EEXIST") {
79
- return null;
80
- }
81
- throw error;
82
- }
83
- }
84
-
85
- async function releaseLock(lockPath: string, expectedToken?: string): Promise<void> {
86
- try {
87
- if (expectedToken !== undefined) {
88
- const info = await readLockInfo(lockPath);
89
- if (!info || info.token !== expectedToken) {
90
- // We are not the owner. The lock either expired and was reaped
91
- // or another process has reclaimed it. Do nothing — releasing
92
- // here would wipe the rightful owner's lock.
93
- logger.debug("file-lock: skipping release for non-owned lock", {
94
- lockPath,
95
- expectedToken,
96
- actualToken: info?.token,
97
- });
98
- return;
99
- }
100
- }
101
- await fs.rm(lockPath, { recursive: true });
102
- } catch {
103
- // Ignore errors on release.
104
- }
105
- }
106
-
107
- async function lockExists(lockPath: string): Promise<boolean> {
108
- try {
109
- await fs.stat(lockPath);
110
- return true;
111
- } catch (err) {
112
- if (isEnoent(err)) return false;
113
- throw err;
114
- }
115
- }
116
-
117
- async function acquireLock(filePath: string, options: FileLockOptions = {}): Promise<() => Promise<void>> {
118
- const opts = { ...DEFAULT_OPTIONS, ...options };
119
- const lockPath = getLockPath(filePath);
120
-
121
- for (let attempt = 0; attempt < opts.retries; attempt++) {
122
- const token = await tryAcquireLock(lockPath);
123
- if (token !== null) {
124
- return () => releaseLock(lockPath, token);
125
- }
126
-
127
- if ((await lockExists(lockPath)) && (await isLockStale(lockPath, opts.staleMs))) {
128
- // Reaping a stale lock — no token because we didn't acquire it. The
129
- // rightful owner is presumed dead; rm without ownership check.
130
- await releaseLock(lockPath);
131
- continue;
132
- }
133
-
134
- await Bun.sleep(opts.retryDelayMs);
135
- }
136
-
137
- throw new Error(`Failed to acquire lock for ${filePath} after ${opts.retries} attempts`);
138
- }
139
-
140
- export async function withFileLock<T>(
141
- filePath: string,
142
- fn: () => Promise<T>,
143
- options: FileLockOptions = {},
144
- ): Promise<T> {
145
- const release = await acquireLock(filePath, options);
146
- try {
147
- return await fn();
148
- } finally {
149
- await release();
150
- }
151
- }
152
-
153
- /**
154
- * Test-only handles for the internal lock primitives. These are NOT part of
155
- * the public API — they exist so the contract tests can validate token-keyed
156
- * release semantics and the mkdir-race window without re-implementing them.
157
- */
158
- export const __internalsForTesting = {
159
- tryAcquireLock,
160
- releaseLock,
161
- readLockInfo,
162
- isLockStale,
163
- getLockPath,
164
- };