@xopcai/xopc 0.0.266 → 0.0.268

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 (151) hide show
  1. package/dist/browser-ext/dist/assets/{auth-CknJ00ae.js → auth-cwEj8rFb.js} +735 -224
  2. package/dist/browser-ext/dist/assets/sidepanel-C86O7q5Y.css +299 -0
  3. package/dist/browser-ext/dist/background.js +699 -170
  4. package/dist/browser-ext/dist/sidepanel.html +3 -3
  5. package/dist/browser-ext/dist/sidepanel.js +9608 -777
  6. package/dist/browser-ext/manifest.json +3 -6
  7. package/dist/extensions/telegram/xopc.extension.json +1 -1
  8. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Qrp5ALCF.js → agent-avatar-dicebear-B9WObxjr.js} +1 -1
  9. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-DwinST48.js → agent-defaults-settings-panel-J1YFNG26.js} +1 -1
  10. package/dist/gateway/static/root/assets/{agents-Dlr0YyNt.js → agents-BJuTIRPD.js} +1 -1
  11. package/dist/gateway/static/root/assets/{app-management-settings-panel-bLNvz93c.js → app-management-settings-panel-Bbn9mDfL.js} +1 -1
  12. package/dist/gateway/static/root/assets/{appearance-settings-D5qgIepF.js → appearance-settings-Deo5EsIc.js} +1 -1
  13. package/dist/gateway/static/root/assets/{apps-page-BQVlXhIb.js → apps-page-Cz4d7ErX.js} +1 -1
  14. package/dist/gateway/static/root/assets/{archive-plugin-BEA7VAA4.js → archive-plugin-CxJTU8MU.js} +1 -1
  15. package/dist/gateway/static/root/assets/{attachment-load-B1RyF3uu.js → attachment-load-C71Cukgp.js} +1 -1
  16. package/dist/gateway/static/root/assets/{automations-page-BfH0FdKa.js → automations-page-_J7UZ55Q.js} +1 -1
  17. package/dist/gateway/static/root/assets/{automations-workspace-C37AlD3E.js → automations-workspace-Bn8nwUOL.js} +1 -1
  18. package/dist/gateway/static/root/assets/{binary-plugins-EU884ZXi.js → binary-plugins-CX8fC8Zt.js} +1 -1
  19. package/dist/gateway/static/root/assets/{block-editor-Bjr3naHb.js → block-editor-Ci9fjoQL.js} +1 -1
  20. package/dist/gateway/static/root/assets/{browser-automations-page-CDrYrNGy.js → browser-automations-page-rDSx0ozx.js} +1 -1
  21. package/dist/gateway/static/root/assets/browser-settings-page-B9RA6x4x.js +8 -0
  22. package/dist/gateway/static/root/assets/{capabilities-settings-panel-slt1o04n.js → capabilities-settings-panel-C1GUih8m.js} +1 -1
  23. package/dist/gateway/static/root/assets/{channels-settings-oHTTX6Jy.js → channels-settings-CEhVLJNa.js} +1 -1
  24. package/dist/gateway/static/root/assets/{chat-terminal-dock-DEYQdv6n.js → chat-terminal-dock-DMSl8KxS.js} +1 -1
  25. package/dist/gateway/static/root/assets/{connectors-page-olrbxHIT.js → connectors-page-IQAQDSmO.js} +1 -1
  26. package/dist/gateway/static/root/assets/{cron-expression-DFrbDWpP.js → cron-expression-BByq6Uh8.js} +1 -1
  27. package/dist/gateway/static/root/assets/{date-picker-CYriYKpH.js → date-picker-m6JZCUEW.js} +1 -1
  28. package/dist/gateway/static/root/assets/{dependency-picker-XY3qUErs.js → dependency-picker-CwCAjbaJ.js} +1 -1
  29. package/dist/gateway/static/root/assets/{desktop-pet-Dsc__ynA.js → desktop-pet-DSmeEREd.js} +1 -1
  30. package/dist/gateway/static/root/assets/{desktop-pet-settings-EpuX3C08.js → desktop-pet-settings-MfKceMoe.js} +1 -1
  31. package/dist/gateway/static/root/assets/{extension-debug-page-B_NeLV4K.js → extension-debug-page-CwvWy26h.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-page-D3JC6mLB.js → extension-page-DLrbVBHM.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-settings-page-CXTQt1lp.js → extension-settings-page-BI6C4PM-.js} +1 -1
  34. package/dist/gateway/static/root/assets/{gateway-settings-yeYm_UPE.js → gateway-settings-B3H7Vfbh.js} +1 -1
  35. package/dist/gateway/static/root/assets/{heartbeat-settings-coh1LFsg.js → heartbeat-settings-CUl832Tb.js} +1 -1
  36. package/dist/gateway/static/root/assets/{home-page-CAgoBZqp.js → home-page-CT4JL2jm.js} +1 -1
  37. package/dist/gateway/static/root/assets/{index-Cn__9Un2.js → index-wKE8Q2FX.js} +7 -7
  38. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Cu1bI3Bg.js → keyboard-shortcuts-settings-r4DCS19O.js} +1 -1
  39. package/dist/gateway/static/root/assets/{local-app-workbench-page-C9s2lFaI.js → local-app-workbench-page-qwLuir8A.js} +1 -1
  40. package/dist/gateway/static/root/assets/{local-apps-page-CJ03Umjr.js → local-apps-page-DkfCRLUh.js} +1 -1
  41. package/dist/gateway/static/root/assets/{logs-page-CY_DQKJ2.js → logs-page-DrNaaPWc.js} +1 -1
  42. package/dist/gateway/static/root/assets/{management-settings-YPEemeMo.js → management-settings-Cfx4ATTa.js} +1 -1
  43. package/dist/gateway/static/root/assets/{media-plugins-BjT6jlrA.js → media-plugins-DOLjWd3y.js} +1 -1
  44. package/dist/gateway/static/root/assets/{mobile-pairing-wizard-A8eLtV6v.js → mobile-pairing-wizard-BGW9joAb.js} +1 -1
  45. package/dist/gateway/static/root/assets/{note-detail-panel-DHT2QksJ.js → note-detail-panel-Dbj5OvXE.js} +3 -3
  46. package/dist/gateway/static/root/assets/{notes-page-BRFQiivM.js → notes-page-5hRB3WXc.js} +1 -1
  47. package/dist/gateway/static/root/assets/{page-tabs-DNUwulnl.js → page-tabs-_SGXMT2O.js} +1 -1
  48. package/dist/gateway/static/root/assets/{preview-open-alternatives-B33KsKkJ.js → preview-open-alternatives-lVetfCWk.js} +1 -1
  49. package/dist/gateway/static/root/assets/{project-detail-page-CItEFVt9.js → project-detail-page-CblWLA8k.js} +1 -1
  50. package/dist/gateway/static/root/assets/{projects-page-Bmy-c_lL.js → projects-page-CGk-kzqs.js} +1 -1
  51. package/dist/gateway/static/root/assets/{remote-access-hub-H6B1cA2V.js → remote-access-hub-Du8PweyU.js} +1 -1
  52. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BsCovC39.js → runtime-tools-settings-panel-BDqVrUEj.js} +1 -1
  53. package/dist/gateway/static/root/assets/{schema-form-NsPb_Vam.js → schema-form-8RQgFJJO.js} +1 -1
  54. package/dist/gateway/static/root/assets/{sessions-page-CyYLSXk_.js → sessions-page-BFm94dJC.js} +1 -1
  55. package/dist/gateway/static/root/assets/settings-advanced-gate-C0H-cToA.js +1 -0
  56. package/dist/gateway/static/root/assets/{settings-form-section-BzPIPe1-.js → settings-form-section-j4UjGM-f.js} +1 -1
  57. package/dist/gateway/static/root/assets/{settings-page-4idpMhsw.js → settings-page-lMi50B_s.js} +1 -1
  58. package/dist/gateway/static/root/assets/{setup-status-panel-D2ds_TTD.js → setup-status-panel-Dx4Fy3rU.js} +1 -1
  59. package/dist/gateway/static/root/assets/{share-preview-page-Djo3VTaC.js → share-preview-page-aSyKRH5y.js} +1 -1
  60. package/dist/gateway/static/root/assets/{shares-settings-fhRzNfRp.js → shares-settings-CqF9s0pl.js} +1 -1
  61. package/dist/gateway/static/root/assets/{skill-reload-api-Ct8tUju_.js → skill-reload-api-BBL_ZUSC.js} +1 -1
  62. package/dist/gateway/static/root/assets/{skills-page-riM7FytZ.js → skills-page-C2_CAvxW.js} +1 -1
  63. package/dist/gateway/static/root/assets/{skills-page.utils-B3G8LBb-.js → skills-page.utils-B00e4Q30.js} +1 -1
  64. package/dist/gateway/static/root/assets/{system-settings-panel-CW91jjnB.js → system-settings-panel-CulAkrsV.js} +1 -1
  65. package/dist/gateway/static/root/assets/{task-detail-page-BBXKvl0k.js → task-detail-page-D1FmRFIn.js} +1 -1
  66. package/dist/gateway/static/root/assets/{tunnel-api-DILER0Yb.js → tunnel-api-C01xgCWR.js} +1 -1
  67. package/dist/gateway/static/root/assets/{use-autosave-GxnP8vLO.js → use-autosave-DfSJ3c2p.js} +1 -1
  68. package/dist/gateway/static/root/assets/{user-model-page-I1H4XRfA.js → user-model-page-BGC_5baJ.js} +1 -1
  69. package/dist/gateway/static/root/assets/{voice-api-key-field-CI3sXyl4.js → voice-api-key-field-9v-miN8o.js} +1 -1
  70. package/dist/gateway/static/root/assets/{work-discovery-overlay-DZC9Exn3.js → work-discovery-overlay-CREYgh-1.js} +1 -1
  71. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CaBaQER2.js → workflow-run-setup-panel-BfCWdw8F.js} +1 -1
  72. package/dist/gateway/static/root/assets/{workflows-page-C-_Dxihw.js → workflows-page-CvmKQkD0.js} +1 -1
  73. package/dist/gateway/static/root/index.html +1 -1
  74. package/dist/package.js +1 -1
  75. package/dist/packages/browser-control-contract/src/index.js +30 -1
  76. package/dist/src/agent/agent-manager.js +4 -4
  77. package/dist/src/agent/background-review/run-background-review.js +1 -1
  78. package/dist/src/agent/embedded/model-runtime.js +1 -1
  79. package/dist/src/agent/embedded/run-for-session.js +1 -1
  80. package/dist/src/agent/external-tools/endpoint-provider.js +3 -1
  81. package/dist/src/agent/image/tool-model-config.js +1 -1
  82. package/dist/src/agent/mcp/oauth/mcp-oauth-store.js +1 -1
  83. package/dist/src/agent/service.js +2 -2
  84. package/dist/src/agent/session/session-inspector.js +1 -1
  85. package/dist/src/agent/skills/hub-lock.js +1 -1
  86. package/dist/src/agent/tasks/task-judge-service.js +3 -3
  87. package/dist/src/agent/tools/factory.js +1 -1
  88. package/dist/src/ai-assist/text-assist.js +1 -1
  89. package/dist/src/auth/credentials.js +2 -2
  90. package/dist/src/auth/oauth/xopc-cloud.js +1 -1
  91. package/dist/src/auth/profiles/store.js +1 -1
  92. package/dist/src/auth/sync-provider-auth.js +2 -2
  93. package/dist/src/browser/automations/runtime-service.d.ts +2 -0
  94. package/dist/src/browser/automations/runtime-service.js +1 -1
  95. package/dist/src/browser/drivers/create-driver.d.ts +2 -1
  96. package/dist/src/browser/drivers/create-driver.js +4 -6
  97. package/dist/src/browser/drivers/extension-driver.d.ts +7 -2
  98. package/dist/src/browser/enrollment.d.ts +8 -0
  99. package/dist/src/browser/enrollment.js +25 -0
  100. package/dist/src/browser/extension-identity.d.ts +2 -0
  101. package/dist/src/browser/extension-identity.js +5 -0
  102. package/dist/src/browser/native-messaging-host.d.ts +6 -3
  103. package/dist/src/browser/native-messaging-host.js +23 -5
  104. package/dist/src/browser/providers/browser-ext-install.d.ts +4 -1
  105. package/dist/src/browser/providers/browser-ext-install.js +47 -8
  106. package/dist/src/browser/providers/extension.js +1 -1
  107. package/dist/src/browser/providers/realtime-extension.d.ts +29 -0
  108. package/dist/src/browser/providers/realtime-extension.js +83 -0
  109. package/dist/src/browser/runtime/browser-runtime.js +3 -2
  110. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  111. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  112. package/dist/src/cli/commands/models.js +1 -1
  113. package/dist/src/config/index.js +4 -4
  114. package/dist/src/config/loader.js +2 -2
  115. package/dist/src/config/models-json.js +1 -1
  116. package/dist/src/endpoint-tools/policy.js +10 -3
  117. package/dist/src/extensions/lockfile.js +1 -1
  118. package/dist/src/gateway/agents-admin.js +1 -1
  119. package/dist/src/gateway/file-path-classifier.js +1 -1
  120. package/dist/src/gateway/heartbeat/service.js +1 -1
  121. package/dist/src/gateway/hono/app.js +6 -1
  122. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  123. package/dist/src/gateway/hono/routes/browser.js +20 -30
  124. package/dist/src/gateway/hono/routes/models.js +1 -1
  125. package/dist/src/gateway/security/origin-check.js +5 -0
  126. package/dist/src/gateway/service/config-coordinator.d.ts +0 -2
  127. package/dist/src/gateway/service/config-coordinator.js +1 -3
  128. package/dist/src/gateway/service.d.ts +0 -11
  129. package/dist/src/gateway/service.js +11 -89
  130. package/dist/src/heartbeat/index.js +1 -1
  131. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -1
  132. package/dist/src/providers/index.js +2 -2
  133. package/dist/src/providers/model-registry.js +1 -1
  134. package/dist/src/providers/register-runtime-providers.js +1 -1
  135. package/dist/src/storage/sqlite/device-pairing-approval.js +24 -3
  136. package/dist/src/storage/sqlite/device-pairing-repository.d.ts +10 -1
  137. package/dist/src/storage/sqlite/device-pairing-repository.js +6 -5
  138. package/dist/src/storage/sqlite/migrations/157_browser_endpoint_principals.sql +50 -0
  139. package/dist/src/storage/sqlite/migrations/158_browser_native_enrollment.sql +4 -0
  140. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  141. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  142. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  143. package/dist/src/storage/sqlite/schema.js +1 -1
  144. package/dist/src/tui/tui-startup-resources.js +1 -1
  145. package/dist/src/tui/tui.js +1 -1
  146. package/dist/src/work-discovery/service.js +1 -1
  147. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  148. package/package.json +3 -3
  149. package/dist/browser-ext/dist/assets/sidepanel-C69P3v9u.css +0 -113
  150. package/dist/gateway/static/root/assets/browser-settings-page-DOmUkj2k.js +0 -8
  151. package/dist/gateway/static/root/assets/settings-advanced-gate-3T4rPk4A.js +0 -1
@@ -5099,6 +5099,562 @@ function superRefine(fn, params) {
5099
5099
  return _superRefine(fn, params);
5100
5100
  }
5101
5101
 
5102
+ const ENDPOINT_PROTOCOL_VERSION = 2;
5103
+ const endpointKindSchema = _enum(["web", "desktop", "mobile", "browser"]);
5104
+ const endpointAvailabilitySchema = _enum(["foreground", "background"]);
5105
+ const endpointEffectSchema = _enum(["read", "write", "destructive"]);
5106
+ const endpointConfirmationSchema = _enum(["never", "always"]);
5107
+ const endpointResultKindSchema = _enum(["text", "json", "file"]);
5108
+ const endpointSensitivitySchema = _enum(["public", "personal"]);
5109
+ const identifierSchema = string().min(1).max(160);
5110
+ const turnTokenSchema = string().min(32).max(160).regex(/^[A-Za-z0-9_-]+$/);
5111
+ const turnOriginSchema = discriminatedUnion("type", [
5112
+ strictObject({ type: literal("endpoint"), endpointId: identifierSchema }),
5113
+ strictObject({ type: literal("channel"), channel: string().min(1).max(64) }),
5114
+ strictObject({
5115
+ type: literal("system"),
5116
+ source: _enum(["cli", "automation", "heartbeat", "workflow", "internal"])
5117
+ })
5118
+ ]);
5119
+ const endpointTurnOriginSchema = turnOriginSchema.options[0];
5120
+ endpointTurnOriginSchema.extend({ token: turnTokenSchema });
5121
+ const messageIdSchema$1 = uuid();
5122
+ const timestampSchema$1 = number().int().nonnegative();
5123
+ const jsonObjectSchema = record(string(), unknown());
5124
+ const endpointToolDescriptorSchema = strictObject({
5125
+ name: string().regex(/^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/).max(120),
5126
+ title: string().min(1).max(120),
5127
+ description: string().min(1).max(2e3),
5128
+ inputSchema: jsonObjectSchema,
5129
+ outputSchema: jsonObjectSchema,
5130
+ policyId: string().regex(/^[a-z][a-z0-9_.-]+$/).max(120),
5131
+ sensitivity: endpointSensitivitySchema,
5132
+ effect: endpointEffectSchema,
5133
+ confirmation: endpointConfirmationSchema,
5134
+ requiresForeground: boolean(),
5135
+ requiredPermissions: array(string().min(1).max(120)).max(20),
5136
+ timeoutMs: number().int().min(1e3).max(3e5),
5137
+ maxConcurrency: number().int().min(1).max(4),
5138
+ supportsCancellation: boolean(),
5139
+ idempotent: boolean(),
5140
+ resultKinds: array(endpointResultKindSchema).min(1).max(3)
5141
+ });
5142
+ strictObject({
5143
+ principalId: identifierSchema,
5144
+ displayName: string().min(1).max(120),
5145
+ kind: endpointKindSchema,
5146
+ platform: string().min(1).max(80),
5147
+ publicKey: string().min(32).max(2048)
5148
+ });
5149
+ strictObject({
5150
+ endpointId: identifierSchema
5151
+ });
5152
+ const backgroundCapabilitySchema = string().regex(/^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/).max(120);
5153
+ strictObject({
5154
+ jobId: uuid(),
5155
+ endpointId: identifierSchema,
5156
+ capability: backgroundCapabilitySchema,
5157
+ input: jsonObjectSchema,
5158
+ idempotencyKey: identifierSchema,
5159
+ consentGrantId: identifierSchema,
5160
+ createdAt: timestampSchema$1,
5161
+ expiresAt: timestampSchema$1
5162
+ });
5163
+ strictObject({
5164
+ jobId: uuid(),
5165
+ status: _enum(["queued", "running", "succeeded", "failed", "cancelled"]),
5166
+ updatedAt: timestampSchema$1,
5167
+ output: jsonObjectSchema.optional(),
5168
+ error: strictObject({
5169
+ code: identifierSchema,
5170
+ message: string().min(1).max(2e3)
5171
+ }).optional()
5172
+ });
5173
+ strictObject({
5174
+ eventId: uuid(),
5175
+ endpointId: identifierSchema,
5176
+ subscriptionId: identifierSchema,
5177
+ eventType: backgroundCapabilitySchema,
5178
+ data: jsonObjectSchema,
5179
+ occurredAt: timestampSchema$1
5180
+ });
5181
+ const endpointHelloPayloadSchema = strictObject({
5182
+ principalId: identifierSchema,
5183
+ endpointId: identifierSchema,
5184
+ connectionInstanceId: uuid(),
5185
+ displayName: string().min(1).max(120),
5186
+ kind: endpointKindSchema,
5187
+ platform: string().min(1).max(80),
5188
+ appVersion: string().min(1).max(80),
5189
+ availability: endpointAvailabilitySchema,
5190
+ nonce: identifierSchema,
5191
+ signedAt: timestampSchema$1,
5192
+ signature: string().min(16).max(1024),
5193
+ tools: array(endpointToolDescriptorSchema).max(100)
5194
+ });
5195
+ const toolInvokePayloadSchema = strictObject({
5196
+ invocationId: uuid(),
5197
+ toolCallId: identifierSchema,
5198
+ toolName: endpointToolDescriptorSchema.shape.name,
5199
+ arguments: jsonObjectSchema,
5200
+ descriptorRevision: string().min(1).max(128),
5201
+ confirmationRequired: boolean(),
5202
+ deadlineAt: timestampSchema$1,
5203
+ uploadGrant: strictObject({
5204
+ path: string().startsWith("/").max(500),
5205
+ token: identifierSchema,
5206
+ maxBytes: number().int().positive(),
5207
+ maxFiles: number().int().min(1).max(20),
5208
+ expiresAt: timestampSchema$1
5209
+ }).optional()
5210
+ });
5211
+ const toolCancelPayloadSchema = strictObject({
5212
+ invocationId: uuid(),
5213
+ reason: string().min(1).max(500)
5214
+ });
5215
+ const invocationPayloadSchema = strictObject({ invocationId: uuid() });
5216
+ const endpointToolContentSchema = discriminatedUnion("type", [
5217
+ strictObject({ type: literal("text"), text: string().max(256 * 1024) }),
5218
+ strictObject({ type: literal("json"), value: unknown() }),
5219
+ strictObject({
5220
+ type: literal("file"),
5221
+ fileId: identifierSchema,
5222
+ name: string().min(1).max(255),
5223
+ mimeType: string().min(1).max(255),
5224
+ size: number().int().nonnegative(),
5225
+ sha256: string().regex(/^[a-f0-9]{64}$/)
5226
+ })
5227
+ ]);
5228
+ const endpointToolErrorCodeSchema = _enum([
5229
+ "ENDPOINT_OFFLINE",
5230
+ "ENDPOINT_NOT_FOREGROUND",
5231
+ "ENDPOINT_DISCONNECTED",
5232
+ "TOOL_NOT_FOUND",
5233
+ "TOOL_REVISION_MISMATCH",
5234
+ "INVALID_ARGUMENTS",
5235
+ "PERMISSION_DENIED",
5236
+ "USER_DENIED",
5237
+ "TOOL_BUSY",
5238
+ "TOOL_TIMEOUT",
5239
+ "TOOL_CANCELLED",
5240
+ "RESULT_TOO_LARGE",
5241
+ "PROTOCOL_ERROR"
5242
+ ]);
5243
+ const envelope$1 = (type, payload) => strictObject({
5244
+ protocolVersion: literal(ENDPOINT_PROTOCOL_VERSION),
5245
+ messageId: messageIdSchema$1,
5246
+ type: literal(type),
5247
+ sentAt: timestampSchema$1,
5248
+ payload
5249
+ });
5250
+ const clientEndpointMessageSchema = discriminatedUnion("type", [
5251
+ envelope$1("endpoint.availability_changed", strictObject({ availability: endpointAvailabilitySchema })),
5252
+ envelope$1("tool.received", invocationPayloadSchema),
5253
+ envelope$1("tool.progress", strictObject({
5254
+ invocationId: uuid(),
5255
+ message: string().max(500).optional(),
5256
+ percent: number().min(0).max(100).optional()
5257
+ })),
5258
+ envelope$1("tool.result", strictObject({
5259
+ invocationId: uuid(),
5260
+ content: array(endpointToolContentSchema).min(1).max(20),
5261
+ details: jsonObjectSchema.optional()
5262
+ })),
5263
+ envelope$1("tool.error", strictObject({
5264
+ invocationId: uuid(),
5265
+ code: endpointToolErrorCodeSchema,
5266
+ message: string().min(1).max(2e3)
5267
+ })),
5268
+ envelope$1("tool.cancelled", invocationPayloadSchema)
5269
+ ]);
5270
+ const serverEndpointMessageSchema = discriminatedUnion("type", [
5271
+ envelope$1("tool.invoke", toolInvokePayloadSchema),
5272
+ envelope$1("tool.cancel", toolCancelPayloadSchema)
5273
+ ]);
5274
+ function canonicalJson(value) {
5275
+ if (value === null) return "null";
5276
+ if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
5277
+ if (typeof value === "number") {
5278
+ if (!Number.isFinite(value)) throw new Error("Canonical JSON only supports finite numbers");
5279
+ return JSON.stringify(value);
5280
+ }
5281
+ if (typeof value !== "object") throw new Error(`Canonical JSON does not support ${typeof value}`);
5282
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
5283
+ const prototype = Object.getPrototypeOf(value);
5284
+ if (prototype !== Object.prototype && prototype !== null) {
5285
+ throw new Error("Canonical JSON only supports plain objects");
5286
+ }
5287
+ const record = value;
5288
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
5289
+ }
5290
+ function endpointHelloSigningPayload(payload) {
5291
+ const { signature: _signature, ...unsigned } = payload;
5292
+ return canonicalJson(unsigned);
5293
+ }
5294
+
5295
+ const REALTIME_PROTOCOL_VERSION = 1;
5296
+ const REALTIME_HEARTBEAT_INTERVAL_MS = 15e3;
5297
+ const REALTIME_HEARTBEAT_TIMEOUT_MS = 45e3;
5298
+ const realtimeClientKindSchema = _enum([
5299
+ "web",
5300
+ "desktop",
5301
+ "mobile",
5302
+ "browser_extension",
5303
+ "tui",
5304
+ "mcp"
5305
+ ]);
5306
+ const realtimeTopicSchema = string().min(1).max(512);
5307
+ const realtimeEventNameSchema = string().regex(/^[a-z][a-z0-9_.-]*$/).max(160);
5308
+ const messageIdSchema = uuid();
5309
+ const timestampSchema = number().int().nonnegative();
5310
+ const sequenceSchema = number().int().positive();
5311
+ const envelope = (kind, payload) => strictObject({
5312
+ protocolVersion: literal(REALTIME_PROTOCOL_VERSION),
5313
+ messageId: messageIdSchema,
5314
+ kind: literal(kind),
5315
+ sentAt: timestampSchema,
5316
+ payload
5317
+ });
5318
+ const realtimeSubscriptionSchema = strictObject({
5319
+ topic: realtimeTopicSchema,
5320
+ afterSeq: number().int().nonnegative().optional()
5321
+ });
5322
+ discriminatedUnion("kind", [
5323
+ envelope("realtime.hello", strictObject({
5324
+ ticket: string().min(32).max(512),
5325
+ clientId: string().min(1).max(160),
5326
+ clientKind: realtimeClientKindSchema,
5327
+ subscriptions: array(realtimeSubscriptionSchema).max(100).default([]),
5328
+ endpoint: endpointHelloPayloadSchema.optional()
5329
+ })),
5330
+ envelope("realtime.subscribe", strictObject({
5331
+ subscriptions: array(realtimeSubscriptionSchema).min(1).max(100)
5332
+ })),
5333
+ envelope("realtime.unsubscribe", strictObject({
5334
+ topics: array(realtimeTopicSchema).min(1).max(100)
5335
+ })),
5336
+ envelope("realtime.ping", strictObject({})),
5337
+ envelope("endpoint.message", clientEndpointMessageSchema)
5338
+ ]);
5339
+ const realtimeEventPayloadSchema = strictObject({
5340
+ topic: realtimeTopicSchema,
5341
+ seq: sequenceSchema,
5342
+ event: realtimeEventNameSchema,
5343
+ data: unknown()
5344
+ });
5345
+ const serverRealtimeMessageSchema = discriminatedUnion("kind", [
5346
+ envelope("realtime.ready", strictObject({
5347
+ connectionId: uuid(),
5348
+ heartbeatIntervalMs: literal(REALTIME_HEARTBEAT_INTERVAL_MS),
5349
+ heartbeatTimeoutMs: literal(REALTIME_HEARTBEAT_TIMEOUT_MS),
5350
+ endpoint: strictObject({
5351
+ endpointId: string().min(1).max(160),
5352
+ turnToken: string().min(32).max(160)
5353
+ }).optional()
5354
+ })),
5355
+ envelope("realtime.event", realtimeEventPayloadSchema),
5356
+ envelope("realtime.subscribed", strictObject({
5357
+ topic: realtimeTopicSchema,
5358
+ cursor: number().int().nonnegative()
5359
+ })),
5360
+ envelope("realtime.gap", strictObject({
5361
+ topic: realtimeTopicSchema,
5362
+ requestedSeq: number().int().nonnegative(),
5363
+ earliestSeq: sequenceSchema,
5364
+ recoverable: boolean()
5365
+ })),
5366
+ envelope("realtime.pong", strictObject({})),
5367
+ envelope("realtime.error", strictObject({
5368
+ code: string().min(1).max(80),
5369
+ message: string().min(1).max(500)
5370
+ })),
5371
+ envelope("endpoint.message", serverEndpointMessageSchema)
5372
+ ]);
5373
+ function parseServerRealtimeMessage(value) {
5374
+ return serverRealtimeMessageSchema.parse(value);
5375
+ }
5376
+
5377
+ function frameText(data) {
5378
+ if (typeof data === "string") return data;
5379
+ if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
5380
+ if (ArrayBuffer.isView(data)) {
5381
+ const bytes = Uint8Array.from(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
5382
+ return new TextDecoder().decode(bytes);
5383
+ }
5384
+ throw new Error("Realtime server sent a non-text frame");
5385
+ }
5386
+ function isNonRetryableCloseCode(code) {
5387
+ return code === 1009 || code === 4400 || code === 4401 || code === 4413;
5388
+ }
5389
+ class RealtimeClient {
5390
+ constructor(options) {
5391
+ this.options = options;
5392
+ }
5393
+ options;
5394
+ desiredSubscriptions = /* @__PURE__ */ new Map();
5395
+ cursors = /* @__PURE__ */ new Map();
5396
+ socket;
5397
+ reconnectTimer;
5398
+ heartbeatTimer;
5399
+ connectionTimer;
5400
+ ticketAbort;
5401
+ generation = 0;
5402
+ reconnectAttempts = 0;
5403
+ shouldReconnect = false;
5404
+ ready = false;
5405
+ lastServerMessageAt = 0;
5406
+ endpointBinding;
5407
+ reconnectWhenSocketOpens = false;
5408
+ connect() {
5409
+ if (this.shouldReconnect) return;
5410
+ this.shouldReconnect = true;
5411
+ void this.open(false);
5412
+ }
5413
+ disconnect() {
5414
+ this.shouldReconnect = false;
5415
+ this.reconnectWhenSocketOpens = false;
5416
+ this.generation += 1;
5417
+ this.clearTimers();
5418
+ this.ready = false;
5419
+ this.endpointBinding?.onDisconnected?.();
5420
+ this.socket?.close(1e3, "Client disconnected");
5421
+ this.socket = void 0;
5422
+ this.options.onStateChange?.("disconnected");
5423
+ }
5424
+ reconnect() {
5425
+ this.reconnectWhenSocketOpens = false;
5426
+ this.generation += 1;
5427
+ this.clearTimers();
5428
+ this.ready = false;
5429
+ this.endpointBinding?.onDisconnected?.();
5430
+ this.socket?.close(1e3, "Client reconnecting");
5431
+ this.socket = void 0;
5432
+ this.reconnectAttempts = 0;
5433
+ this.shouldReconnect = true;
5434
+ void this.open(true);
5435
+ }
5436
+ subscribe(topic, afterSeq) {
5437
+ this.desiredSubscriptions.set(topic, afterSeq);
5438
+ if (afterSeq !== void 0) this.cursors.set(topic, afterSeq);
5439
+ if (this.ready) {
5440
+ this.sendSubscriptions([{ topic, afterSeq: this.cursors.get(topic) ?? afterSeq }]);
5441
+ }
5442
+ }
5443
+ unsubscribe(topic) {
5444
+ this.desiredSubscriptions.delete(topic);
5445
+ if (this.ready) this.send(this.clientMessage("realtime.unsubscribe", { topics: [topic] }));
5446
+ }
5447
+ sendEndpointMessage(message) {
5448
+ if (!this.ready) throw new Error("Realtime connection is not ready");
5449
+ this.send(this.clientMessage("endpoint.message", message));
5450
+ }
5451
+ setEndpoint(binding) {
5452
+ this.endpointBinding = binding;
5453
+ this.refreshEndpointConnection();
5454
+ }
5455
+ clearEndpoint(binding) {
5456
+ if (binding && this.endpointBinding !== binding) return;
5457
+ const previous = this.endpointBinding;
5458
+ this.endpointBinding = void 0;
5459
+ previous?.onDisconnected?.();
5460
+ this.refreshEndpointConnection();
5461
+ }
5462
+ refreshEndpointConnection() {
5463
+ if (!this.shouldReconnect) return;
5464
+ if (this.socket?.readyState === 0) {
5465
+ this.reconnectWhenSocketOpens = true;
5466
+ return;
5467
+ }
5468
+ this.reconnect();
5469
+ }
5470
+ async open(reconnecting) {
5471
+ const generation = ++this.generation;
5472
+ const timeoutMs = this.options.connectionTimeoutMs ?? 15e3;
5473
+ const ticketAbort = new AbortController();
5474
+ this.ticketAbort?.abort();
5475
+ this.ticketAbort = ticketAbort;
5476
+ let socket;
5477
+ let closed = false;
5478
+ let messageQueue;
5479
+ const failAttempt = (error, retry = true) => {
5480
+ if (closed || generation !== this.generation) return;
5481
+ closed = true;
5482
+ this.clearConnectionTimer();
5483
+ ticketAbort.abort();
5484
+ if (this.ticketAbort === ticketAbort) this.ticketAbort = void 0;
5485
+ this.ready = false;
5486
+ this.endpointBinding?.onDisconnected?.();
5487
+ this.clearHeartbeat();
5488
+ if (this.socket === socket) this.socket = void 0;
5489
+ if (socket) {
5490
+ socket.onopen = null;
5491
+ socket.onmessage = null;
5492
+ socket.onerror = null;
5493
+ socket.onclose = null;
5494
+ socket.close(4e3, error.slice(0, 120));
5495
+ }
5496
+ if (this.shouldReconnect && retry) this.scheduleReconnect(error);
5497
+ else if (this.shouldReconnect) {
5498
+ this.shouldReconnect = false;
5499
+ this.options.onStateChange?.("error", error);
5500
+ } else this.options.onStateChange?.("disconnected");
5501
+ };
5502
+ this.connectionTimer = setTimeout(() => failAttempt("Realtime connection timed out"), timeoutMs);
5503
+ this.options.onStateChange?.(reconnecting ? "reconnecting" : "connecting");
5504
+ try {
5505
+ const [ticket, endpoint] = await Promise.all([
5506
+ this.options.issueTicket(ticketAbort.signal),
5507
+ this.endpointBinding?.createHello()
5508
+ ]);
5509
+ if (closed || !this.shouldReconnect || generation !== this.generation) return;
5510
+ socket = this.options.createWebSocket(this.options.getWebSocketUrl());
5511
+ this.socket = socket;
5512
+ socket.onopen = () => {
5513
+ if (generation !== this.generation) return;
5514
+ if (this.reconnectWhenSocketOpens) {
5515
+ this.reconnect();
5516
+ return;
5517
+ }
5518
+ const subscriptions = [...this.desiredSubscriptions].map(([topic, afterSeq]) => ({
5519
+ topic,
5520
+ afterSeq: this.cursors.get(topic) ?? afterSeq
5521
+ }));
5522
+ this.send(this.clientMessage("realtime.hello", {
5523
+ ticket,
5524
+ clientId: this.options.clientId,
5525
+ clientKind: this.options.clientKind,
5526
+ subscriptions,
5527
+ ...endpoint ? { endpoint } : {}
5528
+ }));
5529
+ };
5530
+ socket.onmessage = (event) => {
5531
+ if (generation !== this.generation) return;
5532
+ const process = () => {
5533
+ if (closed || generation !== this.generation) return;
5534
+ return this.handleServerMessage(frameText(event.data));
5535
+ };
5536
+ if (!messageQueue) {
5537
+ try {
5538
+ const pending = process();
5539
+ if (!pending) return;
5540
+ const current2 = pending.catch((error) => failAttempt(error instanceof Error ? error.message : "Invalid realtime server message")).then(() => void 0);
5541
+ messageQueue = current2;
5542
+ void current2.finally(() => {
5543
+ if (messageQueue === current2) messageQueue = void 0;
5544
+ });
5545
+ } catch (error) {
5546
+ failAttempt(error instanceof Error ? error.message : "Invalid realtime server message");
5547
+ }
5548
+ return;
5549
+ }
5550
+ const current = messageQueue.then(process).catch((error) => failAttempt(error instanceof Error ? error.message : "Invalid realtime server message")).then(() => void 0);
5551
+ messageQueue = current;
5552
+ void current.finally(() => {
5553
+ if (messageQueue === current) messageQueue = void 0;
5554
+ });
5555
+ };
5556
+ socket.onerror = () => {
5557
+ };
5558
+ socket.onclose = (event) => {
5559
+ failAttempt(
5560
+ event.reason || `WebSocket closed (${event.code ?? 0})`,
5561
+ !isNonRetryableCloseCode(event.code)
5562
+ );
5563
+ };
5564
+ } catch (error) {
5565
+ failAttempt(error instanceof Error ? error.message : "Realtime connection failed");
5566
+ }
5567
+ }
5568
+ handleServerMessage(text) {
5569
+ const message = parseServerRealtimeMessage(JSON.parse(text));
5570
+ this.lastServerMessageAt = Date.now();
5571
+ if (message.kind === "realtime.ready") {
5572
+ this.ready = true;
5573
+ this.reconnectAttempts = 0;
5574
+ this.clearConnectionTimer();
5575
+ this.ticketAbort = void 0;
5576
+ this.options.onStateChange?.("connected");
5577
+ if (message.payload.endpoint) this.endpointBinding?.onReady(message.payload.endpoint);
5578
+ this.clearHeartbeat();
5579
+ this.heartbeatTimer = setInterval(() => {
5580
+ if (!this.ready) return;
5581
+ if (Date.now() - this.lastServerMessageAt >= message.payload.heartbeatTimeoutMs) {
5582
+ this.reconnect();
5583
+ return;
5584
+ }
5585
+ this.send(this.clientMessage("realtime.ping", {}));
5586
+ }, message.payload.heartbeatIntervalMs);
5587
+ } else if (message.kind === "realtime.event") {
5588
+ const cursor = this.cursors.get(message.payload.topic);
5589
+ if (cursor !== void 0 && message.payload.seq <= cursor) return;
5590
+ this.options.onEvent?.(message.payload);
5591
+ this.cursors.set(message.payload.topic, message.payload.seq);
5592
+ } else if (message.kind === "realtime.subscribed") {
5593
+ this.cursors.set(message.payload.topic, message.payload.cursor);
5594
+ } else if (message.kind === "realtime.gap") {
5595
+ this.cursors.set(message.payload.topic, message.payload.earliestSeq - 1);
5596
+ return this.options.onGap?.(message.payload);
5597
+ } else if (message.kind === "realtime.error") {
5598
+ this.options.onStateChange?.("error", message.payload.message);
5599
+ } else if (message.kind === "endpoint.message") {
5600
+ this.endpointBinding?.onMessage(message.payload);
5601
+ this.options.onEndpointMessage?.(message.payload);
5602
+ }
5603
+ }
5604
+ sendSubscriptions(subscriptions) {
5605
+ this.send(this.clientMessage("realtime.subscribe", { subscriptions }));
5606
+ }
5607
+ clientMessage(kind, payload) {
5608
+ const messageId = this.options.createMessageId?.() ?? globalThis.crypto?.randomUUID?.();
5609
+ if (!messageId) throw new Error("RealtimeClient requires createMessageId when crypto.randomUUID is unavailable");
5610
+ return {
5611
+ protocolVersion: REALTIME_PROTOCOL_VERSION,
5612
+ messageId,
5613
+ kind,
5614
+ sentAt: Date.now(),
5615
+ payload
5616
+ };
5617
+ }
5618
+ send(message) {
5619
+ if (!this.socket || this.socket.readyState !== 1) return;
5620
+ this.socket.send(JSON.stringify(message));
5621
+ }
5622
+ scheduleReconnect(error) {
5623
+ if (!this.shouldReconnect) return;
5624
+ this.reconnectAttempts += 1;
5625
+ const max = this.options.maxReconnectAttempts;
5626
+ if (max !== void 0 && this.reconnectAttempts > max) {
5627
+ this.shouldReconnect = false;
5628
+ this.options.onStateChange?.("error", error);
5629
+ return;
5630
+ }
5631
+ this.options.onStateChange?.("reconnecting", error);
5632
+ const cap = Math.min(3e4, 1e3 * 2 ** Math.min(this.reconnectAttempts - 1, 5));
5633
+ const delay = Math.round(cap * (0.5 + Math.random() * 0.5));
5634
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
5635
+ this.reconnectTimer = setTimeout(() => {
5636
+ this.reconnectTimer = void 0;
5637
+ void this.open(true);
5638
+ }, delay);
5639
+ }
5640
+ clearHeartbeat() {
5641
+ if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
5642
+ this.heartbeatTimer = void 0;
5643
+ }
5644
+ clearTimers() {
5645
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
5646
+ this.reconnectTimer = void 0;
5647
+ this.clearConnectionTimer();
5648
+ this.ticketAbort?.abort();
5649
+ this.ticketAbort = void 0;
5650
+ this.clearHeartbeat();
5651
+ }
5652
+ clearConnectionTimer() {
5653
+ if (this.connectionTimer) clearTimeout(this.connectionTimer);
5654
+ this.connectionTimer = void 0;
5655
+ }
5656
+ }
5657
+
5102
5658
  const devicePairingKeySchema = strictObject({
5103
5659
  kty: literal("EC"),
5104
5660
  crv: literal("P-256"),
@@ -5201,15 +5757,57 @@ function truncateUtf8(value, maxBytes) {
5201
5757
  return { value: value.slice(0, low), truncated: true };
5202
5758
  }
5203
5759
  function sanitizedUrl(raw) {
5204
- const url = new URL(raw);
5760
+ let url;
5761
+ try {
5762
+ url = new URL(raw);
5763
+ } catch {
5764
+ throw new Error("This tab does not have a valid web address");
5765
+ }
5205
5766
  if (url.protocol !== "http:" && url.protocol !== "https:") {
5206
- throw new Error("This page cannot be attached");
5767
+ throw new Error("Chrome does not allow xopc to read this page. Open a regular http(s) page and try again.");
5768
+ }
5769
+ if (url.hostname === "chromewebstore.google.com" || url.hostname === "chrome.google.com" && url.pathname.startsWith("/webstore")) {
5770
+ throw new Error("Chrome does not allow extensions to read Chrome Web Store pages.");
5207
5771
  }
5208
5772
  url.username = "";
5209
5773
  url.password = "";
5210
5774
  url.hash = "";
5211
5775
  return url.toString();
5212
5776
  }
5777
+ function siteAccessError(cause, url) {
5778
+ const message = cause instanceof Error ? cause.message : String(cause);
5779
+ const hostname = new URL(url).hostname;
5780
+ if (/cannot access|cannot be scripted|missing host permission|extensions gallery|chrome web store/i.test(message)) {
5781
+ return new Error(`Chrome blocked access to ${hostname}. Check xopc's site access for this page and try again.`);
5782
+ }
5783
+ return cause instanceof Error ? cause : new Error(message);
5784
+ }
5785
+ async function runWithTabSiteAccess(tabId, operation) {
5786
+ const tab = await chrome.tabs.get(tabId);
5787
+ if (tab.id === void 0 || tab.windowId === void 0 || !tab.url) {
5788
+ throw new Error("The selected tab is no longer available");
5789
+ }
5790
+ const url = sanitizedUrl(tab.url);
5791
+ const originPattern = `${new URL(url).origin}/*`;
5792
+ const alreadyGranted = await chrome.permissions.contains({ origins: [originPattern] });
5793
+ let newlyGranted = false;
5794
+ if (!alreadyGranted) {
5795
+ let granted = false;
5796
+ try {
5797
+ granted = await chrome.permissions.request({ origins: [originPattern] });
5798
+ } catch (cause) {
5799
+ throw siteAccessError(cause, url);
5800
+ }
5801
+ if (!granted) throw new Error(`Allow xopc to access ${new URL(url).hostname} to continue.`);
5802
+ newlyGranted = true;
5803
+ }
5804
+ try {
5805
+ return await operation({ ...tab, id: tab.id, windowId: tab.windowId, url });
5806
+ } catch (cause) {
5807
+ if (newlyGranted) await chrome.permissions.remove({ origins: [originPattern] }).catch(() => false);
5808
+ throw siteAccessError(cause, url);
5809
+ }
5810
+ }
5213
5811
  async function sha256(value) {
5214
5812
  const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
5215
5813
  return [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
@@ -5259,6 +5857,9 @@ async function captureTabPage(tabId, mode) {
5259
5857
  const raw = result[0]?.result;
5260
5858
  if (!raw) throw new Error("Could not read this page");
5261
5859
  const url = sanitizedUrl(raw.url);
5860
+ if (new URL(url).origin !== new URL(sanitizedUrl(tab.url)).origin) {
5861
+ throw new Error("The page changed while xopc was reading it. Try again.");
5862
+ }
5262
5863
  const selected = raw.selection ? truncateUtf8(raw.selection, MAX_BROWSER_SELECTION_BYTES) : void 0;
5263
5864
  const page = raw.text ? truncateUtf8(raw.text, MAX_BROWSER_PAGE_TEXT_BYTES) : void 0;
5264
5865
  const canonical = {
@@ -5278,16 +5879,8 @@ ${raw.timeOrigin}`),
5278
5879
  truncated: selected?.truncated === true || page?.truncated === true
5279
5880
  };
5280
5881
  }
5281
- async function captureTabWithPermission(tabId, url, mode) {
5282
- const originPattern = `${new URL(sanitizedUrl(url)).origin}/*`;
5283
- const alreadyGranted = await chrome.permissions.contains({ origins: [originPattern] });
5284
- const granted = alreadyGranted || await chrome.permissions.request({ origins: [originPattern] });
5285
- if (!granted) throw new Error("Site access is required to attach this tab");
5286
- try {
5287
- return await captureTabPage(tabId, mode);
5288
- } finally {
5289
- if (!alreadyGranted) await chrome.permissions.remove({ origins: [originPattern] });
5290
- }
5882
+ async function captureTabWithPermission(tabId, mode) {
5883
+ return runWithTabSiteAccess(tabId, () => captureTabPage(tabId, mode));
5291
5884
  }
5292
5885
  async function activeTabId() {
5293
5886
  return (await chrome.tabs.query({ active: true, currentWindow: true }))[0]?.id;
@@ -5295,229 +5888,132 @@ async function activeTabId() {
5295
5888
  async function currentTabDescriptor() {
5296
5889
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
5297
5890
  if (tab.id === void 0 || tab.windowId === void 0 || !tab.url) throw new Error("No active web page");
5298
- sanitizedUrl(tab.url);
5299
- const result = await chrome.scripting.executeScript({
5300
- target: { tabId: tab.id },
5301
- func: () => ({ url: location.href, timeOrigin: performance.timeOrigin })
5302
- });
5303
- const marker = result[0]?.result;
5304
- if (!marker) throw new Error("Could not identify this page");
5305
- const currentUrl = sanitizedUrl(marker.url);
5306
- return {
5307
- tabId: String(tab.id),
5308
- windowId: String(tab.windowId),
5309
- documentId: await sha256(`${currentUrl}
5891
+ return runWithTabSiteAccess(tab.id, async (accessibleTab) => {
5892
+ const result = await chrome.scripting.executeScript({
5893
+ target: { tabId: accessibleTab.id },
5894
+ func: () => ({ url: location.href, timeOrigin: performance.timeOrigin })
5895
+ });
5896
+ const marker = result[0]?.result;
5897
+ if (!marker) throw new Error("Could not identify this page");
5898
+ const currentUrl = sanitizedUrl(marker.url);
5899
+ if (new URL(currentUrl).origin !== new URL(accessibleTab.url).origin) {
5900
+ throw new Error("The page changed while xopc was connecting to it. Try again.");
5901
+ }
5902
+ return {
5903
+ tabId: String(accessibleTab.id),
5904
+ windowId: String(accessibleTab.windowId),
5905
+ documentId: await sha256(`${currentUrl}
5310
5906
  ${marker.timeOrigin}`),
5311
- urlOrigin: new URL(currentUrl).origin
5312
- };
5907
+ urlOrigin: new URL(currentUrl).origin
5908
+ };
5909
+ });
5313
5910
  }
5314
5911
 
5315
- const ENDPOINT_PROTOCOL_VERSION = 2;
5316
- const endpointKindSchema = _enum(["web", "desktop", "mobile", "browser"]);
5317
- const endpointAvailabilitySchema = _enum(["foreground", "background"]);
5318
- const endpointEffectSchema = _enum(["read", "write", "destructive"]);
5319
- const endpointConfirmationSchema = _enum(["never", "always"]);
5320
- const endpointResultKindSchema = _enum(["text", "json", "file"]);
5321
- const endpointSensitivitySchema = _enum(["public", "personal"]);
5322
- const identifierSchema = string().min(1).max(160);
5323
- const turnTokenSchema = string().min(32).max(160).regex(/^[A-Za-z0-9_-]+$/);
5324
- const turnOriginSchema = discriminatedUnion("type", [
5325
- strictObject({ type: literal("endpoint"), endpointId: identifierSchema }),
5326
- strictObject({ type: literal("channel"), channel: string().min(1).max(64) }),
5327
- strictObject({
5328
- type: literal("system"),
5329
- source: _enum(["cli", "automation", "heartbeat", "workflow", "internal"])
5330
- })
5331
- ]);
5332
- const endpointTurnOriginSchema = turnOriginSchema.options[0];
5333
- endpointTurnOriginSchema.extend({ token: turnTokenSchema });
5334
- const messageIdSchema = uuid();
5335
- const timestampSchema = number().int().nonnegative();
5336
- const jsonObjectSchema = record(string(), unknown());
5337
- const endpointToolDescriptorSchema = strictObject({
5338
- name: string().regex(/^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/).max(120),
5339
- title: string().min(1).max(120),
5340
- description: string().min(1).max(2e3),
5341
- inputSchema: jsonObjectSchema,
5342
- outputSchema: jsonObjectSchema,
5343
- policyId: string().regex(/^[a-z][a-z0-9_.-]+$/).max(120),
5344
- sensitivity: endpointSensitivitySchema,
5345
- effect: endpointEffectSchema,
5346
- confirmation: endpointConfirmationSchema,
5347
- requiresForeground: boolean(),
5348
- requiredPermissions: array(string().min(1).max(120)).max(20),
5349
- timeoutMs: number().int().min(1e3).max(3e5),
5350
- maxConcurrency: number().int().min(1).max(4),
5351
- supportsCancellation: boolean(),
5352
- idempotent: boolean(),
5353
- resultKinds: array(endpointResultKindSchema).min(1).max(3)
5354
- });
5355
- strictObject({
5356
- principalId: identifierSchema,
5357
- displayName: string().min(1).max(120),
5358
- kind: endpointKindSchema,
5359
- platform: string().min(1).max(80),
5360
- publicKey: string().min(32).max(2048)
5361
- });
5362
- strictObject({
5363
- endpointId: identifierSchema
5364
- });
5365
- const backgroundCapabilitySchema = string().regex(/^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$/).max(120);
5366
- strictObject({
5367
- jobId: uuid(),
5368
- endpointId: identifierSchema,
5369
- capability: backgroundCapabilitySchema,
5370
- input: jsonObjectSchema,
5371
- idempotencyKey: identifierSchema,
5372
- consentGrantId: identifierSchema,
5373
- createdAt: timestampSchema,
5374
- expiresAt: timestampSchema
5375
- });
5376
- strictObject({
5377
- jobId: uuid(),
5378
- status: _enum(["queued", "running", "succeeded", "failed", "cancelled"]),
5379
- updatedAt: timestampSchema,
5380
- output: jsonObjectSchema.optional(),
5381
- error: strictObject({
5382
- code: identifierSchema,
5383
- message: string().min(1).max(2e3)
5384
- }).optional()
5385
- });
5386
- strictObject({
5387
- eventId: uuid(),
5388
- endpointId: identifierSchema,
5389
- subscriptionId: identifierSchema,
5390
- eventType: backgroundCapabilitySchema,
5391
- data: jsonObjectSchema,
5392
- occurredAt: timestampSchema
5393
- });
5394
- const endpointHelloPayloadSchema = strictObject({
5395
- principalId: identifierSchema,
5396
- endpointId: identifierSchema,
5397
- connectionInstanceId: uuid(),
5398
- displayName: string().min(1).max(120),
5399
- kind: endpointKindSchema,
5400
- platform: string().min(1).max(80),
5401
- appVersion: string().min(1).max(80),
5402
- availability: endpointAvailabilitySchema,
5403
- nonce: identifierSchema,
5404
- signedAt: timestampSchema,
5405
- signature: string().min(16).max(1024),
5406
- tools: array(endpointToolDescriptorSchema).max(100)
5407
- });
5408
- const toolInvokePayloadSchema = strictObject({
5409
- invocationId: uuid(),
5410
- toolCallId: identifierSchema,
5411
- toolName: endpointToolDescriptorSchema.shape.name,
5412
- arguments: jsonObjectSchema,
5413
- descriptorRevision: string().min(1).max(128),
5414
- confirmationRequired: boolean(),
5415
- deadlineAt: timestampSchema,
5416
- uploadGrant: strictObject({
5417
- path: string().startsWith("/").max(500),
5418
- token: identifierSchema,
5419
- maxBytes: number().int().positive(),
5420
- maxFiles: number().int().min(1).max(20),
5421
- expiresAt: timestampSchema
5422
- }).optional()
5423
- });
5424
- const toolCancelPayloadSchema = strictObject({
5425
- invocationId: uuid(),
5426
- reason: string().min(1).max(500)
5427
- });
5428
- const invocationPayloadSchema = strictObject({ invocationId: uuid() });
5429
- const endpointToolContentSchema = discriminatedUnion("type", [
5430
- strictObject({ type: literal("text"), text: string().max(256 * 1024) }),
5431
- strictObject({ type: literal("json"), value: unknown() }),
5432
- strictObject({
5433
- type: literal("file"),
5434
- fileId: identifierSchema,
5435
- name: string().min(1).max(255),
5436
- mimeType: string().min(1).max(255),
5437
- size: number().int().nonnegative(),
5438
- sha256: string().regex(/^[a-f0-9]{64}$/)
5439
- })
5440
- ]);
5441
- const endpointToolErrorCodeSchema = _enum([
5442
- "ENDPOINT_OFFLINE",
5443
- "ENDPOINT_NOT_FOREGROUND",
5444
- "ENDPOINT_DISCONNECTED",
5445
- "TOOL_NOT_FOUND",
5446
- "TOOL_REVISION_MISMATCH",
5447
- "INVALID_ARGUMENTS",
5448
- "PERMISSION_DENIED",
5449
- "USER_DENIED",
5450
- "TOOL_BUSY",
5451
- "TOOL_TIMEOUT",
5452
- "TOOL_CANCELLED",
5453
- "RESULT_TOO_LARGE",
5454
- "PROTOCOL_ERROR"
5455
- ]);
5456
- const envelope = (type, payload) => strictObject({
5457
- protocolVersion: literal(ENDPOINT_PROTOCOL_VERSION),
5458
- messageId: messageIdSchema,
5459
- type: literal(type),
5460
- sentAt: timestampSchema,
5461
- payload
5462
- });
5463
- const clientEndpointMessageSchema = discriminatedUnion("type", [
5464
- envelope("endpoint.availability_changed", strictObject({ availability: endpointAvailabilitySchema })),
5465
- envelope("tool.received", invocationPayloadSchema),
5466
- envelope("tool.progress", strictObject({
5467
- invocationId: uuid(),
5468
- message: string().max(500).optional(),
5469
- percent: number().min(0).max(100).optional()
5470
- })),
5471
- envelope("tool.result", strictObject({
5472
- invocationId: uuid(),
5473
- content: array(endpointToolContentSchema).min(1).max(20),
5474
- details: jsonObjectSchema.optional()
5475
- })),
5476
- envelope("tool.error", strictObject({
5477
- invocationId: uuid(),
5478
- code: endpointToolErrorCodeSchema,
5479
- message: string().min(1).max(2e3)
5480
- })),
5481
- envelope("tool.cancelled", invocationPayloadSchema)
5482
- ]);
5483
- const serverEndpointMessageSchema = discriminatedUnion("type", [
5484
- envelope("tool.invoke", toolInvokePayloadSchema),
5485
- envelope("tool.cancel", toolCancelPayloadSchema)
5486
- ]);
5487
- function canonicalJson(value) {
5488
- if (value === null) return "null";
5489
- if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
5490
- if (typeof value === "number") {
5491
- if (!Number.isFinite(value)) throw new Error("Canonical JSON only supports finite numbers");
5492
- return JSON.stringify(value);
5912
+ const DATABASE_NAME = "xopc-browser-chat-state";
5913
+ const DATABASE_VERSION = 1;
5914
+ const OUTBOX_STORE = "outbox";
5915
+ function openDatabase() {
5916
+ return new Promise((resolve, reject) => {
5917
+ const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION);
5918
+ request.onupgradeneeded = () => {
5919
+ if (!request.result.objectStoreNames.contains(OUTBOX_STORE)) {
5920
+ request.result.createObjectStore(OUTBOX_STORE);
5921
+ }
5922
+ };
5923
+ request.onsuccess = () => resolve(request.result);
5924
+ request.onerror = () => reject(request.error ?? new Error("Could not open the browser chat outbox"));
5925
+ });
5926
+ }
5927
+ function transactionResult(database, mode, createRequest) {
5928
+ return new Promise((resolve, reject) => {
5929
+ const transaction = database.transaction(OUTBOX_STORE, mode);
5930
+ const request = createRequest(transaction.objectStore(OUTBOX_STORE));
5931
+ let result;
5932
+ request.onsuccess = () => {
5933
+ result = request.result;
5934
+ };
5935
+ request.onerror = () => reject(request.error ?? new Error("Browser chat outbox request failed"));
5936
+ transaction.oncomplete = () => resolve(result);
5937
+ transaction.onerror = () => reject(transaction.error ?? new Error("Browser chat outbox transaction failed"));
5938
+ transaction.onabort = () => reject(transaction.error ?? new Error("Browser chat outbox transaction was aborted"));
5939
+ });
5940
+ }
5941
+ async function readBrowserOutbox(sessionKey) {
5942
+ const database = await openDatabase();
5943
+ try {
5944
+ return await transactionResult(
5945
+ database,
5946
+ "readonly",
5947
+ (store) => store.get(sessionKey)
5948
+ );
5949
+ } finally {
5950
+ database.close();
5493
5951
  }
5494
- if (typeof value !== "object") throw new Error(`Canonical JSON does not support ${typeof value}`);
5495
- if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
5496
- const prototype = Object.getPrototypeOf(value);
5497
- if (prototype !== Object.prototype && prototype !== null) {
5498
- throw new Error("Canonical JSON only supports plain objects");
5952
+ }
5953
+ async function writeBrowserOutbox(sessionKey, value) {
5954
+ const database = await openDatabase();
5955
+ try {
5956
+ await transactionResult(database, "readwrite", (store) => store.put(value, sessionKey));
5957
+ } finally {
5958
+ database.close();
5499
5959
  }
5500
- const record = value;
5501
- return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
5502
5960
  }
5503
- function endpointHelloSigningPayload(payload) {
5504
- const { signature: _signature, ...unsigned } = payload;
5505
- return canonicalJson(unsigned);
5961
+ async function deleteBrowserOutbox(sessionKey) {
5962
+ const database = await openDatabase();
5963
+ try {
5964
+ await transactionResult(database, "readwrite", (store) => store.delete(sessionKey));
5965
+ } finally {
5966
+ database.close();
5967
+ }
5968
+ }
5969
+ async function clearBrowserOutboxes() {
5970
+ const database = await openDatabase();
5971
+ try {
5972
+ await transactionResult(database, "readwrite", (store) => store.clear());
5973
+ } finally {
5974
+ database.close();
5975
+ }
5506
5976
  }
5507
5977
 
5508
5978
  const PROFILE_KEY = "xopc.browser.profile";
5979
+ const AUTO_CONNECT_KEY = "xopc.browser.auto-connect";
5509
5980
  const KEY_DATABASE = "xopc-browser-identity";
5510
5981
  const KEY_STORE = "identity";
5511
5982
  const KEY_NAME = "device-key";
5512
5983
  const NATIVE_HOST_NAME = "ai.xopc.browser";
5513
5984
  let refreshTask;
5514
- async function discoverLocalGateway() {
5985
+ async function browserPublicKeyThumbprint(jwk) {
5986
+ const canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
5987
+ return encodeBase64Url(new Uint8Array(await crypto.subtle.digest(
5988
+ "SHA-256",
5989
+ new TextEncoder().encode(canonical)
5990
+ )));
5991
+ }
5992
+ async function isAutoConnectEnabled() {
5993
+ const stored = await chrome.storage.local.get(AUTO_CONNECT_KEY);
5994
+ return stored[AUTO_CONNECT_KEY] !== false;
5995
+ }
5996
+ async function setAutoConnectEnabled(enabled) {
5997
+ await chrome.storage.local.set({ [AUTO_CONNECT_KEY]: enabled });
5998
+ }
5999
+ async function discoverLocalGateway(options) {
5515
6000
  try {
6001
+ if (!options?.force && !await isAutoConnectEnabled()) return void 0;
6002
+ const pair = await getOrCreateKeyPair();
6003
+ const key = await publicKeyJwk(pair);
6004
+ const nonce = randomNonce();
5516
6005
  const response = await chrome.runtime.sendNativeMessage(
5517
6006
  NATIVE_HOST_NAME,
5518
- { type: "bootstrap" }
6007
+ {
6008
+ type: "bootstrap",
6009
+ extensionId: chrome.runtime.id,
6010
+ publicKeyJwk: key,
6011
+ publicKeyThumbprint: await browserPublicKeyThumbprint(key),
6012
+ nonce
6013
+ }
5519
6014
  );
5520
6015
  if (response.ok !== true || typeof response.gatewayUrl !== "string" || typeof response.pairingLink !== "string" || typeof response.expiresAt !== "number") {
6016
+ if (options?.force) throw new Error(response.error || "The local xopc enrollment host returned an invalid response");
5521
6017
  return void 0;
5522
6018
  }
5523
6019
  return {
@@ -5525,7 +6021,8 @@ async function discoverLocalGateway() {
5525
6021
  pairingLink: response.pairingLink,
5526
6022
  expiresAt: response.expiresAt
5527
6023
  };
5528
- } catch {
6024
+ } catch (cause) {
6025
+ if (options?.force) throw cause;
5529
6026
  return void 0;
5530
6027
  }
5531
6028
  }
@@ -5598,6 +6095,14 @@ async function getOrCreateKeyPair() {
5598
6095
  }).finally(() => database.close());
5599
6096
  return pair;
5600
6097
  }
6098
+ async function forgetIdentity() {
6099
+ await new Promise((resolve, reject) => {
6100
+ const request = indexedDB.deleteDatabase(KEY_DATABASE);
6101
+ request.onsuccess = () => resolve();
6102
+ request.onerror = () => reject(request.error);
6103
+ request.onblocked = () => reject(new Error("Browser identity is still in use"));
6104
+ });
6105
+ }
5601
6106
  async function publicKeyJwk(pair) {
5602
6107
  const exported = await crypto.subtle.exportKey("jwk", pair.publicKey);
5603
6108
  if (!exported.x || !exported.y) throw new Error("Browser identity key export failed");
@@ -5718,7 +6223,7 @@ async function pairGateway(link, onApproval) {
5718
6223
  const requestId = crypto.randomUUID();
5719
6224
  const initialRefreshToken = createRefreshToken();
5720
6225
  let result = await signedPairingRequest(pair, payload, origin, requestId, "request");
5721
- onApproval(result.request.confirmationCode);
6226
+ if (result.request.status === "pending") onApproval(result.request.confirmationCode);
5722
6227
  while (result.request.status === "pending") {
5723
6228
  await wait(1500);
5724
6229
  result = await signedPairingRequest(pair, payload, origin, requestId, "status");
@@ -5801,7 +6306,7 @@ async function registerBrowserEndpoint() {
5801
6306
  });
5802
6307
  if (!response.ok) throw new Error(`Browser endpoint registration failed (${response.status})`);
5803
6308
  }
5804
- async function createBrowserEndpointHello() {
6309
+ async function createBrowserEndpointHello(tools = []) {
5805
6310
  const profile = await getAccessProfile();
5806
6311
  const pair = await getOrCreateKeyPair();
5807
6312
  const unsigned = {
@@ -5816,7 +6321,7 @@ async function createBrowserEndpointHello() {
5816
6321
  nonce: crypto.randomUUID(),
5817
6322
  signedAt: Date.now(),
5818
6323
  signature: "pending",
5819
- tools: []
6324
+ tools
5820
6325
  };
5821
6326
  return {
5822
6327
  ...unsigned,
@@ -5835,10 +6340,16 @@ async function revokeAndForgetProfile() {
5835
6340
  });
5836
6341
  } finally {
5837
6342
  await forgetProfile();
6343
+ try {
6344
+ await clearBrowserOutboxes();
6345
+ } finally {
6346
+ try {
6347
+ await forgetIdentity();
6348
+ } finally {
6349
+ await setAutoConnectEnabled(false);
6350
+ }
6351
+ }
5838
6352
  }
5839
6353
  }
5840
- async function signBrowserBridgeChallenge(payload) {
5841
- return sign(await getOrCreateKeyPair(), payload);
5842
- }
5843
6354
 
5844
- export { PENDING_CONTEXT_KEY as P, TAB_BINDING_PREFIX as T, _enum as _, activeTabId as a, array as b, boolean as c, captureTabPage as d, captureTabWithPermission as e, clientEndpointMessageSchema as f, createBrowserEndpointHello as g, currentTabDescriptor as h, discoverLocalGateway as i, discriminatedUnion as j, endpointHelloPayloadSchema as k, gatewayFetch as l, getAccessProfile as m, literal as n, number as o, pairGateway as p, registerBrowserEndpoint as q, readProfile as r, revokeAndForgetProfile as s, serverEndpointMessageSchema as t, signBrowserBridgeChallenge as u, strictObject as v, string as w, unknown as x, uuid as y };
6355
+ export { ENDPOINT_PROTOCOL_VERSION as E, PENDING_CONTEXT_KEY as P, RealtimeClient as R, TAB_BINDING_PREFIX as T, activeTabId as a, captureTabWithPermission as b, canonicalJson as c, createBrowserEndpointHello as d, currentTabDescriptor as e, deleteBrowserOutbox as f, discoverLocalGateway as g, endpointToolContentSchema as h, gatewayFetch as i, getAccessProfile as j, readProfile as k, registerBrowserEndpoint as l, revokeAndForgetProfile as m, runWithTabSiteAccess as n, pairGateway as p, readBrowserOutbox as r, setAutoConnectEnabled as s, writeBrowserOutbox as w };