@oh-my-pi/pi-coding-agent 17.2.4 → 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 (319) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/{CHANGELOG-bpmhv26t.md → CHANGELOG-q2w43aw3.md} +51 -0
  3. package/dist/cli.js +4148 -3717
  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/help-extra.d.ts +2 -0
  9. package/dist/types/cli/thinking-levels.d.ts +5 -0
  10. package/dist/types/cli/update-cli.d.ts +13 -0
  11. package/dist/types/commands/browser-relay.d.ts +38 -0
  12. package/dist/types/commands/commit.d.ts +3 -0
  13. package/dist/types/commands/complete.d.ts +1 -1
  14. package/dist/types/commands/completions.d.ts +6 -0
  15. package/dist/types/commands/grep.d.ts +3 -0
  16. package/dist/types/commands/join.d.ts +4 -0
  17. package/dist/types/commands/launch-help.d.ts +171 -0
  18. package/dist/types/commands/launch.d.ts +5 -1
  19. package/dist/types/commands/models.d.ts +3 -0
  20. package/dist/types/commands/say.d.ts +10 -0
  21. package/dist/types/commands/worktree.d.ts +3 -0
  22. package/dist/types/config/keybindings.d.ts +5 -0
  23. package/dist/types/config/model-discovery.d.ts +2 -2
  24. package/dist/types/config/models-config-schema-bundle.d.ts +3 -0
  25. package/dist/types/config/models-config-schema.d.ts +2 -0
  26. package/dist/types/config/models-config.d.ts +1 -0
  27. package/dist/types/config/service-tier.d.ts +2 -0
  28. package/dist/types/config/settings-schema.d.ts +69 -21
  29. package/dist/types/cursor-bridge-tools.d.ts +1 -1
  30. package/dist/types/cursor.d.ts +7 -4
  31. package/dist/types/edit/index.d.ts +3 -3
  32. package/dist/types/edit/modes/replace.d.ts +22 -15
  33. package/dist/types/edit/renderer.d.ts +1 -1
  34. package/dist/types/eval/py/kernel.d.ts +3 -1
  35. package/dist/types/eval/py/spawn-options.d.ts +12 -16
  36. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +8 -1
  37. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
  38. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
  39. package/dist/types/launch/broker.d.ts +1 -1
  40. package/dist/types/launch/client.d.ts +6 -3
  41. package/dist/types/launch/ensure.d.ts +8 -0
  42. package/dist/types/launch/paths.d.ts +1 -1
  43. package/dist/types/launch/protocol.d.ts +2 -2
  44. package/dist/types/lsp/client.d.ts +2 -0
  45. package/dist/types/lsp/mux/daemon.d.ts +15 -0
  46. package/dist/types/lsp/mux/protocol.d.ts +57 -0
  47. package/dist/types/lsp/mux/server.d.ts +18 -0
  48. package/dist/types/lsp/types.d.ts +22 -2
  49. package/dist/types/main.d.ts +2 -0
  50. package/dist/types/mnemopi/embed-client.d.ts +1 -1
  51. package/dist/types/mnemopi/state.d.ts +0 -23
  52. package/dist/types/modes/components/assistant-message.d.ts +2 -0
  53. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  54. package/dist/types/modes/components/read-tool-group.d.ts +2 -0
  55. package/dist/types/modes/components/stripped-tool-calls-placeholder.d.ts +12 -0
  56. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  57. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  58. package/dist/types/modes/interactive-mode.d.ts +1 -0
  59. package/dist/types/modes/types.d.ts +1 -0
  60. package/dist/types/modes/workflow.d.ts +2 -1
  61. package/dist/types/sdk.d.ts +3 -1
  62. package/dist/types/secrets/index.d.ts +1 -1
  63. package/dist/types/session/agent-session-types.d.ts +3 -1
  64. package/dist/types/session/indexed-session-storage.d.ts +1 -0
  65. package/dist/types/session/session-advisors.d.ts +1 -1
  66. package/dist/types/session/session-context.d.ts +2 -1
  67. package/dist/types/session/session-maintenance.d.ts +1 -0
  68. package/dist/types/session/session-manager.d.ts +16 -11
  69. package/dist/types/session/session-stats.d.ts +10 -0
  70. package/dist/types/session/session-storage.d.ts +15 -3
  71. package/dist/types/system-prompt.d.ts +4 -1
  72. package/dist/types/task/index.d.ts +2 -1
  73. package/dist/types/task/spawn-policy.d.ts +5 -0
  74. package/dist/types/thinking.d.ts +1 -7
  75. package/dist/types/tools/ast-grep.d.ts +1 -1
  76. package/dist/types/tools/browser/registry.d.ts +2 -1
  77. package/dist/types/tools/browser/relay/bridge.d.ts +35 -0
  78. package/dist/types/tools/browser/relay/daemon.d.ts +16 -0
  79. package/dist/types/tools/browser/relay/kind.d.ts +19 -0
  80. package/dist/types/tools/browser/relay/protocol.d.ts +97 -0
  81. package/dist/types/tools/browser/relay/server.d.ts +35 -0
  82. package/dist/types/tools/browser/render.d.ts +1 -0
  83. package/dist/types/tools/browser.d.ts +3 -0
  84. package/dist/types/tools/computer/exposure.d.ts +9 -7
  85. package/dist/types/tools/computer/protocol.d.ts +70 -17
  86. package/dist/types/tools/computer/supervisor.d.ts +23 -10
  87. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  88. package/dist/types/tools/computer/worker.d.ts +42 -7
  89. package/dist/types/tools/computer-renderer.d.ts +7 -5
  90. package/dist/types/tools/computer.d.ts +25 -44
  91. package/dist/types/tools/context.d.ts +3 -0
  92. package/dist/types/tools/glob.d.ts +1 -1
  93. package/dist/types/tools/grep.d.ts +1 -1
  94. package/dist/types/tools/render-utils.d.ts +2 -0
  95. package/dist/types/tools/{browser/run-cancellation.d.ts → run-scope.d.ts} +8 -8
  96. package/dist/types/tools/tool-timeouts.d.ts +5 -0
  97. package/dist/types/tools/xdev.d.ts +8 -0
  98. package/dist/types/utils/clipboard.d.ts +2 -2
  99. package/dist/types/web/kagi.d.ts +1 -0
  100. package/dist/types/web/search/index.d.ts +1 -1
  101. package/dist/types/web/search/providers/anthropic.d.ts +1 -0
  102. package/dist/types/web/search/providers/base.d.ts +2 -0
  103. package/dist/types/web/search/providers/brave.d.ts +1 -0
  104. package/dist/types/web/search/providers/browser-page.d.ts +1 -0
  105. package/dist/types/web/search/providers/codex.d.ts +1 -0
  106. package/dist/types/web/search/providers/exa.d.ts +1 -0
  107. package/dist/types/web/search/providers/firecrawl.d.ts +1 -0
  108. package/dist/types/web/search/providers/gemini.d.ts +1 -0
  109. package/dist/types/web/search/providers/jina.d.ts +1 -0
  110. package/dist/types/web/search/providers/kagi.d.ts +1 -0
  111. package/dist/types/web/search/providers/kimi.d.ts +1 -0
  112. package/dist/types/web/search/providers/parallel.d.ts +1 -0
  113. package/dist/types/web/search/providers/perplexity.d.ts +1 -0
  114. package/dist/types/web/search/providers/searxng.d.ts +1 -0
  115. package/dist/types/web/search/providers/tavily.d.ts +1 -0
  116. package/dist/types/web/search/providers/tinyfish.d.ts +1 -0
  117. package/dist/types/web/search/providers/utils.d.ts +6 -9
  118. package/dist/types/web/search/providers/zai.d.ts +1 -0
  119. package/dist/types/web/search/types.d.ts +4 -0
  120. package/package.json +12 -12
  121. package/src/cli/args.ts +7 -87
  122. package/src/cli/browser-relay-cli.ts +119 -0
  123. package/src/cli/command-help.ts +103 -0
  124. package/src/cli/completion-gen.ts +5 -5
  125. package/src/cli/flag-tables.ts +9 -0
  126. package/src/cli/help-extra.ts +89 -0
  127. package/src/cli/setup-cli.ts +13 -21
  128. package/src/cli/thinking-levels.ts +7 -0
  129. package/src/cli/update-cli.ts +26 -7
  130. package/src/cli-commands.ts +170 -33
  131. package/src/cli.ts +17 -5
  132. package/src/commands/acp.ts +3 -1
  133. package/src/commands/agents.ts +3 -2
  134. package/src/commands/auth-broker.ts +3 -2
  135. package/src/commands/auth-gateway.ts +3 -2
  136. package/src/commands/bench.ts +2 -3
  137. package/src/commands/browser-relay.ts +53 -0
  138. package/src/commands/cleanse.ts +2 -2
  139. package/src/commands/commit.ts +3 -2
  140. package/src/commands/complete.ts +2 -1
  141. package/src/commands/completions.ts +3 -2
  142. package/src/commands/config.ts +3 -2
  143. package/src/commands/dry-balance.ts +2 -2
  144. package/src/commands/gallery.ts +3 -2
  145. package/src/commands/gc.ts +3 -2
  146. package/src/commands/grep.ts +3 -2
  147. package/src/commands/grievances.ts +3 -2
  148. package/src/commands/install.ts +2 -2
  149. package/src/commands/join.ts +3 -2
  150. package/src/commands/launch-help.ts +116 -0
  151. package/src/commands/launch.ts +7 -191
  152. package/src/commands/models.ts +3 -2
  153. package/src/commands/plugin.ts +3 -2
  154. package/src/commands/read.ts +3 -2
  155. package/src/commands/say.ts +3 -2
  156. package/src/commands/setup.ts +3 -2
  157. package/src/commands/shell.ts +3 -2
  158. package/src/commands/ssh.ts +3 -2
  159. package/src/commands/stats.ts +3 -2
  160. package/src/commands/tiny-models.ts +2 -2
  161. package/src/commands/token.ts +2 -2
  162. package/src/commands/ttsr.ts +2 -2
  163. package/src/commands/update.ts +3 -2
  164. package/src/commands/usage.ts +3 -2
  165. package/src/commands/web-search.ts +3 -2
  166. package/src/commands/worktree.ts +3 -2
  167. package/src/config/keybindings.ts +5 -0
  168. package/src/config/model-discovery.ts +30 -13
  169. package/src/config/model-registry.ts +9 -5
  170. package/src/config/models-config-schema-bundle.ts +9 -0
  171. package/src/config/resolve-config-value.ts +2 -1
  172. package/src/config/service-tier.ts +5 -0
  173. package/src/config/settings-schema.ts +72 -20
  174. package/src/cursor-bridge-tools.ts +1 -1
  175. package/src/cursor.ts +10 -14
  176. package/src/edit/diff.ts +17 -8
  177. package/src/edit/index.ts +20 -7
  178. package/src/edit/modes/replace.ts +93 -40
  179. package/src/edit/renderer.ts +1 -1
  180. package/src/edit/streaming.ts +14 -17
  181. package/src/eval/py/kernel.ts +2 -1
  182. package/src/eval/py/spawn-options.ts +24 -29
  183. package/src/export/html/template.js +8 -26
  184. package/src/extensibility/extensions/wrapper.ts +1 -0
  185. package/src/extensibility/legacy-pi-ai-shim.ts +22 -1
  186. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -6
  187. package/src/extensibility/plugins/legacy-pi-compat.ts +124 -57
  188. package/src/extensibility/skills.ts +11 -0
  189. package/src/extensibility/utils.ts +10 -2
  190. package/src/launch/broker.ts +1 -1
  191. package/src/launch/client.ts +32 -13
  192. package/src/launch/ensure.ts +73 -0
  193. package/src/launch/paths.ts +1 -1
  194. package/src/launch/protocol.ts +2 -2
  195. package/src/lsp/client.ts +23 -2
  196. package/src/lsp/index.ts +8 -0
  197. package/src/lsp/mux/daemon.ts +348 -0
  198. package/src/lsp/mux/protocol.ts +96 -0
  199. package/src/lsp/mux/server.ts +797 -0
  200. package/src/lsp/types.ts +28 -2
  201. package/src/main.ts +7 -2
  202. package/src/mcp/oauth-flow.ts +11 -1
  203. package/src/mnemopi/embed-client.ts +49 -2
  204. package/src/mnemopi/state.ts +57 -23
  205. package/src/modes/components/assistant-message.ts +11 -0
  206. package/src/modes/components/chat-transcript-builder.ts +4 -0
  207. package/src/modes/components/custom-editor.ts +9 -0
  208. package/src/modes/components/read-tool-group.ts +10 -0
  209. package/src/modes/components/status-line/component.ts +1 -1
  210. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  211. package/src/modes/components/tool-execution.ts +8 -0
  212. package/src/modes/components/tree-selector.ts +46 -48
  213. package/src/modes/controllers/event-controller.ts +17 -0
  214. package/src/modes/controllers/input-controller.ts +39 -1
  215. package/src/modes/controllers/mcp-command-controller.ts +11 -5
  216. package/src/modes/controllers/selector-controller.ts +20 -0
  217. package/src/modes/interactive-mode.ts +2 -0
  218. package/src/modes/types.ts +1 -0
  219. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  220. package/src/modes/utils/interactive-context-helpers.ts +1 -0
  221. package/src/modes/utils/ui-helpers.ts +6 -10
  222. package/src/modes/workflow.ts +8 -2
  223. package/src/prompts/agents/init.md +1 -1
  224. package/src/prompts/system/plan-mode-active.md +2 -2
  225. package/src/prompts/system/system-prompt.md +26 -52
  226. package/src/prompts/system/workflow-notice.md +3 -3
  227. package/src/prompts/tools/ast-grep.md +1 -1
  228. package/src/prompts/tools/bash.md +1 -1
  229. package/src/prompts/tools/browser.md +1 -0
  230. package/src/prompts/tools/computer.md +21 -21
  231. package/src/prompts/tools/glob.md +1 -1
  232. package/src/prompts/tools/grep.md +1 -1
  233. package/src/prompts/tools/replace.md +5 -4
  234. package/src/prompts/tools/task.md +4 -4
  235. package/src/sdk.ts +34 -6
  236. package/src/secrets/index.ts +1 -1
  237. package/src/session/agent-session-types.ts +3 -1
  238. package/src/session/agent-session.ts +13 -1
  239. package/src/session/indexed-session-storage.ts +9 -0
  240. package/src/session/prewalk.ts +24 -2
  241. package/src/session/session-advisors.ts +2 -2
  242. package/src/session/session-context.ts +16 -12
  243. package/src/session/session-dump-format.ts +1 -1
  244. package/src/session/session-maintenance.ts +38 -4
  245. package/src/session/session-manager.ts +102 -67
  246. package/src/session/session-paths.ts +118 -55
  247. package/src/session/session-stats.ts +48 -3
  248. package/src/session/session-storage.ts +30 -22
  249. package/src/session/stream-guards.ts +1 -1
  250. package/src/slash-commands/builtin-registry.ts +27 -24
  251. package/src/system-prompt.ts +8 -3
  252. package/src/task/index.ts +23 -8
  253. package/src/task/spawn-policy.ts +14 -0
  254. package/src/thinking.ts +2 -8
  255. package/src/tools/ast-grep.ts +10 -4
  256. package/src/tools/browser/cmux/cmux-tab.ts +5 -10
  257. package/src/tools/browser/cmux/rpc.ts +2 -8
  258. package/src/tools/browser/registry.ts +53 -2
  259. package/src/tools/browser/relay/bridge.ts +945 -0
  260. package/src/tools/browser/relay/daemon.ts +117 -0
  261. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  262. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  263. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  264. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  265. package/src/tools/browser/relay/kind.ts +41 -0
  266. package/src/tools/browser/relay/protocol.ts +54 -0
  267. package/src/tools/browser/relay/server.ts +141 -0
  268. package/src/tools/browser/render.ts +4 -1
  269. package/src/tools/browser/shared-daemon.ts +7 -58
  270. package/src/tools/browser/tab-supervisor.ts +5 -4
  271. package/src/tools/browser/tab-worker.ts +33 -12
  272. package/src/tools/browser.ts +26 -1
  273. package/src/tools/computer/exposure.ts +11 -35
  274. package/src/tools/computer/protocol.ts +51 -10
  275. package/src/tools/computer/supervisor.ts +227 -124
  276. package/src/tools/computer/worker-entry.ts +11 -6
  277. package/src/tools/computer/worker.ts +695 -81
  278. package/src/tools/computer-renderer.ts +104 -65
  279. package/src/tools/computer.ts +134 -428
  280. package/src/tools/context.ts +3 -0
  281. package/src/tools/glob.ts +9 -2
  282. package/src/tools/grep.ts +12 -6
  283. package/src/tools/inspect-image.ts +32 -6
  284. package/src/tools/render-utils.ts +2 -0
  285. package/src/tools/{browser/run-cancellation.ts → run-scope.ts} +11 -11
  286. package/src/tools/tool-timeouts.ts +1 -0
  287. package/src/tools/xdev.ts +29 -2
  288. package/src/utils/clipboard.ts +40 -14
  289. package/src/utils/command-args.ts +26 -28
  290. package/src/utils/tool-choice.ts +0 -13
  291. package/src/web/kagi.ts +2 -1
  292. package/src/web/search/index.ts +18 -2
  293. package/src/web/search/providers/anthropic.ts +4 -1
  294. package/src/web/search/providers/base.ts +2 -0
  295. package/src/web/search/providers/brave.ts +3 -1
  296. package/src/web/search/providers/browser-page.ts +6 -4
  297. package/src/web/search/providers/codex.ts +4 -1
  298. package/src/web/search/providers/duckduckgo.ts +2 -1
  299. package/src/web/search/providers/ecosia.ts +2 -1
  300. package/src/web/search/providers/exa.ts +4 -2
  301. package/src/web/search/providers/firecrawl.ts +3 -1
  302. package/src/web/search/providers/gemini.ts +8 -2
  303. package/src/web/search/providers/google.ts +2 -1
  304. package/src/web/search/providers/jina.ts +12 -2
  305. package/src/web/search/providers/kagi.ts +3 -0
  306. package/src/web/search/providers/kimi.ts +5 -1
  307. package/src/web/search/providers/mojeek.ts +2 -1
  308. package/src/web/search/providers/parallel.ts +5 -1
  309. package/src/web/search/providers/perplexity.ts +6 -3
  310. package/src/web/search/providers/public.ts +1 -1
  311. package/src/web/search/providers/searxng.ts +11 -5
  312. package/src/web/search/providers/startpage.ts +10 -4
  313. package/src/web/search/providers/synthetic.ts +11 -5
  314. package/src/web/search/providers/tavily.ts +3 -1
  315. package/src/web/search/providers/tinyfish.ts +3 -1
  316. package/src/web/search/providers/utils.ts +12 -10
  317. package/src/web/search/providers/xai.ts +2 -2
  318. package/src/web/search/providers/zai.ts +18 -3
  319. package/src/web/search/types.ts +6 -0
@@ -24,7 +24,7 @@ interface BrowserRenderArgs {
24
24
  code?: string;
25
25
  all?: boolean;
26
26
  kill?: boolean;
27
- app?: { path?: string; cdp_url?: string; target?: string; cmux?: boolean; surface?: string };
27
+ app?: { path?: string; cdp_url?: string; relay?: boolean; target?: string; cmux?: boolean; surface?: string };
28
28
  viewport?: { width: number; height: number; scale?: number };
29
29
  timeout?: number;
30
30
  }
@@ -39,6 +39,7 @@ function describeBrowser(args: BrowserRenderArgs, details: BrowserToolDetails |
39
39
  if (cdpUrl) return `connected ${cdpUrl}`;
40
40
  const appPath = typeof args.app?.path === "string" ? args.app.path : "";
41
41
  if (appPath) return `spawned ${shortenPath(appPath)}`;
42
+ if (args.app?.relay) return "relay";
42
43
  if (args.app?.cmux !== false && (args.app?.cmux === true || args.app?.surface)) {
43
44
  return args.app.surface ? `cmux ${args.app.surface}` : "cmux";
44
45
  }
@@ -49,6 +50,8 @@ function describeBrowser(args: BrowserRenderArgs, details: BrowserToolDetails |
49
50
  return "spawned";
50
51
  case "connected":
51
52
  return "connected";
53
+ case "relay":
54
+ return "relay";
52
55
  case "cmux":
53
56
  return "cmux";
54
57
  default:
@@ -11,7 +11,8 @@
11
11
  import * as fs from "node:fs/promises";
12
12
  import * as path from "node:path";
13
13
  import { logger } from "@oh-my-pi/pi-utils";
14
- import { type DaemonBrokerClient, daemonClientForProject } from "../../launch/client";
14
+ import { daemonClientForProject } from "../../launch/client";
15
+ import { describeQuietly, stopQuietly, waitReady } from "../../launch/ensure";
15
16
  import { daemonRuntimeDir } from "../../launch/paths";
16
17
  import type { DaemonSnapshot } from "../../launch/protocol";
17
18
  import { throwIfAborted } from "../tool-errors";
@@ -20,7 +21,6 @@ import { resolveSharedBrowserLaunchSpec } from "./launch";
20
21
  /** Chrome prints this on stderr once the CDP listener is up; the broker's ready probe captures the line. */
21
22
  const READY_LOG_PATTERN = String.raw`DevTools listening on ws://\S+`;
22
23
  const READY_TIMEOUT_MS = 30_000;
23
- const STOP_TIMEOUT_MS = 5_000;
24
24
  const PROBE_TIMEOUT_MS = 1_500;
25
25
  /** describe→start rounds before giving up; bounds cross-process start races and wedged-Chrome replacement. */
26
26
  const ENSURE_ATTEMPTS = 3;
@@ -59,58 +59,6 @@ async function probeEndpoint(wsEndpoint: string): Promise<boolean> {
59
59
  }
60
60
  }
61
61
 
62
- /** Snapshot the daemon, treating "unknown daemon" as absent. */
63
- async function describeQuietly(
64
- client: DaemonBrokerClient,
65
- name: string,
66
- signal?: AbortSignal,
67
- ): Promise<DaemonSnapshot | undefined> {
68
- try {
69
- const result = await client.request({ op: "describe", name }, signal);
70
- return result.op === "describe" ? result.daemon : undefined;
71
- } catch (error) {
72
- throwIfAborted(signal);
73
- logger.debug("Shared browser describe failed", {
74
- name,
75
- error: error instanceof Error ? error.message : String(error),
76
- });
77
- return undefined;
78
- }
79
- }
80
-
81
- /** Block until the daemon reports ready; undefined on timeout or pre-ready exit. */
82
- async function waitReady(
83
- client: DaemonBrokerClient,
84
- name: string,
85
- signal?: AbortSignal,
86
- ): Promise<DaemonSnapshot | undefined> {
87
- try {
88
- const result = await client.request({ op: "wait", name, for: "ready", timeoutMs: READY_TIMEOUT_MS }, signal);
89
- if (result.op !== "wait" || result.timedOut) return undefined;
90
- return result.daemon;
91
- } catch (error) {
92
- throwIfAborted(signal);
93
- logger.debug("Shared browser ready wait failed", {
94
- name,
95
- error: error instanceof Error ? error.message : String(error),
96
- });
97
- return undefined;
98
- }
99
- }
100
-
101
- /** Best-effort stop before replacing a wedged or endpoint-less daemon. */
102
- async function stopQuietly(client: DaemonBrokerClient, name: string, signal?: AbortSignal): Promise<void> {
103
- try {
104
- await client.request({ op: "stop", name, timeoutMs: STOP_TIMEOUT_MS }, signal);
105
- } catch (error) {
106
- throwIfAborted(signal);
107
- logger.debug("Shared browser stop failed", {
108
- name,
109
- error: error instanceof Error ? error.message : String(error),
110
- });
111
- }
112
- }
113
-
114
62
  /**
115
63
  * Ensure the project-shared automation Chromium is running and reachable,
116
64
  * launching it under the daemon broker when needed. Idempotent across
@@ -139,16 +87,17 @@ export async function ensureSharedBrowser(opts: {
139
87
  await fs.mkdir(userDataDir, { recursive: true });
140
88
  for (let attempt = 0; attempt < ENSURE_ATTEMPTS; attempt++) {
141
89
  throwIfAborted(opts.signal);
142
- const existing = await describeQuietly(client, name, opts.signal);
90
+ const existing = await describeQuietly(client, name, "Shared browser", opts.signal);
143
91
  if (existing && existing.state !== "exited" && existing.state !== "failed") {
144
- const settled = existing.readyAt !== undefined ? existing : await waitReady(client, name, opts.signal);
92
+ const settled =
93
+ existing.readyAt !== undefined ? existing : await waitReady(client, name, "Shared browser", opts.signal);
145
94
  const wsEndpoint = wsEndpointOf(settled);
146
95
  if (wsEndpoint && (await probeEndpoint(wsEndpoint))) {
147
96
  return { wsEndpoint, daemonName: name, projectDir: client.projectDir };
148
97
  }
149
98
  // Live record but unreachable Chrome (wedged, or readiness never
150
99
  // matched): replace it rather than handing out a dead endpoint.
151
- await stopQuietly(client, name, opts.signal);
100
+ await stopQuietly(client, name, "Shared browser", opts.signal);
152
101
  continue;
153
102
  }
154
103
  try {
@@ -175,7 +124,7 @@ export async function ensureSharedBrowser(opts: {
175
124
  if (wsEndpoint && (await probeEndpoint(wsEndpoint))) {
176
125
  return { wsEndpoint, daemonName: name, projectDir: client.projectDir };
177
126
  }
178
- await stopQuietly(client, name, opts.signal);
127
+ await stopQuietly(client, name, "Shared browser", opts.signal);
179
128
  } catch (error) {
180
129
  throwIfAborted(opts.signal);
181
130
  // Lost a cross-process start race ("already starting/ready"); the next
@@ -698,10 +698,11 @@ async function buildInitPayload(browser: PuppeteerBrowserHandle, opts: AcquireTa
698
698
  timeoutMs: opts.timeoutMs,
699
699
  };
700
700
  }
701
- // A connected browser is user-driven. When no target is requested, adopt its
702
- // visible tab and avoid raising it before screenshots. An explicit target may
703
- // be backgrounded, so retain activation to guarantee target-correct pixels.
704
- const activateForScreenshot = browser.kind.kind !== "connected" || !shouldPreserveConnectedBrowserFocus(opts.target);
701
+ // Connected and relay browsers are user-driven. When no target is requested,
702
+ // adopt the visible tab and avoid raising it before screenshots. An explicit
703
+ // target may be backgrounded, so retain activation for target-correct pixels.
704
+ const userDriven = browser.kind.kind === "connected" || browser.kind.kind === "relay";
705
+ const activateForScreenshot = !userDriven || !shouldPreserveConnectedBrowserFocus(opts.target);
705
706
  const page = await pickElectronTarget(browser.browser, {
706
707
  matcher: opts.target,
707
708
  preferVisible: !activateForScreenshot,
@@ -20,6 +20,14 @@ import { JsRuntime, type RuntimeHooks } from "../../eval/js/shared/runtime";
20
20
  import { resizeImage } from "../../utils/image-resize";
21
21
  import { resolveToCwd } from "../path-utils";
22
22
  import { formatScreenshot } from "../render-utils";
23
+ import {
24
+ bindRunFacade,
25
+ CELL_BUDGET_SLACK_MS,
26
+ markHandled,
27
+ resolvePredicateTimeout,
28
+ type WaitPredicateOptions,
29
+ waitForRun,
30
+ } from "../run-scope";
23
31
  import { ToolAbortError, ToolError, throwIfAborted } from "../tool-errors";
24
32
  import {
25
33
  type AriaSnapshotOptions,
@@ -36,14 +44,6 @@ import {
36
44
  loadPuppeteerInWorker,
37
45
  } from "./launch";
38
46
  import { extractReadableFromHtml, type ReadableFormat } from "./readable";
39
- import {
40
- bindBrowserRunFacade,
41
- CELL_BUDGET_SLACK_MS,
42
- markHandled,
43
- resolvePredicateTimeout,
44
- type WaitPredicateOptions,
45
- waitForBrowserRun,
46
- } from "./run-cancellation";
47
47
  import { cloneSafe, RunOutput } from "./run-output";
48
48
  import type {
49
49
  Observation,
@@ -827,6 +827,7 @@ export class WorkerCore {
827
827
  const page = await target.page();
828
828
  if (!page) throw new ToolError(`Target ${payload.targetId} is no longer available on the attached browser`);
829
829
  this.#page = page;
830
+ await this.#claimRelayTarget(page);
830
831
  this.#observeDialogs();
831
832
  if (payload.dialogs) this.#applyDialogPolicy(payload.dialogs);
832
833
  if (payload.url) {
@@ -853,6 +854,26 @@ export class WorkerCore {
853
854
  throw new ToolError(`Target ${targetId} is no longer available on the attached browser`);
854
855
  }
855
856
 
857
+ /**
858
+ * Tell the omp browser relay this worker drives the adopted page, so the
859
+ * relay adds it to the per-window "omp" tab group. Best-effort: plain CDP
860
+ * backends (real Chrome, cmux) reject the relay-private method.
861
+ */
862
+ async #claimRelayTarget(page: Page): Promise<void> {
863
+ let session: CDPSession | undefined;
864
+ try {
865
+ session = await page.createCDPSession();
866
+ // Puppeteer's protocol map cannot express the relay-private method; the
867
+ // send signature is otherwise identical.
868
+ const raw = session as unknown as { send(method: string): Promise<unknown> };
869
+ await raw.send("OMP.claimTarget");
870
+ } catch {
871
+ // Not the omp relay; nothing to claim.
872
+ } finally {
873
+ await session?.detach().catch(() => undefined);
874
+ }
875
+ }
876
+
856
877
  /**
857
878
  * Best-effort unblocking of a wedged target during post-timeout recovery: dismiss any
858
879
  * open JS dialog and stop a pending navigation over a raw CDP session (created on the
@@ -975,9 +996,9 @@ export class WorkerCore {
975
996
  const runtime = this.#ensureRuntime(msg.session);
976
997
  runtime.setCwd(msg.session.cwd);
977
998
  runtime.setRunScope({
978
- page: bindBrowserRunFacade(runPage.page, signal),
979
- browser: bindBrowserRunFacade(browser, signal),
980
- tab: bindBrowserRunFacade(tabApi, signal),
999
+ page: bindRunFacade(runPage.page, signal),
1000
+ browser: bindRunFacade(browser, signal),
1001
+ tab: bindRunFacade(tabApi, signal),
981
1002
  assert: (cond: unknown, text?: string): void => {
982
1003
  if (!cond) throw new ToolError(text ?? "Assertion failed");
983
1004
  },
@@ -991,7 +1012,7 @@ export class WorkerCore {
991
1012
  : { timeout: resolvePredicateTimeout(msg.timeoutMs, opts?.timeout), interval: opts?.interval };
992
1013
  return markHandled(
993
1014
  this.#runOp(active, label, signal, Number.POSITIVE_INFINITY, sig =>
994
- waitForBrowserRun(msOrPredicate, sig, resolved),
1015
+ waitForRun(msOrPredicate, sig, resolved),
995
1016
  ),
996
1017
  );
997
1018
  },
@@ -15,6 +15,7 @@ import {
15
15
  holdBrowser,
16
16
  releaseBrowser,
17
17
  } from "./browser/registry";
18
+ import { resolveRelayKind } from "./browser/relay/kind";
18
19
  import type { Observation, ScreenshotResult } from "./browser/tab-protocol";
19
20
  import {
20
21
  type AcquireTabResult,
@@ -39,6 +40,7 @@ export {
39
40
  export { cmuxSnapshotToObservation, mapWaitUntil, resolveCmuxKind, serializeEval } from "./browser/cmux/rpc";
40
41
  export { CmuxSocketClient } from "./browser/cmux/socket-client";
41
42
  export { extractReadableFromHtml, type ReadableFormat, type ReadableResult } from "./browser/readable";
43
+ export { DEFAULT_RELAY_URL, type RelayKind, resolveRelayKind } from "./browser/relay/kind";
42
44
  export type { Observation, ObservationEntry } from "./browser/tab-protocol";
43
45
 
44
46
  const DEFAULT_TAB_NAME = "main";
@@ -46,6 +48,7 @@ const DEFAULT_TAB_NAME = "main";
46
48
  const appSchema = type({
47
49
  "path?": type("string").describe("binary path to spawn"),
48
50
  "cdp_url?": type("string").describe("existing cdp endpoint"),
51
+ "relay?": type("boolean").describe("drive the user's own tabs via the omp browser relay"),
49
52
  "args?": type("string[]").describe("extra cli args"),
50
53
  "target?": type("string").describe("substring to pick a window"),
51
54
  });
@@ -95,7 +98,24 @@ function resolveBrowserKind(params: BrowserParams, session: ToolSession): Browse
95
98
  const exe = resolveToCwd(app.path, session.cwd);
96
99
  return { kind: "spawned", path: exe };
97
100
  }
98
- // A configured endpoint is a default, not an override: explicit app options win.
101
+ const relayUrl = session.settings.get("browser.relayUrl") as string | undefined;
102
+ // Explicit app.relay wins over every setting; PI_BROWSER_RELAY stays the
103
+ // final kill switch (a relay that is down would otherwise brick the tool).
104
+ if (app?.relay) {
105
+ const relayKind = resolveRelayKind({ settingEnabled: true, url: relayUrl });
106
+ if (relayKind) return relayKind;
107
+ }
108
+ // Relay before cdpUrl among settings: enabling the opt-out-by-default relay
109
+ // is a deliberate mode selection, while cdpUrl is a standing fallback
110
+ // endpoint. A configured endpoint is a default, not an override: explicit
111
+ // app options win.
112
+ if (app?.relay !== false) {
113
+ const relayKind = resolveRelayKind({
114
+ settingEnabled: session.settings.get("browser.relay") as boolean | undefined,
115
+ url: relayUrl,
116
+ });
117
+ if (relayKind) return relayKind;
118
+ }
99
119
  const configuredCdpUrl = (session.settings.get("browser.cdpUrl") as string | undefined)?.trim();
100
120
  if (configuredCdpUrl) {
101
121
  return { kind: "connected", cdpUrl: configuredCdpUrl.replace(/\/+$/, "") };
@@ -427,6 +447,8 @@ function describeBrowser(handle: BrowserHandle): string {
427
447
  return `spawned ${handle.kind.path} (pid ${handle.pid ?? "?"})`;
428
448
  case "connected":
429
449
  return `connected ${handle.cdpUrl ?? handle.kind.cdpUrl}`;
450
+ case "relay":
451
+ return `relay ${handle.cdpUrl ?? handle.kind.cdpUrl}`;
430
452
  }
431
453
  }
432
454
 
@@ -438,6 +460,8 @@ function describeKind(kind: BrowserKind): string {
438
460
  return `spawned:${kind.path}`;
439
461
  case "connected":
440
462
  return `connected:${kind.cdpUrl}`;
463
+ case "relay":
464
+ return `relay:${kind.cdpUrl}`;
441
465
  case "cmux":
442
466
  return `cmux:${kind.surface ?? "split"}`;
443
467
  }
@@ -448,6 +472,7 @@ function sameBrowserKind(a: BrowserKind, b: BrowserKind): boolean {
448
472
  if (a.kind === "headless" && b.kind === "headless") return a.headless === b.headless;
449
473
  if (a.kind === "spawned" && b.kind === "spawned") return a.path === b.path;
450
474
  if (a.kind === "connected" && b.kind === "connected") return a.cdpUrl === b.cdpUrl;
475
+ if (a.kind === "relay" && b.kind === "relay") return a.cdpUrl === b.cdpUrl;
451
476
  if (a.kind === "cmux" && b.kind === "cmux") return a.socketPath === b.socketPath;
452
477
  return false;
453
478
  }
@@ -1,38 +1,14 @@
1
1
  import type { Model } from "@oh-my-pi/pi-ai";
2
2
 
3
- export type ComputerExposureMode = "native" | "function" | "unavailable";
4
-
5
- export interface ComputerExposureOptions {
6
- azureBaseUrl?: string;
7
- azureResourceName?: string;
8
- }
9
-
10
- function isFirstPartyAzureEndpoint(baseUrl: string): boolean {
11
- try {
12
- const url = new URL(baseUrl);
13
- return (
14
- url.protocol === "https:" &&
15
- (url.hostname.endsWith(".openai.azure.com") || url.hostname === "models.inference.ai.azure.com")
16
- );
17
- } catch {
18
- return false;
19
- }
20
- }
21
-
22
- /** Match the provider transport's effective Computer Use tool representation. */
23
- export function computerExposureMode(
24
- model: Model | undefined,
25
- options?: ComputerExposureOptions,
26
- ): ComputerExposureMode {
27
- if (!model) return "unavailable";
28
- if (model.supportsComputerUse !== true) return "function";
29
- if (model.api !== "azure-openai-responses" || model.supportsComputerUseConfig !== undefined) return "native";
30
- const baseUrl =
31
- options?.azureBaseUrl?.trim() ||
32
- process.env.AZURE_OPENAI_BASE_URL?.trim() ||
33
- (options?.azureResourceName || process.env.AZURE_OPENAI_RESOURCE_NAME
34
- ? `https://${options?.azureResourceName || process.env.AZURE_OPENAI_RESOURCE_NAME}.openai.azure.com/openai/v1`
35
- : undefined) ||
36
- model.baseUrl;
37
- return baseUrl && isFirstPartyAzureEndpoint(baseUrl) ? "native" : "function";
3
+ /** How the computer tool is represented to the active model. */
4
+ export type ComputerExposureMode = "function" | "unavailable";
5
+
6
+ /**
7
+ * Report the computer tool's callable representation.
8
+ *
9
+ * Window selection is part of the tool's JSON schema, so even models with
10
+ * provider-native Computer Use support receive it as a function.
11
+ */
12
+ export function computerExposureMode(model: Model | undefined): ComputerExposureMode {
13
+ return model ? "function" : "unavailable";
38
14
  }
@@ -1,26 +1,67 @@
1
- import type { DesktopAction, DesktopCapabilities, DesktopCapture, DesktopSessionOptions } from "@oh-my-pi/pi-natives";
1
+ import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
2
+ import type { DesktopCapabilities } from "@oh-my-pi/pi-natives";
2
3
 
4
+ /** Hidden CLI selector that re-enters the computer worker host. */
3
5
  export const COMPUTER_WORKER_ARG = "__omp_worker_computer";
4
6
 
7
+ /** Frozen run settings transferred from the tool session to the worker. */
8
+ export interface ComputerSessionSnapshot {
9
+ cwd: string;
10
+ sessionId: string;
11
+ captureMaxWidth: number;
12
+ captureMaxHeight: number;
13
+ display: string;
14
+ readOnly: boolean;
15
+ }
16
+
17
+ /** Reply envelope for a session tool invoked by desktop JavaScript. */
18
+ export type ToolReply = { ok: true; value: unknown } | { ok: false; error: RunErrorPayload };
19
+
20
+ /** Commands accepted by the persistent computer worker. */
5
21
  export type ComputerWorkerInbound =
6
22
  | { type: "ping"; id: string }
7
- | { type: "init"; options: DesktopSessionOptions }
8
- | { type: "execute"; id: string; actions: DesktopAction[] }
23
+ | { type: "run"; id: string; code: string; timeoutMs: number; session: ComputerSessionSnapshot }
24
+ | { type: "abort"; id: string }
25
+ | { type: "tool-reply"; id: string; reply: ToolReply }
9
26
  | { type: "close" };
10
27
 
11
- export type ComputerWorkerOutbound =
12
- | { type: "pong"; id: string }
13
- | { type: "ready"; capabilities: DesktopCapabilities }
14
- | { type: "result"; id: string; capture: DesktopCapture; capabilities: DesktopCapabilities }
15
- | { type: "error"; id?: string; error: ComputerWorkerError }
16
- | { type: "closed" };
28
+ /** Successful computer run output returned to the tool supervisor. */
29
+ export interface ComputerRunOk {
30
+ displays: Array<TextContent | ImageContent>;
31
+ returnValue: unknown;
32
+ screenshots: ComputerScreenshot[];
33
+ capabilities?: DesktopCapabilities;
34
+ }
17
35
 
18
- export interface ComputerWorkerError {
36
+ /** Full-resolution screenshot emitted during one computer run. */
37
+ export interface ComputerScreenshot {
38
+ path: string;
39
+ width: number;
40
+ height: number;
41
+ sourceWidth?: number;
42
+ sourceHeight?: number;
43
+ target: string;
44
+ }
45
+
46
+ /** Clone-safe error metadata returned across the worker boundary. */
47
+ export interface RunErrorPayload {
19
48
  name: string;
20
49
  message: string;
21
50
  stack?: string;
51
+ isToolError: boolean;
52
+ isAbort: boolean;
22
53
  }
23
54
 
55
+ /** Events emitted by the persistent computer worker. */
56
+ export type ComputerWorkerOutbound =
57
+ | { type: "ready" }
58
+ | { type: "pong"; id: string }
59
+ | { type: "result"; id: string; ok: true; payload: ComputerRunOk }
60
+ | { type: "result"; id: string; ok: false; error: RunErrorPayload }
61
+ | { type: "tool-call"; id: string; runId: string; name: string; args: unknown }
62
+ | { type: "closed" };
63
+
64
+ /** Transport used by the worker core in Bun workers and tests. */
24
65
  export interface ComputerWorkerTransport {
25
66
  send(message: ComputerWorkerOutbound, transfer?: Bun.Transferable[]): void;
26
67
  onMessage(handler: (message: ComputerWorkerInbound) => void): () => void;