@elizaos/plugin-browser 2.0.0-alpha.9 → 2.0.3-beta.2

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 (256) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -83
  3. package/auto-enable.ts +24 -0
  4. package/dist/actions/browser-autofill-login.d.ts +43 -0
  5. package/dist/actions/browser-autofill-login.d.ts.map +1 -0
  6. package/dist/actions/browser-autofill-login.js +278 -0
  7. package/dist/actions/browser-autofill-login.js.map +1 -0
  8. package/dist/actions/browser.d.ts +11 -0
  9. package/dist/actions/browser.d.ts.map +1 -0
  10. package/dist/actions/browser.js +412 -0
  11. package/dist/actions/browser.js.map +1 -0
  12. package/dist/actions/manage-browser-bridge.d.ts +34 -0
  13. package/dist/actions/manage-browser-bridge.d.ts.map +1 -0
  14. package/dist/actions/manage-browser-bridge.js +572 -0
  15. package/dist/actions/manage-browser-bridge.js.map +1 -0
  16. package/dist/bridge-policy.d.ts +10 -0
  17. package/dist/bridge-policy.d.ts.map +1 -0
  18. package/dist/bridge-policy.js +37 -0
  19. package/dist/bridge-policy.js.map +1 -0
  20. package/dist/bridge-readiness.d.ts +16 -0
  21. package/dist/bridge-readiness.d.ts.map +1 -0
  22. package/dist/bridge-readiness.js +82 -0
  23. package/dist/bridge-readiness.js.map +1 -0
  24. package/dist/bridge-records.d.ts +9 -0
  25. package/dist/bridge-records.d.ts.map +1 -0
  26. package/dist/bridge-records.js +37 -0
  27. package/dist/bridge-records.js.map +1 -0
  28. package/dist/browser-capture-hooks.d.ts +9 -0
  29. package/dist/browser-capture-hooks.d.ts.map +1 -0
  30. package/dist/browser-capture-hooks.js +15 -0
  31. package/dist/browser-capture-hooks.js.map +1 -0
  32. package/dist/browser-service.d.ts +103 -0
  33. package/dist/browser-service.d.ts.map +1 -0
  34. package/dist/browser-service.js +186 -0
  35. package/dist/browser-service.js.map +1 -0
  36. package/dist/browser-workspace-hooks.d.ts +14 -0
  37. package/dist/browser-workspace-hooks.d.ts.map +1 -0
  38. package/dist/browser-workspace-hooks.js +15 -0
  39. package/dist/browser-workspace-hooks.js.map +1 -0
  40. package/dist/companion-auth.d.ts +34 -0
  41. package/dist/companion-auth.d.ts.map +1 -0
  42. package/dist/companion-auth.js +98 -0
  43. package/dist/companion-auth.js.map +1 -0
  44. package/dist/contracts.d.ts +284 -0
  45. package/dist/contracts.d.ts.map +1 -0
  46. package/dist/contracts.js +56 -0
  47. package/dist/contracts.js.map +1 -0
  48. package/dist/index.d.ts +30 -16
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +76 -90
  51. package/dist/index.js.map +1 -1
  52. package/dist/lifeops-session-contracts.d.ts +46 -0
  53. package/dist/lifeops-session-contracts.d.ts.map +1 -0
  54. package/dist/lifeops-session-contracts.js +1 -0
  55. package/dist/lifeops-session-contracts.js.map +1 -0
  56. package/dist/message-adapter.d.ts +9 -0
  57. package/dist/message-adapter.d.ts.map +1 -0
  58. package/dist/message-adapter.js +104 -0
  59. package/dist/message-adapter.js.map +1 -0
  60. package/dist/packaging.d.ts +27 -0
  61. package/dist/packaging.d.ts.map +1 -0
  62. package/dist/packaging.js +571 -0
  63. package/dist/packaging.js.map +1 -0
  64. package/dist/password-manager-bridge.d.ts +50 -0
  65. package/dist/password-manager-bridge.d.ts.map +1 -0
  66. package/dist/password-manager-bridge.js +437 -0
  67. package/dist/password-manager-bridge.js.map +1 -0
  68. package/dist/plugin.d.ts +10 -0
  69. package/dist/plugin.d.ts.map +1 -0
  70. package/dist/plugin.js +168 -0
  71. package/dist/plugin.js.map +1 -0
  72. package/dist/providers/workspace.d.ts +13 -0
  73. package/dist/providers/workspace.d.ts.map +1 -0
  74. package/dist/providers/workspace.js +64 -0
  75. package/dist/providers/workspace.js.map +1 -0
  76. package/dist/routes/bridge.d.ts +37 -0
  77. package/dist/routes/bridge.d.ts.map +1 -0
  78. package/dist/routes/bridge.js +844 -0
  79. package/dist/routes/bridge.js.map +1 -0
  80. package/dist/routes/workspace-account-gate.d.ts +29 -0
  81. package/dist/routes/workspace-account-gate.d.ts.map +1 -0
  82. package/dist/routes/workspace-account-gate.js +147 -0
  83. package/dist/routes/workspace-account-gate.js.map +1 -0
  84. package/dist/routes/workspace-setup.d.ts +10 -0
  85. package/dist/routes/workspace-setup.d.ts.map +1 -0
  86. package/dist/routes/workspace-setup.js +65 -0
  87. package/dist/routes/workspace-setup.js.map +1 -0
  88. package/dist/routes/workspace.d.ts +20 -0
  89. package/dist/routes/workspace.d.ts.map +1 -0
  90. package/dist/routes/workspace.js +276 -0
  91. package/dist/routes/workspace.js.map +1 -0
  92. package/dist/schema.d.ts +2326 -0
  93. package/dist/schema.d.ts.map +1 -0
  94. package/dist/schema.js +133 -0
  95. package/dist/schema.js.map +1 -0
  96. package/dist/service.d.ts +30 -0
  97. package/dist/service.d.ts.map +1 -0
  98. package/dist/service.js +5 -0
  99. package/dist/service.js.map +1 -0
  100. package/dist/targets/bridge-target.d.ts +31 -0
  101. package/dist/targets/bridge-target.d.ts.map +1 -0
  102. package/dist/targets/bridge-target.js +98 -0
  103. package/dist/targets/bridge-target.js.map +1 -0
  104. package/dist/targets/stagehand-target.d.ts +3 -0
  105. package/dist/targets/stagehand-target.d.ts.map +1 -0
  106. package/dist/targets/stagehand-target.js +187 -0
  107. package/dist/targets/stagehand-target.js.map +1 -0
  108. package/dist/workspace/browser-capture.d.ts +41 -0
  109. package/dist/workspace/browser-capture.d.ts.map +1 -0
  110. package/dist/workspace/browser-capture.js +159 -0
  111. package/dist/workspace/browser-capture.js.map +1 -0
  112. package/dist/workspace/browser-workspace-desktop.d.ts +19 -0
  113. package/dist/workspace/browser-workspace-desktop.d.ts.map +1 -0
  114. package/dist/workspace/browser-workspace-desktop.js +1578 -0
  115. package/dist/workspace/browser-workspace-desktop.js.map +1 -0
  116. package/dist/workspace/browser-workspace-elements.d.ts +42 -0
  117. package/dist/workspace/browser-workspace-elements.d.ts.map +1 -0
  118. package/dist/workspace/browser-workspace-elements.js +547 -0
  119. package/dist/workspace/browser-workspace-elements.js.map +1 -0
  120. package/dist/workspace/browser-workspace-forms.d.ts +19 -0
  121. package/dist/workspace/browser-workspace-forms.d.ts.map +1 -0
  122. package/dist/workspace/browser-workspace-forms.js +277 -0
  123. package/dist/workspace/browser-workspace-forms.js.map +1 -0
  124. package/dist/workspace/browser-workspace-helpers.d.ts +32 -0
  125. package/dist/workspace/browser-workspace-helpers.d.ts.map +1 -0
  126. package/dist/workspace/browser-workspace-helpers.js +232 -0
  127. package/dist/workspace/browser-workspace-helpers.js.map +1 -0
  128. package/dist/workspace/browser-workspace-jsdom.d.ts +16 -0
  129. package/dist/workspace/browser-workspace-jsdom.d.ts.map +1 -0
  130. package/dist/workspace/browser-workspace-jsdom.js +233 -0
  131. package/dist/workspace/browser-workspace-jsdom.js.map +1 -0
  132. package/dist/workspace/browser-workspace-network.d.ts +7 -0
  133. package/dist/workspace/browser-workspace-network.d.ts.map +1 -0
  134. package/dist/workspace/browser-workspace-network.js +145 -0
  135. package/dist/workspace/browser-workspace-network.js.map +1 -0
  136. package/dist/workspace/browser-workspace-snapshots.d.ts +14 -0
  137. package/dist/workspace/browser-workspace-snapshots.d.ts.map +1 -0
  138. package/dist/workspace/browser-workspace-snapshots.js +144 -0
  139. package/dist/workspace/browser-workspace-snapshots.js.map +1 -0
  140. package/dist/workspace/browser-workspace-state.d.ts +24 -0
  141. package/dist/workspace/browser-workspace-state.d.ts.map +1 -0
  142. package/dist/workspace/browser-workspace-state.js +155 -0
  143. package/dist/workspace/browser-workspace-state.js.map +1 -0
  144. package/dist/workspace/browser-workspace-types.d.ts +345 -0
  145. package/dist/workspace/browser-workspace-types.d.ts.map +1 -0
  146. package/dist/workspace/browser-workspace-types.js +11 -0
  147. package/dist/workspace/browser-workspace-types.js.map +1 -0
  148. package/dist/workspace/browser-workspace-web.d.ts +8 -0
  149. package/dist/workspace/browser-workspace-web.d.ts.map +1 -0
  150. package/dist/workspace/browser-workspace-web.js +1342 -0
  151. package/dist/workspace/browser-workspace-web.js.map +1 -0
  152. package/dist/workspace/browser-workspace.d.ts +39 -0
  153. package/dist/workspace/browser-workspace.d.ts.map +1 -0
  154. package/dist/workspace/browser-workspace.js +958 -0
  155. package/dist/workspace/browser-workspace.js.map +1 -0
  156. package/dist/workspace/index.d.ts +26 -0
  157. package/dist/workspace/index.d.ts.map +1 -0
  158. package/dist/workspace/index.js +3 -0
  159. package/dist/workspace/index.js.map +1 -0
  160. package/dist/workspace.d.ts +2 -0
  161. package/dist/workspace.d.ts.map +1 -0
  162. package/dist/workspace.js +2 -0
  163. package/dist/workspace.js.map +1 -0
  164. package/package.json +71 -110
  165. package/dist/actions/click.d.ts +0 -3
  166. package/dist/actions/click.d.ts.map +0 -1
  167. package/dist/actions/click.js +0 -158
  168. package/dist/actions/click.js.map +0 -1
  169. package/dist/actions/extract.d.ts +0 -3
  170. package/dist/actions/extract.d.ts.map +0 -1
  171. package/dist/actions/extract.js +0 -168
  172. package/dist/actions/extract.js.map +0 -1
  173. package/dist/actions/index.d.ts +0 -7
  174. package/dist/actions/index.d.ts.map +0 -1
  175. package/dist/actions/index.js +0 -7
  176. package/dist/actions/index.js.map +0 -1
  177. package/dist/actions/navigate.d.ts +0 -3
  178. package/dist/actions/navigate.d.ts.map +0 -1
  179. package/dist/actions/navigate.js +0 -187
  180. package/dist/actions/navigate.js.map +0 -1
  181. package/dist/actions/screenshot.d.ts +0 -3
  182. package/dist/actions/screenshot.d.ts.map +0 -1
  183. package/dist/actions/screenshot.js +0 -167
  184. package/dist/actions/screenshot.js.map +0 -1
  185. package/dist/actions/select.d.ts +0 -3
  186. package/dist/actions/select.d.ts.map +0 -1
  187. package/dist/actions/select.js +0 -167
  188. package/dist/actions/select.js.map +0 -1
  189. package/dist/actions/type.d.ts +0 -3
  190. package/dist/actions/type.d.ts.map +0 -1
  191. package/dist/actions/type.js +0 -167
  192. package/dist/actions/type.js.map +0 -1
  193. package/dist/cli/index.d.ts +0 -8
  194. package/dist/cli/index.d.ts.map +0 -1
  195. package/dist/cli/index.js +0 -13
  196. package/dist/cli/index.js.map +0 -1
  197. package/dist/cli/register.d.ts +0 -20
  198. package/dist/cli/register.d.ts.map +0 -1
  199. package/dist/cli/register.js +0 -403
  200. package/dist/cli/register.js.map +0 -1
  201. package/dist/providerRelevance.d.ts +0 -4
  202. package/dist/providerRelevance.d.ts.map +0 -1
  203. package/dist/providerRelevance.js +0 -33
  204. package/dist/providerRelevance.js.map +0 -1
  205. package/dist/providers/browser-state.d.ts +0 -3
  206. package/dist/providers/browser-state.d.ts.map +0 -1
  207. package/dist/providers/browser-state.js +0 -72
  208. package/dist/providers/browser-state.js.map +0 -1
  209. package/dist/providers/index.d.ts +0 -2
  210. package/dist/providers/index.d.ts.map +0 -1
  211. package/dist/providers/index.js +0 -2
  212. package/dist/providers/index.js.map +0 -1
  213. package/dist/services/browser-service.d.ts +0 -32
  214. package/dist/services/browser-service.d.ts.map +0 -1
  215. package/dist/services/browser-service.js +0 -213
  216. package/dist/services/browser-service.js.map +0 -1
  217. package/dist/services/index.d.ts +0 -4
  218. package/dist/services/index.d.ts.map +0 -1
  219. package/dist/services/index.js +0 -4
  220. package/dist/services/index.js.map +0 -1
  221. package/dist/services/process-manager.d.ts +0 -24
  222. package/dist/services/process-manager.d.ts.map +0 -1
  223. package/dist/services/process-manager.js +0 -270
  224. package/dist/services/process-manager.js.map +0 -1
  225. package/dist/services/websocket-client.d.ts +0 -35
  226. package/dist/services/websocket-client.d.ts.map +0 -1
  227. package/dist/services/websocket-client.js +0 -221
  228. package/dist/services/websocket-client.js.map +0 -1
  229. package/dist/types.d.ts +0 -101
  230. package/dist/types.d.ts.map +0 -1
  231. package/dist/types.js +0 -2
  232. package/dist/types.js.map +0 -1
  233. package/dist/utils/captcha.d.ts +0 -33
  234. package/dist/utils/captcha.d.ts.map +0 -1
  235. package/dist/utils/captcha.js +0 -219
  236. package/dist/utils/captcha.js.map +0 -1
  237. package/dist/utils/errors.d.ts +0 -37
  238. package/dist/utils/errors.d.ts.map +0 -1
  239. package/dist/utils/errors.js +0 -81
  240. package/dist/utils/errors.js.map +0 -1
  241. package/dist/utils/index.d.ts +0 -5
  242. package/dist/utils/index.d.ts.map +0 -1
  243. package/dist/utils/index.js +0 -5
  244. package/dist/utils/index.js.map +0 -1
  245. package/dist/utils/retry.d.ts +0 -26
  246. package/dist/utils/retry.d.ts.map +0 -1
  247. package/dist/utils/retry.js +0 -55
  248. package/dist/utils/retry.js.map +0 -1
  249. package/dist/utils/security.d.ts +0 -27
  250. package/dist/utils/security.d.ts.map +0 -1
  251. package/dist/utils/security.js +0 -139
  252. package/dist/utils/security.js.map +0 -1
  253. package/dist/utils/url.d.ts +0 -12
  254. package/dist/utils/url.d.ts.map +0 -1
  255. package/dist/utils/url.js +0 -39
  256. package/dist/utils/url.js.map +0 -1
@@ -0,0 +1,844 @@
1
+ import fs from "node:fs";
2
+ import { logger } from "@elizaos/core";
3
+ import {
4
+ BROWSER_BRIDGE_PACKAGE_PATH_TARGETS
5
+ } from "../contracts.js";
6
+ import {
7
+ buildBrowserBridgeCompanionPackage,
8
+ getBrowserBridgeCompanionDownloadFile,
9
+ getBrowserBridgeCompanionPackageStatus,
10
+ openBrowserBridgeCompanionManager,
11
+ openBrowserBridgeCompanionPackagePath
12
+ } from "../packaging.js";
13
+ import {
14
+ BROWSER_BRIDGE_ROUTE_SERVICE_TYPE
15
+ } from "../service.js";
16
+ function getService(ctx) {
17
+ if (!ctx.state.runtime) {
18
+ ctx.error(ctx.res, "Agent runtime is not available", 503);
19
+ return null;
20
+ }
21
+ const service = ctx.state.runtime.getService(
22
+ BROWSER_BRIDGE_ROUTE_SERVICE_TYPE
23
+ );
24
+ if (!service) {
25
+ ctx.error(ctx.res, "Browser Bridge service is not available", 503);
26
+ return null;
27
+ }
28
+ return service;
29
+ }
30
+ function getBrowserCompanionAuth(ctx) {
31
+ const companionHeader = ctx.req.headers["x-browser-bridge-companion-id"];
32
+ const companionId = typeof companionHeader === "string" ? companionHeader.trim() : "";
33
+ if (!companionId) {
34
+ routeJsonError(
35
+ ctx,
36
+ "Missing X-Browser-Bridge-Companion-Id header",
37
+ 401,
38
+ "browser_bridge_companion_auth_missing_id"
39
+ );
40
+ return null;
41
+ }
42
+ const authHeader = typeof ctx.req.headers.authorization === "string" ? ctx.req.headers.authorization.trim() : "";
43
+ const match = /^Bearer\s+(.+)$/i.exec(authHeader);
44
+ const pairingToken = match?.[1]?.trim() ?? "";
45
+ if (!pairingToken) {
46
+ routeJsonError(
47
+ ctx,
48
+ "Missing browser companion bearer token",
49
+ 401,
50
+ "browser_bridge_companion_auth_missing_token"
51
+ );
52
+ return null;
53
+ }
54
+ return {
55
+ companionId,
56
+ pairingToken
57
+ };
58
+ }
59
+ function browserAutoPairOriginAllowed(ctx) {
60
+ const originHeader = typeof ctx.req.headers.origin === "string" ? ctx.req.headers.origin.trim() : "";
61
+ if (!originHeader) {
62
+ return requestIsLoopback(ctx);
63
+ }
64
+ if (originHeader === ctx.url.origin) {
65
+ return true;
66
+ }
67
+ return originHeader.startsWith("chrome-extension://") || originHeader.startsWith("safari-web-extension://");
68
+ }
69
+ function requestIsLoopback(ctx) {
70
+ const remoteAddress = ctx.req.socket.remoteAddress?.trim().toLowerCase();
71
+ return remoteAddress === "127.0.0.1" || remoteAddress === "::1" || remoteAddress === "0:0:0:0:0:0:0:1" || remoteAddress === "::ffff:127.0.0.1" || remoteAddress === "::ffff:0:127.0.0.1";
72
+ }
73
+ const BROWSER_BRIDGE_RATE_LIMITS = {
74
+ default: { maxRequests: 60, windowMs: 6e4 }
75
+ };
76
+ const rateLimitBuckets = /* @__PURE__ */ new Map();
77
+ const RATE_LIMIT_CLEANUP_INTERVAL_MS = 5 * 60 * 1e3;
78
+ let lastRateLimitCleanup = Date.now();
79
+ function cleanupRateLimitBuckets(windowMs) {
80
+ const now = Date.now();
81
+ if (now - lastRateLimitCleanup < RATE_LIMIT_CLEANUP_INTERVAL_MS) return;
82
+ lastRateLimitCleanup = now;
83
+ const cutoff = now - windowMs;
84
+ for (const [key, entry] of rateLimitBuckets) {
85
+ entry.timestamps = entry.timestamps.filter(
86
+ (timestamp) => timestamp > cutoff
87
+ );
88
+ if (entry.timestamps.length === 0) rateLimitBuckets.delete(key);
89
+ }
90
+ }
91
+ function checkBrowserBridgeRateLimit(key, config) {
92
+ const now = Date.now();
93
+ cleanupRateLimitBuckets(config.windowMs);
94
+ let entry = rateLimitBuckets.get(key);
95
+ if (!entry) {
96
+ entry = { timestamps: [] };
97
+ rateLimitBuckets.set(key, entry);
98
+ }
99
+ const cutoff = now - config.windowMs;
100
+ entry.timestamps = entry.timestamps.filter((timestamp) => timestamp > cutoff);
101
+ if (entry.timestamps.length >= config.maxRequests) {
102
+ const oldestInWindow = entry.timestamps[0];
103
+ const retryAfterMs = oldestInWindow === void 0 ? 0 : oldestInWindow + config.windowMs - now;
104
+ return { allowed: false, retryAfterMs: Math.max(retryAfterMs, 0) };
105
+ }
106
+ entry.timestamps.push(now);
107
+ return { allowed: true, retryAfterMs: 0 };
108
+ }
109
+ function rateLimitRequest(ctx, operation) {
110
+ const agentId = String(ctx.state.runtime?.agentId ?? "unknown");
111
+ const companionHeader = ctx.req.headers["x-browser-bridge-companion-id"];
112
+ const companionId = typeof companionHeader === "string" ? companionHeader.trim() : "anonymous";
113
+ const remoteAddress = ctx.req.socket.remoteAddress?.trim() ?? "unknown";
114
+ const limitKey = `${agentId}:${operation}:${remoteAddress}:${companionId}`;
115
+ const config = BROWSER_BRIDGE_RATE_LIMITS.default;
116
+ const { allowed, retryAfterMs } = checkBrowserBridgeRateLimit(
117
+ limitKey,
118
+ config
119
+ );
120
+ if (!allowed) {
121
+ ctx.res.writeHead(429, {
122
+ "Content-Type": "application/json",
123
+ "Retry-After": String(Math.ceil(retryAfterMs / 1e3))
124
+ });
125
+ ctx.res.end(JSON.stringify({ error: "Rate limit exceeded", retryAfterMs }));
126
+ return true;
127
+ }
128
+ return false;
129
+ }
130
+ function routeOperation(ctx) {
131
+ return `${ctx.method.toUpperCase()} ${ctx.pathname}`;
132
+ }
133
+ function sanitizeTelemetryToken(value) {
134
+ if (!value) return void 0;
135
+ const token = value.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
136
+ const normalized = token.replace(/_+/g, "_").replace(/^_+|_+$/g, "");
137
+ return normalized ? normalized.slice(0, 64) : void 0;
138
+ }
139
+ function inferTelemetryErrorKind(error) {
140
+ if (error instanceof Error) {
141
+ const message = error.message.toLowerCase();
142
+ if (error.name === "AbortError" || error.name === "TimeoutError" || message.includes("timeout") || message.includes("timed out")) {
143
+ return "timeout";
144
+ }
145
+ return sanitizeTelemetryToken(error.name);
146
+ }
147
+ return typeof error === "string" ? sanitizeTelemetryToken(error) : void 0;
148
+ }
149
+ function createBrowserBridgeTelemetrySpan(meta) {
150
+ const startedAt = Date.now();
151
+ let settled = false;
152
+ const finalize = (outcome, args) => {
153
+ if (settled) return;
154
+ settled = true;
155
+ const event = {
156
+ schema: "integration_boundary_v1",
157
+ boundary: meta.boundary,
158
+ operation: meta.operation,
159
+ outcome,
160
+ durationMs: Math.max(0, Date.now() - startedAt)
161
+ };
162
+ if (typeof meta.timeoutMs === "number") event.timeoutMs = meta.timeoutMs;
163
+ if (typeof args?.statusCode === "number")
164
+ event.statusCode = args.statusCode;
165
+ if (outcome === "failure") {
166
+ event.errorKind = sanitizeTelemetryToken(args?.errorKind) ?? inferTelemetryErrorKind(args?.error);
167
+ }
168
+ const line = `[integration] ${JSON.stringify(event)}`;
169
+ if (outcome === "success") {
170
+ logger.info(line);
171
+ } else {
172
+ logger.warn(line);
173
+ }
174
+ };
175
+ return {
176
+ success: (args) => finalize("success", args),
177
+ failure: (args) => finalize("failure", args)
178
+ };
179
+ }
180
+ function errorMessage(error) {
181
+ return error instanceof Error ? error.message : String(error);
182
+ }
183
+ function routeJsonError(ctx, message, status, code) {
184
+ ctx.json(
185
+ ctx.res,
186
+ {
187
+ error: message,
188
+ ...code ? { code } : {}
189
+ },
190
+ status
191
+ );
192
+ }
193
+ function isBrowserBridgeRouteBodyObject(value) {
194
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
195
+ }
196
+ function rejectMalformedBrowserBridgePayload(ctx) {
197
+ ctx.error(ctx.res, "request body must be a JSON object", 400);
198
+ return true;
199
+ }
200
+ function isStatusError(error) {
201
+ return error instanceof Error && "status" in error && typeof error.status === "number";
202
+ }
203
+ function statusErrorCode(error) {
204
+ if (error && typeof error === "object" && "code" in error && typeof error.code === "string") {
205
+ return error.code;
206
+ }
207
+ return null;
208
+ }
209
+ function decodeMatchedPathComponent(ctx, match, index, res, label) {
210
+ const raw = match?.[index];
211
+ return raw ? ctx.decodePathComponent(raw, res, label) : null;
212
+ }
213
+ async function runRoute(ctx, fn) {
214
+ const operation = routeOperation(ctx);
215
+ const span = createBrowserBridgeTelemetrySpan({
216
+ boundary: "browser-bridge",
217
+ operation
218
+ });
219
+ const service = getService(ctx);
220
+ if (!service) {
221
+ logger.info(
222
+ {
223
+ boundary: "browser-bridge",
224
+ operation,
225
+ statusCode: 503
226
+ },
227
+ "[browser-bridge] Route rejected because agent runtime is unavailable"
228
+ );
229
+ span.failure({
230
+ statusCode: 503,
231
+ errorKind: "runtime_unavailable"
232
+ });
233
+ return true;
234
+ }
235
+ try {
236
+ await fn(service);
237
+ span.success({
238
+ statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200
239
+ });
240
+ return true;
241
+ } catch (error) {
242
+ if (isStatusError(error)) {
243
+ const logFn = error.status === 401 ? logger.debug.bind(logger) : logger.warn.bind(logger);
244
+ logFn(
245
+ {
246
+ boundary: "browser-bridge",
247
+ operation,
248
+ statusCode: error.status
249
+ },
250
+ `[browser-bridge] Route failed: ${error.message}`
251
+ );
252
+ span.failure({
253
+ statusCode: error.status,
254
+ error,
255
+ errorKind: error.status === 401 ? "browser_bridge_auth_invalid" : "browser_bridge_service_error"
256
+ });
257
+ routeJsonError(ctx, error.message, error.status, statusErrorCode(error));
258
+ return true;
259
+ }
260
+ logger.error(
261
+ {
262
+ boundary: "browser-bridge",
263
+ operation
264
+ },
265
+ `[browser-bridge] Route crashed: ${errorMessage(error)}`
266
+ );
267
+ span.failure({
268
+ error,
269
+ errorKind: "unhandled_error"
270
+ });
271
+ throw error;
272
+ }
273
+ }
274
+ async function runStatelessRoute(ctx, fn) {
275
+ const operation = routeOperation(ctx);
276
+ const span = createBrowserBridgeTelemetrySpan({
277
+ boundary: "browser-bridge",
278
+ operation
279
+ });
280
+ try {
281
+ await fn();
282
+ span.success({
283
+ statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200
284
+ });
285
+ return true;
286
+ } catch (error) {
287
+ if (isStatusError(error)) {
288
+ logger.warn(
289
+ {
290
+ boundary: "browser-bridge",
291
+ operation,
292
+ statusCode: error.status
293
+ },
294
+ `[browser-bridge] Route failed: ${error.message}`
295
+ );
296
+ span.failure({
297
+ statusCode: error.status,
298
+ error,
299
+ errorKind: "browser_bridge_service_error"
300
+ });
301
+ ctx.error(ctx.res, error.message, error.status);
302
+ return true;
303
+ }
304
+ logger.error(
305
+ {
306
+ boundary: "browser-bridge",
307
+ operation
308
+ },
309
+ `[browser-bridge] Route crashed: ${errorMessage(error)}`
310
+ );
311
+ span.failure({
312
+ error,
313
+ errorKind: "unhandled_error"
314
+ });
315
+ throw error;
316
+ }
317
+ }
318
+ async function handleBrowserBridgeRoutes(ctx) {
319
+ const { req, res, method, pathname, json, readJsonBody } = ctx;
320
+ if (method === "GET" && pathname === "/api/browser-bridge/sessions") {
321
+ return runRoute(ctx, async (service) => {
322
+ json(res, {
323
+ sessions: await service.listBrowserSessions(ctx.state.adminEntityId)
324
+ });
325
+ });
326
+ }
327
+ if (method === "GET" && pathname === "/api/browser-bridge/settings") {
328
+ return runRoute(ctx, async (service) => {
329
+ json(res, {
330
+ settings: await service.getBrowserSettings(ctx.state.adminEntityId)
331
+ });
332
+ });
333
+ }
334
+ if (method === "POST" && pathname === "/api/browser-bridge/settings") {
335
+ const body = await readJsonBody(
336
+ req,
337
+ res
338
+ );
339
+ if (!body) return true;
340
+ if (!isBrowserBridgeRouteBodyObject(body)) {
341
+ return rejectMalformedBrowserBridgePayload(ctx);
342
+ }
343
+ return runRoute(ctx, async (service) => {
344
+ json(res, {
345
+ settings: await service.updateBrowserSettings(
346
+ body,
347
+ ctx.state.adminEntityId
348
+ )
349
+ });
350
+ });
351
+ }
352
+ if (method === "POST" && pathname === "/api/browser-bridge/companions/pair") {
353
+ const body = await readJsonBody(
354
+ req,
355
+ res
356
+ );
357
+ if (!body) return true;
358
+ if (!isBrowserBridgeRouteBodyObject(body)) {
359
+ return rejectMalformedBrowserBridgePayload(ctx);
360
+ }
361
+ return runRoute(ctx, async (service) => {
362
+ json(
363
+ res,
364
+ await service.createBrowserCompanionPairing(
365
+ body,
366
+ ctx.state.adminEntityId
367
+ ),
368
+ 201
369
+ );
370
+ });
371
+ }
372
+ if (method === "POST" && pathname === "/api/browser-bridge/companions/auto-pair") {
373
+ if (rateLimitRequest(ctx, "companions:auto-pair")) {
374
+ return true;
375
+ }
376
+ if (!browserAutoPairOriginAllowed(ctx)) {
377
+ ctx.error(
378
+ res,
379
+ "browser auto-pair must come from the agent app or a browser extension",
380
+ 403
381
+ );
382
+ return true;
383
+ }
384
+ const body = await readJsonBody(req, res);
385
+ if (!body) return true;
386
+ if (!isBrowserBridgeRouteBodyObject(body)) {
387
+ return rejectMalformedBrowserBridgePayload(ctx);
388
+ }
389
+ return runRoute(ctx, async (service) => {
390
+ json(
391
+ res,
392
+ await service.autoPairBrowserCompanion(
393
+ body,
394
+ ctx.url.origin,
395
+ ctx.state.adminEntityId
396
+ ),
397
+ 201
398
+ );
399
+ });
400
+ }
401
+ if (method === "GET" && pathname === "/api/browser-bridge/companions") {
402
+ return runRoute(ctx, async (service) => {
403
+ json(res, {
404
+ companions: await service.listBrowserCompanions(
405
+ ctx.state.adminEntityId
406
+ )
407
+ });
408
+ });
409
+ }
410
+ if (method === "POST" && pathname === "/api/browser-bridge/companions/revoke") {
411
+ if (rateLimitRequest(ctx, "companions:revoke")) {
412
+ return true;
413
+ }
414
+ return runRoute(ctx, async (service) => {
415
+ const auth = getBrowserCompanionAuth(ctx);
416
+ if (!auth) {
417
+ return;
418
+ }
419
+ json(
420
+ res,
421
+ await service.revokeBrowserCompanionFromCompanion(
422
+ auth.companionId,
423
+ auth.pairingToken,
424
+ ctx.state.adminEntityId
425
+ )
426
+ );
427
+ });
428
+ }
429
+ const browserCompanionRevokeMatch = pathname.match(
430
+ /^\/api\/browser-bridge\/companions\/([^/]+)\/revoke$/
431
+ );
432
+ if (method === "POST" && browserCompanionRevokeMatch) {
433
+ const companionId = decodeMatchedPathComponent(
434
+ ctx,
435
+ browserCompanionRevokeMatch,
436
+ 1,
437
+ res,
438
+ "browser companion id"
439
+ );
440
+ if (!companionId) return true;
441
+ return runRoute(ctx, async (service) => {
442
+ json(
443
+ res,
444
+ await service.revokeBrowserCompanion(
445
+ companionId,
446
+ ctx.state.adminEntityId
447
+ )
448
+ );
449
+ });
450
+ }
451
+ if (method === "GET" && pathname === "/api/browser-bridge/packages") {
452
+ return runStatelessRoute(ctx, async () => {
453
+ json(res, { status: getBrowserBridgeCompanionPackageStatus() });
454
+ });
455
+ }
456
+ if (method === "POST" && pathname === "/api/browser-bridge/packages/open-path") {
457
+ if (!requestIsLoopback(ctx)) {
458
+ ctx.error(
459
+ res,
460
+ "Local extension install helpers can only run on the same machine as the agent",
461
+ 403
462
+ );
463
+ return true;
464
+ }
465
+ const body = await readJsonBody(req, res);
466
+ if (!body) return true;
467
+ if (!isBrowserBridgeRouteBodyObject(body)) {
468
+ return rejectMalformedBrowserBridgePayload(ctx);
469
+ }
470
+ if (typeof body.target !== "string" || !BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.includes(
471
+ body.target
472
+ )) {
473
+ ctx.error(
474
+ res,
475
+ `target must be one of: ${BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.join(", ")}`,
476
+ 400
477
+ );
478
+ return true;
479
+ }
480
+ const validatedTarget = body.target;
481
+ return runStatelessRoute(ctx, async () => {
482
+ json(
483
+ res,
484
+ await openBrowserBridgeCompanionPackagePath(validatedTarget, {
485
+ revealOnly: body.revealOnly === true
486
+ })
487
+ );
488
+ });
489
+ }
490
+ if (method === "POST" && pathname === "/api/browser-bridge/companions/sync") {
491
+ if (rateLimitRequest(ctx, "companions:sync")) {
492
+ return true;
493
+ }
494
+ return runRoute(ctx, async (service) => {
495
+ const auth = getBrowserCompanionAuth(ctx);
496
+ if (!auth) {
497
+ return;
498
+ }
499
+ const body = await readJsonBody(req, res);
500
+ if (!body) return;
501
+ if (!isBrowserBridgeRouteBodyObject(body)) {
502
+ rejectMalformedBrowserBridgePayload(ctx);
503
+ return;
504
+ }
505
+ json(
506
+ res,
507
+ await service.syncBrowserCompanion(
508
+ auth.companionId,
509
+ auth.pairingToken,
510
+ body,
511
+ ctx.state.adminEntityId
512
+ )
513
+ );
514
+ });
515
+ }
516
+ if (method === "GET" && pathname === "/api/browser-bridge/tabs") {
517
+ return runRoute(ctx, async (service) => {
518
+ json(res, {
519
+ tabs: await service.listBrowserTabs(ctx.state.adminEntityId)
520
+ });
521
+ });
522
+ }
523
+ const browserPackageBuildMatch = pathname.match(
524
+ /^\/api\/browser-bridge\/packages\/([^/]+)\/build$/
525
+ );
526
+ if (method === "POST" && browserPackageBuildMatch) {
527
+ const browser = decodeMatchedPathComponent(
528
+ ctx,
529
+ browserPackageBuildMatch,
530
+ 1,
531
+ res,
532
+ "browser package target"
533
+ );
534
+ if (!browser) return true;
535
+ if (browser !== "chrome" && browser !== "safari") {
536
+ ctx.error(res, "browser must be chrome or safari", 400);
537
+ return true;
538
+ }
539
+ return runStatelessRoute(ctx, async () => {
540
+ json(res, {
541
+ status: await buildBrowserBridgeCompanionPackage(browser)
542
+ });
543
+ });
544
+ }
545
+ const browserPackageOpenManagerMatch = pathname.match(
546
+ /^\/api\/browser-bridge\/packages\/([^/]+)\/open-manager$/
547
+ );
548
+ if (method === "POST" && browserPackageOpenManagerMatch) {
549
+ if (!requestIsLoopback(ctx)) {
550
+ ctx.error(
551
+ res,
552
+ "Local extension install helpers can only run on the same machine as the agent",
553
+ 403
554
+ );
555
+ return true;
556
+ }
557
+ const browser = decodeMatchedPathComponent(
558
+ ctx,
559
+ browserPackageOpenManagerMatch,
560
+ 1,
561
+ res,
562
+ "browser package target"
563
+ );
564
+ if (!browser) return true;
565
+ if (browser !== "chrome" && browser !== "safari") {
566
+ ctx.error(res, "browser must be chrome or safari", 400);
567
+ return true;
568
+ }
569
+ return runStatelessRoute(ctx, async () => {
570
+ json(res, await openBrowserBridgeCompanionManager(browser));
571
+ });
572
+ }
573
+ const browserPackageDownloadMatch = pathname.match(
574
+ /^\/api\/browser-bridge\/packages\/([^/]+)\/download$/
575
+ );
576
+ if (method === "GET" && browserPackageDownloadMatch) {
577
+ const browser = decodeMatchedPathComponent(
578
+ ctx,
579
+ browserPackageDownloadMatch,
580
+ 1,
581
+ res,
582
+ "browser package target"
583
+ );
584
+ if (!browser) return true;
585
+ if (browser !== "chrome" && browser !== "safari") {
586
+ ctx.error(res, "browser must be chrome or safari", 400);
587
+ return true;
588
+ }
589
+ return runStatelessRoute(ctx, async () => {
590
+ const artifact = getBrowserBridgeCompanionDownloadFile(browser);
591
+ res.statusCode = 200;
592
+ res.setHeader("Content-Type", artifact.contentType);
593
+ res.setHeader(
594
+ "Content-Disposition",
595
+ `attachment; filename="${artifact.filename}"`
596
+ );
597
+ await new Promise((resolve, reject) => {
598
+ const stream = fs.createReadStream(artifact.path);
599
+ stream.on("error", reject);
600
+ res.on("error", reject);
601
+ stream.on("end", resolve);
602
+ stream.pipe(res);
603
+ });
604
+ });
605
+ }
606
+ if (method === "GET" && pathname === "/api/browser-bridge/current-page") {
607
+ return runRoute(ctx, async (service) => {
608
+ json(res, {
609
+ page: await service.getCurrentBrowserPage(ctx.state.adminEntityId)
610
+ });
611
+ });
612
+ }
613
+ if (method === "POST" && pathname === "/api/browser-bridge/sync") {
614
+ const body = await readJsonBody(req, res);
615
+ if (!body) return true;
616
+ if (!isBrowserBridgeRouteBodyObject(body)) {
617
+ return rejectMalformedBrowserBridgePayload(ctx);
618
+ }
619
+ return runRoute(ctx, async (service) => {
620
+ json(res, await service.syncBrowserState(body, ctx.state.adminEntityId));
621
+ });
622
+ }
623
+ if (method === "POST" && pathname === "/api/browser-bridge/sessions") {
624
+ const body = await readJsonBody(
625
+ req,
626
+ res
627
+ );
628
+ if (!body) return true;
629
+ if (!isBrowserBridgeRouteBodyObject(body)) {
630
+ return rejectMalformedBrowserBridgePayload(ctx);
631
+ }
632
+ return runRoute(ctx, async (service) => {
633
+ json(
634
+ res,
635
+ {
636
+ session: await service.createBrowserSession(
637
+ body,
638
+ ctx.state.adminEntityId
639
+ )
640
+ },
641
+ 201
642
+ );
643
+ });
644
+ }
645
+ const browserSessionMatch = pathname.match(
646
+ /^\/api\/browser-bridge\/sessions\/([^/]+)$/
647
+ );
648
+ if (browserSessionMatch) {
649
+ const sessionId = decodeMatchedPathComponent(
650
+ ctx,
651
+ browserSessionMatch,
652
+ 1,
653
+ res,
654
+ "browser session id"
655
+ );
656
+ if (!sessionId) return true;
657
+ if (method === "GET") {
658
+ return runRoute(ctx, async (service) => {
659
+ json(res, {
660
+ session: await service.getBrowserSession(
661
+ sessionId,
662
+ ctx.state.adminEntityId
663
+ )
664
+ });
665
+ });
666
+ }
667
+ }
668
+ const browserConfirmMatch = pathname.match(
669
+ /^\/api\/browser-bridge\/sessions\/([^/]+)\/confirm$/
670
+ );
671
+ if (method === "POST" && browserConfirmMatch) {
672
+ const sessionId = decodeMatchedPathComponent(
673
+ ctx,
674
+ browserConfirmMatch,
675
+ 1,
676
+ res,
677
+ "browser session id"
678
+ );
679
+ if (!sessionId) return true;
680
+ const body = await readJsonBody(
681
+ req,
682
+ res
683
+ );
684
+ if (!body) return true;
685
+ if (!isBrowserBridgeRouteBodyObject(body)) {
686
+ return rejectMalformedBrowserBridgePayload(ctx);
687
+ }
688
+ return runRoute(ctx, async (service) => {
689
+ json(res, {
690
+ session: await service.confirmBrowserSession(
691
+ sessionId,
692
+ body,
693
+ ctx.state.adminEntityId
694
+ )
695
+ });
696
+ });
697
+ }
698
+ const browserProgressMatch = pathname.match(
699
+ /^\/api\/browser-bridge\/sessions\/([^/]+)\/progress$/
700
+ );
701
+ if (method === "POST" && browserProgressMatch) {
702
+ const sessionId = decodeMatchedPathComponent(
703
+ ctx,
704
+ browserProgressMatch,
705
+ 1,
706
+ res,
707
+ "browser session id"
708
+ );
709
+ if (!sessionId) return true;
710
+ const body = await readJsonBody(
711
+ req,
712
+ res
713
+ );
714
+ if (!body) return true;
715
+ if (!isBrowserBridgeRouteBodyObject(body)) {
716
+ return rejectMalformedBrowserBridgePayload(ctx);
717
+ }
718
+ return runRoute(ctx, async (service) => {
719
+ json(res, {
720
+ session: await service.updateBrowserSessionProgress(
721
+ sessionId,
722
+ body,
723
+ ctx.state.adminEntityId
724
+ )
725
+ });
726
+ });
727
+ }
728
+ const browserCompleteMatch = pathname.match(
729
+ /^\/api\/browser-bridge\/sessions\/([^/]+)\/complete$/
730
+ );
731
+ if (method === "POST" && browserCompleteMatch) {
732
+ const sessionId = decodeMatchedPathComponent(
733
+ ctx,
734
+ browserCompleteMatch,
735
+ 1,
736
+ res,
737
+ "browser session id"
738
+ );
739
+ if (!sessionId) return true;
740
+ const body = await readJsonBody(
741
+ req,
742
+ res
743
+ );
744
+ if (!body) return true;
745
+ if (!isBrowserBridgeRouteBodyObject(body)) {
746
+ return rejectMalformedBrowserBridgePayload(ctx);
747
+ }
748
+ return runRoute(ctx, async (service) => {
749
+ json(res, {
750
+ session: await service.completeBrowserSession(
751
+ sessionId,
752
+ body,
753
+ ctx.state.adminEntityId
754
+ )
755
+ });
756
+ });
757
+ }
758
+ const browserCompanionProgressMatch = pathname.match(
759
+ /^\/api\/browser-bridge\/companions\/sessions\/([^/]+)\/progress$/
760
+ );
761
+ if (method === "POST" && browserCompanionProgressMatch) {
762
+ if (rateLimitRequest(ctx, "companions:session-progress")) {
763
+ return true;
764
+ }
765
+ const sessionId = decodeMatchedPathComponent(
766
+ ctx,
767
+ browserCompanionProgressMatch,
768
+ 1,
769
+ res,
770
+ "browser session id"
771
+ );
772
+ if (!sessionId) return true;
773
+ return runRoute(ctx, async (service) => {
774
+ const auth = getBrowserCompanionAuth(ctx);
775
+ if (!auth) {
776
+ return;
777
+ }
778
+ const body = await readJsonBody(
779
+ req,
780
+ res
781
+ );
782
+ if (!body) return;
783
+ if (!isBrowserBridgeRouteBodyObject(body)) {
784
+ rejectMalformedBrowserBridgePayload(ctx);
785
+ return;
786
+ }
787
+ json(res, {
788
+ session: await service.updateBrowserSessionProgressFromCompanion(
789
+ auth.companionId,
790
+ auth.pairingToken,
791
+ sessionId,
792
+ body,
793
+ ctx.state.adminEntityId
794
+ )
795
+ });
796
+ });
797
+ }
798
+ const browserCompanionCompleteMatch = pathname.match(
799
+ /^\/api\/browser-bridge\/companions\/sessions\/([^/]+)\/complete$/
800
+ );
801
+ if (method === "POST" && browserCompanionCompleteMatch) {
802
+ if (rateLimitRequest(ctx, "companions:session-complete")) {
803
+ return true;
804
+ }
805
+ const sessionId = decodeMatchedPathComponent(
806
+ ctx,
807
+ browserCompanionCompleteMatch,
808
+ 1,
809
+ res,
810
+ "browser session id"
811
+ );
812
+ if (!sessionId) return true;
813
+ return runRoute(ctx, async (service) => {
814
+ const auth = getBrowserCompanionAuth(ctx);
815
+ if (!auth) {
816
+ return;
817
+ }
818
+ const body = await readJsonBody(
819
+ req,
820
+ res
821
+ );
822
+ if (!body) return;
823
+ if (!isBrowserBridgeRouteBodyObject(body)) {
824
+ rejectMalformedBrowserBridgePayload(ctx);
825
+ return;
826
+ }
827
+ json(res, {
828
+ session: await service.completeBrowserSessionFromCompanion(
829
+ auth.companionId,
830
+ auth.pairingToken,
831
+ sessionId,
832
+ body,
833
+ ctx.state.adminEntityId
834
+ )
835
+ });
836
+ });
837
+ }
838
+ return false;
839
+ }
840
+ export {
841
+ handleBrowserBridgeRoutes,
842
+ rateLimitRequest
843
+ };
844
+ //# sourceMappingURL=bridge.js.map