@elizaos/plugin-browser 2.0.0-beta.1 → 2.0.11-beta.7

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 (169) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -64
  3. package/dist/actions/browser-autofill-login.d.ts.map +1 -1
  4. package/dist/actions/browser-autofill-login.js.map +1 -1
  5. package/dist/actions/browser.d.ts +5 -6
  6. package/dist/actions/browser.d.ts.map +1 -1
  7. package/dist/actions/browser.js +54 -59
  8. package/dist/actions/browser.js.map +1 -1
  9. package/dist/actions/manage-browser-bridge.d.ts.map +1 -1
  10. package/dist/actions/manage-browser-bridge.js +10 -14
  11. package/dist/actions/manage-browser-bridge.js.map +1 -1
  12. package/dist/bridge-policy.d.ts +10 -0
  13. package/dist/bridge-policy.d.ts.map +1 -0
  14. package/dist/bridge-policy.js +37 -0
  15. package/dist/bridge-policy.js.map +1 -0
  16. package/dist/bridge-readiness.d.ts +16 -0
  17. package/dist/bridge-readiness.d.ts.map +1 -0
  18. package/dist/bridge-readiness.js +82 -0
  19. package/dist/bridge-readiness.js.map +1 -0
  20. package/dist/bridge-records.d.ts +9 -0
  21. package/dist/bridge-records.d.ts.map +1 -0
  22. package/dist/bridge-records.js +37 -0
  23. package/dist/bridge-records.js.map +1 -0
  24. package/dist/browser-capture-hooks.d.ts +9 -0
  25. package/dist/browser-capture-hooks.d.ts.map +1 -0
  26. package/dist/browser-capture-hooks.js +15 -0
  27. package/dist/browser-capture-hooks.js.map +1 -0
  28. package/dist/browser-service.d.ts +22 -4
  29. package/dist/browser-service.d.ts.map +1 -1
  30. package/dist/browser-service.js +63 -15
  31. package/dist/browser-service.js.map +1 -1
  32. package/dist/browser-workspace-hooks.d.ts +14 -0
  33. package/dist/browser-workspace-hooks.d.ts.map +1 -0
  34. package/dist/browser-workspace-hooks.js +15 -0
  35. package/dist/browser-workspace-hooks.js.map +1 -0
  36. package/dist/companion-auth.d.ts +34 -0
  37. package/dist/companion-auth.d.ts.map +1 -0
  38. package/dist/companion-auth.js +98 -0
  39. package/dist/companion-auth.js.map +1 -0
  40. package/dist/index.d.ts +9 -3
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +46 -11
  43. package/dist/index.js.map +1 -1
  44. package/dist/message-adapter.d.ts +9 -0
  45. package/dist/message-adapter.d.ts.map +1 -0
  46. package/dist/message-adapter.js +104 -0
  47. package/dist/message-adapter.js.map +1 -0
  48. package/dist/packaging.d.ts.map +1 -1
  49. package/dist/packaging.js +2 -0
  50. package/dist/packaging.js.map +1 -1
  51. package/dist/password-manager-bridge.d.ts +50 -0
  52. package/dist/password-manager-bridge.d.ts.map +1 -0
  53. package/dist/password-manager-bridge.js +437 -0
  54. package/dist/password-manager-bridge.js.map +1 -0
  55. package/dist/plugin.d.ts.map +1 -1
  56. package/dist/plugin.js +8 -4
  57. package/dist/plugin.js.map +1 -1
  58. package/dist/providers/workspace.d.ts +1 -1
  59. package/dist/providers/workspace.js.map +1 -1
  60. package/dist/routes/bridge.d.ts.map +1 -1
  61. package/dist/routes/bridge.js +63 -14
  62. package/dist/routes/bridge.js.map +1 -1
  63. package/dist/routes/workspace-setup.d.ts.map +1 -1
  64. package/dist/routes/workspace-setup.js +1 -1
  65. package/dist/routes/workspace-setup.js.map +1 -1
  66. package/dist/routes/workspace.d.ts +1 -2
  67. package/dist/routes/workspace.d.ts.map +1 -1
  68. package/dist/routes/workspace.js +63 -3
  69. package/dist/routes/workspace.js.map +1 -1
  70. package/dist/schema.d.ts +2 -2
  71. package/dist/schema.js.map +1 -1
  72. package/dist/service.d.ts +1 -1
  73. package/dist/service.d.ts.map +1 -1
  74. package/dist/service.js.map +1 -1
  75. package/dist/targets/bridge-target.d.ts +1 -1
  76. package/dist/targets/bridge-target.d.ts.map +1 -1
  77. package/dist/targets/bridge-target.js.map +1 -1
  78. package/dist/targets/stagehand-target.d.ts +3 -0
  79. package/dist/targets/stagehand-target.d.ts.map +1 -0
  80. package/dist/targets/stagehand-target.js +187 -0
  81. package/dist/targets/stagehand-target.js.map +1 -0
  82. package/dist/workspace/browser-capture.d.ts +1 -1
  83. package/dist/workspace/browser-capture.js.map +1 -1
  84. package/dist/workspace/browser-workspace-desktop.d.ts +1 -1
  85. package/dist/workspace/browser-workspace-desktop.d.ts.map +1 -1
  86. package/dist/workspace/browser-workspace-desktop.js +47 -25
  87. package/dist/workspace/browser-workspace-desktop.js.map +1 -1
  88. package/dist/workspace/browser-workspace-forms.d.ts.map +1 -1
  89. package/dist/workspace/browser-workspace-forms.js +1 -1
  90. package/dist/workspace/browser-workspace-forms.js.map +1 -1
  91. package/dist/workspace/browser-workspace-helpers.d.ts +7 -0
  92. package/dist/workspace/browser-workspace-helpers.d.ts.map +1 -1
  93. package/dist/workspace/browser-workspace-helpers.js +37 -0
  94. package/dist/workspace/browser-workspace-helpers.js.map +1 -1
  95. package/dist/workspace/browser-workspace-network.d.ts +1 -1
  96. package/dist/workspace/browser-workspace-network.d.ts.map +1 -1
  97. package/dist/workspace/browser-workspace-types.d.ts +15 -0
  98. package/dist/workspace/browser-workspace-types.d.ts.map +1 -1
  99. package/dist/workspace/browser-workspace-types.js.map +1 -1
  100. package/dist/workspace/browser-workspace-web.d.ts.map +1 -1
  101. package/dist/workspace/browser-workspace-web.js +15 -88
  102. package/dist/workspace/browser-workspace-web.js.map +1 -1
  103. package/dist/workspace/browser-workspace.d.ts +1 -1
  104. package/dist/workspace/browser-workspace.d.ts.map +1 -1
  105. package/dist/workspace/browser-workspace.js +9 -4
  106. package/dist/workspace/browser-workspace.js.map +1 -1
  107. package/package.json +28 -7
  108. package/dist/actions/browser-autofill-login.d.js +0 -1
  109. package/dist/actions/browser-autofill-login.d.js.map +0 -1
  110. package/dist/actions/browser.d.js +0 -1
  111. package/dist/actions/browser.d.js.map +0 -1
  112. package/dist/actions/manage-browser-bridge.d.js +0 -1
  113. package/dist/actions/manage-browser-bridge.d.js.map +0 -1
  114. package/dist/ambient-jsdom.d.js +0 -1
  115. package/dist/ambient-jsdom.d.js.map +0 -1
  116. package/dist/browser-service.d.js +0 -1
  117. package/dist/browser-service.d.js.map +0 -1
  118. package/dist/contracts.d.js +0 -1
  119. package/dist/contracts.d.js.map +0 -1
  120. package/dist/index.d.js +0 -21
  121. package/dist/index.d.js.map +0 -1
  122. package/dist/lifeops-session-contracts.d.js +0 -1
  123. package/dist/lifeops-session-contracts.d.js.map +0 -1
  124. package/dist/packaging.d.js +0 -1
  125. package/dist/packaging.d.js.map +0 -1
  126. package/dist/plugin.d.js +0 -1
  127. package/dist/plugin.d.js.map +0 -1
  128. package/dist/providers/workspace.d.js +0 -1
  129. package/dist/providers/workspace.d.js.map +0 -1
  130. package/dist/routes/bridge.d.js +0 -1
  131. package/dist/routes/bridge.d.js.map +0 -1
  132. package/dist/routes/workspace-account-gate.d.js +0 -1
  133. package/dist/routes/workspace-account-gate.d.js.map +0 -1
  134. package/dist/routes/workspace-setup.d.js +0 -1
  135. package/dist/routes/workspace-setup.d.js.map +0 -1
  136. package/dist/routes/workspace.d.js +0 -1
  137. package/dist/routes/workspace.d.js.map +0 -1
  138. package/dist/schema.d.js +0 -1
  139. package/dist/schema.d.js.map +0 -1
  140. package/dist/service.d.js +0 -1
  141. package/dist/service.d.js.map +0 -1
  142. package/dist/targets/bridge-target.d.js +0 -1
  143. package/dist/targets/bridge-target.d.js.map +0 -1
  144. package/dist/workspace/browser-capture.d.js +0 -1
  145. package/dist/workspace/browser-capture.d.js.map +0 -1
  146. package/dist/workspace/browser-workspace-desktop.d.js +0 -1
  147. package/dist/workspace/browser-workspace-desktop.d.js.map +0 -1
  148. package/dist/workspace/browser-workspace-elements.d.js +0 -1
  149. package/dist/workspace/browser-workspace-elements.d.js.map +0 -1
  150. package/dist/workspace/browser-workspace-forms.d.js +0 -1
  151. package/dist/workspace/browser-workspace-forms.d.js.map +0 -1
  152. package/dist/workspace/browser-workspace-helpers.d.js +0 -1
  153. package/dist/workspace/browser-workspace-helpers.d.js.map +0 -1
  154. package/dist/workspace/browser-workspace-jsdom.d.js +0 -1
  155. package/dist/workspace/browser-workspace-jsdom.d.js.map +0 -1
  156. package/dist/workspace/browser-workspace-network.d.js +0 -1
  157. package/dist/workspace/browser-workspace-network.d.js.map +0 -1
  158. package/dist/workspace/browser-workspace-snapshots.d.js +0 -1
  159. package/dist/workspace/browser-workspace-snapshots.d.js.map +0 -1
  160. package/dist/workspace/browser-workspace-state.d.js +0 -1
  161. package/dist/workspace/browser-workspace-state.d.js.map +0 -1
  162. package/dist/workspace/browser-workspace-types.d.js +0 -1
  163. package/dist/workspace/browser-workspace-types.d.js.map +0 -1
  164. package/dist/workspace/browser-workspace-web.d.js +0 -1
  165. package/dist/workspace/browser-workspace-web.d.js.map +0 -1
  166. package/dist/workspace/browser-workspace.d.js +0 -11
  167. package/dist/workspace/browser-workspace.d.js.map +0 -1
  168. package/dist/workspace/index.d.js +0 -3
  169. package/dist/workspace/index.d.js.map +0 -1
@@ -82,7 +82,9 @@ function cleanupRateLimitBuckets(windowMs) {
82
82
  lastRateLimitCleanup = now;
83
83
  const cutoff = now - windowMs;
84
84
  for (const [key, entry] of rateLimitBuckets) {
85
- entry.timestamps = entry.timestamps.filter((timestamp) => timestamp > cutoff);
85
+ entry.timestamps = entry.timestamps.filter(
86
+ (timestamp) => timestamp > cutoff
87
+ );
86
88
  if (entry.timestamps.length === 0) rateLimitBuckets.delete(key);
87
89
  }
88
90
  }
@@ -158,7 +160,8 @@ function createBrowserBridgeTelemetrySpan(meta) {
158
160
  durationMs: Math.max(0, Date.now() - startedAt)
159
161
  };
160
162
  if (typeof meta.timeoutMs === "number") event.timeoutMs = meta.timeoutMs;
161
- if (typeof args?.statusCode === "number") event.statusCode = args.statusCode;
163
+ if (typeof args?.statusCode === "number")
164
+ event.statusCode = args.statusCode;
162
165
  if (outcome === "failure") {
163
166
  event.errorKind = sanitizeTelemetryToken(args?.errorKind) ?? inferTelemetryErrorKind(args?.error);
164
167
  }
@@ -187,6 +190,13 @@ function routeJsonError(ctx, message, status, code) {
187
190
  status
188
191
  );
189
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
+ }
190
200
  function isStatusError(error) {
191
201
  return error instanceof Error && "status" in error && typeof error.status === "number";
192
202
  }
@@ -327,6 +337,9 @@ async function handleBrowserBridgeRoutes(ctx) {
327
337
  res
328
338
  );
329
339
  if (!body) return true;
340
+ if (!isBrowserBridgeRouteBodyObject(body)) {
341
+ return rejectMalformedBrowserBridgePayload(ctx);
342
+ }
330
343
  return runRoute(ctx, async (service) => {
331
344
  json(res, {
332
345
  settings: await service.updateBrowserSettings(
@@ -342,6 +355,9 @@ async function handleBrowserBridgeRoutes(ctx) {
342
355
  res
343
356
  );
344
357
  if (!body) return true;
358
+ if (!isBrowserBridgeRouteBodyObject(body)) {
359
+ return rejectMalformedBrowserBridgePayload(ctx);
360
+ }
345
361
  return runRoute(ctx, async (service) => {
346
362
  json(
347
363
  res,
@@ -367,6 +383,9 @@ async function handleBrowserBridgeRoutes(ctx) {
367
383
  }
368
384
  const body = await readJsonBody(req, res);
369
385
  if (!body) return true;
386
+ if (!isBrowserBridgeRouteBodyObject(body)) {
387
+ return rejectMalformedBrowserBridgePayload(ctx);
388
+ }
370
389
  return runRoute(ctx, async (service) => {
371
390
  json(
372
391
  res,
@@ -445,6 +464,9 @@ async function handleBrowserBridgeRoutes(ctx) {
445
464
  }
446
465
  const body = await readJsonBody(req, res);
447
466
  if (!body) return true;
467
+ if (!isBrowserBridgeRouteBodyObject(body)) {
468
+ return rejectMalformedBrowserBridgePayload(ctx);
469
+ }
448
470
  if (typeof body.target !== "string" || !BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.includes(
449
471
  body.target
450
472
  )) {
@@ -469,13 +491,17 @@ async function handleBrowserBridgeRoutes(ctx) {
469
491
  if (rateLimitRequest(ctx, "companions:sync")) {
470
492
  return true;
471
493
  }
472
- const body = await readJsonBody(req, res);
473
- if (!body) return true;
474
494
  return runRoute(ctx, async (service) => {
475
495
  const auth = getBrowserCompanionAuth(ctx);
476
496
  if (!auth) {
477
497
  return;
478
498
  }
499
+ const body = await readJsonBody(req, res);
500
+ if (!body) return;
501
+ if (!isBrowserBridgeRouteBodyObject(body)) {
502
+ rejectMalformedBrowserBridgePayload(ctx);
503
+ return;
504
+ }
479
505
  json(
480
506
  res,
481
507
  await service.syncBrowserCompanion(
@@ -587,6 +613,9 @@ async function handleBrowserBridgeRoutes(ctx) {
587
613
  if (method === "POST" && pathname === "/api/browser-bridge/sync") {
588
614
  const body = await readJsonBody(req, res);
589
615
  if (!body) return true;
616
+ if (!isBrowserBridgeRouteBodyObject(body)) {
617
+ return rejectMalformedBrowserBridgePayload(ctx);
618
+ }
590
619
  return runRoute(ctx, async (service) => {
591
620
  json(res, await service.syncBrowserState(body, ctx.state.adminEntityId));
592
621
  });
@@ -597,6 +626,9 @@ async function handleBrowserBridgeRoutes(ctx) {
597
626
  res
598
627
  );
599
628
  if (!body) return true;
629
+ if (!isBrowserBridgeRouteBodyObject(body)) {
630
+ return rejectMalformedBrowserBridgePayload(ctx);
631
+ }
600
632
  return runRoute(ctx, async (service) => {
601
633
  json(
602
634
  res,
@@ -650,6 +682,9 @@ async function handleBrowserBridgeRoutes(ctx) {
650
682
  res
651
683
  );
652
684
  if (!body) return true;
685
+ if (!isBrowserBridgeRouteBodyObject(body)) {
686
+ return rejectMalformedBrowserBridgePayload(ctx);
687
+ }
653
688
  return runRoute(ctx, async (service) => {
654
689
  json(res, {
655
690
  session: await service.confirmBrowserSession(
@@ -677,6 +712,9 @@ async function handleBrowserBridgeRoutes(ctx) {
677
712
  res
678
713
  );
679
714
  if (!body) return true;
715
+ if (!isBrowserBridgeRouteBodyObject(body)) {
716
+ return rejectMalformedBrowserBridgePayload(ctx);
717
+ }
680
718
  return runRoute(ctx, async (service) => {
681
719
  json(res, {
682
720
  session: await service.updateBrowserSessionProgress(
@@ -704,6 +742,9 @@ async function handleBrowserBridgeRoutes(ctx) {
704
742
  res
705
743
  );
706
744
  if (!body) return true;
745
+ if (!isBrowserBridgeRouteBodyObject(body)) {
746
+ return rejectMalformedBrowserBridgePayload(ctx);
747
+ }
707
748
  return runRoute(ctx, async (service) => {
708
749
  json(res, {
709
750
  session: await service.completeBrowserSession(
@@ -729,16 +770,20 @@ async function handleBrowserBridgeRoutes(ctx) {
729
770
  "browser session id"
730
771
  );
731
772
  if (!sessionId) return true;
732
- const body = await readJsonBody(
733
- req,
734
- res
735
- );
736
- if (!body) return true;
737
773
  return runRoute(ctx, async (service) => {
738
774
  const auth = getBrowserCompanionAuth(ctx);
739
775
  if (!auth) {
740
776
  return;
741
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
+ }
742
787
  json(res, {
743
788
  session: await service.updateBrowserSessionProgressFromCompanion(
744
789
  auth.companionId,
@@ -765,16 +810,20 @@ async function handleBrowserBridgeRoutes(ctx) {
765
810
  "browser session id"
766
811
  );
767
812
  if (!sessionId) return true;
768
- const body = await readJsonBody(
769
- req,
770
- res
771
- );
772
- if (!body) return true;
773
813
  return runRoute(ctx, async (service) => {
774
814
  const auth = getBrowserCompanionAuth(ctx);
775
815
  if (!auth) {
776
816
  return;
777
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
+ }
778
827
  json(res, {
779
828
  session: await service.completeBrowserSessionFromCompanion(
780
829
  auth.companionId,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/bridge.ts"],"sourcesContent":["/**\n * Agent Browser Bridge HTTP routes.\n *\n * Mounted under `/api/browser-bridge/*` by the plugin-collector. Pair +\n * settings + companion-sync + tabs + current-page + packaging endpoints\n * are fully generic. Session endpoints (confirm/progress/complete) still\n * operate on the LifeOps-owned `life_browser_sessions` table and therefore\n * call into the LifeOps route service because that app owns workflow-scoped\n * browser sessions.\n *\n * Companion/auto-pair/sync authentication uses the\n * `X-Browser-Bridge-Companion-Id` header paired with a bearer pairing\n * token. The old `X-LifeOps-Browser-Companion-Id` and legacy\n * `x-eliza-browser-companion-id` aliases were deliberately removed.\n */\n\nimport fs from \"node:fs\";\nimport type http from \"node:http\";\nimport type { ReadJsonBodyOptions } from \"@elizaos/core\";\nimport { type AgentRuntime, logger, type UUID } from \"@elizaos/core\";\nimport type {\n CompleteLifeOpsBrowserSessionRequest,\n ConfirmLifeOpsBrowserSessionRequest,\n CreateLifeOpsBrowserSessionRequest,\n} from \"../lifeops-session-contracts.js\";\nimport {\n type BrowserBridgeCompanionAuthErrorCode,\n BROWSER_BRIDGE_PACKAGE_PATH_TARGETS,\n type CreateBrowserBridgeCompanionAutoPairRequest,\n type CreateBrowserBridgeCompanionPairingRequest,\n type SyncBrowserBridgeStateRequest,\n type UpdateBrowserBridgeSessionProgressRequest,\n type UpdateBrowserBridgeSettingsRequest,\n} from \"../contracts.js\";\nimport {\n buildBrowserBridgeCompanionPackage,\n getBrowserBridgeCompanionDownloadFile,\n getBrowserBridgeCompanionPackageStatus,\n openBrowserBridgeCompanionManager,\n openBrowserBridgeCompanionPackagePath,\n} from \"../packaging.js\";\nimport {\n BROWSER_BRIDGE_ROUTE_SERVICE_TYPE,\n type BrowserBridgeRouteService,\n} from \"../service.js\";\n\nexport interface BrowserBridgeRouteContext {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n method: string;\n pathname: string;\n url: URL;\n state: {\n runtime: AgentRuntime | null;\n adminEntityId: UUID | null;\n };\n json: (res: http.ServerResponse, data: unknown, status?: number) => void;\n error: (res: http.ServerResponse, message: string, status?: number) => void;\n readJsonBody: <T extends object>(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n options?: ReadJsonBodyOptions,\n ) => Promise<T | null>;\n decodePathComponent: (\n raw: string,\n res: http.ServerResponse,\n label: string,\n ) => string | null;\n}\n\nfunction getService(\n ctx: BrowserBridgeRouteContext,\n): BrowserBridgeRouteService | null {\n if (!ctx.state.runtime) {\n ctx.error(ctx.res, \"Agent runtime is not available\", 503);\n return null;\n }\n const service = ctx.state.runtime.getService<BrowserBridgeRouteService>(\n BROWSER_BRIDGE_ROUTE_SERVICE_TYPE,\n );\n if (!service) {\n ctx.error(ctx.res, \"Browser Bridge service is not available\", 503);\n return null;\n }\n return service;\n}\n\nfunction getBrowserCompanionAuth(\n ctx: BrowserBridgeRouteContext,\n): { companionId: string; pairingToken: string } | null {\n const companionHeader = ctx.req.headers[\"x-browser-bridge-companion-id\"];\n const companionId =\n typeof companionHeader === \"string\" ? companionHeader.trim() : \"\";\n if (!companionId) {\n routeJsonError(\n ctx,\n \"Missing X-Browser-Bridge-Companion-Id header\",\n 401,\n \"browser_bridge_companion_auth_missing_id\",\n );\n return null;\n }\n const authHeader =\n typeof ctx.req.headers.authorization === \"string\"\n ? ctx.req.headers.authorization.trim()\n : \"\";\n const match = /^Bearer\\s+(.+)$/i.exec(authHeader);\n const pairingToken = match?.[1]?.trim() ?? \"\";\n if (!pairingToken) {\n routeJsonError(\n ctx,\n \"Missing browser companion bearer token\",\n 401,\n \"browser_bridge_companion_auth_missing_token\",\n );\n return null;\n }\n return {\n companionId,\n pairingToken,\n };\n}\n\nfunction browserAutoPairOriginAllowed(ctx: BrowserBridgeRouteContext): boolean {\n const originHeader =\n typeof ctx.req.headers.origin === \"string\"\n ? ctx.req.headers.origin.trim()\n : \"\";\n if (!originHeader) {\n return requestIsLoopback(ctx);\n }\n if (originHeader === ctx.url.origin) {\n return true;\n }\n return (\n originHeader.startsWith(\"chrome-extension://\") ||\n originHeader.startsWith(\"safari-web-extension://\")\n );\n}\n\nfunction requestIsLoopback(ctx: BrowserBridgeRouteContext): boolean {\n const remoteAddress = ctx.req.socket.remoteAddress?.trim().toLowerCase();\n return (\n remoteAddress === \"127.0.0.1\" ||\n remoteAddress === \"::1\" ||\n remoteAddress === \"0:0:0:0:0:0:0:1\" ||\n remoteAddress === \"::ffff:127.0.0.1\" ||\n remoteAddress === \"::ffff:0:127.0.0.1\"\n );\n}\n\nconst BROWSER_BRIDGE_RATE_LIMITS = {\n default: { maxRequests: 60, windowMs: 60_000 },\n} satisfies Record<string, RateLimitConfig>;\n\ninterface RateLimitConfig {\n maxRequests: number;\n windowMs: number;\n}\n\ninterface RateLimitEntry {\n timestamps: number[];\n}\n\nconst rateLimitBuckets = new Map<string, RateLimitEntry>();\nconst RATE_LIMIT_CLEANUP_INTERVAL_MS = 5 * 60 * 1_000;\nlet lastRateLimitCleanup = Date.now();\n\nfunction cleanupRateLimitBuckets(windowMs: number): void {\n const now = Date.now();\n if (now - lastRateLimitCleanup < RATE_LIMIT_CLEANUP_INTERVAL_MS) return;\n lastRateLimitCleanup = now;\n const cutoff = now - windowMs;\n for (const [key, entry] of rateLimitBuckets) {\n entry.timestamps = entry.timestamps.filter((timestamp) => timestamp > cutoff);\n if (entry.timestamps.length === 0) rateLimitBuckets.delete(key);\n }\n}\n\nfunction checkBrowserBridgeRateLimit(\n key: string,\n config: RateLimitConfig,\n): { allowed: boolean; retryAfterMs: number } {\n const now = Date.now();\n cleanupRateLimitBuckets(config.windowMs);\n let entry = rateLimitBuckets.get(key);\n if (!entry) {\n entry = { timestamps: [] };\n rateLimitBuckets.set(key, entry);\n }\n\n const cutoff = now - config.windowMs;\n entry.timestamps = entry.timestamps.filter((timestamp) => timestamp > cutoff);\n\n if (entry.timestamps.length >= config.maxRequests) {\n const oldestInWindow = entry.timestamps[0];\n const retryAfterMs =\n oldestInWindow === undefined ? 0 : oldestInWindow + config.windowMs - now;\n return { allowed: false, retryAfterMs: Math.max(retryAfterMs, 0) };\n }\n\n entry.timestamps.push(now);\n return { allowed: true, retryAfterMs: 0 };\n}\n\nfunction rateLimitRequest(\n ctx: BrowserBridgeRouteContext,\n operation: string,\n): boolean {\n const agentId = String(ctx.state.runtime?.agentId ?? \"unknown\");\n const companionHeader = ctx.req.headers[\"x-browser-bridge-companion-id\"];\n const companionId =\n typeof companionHeader === \"string\" ? companionHeader.trim() : \"anonymous\";\n const remoteAddress = ctx.req.socket.remoteAddress?.trim() ?? \"unknown\";\n const limitKey = `${agentId}:${operation}:${remoteAddress}:${companionId}`;\n const config = BROWSER_BRIDGE_RATE_LIMITS.default;\n const { allowed, retryAfterMs } = checkBrowserBridgeRateLimit(\n limitKey,\n config,\n );\n if (!allowed) {\n ctx.res.writeHead(429, {\n \"Content-Type\": \"application/json\",\n \"Retry-After\": String(Math.ceil(retryAfterMs / 1_000)),\n });\n ctx.res.end(JSON.stringify({ error: \"Rate limit exceeded\", retryAfterMs }));\n return true;\n }\n return false;\n}\n\nfunction routeOperation(ctx: BrowserBridgeRouteContext): string {\n return `${ctx.method.toUpperCase()} ${ctx.pathname}`;\n}\n\ninterface BrowserBridgeTelemetrySpan {\n success: (args?: { statusCode?: number }) => void;\n failure: (args?: {\n statusCode?: number;\n error?: unknown;\n errorKind?: string;\n }) => void;\n}\n\nfunction sanitizeTelemetryToken(value: string | undefined): string | undefined {\n if (!value) return undefined;\n const token = value.toLowerCase().replace(/[^a-z0-9_-]/g, \"_\");\n const normalized = token.replace(/_+/g, \"_\").replace(/^_+|_+$/g, \"\");\n return normalized ? normalized.slice(0, 64) : undefined;\n}\n\nfunction inferTelemetryErrorKind(error: unknown): string | undefined {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (\n error.name === \"AbortError\" ||\n error.name === \"TimeoutError\" ||\n message.includes(\"timeout\") ||\n message.includes(\"timed out\")\n ) {\n return \"timeout\";\n }\n return sanitizeTelemetryToken(error.name);\n }\n return typeof error === \"string\" ? sanitizeTelemetryToken(error) : undefined;\n}\n\nfunction createBrowserBridgeTelemetrySpan(meta: {\n boundary: \"browser-bridge\";\n operation: string;\n timeoutMs?: number;\n}): BrowserBridgeTelemetrySpan {\n const startedAt = Date.now();\n let settled = false;\n\n const finalize = (\n outcome: \"success\" | \"failure\",\n args?: { statusCode?: number; error?: unknown; errorKind?: string },\n ): void => {\n if (settled) return;\n settled = true;\n const event: Record<string, unknown> = {\n schema: \"integration_boundary_v1\",\n boundary: meta.boundary,\n operation: meta.operation,\n outcome,\n durationMs: Math.max(0, Date.now() - startedAt),\n };\n if (typeof meta.timeoutMs === \"number\") event.timeoutMs = meta.timeoutMs;\n if (typeof args?.statusCode === \"number\") event.statusCode = args.statusCode;\n if (outcome === \"failure\") {\n event.errorKind =\n sanitizeTelemetryToken(args?.errorKind) ??\n inferTelemetryErrorKind(args?.error);\n }\n const line = `[integration] ${JSON.stringify(event)}`;\n if (outcome === \"success\") {\n logger.info(line);\n } else {\n logger.warn(line);\n }\n };\n\n return {\n success: (args) => finalize(\"success\", args),\n failure: (args) => finalize(\"failure\", args),\n };\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction routeJsonError(\n ctx: BrowserBridgeRouteContext,\n message: string,\n status: number,\n code?: BrowserBridgeCompanionAuthErrorCode | string | null,\n): void {\n ctx.json(\n ctx.res,\n {\n error: message,\n ...(code ? { code } : {}),\n },\n status,\n );\n}\n\nfunction isStatusError(\n error: unknown,\n): error is Error & { readonly status: number } {\n return (\n error instanceof Error &&\n \"status\" in error &&\n typeof error.status === \"number\"\n );\n}\n\nfunction statusErrorCode(error: unknown): string | null {\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n typeof (error as { code?: unknown }).code === \"string\"\n ) {\n return (error as { code: string }).code;\n }\n return null;\n}\n\nfunction decodeMatchedPathComponent(\n ctx: BrowserBridgeRouteContext,\n match: RegExpMatchArray | null,\n index: number,\n res: http.ServerResponse,\n label: string,\n): string | null {\n const raw = match?.[index];\n return raw ? ctx.decodePathComponent(raw, res, label) : null;\n}\n\nasync function runRoute(\n ctx: BrowserBridgeRouteContext,\n fn: (service: BrowserBridgeRouteService) => Promise<void>,\n): Promise<boolean> {\n const operation = routeOperation(ctx);\n const span = createBrowserBridgeTelemetrySpan({\n boundary: \"browser-bridge\",\n operation,\n });\n const service = getService(ctx);\n if (!service) {\n logger.info(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: 503,\n },\n \"[browser-bridge] Route rejected because agent runtime is unavailable\",\n );\n span.failure({\n statusCode: 503,\n errorKind: \"runtime_unavailable\",\n });\n return true;\n }\n try {\n await fn(service);\n span.success({\n statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200,\n });\n return true;\n } catch (error) {\n if (isStatusError(error)) {\n const logFn =\n error.status === 401\n ? logger.debug.bind(logger)\n : logger.warn.bind(logger);\n logFn(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: error.status,\n },\n `[browser-bridge] Route failed: ${error.message}`,\n );\n span.failure({\n statusCode: error.status,\n error,\n errorKind:\n error.status === 401\n ? \"browser_bridge_auth_invalid\"\n : \"browser_bridge_service_error\",\n });\n routeJsonError(ctx, error.message, error.status, statusErrorCode(error));\n return true;\n }\n logger.error(\n {\n boundary: \"browser-bridge\",\n operation,\n },\n `[browser-bridge] Route crashed: ${errorMessage(error)}`,\n );\n span.failure({\n error,\n errorKind: \"unhandled_error\",\n });\n throw error;\n }\n}\n\nasync function runStatelessRoute(\n ctx: BrowserBridgeRouteContext,\n fn: () => Promise<void>,\n): Promise<boolean> {\n const operation = routeOperation(ctx);\n const span = createBrowserBridgeTelemetrySpan({\n boundary: \"browser-bridge\",\n operation,\n });\n try {\n await fn();\n span.success({\n statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200,\n });\n return true;\n } catch (error) {\n if (isStatusError(error)) {\n logger.warn(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: error.status,\n },\n `[browser-bridge] Route failed: ${error.message}`,\n );\n span.failure({\n statusCode: error.status,\n error,\n errorKind: \"browser_bridge_service_error\",\n });\n ctx.error(ctx.res, error.message, error.status);\n return true;\n }\n logger.error(\n {\n boundary: \"browser-bridge\",\n operation,\n },\n `[browser-bridge] Route crashed: ${errorMessage(error)}`,\n );\n span.failure({\n error,\n errorKind: \"unhandled_error\",\n });\n throw error;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Dispatcher\n// ---------------------------------------------------------------------------\n\nexport async function handleBrowserBridgeRoutes(\n ctx: BrowserBridgeRouteContext,\n): Promise<boolean> {\n const { req, res, method, pathname, json, readJsonBody } = ctx;\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/sessions\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n sessions: await service.listBrowserSessions(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/settings\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n settings: await service.getBrowserSettings(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/settings\") {\n const body = await readJsonBody<UpdateBrowserBridgeSettingsRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(res, {\n settings: await service.updateBrowserSettings(\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/companions/pair\") {\n const body = await readJsonBody<CreateBrowserBridgeCompanionPairingRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.createBrowserCompanionPairing(\n body,\n ctx.state.adminEntityId,\n ),\n 201,\n );\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/companions/auto-pair\"\n ) {\n if (rateLimitRequest(ctx, \"companions:auto-pair\")) {\n return true;\n }\n if (!browserAutoPairOriginAllowed(ctx)) {\n ctx.error(\n res,\n \"browser auto-pair must come from the agent app or a browser extension\",\n 403,\n );\n return true;\n }\n const body =\n await readJsonBody<CreateBrowserBridgeCompanionAutoPairRequest>(req, res);\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.autoPairBrowserCompanion(\n body,\n ctx.url.origin,\n ctx.state.adminEntityId,\n ),\n 201,\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/companions\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n companions: await service.listBrowserCompanions(\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/companions/revoke\"\n ) {\n if (rateLimitRequest(ctx, \"companions:revoke\")) {\n return true;\n }\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n json(\n res,\n await service.revokeBrowserCompanionFromCompanion(\n auth.companionId,\n auth.pairingToken,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n const browserCompanionRevokeMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/([^/]+)\\/revoke$/,\n );\n if (method === \"POST\" && browserCompanionRevokeMatch) {\n const companionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionRevokeMatch,\n 1,\n res,\n \"browser companion id\",\n );\n if (!companionId) return true;\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.revokeBrowserCompanion(\n companionId,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/packages\") {\n return runStatelessRoute(ctx, async () => {\n json(res, { status: getBrowserBridgeCompanionPackageStatus() });\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/packages/open-path\"\n ) {\n if (!requestIsLoopback(ctx)) {\n ctx.error(\n res,\n \"Local extension install helpers can only run on the same machine as the agent\",\n 403,\n );\n return true;\n }\n const body = await readJsonBody<{\n target?: string;\n revealOnly?: boolean;\n }>(req, res);\n if (!body) return true;\n if (\n typeof body.target !== \"string\" ||\n !BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.includes(\n body.target as (typeof BROWSER_BRIDGE_PACKAGE_PATH_TARGETS)[number],\n )\n ) {\n ctx.error(\n res,\n `target must be one of: ${BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.join(\", \")}`,\n 400,\n );\n return true;\n }\n const validatedTarget =\n body.target as (typeof BROWSER_BRIDGE_PACKAGE_PATH_TARGETS)[number];\n return runStatelessRoute(ctx, async () => {\n json(\n res,\n await openBrowserBridgeCompanionPackagePath(validatedTarget, {\n revealOnly: body.revealOnly === true,\n }),\n );\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/companions/sync\") {\n if (rateLimitRequest(ctx, \"companions:sync\")) {\n return true;\n }\n const body = await readJsonBody<SyncBrowserBridgeStateRequest>(req, res);\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n json(\n res,\n await service.syncBrowserCompanion(\n auth.companionId,\n auth.pairingToken,\n body,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/tabs\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n tabs: await service.listBrowserTabs(ctx.state.adminEntityId),\n });\n });\n }\n\n const browserPackageBuildMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/build$/,\n );\n if (method === \"POST\" && browserPackageBuildMatch) {\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageBuildMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n json(res, {\n status: await buildBrowserBridgeCompanionPackage(browser),\n });\n });\n }\n\n const browserPackageOpenManagerMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/open-manager$/,\n );\n if (method === \"POST\" && browserPackageOpenManagerMatch) {\n if (!requestIsLoopback(ctx)) {\n ctx.error(\n res,\n \"Local extension install helpers can only run on the same machine as the agent\",\n 403,\n );\n return true;\n }\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageOpenManagerMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n json(res, await openBrowserBridgeCompanionManager(browser));\n });\n }\n\n const browserPackageDownloadMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/download$/,\n );\n if (method === \"GET\" && browserPackageDownloadMatch) {\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageDownloadMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n const artifact = getBrowserBridgeCompanionDownloadFile(browser);\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", artifact.contentType);\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${artifact.filename}\"`,\n );\n await new Promise<void>((resolve, reject) => {\n const stream = fs.createReadStream(artifact.path);\n stream.on(\"error\", reject);\n res.on(\"error\", reject);\n stream.on(\"end\", resolve);\n stream.pipe(res);\n });\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/current-page\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n page: await service.getCurrentBrowserPage(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/sync\") {\n const body = await readJsonBody<SyncBrowserBridgeStateRequest>(req, res);\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(res, await service.syncBrowserState(body, ctx.state.adminEntityId));\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/sessions\") {\n const body = await readJsonBody<CreateLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(\n res,\n {\n session: await service.createBrowserSession(\n body,\n ctx.state.adminEntityId,\n ),\n },\n 201,\n );\n });\n }\n\n const browserSessionMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)$/,\n );\n if (browserSessionMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserSessionMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n if (method === \"GET\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.getBrowserSession(\n sessionId,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n }\n\n const browserConfirmMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/confirm$/,\n );\n if (method === \"POST\" && browserConfirmMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserConfirmMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<ConfirmLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.confirmBrowserSession(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserProgressMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/progress$/,\n );\n if (method === \"POST\" && browserProgressMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserProgressMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<UpdateBrowserBridgeSessionProgressRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.updateBrowserSessionProgress(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompleteMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/complete$/,\n );\n if (method === \"POST\" && browserCompleteMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompleteMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<CompleteLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.completeBrowserSession(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompanionProgressMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/sessions\\/([^/]+)\\/progress$/,\n );\n if (method === \"POST\" && browserCompanionProgressMatch) {\n if (rateLimitRequest(ctx, \"companions:session-progress\")) {\n return true;\n }\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionProgressMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<UpdateBrowserBridgeSessionProgressRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n json(res, {\n session: await service.updateBrowserSessionProgressFromCompanion(\n auth.companionId,\n auth.pairingToken,\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompanionCompleteMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/sessions\\/([^/]+)\\/complete$/,\n );\n if (method === \"POST\" && browserCompanionCompleteMatch) {\n if (rateLimitRequest(ctx, \"companions:session-complete\")) {\n return true;\n }\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionCompleteMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<CompleteLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n json(res, {\n session: await service.completeBrowserSessionFromCompanion(\n auth.companionId,\n auth.pairingToken,\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n return false;\n}\n\nexport { rateLimitRequest };\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/routes/bridge.ts"],"sourcesContent":["/**\n * Agent Browser Bridge HTTP routes.\n *\n * Mounted under `/api/browser-bridge/*` by the plugin-collector. Pair +\n * settings + companion-sync + tabs + current-page + packaging endpoints\n * are fully generic. Session endpoints (confirm/progress/complete) still\n * operate on the LifeOps-owned `life_browser_sessions` table and therefore\n * call into the LifeOps route service because that app owns workflow-scoped\n * browser sessions.\n *\n * Companion/auto-pair/sync authentication uses the\n * `X-Browser-Bridge-Companion-Id` header paired with a bearer pairing\n * token. The old `X-LifeOps-Browser-Companion-Id` and legacy\n * `x-eliza-browser-companion-id` aliases were deliberately removed.\n */\n\nimport fs from \"node:fs\";\nimport type http from \"node:http\";\nimport type { ReadJsonBodyOptions } from \"@elizaos/core\";\nimport { type AgentRuntime, logger, type UUID } from \"@elizaos/core\";\nimport {\n BROWSER_BRIDGE_PACKAGE_PATH_TARGETS,\n type BrowserBridgeCompanionAuthErrorCode,\n type CreateBrowserBridgeCompanionAutoPairRequest,\n type CreateBrowserBridgeCompanionPairingRequest,\n type SyncBrowserBridgeStateRequest,\n type UpdateBrowserBridgeSessionProgressRequest,\n type UpdateBrowserBridgeSettingsRequest,\n} from \"../contracts.js\";\nimport type {\n CompleteLifeOpsBrowserSessionRequest,\n ConfirmLifeOpsBrowserSessionRequest,\n CreateLifeOpsBrowserSessionRequest,\n} from \"../lifeops-session-contracts.js\";\nimport {\n buildBrowserBridgeCompanionPackage,\n getBrowserBridgeCompanionDownloadFile,\n getBrowserBridgeCompanionPackageStatus,\n openBrowserBridgeCompanionManager,\n openBrowserBridgeCompanionPackagePath,\n} from \"../packaging.js\";\nimport {\n BROWSER_BRIDGE_ROUTE_SERVICE_TYPE,\n type BrowserBridgeRouteService,\n} from \"../service.js\";\n\nexport interface BrowserBridgeRouteContext {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n method: string;\n pathname: string;\n url: URL;\n state: {\n runtime: AgentRuntime | null;\n adminEntityId: UUID | null;\n };\n json: (res: http.ServerResponse, data: unknown, status?: number) => void;\n error: (res: http.ServerResponse, message: string, status?: number) => void;\n readJsonBody: <T extends object>(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n options?: ReadJsonBodyOptions,\n ) => Promise<T | null>;\n decodePathComponent: (\n raw: string,\n res: http.ServerResponse,\n label: string,\n ) => string | null;\n}\n\nfunction getService(\n ctx: BrowserBridgeRouteContext,\n): BrowserBridgeRouteService | null {\n if (!ctx.state.runtime) {\n ctx.error(ctx.res, \"Agent runtime is not available\", 503);\n return null;\n }\n const service = ctx.state.runtime.getService<BrowserBridgeRouteService>(\n BROWSER_BRIDGE_ROUTE_SERVICE_TYPE,\n );\n if (!service) {\n ctx.error(ctx.res, \"Browser Bridge service is not available\", 503);\n return null;\n }\n return service;\n}\n\nfunction getBrowserCompanionAuth(\n ctx: BrowserBridgeRouteContext,\n): { companionId: string; pairingToken: string } | null {\n const companionHeader = ctx.req.headers[\"x-browser-bridge-companion-id\"];\n const companionId =\n typeof companionHeader === \"string\" ? companionHeader.trim() : \"\";\n if (!companionId) {\n routeJsonError(\n ctx,\n \"Missing X-Browser-Bridge-Companion-Id header\",\n 401,\n \"browser_bridge_companion_auth_missing_id\",\n );\n return null;\n }\n const authHeader =\n typeof ctx.req.headers.authorization === \"string\"\n ? ctx.req.headers.authorization.trim()\n : \"\";\n const match = /^Bearer\\s+(.+)$/i.exec(authHeader);\n const pairingToken = match?.[1]?.trim() ?? \"\";\n if (!pairingToken) {\n routeJsonError(\n ctx,\n \"Missing browser companion bearer token\",\n 401,\n \"browser_bridge_companion_auth_missing_token\",\n );\n return null;\n }\n return {\n companionId,\n pairingToken,\n };\n}\n\nfunction browserAutoPairOriginAllowed(ctx: BrowserBridgeRouteContext): boolean {\n const originHeader =\n typeof ctx.req.headers.origin === \"string\"\n ? ctx.req.headers.origin.trim()\n : \"\";\n if (!originHeader) {\n return requestIsLoopback(ctx);\n }\n if (originHeader === ctx.url.origin) {\n return true;\n }\n return (\n originHeader.startsWith(\"chrome-extension://\") ||\n originHeader.startsWith(\"safari-web-extension://\")\n );\n}\n\nfunction requestIsLoopback(ctx: BrowserBridgeRouteContext): boolean {\n const remoteAddress = ctx.req.socket.remoteAddress?.trim().toLowerCase();\n return (\n remoteAddress === \"127.0.0.1\" ||\n remoteAddress === \"::1\" ||\n remoteAddress === \"0:0:0:0:0:0:0:1\" ||\n remoteAddress === \"::ffff:127.0.0.1\" ||\n remoteAddress === \"::ffff:0:127.0.0.1\"\n );\n}\n\nconst BROWSER_BRIDGE_RATE_LIMITS = {\n default: { maxRequests: 60, windowMs: 60_000 },\n} satisfies Record<string, RateLimitConfig>;\n\ninterface RateLimitConfig {\n maxRequests: number;\n windowMs: number;\n}\n\ninterface RateLimitEntry {\n timestamps: number[];\n}\n\nconst rateLimitBuckets = new Map<string, RateLimitEntry>();\nconst RATE_LIMIT_CLEANUP_INTERVAL_MS = 5 * 60 * 1_000;\nlet lastRateLimitCleanup = Date.now();\n\nfunction cleanupRateLimitBuckets(windowMs: number): void {\n const now = Date.now();\n if (now - lastRateLimitCleanup < RATE_LIMIT_CLEANUP_INTERVAL_MS) return;\n lastRateLimitCleanup = now;\n const cutoff = now - windowMs;\n for (const [key, entry] of rateLimitBuckets) {\n entry.timestamps = entry.timestamps.filter(\n (timestamp) => timestamp > cutoff,\n );\n if (entry.timestamps.length === 0) rateLimitBuckets.delete(key);\n }\n}\n\nfunction checkBrowserBridgeRateLimit(\n key: string,\n config: RateLimitConfig,\n): { allowed: boolean; retryAfterMs: number } {\n const now = Date.now();\n cleanupRateLimitBuckets(config.windowMs);\n let entry = rateLimitBuckets.get(key);\n if (!entry) {\n entry = { timestamps: [] };\n rateLimitBuckets.set(key, entry);\n }\n\n const cutoff = now - config.windowMs;\n entry.timestamps = entry.timestamps.filter((timestamp) => timestamp > cutoff);\n\n if (entry.timestamps.length >= config.maxRequests) {\n const oldestInWindow = entry.timestamps[0];\n const retryAfterMs =\n oldestInWindow === undefined ? 0 : oldestInWindow + config.windowMs - now;\n return { allowed: false, retryAfterMs: Math.max(retryAfterMs, 0) };\n }\n\n entry.timestamps.push(now);\n return { allowed: true, retryAfterMs: 0 };\n}\n\nfunction rateLimitRequest(\n ctx: BrowserBridgeRouteContext,\n operation: string,\n): boolean {\n const agentId = String(ctx.state.runtime?.agentId ?? \"unknown\");\n const companionHeader = ctx.req.headers[\"x-browser-bridge-companion-id\"];\n const companionId =\n typeof companionHeader === \"string\" ? companionHeader.trim() : \"anonymous\";\n const remoteAddress = ctx.req.socket.remoteAddress?.trim() ?? \"unknown\";\n const limitKey = `${agentId}:${operation}:${remoteAddress}:${companionId}`;\n const config = BROWSER_BRIDGE_RATE_LIMITS.default;\n const { allowed, retryAfterMs } = checkBrowserBridgeRateLimit(\n limitKey,\n config,\n );\n if (!allowed) {\n ctx.res.writeHead(429, {\n \"Content-Type\": \"application/json\",\n \"Retry-After\": String(Math.ceil(retryAfterMs / 1_000)),\n });\n ctx.res.end(JSON.stringify({ error: \"Rate limit exceeded\", retryAfterMs }));\n return true;\n }\n return false;\n}\n\nfunction routeOperation(ctx: BrowserBridgeRouteContext): string {\n return `${ctx.method.toUpperCase()} ${ctx.pathname}`;\n}\n\ninterface BrowserBridgeTelemetrySpan {\n success: (args?: { statusCode?: number }) => void;\n failure: (args?: {\n statusCode?: number;\n error?: unknown;\n errorKind?: string;\n }) => void;\n}\n\nfunction sanitizeTelemetryToken(value: string | undefined): string | undefined {\n if (!value) return undefined;\n const token = value.toLowerCase().replace(/[^a-z0-9_-]/g, \"_\");\n const normalized = token.replace(/_+/g, \"_\").replace(/^_+|_+$/g, \"\");\n return normalized ? normalized.slice(0, 64) : undefined;\n}\n\nfunction inferTelemetryErrorKind(error: unknown): string | undefined {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (\n error.name === \"AbortError\" ||\n error.name === \"TimeoutError\" ||\n message.includes(\"timeout\") ||\n message.includes(\"timed out\")\n ) {\n return \"timeout\";\n }\n return sanitizeTelemetryToken(error.name);\n }\n return typeof error === \"string\" ? sanitizeTelemetryToken(error) : undefined;\n}\n\nfunction createBrowserBridgeTelemetrySpan(meta: {\n boundary: \"browser-bridge\";\n operation: string;\n timeoutMs?: number;\n}): BrowserBridgeTelemetrySpan {\n const startedAt = Date.now();\n let settled = false;\n\n const finalize = (\n outcome: \"success\" | \"failure\",\n args?: { statusCode?: number; error?: unknown; errorKind?: string },\n ): void => {\n if (settled) return;\n settled = true;\n const event: Record<string, unknown> = {\n schema: \"integration_boundary_v1\",\n boundary: meta.boundary,\n operation: meta.operation,\n outcome,\n durationMs: Math.max(0, Date.now() - startedAt),\n };\n if (typeof meta.timeoutMs === \"number\") event.timeoutMs = meta.timeoutMs;\n if (typeof args?.statusCode === \"number\")\n event.statusCode = args.statusCode;\n if (outcome === \"failure\") {\n event.errorKind =\n sanitizeTelemetryToken(args?.errorKind) ??\n inferTelemetryErrorKind(args?.error);\n }\n const line = `[integration] ${JSON.stringify(event)}`;\n if (outcome === \"success\") {\n logger.info(line);\n } else {\n logger.warn(line);\n }\n };\n\n return {\n success: (args) => finalize(\"success\", args),\n failure: (args) => finalize(\"failure\", args),\n };\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction routeJsonError(\n ctx: BrowserBridgeRouteContext,\n message: string,\n status: number,\n code?: BrowserBridgeCompanionAuthErrorCode | string | null,\n): void {\n ctx.json(\n ctx.res,\n {\n error: message,\n ...(code ? { code } : {}),\n },\n status,\n );\n}\n\nfunction isBrowserBridgeRouteBodyObject(\n value: unknown,\n): value is Record<string, unknown> {\n return Boolean(value && typeof value === \"object\" && !Array.isArray(value));\n}\n\nfunction rejectMalformedBrowserBridgePayload(\n ctx: BrowserBridgeRouteContext,\n): true {\n ctx.error(ctx.res, \"request body must be a JSON object\", 400);\n return true;\n}\n\nfunction isStatusError(\n error: unknown,\n): error is Error & { readonly status: number } {\n return (\n error instanceof Error &&\n \"status\" in error &&\n typeof error.status === \"number\"\n );\n}\n\nfunction statusErrorCode(error: unknown): string | null {\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n typeof (error as { code?: unknown }).code === \"string\"\n ) {\n return (error as { code: string }).code;\n }\n return null;\n}\n\nfunction decodeMatchedPathComponent(\n ctx: BrowserBridgeRouteContext,\n match: RegExpMatchArray | null,\n index: number,\n res: http.ServerResponse,\n label: string,\n): string | null {\n const raw = match?.[index];\n return raw ? ctx.decodePathComponent(raw, res, label) : null;\n}\n\nasync function runRoute(\n ctx: BrowserBridgeRouteContext,\n fn: (service: BrowserBridgeRouteService) => Promise<void>,\n): Promise<boolean> {\n const operation = routeOperation(ctx);\n const span = createBrowserBridgeTelemetrySpan({\n boundary: \"browser-bridge\",\n operation,\n });\n const service = getService(ctx);\n if (!service) {\n logger.info(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: 503,\n },\n \"[browser-bridge] Route rejected because agent runtime is unavailable\",\n );\n span.failure({\n statusCode: 503,\n errorKind: \"runtime_unavailable\",\n });\n return true;\n }\n try {\n await fn(service);\n span.success({\n statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200,\n });\n return true;\n } catch (error) {\n if (isStatusError(error)) {\n const logFn =\n error.status === 401\n ? logger.debug.bind(logger)\n : logger.warn.bind(logger);\n logFn(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: error.status,\n },\n `[browser-bridge] Route failed: ${error.message}`,\n );\n span.failure({\n statusCode: error.status,\n error,\n errorKind:\n error.status === 401\n ? \"browser_bridge_auth_invalid\"\n : \"browser_bridge_service_error\",\n });\n routeJsonError(ctx, error.message, error.status, statusErrorCode(error));\n return true;\n }\n logger.error(\n {\n boundary: \"browser-bridge\",\n operation,\n },\n `[browser-bridge] Route crashed: ${errorMessage(error)}`,\n );\n span.failure({\n error,\n errorKind: \"unhandled_error\",\n });\n throw error;\n }\n}\n\nasync function runStatelessRoute(\n ctx: BrowserBridgeRouteContext,\n fn: () => Promise<void>,\n): Promise<boolean> {\n const operation = routeOperation(ctx);\n const span = createBrowserBridgeTelemetrySpan({\n boundary: \"browser-bridge\",\n operation,\n });\n try {\n await fn();\n span.success({\n statusCode: ctx.res.statusCode >= 400 ? ctx.res.statusCode : 200,\n });\n return true;\n } catch (error) {\n if (isStatusError(error)) {\n logger.warn(\n {\n boundary: \"browser-bridge\",\n operation,\n statusCode: error.status,\n },\n `[browser-bridge] Route failed: ${error.message}`,\n );\n span.failure({\n statusCode: error.status,\n error,\n errorKind: \"browser_bridge_service_error\",\n });\n ctx.error(ctx.res, error.message, error.status);\n return true;\n }\n logger.error(\n {\n boundary: \"browser-bridge\",\n operation,\n },\n `[browser-bridge] Route crashed: ${errorMessage(error)}`,\n );\n span.failure({\n error,\n errorKind: \"unhandled_error\",\n });\n throw error;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Dispatcher\n// ---------------------------------------------------------------------------\n\nexport async function handleBrowserBridgeRoutes(\n ctx: BrowserBridgeRouteContext,\n): Promise<boolean> {\n const { req, res, method, pathname, json, readJsonBody } = ctx;\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/sessions\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n sessions: await service.listBrowserSessions(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/settings\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n settings: await service.getBrowserSettings(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/settings\") {\n const body = await readJsonBody<UpdateBrowserBridgeSettingsRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(res, {\n settings: await service.updateBrowserSettings(\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/companions/pair\") {\n const body = await readJsonBody<CreateBrowserBridgeCompanionPairingRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.createBrowserCompanionPairing(\n body,\n ctx.state.adminEntityId,\n ),\n 201,\n );\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/companions/auto-pair\"\n ) {\n if (rateLimitRequest(ctx, \"companions:auto-pair\")) {\n return true;\n }\n if (!browserAutoPairOriginAllowed(ctx)) {\n ctx.error(\n res,\n \"browser auto-pair must come from the agent app or a browser extension\",\n 403,\n );\n return true;\n }\n const body =\n await readJsonBody<CreateBrowserBridgeCompanionAutoPairRequest>(req, res);\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.autoPairBrowserCompanion(\n body,\n ctx.url.origin,\n ctx.state.adminEntityId,\n ),\n 201,\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/companions\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n companions: await service.listBrowserCompanions(\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/companions/revoke\"\n ) {\n if (rateLimitRequest(ctx, \"companions:revoke\")) {\n return true;\n }\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n json(\n res,\n await service.revokeBrowserCompanionFromCompanion(\n auth.companionId,\n auth.pairingToken,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n const browserCompanionRevokeMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/([^/]+)\\/revoke$/,\n );\n if (method === \"POST\" && browserCompanionRevokeMatch) {\n const companionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionRevokeMatch,\n 1,\n res,\n \"browser companion id\",\n );\n if (!companionId) return true;\n return runRoute(ctx, async (service) => {\n json(\n res,\n await service.revokeBrowserCompanion(\n companionId,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/packages\") {\n return runStatelessRoute(ctx, async () => {\n json(res, { status: getBrowserBridgeCompanionPackageStatus() });\n });\n }\n\n if (\n method === \"POST\" &&\n pathname === \"/api/browser-bridge/packages/open-path\"\n ) {\n if (!requestIsLoopback(ctx)) {\n ctx.error(\n res,\n \"Local extension install helpers can only run on the same machine as the agent\",\n 403,\n );\n return true;\n }\n const body = await readJsonBody<{\n target?: string;\n revealOnly?: boolean;\n }>(req, res);\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n if (\n typeof body.target !== \"string\" ||\n !BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.includes(\n body.target as (typeof BROWSER_BRIDGE_PACKAGE_PATH_TARGETS)[number],\n )\n ) {\n ctx.error(\n res,\n `target must be one of: ${BROWSER_BRIDGE_PACKAGE_PATH_TARGETS.join(\", \")}`,\n 400,\n );\n return true;\n }\n const validatedTarget =\n body.target as (typeof BROWSER_BRIDGE_PACKAGE_PATH_TARGETS)[number];\n return runStatelessRoute(ctx, async () => {\n json(\n res,\n await openBrowserBridgeCompanionPackagePath(validatedTarget, {\n revealOnly: body.revealOnly === true,\n }),\n );\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/companions/sync\") {\n if (rateLimitRequest(ctx, \"companions:sync\")) {\n return true;\n }\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n const body = await readJsonBody<SyncBrowserBridgeStateRequest>(req, res);\n if (!body) return;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n rejectMalformedBrowserBridgePayload(ctx);\n return;\n }\n json(\n res,\n await service.syncBrowserCompanion(\n auth.companionId,\n auth.pairingToken,\n body,\n ctx.state.adminEntityId,\n ),\n );\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/tabs\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n tabs: await service.listBrowserTabs(ctx.state.adminEntityId),\n });\n });\n }\n\n const browserPackageBuildMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/build$/,\n );\n if (method === \"POST\" && browserPackageBuildMatch) {\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageBuildMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n json(res, {\n status: await buildBrowserBridgeCompanionPackage(browser),\n });\n });\n }\n\n const browserPackageOpenManagerMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/open-manager$/,\n );\n if (method === \"POST\" && browserPackageOpenManagerMatch) {\n if (!requestIsLoopback(ctx)) {\n ctx.error(\n res,\n \"Local extension install helpers can only run on the same machine as the agent\",\n 403,\n );\n return true;\n }\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageOpenManagerMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n json(res, await openBrowserBridgeCompanionManager(browser));\n });\n }\n\n const browserPackageDownloadMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/packages\\/([^/]+)\\/download$/,\n );\n if (method === \"GET\" && browserPackageDownloadMatch) {\n const browser = decodeMatchedPathComponent(\n ctx,\n browserPackageDownloadMatch,\n 1,\n res,\n \"browser package target\",\n );\n if (!browser) return true;\n if (browser !== \"chrome\" && browser !== \"safari\") {\n ctx.error(res, \"browser must be chrome or safari\", 400);\n return true;\n }\n return runStatelessRoute(ctx, async () => {\n const artifact = getBrowserBridgeCompanionDownloadFile(browser);\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", artifact.contentType);\n res.setHeader(\n \"Content-Disposition\",\n `attachment; filename=\"${artifact.filename}\"`,\n );\n await new Promise<void>((resolve, reject) => {\n const stream = fs.createReadStream(artifact.path);\n stream.on(\"error\", reject);\n res.on(\"error\", reject);\n stream.on(\"end\", resolve);\n stream.pipe(res);\n });\n });\n }\n\n if (method === \"GET\" && pathname === \"/api/browser-bridge/current-page\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n page: await service.getCurrentBrowserPage(ctx.state.adminEntityId),\n });\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/sync\") {\n const body = await readJsonBody<SyncBrowserBridgeStateRequest>(req, res);\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(res, await service.syncBrowserState(body, ctx.state.adminEntityId));\n });\n }\n\n if (method === \"POST\" && pathname === \"/api/browser-bridge/sessions\") {\n const body = await readJsonBody<CreateLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(\n res,\n {\n session: await service.createBrowserSession(\n body,\n ctx.state.adminEntityId,\n ),\n },\n 201,\n );\n });\n }\n\n const browserSessionMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)$/,\n );\n if (browserSessionMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserSessionMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n if (method === \"GET\") {\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.getBrowserSession(\n sessionId,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n }\n\n const browserConfirmMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/confirm$/,\n );\n if (method === \"POST\" && browserConfirmMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserConfirmMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<ConfirmLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.confirmBrowserSession(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserProgressMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/progress$/,\n );\n if (method === \"POST\" && browserProgressMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserProgressMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<UpdateBrowserBridgeSessionProgressRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.updateBrowserSessionProgress(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompleteMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/sessions\\/([^/]+)\\/complete$/,\n );\n if (method === \"POST\" && browserCompleteMatch) {\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompleteMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n const body = await readJsonBody<CompleteLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return true;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n return rejectMalformedBrowserBridgePayload(ctx);\n }\n return runRoute(ctx, async (service) => {\n json(res, {\n session: await service.completeBrowserSession(\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompanionProgressMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/sessions\\/([^/]+)\\/progress$/,\n );\n if (method === \"POST\" && browserCompanionProgressMatch) {\n if (rateLimitRequest(ctx, \"companions:session-progress\")) {\n return true;\n }\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionProgressMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n const body = await readJsonBody<UpdateBrowserBridgeSessionProgressRequest>(\n req,\n res,\n );\n if (!body) return;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n rejectMalformedBrowserBridgePayload(ctx);\n return;\n }\n json(res, {\n session: await service.updateBrowserSessionProgressFromCompanion(\n auth.companionId,\n auth.pairingToken,\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n const browserCompanionCompleteMatch = pathname.match(\n /^\\/api\\/browser-bridge\\/companions\\/sessions\\/([^/]+)\\/complete$/,\n );\n if (method === \"POST\" && browserCompanionCompleteMatch) {\n if (rateLimitRequest(ctx, \"companions:session-complete\")) {\n return true;\n }\n const sessionId = decodeMatchedPathComponent(\n ctx,\n browserCompanionCompleteMatch,\n 1,\n res,\n \"browser session id\",\n );\n if (!sessionId) return true;\n return runRoute(ctx, async (service) => {\n const auth = getBrowserCompanionAuth(ctx);\n if (!auth) {\n return;\n }\n const body = await readJsonBody<CompleteLifeOpsBrowserSessionRequest>(\n req,\n res,\n );\n if (!body) return;\n if (!isBrowserBridgeRouteBodyObject(body)) {\n rejectMalformedBrowserBridgePayload(ctx);\n return;\n }\n json(res, {\n session: await service.completeBrowserSessionFromCompanion(\n auth.companionId,\n auth.pairingToken,\n sessionId,\n body,\n ctx.state.adminEntityId,\n ),\n });\n });\n }\n\n return false;\n}\n\nexport { rateLimitRequest };\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-setup.d.ts","sourceRoot":"","sources":["../../src/routes/workspace-setup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,KAAK,EAAiB,KAAK,EAAE,MAAM,eAAe,CAAC;AAmE1D,eAAO,MAAM,sBAAsB,EAAE,KAAK,EASvC,CAAC"}
1
+ {"version":3,"file":"workspace-setup.d.ts","sourceRoot":"","sources":["../../src/routes/workspace-setup.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAqC,KAAK,EAAE,MAAM,eAAe,CAAC;AAsE9E,eAAO,MAAM,sBAAsB,EAAE,KAAK,EASvC,CAAC"}
@@ -1,9 +1,9 @@
1
+ import { TLSSocket } from "node:tls";
1
2
  import {
2
3
  readJsonBody as httpReadJsonBody,
3
4
  sendJson as httpSendJson,
4
5
  sendJsonError as httpSendJsonError
5
6
  } from "@elizaos/core";
6
- import { TLSSocket } from "node:tls";
7
7
  import {
8
8
  BROWSER_WORKSPACE_ROUTE_PATHS,
9
9
  handleBrowserWorkspaceRoutes
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/workspace-setup.ts"],"sourcesContent":["/**\n * Browser workspace HTTP routes — Plugin route registration.\n *\n * Mounts the legacy `/api/browser-workspace/*` paths through `Plugin.routes`\n * with `rawPath: true` so they keep their absolute paths under the runtime\n * route registry (no `/<pluginName>/` prefix).\n */\n\nimport type http from \"node:http\";\nimport {\n readJsonBody as httpReadJsonBody,\n sendJson as httpSendJson,\n sendJsonError as httpSendJsonError,\n} from \"@elizaos/core\";\nimport { TLSSocket } from \"node:tls\";\nimport type { IAgentRuntime, Route } from \"@elizaos/core\";\nimport {\n BROWSER_WORKSPACE_ROUTE_PATHS,\n handleBrowserWorkspaceRoutes,\n} from \"./workspace.js\";\n\nfunction json(res: http.ServerResponse, data: unknown, status = 200): void {\n httpSendJson(res, data, status);\n}\n\nfunction error(res: http.ServerResponse, message: string, status = 400): void {\n httpSendJsonError(res, message, status);\n}\n\nfunction firstHeaderValue(value: string | string[] | undefined): string | null {\n if (Array.isArray(value)) {\n return firstHeaderValue(value[0]);\n }\n if (typeof value !== \"string\") {\n return null;\n }\n const normalized = value.split(\",\")[0]?.trim();\n return normalized ? normalized : null;\n}\n\nfunction requestBaseUrl(req: http.IncomingMessage): string {\n const headers = req.headers ?? {};\n const protocol =\n firstHeaderValue(headers[\"x-forwarded-proto\"]) ??\n (req.socket instanceof TLSSocket && req.socket.encrypted\n ? \"https\"\n : \"http\");\n const host =\n firstHeaderValue(headers[\"x-forwarded-host\"]) ??\n firstHeaderValue(headers.host) ??\n \"localhost\";\n return `${protocol}://${host}`;\n}\n\ntype PluginRouteHandler = NonNullable<Route[\"handler\"]>;\n\nfunction browserWorkspaceRouteHandler(): PluginRouteHandler {\n return async (\n req: unknown,\n res: unknown,\n runtime: unknown,\n ): Promise<void> => {\n const httpReq = req as http.IncomingMessage;\n const httpRes = res as http.ServerResponse;\n const method = (httpReq.method ?? \"GET\").toUpperCase();\n const url = new URL(httpReq.url ?? \"/\", requestBaseUrl(httpReq));\n await handleBrowserWorkspaceRoutes({\n req: httpReq,\n res: httpRes,\n method,\n pathname: url.pathname,\n url,\n state: {\n runtime: (runtime as IAgentRuntime) ?? null,\n },\n readJsonBody: httpReadJsonBody,\n json,\n error,\n });\n };\n}\n\nexport const browserWorkspaceRoutes: Route[] =\n BROWSER_WORKSPACE_ROUTE_PATHS.map(\n (r) =>\n ({\n type: r.type as Route[\"type\"],\n path: r.path,\n rawPath: true as const,\n handler: browserWorkspaceRouteHandler(),\n }) as Route,\n );\n"],"mappings":"AASA;AAAA,EACE,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,iBAAiB;AAAA,OACZ;AACP,SAAS,iBAAiB;AAE1B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,SAAS,KAAK,KAA0B,MAAe,SAAS,KAAW;AACzE,eAAa,KAAK,MAAM,MAAM;AAChC;AAEA,SAAS,MAAM,KAA0B,SAAiB,SAAS,KAAW;AAC5E,oBAAkB,KAAK,SAAS,MAAM;AACxC;AAEA,SAAS,iBAAiB,OAAqD;AAC7E,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,iBAAiB,MAAM,CAAC,CAAC;AAAA,EAClC;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO;AAAA,EACT;AACA,QAAM,aAAa,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AAC7C,SAAO,aAAa,aAAa;AACnC;AAEA,SAAS,eAAe,KAAmC;AACzD,QAAM,UAAU,IAAI,WAAW,CAAC;AAChC,QAAM,WACJ,iBAAiB,QAAQ,mBAAmB,CAAC,MAC5C,IAAI,kBAAkB,aAAa,IAAI,OAAO,YAC3C,UACA;AACN,QAAM,OACJ,iBAAiB,QAAQ,kBAAkB,CAAC,KAC5C,iBAAiB,QAAQ,IAAI,KAC7B;AACF,SAAO,GAAG,QAAQ,MAAM,IAAI;AAC9B;AAIA,SAAS,+BAAmD;AAC1D,SAAO,OACL,KACA,KACA,YACkB;AAClB,UAAM,UAAU;AAChB,UAAM,UAAU;AAChB,UAAM,UAAU,QAAQ,UAAU,OAAO,YAAY;AACrD,UAAM,MAAM,IAAI,IAAI,QAAQ,OAAO,KAAK,eAAe,OAAO,CAAC;AAC/D,UAAM,6BAA6B;AAAA,MACjC,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA,UAAU,IAAI;AAAA,MACd;AAAA,MACA,OAAO;AAAA,QACL,SAAU,WAA6B;AAAA,MACzC;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEO,MAAM,yBACX,8BAA8B;AAAA,EAC5B,CAAC,OACE;AAAA,IACC,MAAM,EAAE;AAAA,IACR,MAAM,EAAE;AAAA,IACR,SAAS;AAAA,IACT,SAAS,6BAA6B;AAAA,EACxC;AACJ;","names":[]}
1
+ {"version":3,"sources":["../../src/routes/workspace-setup.ts"],"sourcesContent":["/**\n * Browser workspace HTTP routes — Plugin route registration.\n *\n * Mounts the legacy `/api/browser-workspace/*` paths through `Plugin.routes`\n * with `rawPath: true` so they keep their absolute paths under the runtime\n * route registry (no `/<pluginName>/` prefix).\n */\n\nimport type http from \"node:http\";\nimport { TLSSocket } from \"node:tls\";\nimport type { IAgentRuntime, LegacyRouteHandler, Route } from \"@elizaos/core\";\nimport {\n readJsonBody as httpReadJsonBody,\n sendJson as httpSendJson,\n sendJsonError as httpSendJsonError,\n} from \"@elizaos/core\";\nimport {\n BROWSER_WORKSPACE_ROUTE_PATHS,\n handleBrowserWorkspaceRoutes,\n} from \"./workspace.js\";\n\nfunction json(res: http.ServerResponse, data: unknown, status = 200): void {\n httpSendJson(res, data, status);\n}\n\nfunction error(res: http.ServerResponse, message: string, status = 400): void {\n httpSendJsonError(res, message, status);\n}\n\nfunction firstHeaderValue(value: string | string[] | undefined): string | null {\n if (Array.isArray(value)) {\n return firstHeaderValue(value[0]);\n }\n if (typeof value !== \"string\") {\n return null;\n }\n const normalized = value.split(\",\")[0]?.trim();\n return normalized ? normalized : null;\n}\n\nfunction requestBaseUrl(req: http.IncomingMessage): string {\n const headers = req.headers ?? {};\n const protocol =\n firstHeaderValue(headers[\"x-forwarded-proto\"]) ??\n (req.socket instanceof TLSSocket && req.socket.encrypted\n ? \"https\"\n : \"http\");\n const host =\n firstHeaderValue(headers[\"x-forwarded-host\"]) ??\n firstHeaderValue(headers.host) ??\n \"localhost\";\n return `${protocol}://${host}`;\n}\n\nfunction browserWorkspaceRouteHandler(): LegacyRouteHandler {\n return async (\n req: unknown,\n res: unknown,\n runtime: unknown,\n ): Promise<void> => {\n const httpReq = req as http.IncomingMessage;\n const httpRes = res as http.ServerResponse;\n const method = (httpReq.method ?? \"GET\").toUpperCase();\n const url = new URL(httpReq.url ?? \"/\", requestBaseUrl(httpReq));\n await handleBrowserWorkspaceRoutes({\n req: httpReq,\n res: httpRes,\n method,\n pathname: url.pathname,\n url,\n state: {\n runtime: (runtime as IAgentRuntime) ?? null,\n },\n readJsonBody: httpReadJsonBody,\n json,\n error,\n });\n };\n}\n\nexport const browserWorkspaceRoutes: Route[] =\n BROWSER_WORKSPACE_ROUTE_PATHS.map(\n (r) =>\n ({\n type: r.type as Route[\"type\"],\n path: r.path,\n rawPath: true as const,\n handler: browserWorkspaceRouteHandler(),\n }) as Route,\n );\n"],"mappings":"AASA,SAAS,iBAAiB;AAE1B;AAAA,EACE,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,iBAAiB;AAAA,OACZ;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,SAAS,KAAK,KAA0B,MAAe,SAAS,KAAW;AACzE,eAAa,KAAK,MAAM,MAAM;AAChC;AAEA,SAAS,MAAM,KAA0B,SAAiB,SAAS,KAAW;AAC5E,oBAAkB,KAAK,SAAS,MAAM;AACxC;AAEA,SAAS,iBAAiB,OAAqD;AAC7E,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,iBAAiB,MAAM,CAAC,CAAC;AAAA,EAClC;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO;AAAA,EACT;AACA,QAAM,aAAa,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AAC7C,SAAO,aAAa,aAAa;AACnC;AAEA,SAAS,eAAe,KAAmC;AACzD,QAAM,UAAU,IAAI,WAAW,CAAC;AAChC,QAAM,WACJ,iBAAiB,QAAQ,mBAAmB,CAAC,MAC5C,IAAI,kBAAkB,aAAa,IAAI,OAAO,YAC3C,UACA;AACN,QAAM,OACJ,iBAAiB,QAAQ,kBAAkB,CAAC,KAC5C,iBAAiB,QAAQ,IAAI,KAC7B;AACF,SAAO,GAAG,QAAQ,MAAM,IAAI;AAC9B;AAEA,SAAS,+BAAmD;AAC1D,SAAO,OACL,KACA,KACA,YACkB;AAClB,UAAM,UAAU;AAChB,UAAM,UAAU;AAChB,UAAM,UAAU,QAAQ,UAAU,OAAO,YAAY;AACrD,UAAM,MAAM,IAAI,IAAI,QAAQ,OAAO,KAAK,eAAe,OAAO,CAAC;AAC/D,UAAM,6BAA6B;AAAA,MACjC,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA,UAAU,IAAI;AAAA,MACd;AAAA,MACA,OAAO;AAAA,QACL,SAAU,WAA6B;AAAA,MACzC;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEO,MAAM,yBACX,8BAA8B;AAAA,EAC5B,CAAC,OACE;AAAA,IACC,MAAM,EAAE;AAAA,IACR,MAAM,EAAE;AAAA,IACR,SAAS;AAAA,IACT,SAAS,6BAA6B;AAAA,EACxC;AACJ;","names":[]}
@@ -5,8 +5,7 @@
5
5
  * legacy `/api/browser-workspace/*` paths are preserved. Implementation
6
6
  * lives in `@elizaos/plugin-browser/workspace`; this is the HTTP edge.
7
7
  */
8
- import type { RouteRequestContext } from "@elizaos/core";
9
- import type { IAgentRuntime } from "@elizaos/core";
8
+ import type { IAgentRuntime, RouteRequestContext } from "@elizaos/core";
10
9
  export interface BrowserWorkspaceRouteContext extends RouteRequestContext {
11
10
  url?: URL;
12
11
  state?: {
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/routes/workspace.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAqDnD,MAAM,WAAW,4BAA6B,SAAQ,mBAAmB;IACvE,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;KAChC,CAAC;CACH;AAuDD,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,4BAA4B,GAChC,OAAO,CAAC,OAAO,CAAC,CAsKlB;AAED,eAAO,MAAM,6BAA6B,EAAE,KAAK,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAWA,CAAC"}
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/routes/workspace.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAyDxE,MAAM,WAAW,4BAA6B,SAAQ,mBAAmB;IACvE,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;KAChC,CAAC;CACH;AA0FD,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,4BAA4B,GAChC,OAAO,CAAC,OAAO,CAAC,CAqMlB;AAED,eAAO,MAAM,6BAA6B,EAAE,KAAK,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAYA,CAAC"}
@@ -1,3 +1,5 @@
1
+ import { requestBrowserWorkspace } from "../workspace/browser-workspace-desktop.js";
2
+ import { assertBrowserWorkspaceUserScriptAllowed } from "../workspace/browser-workspace-helpers.js";
1
3
  import {
2
4
  closeBrowserWorkspaceTab,
3
5
  evaluateBrowserWorkspaceTab,
@@ -5,6 +7,7 @@ import {
5
7
  getBrowserWorkspaceSnapshot,
6
8
  getBrowserWorkspaceUnavailableMessage,
7
9
  hideBrowserWorkspaceTab,
10
+ isBrowserWorkspaceBridgeConfigured,
8
11
  listBrowserWorkspaceTabs,
9
12
  navigateBrowserWorkspaceTab,
10
13
  openBrowserWorkspaceTab,
@@ -40,6 +43,33 @@ function connectorReferenceFromSearchParams(url) {
40
43
  partition: url?.searchParams.get("partition")
41
44
  };
42
45
  }
46
+ function buildBrowserWorkspaceEventsBridgePath(url) {
47
+ const params = new URLSearchParams();
48
+ for (const key of ["after", "limit", "tabId", "type"]) {
49
+ const value = url?.searchParams.get(key)?.trim();
50
+ if (value) {
51
+ params.set(key, value);
52
+ }
53
+ }
54
+ const query = params.toString();
55
+ return query ? `/events?${query}` : "/events";
56
+ }
57
+ function isBrowserWorkspaceRouteBodyObject(value) {
58
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
59
+ }
60
+ function rejectMalformedBrowserWorkspacePayload(ctx) {
61
+ ctx.json(ctx.res, { error: "request body must be a JSON object" }, 400);
62
+ return true;
63
+ }
64
+ function decodeBrowserWorkspaceTabId(raw) {
65
+ if (typeof raw !== "string") return null;
66
+ try {
67
+ const decoded = decodeURIComponent(raw).trim();
68
+ return decoded ? decoded : null;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
43
73
  async function assertBrowserWorkspaceTabConnectorAccountGate(ctx, tabId, reference, operation) {
44
74
  const tabs = await listBrowserWorkspaceTabs();
45
75
  const tab = tabs.find((entry) => entry.id === tabId) ?? null;
@@ -53,7 +83,7 @@ async function assertBrowserWorkspaceTabConnectorAccountGate(ctx, tabId, referen
53
83
  }
54
84
  async function handleBrowserWorkspaceRoutes(ctx) {
55
85
  const { req, res, method, pathname, readJsonBody, json } = ctx;
56
- if (pathname !== "/api/browser-workspace" && pathname !== "/api/browser-workspace/command" && pathname !== "/api/browser-workspace/tabs" && !pathname.startsWith("/api/browser-workspace/tabs/")) {
86
+ if (pathname !== "/api/browser-workspace" && pathname !== "/api/browser-workspace/command" && pathname !== "/api/browser-workspace/events" && pathname !== "/api/browser-workspace/tabs" && !pathname.startsWith("/api/browser-workspace/tabs/")) {
57
87
  return false;
58
88
  }
59
89
  try {
@@ -61,8 +91,23 @@ async function handleBrowserWorkspaceRoutes(ctx) {
61
91
  json(res, await getBrowserWorkspaceSnapshot());
62
92
  return true;
63
93
  }
94
+ if (pathname === "/api/browser-workspace/events" && method === "GET") {
95
+ if (!isBrowserWorkspaceBridgeConfigured()) {
96
+ throw new Error(getBrowserWorkspaceUnavailableMessage());
97
+ }
98
+ json(
99
+ res,
100
+ await requestBrowserWorkspace(
101
+ buildBrowserWorkspaceEventsBridgePath(ctx.url)
102
+ )
103
+ );
104
+ return true;
105
+ }
64
106
  if (pathname === "/api/browser-workspace/command" && method === "POST") {
65
107
  const body = await readJsonBody(req, res) ?? null;
108
+ if (!isBrowserWorkspaceRouteBodyObject(body)) {
109
+ return rejectMalformedBrowserWorkspacePayload(ctx);
110
+ }
66
111
  if (!body?.subaction) {
67
112
  json(res, { error: "subaction is required" }, 400);
68
113
  return true;
@@ -80,7 +125,10 @@ async function handleBrowserWorkspaceRoutes(ctx) {
80
125
  return true;
81
126
  }
82
127
  if (pathname === "/api/browser-workspace/tabs" && method === "POST") {
83
- const body = await readJsonBody(req, res) ?? {};
128
+ const body = await readJsonBody(req, res) ?? null;
129
+ if (!isBrowserWorkspaceRouteBodyObject(body)) {
130
+ return rejectMalformedBrowserWorkspacePayload(ctx);
131
+ }
84
132
  const connectorGate = await assertBrowserWorkspaceConnectorAccountGate({
85
133
  runtime: ctx.state?.runtime ?? null,
86
134
  connectorProvider: body.connectorProvider,
@@ -102,7 +150,11 @@ async function handleBrowserWorkspaceRoutes(ctx) {
102
150
  if (!match) {
103
151
  return false;
104
152
  }
105
- const tabId = decodeURIComponent(match[1]).trim();
153
+ const tabId = decodeBrowserWorkspaceTabId(match[1]);
154
+ if (!tabId) {
155
+ json(res, { error: "valid tab id is required" }, 400);
156
+ return true;
157
+ }
106
158
  const action = match[2] ?? null;
107
159
  if (!action && method === "DELETE") {
108
160
  await assertBrowserWorkspaceTabConnectorAccountGate(
@@ -151,6 +203,9 @@ async function handleBrowserWorkspaceRoutes(ctx) {
151
203
  }
152
204
  if (action === "navigate" && method === "POST") {
153
205
  const body = await readJsonBody(req, res);
206
+ if (!isBrowserWorkspaceRouteBodyObject(body)) {
207
+ return rejectMalformedBrowserWorkspacePayload(ctx);
208
+ }
154
209
  if (!body?.url?.trim()) {
155
210
  json(res, { error: "url is required" }, 400);
156
211
  return true;
@@ -171,6 +226,9 @@ async function handleBrowserWorkspaceRoutes(ctx) {
171
226
  }
172
227
  if (action === "eval" && method === "POST") {
173
228
  const body = await readJsonBody(req, res);
229
+ if (!isBrowserWorkspaceRouteBodyObject(body)) {
230
+ return rejectMalformedBrowserWorkspacePayload(ctx);
231
+ }
174
232
  if (!body?.script?.trim()) {
175
233
  json(res, { error: "script is required" }, 400);
176
234
  return true;
@@ -181,6 +239,7 @@ async function handleBrowserWorkspaceRoutes(ctx) {
181
239
  body,
182
240
  "evaluate browser workspace tab"
183
241
  );
242
+ assertBrowserWorkspaceUserScriptAllowed(body.script, "eval", "desktop");
184
243
  json(res, {
185
244
  result: await evaluateBrowserWorkspaceTab({
186
245
  id: tabId,
@@ -200,6 +259,7 @@ async function handleBrowserWorkspaceRoutes(ctx) {
200
259
  const BROWSER_WORKSPACE_ROUTE_PATHS = [
201
260
  { type: "GET", path: "/api/browser-workspace" },
202
261
  { type: "POST", path: "/api/browser-workspace/command" },
262
+ { type: "GET", path: "/api/browser-workspace/events" },
203
263
  { type: "GET", path: "/api/browser-workspace/tabs" },
204
264
  { type: "POST", path: "/api/browser-workspace/tabs" },
205
265
  { type: "DELETE", path: "/api/browser-workspace/tabs/:tabId" },