@wingman-ai/gateway 0.5.3 → 0.6.0

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 (327) hide show
  1. package/dist/agent/backend/filtered-backend.cjs +130 -0
  2. package/dist/agent/backend/filtered-backend.d.ts +10 -0
  3. package/dist/agent/backend/filtered-backend.js +87 -0
  4. package/dist/agent/config/agentConfig.cjs +4 -0
  5. package/dist/agent/config/agentConfig.d.ts +12 -0
  6. package/dist/agent/config/agentConfig.js +4 -0
  7. package/dist/agent/config/toolRegistry.cjs +75 -1
  8. package/dist/agent/config/toolRegistry.d.ts +3 -0
  9. package/dist/agent/config/toolRegistry.js +75 -1
  10. package/dist/agent/middleware/additional-messages.cjs +4 -1
  11. package/dist/agent/middleware/additional-messages.js +4 -1
  12. package/dist/agent/middleware/large-tool-results.cjs +207 -0
  13. package/dist/agent/middleware/large-tool-results.d.ts +16 -0
  14. package/dist/agent/middleware/large-tool-results.js +173 -0
  15. package/dist/agent/tools/browser_control.cjs +9 -1231
  16. package/dist/agent/tools/browser_control.d.ts +126 -234
  17. package/dist/agent/tools/browser_control.js +7 -1226
  18. package/dist/agent/tools/browser_runtime.cjs +1358 -0
  19. package/dist/agent/tools/browser_runtime.d.ts +617 -0
  20. package/dist/agent/tools/browser_runtime.js +1288 -0
  21. package/dist/agent/tools/browser_session.cjs +189 -0
  22. package/dist/agent/tools/browser_session.d.ts +686 -0
  23. package/dist/agent/tools/browser_session.js +146 -0
  24. package/dist/agent/tools/browser_session_manager.cjs +213 -0
  25. package/dist/agent/tools/browser_session_manager.d.ts +70 -0
  26. package/dist/agent/tools/browser_session_manager.js +176 -0
  27. package/dist/cli/commands/init.cjs +80 -98
  28. package/dist/cli/commands/init.js +80 -98
  29. package/dist/cli/config/loader.cjs +0 -5
  30. package/dist/cli/config/loader.js +0 -5
  31. package/dist/cli/config/schema.cjs +3 -7
  32. package/dist/cli/config/schema.d.ts +6 -6
  33. package/dist/cli/config/schema.js +3 -7
  34. package/dist/cli/core/agentInvoker.cjs +88 -22
  35. package/dist/cli/core/agentInvoker.d.ts +10 -3
  36. package/dist/cli/core/agentInvoker.js +88 -25
  37. package/dist/cli/core/outputManager.cjs +7 -2
  38. package/dist/cli/core/outputManager.d.ts +2 -2
  39. package/dist/cli/core/outputManager.js +7 -2
  40. package/dist/cli/core/sessionManager.cjs +208 -41
  41. package/dist/cli/core/sessionManager.d.ts +20 -0
  42. package/dist/cli/core/sessionManager.js +208 -41
  43. package/dist/cli/index.cjs +16 -1
  44. package/dist/cli/index.js +16 -1
  45. package/dist/cli/services/updateCheck.cjs +212 -0
  46. package/dist/cli/services/updateCheck.d.ts +26 -0
  47. package/dist/cli/services/updateCheck.js +166 -0
  48. package/dist/cli/types.d.ts +2 -1
  49. package/dist/gateway/server.cjs +7 -0
  50. package/dist/gateway/server.js +7 -0
  51. package/dist/webui/assets/index-D3x3G75t.css +11 -0
  52. package/dist/webui/assets/index-UpMmcU1f.js +215 -0
  53. package/dist/webui/index.html +2 -2
  54. package/package.json +12 -12
  55. package/templates/agents/README.md +3 -1
  56. package/templates/agents/coding/agent.md +6 -13
  57. package/templates/agents/coding-v2/agent.md +6 -1
  58. package/templates/agents/game-dev/agent.md +9 -2
  59. package/templates/agents/game-dev/game-designer.md +4 -0
  60. package/templates/agents/game-dev/scene-engineer.md +4 -0
  61. package/templates/agents/main/agent.md +7 -2
  62. package/templates/agents/researcher/agent.md +14 -3
  63. package/templates/agents/stock-trader/agent.md +4 -0
  64. package/dist/agent/tests/agentConfig.test.cjs +0 -224
  65. package/dist/agent/tests/agentConfig.test.d.ts +0 -1
  66. package/dist/agent/tests/agentConfig.test.js +0 -218
  67. package/dist/agent/tests/agentLoader.test.cjs +0 -335
  68. package/dist/agent/tests/agentLoader.test.d.ts +0 -1
  69. package/dist/agent/tests/agentLoader.test.js +0 -329
  70. package/dist/agent/tests/backgroundTerminal.test.cjs +0 -70
  71. package/dist/agent/tests/backgroundTerminal.test.d.ts +0 -1
  72. package/dist/agent/tests/backgroundTerminal.test.js +0 -64
  73. package/dist/agent/tests/browserControlHelpers.test.cjs +0 -35
  74. package/dist/agent/tests/browserControlHelpers.test.d.ts +0 -1
  75. package/dist/agent/tests/browserControlHelpers.test.js +0 -29
  76. package/dist/agent/tests/browserControlTool.test.cjs +0 -2117
  77. package/dist/agent/tests/browserControlTool.test.d.ts +0 -1
  78. package/dist/agent/tests/browserControlTool.test.js +0 -2111
  79. package/dist/agent/tests/commandExecuteTool.test.cjs +0 -29
  80. package/dist/agent/tests/commandExecuteTool.test.d.ts +0 -1
  81. package/dist/agent/tests/commandExecuteTool.test.js +0 -23
  82. package/dist/agent/tests/internet_search.test.cjs +0 -107
  83. package/dist/agent/tests/internet_search.test.d.ts +0 -1
  84. package/dist/agent/tests/internet_search.test.js +0 -101
  85. package/dist/agent/tests/mcpClientManager.test.cjs +0 -290
  86. package/dist/agent/tests/mcpClientManager.test.d.ts +0 -1
  87. package/dist/agent/tests/mcpClientManager.test.js +0 -284
  88. package/dist/agent/tests/mcpResourceTools.test.cjs +0 -101
  89. package/dist/agent/tests/mcpResourceTools.test.d.ts +0 -1
  90. package/dist/agent/tests/mcpResourceTools.test.js +0 -95
  91. package/dist/agent/tests/modelFactory.test.cjs +0 -190
  92. package/dist/agent/tests/modelFactory.test.d.ts +0 -1
  93. package/dist/agent/tests/modelFactory.test.js +0 -184
  94. package/dist/agent/tests/terminalSessionManager.test.cjs +0 -121
  95. package/dist/agent/tests/terminalSessionManager.test.d.ts +0 -1
  96. package/dist/agent/tests/terminalSessionManager.test.js +0 -115
  97. package/dist/agent/tests/test-agent-loader.cjs +0 -33
  98. package/dist/agent/tests/test-agent-loader.d.ts +0 -1
  99. package/dist/agent/tests/test-agent-loader.js +0 -27
  100. package/dist/agent/tests/test-subagent-loading.cjs +0 -99
  101. package/dist/agent/tests/test-subagent-loading.d.ts +0 -1
  102. package/dist/agent/tests/test-subagent-loading.js +0 -93
  103. package/dist/agent/tests/toolRegistry.test.cjs +0 -147
  104. package/dist/agent/tests/toolRegistry.test.d.ts +0 -1
  105. package/dist/agent/tests/toolRegistry.test.js +0 -141
  106. package/dist/agent/tests/uiRegistryTools.test.cjs +0 -114
  107. package/dist/agent/tests/uiRegistryTools.test.d.ts +0 -1
  108. package/dist/agent/tests/uiRegistryTools.test.js +0 -105
  109. package/dist/agent/tests/xaiImageModel.test.cjs +0 -194
  110. package/dist/agent/tests/xaiImageModel.test.d.ts +0 -1
  111. package/dist/agent/tests/xaiImageModel.test.js +0 -188
  112. package/dist/tests/additionalMessageMiddleware.test.cjs +0 -216
  113. package/dist/tests/additionalMessageMiddleware.test.d.ts +0 -1
  114. package/dist/tests/additionalMessageMiddleware.test.js +0 -188
  115. package/dist/tests/agent-config-voice.test.cjs +0 -25
  116. package/dist/tests/agent-config-voice.test.d.ts +0 -1
  117. package/dist/tests/agent-config-voice.test.js +0 -19
  118. package/dist/tests/agentInvokerAttachments.test.cjs +0 -190
  119. package/dist/tests/agentInvokerAttachments.test.d.ts +0 -1
  120. package/dist/tests/agentInvokerAttachments.test.js +0 -184
  121. package/dist/tests/agentInvokerSummarization.test.cjs +0 -613
  122. package/dist/tests/agentInvokerSummarization.test.d.ts +0 -1
  123. package/dist/tests/agentInvokerSummarization.test.js +0 -607
  124. package/dist/tests/agentInvokerTokenUsage.test.cjs +0 -124
  125. package/dist/tests/agentInvokerTokenUsage.test.d.ts +0 -1
  126. package/dist/tests/agentInvokerTokenUsage.test.js +0 -118
  127. package/dist/tests/agentInvokerWorkdir.test.cjs +0 -150
  128. package/dist/tests/agentInvokerWorkdir.test.d.ts +0 -1
  129. package/dist/tests/agentInvokerWorkdir.test.js +0 -122
  130. package/dist/tests/agents-api.test.cjs +0 -324
  131. package/dist/tests/agents-api.test.d.ts +0 -1
  132. package/dist/tests/agents-api.test.js +0 -318
  133. package/dist/tests/attachments-utils.test.cjs +0 -46
  134. package/dist/tests/attachments-utils.test.d.ts +0 -1
  135. package/dist/tests/attachments-utils.test.js +0 -40
  136. package/dist/tests/browser-command.test.cjs +0 -264
  137. package/dist/tests/browser-command.test.d.ts +0 -1
  138. package/dist/tests/browser-command.test.js +0 -258
  139. package/dist/tests/browser-relay-server.test.cjs +0 -20
  140. package/dist/tests/browser-relay-server.test.d.ts +0 -1
  141. package/dist/tests/browser-relay-server.test.js +0 -14
  142. package/dist/tests/bunSqliteAdapter.test.cjs +0 -265
  143. package/dist/tests/bunSqliteAdapter.test.d.ts +0 -1
  144. package/dist/tests/bunSqliteAdapter.test.js +0 -259
  145. package/dist/tests/candleRange.test.cjs +0 -48
  146. package/dist/tests/candleRange.test.d.ts +0 -1
  147. package/dist/tests/candleRange.test.js +0 -42
  148. package/dist/tests/cli-config-loader.test.cjs +0 -532
  149. package/dist/tests/cli-config-loader.test.d.ts +0 -1
  150. package/dist/tests/cli-config-loader.test.js +0 -526
  151. package/dist/tests/cli-config-warnings.test.cjs +0 -94
  152. package/dist/tests/cli-config-warnings.test.d.ts +0 -1
  153. package/dist/tests/cli-config-warnings.test.js +0 -88
  154. package/dist/tests/cli-init.test.cjs +0 -225
  155. package/dist/tests/cli-init.test.d.ts +0 -1
  156. package/dist/tests/cli-init.test.js +0 -219
  157. package/dist/tests/cli-workspace-root.test.cjs +0 -114
  158. package/dist/tests/cli-workspace-root.test.d.ts +0 -1
  159. package/dist/tests/cli-workspace-root.test.js +0 -108
  160. package/dist/tests/codex-credentials-precedence.test.cjs +0 -94
  161. package/dist/tests/codex-credentials-precedence.test.d.ts +0 -1
  162. package/dist/tests/codex-credentials-precedence.test.js +0 -88
  163. package/dist/tests/codex-provider.test.cjs +0 -383
  164. package/dist/tests/codex-provider.test.d.ts +0 -1
  165. package/dist/tests/codex-provider.test.js +0 -377
  166. package/dist/tests/config-json-schema.test.cjs +0 -37
  167. package/dist/tests/config-json-schema.test.d.ts +0 -1
  168. package/dist/tests/config-json-schema.test.js +0 -31
  169. package/dist/tests/discord-adapter.test.cjs +0 -89
  170. package/dist/tests/discord-adapter.test.d.ts +0 -1
  171. package/dist/tests/discord-adapter.test.js +0 -83
  172. package/dist/tests/falRuntime.test.cjs +0 -78
  173. package/dist/tests/falRuntime.test.d.ts +0 -1
  174. package/dist/tests/falRuntime.test.js +0 -72
  175. package/dist/tests/falSummary.test.cjs +0 -51
  176. package/dist/tests/falSummary.test.d.ts +0 -1
  177. package/dist/tests/falSummary.test.js +0 -45
  178. package/dist/tests/fs-api.test.cjs +0 -138
  179. package/dist/tests/fs-api.test.d.ts +0 -1
  180. package/dist/tests/fs-api.test.js +0 -132
  181. package/dist/tests/gateway-command-workspace.test.cjs +0 -150
  182. package/dist/tests/gateway-command-workspace.test.d.ts +0 -1
  183. package/dist/tests/gateway-command-workspace.test.js +0 -144
  184. package/dist/tests/gateway-http-security.test.cjs +0 -318
  185. package/dist/tests/gateway-http-security.test.d.ts +0 -1
  186. package/dist/tests/gateway-http-security.test.js +0 -312
  187. package/dist/tests/gateway-node-mode.test.cjs +0 -174
  188. package/dist/tests/gateway-node-mode.test.d.ts +0 -1
  189. package/dist/tests/gateway-node-mode.test.js +0 -168
  190. package/dist/tests/gateway-origin-policy.test.cjs +0 -82
  191. package/dist/tests/gateway-origin-policy.test.d.ts +0 -1
  192. package/dist/tests/gateway-origin-policy.test.js +0 -76
  193. package/dist/tests/gateway-request-execution-overrides.test.cjs +0 -42
  194. package/dist/tests/gateway-request-execution-overrides.test.d.ts +0 -1
  195. package/dist/tests/gateway-request-execution-overrides.test.js +0 -36
  196. package/dist/tests/gateway.test.cjs +0 -700
  197. package/dist/tests/gateway.test.d.ts +0 -1
  198. package/dist/tests/gateway.test.js +0 -694
  199. package/dist/tests/hooks-matcher.test.cjs +0 -309
  200. package/dist/tests/hooks-matcher.test.d.ts +0 -1
  201. package/dist/tests/hooks-matcher.test.js +0 -303
  202. package/dist/tests/hooks-merger.test.cjs +0 -528
  203. package/dist/tests/hooks-merger.test.d.ts +0 -1
  204. package/dist/tests/hooks-merger.test.js +0 -522
  205. package/dist/tests/imagePersistence.test.cjs +0 -169
  206. package/dist/tests/imagePersistence.test.d.ts +0 -1
  207. package/dist/tests/imagePersistence.test.js +0 -163
  208. package/dist/tests/integration/agent-invocation.integration.test.cjs +0 -264
  209. package/dist/tests/integration/agent-invocation.integration.test.d.ts +0 -1
  210. package/dist/tests/integration/agent-invocation.integration.test.js +0 -258
  211. package/dist/tests/integration/finnhub-candles.integration.test.cjs +0 -98
  212. package/dist/tests/integration/finnhub-candles.integration.test.d.ts +0 -1
  213. package/dist/tests/integration/finnhub-candles.integration.test.js +0 -92
  214. package/dist/tests/integration/summarization-e2e.integration.test.cjs +0 -127
  215. package/dist/tests/integration/summarization-e2e.integration.test.d.ts +0 -1
  216. package/dist/tests/integration/summarization-e2e.integration.test.js +0 -121
  217. package/dist/tests/logger.test.cjs +0 -353
  218. package/dist/tests/logger.test.d.ts +0 -1
  219. package/dist/tests/logger.test.js +0 -347
  220. package/dist/tests/mediaCompatibilityMiddleware.test.cjs +0 -106
  221. package/dist/tests/mediaCompatibilityMiddleware.test.d.ts +0 -1
  222. package/dist/tests/mediaCompatibilityMiddleware.test.js +0 -100
  223. package/dist/tests/node-tools.test.cjs +0 -77
  224. package/dist/tests/node-tools.test.d.ts +0 -1
  225. package/dist/tests/node-tools.test.js +0 -71
  226. package/dist/tests/nodes-api.test.cjs +0 -86
  227. package/dist/tests/nodes-api.test.d.ts +0 -1
  228. package/dist/tests/nodes-api.test.js +0 -80
  229. package/dist/tests/outputManagerContextSummarized.test.cjs +0 -43
  230. package/dist/tests/outputManagerContextSummarized.test.d.ts +0 -1
  231. package/dist/tests/outputManagerContextSummarized.test.js +0 -37
  232. package/dist/tests/provider-command-codex.test.cjs +0 -57
  233. package/dist/tests/provider-command-codex.test.d.ts +0 -1
  234. package/dist/tests/provider-command-codex.test.js +0 -51
  235. package/dist/tests/routines-api.test.cjs +0 -107
  236. package/dist/tests/routines-api.test.d.ts +0 -1
  237. package/dist/tests/routines-api.test.js +0 -101
  238. package/dist/tests/run-terminal-bench-official-script.test.cjs +0 -61
  239. package/dist/tests/run-terminal-bench-official-script.test.d.ts +0 -1
  240. package/dist/tests/run-terminal-bench-official-script.test.js +0 -55
  241. package/dist/tests/sessionManager-uionly.test.cjs +0 -50
  242. package/dist/tests/sessionManager-uionly.test.d.ts +0 -1
  243. package/dist/tests/sessionManager-uionly.test.js +0 -44
  244. package/dist/tests/sessionMessageAttachments.test.cjs +0 -197
  245. package/dist/tests/sessionMessageAttachments.test.d.ts +0 -1
  246. package/dist/tests/sessionMessageAttachments.test.js +0 -191
  247. package/dist/tests/sessionMessageRole.test.cjs +0 -44
  248. package/dist/tests/sessionMessageRole.test.d.ts +0 -1
  249. package/dist/tests/sessionMessageRole.test.js +0 -38
  250. package/dist/tests/sessionStateMessages.test.cjs +0 -236
  251. package/dist/tests/sessionStateMessages.test.d.ts +0 -1
  252. package/dist/tests/sessionStateMessages.test.js +0 -230
  253. package/dist/tests/sessions-api.test.cjs +0 -250
  254. package/dist/tests/sessions-api.test.d.ts +0 -1
  255. package/dist/tests/sessions-api.test.js +0 -244
  256. package/dist/tests/skill-activation.test.cjs +0 -86
  257. package/dist/tests/skill-activation.test.d.ts +0 -1
  258. package/dist/tests/skill-activation.test.js +0 -80
  259. package/dist/tests/skill-metadata.test.cjs +0 -119
  260. package/dist/tests/skill-metadata.test.d.ts +0 -1
  261. package/dist/tests/skill-metadata.test.js +0 -113
  262. package/dist/tests/skill-repository.test.cjs +0 -469
  263. package/dist/tests/skill-repository.test.d.ts +0 -1
  264. package/dist/tests/skill-repository.test.js +0 -463
  265. package/dist/tests/skill-security-scanner.test.cjs +0 -126
  266. package/dist/tests/skill-security-scanner.test.d.ts +0 -1
  267. package/dist/tests/skill-security-scanner.test.js +0 -120
  268. package/dist/tests/sms-api.test.cjs +0 -183
  269. package/dist/tests/sms-api.test.d.ts +0 -1
  270. package/dist/tests/sms-api.test.js +0 -177
  271. package/dist/tests/sms-commands.test.cjs +0 -90
  272. package/dist/tests/sms-commands.test.d.ts +0 -1
  273. package/dist/tests/sms-commands.test.js +0 -84
  274. package/dist/tests/sms-policy-store.test.cjs +0 -69
  275. package/dist/tests/sms-policy-store.test.d.ts +0 -1
  276. package/dist/tests/sms-policy-store.test.js +0 -63
  277. package/dist/tests/teams-adapter.test.cjs +0 -58
  278. package/dist/tests/teams-adapter.test.d.ts +0 -1
  279. package/dist/tests/teams-adapter.test.js +0 -52
  280. package/dist/tests/technicalIndicators.test.cjs +0 -82
  281. package/dist/tests/technicalIndicators.test.d.ts +0 -1
  282. package/dist/tests/technicalIndicators.test.js +0 -76
  283. package/dist/tests/terminal-bench-adapters-helpers.test.cjs +0 -64
  284. package/dist/tests/terminal-bench-adapters-helpers.test.d.ts +0 -1
  285. package/dist/tests/terminal-bench-adapters-helpers.test.js +0 -58
  286. package/dist/tests/terminal-bench-cleanup.test.cjs +0 -93
  287. package/dist/tests/terminal-bench-cleanup.test.d.ts +0 -1
  288. package/dist/tests/terminal-bench-cleanup.test.js +0 -87
  289. package/dist/tests/terminal-bench-config.test.cjs +0 -62
  290. package/dist/tests/terminal-bench-config.test.d.ts +0 -1
  291. package/dist/tests/terminal-bench-config.test.js +0 -56
  292. package/dist/tests/terminal-bench-official.test.cjs +0 -194
  293. package/dist/tests/terminal-bench-official.test.d.ts +0 -1
  294. package/dist/tests/terminal-bench-official.test.js +0 -188
  295. package/dist/tests/terminal-bench-runner.test.cjs +0 -82
  296. package/dist/tests/terminal-bench-runner.test.d.ts +0 -1
  297. package/dist/tests/terminal-bench-runner.test.js +0 -76
  298. package/dist/tests/terminal-bench-scoring.test.cjs +0 -128
  299. package/dist/tests/terminal-bench-scoring.test.d.ts +0 -1
  300. package/dist/tests/terminal-bench-scoring.test.js +0 -122
  301. package/dist/tests/terminalProbe.test.cjs +0 -45
  302. package/dist/tests/terminalProbe.test.d.ts +0 -1
  303. package/dist/tests/terminalProbe.test.js +0 -39
  304. package/dist/tests/terminalProbeAuth.test.cjs +0 -85
  305. package/dist/tests/terminalProbeAuth.test.d.ts +0 -1
  306. package/dist/tests/terminalProbeAuth.test.js +0 -79
  307. package/dist/tests/toolDisplayHelpers.test.cjs +0 -46
  308. package/dist/tests/toolDisplayHelpers.test.d.ts +0 -1
  309. package/dist/tests/toolDisplayHelpers.test.js +0 -40
  310. package/dist/tests/uv.test.cjs +0 -47
  311. package/dist/tests/uv.test.d.ts +0 -1
  312. package/dist/tests/uv.test.js +0 -41
  313. package/dist/tests/voice-config.test.cjs +0 -35
  314. package/dist/tests/voice-config.test.d.ts +0 -1
  315. package/dist/tests/voice-config.test.js +0 -29
  316. package/dist/tests/websocket-transport.test.cjs +0 -31
  317. package/dist/tests/websocket-transport.test.d.ts +0 -1
  318. package/dist/tests/websocket-transport.test.js +0 -25
  319. package/dist/tests/yahooCandles.test.cjs +0 -111
  320. package/dist/tests/yahooCandles.test.d.ts +0 -1
  321. package/dist/tests/yahooCandles.test.js +0 -105
  322. package/dist/tools/finance/optionsAnalytics.test.cjs +0 -128
  323. package/dist/tools/finance/optionsAnalytics.test.d.ts +0 -1
  324. package/dist/tools/finance/optionsAnalytics.test.js +0 -122
  325. package/dist/webui/assets/index-BMf95nv5.js +0 -215
  326. package/dist/webui/assets/index-DhJQ8Mbn.css +0 -11
  327. package/dist/webui/assets/wingman_logo-Cogyt3qm.webp +0 -0
@@ -0,0 +1,1288 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { array, boolean as external_zod_boolean, discriminatedUnion, enum as external_zod_enum, literal, number, object, string, union } from "zod";
7
+ import { createLogger } from "../../logger.js";
8
+ const logger = createLogger();
9
+ const DEVTOOLS_ENDPOINT_REGEX = /DevTools listening on (ws:\/\/\S+)/i;
10
+ const DEFAULT_ACTION_TIMEOUT_MS = 30000;
11
+ const MAX_ACTION_TIMEOUT_MS = 300000;
12
+ const DEFAULT_LAUNCH_TIMEOUT_MS = 15000;
13
+ const DEFAULT_RELAY_CONNECT_TIMEOUT_MS = 5000;
14
+ const DEFAULT_RELAY_HOST = "127.0.0.1";
15
+ const DEFAULT_RELAY_PORT = 18792;
16
+ const DEFAULT_MAX_EXTRACT_CHARS = 5000;
17
+ const MAX_EXTRACT_CHARS = 1000000;
18
+ const MAX_ACTIONS = 25;
19
+ const DEFAULT_PROFILES_ROOT = ".wingman/browser-profiles";
20
+ const DEFAULT_EXTENSIONS_ROOT = ".wingman/browser-extensions";
21
+ const DEFAULT_BROWSER_VIDEO_ROOT = ".wingman/browser/videos";
22
+ const DEFAULT_BUNDLED_EXTENSION_ID = "wingman";
23
+ const BUNDLED_EXTENSION_RELATIVE_PATH = "../../../extensions/wingman-browser-extension";
24
+ const PERSISTENT_PROFILE_IGNORE_DEFAULT_ARGS = [
25
+ "--password-store=basic",
26
+ "--use-mock-keychain"
27
+ ];
28
+ const PROFILE_LOCK_FILENAME = ".wingman-browser.lock";
29
+ const PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
30
+ const CHROME_COMMON_ARGS = [
31
+ "--disable-extensions",
32
+ "--disable-background-networking",
33
+ "--disable-default-apps",
34
+ "--no-default-browser-check",
35
+ "--no-first-run",
36
+ "--disable-sync",
37
+ "--disable-component-update",
38
+ "--mute-audio",
39
+ "--hide-scrollbars"
40
+ ];
41
+ const BrowserTransportPreferenceSchema = external_zod_enum([
42
+ "auto",
43
+ "playwright",
44
+ "relay"
45
+ ]);
46
+ const BrowserVideoSizeSchema = object({
47
+ width: number().int().min(1).max(3840).describe("Video width in pixels"),
48
+ height: number().int().min(1).max(3840).describe("Video height in pixels")
49
+ });
50
+ const BrowserSessionVideoRecordingSchema = union([
51
+ external_zod_boolean(),
52
+ object({
53
+ dir: string().min(1).optional().describe("Relative output directory within the workspace for videos"),
54
+ size: BrowserVideoSizeSchema.optional().describe("Optional recorded video dimensions")
55
+ })
56
+ ]);
57
+ const NavigateActionSchema = object({
58
+ type: literal("navigate"),
59
+ url: string().url().describe("Destination URL")
60
+ });
61
+ const NavigateAliasActionSchema = object({
62
+ type: literal("url"),
63
+ url: string().url().describe("Destination URL")
64
+ });
65
+ const NavigateOpenAliasActionSchema = object({
66
+ type: literal("open"),
67
+ url: string().url().describe("Destination URL")
68
+ });
69
+ const NavigateGotoAliasActionSchema = object({
70
+ type: literal("goto"),
71
+ url: string().url().describe("Destination URL")
72
+ });
73
+ const ClickActionSchema = object({
74
+ type: literal("click"),
75
+ selector: string().min(1).describe("CSS selector to click")
76
+ });
77
+ const TypeActionSchema = object({
78
+ type: literal("type"),
79
+ selector: string().min(1).describe("CSS selector to target"),
80
+ text: string().describe("Text value to enter"),
81
+ submit: external_zod_boolean().optional().default(false).describe("Press Enter after typing")
82
+ });
83
+ const PressActionSchema = object({
84
+ type: literal("press"),
85
+ key: string().min(1).describe("Keyboard key (for example Enter, Tab, ArrowDown)")
86
+ });
87
+ const WaitActionSchema = object({
88
+ type: literal("wait"),
89
+ ms: number().int().min(1).max(MAX_ACTION_TIMEOUT_MS).optional().describe("How long to wait in milliseconds"),
90
+ selector: string().min(1).optional().describe("Wait for this selector to become visible"),
91
+ url: string().min(1).optional().describe("Wait for this URL/glob pattern"),
92
+ load: external_zod_enum([
93
+ "load",
94
+ "domcontentloaded",
95
+ "networkidle"
96
+ ]).optional().describe("Wait for this load state"),
97
+ fn: string().min(1).optional().describe("Wait for this JavaScript predicate to become truthy"),
98
+ timeoutMs: number().int().min(1).max(MAX_ACTION_TIMEOUT_MS).optional().describe("Optional timeout override in milliseconds")
99
+ }).refine((action)=>Boolean(action.ms || action.selector || action.url || action.load || action.fn), {
100
+ message: "wait requires ms or one of selector/url/load/fn"
101
+ });
102
+ const WaitAliasActionSchema = object({
103
+ type: literal("ms"),
104
+ ms: number().int().min(1).max(MAX_ACTION_TIMEOUT_MS).describe("How long to wait in milliseconds")
105
+ });
106
+ const WaitSleepAliasActionSchema = object({
107
+ type: literal("sleep"),
108
+ ms: number().int().min(1).max(MAX_ACTION_TIMEOUT_MS).describe("How long to wait in milliseconds")
109
+ });
110
+ const WaitPauseAliasActionSchema = object({
111
+ type: literal("pause"),
112
+ ms: number().int().min(1).max(MAX_ACTION_TIMEOUT_MS).describe("How long to wait in milliseconds")
113
+ });
114
+ const WaitForActionBaseSchema = object({
115
+ selector: string().min(1).optional().describe("Wait for this selector to become visible"),
116
+ url: string().min(1).optional().describe("Wait for this URL/glob pattern"),
117
+ load: external_zod_enum([
118
+ "load",
119
+ "domcontentloaded",
120
+ "networkidle"
121
+ ]).optional().describe("Wait for this load state"),
122
+ fn: string().min(1).optional().describe("Wait for this JavaScript predicate to become truthy"),
123
+ timeoutMs: number().int().min(1).max(MAX_ACTION_TIMEOUT_MS).optional().describe("Optional timeout override in milliseconds")
124
+ });
125
+ const WaitForActionSchema = WaitForActionBaseSchema.extend({
126
+ type: literal("wait_for")
127
+ }).refine((action)=>Boolean(action.selector || action.url || action.load || action.fn), {
128
+ message: "wait_for requires at least one of selector/url/load/fn"
129
+ });
130
+ const WaitUntilAliasActionSchema = WaitForActionBaseSchema.extend({
131
+ type: literal("wait_until")
132
+ }).refine((action)=>Boolean(action.selector || action.url || action.load || action.fn), {
133
+ message: "wait_until requires at least one of selector/url/load/fn"
134
+ });
135
+ const ExtractTextActionSchema = object({
136
+ type: literal("extract_text"),
137
+ selector: string().min(1).optional().describe("Optional CSS selector; defaults to body"),
138
+ maxChars: number().int().min(1).max(MAX_EXTRACT_CHARS).optional().default(DEFAULT_MAX_EXTRACT_CHARS).describe("Maximum returned characters")
139
+ });
140
+ const ExtractTextAliasActionSchema = object({
141
+ type: literal("selector"),
142
+ selector: string().min(1).describe("CSS selector"),
143
+ maxChars: number().int().min(1).max(MAX_EXTRACT_CHARS).optional().default(DEFAULT_MAX_EXTRACT_CHARS).describe("Maximum returned characters")
144
+ });
145
+ const ExtractAliasActionSchema = object({
146
+ type: literal("extract"),
147
+ selector: string().min(1).optional().describe("Optional CSS selector; defaults to body"),
148
+ maxChars: number().int().min(1).max(MAX_EXTRACT_CHARS).optional().default(DEFAULT_MAX_EXTRACT_CHARS).describe("Maximum returned characters")
149
+ });
150
+ const GetContentAliasActionSchema = object({
151
+ type: literal("getContent"),
152
+ selector: string().min(1).optional().describe("Optional CSS selector; defaults to body"),
153
+ maxChars: number().int().min(1).max(MAX_EXTRACT_CHARS).optional().default(DEFAULT_MAX_EXTRACT_CHARS).describe("Maximum returned characters")
154
+ });
155
+ const GetContentSnakeAliasActionSchema = object({
156
+ type: literal("get_content"),
157
+ selector: string().min(1).optional().describe("Optional CSS selector; defaults to body"),
158
+ maxChars: number().int().min(1).max(MAX_EXTRACT_CHARS).optional().default(DEFAULT_MAX_EXTRACT_CHARS).describe("Maximum returned characters")
159
+ });
160
+ const QuerySelectorAliasActionSchema = object({
161
+ type: literal("querySelector"),
162
+ selector: string().min(1).optional().describe("Optional CSS selector; defaults to body"),
163
+ maxChars: number().int().min(1).max(MAX_EXTRACT_CHARS).optional().default(DEFAULT_MAX_EXTRACT_CHARS).describe("Maximum returned characters")
164
+ });
165
+ const QuerySelectorSnakeAliasActionSchema = object({
166
+ type: literal("query_selector"),
167
+ selector: string().min(1).optional().describe("Optional CSS selector; defaults to body"),
168
+ maxChars: number().int().min(1).max(MAX_EXTRACT_CHARS).optional().default(DEFAULT_MAX_EXTRACT_CHARS).describe("Maximum returned characters")
169
+ });
170
+ const ScreenshotActionSchema = object({
171
+ type: literal("screenshot"),
172
+ path: string().min(1).optional().describe("Relative output path within workspace"),
173
+ fullPage: external_zod_boolean().optional().default(true).describe("Capture the full page")
174
+ });
175
+ const ScreenshotAliasActionSchema = object({
176
+ type: literal("path"),
177
+ path: string().min(1).describe("Relative output path within workspace"),
178
+ fullPage: external_zod_boolean().optional().default(true).describe("Capture the full page")
179
+ });
180
+ const SnapshotAliasActionSchema = object({
181
+ type: literal("snapshot"),
182
+ path: string().min(1).describe("Relative output path within workspace"),
183
+ fullPage: external_zod_boolean().optional().default(true).describe("Capture the full page")
184
+ });
185
+ const CaptureAliasActionSchema = object({
186
+ type: literal("capture"),
187
+ path: string().min(1).describe("Relative output path within workspace"),
188
+ fullPage: external_zod_boolean().optional().default(true).describe("Capture the full page")
189
+ });
190
+ const EvaluateActionSchema = object({
191
+ type: literal("evaluate"),
192
+ expression: string().min(1).describe("JavaScript expression to evaluate in page context")
193
+ });
194
+ const EvaluateAliasActionSchema = object({
195
+ type: literal("expression"),
196
+ expression: string().min(1).describe("JavaScript expression to evaluate in page context")
197
+ });
198
+ const EvaluateJsAliasActionSchema = object({
199
+ type: literal("js"),
200
+ expression: string().min(1).describe("JavaScript expression to evaluate in page context")
201
+ });
202
+ const EvaluateScriptAliasActionSchema = object({
203
+ type: literal("script"),
204
+ expression: string().min(1).describe("JavaScript expression to evaluate in page context")
205
+ });
206
+ const BrowserActionSchema = discriminatedUnion("type", [
207
+ NavigateActionSchema,
208
+ NavigateAliasActionSchema,
209
+ NavigateOpenAliasActionSchema,
210
+ NavigateGotoAliasActionSchema,
211
+ ClickActionSchema,
212
+ TypeActionSchema,
213
+ PressActionSchema,
214
+ WaitActionSchema,
215
+ WaitAliasActionSchema,
216
+ WaitSleepAliasActionSchema,
217
+ WaitPauseAliasActionSchema,
218
+ WaitForActionSchema,
219
+ WaitUntilAliasActionSchema,
220
+ ExtractTextActionSchema,
221
+ ExtractTextAliasActionSchema,
222
+ ExtractAliasActionSchema,
223
+ GetContentAliasActionSchema,
224
+ GetContentSnakeAliasActionSchema,
225
+ QuerySelectorAliasActionSchema,
226
+ QuerySelectorSnakeAliasActionSchema,
227
+ ScreenshotActionSchema,
228
+ ScreenshotAliasActionSchema,
229
+ SnapshotAliasActionSchema,
230
+ CaptureAliasActionSchema,
231
+ EvaluateActionSchema,
232
+ EvaluateAliasActionSchema,
233
+ EvaluateJsAliasActionSchema,
234
+ EvaluateScriptAliasActionSchema
235
+ ]);
236
+ const BrowserControlInputSchema = object({
237
+ url: string().url().optional().describe("Optional initial URL to open"),
238
+ actions: array(BrowserActionSchema).max(MAX_ACTIONS).optional().default([]).describe("Ordered browser actions to execute"),
239
+ headless: external_zod_boolean().optional().describe("Launch browser in headless mode. Non-persistent runs default to headless; persistent browser profiles default to headed unless headless is explicitly requested."),
240
+ timeoutMs: number().int().min(1000).max(MAX_ACTION_TIMEOUT_MS).optional().default(DEFAULT_ACTION_TIMEOUT_MS).describe("Per-action timeout in milliseconds"),
241
+ executablePath: string().min(1).optional().describe("Optional path to Chrome/Chromium binary. Falls back to WINGMAN_CHROME_EXECUTABLE or common install paths."),
242
+ transport: BrowserTransportPreferenceSchema.optional().describe('Optional per-call browser transport override ("auto", "playwright", or "relay").')
243
+ });
244
+ const BrowserSessionActionInputSchema = object({
245
+ session_id: string().min(1).describe("Existing browser session id"),
246
+ url: string().url().optional().describe("Optional URL to open before actions"),
247
+ actions: array(BrowserActionSchema).max(MAX_ACTIONS).optional().default([]).describe("Ordered browser actions to execute in the existing session"),
248
+ timeoutMs: number().int().min(1000).max(MAX_ACTION_TIMEOUT_MS).optional().default(DEFAULT_ACTION_TIMEOUT_MS).describe("Per-action timeout in milliseconds")
249
+ });
250
+ const DEFAULT_BROWSER_CONTROL_DEPENDENCIES = {
251
+ importPlaywright: async ()=>await import("playwright-core"),
252
+ startChrome: startChromeWithDevtools,
253
+ resolveRelayWsEndpoint,
254
+ mkTempDir: ()=>mkdtempSync(join(tmpdir(), "wingman-browser-")),
255
+ removeDir: (target)=>rmSync(target, {
256
+ recursive: true,
257
+ force: true
258
+ }),
259
+ now: ()=>Date.now()
260
+ };
261
+ const createBrowserDependencies = (dependencies = {})=>({
262
+ ...DEFAULT_BROWSER_CONTROL_DEPENDENCIES,
263
+ ...dependencies
264
+ });
265
+ function getChromeCandidates() {
266
+ const candidates = [
267
+ process.env.WINGMAN_CHROME_EXECUTABLE,
268
+ "google-chrome",
269
+ "chromium-browser",
270
+ "chromium"
271
+ ].filter((candidate)=>Boolean(candidate?.trim()));
272
+ if ("darwin" === process.platform) candidates.push("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium");
273
+ if ("linux" === process.platform) candidates.push("/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium-browser", "/usr/bin/chromium");
274
+ if ("win32" === process.platform) candidates.push("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe");
275
+ return candidates;
276
+ }
277
+ function resolveBinaryFromPath(binaryName) {
278
+ const locator = "win32" === process.platform ? "where" : "which";
279
+ const result = spawnSync(locator, [
280
+ binaryName
281
+ ], {
282
+ encoding: "utf-8"
283
+ });
284
+ if (0 !== result.status || !result.stdout) return null;
285
+ const firstLine = result.stdout.split(/\r?\n/).map((line)=>line.trim()).find(Boolean);
286
+ return firstLine || null;
287
+ }
288
+ function resolveChromeExecutablePath(explicitPath) {
289
+ const candidatePool = explicitPath?.trim() ? [
290
+ explicitPath.trim()
291
+ ] : getChromeCandidates();
292
+ for (const candidate of candidatePool)if (candidate) {
293
+ if (isAbsolute(candidate) && existsSync(candidate)) return candidate;
294
+ if (!isAbsolute(candidate)) {
295
+ const fromPath = resolveBinaryFromPath(candidate);
296
+ if (fromPath) return fromPath;
297
+ }
298
+ }
299
+ if (explicitPath?.trim()) throw new Error(`Chrome executable not found at "${explicitPath}". Provide a valid executable path.`);
300
+ throw new Error("No Chrome/Chromium executable found. Install Chrome/Chromium or set WINGMAN_CHROME_EXECUTABLE.");
301
+ }
302
+ function waitForDevtoolsEndpoint(chromeProcess, launchTimeoutMs, userDataDir) {
303
+ return new Promise((resolveEndpoint, rejectEndpoint)=>{
304
+ let settled = false;
305
+ let logs = "";
306
+ const intervalHandle = setInterval(()=>{
307
+ const endpointFromFile = readDevtoolsEndpointFromFile(userDataDir);
308
+ if (!endpointFromFile) return;
309
+ finish(()=>resolveEndpoint(endpointFromFile));
310
+ }, 100);
311
+ const finish = (callback)=>{
312
+ if (settled) return;
313
+ settled = true;
314
+ clearTimeout(timeoutHandle);
315
+ clearInterval(intervalHandle);
316
+ chromeProcess.stdout.removeListener("data", onData);
317
+ chromeProcess.stderr.removeListener("data", onData);
318
+ chromeProcess.removeListener("error", onError);
319
+ chromeProcess.removeListener("exit", onExit);
320
+ callback();
321
+ };
322
+ const onData = (chunk)=>{
323
+ const text = chunk.toString();
324
+ logs += text;
325
+ const match = text.match(DEVTOOLS_ENDPOINT_REGEX);
326
+ if (!match?.[1]) return;
327
+ finish(()=>resolveEndpoint(match[1]));
328
+ };
329
+ const onError = (error)=>{
330
+ finish(()=>rejectEndpoint(new Error(`Failed to launch Chrome for CDP connection: ${error.message}`)));
331
+ };
332
+ const onExit = (code)=>{
333
+ finish(()=>rejectEndpoint(new Error(`Chrome exited before exposing DevTools endpoint (code: ${code ?? "unknown"}). Output: ${logs.trim() || "none"}`)));
334
+ };
335
+ const timeoutHandle = setTimeout(()=>{
336
+ finish(()=>rejectEndpoint(new Error(`Timed out waiting for DevTools endpoint after ${launchTimeoutMs}ms.`)));
337
+ }, launchTimeoutMs);
338
+ chromeProcess.stdout.on("data", onData);
339
+ chromeProcess.stderr.on("data", onData);
340
+ chromeProcess.on("error", onError);
341
+ chromeProcess.on("exit", onExit);
342
+ });
343
+ }
344
+ function readDevtoolsEndpointFromFile(userDataDir) {
345
+ const activePortPath = join(userDataDir, "DevToolsActivePort");
346
+ if (!existsSync(activePortPath)) return null;
347
+ try {
348
+ const raw = readFileSync(activePortPath, "utf-8").trim();
349
+ if (!raw) return null;
350
+ const [portLine, browserPathLine] = raw.split(/\r?\n/);
351
+ const port = Number.parseInt(portLine?.trim() || "", 10);
352
+ if (!Number.isFinite(port) || port <= 0) return null;
353
+ const rawPath = browserPathLine?.trim();
354
+ if (!rawPath) return null;
355
+ const browserPath = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
356
+ return `ws://127.0.0.1:${port}${browserPath}`;
357
+ } catch {
358
+ return null;
359
+ }
360
+ }
361
+ async function closeChromeProcess(chromeProcess) {
362
+ if (null !== chromeProcess.exitCode || chromeProcess.killed) return;
363
+ await new Promise((resolveClose)=>{
364
+ let resolved = false;
365
+ const finish = ()=>{
366
+ if (resolved) return;
367
+ resolved = true;
368
+ resolveClose();
369
+ };
370
+ const forceKillTimeout = setTimeout(()=>{
371
+ if (null === chromeProcess.exitCode && !chromeProcess.killed) chromeProcess.kill("SIGKILL");
372
+ }, 2000);
373
+ chromeProcess.once("exit", ()=>{
374
+ clearTimeout(forceKillTimeout);
375
+ finish();
376
+ });
377
+ try {
378
+ chromeProcess.kill("SIGTERM");
379
+ } catch {
380
+ clearTimeout(forceKillTimeout);
381
+ finish();
382
+ }
383
+ });
384
+ }
385
+ function clearStaleDevtoolsArtifacts(userDataDir) {
386
+ const activePortPath = join(userDataDir, "DevToolsActivePort");
387
+ try {
388
+ unlinkSync(activePortPath);
389
+ } catch {}
390
+ }
391
+ async function closeOptionalHandle(candidate) {
392
+ if (!candidate?.close) return;
393
+ try {
394
+ await candidate.close();
395
+ } catch {}
396
+ }
397
+ async function closeHandle(candidate) {
398
+ if (!candidate) return;
399
+ try {
400
+ await candidate.close();
401
+ } catch {}
402
+ }
403
+ async function startChromeWithDevtools(input) {
404
+ const executablePath = resolveChromeExecutablePath(input.executablePath);
405
+ clearStaleDevtoolsArtifacts(input.userDataDir);
406
+ const args = [
407
+ "--remote-debugging-port=0",
408
+ `--user-data-dir=${input.userDataDir}`,
409
+ ...input.chromeArgs || CHROME_COMMON_ARGS
410
+ ];
411
+ if (input.headless) args.push("--headless=new");
412
+ args.push("about:blank");
413
+ const chromeProcess = spawn(executablePath, args, {
414
+ stdio: [
415
+ "ignore",
416
+ "pipe",
417
+ "pipe"
418
+ ]
419
+ });
420
+ let wsEndpoint = "";
421
+ try {
422
+ wsEndpoint = await waitForDevtoolsEndpoint(chromeProcess, input.launchTimeoutMs, input.userDataDir);
423
+ } catch (error) {
424
+ await closeChromeProcess(chromeProcess);
425
+ throw error;
426
+ }
427
+ return {
428
+ wsEndpoint,
429
+ close: async ()=>{
430
+ await closeChromeProcess(chromeProcess);
431
+ }
432
+ };
433
+ }
434
+ function resolveBrowserTransportPreference(value) {
435
+ if ("playwright" === value || "relay" === value) return value;
436
+ return "auto";
437
+ }
438
+ function resolveRelayConfig(options) {
439
+ if (!options.relayConfig?.enabled) return null;
440
+ const host = (options.relayConfig.host || DEFAULT_RELAY_HOST).trim();
441
+ const port = Number.isInteger(options.relayConfig.port) ? Number(options.relayConfig.port) : DEFAULT_RELAY_PORT;
442
+ const requireAuth = false !== options.relayConfig.requireAuth;
443
+ const authToken = options.relayConfig.authToken?.trim() || void 0;
444
+ if (!host) throw new Error("Browser relay host cannot be empty.");
445
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`Invalid browser relay port: ${String(options.relayConfig.port)}`);
446
+ if (requireAuth && !authToken) throw new Error('Browser relay requires authToken. Run "wingman browser extension pair" and configure the extension token.');
447
+ return {
448
+ host,
449
+ port,
450
+ requireAuth,
451
+ authToken
452
+ };
453
+ }
454
+ async function resolveRelayWsEndpoint(config, timeoutMs) {
455
+ const relayHttpBase = `http://${config.host}:${config.port}`;
456
+ const controller = new AbortController();
457
+ const timer = setTimeout(()=>controller.abort(), timeoutMs);
458
+ try {
459
+ const versionResponse = await fetch(`${relayHttpBase}/json/version`, {
460
+ method: "GET",
461
+ signal: controller.signal
462
+ });
463
+ if (!versionResponse.ok) throw new Error(`Browser relay endpoint returned HTTP ${versionResponse.status}`);
464
+ const payload = await versionResponse.json();
465
+ if ("string" == typeof payload?.webSocketDebuggerUrl && payload.webSocketDebuggerUrl.trim()) return payload.webSocketDebuggerUrl.trim();
466
+ } catch (error) {
467
+ const suffix = error instanceof Error ? `: ${error.message}` : "";
468
+ throw new Error(`Failed to resolve browser relay endpoint${suffix}`);
469
+ } finally{
470
+ clearTimeout(timer);
471
+ }
472
+ const tokenParam = config.requireAuth && config.authToken ? `?token=${encodeURIComponent(config.authToken)}` : "";
473
+ return `ws://${config.host}:${config.port}/cdp${tokenParam}`;
474
+ }
475
+ function preferPersistentLaunch(options, isPersistentProfile) {
476
+ if ("boolean" == typeof options.preferPersistentLaunch) return options.preferPersistentLaunch;
477
+ return isPersistentProfile;
478
+ }
479
+ function resolveHeadlessMode(inputHeadless, isPersistentProfile) {
480
+ if ("boolean" == typeof inputHeadless) return inputHeadless;
481
+ return !isPersistentProfile;
482
+ }
483
+ function selectContext(contexts) {
484
+ if (0 === contexts.length) throw new Error("Failed to initialize browser context.");
485
+ const reversed = [
486
+ ...contexts
487
+ ].reverse();
488
+ return reversed.find((candidate)=>candidate.pages().length > 0) || reversed[0];
489
+ }
490
+ async function launchPersistentContext(playwright, userDataDir, executablePath, headless, startupTimeoutMs, chromeArgs, videoRecording) {
491
+ if ("function" != typeof playwright.chromium.launchPersistentContext) throw new Error("playwright-core runtime does not support launchPersistentContext.");
492
+ return playwright.chromium.launchPersistentContext(userDataDir, {
493
+ executablePath: resolveChromeExecutablePath(executablePath),
494
+ headless,
495
+ timeout: startupTimeoutMs,
496
+ args: chromeArgs,
497
+ ignoreDefaultArgs: PERSISTENT_PROFILE_IGNORE_DEFAULT_ARGS,
498
+ ...videoRecording ? {
499
+ recordVideo: {
500
+ dir: videoRecording.dirAbsolute,
501
+ ...videoRecording.size ? {
502
+ size: videoRecording.size
503
+ } : {}
504
+ }
505
+ } : {}
506
+ });
507
+ }
508
+ function validateExtensionId(extensionId) {
509
+ const normalized = extensionId.trim();
510
+ if (!normalized) throw new Error("Extension ID cannot be empty.");
511
+ if (!PROFILE_ID_PATTERN.test(normalized)) throw new Error(`Invalid extension ID "${extensionId}". Use letters, numbers, dot, underscore, or dash.`);
512
+ return normalized;
513
+ }
514
+ function resolveExtensionPath(workspace, extensionId, options) {
515
+ const mappedPath = options.extensionPaths?.[extensionId];
516
+ if (mappedPath?.trim()) {
517
+ const trimmed = mappedPath.trim();
518
+ return isAbsolute(trimmed) ? trimmed : resolve(workspace, trimmed);
519
+ }
520
+ const rootDir = options.extensionsRootDir?.trim() || DEFAULT_EXTENSIONS_ROOT;
521
+ const absoluteRoot = isAbsolute(rootDir) ? rootDir : resolve(workspace, rootDir);
522
+ return join(absoluteRoot, extensionId);
523
+ }
524
+ function resolveBundledExtensionSourcePath() {
525
+ const bundledPath = resolve(fileURLToPath(new URL(BUNDLED_EXTENSION_RELATIVE_PATH, import.meta.url)));
526
+ const bundledManifest = join(bundledPath, "manifest.json");
527
+ return existsSync(bundledManifest) ? bundledPath : null;
528
+ }
529
+ function ensureBundledWingmanExtension(extensionPath) {
530
+ const bundledSourcePath = resolveBundledExtensionSourcePath();
531
+ if (!bundledSourcePath) return false;
532
+ mkdirSync(dirname(extensionPath), {
533
+ recursive: true
534
+ });
535
+ cpSync(bundledSourcePath, extensionPath, {
536
+ recursive: true,
537
+ force: true
538
+ });
539
+ return true;
540
+ }
541
+ function resolveEnabledExtensions(workspace, options) {
542
+ const requestedIds = options.browserExtensions?.length ? options.browserExtensions : options.defaultExtensions;
543
+ if (!requestedIds?.length) return {
544
+ extensionIds: [],
545
+ extensionDirs: []
546
+ };
547
+ const uniqueIds = Array.from(new Set(requestedIds.map((value)=>validateExtensionId(value))));
548
+ const extensionDirs = uniqueIds.map((extensionId)=>{
549
+ const extensionPath = resolveExtensionPath(workspace, extensionId, options);
550
+ if (!existsSync(extensionPath)) {
551
+ if (extensionId === DEFAULT_BUNDLED_EXTENSION_ID && ensureBundledWingmanExtension(extensionPath)) logger.info(`browser_control auto-provisioned bundled extension "${extensionId}" at ${extensionPath}`);
552
+ }
553
+ if (!existsSync(extensionPath)) throw new Error(`Configured extension path does not exist for "${extensionId}": ${extensionPath}`);
554
+ const manifestPath = join(extensionPath, "manifest.json");
555
+ if (!existsSync(manifestPath)) throw new Error(`manifest.json not found for extension "${extensionId}" at ${extensionPath}`);
556
+ return extensionPath;
557
+ });
558
+ return {
559
+ extensionIds: uniqueIds,
560
+ extensionDirs
561
+ };
562
+ }
563
+ function buildChromeArgs(extensionDirs) {
564
+ if (0 === extensionDirs.length) return CHROME_COMMON_ARGS;
565
+ const joined = extensionDirs.join(",");
566
+ return [
567
+ ...CHROME_COMMON_ARGS.filter((arg)=>"--disable-extensions" !== arg),
568
+ `--disable-extensions-except=${joined}`,
569
+ `--load-extension=${joined}`
570
+ ];
571
+ }
572
+ function validateProfileId(profileId) {
573
+ const normalized = profileId.trim();
574
+ if (!normalized) throw new Error("browserProfile cannot be empty.");
575
+ if (!PROFILE_ID_PATTERN.test(normalized)) throw new Error(`Invalid browserProfile "${profileId}". Use letters, numbers, dot, underscore, or dash.`);
576
+ return normalized;
577
+ }
578
+ function resolveProfilePath(workspace, profileId, options) {
579
+ const normalizedProfileId = validateProfileId(profileId);
580
+ const mappedPath = options.profilePaths?.[normalizedProfileId];
581
+ if (mappedPath?.trim()) {
582
+ const trimmed = mappedPath.trim();
583
+ return isAbsolute(trimmed) ? trimmed : resolve(workspace, trimmed);
584
+ }
585
+ const rootDir = options.profilesRootDir?.trim() || DEFAULT_PROFILES_ROOT;
586
+ const absoluteRoot = isAbsolute(rootDir) ? rootDir : resolve(workspace, rootDir);
587
+ return join(absoluteRoot, normalizedProfileId);
588
+ }
589
+ function readProfileLockMetadata(lockPath) {
590
+ try {
591
+ const raw = readFileSync(lockPath, "utf-8");
592
+ const parsed = JSON.parse(raw);
593
+ if (!parsed || "object" != typeof parsed) return null;
594
+ return parsed;
595
+ } catch {
596
+ return null;
597
+ }
598
+ }
599
+ function isPidAlive(pid) {
600
+ if (!Number.isInteger(pid) || pid <= 0) return false;
601
+ try {
602
+ process.kill(pid, 0);
603
+ return true;
604
+ } catch (error) {
605
+ if (error && "object" == typeof error && "code" in error && "EPERM" === error.code) return true;
606
+ return false;
607
+ }
608
+ }
609
+ function acquireProfileLock(profileDir) {
610
+ const lockPath = join(profileDir, PROFILE_LOCK_FILENAME);
611
+ const writeLock = ()=>writeFileSync(lockPath, JSON.stringify({
612
+ pid: process.pid,
613
+ createdAt: new Date().toISOString()
614
+ }), {
615
+ flag: "wx"
616
+ });
617
+ try {
618
+ writeLock();
619
+ } catch (error) {
620
+ if (!(error instanceof Error && "code" in error && "EEXIST" === error.code)) throw error;
621
+ const lockMetadata = readProfileLockMetadata(lockPath);
622
+ if ("number" == typeof lockMetadata?.pid && lockMetadata.pid === process.pid) return ()=>{};
623
+ const stalePid = "number" == typeof lockMetadata?.pid && !isPidAlive(lockMetadata.pid);
624
+ if (!stalePid) throw new Error(`Browser profile is already in use: ${profileDir}. Wait for the other run to finish.`);
625
+ try {
626
+ unlinkSync(lockPath);
627
+ } catch {
628
+ throw new Error(`Browser profile is already in use: ${profileDir}. Wait for the other run to finish.`);
629
+ }
630
+ try {
631
+ writeLock();
632
+ } catch {
633
+ throw new Error(`Browser profile is already in use: ${profileDir}. Wait for the other run to finish.`);
634
+ }
635
+ }
636
+ let released = false;
637
+ return ()=>{
638
+ if (released) return;
639
+ released = true;
640
+ try {
641
+ unlinkSync(lockPath);
642
+ } catch {}
643
+ };
644
+ }
645
+ function resolveUserDataDir(workspace, options, dependencies) {
646
+ const configuredProfile = options.browserProfile?.trim();
647
+ if (!configuredProfile) return {
648
+ userDataDir: dependencies.mkTempDir(),
649
+ persistentProfile: false
650
+ };
651
+ const profileId = validateProfileId(configuredProfile);
652
+ const profileDir = resolveProfilePath(workspace, profileId, options);
653
+ mkdirSync(profileDir, {
654
+ recursive: true
655
+ });
656
+ const releaseLock = acquireProfileLock(profileDir);
657
+ return {
658
+ userDataDir: profileDir,
659
+ persistentProfile: true,
660
+ profileId,
661
+ releaseLock
662
+ };
663
+ }
664
+ function resolveWorkspaceRelativePath(workspace, targetPath) {
665
+ const absolutePath = resolve(workspace, targetPath);
666
+ const relativePath = relative(resolve(workspace), absolutePath);
667
+ if (relativePath.startsWith("..") || isAbsolute(relativePath)) throw new Error("Output path must stay inside the workspace.");
668
+ return absolutePath;
669
+ }
670
+ function resolveScreenshotPath(workspace, requestedPath, now, actionIndex) {
671
+ if (requestedPath && isAbsolute(requestedPath)) throw new Error("Screenshot path must be relative to the workspace.");
672
+ const fallback = join(".wingman", "browser", `screenshot-${now()}-${actionIndex + 1}.png`);
673
+ const relativeOutputPath = requestedPath || fallback;
674
+ const absoluteOutputPath = resolveWorkspaceRelativePath(workspace, relativeOutputPath);
675
+ mkdirSync(dirname(absoluteOutputPath), {
676
+ recursive: true
677
+ });
678
+ return {
679
+ absolute: absoluteOutputPath,
680
+ relative: relative(workspace, absoluteOutputPath).split("\\").join("/")
681
+ };
682
+ }
683
+ function toGatewayFileUrl(absolutePath) {
684
+ return `/api/fs/file?path=${encodeURIComponent(absolutePath)}`;
685
+ }
686
+ function resolveVideoOutputDir(workspace, requested, now) {
687
+ if (!requested) return null;
688
+ const dir = "object" == typeof requested && requested.dir?.trim() ? requested.dir.trim() : join(DEFAULT_BROWSER_VIDEO_ROOT, `recording-${now()}`);
689
+ if (isAbsolute(dir)) throw new Error("Video recording directory must be relative to the workspace.");
690
+ const dirAbsolute = resolveWorkspaceRelativePath(workspace, dir);
691
+ mkdirSync(dirAbsolute, {
692
+ recursive: true
693
+ });
694
+ return {
695
+ dirAbsolute,
696
+ dirRelative: relative(workspace, dirAbsolute).split("\\").join("/"),
697
+ ..."object" == typeof requested && requested.size ? {
698
+ size: requested.size
699
+ } : {}
700
+ };
701
+ }
702
+ function resolveVideoMimeType(pathname) {
703
+ const extension = extname(pathname).trim().toLowerCase();
704
+ switch(extension){
705
+ case ".mp4":
706
+ return "video/mp4";
707
+ case ".mov":
708
+ return "video/quicktime";
709
+ case ".webm":
710
+ default:
711
+ return "video/webm";
712
+ }
713
+ }
714
+ function buildScreenshotMedia(workspace, actionResults) {
715
+ return actionResults.flatMap((result, index)=>{
716
+ if ("screenshot" !== result.type || "string" != typeof result.path || !result.path.trim()) return [];
717
+ const relativePath = result.path.trim();
718
+ const absolutePath = "string" == typeof result.absolutePath && result.absolutePath.trim() ? result.absolutePath.trim() : resolveWorkspaceRelativePath(workspace, relativePath);
719
+ const uri = "string" == typeof result.uri && result.uri.trim() ? result.uri.trim() : toGatewayFileUrl(absolutePath);
720
+ return [
721
+ {
722
+ kind: "image",
723
+ source: "screenshot",
724
+ path: absolutePath,
725
+ relativePath,
726
+ uri,
727
+ url: uri,
728
+ mimeType: "image/png",
729
+ name: basename(absolutePath),
730
+ ..."boolean" == typeof result.fullPage ? {
731
+ fullPage: result.fullPage
732
+ } : {},
733
+ actionIndex: index
734
+ }
735
+ ];
736
+ });
737
+ }
738
+ function summarizeVideoRecording(recording, state, videoCount = 0) {
739
+ if (!recording) return null;
740
+ return {
741
+ enabled: true,
742
+ state,
743
+ dir: recording.dirRelative,
744
+ size: recording.size || null,
745
+ videoCount
746
+ };
747
+ }
748
+ async function registerPageVideoHandle(runtime, page) {
749
+ if (!runtime.videoRecording || "function" != typeof page.video) return;
750
+ try {
751
+ const video = page.video();
752
+ if (video) runtime.videoRecording.handles.add(video);
753
+ } catch {}
754
+ }
755
+ async function finalizeRecordedVideos(runtime) {
756
+ if (!runtime.videoRecording || 0 === runtime.videoRecording.handles.size) return [];
757
+ const seen = new Set();
758
+ const media = [];
759
+ for (const handle of runtime.videoRecording.handles)try {
760
+ const absolutePath = (await handle.path()).trim();
761
+ if (!absolutePath) continue;
762
+ const relativePath = relative(runtime.workspace, absolutePath).split("\\").join("/");
763
+ if (!relativePath || relativePath.startsWith("..") || isAbsolute(relativePath) || seen.has(relativePath)) continue;
764
+ seen.add(relativePath);
765
+ const uri = toGatewayFileUrl(absolutePath);
766
+ media.push({
767
+ kind: "video",
768
+ source: "video-recording",
769
+ path: absolutePath,
770
+ relativePath,
771
+ uri,
772
+ url: uri,
773
+ mimeType: resolveVideoMimeType(absolutePath),
774
+ name: basename(absolutePath)
775
+ });
776
+ } catch {}
777
+ return media;
778
+ }
779
+ function serializeEvaluation(value) {
780
+ if ("string" == typeof value || "number" == typeof value || "boolean" == typeof value || null === value) return value;
781
+ try {
782
+ return JSON.parse(JSON.stringify(value));
783
+ } catch {
784
+ return String(value);
785
+ }
786
+ }
787
+ function globToRegex(globPattern) {
788
+ let regex = "^";
789
+ for(let index = 0; index < globPattern.length; index += 1){
790
+ const current = globPattern[index];
791
+ const next = globPattern[index + 1];
792
+ if ("*" === current && "*" === next) {
793
+ regex += ".*";
794
+ index += 1;
795
+ continue;
796
+ }
797
+ if ("*" === current) {
798
+ regex += "[^/]*";
799
+ continue;
800
+ }
801
+ regex += current.replace(/[-/\\^$+?.()|[\]{}]/g, "\\$&");
802
+ }
803
+ regex += "$";
804
+ return new RegExp(regex);
805
+ }
806
+ async function waitForUrlFallback(page, urlPattern, timeoutMs) {
807
+ const regex = globToRegex(urlPattern);
808
+ const startedAt = Date.now();
809
+ while(Date.now() - startedAt < timeoutMs){
810
+ if (regex.test(page.url())) return;
811
+ await page.waitForTimeout(100);
812
+ }
813
+ throw new Error(`Timed out waiting for URL pattern "${urlPattern}".`);
814
+ }
815
+ async function waitForPredicateFallback(page, expression, timeoutMs) {
816
+ const startedAt = Date.now();
817
+ while(Date.now() - startedAt < timeoutMs){
818
+ const result = await page.evaluate(expression);
819
+ if (result) return;
820
+ await page.waitForTimeout(100);
821
+ }
822
+ throw new Error("Timed out waiting for JavaScript predicate to become truthy.");
823
+ }
824
+ async function runConditionalWait(page, action, timeoutMs) {
825
+ const waitTimeoutMs = action.timeoutMs ?? timeoutMs;
826
+ if (action.selector) if ("function" == typeof page.waitForSelector) await page.waitForSelector(action.selector, {
827
+ state: "visible",
828
+ timeout: waitTimeoutMs
829
+ });
830
+ else await page.textContent(action.selector, {
831
+ timeout: waitTimeoutMs
832
+ });
833
+ if (action.url) if ("function" == typeof page.waitForURL) await page.waitForURL(globToRegex(action.url), {
834
+ timeout: waitTimeoutMs
835
+ });
836
+ else await waitForUrlFallback(page, action.url, waitTimeoutMs);
837
+ if (action.load && "function" == typeof page.waitForLoadState) await page.waitForLoadState(action.load, {
838
+ timeout: waitTimeoutMs
839
+ });
840
+ if (action.fn) if ("function" == typeof page.waitForFunction) await page.waitForFunction(action.fn, void 0, {
841
+ timeout: waitTimeoutMs
842
+ });
843
+ else await waitForPredicateFallback(page, action.fn, waitTimeoutMs);
844
+ return {
845
+ type: "wait_for",
846
+ selector: action.selector || null,
847
+ url: action.url || null,
848
+ load: action.load || null,
849
+ fn: action.fn || null,
850
+ timeoutMs: waitTimeoutMs
851
+ };
852
+ }
853
+ async function runBrowserAction(page, action, timeoutMs, workspace, now, actionIndex) {
854
+ switch(action.type){
855
+ case "navigate":
856
+ case "url":
857
+ case "open":
858
+ case "goto":
859
+ await page.goto(action.url, {
860
+ waitUntil: "domcontentloaded",
861
+ timeout: timeoutMs
862
+ });
863
+ return {
864
+ type: "navigate",
865
+ url: action.url
866
+ };
867
+ case "click":
868
+ await page.click(action.selector, {
869
+ timeout: timeoutMs
870
+ });
871
+ return {
872
+ type: action.type,
873
+ selector: action.selector
874
+ };
875
+ case "type":
876
+ await page.fill(action.selector, action.text, {
877
+ timeout: timeoutMs
878
+ });
879
+ if (action.submit) await page.keyboard.press("Enter");
880
+ return {
881
+ type: action.type,
882
+ selector: action.selector,
883
+ textLength: action.text.length,
884
+ submit: Boolean(action.submit)
885
+ };
886
+ case "press":
887
+ await page.keyboard.press(action.key);
888
+ return {
889
+ type: action.type,
890
+ key: action.key
891
+ };
892
+ case "wait":
893
+ if ("number" == typeof action.ms) {
894
+ await page.waitForTimeout(action.ms);
895
+ return {
896
+ type: action.type,
897
+ ms: action.ms
898
+ };
899
+ }
900
+ return runConditionalWait(page, {
901
+ type: "wait_for",
902
+ selector: action.selector,
903
+ url: action.url,
904
+ load: action.load,
905
+ fn: action.fn,
906
+ timeoutMs: action.timeoutMs
907
+ }, timeoutMs);
908
+ case "ms":
909
+ case "sleep":
910
+ case "pause":
911
+ await page.waitForTimeout(action.ms);
912
+ return {
913
+ type: "wait",
914
+ ms: action.ms
915
+ };
916
+ case "wait_for":
917
+ case "wait_until":
918
+ return runConditionalWait(page, action, timeoutMs);
919
+ case "extract_text":
920
+ case "extract":
921
+ case "getContent":
922
+ case "get_content":
923
+ case "querySelector":
924
+ case "query_selector":
925
+ {
926
+ const selector = "selector" in action && action.selector ? action.selector : "body";
927
+ const content = await page.textContent(selector, {
928
+ timeout: timeoutMs
929
+ });
930
+ const text = content || "";
931
+ const maxChars = action.maxChars ?? DEFAULT_MAX_EXTRACT_CHARS;
932
+ return {
933
+ type: "extract_text",
934
+ selector,
935
+ text: text.slice(0, maxChars),
936
+ truncated: text.length > maxChars
937
+ };
938
+ }
939
+ case "selector":
940
+ {
941
+ const content = await page.textContent(action.selector, {
942
+ timeout: timeoutMs
943
+ });
944
+ const text = content || "";
945
+ const maxChars = action.maxChars ?? DEFAULT_MAX_EXTRACT_CHARS;
946
+ return {
947
+ type: "extract_text",
948
+ selector: action.selector,
949
+ text: text.slice(0, maxChars),
950
+ truncated: text.length > maxChars
951
+ };
952
+ }
953
+ case "screenshot":
954
+ case "path":
955
+ case "snapshot":
956
+ case "capture":
957
+ {
958
+ const screenshotPath = resolveScreenshotPath(workspace, action.path, now, actionIndex);
959
+ await page.screenshot({
960
+ path: screenshotPath.absolute,
961
+ fullPage: action.fullPage
962
+ });
963
+ return {
964
+ type: "screenshot",
965
+ path: screenshotPath.relative,
966
+ absolutePath: screenshotPath.absolute,
967
+ uri: toGatewayFileUrl(screenshotPath.absolute),
968
+ fullPage: Boolean(action.fullPage)
969
+ };
970
+ }
971
+ case "evaluate":
972
+ case "expression":
973
+ case "js":
974
+ case "script":
975
+ {
976
+ const result = await page.evaluate(action.expression);
977
+ return {
978
+ type: "evaluate",
979
+ expression: action.expression,
980
+ result: serializeEvaluation(result)
981
+ };
982
+ }
983
+ default:
984
+ throw new Error("Unsupported browser action type.");
985
+ }
986
+ }
987
+ const resolveToolOptions = (options)=>{
988
+ const workspace = options.workspace || process.cwd();
989
+ return {
990
+ ...options,
991
+ workspace,
992
+ configWorkspace: options.configWorkspace || workspace,
993
+ launchTimeoutMs: options.launchTimeoutMs || DEFAULT_LAUNCH_TIMEOUT_MS
994
+ };
995
+ };
996
+ async function openBrowserSessionRuntime(options = {}, dependencies = {}, input = {}) {
997
+ const resolvedOptions = resolveToolOptions(options);
998
+ const workspace = resolvedOptions.workspace;
999
+ const configWorkspace = resolvedOptions.configWorkspace;
1000
+ const deps = createBrowserDependencies(dependencies);
1001
+ let userDataDirSelection = null;
1002
+ let chromeSession = null;
1003
+ let browser = null;
1004
+ let launchedContext = null;
1005
+ let browserTransport = "cdp";
1006
+ let transportFallbackReason = null;
1007
+ let reusedExistingCdpSession = false;
1008
+ let context = null;
1009
+ let videoRecording = null;
1010
+ try {
1011
+ userDataDirSelection = resolveUserDataDir(configWorkspace, resolvedOptions, deps);
1012
+ const selectedUserDataDir = userDataDirSelection;
1013
+ const headless = resolveHeadlessMode(input.headless, selectedUserDataDir.persistentProfile);
1014
+ const timeoutMs = input.timeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS;
1015
+ const startupTimeoutMs = Math.max(resolvedOptions.launchTimeoutMs, timeoutMs);
1016
+ const playwright = await deps.importPlaywright();
1017
+ const relayConfig = resolveRelayConfig(resolvedOptions);
1018
+ const executablePath = input.executablePath || resolvedOptions.defaultExecutablePath;
1019
+ const { extensionIds, extensionDirs } = resolveEnabledExtensions(configWorkspace, resolvedOptions);
1020
+ const chromeArgs = buildChromeArgs(extensionDirs);
1021
+ const requestedVideoRecording = resolveVideoOutputDir(workspace, input.recordVideo, deps.now);
1022
+ const usePersistentLaunch = preferPersistentLaunch(resolvedOptions, selectedUserDataDir.persistentProfile);
1023
+ const effectivePersistentLaunch = usePersistentLaunch || Boolean(requestedVideoRecording);
1024
+ const transportRequested = resolveBrowserTransportPreference(input.transport || resolvedOptions.browserTransport);
1025
+ if (requestedVideoRecording && "relay" === transportRequested) throw new Error("Video recording is only supported with Playwright persistent launch sessions.");
1026
+ if (requestedVideoRecording) videoRecording = {
1027
+ ...requestedVideoRecording,
1028
+ handles: new Set()
1029
+ };
1030
+ const closeTransientSessions = async ()=>{
1031
+ if (launchedContext?.close) {
1032
+ try {
1033
+ await launchedContext.close();
1034
+ } catch {}
1035
+ launchedContext = null;
1036
+ }
1037
+ if (browser) {
1038
+ if (!reusedExistingCdpSession) try {
1039
+ await browser.close();
1040
+ } catch {}
1041
+ browser = null;
1042
+ }
1043
+ if (chromeSession) {
1044
+ try {
1045
+ await chromeSession.close();
1046
+ } catch {}
1047
+ chromeSession = null;
1048
+ }
1049
+ context = null;
1050
+ reusedExistingCdpSession = false;
1051
+ };
1052
+ const initializeViaRelay = async (connectTimeoutMs)=>{
1053
+ if (!relayConfig) throw new Error("Browser relay transport requested but browser.relay.enabled is false.");
1054
+ const relayWsEndpoint = await deps.resolveRelayWsEndpoint(relayConfig, connectTimeoutMs);
1055
+ browser = await playwright.chromium.connectOverCDP(relayWsEndpoint, {
1056
+ timeout: connectTimeoutMs
1057
+ });
1058
+ context = selectContext(browser.contexts());
1059
+ browserTransport = "relay-cdp";
1060
+ reusedExistingCdpSession = false;
1061
+ };
1062
+ const initializeViaPlaywright = async ()=>{
1063
+ if (effectivePersistentLaunch) {
1064
+ launchedContext = await launchPersistentContext(playwright, selectedUserDataDir.userDataDir, executablePath, headless, startupTimeoutMs, chromeArgs, videoRecording);
1065
+ context = launchedContext;
1066
+ browserTransport = "persistent-context";
1067
+ return;
1068
+ }
1069
+ if (selectedUserDataDir.persistentProfile) {
1070
+ const existingWsEndpoint = readDevtoolsEndpointFromFile(selectedUserDataDir.userDataDir);
1071
+ if (existingWsEndpoint) try {
1072
+ browser = await playwright.chromium.connectOverCDP(existingWsEndpoint, {
1073
+ timeout: startupTimeoutMs
1074
+ });
1075
+ context = selectContext(browser.contexts());
1076
+ reusedExistingCdpSession = true;
1077
+ } catch (reuseError) {
1078
+ logger.warn(`browser_control failed to attach to existing CDP endpoint, launching a fresh session: ${reuseError instanceof Error ? reuseError.message : String(reuseError)}`);
1079
+ if (browser) {
1080
+ try {
1081
+ await browser.close();
1082
+ } catch {}
1083
+ browser = null;
1084
+ }
1085
+ }
1086
+ }
1087
+ if (!context) {
1088
+ chromeSession = await deps.startChrome({
1089
+ executablePath,
1090
+ headless,
1091
+ launchTimeoutMs: startupTimeoutMs,
1092
+ userDataDir: selectedUserDataDir.userDataDir,
1093
+ chromeArgs
1094
+ });
1095
+ try {
1096
+ browser = await playwright.chromium.connectOverCDP(chromeSession.wsEndpoint, {
1097
+ timeout: startupTimeoutMs
1098
+ });
1099
+ context = selectContext(browser.contexts());
1100
+ } catch (cdpError) {
1101
+ const cdpMessage = cdpError instanceof Error ? cdpError.message : String(cdpError);
1102
+ if (browser) {
1103
+ try {
1104
+ await browser.close();
1105
+ } catch {}
1106
+ browser = null;
1107
+ }
1108
+ if (chromeSession) {
1109
+ try {
1110
+ await chromeSession.close();
1111
+ } catch {}
1112
+ chromeSession = null;
1113
+ }
1114
+ if (selectedUserDataDir.persistentProfile) {
1115
+ logger.warn(`browser_control CDP connection failed for persistent profile, retrying CDP once: ${cdpMessage}`);
1116
+ try {
1117
+ chromeSession = await deps.startChrome({
1118
+ executablePath,
1119
+ headless,
1120
+ launchTimeoutMs: startupTimeoutMs,
1121
+ userDataDir: selectedUserDataDir.userDataDir,
1122
+ chromeArgs
1123
+ });
1124
+ browser = await playwright.chromium.connectOverCDP(chromeSession.wsEndpoint, {
1125
+ timeout: startupTimeoutMs
1126
+ });
1127
+ context = selectContext(browser.contexts());
1128
+ } catch (retryError) {
1129
+ const retryMessage = retryError instanceof Error ? retryError.message : String(retryError);
1130
+ if (browser) {
1131
+ try {
1132
+ await browser.close();
1133
+ } catch {}
1134
+ browser = null;
1135
+ }
1136
+ if (chromeSession) {
1137
+ try {
1138
+ await chromeSession.close();
1139
+ } catch {}
1140
+ chromeSession = null;
1141
+ }
1142
+ const launchPersistent = playwright.chromium.launchPersistentContext;
1143
+ if ("function" != typeof launchPersistent) throw new Error(`CDP connection failed for persistent profile after retry. Initial error: ${cdpMessage}. Retry error: ${retryMessage}`);
1144
+ logger.warn(`browser_control CDP retry failed for persistent profile, falling back to persistent launch: ${retryMessage}`);
1145
+ launchedContext = await launchPersistentContext(playwright, selectedUserDataDir.userDataDir, executablePath, headless, startupTimeoutMs, chromeArgs, videoRecording);
1146
+ context = launchedContext;
1147
+ browserTransport = "persistent-context";
1148
+ }
1149
+ } else {
1150
+ const launchPersistent = playwright.chromium.launchPersistentContext;
1151
+ if ("function" != typeof launchPersistent) throw cdpError;
1152
+ logger.warn(`browser_control CDP connection failed, retrying with persistent launch: ${cdpMessage}`);
1153
+ launchedContext = await launchPersistentContext(playwright, selectedUserDataDir.userDataDir, executablePath, headless, startupTimeoutMs, chromeArgs, videoRecording);
1154
+ context = launchedContext;
1155
+ browserTransport = "persistent-context";
1156
+ }
1157
+ }
1158
+ }
1159
+ };
1160
+ if ("relay" === transportRequested) await initializeViaRelay(startupTimeoutMs);
1161
+ else try {
1162
+ await initializeViaPlaywright();
1163
+ } catch (playwrightError) {
1164
+ if ("auto" !== transportRequested || !relayConfig || requestedVideoRecording) throw playwrightError;
1165
+ await closeTransientSessions();
1166
+ const relayTimeoutMs = Math.min(startupTimeoutMs, DEFAULT_RELAY_CONNECT_TIMEOUT_MS);
1167
+ const playwrightMessage = playwrightError instanceof Error ? playwrightError.message : String(playwrightError);
1168
+ logger.warn(`browser_control playwright initialization failed in auto mode, falling back to relay: ${playwrightMessage}`);
1169
+ await initializeViaRelay(relayTimeoutMs);
1170
+ transportFallbackReason = `playwright initialization failed: ${playwrightMessage}`;
1171
+ }
1172
+ const resolvedContext = context;
1173
+ if (!resolvedContext) throw new Error("Failed to initialize browser context.");
1174
+ return {
1175
+ workspace,
1176
+ configWorkspace,
1177
+ now: deps.now,
1178
+ headless,
1179
+ context: resolvedContext,
1180
+ activePage: null,
1181
+ userDataDirSelection: selectedUserDataDir,
1182
+ browserTransport,
1183
+ transportRequested,
1184
+ transportFallbackReason,
1185
+ reusedExistingCdpSession,
1186
+ extensionIds,
1187
+ launchedContext,
1188
+ browser,
1189
+ chromeSession,
1190
+ videoRecording,
1191
+ closed: false
1192
+ };
1193
+ } catch (error) {
1194
+ await closeOptionalHandle(launchedContext);
1195
+ if (!reusedExistingCdpSession) await closeHandle(browser);
1196
+ await closeHandle(chromeSession);
1197
+ if (userDataDirSelection?.releaseLock) userDataDirSelection.releaseLock();
1198
+ if (userDataDirSelection && !userDataDirSelection.persistentProfile) deps.removeDir(userDataDirSelection.userDataDir);
1199
+ throw error;
1200
+ }
1201
+ }
1202
+ function getPageUrl(page) {
1203
+ try {
1204
+ return page.url().trim();
1205
+ } catch {
1206
+ return "";
1207
+ }
1208
+ }
1209
+ function isBrowserInternalPageUrl(url) {
1210
+ const normalized = url.trim().toLowerCase();
1211
+ if (!normalized) return true;
1212
+ return normalized.startsWith("about:blank") || normalized.startsWith("chrome://") || normalized.startsWith("chrome-extension://") || normalized.startsWith("devtools://") || normalized.startsWith("edge://") || normalized.startsWith("extension://") || normalized.startsWith("moz-extension://");
1213
+ }
1214
+ function selectPreferredPage(pages) {
1215
+ for(let index = pages.length - 1; index >= 0; index -= 1){
1216
+ const candidate = pages[index];
1217
+ if (candidate) {
1218
+ if (!isBrowserInternalPageUrl(getPageUrl(candidate))) return candidate;
1219
+ }
1220
+ }
1221
+ return pages.at(-1) || null;
1222
+ }
1223
+ async function selectActivePage(runtime) {
1224
+ const pages = runtime.context.pages();
1225
+ const preferredPage = selectPreferredPage(pages);
1226
+ let page = runtime.activePage && pages.includes(runtime.activePage) ? runtime.activePage : null;
1227
+ if (preferredPage && (!page || isBrowserInternalPageUrl(getPageUrl(page)))) page = preferredPage;
1228
+ if (!page) page = await runtime.context.newPage();
1229
+ runtime.activePage = page;
1230
+ if ("function" == typeof page.bringToFront) await page.bringToFront();
1231
+ return page;
1232
+ }
1233
+ async function executeBrowserSessionRuntime(runtime, input = {}) {
1234
+ if (runtime.closed) throw new Error("Browser session is already closed.");
1235
+ const timeoutMs = input.timeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS;
1236
+ const page = await selectActivePage(runtime);
1237
+ await registerPageVideoHandle(runtime, page);
1238
+ if (input.url) await page.goto(input.url, {
1239
+ waitUntil: "domcontentloaded",
1240
+ timeout: timeoutMs
1241
+ });
1242
+ const actionResults = [];
1243
+ for (const [index, action] of (input.actions || []).entries()){
1244
+ const result = await runBrowserAction(page, action, timeoutMs, runtime.workspace, runtime.now, index);
1245
+ actionResults.push(result);
1246
+ }
1247
+ const media = buildScreenshotMedia(runtime.workspace, actionResults);
1248
+ return {
1249
+ browser: "cdp" === runtime.browserTransport ? "chrome-cdp" : "persistent-context" === runtime.browserTransport ? "chrome-playwright" : "chrome-relay",
1250
+ transport: runtime.browserTransport,
1251
+ transportRequested: runtime.transportRequested,
1252
+ transportUsed: runtime.browserTransport,
1253
+ fallbackReason: runtime.transportFallbackReason,
1254
+ mode: runtime.headless ? "headless" : "headed",
1255
+ persistentProfile: runtime.userDataDirSelection.persistentProfile,
1256
+ profileId: runtime.userDataDirSelection.profileId || null,
1257
+ profilePath: runtime.userDataDirSelection.persistentProfile ? runtime.userDataDirSelection.userDataDir : null,
1258
+ reusedExistingSession: runtime.reusedExistingCdpSession,
1259
+ executionWorkspace: runtime.workspace,
1260
+ configWorkspace: runtime.configWorkspace,
1261
+ extensions: runtime.extensionIds,
1262
+ finalUrl: page.url(),
1263
+ title: await page.title(),
1264
+ actionResults,
1265
+ media,
1266
+ videoRecording: summarizeVideoRecording(runtime.videoRecording, runtime.videoRecording ? "recording" : "inactive", 0)
1267
+ };
1268
+ }
1269
+ async function closeBrowserSessionRuntime(runtime, dependencies = {}) {
1270
+ if (runtime.closed) return {
1271
+ media: [],
1272
+ videoRecording: summarizeVideoRecording(runtime.videoRecording, "inactive", 0)
1273
+ };
1274
+ runtime.closed = true;
1275
+ const deps = createBrowserDependencies(dependencies);
1276
+ const recording = runtime.videoRecording;
1277
+ await closeOptionalHandle(runtime.launchedContext);
1278
+ const media = await finalizeRecordedVideos(runtime);
1279
+ if (runtime.browser && !runtime.reusedExistingCdpSession) await closeHandle(runtime.browser);
1280
+ await closeHandle(runtime.chromeSession);
1281
+ if (runtime.userDataDirSelection.releaseLock) runtime.userDataDirSelection.releaseLock();
1282
+ if (!runtime.userDataDirSelection.persistentProfile) deps.removeDir(runtime.userDataDirSelection.userDataDir);
1283
+ return {
1284
+ media,
1285
+ videoRecording: summarizeVideoRecording(recording, recording ? "saved" : "inactive", media.length)
1286
+ };
1287
+ }
1288
+ export { BrowserActionSchema, BrowserControlInputSchema, BrowserSessionActionInputSchema, BrowserSessionVideoRecordingSchema, BrowserTransportPreferenceSchema, DEFAULT_ACTION_TIMEOUT_MS, clearStaleDevtoolsArtifacts, closeBrowserSessionRuntime, createBrowserDependencies, executeBrowserSessionRuntime, openBrowserSessionRuntime, runBrowserAction };