@xopcai/xopc 0.0.264 → 0.0.265

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 (175) hide show
  1. package/dist/browser-ext/dist/assets/auth-CknJ00ae.js +5844 -0
  2. package/dist/browser-ext/dist/assets/sidepanel-C69P3v9u.css +113 -0
  3. package/dist/browser-ext/dist/background.js +200 -68
  4. package/dist/browser-ext/dist/sidepanel.html +14 -0
  5. package/dist/browser-ext/dist/sidepanel.js +18832 -0
  6. package/dist/browser-ext/manifest.json +20 -20
  7. package/dist/extensions/telegram/xopc.extension.json +1 -1
  8. package/dist/gateway/static/root/assets/{activity-store-BYBCpt7_.js → activity-store-Bhyy6ENz.js} +1 -1
  9. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-D20aBHPJ.js → agent-avatar-dicebear-DRTTU2H3.js} +1 -1
  10. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-BZvMqNrU.js → agent-defaults-settings-panel-BsXF7LVi.js} +1 -1
  11. package/dist/gateway/static/root/assets/{agents-6foBELwt.js → agents-DpjWfxYx.js} +1 -1
  12. package/dist/gateway/static/root/assets/{api-YbtjDbuG.js → api-B2y7ZjZk.js} +1 -1
  13. package/dist/gateway/static/root/assets/{app-management-settings-panel-4ps1RrDU.js → app-management-settings-panel-33Mc81h2.js} +1 -1
  14. package/dist/gateway/static/root/assets/{appearance-settings-C7nBHnkG.js → appearance-settings-C8JECSiJ.js} +1 -1
  15. package/dist/gateway/static/root/assets/{apps-page-B_g-rL5b.js → apps-page-t64FcxIZ.js} +1 -1
  16. package/dist/gateway/static/root/assets/{archive-plugin-DfKK1iJO.js → archive-plugin-PzZ6SzfY.js} +1 -1
  17. package/dist/gateway/static/root/assets/{attachment-load-BOU1pdnQ.js → attachment-load-OhLL6K__.js} +1 -1
  18. package/dist/gateway/static/root/assets/{automations-page-BePzexAH.js → automations-page-BpAYwuvx.js} +1 -1
  19. package/dist/gateway/static/root/assets/{automations-workspace-7-m5pBEZ.js → automations-workspace-B6aTT5IC.js} +1 -1
  20. package/dist/gateway/static/root/assets/{binary-plugins-Cly8XDyt.js → binary-plugins-B5XpGsHg.js} +1 -1
  21. package/dist/gateway/static/root/assets/{block-editor-DzSu62js.js → block-editor-BgEhoTnN.js} +1 -1
  22. package/dist/gateway/static/root/assets/{browser-automation-inputs-8bHj7Fv2.js → browser-automation-inputs-s5I78Iko.js} +1 -1
  23. package/dist/gateway/static/root/assets/{browser-automations-page-CzL7Aj2k.js → browser-automations-page-CBLunDRH.js} +1 -1
  24. package/dist/gateway/static/root/assets/{browser-settings-page-Dum3mTiz.js → browser-settings-page-CSfpPCuD.js} +1 -1
  25. package/dist/gateway/static/root/assets/{capabilities-settings-panel-Ci6qVS-O.js → capabilities-settings-panel-DftNnICK.js} +2 -2
  26. package/dist/gateway/static/root/assets/{channels-settings-BOGEZnSv.js → channels-settings-CpuWa5qu.js} +1 -1
  27. package/dist/gateway/static/root/assets/{channels-status-swr-BR8oQ_eP.js → channels-status-swr-_OsWr_vS.js} +1 -1
  28. package/dist/gateway/static/root/assets/{chat-terminal-dock-BF9ZXa5Z.js → chat-terminal-dock-mnPsS_GJ.js} +1 -1
  29. package/dist/gateway/static/root/assets/{connectors-page-DBvEvupa.js → connectors-page-D3nHGnNV.js} +1 -1
  30. package/dist/gateway/static/root/assets/{cron-expression-BszT_gjK.js → cron-expression-NcGBvIfe.js} +1 -1
  31. package/dist/gateway/static/root/assets/{date-picker-D2MKBJ0G.js → date-picker-BrVcl7H5.js} +1 -1
  32. package/dist/gateway/static/root/assets/{dependency-picker-DqigBx-G.js → dependency-picker--edd48Y9.js} +1 -1
  33. package/dist/gateway/static/root/assets/{desktop-pet-settings-DMPk4CaK.js → desktop-pet-settings-DWredJwO.js} +1 -1
  34. package/dist/gateway/static/root/assets/{desktop-pet-CapspBGG.js → desktop-pet-vuyyb6I6.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-debug-page-CXLsDtrW.js → extension-debug-page-CtBW4ebj.js} +1 -1
  36. package/dist/gateway/static/root/assets/{extension-page-B2j87ROw.js → extension-page-CsUrmtW7.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-settings-page-t1BApw-u.js → extension-settings-page-BUd31AuK.js} +1 -1
  38. package/dist/gateway/static/root/assets/{files-BArWTfr7.js → files-Be5viEFY.js} +1 -1
  39. package/dist/gateway/static/root/assets/{gateway-config-swr-BFSesvAC.js → gateway-config-swr-OObCK8jM.js} +1 -1
  40. package/dist/gateway/static/root/assets/{gateway-settings-BFAecoGy.js → gateway-settings-C6FQv7op.js} +1 -1
  41. package/dist/gateway/static/root/assets/{gateway-startup-retry-D1giYgI-.js → gateway-startup-retry-D_TxKN_A.js} +1 -1
  42. package/dist/gateway/static/root/assets/{heartbeat-settings-CyD9zDcU.js → heartbeat-settings-CDo1Gjp2.js} +1 -1
  43. package/dist/gateway/static/root/assets/{home-page-uItZMiBE.js → home-page-D3_uvVJY.js} +1 -1
  44. package/dist/gateway/static/root/assets/{image-generation-api-BeB-OPqO.js → image-generation-api-fPU9yGsw.js} +1 -1
  45. package/dist/gateway/static/root/assets/index-Dpu7EKTm.js +74 -0
  46. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Q_CM-ZCs.js → keyboard-shortcuts-settings-CNVmrCrV.js} +1 -1
  47. package/dist/gateway/static/root/assets/{local-app-workbench-page-DaiBWfOt.js → local-app-workbench-page-B0IVDvs-.js} +1 -1
  48. package/dist/gateway/static/root/assets/{local-apps-page-BALMNCSp.js → local-apps-page-DvkarvLt.js} +1 -1
  49. package/dist/gateway/static/root/assets/{locale-store-RJHYFoq9.js → locale-store-DFQX9kTl.js} +1 -1
  50. package/dist/gateway/static/root/assets/{logs-page-C-TS7oFg.js → logs-page-BknY0vZf.js} +1 -1
  51. package/dist/gateway/static/root/assets/{management-settings-BajkJeYl.js → management-settings-DW6hJINQ.js} +1 -1
  52. package/dist/gateway/static/root/assets/{markdown-view-BxoQJqHx.js → markdown-view-D-5jHRWg.js} +1 -1
  53. package/dist/gateway/static/root/assets/{media-plugins-C_8Hl6KG.js → media-plugins-DYautOWY.js} +1 -1
  54. package/dist/gateway/static/root/assets/{messages-Cf1h5WXB.js → messages-Dj8QuS6e.js} +2 -2
  55. package/dist/gateway/static/root/assets/{mobile-pairing-wizard-CsWIZrZW.js → mobile-pairing-wizard-Dgu03cv-.js} +1 -1
  56. package/dist/gateway/static/root/assets/{note-detail-panel-CCbPg_GP.js → note-detail-panel-Cv9uH6Jq.js} +3 -3
  57. package/dist/gateway/static/root/assets/{notes-page-BdxxcUKa.js → notes-page-DcIAiYJn.js} +1 -1
  58. package/dist/gateway/static/root/assets/{page-tabs-BMYQef5D.js → page-tabs-DF6Q5XvA.js} +1 -1
  59. package/dist/gateway/static/root/assets/{preview-open-alternatives-CKZ-H5hJ.js → preview-open-alternatives-CQJH_jAN.js} +1 -1
  60. package/dist/gateway/static/root/assets/{product-delivery-CV4pDOtG.js → product-delivery-CCC4FJ3Y.js} +1 -1
  61. package/dist/gateway/static/root/assets/{product-open-page-zXJIptXs.js → product-open-page-CIGLPFm7.js} +1 -1
  62. package/dist/gateway/static/root/assets/{project-detail-page-DRhhxhZB.js → project-detail-page-D65JfNSw.js} +1 -1
  63. package/dist/gateway/static/root/assets/{projects-page-Wt7gF3Sx.js → projects-page-BfWF7Sz-.js} +1 -1
  64. package/dist/gateway/static/root/assets/{registry-api-BSfFaxVN.js → registry-api-CsZLatKo.js} +1 -1
  65. package/dist/gateway/static/root/assets/{remote-access-hub-B6de5Zrb.js → remote-access-hub-DA1g4CUD.js} +1 -1
  66. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DcUB5h1f.js → runtime-tools-settings-panel-0-bFSBB8.js} +1 -1
  67. package/dist/gateway/static/root/assets/{schema-form-CquWhe7r.js → schema-form-csGbhTUr.js} +1 -1
  68. package/dist/gateway/static/root/assets/{schemas-BHS7bo06.js → schemas-BaskVM5g.js} +2 -2
  69. package/dist/gateway/static/root/assets/{sessions-page-BNWO2EVF.js → sessions-page-DXPBNhrZ.js} +1 -1
  70. package/dist/gateway/static/root/assets/settings-advanced-gate-DxeZm67O.js +1 -0
  71. package/dist/gateway/static/root/assets/{settings-form-section-DwswAefc.js → settings-form-section-YdrdY8B3.js} +1 -1
  72. package/dist/gateway/static/root/assets/{settings-page-CoAVmjjs.js → settings-page-Cv9waRn3.js} +1 -1
  73. package/dist/gateway/static/root/assets/{setup-status-panel-C9EIcWtH.js → setup-status-panel-DPf-_05Z.js} +1 -1
  74. package/dist/gateway/static/root/assets/{share-preview-page-DWwy_4zh.js → share-preview-page-DBoEHTPn.js} +1 -1
  75. package/dist/gateway/static/root/assets/{shares-settings-CIUvq27-.js → shares-settings-BQP5yNcO.js} +1 -1
  76. package/dist/gateway/static/root/assets/skill-reload-api-B5T8cgjq.js +1 -0
  77. package/dist/gateway/static/root/assets/{skills-page-CqsgVtVh.js → skills-page-D1G8h2Lt.js} +1 -1
  78. package/dist/gateway/static/root/assets/{skills-page.utils-B-N73248.js → skills-page.utils-BNZYTop7.js} +1 -1
  79. package/dist/gateway/static/root/assets/{system-settings-panel-BndjXuBZ.js → system-settings-panel-ByuppbHi.js} +1 -1
  80. package/dist/gateway/static/root/assets/{task-detail-page-CG6Ha9t7.js → task-detail-page-DNmkrF6y.js} +1 -1
  81. package/dist/gateway/static/root/assets/{theme-store-C5ssn64t.js → theme-store-zRT6vaZc.js} +1 -1
  82. package/dist/gateway/static/root/assets/{tunnel-api-BxZdcp3h.js → tunnel-api-CxWGpbwr.js} +1 -1
  83. package/dist/gateway/static/root/assets/{use-autosave-mj9CUuz1.js → use-autosave-B0l4qubU.js} +1 -1
  84. package/dist/gateway/static/root/assets/{user-model-page-DKeHMXI4.js → user-model-page-DMsI6YI7.js} +1 -1
  85. package/dist/gateway/static/root/assets/{voice-api-key-field-CzxVMYlr.js → voice-api-key-field-Bcz7boJg.js} +1 -1
  86. package/dist/gateway/static/root/assets/{voice-config-api-COA0V1cK.js → voice-config-api-DpJEu2RQ.js} +1 -1
  87. package/dist/gateway/static/root/assets/{work-discovery-overlay-DMiPK8dp.js → work-discovery-overlay-Dujq1j_u.js} +1 -1
  88. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-C82OXCJK.js → workflow-run-setup-panel-CxNYbLNB.js} +1 -1
  89. package/dist/gateway/static/root/assets/{workflows-page-HLCUV3rQ.js → workflows-page-Dk1jK2nv.js} +1 -1
  90. package/dist/gateway/static/root/index.html +12 -12
  91. package/dist/package.js +1 -1
  92. package/dist/packages/browser-control-contract/src/index.js +14 -0
  93. package/dist/packages/endpoint-tools-protocol/src/index.js +2 -1
  94. package/dist/packages/gateway-contract/src/browser-page-context.js +43 -0
  95. package/dist/packages/gateway-contract/src/browser-tab-binding.js +29 -0
  96. package/dist/packages/gateway-contract/src/device-pairing.js +18 -2
  97. package/dist/packages/realtime-protocol/src/index.js +1 -0
  98. package/dist/packages/realtime-protocol/src/voice-audio.js +70 -9
  99. package/dist/packages/realtime-protocol/src/voice.js +65 -17
  100. package/dist/src/agent/source-context/browser-page.d.ts +3 -0
  101. package/dist/src/agent/source-context/browser-page.js +39 -0
  102. package/dist/src/agent/source-context/injector.js +9 -2
  103. package/dist/src/agent/source-context/types.d.ts +7 -1
  104. package/dist/src/agent/source-context/types.js +4 -1
  105. package/dist/src/agent/tools/factory.js +9 -1
  106. package/dist/src/browser/automations/schema.d.ts +1 -1
  107. package/dist/src/browser/automations/service.d.ts +1 -1
  108. package/dist/src/browser/native-messaging-host.d.ts +15 -0
  109. package/dist/src/browser/native-messaging-host.js +105 -0
  110. package/dist/src/browser/providers/browser-ext-install.d.ts +17 -1
  111. package/dist/src/browser/providers/browser-ext-install.js +82 -16
  112. package/dist/src/browser/providers/extension.d.ts +4 -0
  113. package/dist/src/browser/providers/extension.js +104 -38
  114. package/dist/src/browser/runtime/browser-runtime.d.ts +2 -1
  115. package/dist/src/browser/runtime/browser-runtime.js +6 -2
  116. package/dist/src/cli/commands/browser.js +13 -3
  117. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  118. package/dist/src/endpoint-tools/runtime.js +2 -0
  119. package/dist/src/gateway/chat-stream/mapper.js +3 -2
  120. package/dist/src/gateway/hono/app.js +19 -3
  121. package/dist/src/gateway/hono/middleware/auth.d.ts +4 -1
  122. package/dist/src/gateway/hono/middleware/auth.js +12 -1
  123. package/dist/src/gateway/hono/routes/browser-install.js +1 -1
  124. package/dist/src/gateway/hono/routes/browser.js +106 -7
  125. package/dist/src/gateway/hono/routes/devices.js +36 -3
  126. package/dist/src/gateway/hono/routes/endpoint-tools.js +15 -0
  127. package/dist/src/gateway/hono/routes/session-input-handler.d.ts +20 -2
  128. package/dist/src/gateway/hono/routes/session-input-handler.js +18 -0
  129. package/dist/src/gateway/hono/routes/sessions.js +12 -6
  130. package/dist/src/gateway/hono/routes/voice.js +5 -4
  131. package/dist/src/gateway/security/gateway-scopes.d.ts +1 -0
  132. package/dist/src/gateway/security/gateway-scopes.js +10 -1
  133. package/dist/src/gateway/security/origin-check.d.ts +1 -0
  134. package/dist/src/gateway/security/origin-check.js +5 -1
  135. package/dist/src/gateway/service/agent-runner.js +2 -1
  136. package/dist/src/gateway/service/session-input-coordinator.d.ts +1 -0
  137. package/dist/src/gateway/service/session-input-coordinator.js +5 -4
  138. package/dist/src/gateway/service.js +12 -11
  139. package/dist/src/realtime/runtime.js +2 -1
  140. package/dist/src/session/client-history.d.ts +4 -1
  141. package/dist/src/session/client-history.js +6 -3
  142. package/dist/src/storage/sqlite/browser-tab-binding-repository.d.ts +7 -0
  143. package/dist/src/storage/sqlite/browser-tab-binding-repository.js +60 -0
  144. package/dist/src/storage/sqlite/device-access-repository.d.ts +3 -1
  145. package/dist/src/storage/sqlite/device-access-repository.js +4 -3
  146. package/dist/src/storage/sqlite/device-pairing-approval.js +6 -4
  147. package/dist/src/storage/sqlite/device-pairing-repository.d.ts +1 -1
  148. package/dist/src/storage/sqlite/index.d.ts +1 -0
  149. package/dist/src/storage/sqlite/index.js +2 -1
  150. package/dist/src/storage/sqlite/migrations/155_browser_extension.sql +120 -0
  151. package/dist/src/storage/sqlite/migrations/156_browser_tab_bindings.sql +14 -0
  152. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  153. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  154. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  155. package/dist/src/storage/sqlite/schema.js +1 -1
  156. package/dist/src/voice/realtime/agentBroker.d.ts +54 -0
  157. package/dist/src/voice/realtime/agentBroker.js +116 -0
  158. package/dist/src/voice/realtime/agentEngine.js +62 -15
  159. package/dist/src/voice/realtime/engine.d.ts +2 -1
  160. package/dist/src/voice/realtime/omniEngine.js +36 -7
  161. package/dist/src/voice/realtime/pcmFrameBuffer.d.ts +6 -0
  162. package/dist/src/voice/realtime/pcmFrameBuffer.js +29 -0
  163. package/dist/src/voice/realtime/runtime.js +97 -21
  164. package/dist/src/voice/realtime/runtime.types.d.ts +5 -21
  165. package/dist/src/voice/realtime/turnPolicy.d.ts +36 -0
  166. package/dist/src/voice/realtime/turnPolicy.js +134 -0
  167. package/package.json +2 -2
  168. package/dist/browser-ext/dist/content.js +0 -191
  169. package/dist/browser-ext/dist/popup.js +0 -32
  170. package/dist/browser-ext/popup.html +0 -37
  171. package/dist/gateway/static/root/assets/index-DQtjs2x3.js +0 -74
  172. package/dist/gateway/static/root/assets/settings-advanced-gate-DK1yTYE4.js +0 -1
  173. package/dist/gateway/static/root/assets/skill-reload-api-DfE3zFVw.js +0 -1
  174. package/dist/src/voice/realtime/conversationTurn.d.ts +0 -20
  175. package/dist/src/voice/realtime/conversationTurn.js +0 -83
@@ -1,7 +1,7 @@
1
1
  import { logAuthEvent } from "../../../utils/logger/audit.js";
2
2
  import { createLogger } from "../../../utils/logger/index.js";
3
3
  import { init_logger } from "../../../utils/logger.js";
4
- import { authenticateDeviceAccessToken } from "../../../storage/sqlite/device-access-repository.js";
4
+ import { authenticateDeviceAccessToken, getDevice } from "../../../storage/sqlite/device-access-repository.js";
5
5
  import { safeEqualSecret } from "../../security/secret-equal.js";
6
6
  import { resolveClientIpFromRequest } from "../../client-ip.js";
7
7
  import { authPolicyConfig, buckets, resolveAuthRateLimit } from "../../rate-limit/buckets.js";
@@ -9,6 +9,7 @@ import { getClientIpFromHeaders } from "../../security/loopback.js";
9
9
  import { resolveAuthTracking } from "../../rate-limit/auth-policy.js";
10
10
  import { isAuthRateLimitGloballyDisabled } from "../../rate-limit/env-flags.js";
11
11
  import "../../rate-limit/index.js";
12
+ import { isChromeExtensionOrigin } from "../../security/origin-check.js";
12
13
  import { authorizeTrustedProxy } from "../../trusted-proxy.js";
13
14
  import { setGatewayPrincipal } from "../../security/gateway-principal.js";
14
15
  import { createMiddleware } from "hono/factory";
@@ -208,7 +209,12 @@ function auth(config) {
208
209
  clientIp
209
210
  }, "Credential in query string rejected: use Authorization header for this endpoint");
210
211
  const providedCredential = authHeader || queryToken;
212
+ const browserExtensionOrigin = isChromeExtensionOrigin(origin) ? origin.toLowerCase() : void 0;
211
213
  if (providedCredential && validateCredential(providedCredential, expectedCredential)) {
214
+ if (browserExtensionOrigin) return c.json({
215
+ error: "Forbidden",
216
+ code: "device_credential_required"
217
+ }, 403);
212
218
  recordSuccess(rl);
213
219
  setGatewayPrincipal(c, {
214
220
  kind: "owner",
@@ -220,6 +226,11 @@ function auth(config) {
220
226
  }
221
227
  const deviceIdentity = providedCredential?.startsWith("xopc_at_") ? authenticateDeviceAccessToken(providedCredential) : void 0;
222
228
  if (deviceIdentity) {
229
+ const device = getDevice(deviceIdentity.deviceId);
230
+ if (browserExtensionOrigin && (device?.platform !== "chrome" || `chrome-extension://${device.extensionId}` !== browserExtensionOrigin)) return c.json({
231
+ error: "Forbidden",
232
+ code: "extension_origin_mismatch"
233
+ }, 403);
223
234
  recordSuccess(rl);
224
235
  setGatewayPrincipal(c, {
225
236
  kind: "device",
@@ -1,5 +1,5 @@
1
- import { createGatewayRouteLogger } from "../lib/route-logger.js";
2
1
  import { acquireBrowserInstallLock, cancelBrowserInstall } from "../../../browser/install-lock.js";
2
+ import { createGatewayRouteLogger } from "../lib/route-logger.js";
3
3
  import { runPlaywrightChromiumInstallWithProgress } from "../../../browser/providers/playwright-install.js";
4
4
  import { streamSSE } from "hono/streaming";
5
5
  //#region src/gateway/hono/routes/browser-install.ts
@@ -1,7 +1,11 @@
1
+ import { deleteBrowserTabBinding, getBrowserTabBinding, setBrowserTabBinding } from "../../../storage/sqlite/browser-tab-binding-repository.js";
2
+ import { browserTabBindingRequestSchema } from "../../../../packages/gateway-contract/src/browser-tab-binding.js";
3
+ import { getDevice } from "../../../storage/sqlite/device-access-repository.js";
1
4
  import { decideBrowserApproval, listBrowserApprovals } from "../../../browser/policy/approval-store.js";
2
5
  import { createBrowserDriver } from "../../../browser/drivers/create-driver.js";
3
6
  import { checkBrowserReadiness } from "../../../browser/readiness.js";
4
7
  import { isLoopbackClientIp } from "../../security/loopback.js";
8
+ import { getGatewayPrincipal } from "../../security/gateway-principal.js";
5
9
  import { getConnInfo } from "@hono/node-server/conninfo";
6
10
  //#region src/gateway/hono/routes/browser.ts
7
11
  /** Browser Control status, approvals, and driver diagnostics. */
@@ -48,6 +52,8 @@ async function extensionStatus() {
48
52
  };
49
53
  }
50
54
  function isLocalOwnerRequest(c, service) {
55
+ const principal = getGatewayPrincipal(c);
56
+ if (principal.kind !== "owner" && principal.kind !== "trusted-proxy") return false;
51
57
  if (service.currentConfig.gateway?.bind === "loopback") return true;
52
58
  try {
53
59
  return isLoopbackClientIp(getConnInfo(c).remote.address);
@@ -55,8 +61,99 @@ function isLocalOwnerRequest(c, service) {
55
61
  return false;
56
62
  }
57
63
  }
64
+ function chromeDevicePrincipal(c) {
65
+ const principal = getGatewayPrincipal(c);
66
+ if (principal.kind !== "device" || !principal.deviceId) return void 0;
67
+ const device = getDevice(principal.deviceId);
68
+ return device?.platform === "chrome" && device.revokedAt === void 0 ? principal : void 0;
69
+ }
70
+ function canManageBrowserSession(c, service, sessionKey) {
71
+ const principal = chromeDevicePrincipal(c);
72
+ if (principal) return getBrowserTabBinding(sessionKey)?.principalId === principal.deviceId;
73
+ return isLocalOwnerRequest(c, service);
74
+ }
58
75
  function registerBrowserRoutes(authenticated, deps) {
59
76
  const { service, strictRateLimitMiddleware } = deps;
77
+ authenticated.get("/api/browser/tab-bindings/:sessionKey", (c) => {
78
+ const sessionKey = c.req.param("sessionKey").trim();
79
+ const binding = getBrowserTabBinding(sessionKey);
80
+ if (!binding) return c.json({
81
+ ok: false,
82
+ error: {
83
+ code: "NOT_FOUND",
84
+ message: "Tab binding not found"
85
+ }
86
+ }, 404);
87
+ if (!canManageBrowserSession(c, service, sessionKey)) return c.json({
88
+ ok: false,
89
+ error: {
90
+ code: "FORBIDDEN",
91
+ message: "Tab binding belongs to another device"
92
+ }
93
+ }, 403);
94
+ return c.json({
95
+ ok: true,
96
+ payload: binding
97
+ });
98
+ });
99
+ authenticated.put("/api/browser/tab-bindings/:sessionKey", async (c) => {
100
+ const principal = chromeDevicePrincipal(c);
101
+ if (!principal?.deviceId) return c.json({
102
+ ok: false,
103
+ error: {
104
+ code: "FORBIDDEN",
105
+ message: "Chrome device access required"
106
+ }
107
+ }, 403);
108
+ const parsed = browserTabBindingRequestSchema.safeParse(await c.req.json().catch(() => null));
109
+ if (!parsed.success) return c.json({
110
+ ok: false,
111
+ error: {
112
+ code: "BAD_REQUEST",
113
+ message: "Invalid tab binding"
114
+ }
115
+ }, 400);
116
+ const endpoint = service.endpointTools.registry.get(parsed.data.endpointId);
117
+ if (!service.endpointTools.registry.verifyTurnClaim(parsed.data.endpointId, parsed.data.turnToken) || endpoint?.kind !== "browser" || endpoint.principalId !== principal.deviceId) return c.json({
118
+ ok: false,
119
+ error: {
120
+ code: "INVALID_ENDPOINT",
121
+ message: "Browser endpoint is not active"
122
+ }
123
+ }, 401);
124
+ const sessionKey = c.req.param("sessionKey").trim();
125
+ if (!sessionKey || !await service.sessions.getSession(sessionKey)) return c.json({
126
+ ok: false,
127
+ error: {
128
+ code: "NOT_FOUND",
129
+ message: "Session not found"
130
+ }
131
+ }, 404);
132
+ const { turnToken: _turnToken, ...bindingInput } = parsed.data;
133
+ const binding = setBrowserTabBinding({
134
+ ...bindingInput,
135
+ sessionKey,
136
+ principalId: principal.deviceId
137
+ });
138
+ return c.json({
139
+ ok: true,
140
+ payload: binding
141
+ });
142
+ });
143
+ authenticated.delete("/api/browser/tab-bindings/:sessionKey", (c) => {
144
+ const sessionKey = c.req.param("sessionKey").trim();
145
+ if (!canManageBrowserSession(c, service, sessionKey)) return c.json({
146
+ ok: false,
147
+ error: {
148
+ code: "FORBIDDEN",
149
+ message: "Tab binding belongs to another device"
150
+ }
151
+ }, 403);
152
+ return c.json({
153
+ ok: true,
154
+ payload: { removed: deleteBrowserTabBinding(sessionKey) }
155
+ });
156
+ });
60
157
  authenticated.get("/api/browser/extension-status", async (c) => {
61
158
  try {
62
159
  return c.json(await extensionStatus());
@@ -169,33 +266,35 @@ function registerBrowserRoutes(authenticated, deps) {
169
266
  }
170
267
  });
171
268
  authenticated.get("/api/browser/approvals", (c) => {
172
- if (!isLocalOwnerRequest(c, service)) return c.json({
269
+ const sessionKey = c.req.query("sessionKey");
270
+ if (!isLocalOwnerRequest(c, service) && (!sessionKey || !canManageBrowserSession(c, service, sessionKey))) return c.json({
173
271
  ok: false,
174
272
  error: "Local owner access required."
175
273
  }, 403);
176
274
  return c.json({
177
275
  ok: true,
178
- approvals: listBrowserApprovals(c.req.query("sessionKey"))
276
+ approvals: listBrowserApprovals(sessionKey)
179
277
  });
180
278
  });
181
279
  authenticated.post("/api/browser/approvals/respond", strictRateLimitMiddleware, async (c) => {
182
- if (!isLocalOwnerRequest(c, service)) return c.json({
280
+ const body = await c.req.json().catch(() => null);
281
+ const approval = typeof body?.id === "string" ? listBrowserApprovals().find((candidate) => candidate.id === body.id) : void 0;
282
+ if (!isLocalOwnerRequest(c, service) && (!approval || !canManageBrowserSession(c, service, approval.sessionKey))) return c.json({
183
283
  ok: false,
184
284
  error: "Local owner access required."
185
285
  }, 403);
186
- const body = await c.req.json().catch(() => null);
187
286
  if (typeof body?.id !== "string" || body.decision !== "approved" && body.decision !== "denied") return c.json({
188
287
  ok: false,
189
288
  error: "id and decision are required."
190
289
  }, 400);
191
- const approval = decideBrowserApproval(body.id, body.decision);
192
- if (!approval) return c.json({
290
+ const decided = decideBrowserApproval(body.id, body.decision);
291
+ if (!decided) return c.json({
193
292
  ok: false,
194
293
  error: "Approval not found."
195
294
  }, 404);
196
295
  return c.json({
197
296
  ok: true,
198
- approval
297
+ approval: decided
199
298
  });
200
299
  });
201
300
  authenticated.get("/api/browser/playwright/doctor", async (c) => {
@@ -1,5 +1,5 @@
1
1
  import { runSqliteWriteTransaction } from "../../../storage/sqlite/transaction.js";
2
- import { DEFAULT_MOBILE_SCOPES } from "../../security/gateway-scopes.js";
2
+ import { DEFAULT_BROWSER_EXTENSION_SCOPES, DEFAULT_MOBILE_SCOPES } from "../../security/gateway-scopes.js";
3
3
  import { createDevice, issueDeviceTokenPair, listDevices, revokeDevice, rotateDeviceRefreshToken } from "../../../storage/sqlite/device-access-repository.js";
4
4
  import { getGatewayIdentityPublicKeyRaw, getOrCreateGatewayIdentity, signGatewayPayload } from "../../../storage/sqlite/gateway-identity-repository.js";
5
5
  import { consumeDevicePairingToken, createDevicePairingSetup, isDevicePairingSetupActive } from "../../../storage/sqlite/device-pairing-repository.js";
@@ -21,7 +21,12 @@ const pairingExchangeSchema = z.strictObject({
21
21
  pairingToken: z.string().min(1).max(256),
22
22
  device: z.strictObject({
23
23
  displayName: z.string().trim().min(1).max(80),
24
- platform: z.enum(["ios", "android"]),
24
+ platform: z.enum([
25
+ "ios",
26
+ "android",
27
+ "chrome"
28
+ ]),
29
+ extensionId: z.string().regex(/^[a-p]{32}$/).optional(),
25
30
  publicKeyJwk: z.strictObject({
26
31
  kty: z.literal("EC"),
27
32
  crv: z.literal("P-256"),
@@ -29,6 +34,17 @@ const pairingExchangeSchema = z.strictObject({
29
34
  y: z.string().min(40).max(64)
30
35
  })
31
36
  })
37
+ }).superRefine((value, context) => {
38
+ if (value.device.platform === "chrome" && !value.device.extensionId) context.addIssue({
39
+ code: "custom",
40
+ path: ["device", "extensionId"],
41
+ message: "Chrome devices require an extension id"
42
+ });
43
+ if (value.device.platform !== "chrome" && value.device.extensionId) context.addIssue({
44
+ code: "custom",
45
+ path: ["device", "extensionId"],
46
+ message: "Only Chrome devices may set an extension id"
47
+ });
32
48
  });
33
49
  const pairingProbeSchema = z.strictObject({ pairingId: z.string().uuid() });
34
50
  function pairingLinkPayload(input) {
@@ -100,7 +116,8 @@ function registerDeviceAuthPublicRoutes(app) {
100
116
  displayName: parsed.data.device.displayName,
101
117
  platform: parsed.data.device.platform,
102
118
  publicKeyJwk: parsed.data.device.publicKeyJwk,
103
- scopes: DEFAULT_MOBILE_SCOPES
119
+ extensionId: parsed.data.device.extensionId,
120
+ scopes: parsed.data.device.platform === "chrome" ? DEFAULT_BROWSER_EXTENSION_SCOPES : DEFAULT_MOBILE_SCOPES
104
121
  });
105
122
  return {
106
123
  success: true,
@@ -207,6 +224,22 @@ function registerDeviceRoutes(authenticated, deps) {
207
224
  }
208
225
  }, 404);
209
226
  });
227
+ authenticated.delete("/api/devices/me", (c) => {
228
+ const principal = getGatewayPrincipal(c);
229
+ if (principal.kind !== "device" || !principal.deviceId) return c.json({
230
+ ok: false,
231
+ error: {
232
+ code: "DEVICE_REQUIRED",
233
+ message: "Device access required"
234
+ }
235
+ }, 403);
236
+ const revoked = revokeDevice(principal.deviceId);
237
+ if (revoked) deps.service.realtime.disconnectPrincipal(principal.deviceId);
238
+ return c.json({
239
+ ok: true,
240
+ revoked
241
+ });
242
+ });
210
243
  authenticated.delete("/api/devices/:deviceId", (c) => {
211
244
  const deviceId = c.req.param("deviceId");
212
245
  const revoked = revokeDevice(deviceId);
@@ -1,9 +1,11 @@
1
+ import { getDevice } from "../../../storage/sqlite/device-access-repository.js";
1
2
  import { endpointPrincipalRegistrationSchema, endpointSessionBindingRequestSchema } from "../../../../packages/endpoint-tools-protocol/src/index.js";
2
3
  import { createEndpointPrincipal, getEndpointPrincipal, listEndpointPrincipals, revokeEndpointPrincipal } from "../../../storage/sqlite/endpoint-principal-repository.js";
3
4
  import { listEndpointToolInvocationAudits } from "../../../storage/sqlite/endpoint-tool-invocation-repository.js";
4
5
  import "../../../storage/sqlite/index.js";
5
6
  import { parseEndpointPublicKey } from "../../../endpoint-tools/auth.js";
6
7
  import { ENDPOINT_UPLOAD_MAX_BYTES, EndpointUploadError } from "../../../endpoint-tools/upload-service.js";
8
+ import { getGatewayPrincipal } from "../../security/gateway-principal.js";
7
9
  //#region src/gateway/hono/routes/endpoint-tools.ts
8
10
  async function readBoundedBody(body, maxBytes) {
9
11
  if (!body) return new Uint8Array();
@@ -54,6 +56,19 @@ function registerEndpointToolRoutes(authenticated, deps) {
54
56
  message: "Invalid endpoint principal"
55
57
  }
56
58
  }, 400);
59
+ const gatewayPrincipal = getGatewayPrincipal(c);
60
+ if (gatewayPrincipal.kind === "device") {
61
+ const device = gatewayPrincipal.deviceId ? getDevice(gatewayPrincipal.deviceId) : void 0;
62
+ const ownsPrincipal = parsed.data.principalId === gatewayPrincipal.deviceId;
63
+ const compatibleKind = device?.platform === "chrome" ? parsed.data.kind === "browser" && parsed.data.platform === "chrome" : parsed.data.kind === "mobile";
64
+ if (!ownsPrincipal || !compatibleKind) return c.json({
65
+ ok: false,
66
+ error: {
67
+ code: "FORBIDDEN",
68
+ message: "Endpoint identity does not match this device"
69
+ }
70
+ }, 403);
71
+ }
57
72
  try {
58
73
  parseEndpointPublicKey(parsed.data.publicKey);
59
74
  } catch {
@@ -18,6 +18,12 @@ export declare function submitSessionInput(c: Context, deps: AuthenticatedRouteD
18
18
  code: string;
19
19
  message: string;
20
20
  };
21
+ }, 403, "json">) | (Response & import("hono").TypedResponse<{
22
+ ok: false;
23
+ error: {
24
+ code: string;
25
+ message: string;
26
+ };
21
27
  }, 409, "json">) | (Response & import("hono").TypedResponse<{
22
28
  ok: false;
23
29
  error: {
@@ -63,9 +69,12 @@ export declare function submitSessionInput(c: Context, deps: AuthenticatedRouteD
63
69
  title: string;
64
70
  tokenEstimate?: number;
65
71
  truncated?: boolean;
72
+ url?: string;
73
+ capturedAt?: number;
74
+ documentId?: string;
66
75
  }[];
67
76
  contextSnapshots?: {
68
- kind: import("../../../agent/source-context/types.js").SessionSourceBinding["kind"];
77
+ kind: import("../../../agent/source-context/types.js").SessionSourceBinding["kind"] | "browser_page";
69
78
  sourceId: string;
70
79
  version: string;
71
80
  title: string;
@@ -77,6 +86,9 @@ export declare function submitSessionInput(c: Context, deps: AuthenticatedRouteD
77
86
  }[];
78
87
  tokenEstimate?: number;
79
88
  truncated?: boolean;
89
+ url?: string;
90
+ capturedAt?: number;
91
+ documentId?: string;
80
92
  }[];
81
93
  thinking?: string;
82
94
  origin: {
@@ -163,9 +175,12 @@ export declare function replaceLatestSessionTurn(c: Context, deps: Authenticated
163
175
  title: string;
164
176
  tokenEstimate?: number;
165
177
  truncated?: boolean;
178
+ url?: string;
179
+ capturedAt?: number;
180
+ documentId?: string;
166
181
  }[];
167
182
  contextSnapshots?: {
168
- kind: import("../../../agent/source-context/types.js").SessionSourceBinding["kind"];
183
+ kind: import("../../../agent/source-context/types.js").SessionSourceBinding["kind"] | "browser_page";
169
184
  sourceId: string;
170
185
  version: string;
171
186
  title: string;
@@ -177,6 +192,9 @@ export declare function replaceLatestSessionTurn(c: Context, deps: Authenticated
177
192
  }[];
178
193
  tokenEstimate?: number;
179
194
  truncated?: boolean;
195
+ url?: string;
196
+ capturedAt?: number;
197
+ documentId?: string;
180
198
  }[];
181
199
  thinking?: string;
182
200
  origin: {
@@ -1,9 +1,11 @@
1
+ import { browserPageContextsInputSchema } from "../../../../packages/gateway-contract/src/browser-page-context.js";
1
2
  import { endpointTurnClaimSchema } from "../../../../packages/endpoint-tools-protocol/src/index.js";
2
3
  import { init_providers, resolveModel } from "../../../providers/index.js";
3
4
  import { parseTurnContextRefs } from "../../../agent/source-context/types.js";
4
5
  import { getModelThinking } from "../../../providers/model-thinking.js";
5
6
  import { validateWebchatAttachments, validateWebchatContent } from "../../chat-limits.js";
6
7
  import { withModelConfigLock } from "../../../session/model-config-lock.js";
8
+ import { browserPageContextToAgentContext } from "../../../agent/source-context/browser-page.js";
7
9
  //#region src/gateway/hono/routes/session-input-handler.ts
8
10
  init_providers();
9
11
  const MAX_TURN_CONTEXTS = 5;
@@ -25,6 +27,14 @@ async function submitSessionInput(c, deps, sessionKey) {
25
27
  message: `contextRefs must contain at most ${MAX_TURN_CONTEXTS} valid notes`
26
28
  }
27
29
  }, 400);
30
+ const browserContexts = browserPageContextsInputSchema.safeParse(body.browserContexts ?? []);
31
+ if (!browserContexts.success) return c.json({
32
+ ok: false,
33
+ error: {
34
+ code: "BAD_REQUEST",
35
+ message: "Invalid browser page context"
36
+ }
37
+ }, 400);
28
38
  const content = typeof body.content === "string" ? body.content : "";
29
39
  const contentError = validateWebchatContent(content);
30
40
  if (contentError) return c.json({
@@ -65,6 +75,13 @@ async function submitSessionInput(c, deps, sessionKey) {
65
75
  message: "Endpoint connection is not active"
66
76
  }
67
77
  }, 401);
78
+ if (browserContexts.data.length && deps.service.endpointTools.registry.get(origin.data.endpointId)?.kind !== "browser") return c.json({
79
+ ok: false,
80
+ error: {
81
+ code: "FORBIDDEN",
82
+ message: "Browser context requires a browser endpoint"
83
+ }
84
+ }, 403);
68
85
  if (body.expectedSessionId !== void 0 && (typeof body.expectedSessionId !== "string" || !body.expectedSessionId || body.expectedSessionId.length > 128)) return c.json({
69
86
  ok: false,
70
87
  error: {
@@ -115,6 +132,7 @@ async function submitSessionInput(c, deps, sessionKey) {
115
132
  content,
116
133
  attachments,
117
134
  contextRefs,
135
+ sourceContexts: browserContexts.data.map(browserPageContextToAgentContext),
118
136
  thinking: selection?.thinkingLevel ?? (typeof body.thinking === "string" ? body.thinking : void 0),
119
137
  origin: {
120
138
  type: "endpoint",
@@ -1,3 +1,4 @@
1
+ import { deleteBrowserTabBinding } from "../../../storage/sqlite/browser-tab-binding-repository.js";
1
2
  import { buildSessionKey, init_session_key } from "../../../routing/session-key.js";
2
3
  import { resolveProjectAgentId } from "../../../projects/project-agent.js";
3
4
  import "../../../projects/index.js";
@@ -245,6 +246,7 @@ function registerSessionsRoutes(authenticated, deps) {
245
246
  }, 400);
246
247
  }
247
248
  }
249
+ if (body.createdSurface === "browser_extension") await service.sessions.patch(sessionKey, { customData: { createdSurface: "browser_extension" } });
248
250
  const session = await service.sessions.getSession(sessionKey);
249
251
  const agentConfig = channel === "webchat" ? await service.sessions.getFixedAgentConfig(sessionKey) : void 0;
250
252
  return c.json({
@@ -657,6 +659,7 @@ function registerSessionsRoutes(authenticated, deps) {
657
659
  ok: false,
658
660
  error: result.error
659
661
  }, 404);
662
+ deleteBrowserTabBinding(key);
660
663
  if (requestedProjectId !== void 0) try {
661
664
  if (requestedProjectId) service.projects.attachSession(key, requestedProjectId);
662
665
  else service.projects.detachSession(key);
@@ -832,12 +835,15 @@ function registerSessionsRoutes(authenticated, deps) {
832
835
  error: "Stop the active session run before deleting its environment"
833
836
  }, 409);
834
837
  const result = await service.sessions.delete(key);
835
- if (result.deleted) await environments.release(key).catch((error) => {
836
- log.warn({
837
- err: error,
838
- sessionKey: key
839
- }, "Session deleted but execution environment cleanup failed");
840
- });
838
+ if (result.deleted) {
839
+ deleteBrowserTabBinding(key);
840
+ await environments.release(key).catch((error) => {
841
+ log.warn({
842
+ err: error,
843
+ sessionKey: key
844
+ }, "Session deleted but execution environment cleanup failed");
845
+ });
846
+ }
841
847
  return c.json(result);
842
848
  });
843
849
  authenticated.post("/api/sessions/:key/archive", async (c) => {
@@ -133,12 +133,13 @@ function registerVoiceRoutes(authenticated, deps) {
133
133
  enabled,
134
134
  capabilities: {
135
135
  dictation: availability(Boolean(stt)),
136
- agent: availability(Boolean(stt && tts)),
137
- omni: availability(Boolean(omni)),
136
+ assistant: availability(Boolean(stt && tts)),
137
+ natural: availability(Boolean(omni)),
138
138
  languages: ["zh", "en"],
139
- bargeIn: config.voice?.realtime?.bargeIn ?? true
139
+ bargeIn: config.voice?.realtime?.bargeIn ?? true,
140
+ mediaTransports: ["websocket-pcm"]
140
141
  },
141
- defaultEngine: config.voice?.realtime?.defaultEngine ?? "agent",
142
+ defaultMode: config.voice?.realtime?.defaultEngine === "omni" ? "natural" : "assistant",
142
143
  omni: omni?.route ?? null,
143
144
  stt: stt?.route ?? null,
144
145
  tts: tts ? {
@@ -1,6 +1,7 @@
1
1
  export declare const GATEWAY_SCOPES: readonly ["gateway.status", "agents.read", "agents.run", "sessions.read", "sessions.write", "workspace.read", "workspace.write", "tasks.read", "tasks.write", "automations.read", "automations.write", "notifications.self", "device.self", "gateway.admin"];
2
2
  export type GatewayScope = typeof GATEWAY_SCOPES[number];
3
3
  export declare const DEFAULT_MOBILE_SCOPES: readonly GatewayScope[];
4
+ export declare const DEFAULT_BROWSER_EXTENSION_SCOPES: readonly GatewayScope[];
4
5
  export declare function isGatewayScope(value: unknown): value is GatewayScope;
5
6
  export declare function parseGatewayScopes(value: string): GatewayScope[];
6
7
  export declare function requiredGatewayScope(method: string, path: string): GatewayScope;
@@ -31,6 +31,14 @@ const DEFAULT_MOBILE_SCOPES = [
31
31
  "notifications.self",
32
32
  "device.self"
33
33
  ];
34
+ const DEFAULT_BROWSER_EXTENSION_SCOPES = [
35
+ "gateway.status",
36
+ "agents.read",
37
+ "agents.run",
38
+ "sessions.read",
39
+ "sessions.write",
40
+ "device.self"
41
+ ];
34
42
  function isGatewayScope(value) {
35
43
  return typeof value === "string" && KNOWN_GATEWAY_SCOPES.has(value);
36
44
  }
@@ -48,6 +56,7 @@ function requiredGatewayScope(method, path) {
48
56
  if (path === "/api/device-auth/refresh" || path === "/api/devices/me") return "device.self";
49
57
  if (path.startsWith("/api/devices/me/push")) return "notifications.self";
50
58
  if (path.startsWith("/api/endpoint-tools")) return "device.self";
59
+ if (path.startsWith("/api/browser/tab-bindings") || path.startsWith("/api/browser/approvals")) return methodScope(method, "sessions.read", "sessions.write");
51
60
  if (method === "GET" && path === "/api/connectors/approvals") return "sessions.read";
52
61
  if (method === "POST" && path === "/api/connectors/approvals/respond") return "sessions.write";
53
62
  if (path === "/api/agent" || path.startsWith("/api/agent/")) return "agents.run";
@@ -66,4 +75,4 @@ function hasGatewayScope(granted, required) {
66
75
  return granted.includes("gateway.admin") || granted.includes(required);
67
76
  }
68
77
  //#endregion
69
- export { DEFAULT_MOBILE_SCOPES, GATEWAY_SCOPES, hasGatewayScope, isGatewayScope, parseGatewayScopes, requiredGatewayScope };
78
+ export { DEFAULT_BROWSER_EXTENSION_SCOPES, DEFAULT_MOBILE_SCOPES, GATEWAY_SCOPES, hasGatewayScope, isGatewayScope, parseGatewayScopes, requiredGatewayScope };
@@ -11,6 +11,7 @@ type OriginCheckResult = {
11
11
  ok: false;
12
12
  reason: string;
13
13
  };
14
+ export declare function isChromeExtensionOrigin(origin: string | undefined): boolean;
14
15
  export declare function checkBrowserOrigin(params: {
15
16
  requestHost?: string;
16
17
  origin?: string;
@@ -5,6 +5,10 @@ const LOOPBACK_HOSTNAMES = new Set([
5
5
  "::1",
6
6
  "[::1]"
7
7
  ]);
8
+ const CHROME_EXTENSION_ORIGIN = /^chrome-extension:\/\/[a-p]{32}$/;
9
+ function isChromeExtensionOrigin(origin) {
10
+ return CHROME_EXTENSION_ORIGIN.test((origin ?? "").trim().toLowerCase());
11
+ }
8
12
  function isLoopbackHost(hostname) {
9
13
  return LOOPBACK_HOSTNAMES.has(hostname.toLowerCase());
10
14
  }
@@ -55,4 +59,4 @@ function checkBrowserOrigin(params) {
55
59
  };
56
60
  }
57
61
  //#endregion
58
- export { checkBrowserOrigin };
62
+ export { checkBrowserOrigin, isChromeExtensionOrigin };
@@ -50,7 +50,8 @@ var GatewayAgentRunner = class {
50
50
  const generator = this.runAgent(input.content, "webchat", input.sessionKey, input.origin, input.attachments, input.thinking, {
51
51
  runId: input.runId,
52
52
  taskRunId: input.taskRunId,
53
- sourceContexts: input.sourceContexts
53
+ sourceContexts: input.sourceContexts,
54
+ ...input.origin.type === "channel" && input.origin.channel === "voice" ? { presentation: "voice" } : {}
54
55
  });
55
56
  let result;
56
57
  while (true) {
@@ -11,6 +11,7 @@ export type SubmitSessionInput = {
11
11
  content: string;
12
12
  attachments?: UserTurnAttachment[];
13
13
  contextRefs?: TurnContextRef[];
14
+ sourceContexts?: AgentSourceContext[];
14
15
  thinking?: string;
15
16
  origin: TurnOrigin;
16
17
  };
@@ -7,6 +7,7 @@ import { cancelConnectionObjective, consumeConnectionResume, invalidateConnectio
7
7
  import "../../storage/sqlite/index.js";
8
8
  import { summarizeSourceContext } from "../../agent/source-context/types.js";
9
9
  import { deleteMediaUrisNoLongerReferenced } from "../../media/session-references.js";
10
+ import { fitSourceContextsToBudget } from "../../agent/source-context/budget.js";
10
11
  import crypto from "node:crypto";
11
12
  //#region src/gateway/service/session-input-coordinator.ts
12
13
  init_logger();
@@ -70,7 +71,7 @@ var SessionInputCoordinator = class {
70
71
  const clientMessageId = input.clientMessageId.trim();
71
72
  const content = input.content.trim();
72
73
  const targetTurnId = input.targetTurnId.trim();
73
- if (!sessionKey || !clientMessageId || !targetTurnId || !content && !input.attachments?.length) return {
74
+ if (!sessionKey || !clientMessageId || !targetTurnId || !content && !input.attachments?.length && !input.sourceContexts?.length) return {
74
75
  ok: false,
75
76
  code: "BAD_REQUEST"
76
77
  };
@@ -89,7 +90,7 @@ var SessionInputCoordinator = class {
89
90
  const attachments = await this.deps.prepareAttachments(sessionKey, input.attachments);
90
91
  let sourceContexts;
91
92
  try {
92
- sourceContexts = await this.deps.prepareContexts(input.contextRefs);
93
+ sourceContexts = fitSourceContextsToBudget([...await this.deps.prepareContexts(input.contextRefs) ?? [], ...input.sourceContexts ?? []]);
93
94
  } catch (err) {
94
95
  log.warn({
95
96
  err,
@@ -137,7 +138,7 @@ var SessionInputCoordinator = class {
137
138
  const sessionKey = input.sessionKey;
138
139
  const clientMessageId = input.clientMessageId.trim();
139
140
  const content = input.content.trim();
140
- if (!sessionKey || !clientMessageId || !content && !input.attachments?.length) return {
141
+ if (!sessionKey || !clientMessageId || !content && !input.attachments?.length && !input.sourceContexts?.length) return {
141
142
  ok: false,
142
143
  code: "BAD_REQUEST"
143
144
  };
@@ -158,7 +159,7 @@ var SessionInputCoordinator = class {
158
159
  const attachments = await this.deps.prepareAttachments(sessionKey, input.attachments);
159
160
  let sourceContexts;
160
161
  try {
161
- sourceContexts = await this.deps.prepareContexts(input.contextRefs);
162
+ sourceContexts = fitSourceContextsToBudget([...await this.deps.prepareContexts(input.contextRefs) ?? [], ...input.sourceContexts ?? []]);
162
163
  } catch (err) {
163
164
  log.warn({
164
165
  err,