@opengeni/contracts 0.44.1 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/atlassian.js +10 -8
  2. package/dist/canonical-human-identities.d.ts +157 -0
  3. package/dist/canonical-human-identities.js +23 -0
  4. package/dist/canonical-human-identities.js.map +1 -0
  5. package/dist/chunk-CM6BMECR.js +24 -0
  6. package/dist/chunk-CM6BMECR.js.map +1 -0
  7. package/dist/{chunk-FJBG5E4O.js → chunk-JBCIY7QD.js} +12 -12
  8. package/dist/chunk-JUFK3T4Q.js +80 -0
  9. package/dist/chunk-JUFK3T4Q.js.map +1 -0
  10. package/dist/{chunk-H5BVCZKF.js → chunk-LW7OH6CS.js} +2 -2
  11. package/dist/{chunk-52SGB2QO.js → chunk-M5ZGRVIQ.js} +37 -6
  12. package/dist/chunk-M5ZGRVIQ.js.map +1 -0
  13. package/dist/{chunk-VA22KRHS.js → chunk-Q6A7XT2N.js} +6371 -4814
  14. package/dist/chunk-Q6A7XT2N.js.map +1 -0
  15. package/dist/codex-provider-account-authority.d.ts +23 -0
  16. package/dist/codex-provider-account-authority.js +23 -0
  17. package/dist/codex-provider-account-authority.js.map +1 -0
  18. package/dist/editable-artifact-codec-registry.js +5 -5
  19. package/dist/editable-artifact-live.js +3 -3
  20. package/dist/editable-artifact-serialized-commit.js +4 -4
  21. package/dist/editable-artifacts.js +27 -27
  22. package/dist/google-drive.js +11 -9
  23. package/dist/google-drive.js.map +1 -1
  24. package/dist/index.d.ts +1318 -128
  25. package/dist/index.js +400 -80
  26. package/dist/interaction.d.ts +1415 -20
  27. package/dist/knowledge.d.ts +543 -0
  28. package/dist/slack-task-policy.d.ts +294 -0
  29. package/dist/task-notes.d.ts +128 -0
  30. package/dist/video-generation.d.ts +48 -0
  31. package/dist/video-generation.js +9 -1
  32. package/dist/xai-provider-account-authority.d.ts +23 -0
  33. package/dist/xai-provider-account-authority.js +9 -0
  34. package/dist/xai-provider-account-authority.js.map +1 -0
  35. package/package.json +13 -1
  36. package/src/artifacts.ts +1 -1
  37. package/src/canonical-human-identities.ts +93 -0
  38. package/src/codex-provider-account-authority.ts +37 -0
  39. package/src/index.ts +731 -138
  40. package/src/interaction.ts +886 -5
  41. package/src/knowledge.ts +207 -0
  42. package/src/slack-task-policy.ts +215 -0
  43. package/src/task-notes.ts +92 -0
  44. package/src/video-generation.ts +43 -6
  45. package/src/xai-provider-account-authority.ts +37 -0
  46. package/dist/chunk-52SGB2QO.js.map +0 -1
  47. package/dist/chunk-VA22KRHS.js.map +0 -1
  48. /package/dist/{chunk-FJBG5E4O.js.map → chunk-JBCIY7QD.js.map} +0 -0
  49. /package/dist/{chunk-H5BVCZKF.js.map → chunk-LW7OH6CS.js.map} +0 -0
@@ -5,6 +5,7 @@ export const INTERACTION_PROTOCOL_VERSION = 1 as const;
5
5
  export const BROWSER_CONTROL_PROTOCOL_VERSION = INTERACTION_PROTOCOL_VERSION;
6
6
  export const BROWSER_CONTROL_WEBSOCKET_PROTOCOL = "opengeni.browser.v1" as const;
7
7
  export const COMPUTER_CONTROL_WEBSOCKET_PROTOCOL = "opengeni.computer.v1" as const;
8
+ export const COMPUTER_RFB_WEBSOCKET_PROTOCOL = "opengeni.computer.rfb.v1" as const;
8
9
  export const BROWSER_CONTROL_WEBSOCKET_BEARER_PREFIX = "opengeni.auth." as const;
9
10
  export const BROWSER_CONTROL_MAX_JSON_BYTES = 40 * 1024 * 1024;
10
11
  export const BROWSER_CONTROL_MAX_FRAME_HEADER_BYTES = 64 * 1024;
@@ -16,6 +17,28 @@ export const INTERACTION_MAX_SEMANTIC_NODES = 10_000;
16
17
  export const INTERACTION_MAX_CHANGED_NODES = 2_000;
17
18
  export const INTERACTION_MAX_DIAGNOSTIC_ENTRIES = 1_000;
18
19
  export const INTERACTION_MAX_ACTIONS_PER_BATCH = 32;
20
+ export const INTERACTION_MAX_WORKSPACE_FILES_PER_COMMAND = 100;
21
+ export const INTERACTION_MAX_CLIPBOARD_BYTES = 1024 * 1024;
22
+
23
+ /**
24
+ * Latest-wins workspace invalidation for Browser/Computer resources. This is
25
+ * deliberately a cursor snapshot rather than a durable event-log entry:
26
+ * consumers refetch authoritative resource lists whenever `revision` advances.
27
+ */
28
+ export const WorkspaceInteractionRevisionEvent = z
29
+ .object({
30
+ workspaceId: z.string().uuid(),
31
+ sequence: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
32
+ revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
33
+ type: z.literal("workspace.interaction.changed"),
34
+ occurredAt: z.string().datetime({ offset: true }),
35
+ })
36
+ .strict()
37
+ .refine((event) => event.sequence === event.revision, {
38
+ message: "interaction event sequence must equal revision",
39
+ path: ["sequence"],
40
+ });
41
+ export type WorkspaceInteractionRevisionEvent = z.infer<typeof WorkspaceInteractionRevisionEvent>;
19
42
 
20
43
  const opaqueGeneration = z
21
44
  .string()
@@ -168,6 +191,7 @@ export const BrowserSessionCapabilities = z
168
191
  downloads: z.boolean(),
169
192
  uploads: z.boolean(),
170
193
  clipboard: z.boolean(),
194
+ permissions: z.boolean(),
171
195
  diagnostics: z.boolean(),
172
196
  rawCdp: z.boolean(),
173
197
  linkedComputer: z.boolean(),
@@ -205,6 +229,48 @@ export const BrowserSession = z
205
229
  .strict();
206
230
  export type BrowserSession = z.infer<typeof BrowserSession>;
207
231
 
232
+ /** Controller-private, BrowserSession-scoped clipboard. This is deliberately
233
+ * distinct from a ComputerSession/host OS clipboard so concurrent browsers do
234
+ * not exchange ambient machine state. */
235
+ export const BrowserClipboard = z
236
+ .object({
237
+ browserSessionId: z.string().uuid(),
238
+ controllerGeneration: opaqueGeneration,
239
+ revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
240
+ text: z
241
+ .string()
242
+ .max(INTERACTION_MAX_CLIPBOARD_BYTES)
243
+ .refine(
244
+ (value) => new TextEncoder().encode(value).byteLength <= INTERACTION_MAX_CLIPBOARD_BYTES,
245
+ { message: "browser clipboard text exceeds its UTF-8 byte envelope" },
246
+ ),
247
+ source: z.enum(["empty", "write", "clear", "copy", "paste"]),
248
+ sourceTargetId: boundedOpaqueId.nullable(),
249
+ updatedAt: z.string().datetime({ offset: true }).nullable(),
250
+ })
251
+ .strict()
252
+ .superRefine((clipboard, context) => {
253
+ if (clipboard.revision === 0) {
254
+ if (
255
+ clipboard.text !== "" ||
256
+ clipboard.source !== "empty" ||
257
+ clipboard.sourceTargetId !== null ||
258
+ clipboard.updatedAt !== null
259
+ ) {
260
+ context.addIssue({
261
+ code: "custom",
262
+ message: "initial browser clipboard state must be empty",
263
+ });
264
+ }
265
+ } else if (clipboard.source === "empty" || clipboard.updatedAt === null) {
266
+ context.addIssue({
267
+ code: "custom",
268
+ message: "updated browser clipboard state requires a source and timestamp",
269
+ });
270
+ }
271
+ });
272
+ export type BrowserClipboard = z.infer<typeof BrowserClipboard>;
273
+
208
274
  /** One live Chrome-profile bridge installed on an enrolled machine. This is a
209
275
  * transport endpoint, not saved browser/login state: BrowserIdentity remains
210
276
  * the immutable, reusable state abstraction. */
@@ -444,6 +510,7 @@ export const BrowserIdentity = z
444
510
  workspaceId: z.string().uuid(),
445
511
  name: z.string().trim().min(1).max(200),
446
512
  status: BrowserIdentityStatus,
513
+ version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
447
514
  defaultRevisionId: z.string().uuid().nullable(),
448
515
  headGeneration: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
449
516
  revisionCount: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
@@ -521,6 +588,93 @@ export const NetworkRouteConsistency = z
521
588
  .strict();
522
589
  export type NetworkRouteConsistency = z.infer<typeof NetworkRouteConsistency>;
523
590
 
591
+ export function interactionPlacementsEqual(
592
+ left: InteractionPlacement,
593
+ right: InteractionPlacement,
594
+ ): boolean {
595
+ if (left.kind !== right.kind) return false;
596
+ switch (left.kind) {
597
+ case "sandbox_group":
598
+ return right.kind === "sandbox_group" && left.sandboxGroupId === right.sandboxGroupId;
599
+ case "connected_machine":
600
+ return right.kind === "connected_machine" && left.sandboxId === right.sandboxId;
601
+ case "attached_device":
602
+ return right.kind === "attached_device" && left.deviceId === right.deviceId;
603
+ case "external_provider":
604
+ return (
605
+ right.kind === "external_provider" &&
606
+ left.providerId === right.providerId &&
607
+ left.placementId === right.placementId
608
+ );
609
+ }
610
+ }
611
+
612
+ /** One secret-free compatibility rule shared by configuration, persistence,
613
+ * and launch. `null` placement means the ordinary managed sandbox default. */
614
+ export function networkRoutePlacementCompatibilityIssue(
615
+ configuration: NetworkRouteConfiguration,
616
+ consistency: NetworkRouteConsistency,
617
+ placement: InteractionPlacement | null,
618
+ ): string | null {
619
+ if (configuration.kind === "tunnel") {
620
+ if (!placement || !interactionPlacementsEqual(configuration.placement, placement)) {
621
+ return "Tunnel route is bound to another placement";
622
+ }
623
+ }
624
+ if (configuration.kind === "managed") {
625
+ if (placement?.kind !== "external_provider") {
626
+ return "Managed NetworkRoutes require an external browser provider placement";
627
+ }
628
+ if (configuration.providerId !== placement.providerId) {
629
+ return "Managed NetworkRoute belongs to another external browser provider";
630
+ }
631
+ if (configuration.credential !== null) {
632
+ return "Managed provider routes cannot use a separate proxy credential";
633
+ }
634
+ if (consistency.dns !== "provider") {
635
+ return "Managed provider routes require provider DNS";
636
+ }
637
+ if (configuration.providerId === "browserbase") {
638
+ if (configuration.routeId !== "default" || configuration.egressClass !== "residential") {
639
+ return "Browserbase supports only its default managed residential route";
640
+ }
641
+ if (configuration.region !== null && !/^[A-Za-z]{2}$/u.test(configuration.region)) {
642
+ return "Browserbase managed route region must be a two-letter country code";
643
+ }
644
+ if (consistency.stability !== "session") {
645
+ return "Browserbase managed routing cannot promise a stable IP across sessions";
646
+ }
647
+ } else if (configuration.providerId !== "kernel") {
648
+ return `Managed NetworkRoute provider ${configuration.providerId} is unsupported`;
649
+ }
650
+ return null;
651
+ }
652
+ if (placement?.kind === "external_provider") {
653
+ return "External browser providers require a provider-managed NetworkRoute";
654
+ }
655
+ const expectedDns = configuration.kind === "proxy" ? "proxy" : "placement";
656
+ if (consistency.dns !== expectedDns) {
657
+ return `Network route ${configuration.kind} cannot provide ${consistency.dns} DNS`;
658
+ }
659
+ if (consistency.webRtc === "proxy_only" && configuration.kind !== "proxy") {
660
+ return "WebRTC proxy-only routing requires a proxy network route";
661
+ }
662
+ if (placement?.kind === "attached_device") {
663
+ if (configuration.kind === "proxy") {
664
+ return "Attached Chrome cannot change its process-scoped proxy configuration";
665
+ }
666
+ if (
667
+ consistency.locale !== null ||
668
+ consistency.timezone !== null ||
669
+ consistency.geolocation !== null ||
670
+ consistency.webRtc !== "default"
671
+ ) {
672
+ return "Attached Chrome cannot change process-scoped route emulation";
673
+ }
674
+ }
675
+ return null;
676
+ }
677
+
524
678
  export const NetworkRoute = z
525
679
  .object({
526
680
  id: z.string().uuid(),
@@ -637,6 +791,9 @@ export const SiteAuthAuthority = z.discriminatedUnion("kind", [
637
791
  kind: z.literal("external_provider"),
638
792
  label: z.string().trim().min(1).max(200),
639
793
  adapterId: boundedOpaqueId,
794
+ /** Opaque provider-owned auth-connection reference. It is meaningful
795
+ * only to the named adapter and never treated as credential material. */
796
+ connectionId: boundedOpaqueId,
640
797
  credential: InteractionCredentialAuthorityRef.nullable(),
641
798
  })
642
799
  .strict(),
@@ -673,6 +830,22 @@ export type SiteAuthHealthPolicy = z.infer<typeof SiteAuthHealthPolicy>;
673
830
  export const SiteAuthVerificationState = z.enum(["unknown", "verified", "needs_repair", "failed"]);
674
831
  export type SiteAuthVerificationState = z.infer<typeof SiteAuthVerificationState>;
675
832
 
833
+ export const SiteAuthMaintenance = z
834
+ .object({
835
+ action: z.enum(["health_check", "repair"]),
836
+ /** Hidden until the durable session start has been confirmed. */
837
+ sessionId: z.string().uuid().nullable(),
838
+ dueAt: z.string().datetime({ offset: true }),
839
+ startedAt: z.string().datetime({ offset: true }).nullable(),
840
+ })
841
+ .strict();
842
+ export type SiteAuthMaintenance = z.infer<typeof SiteAuthMaintenance>;
843
+
844
+ /** Trusted service provenance for scheduler-authored maintenance sessions. */
845
+ export const SITE_AUTH_MAINTENANCE_OPERATION_CONTEXT_KEY =
846
+ "opengeniSiteAuthMaintenanceOperationId" as const;
847
+ export const SITE_AUTH_MAINTENANCE_CONNECTION_CONTEXT_KEY = "opengeniSiteAuthConnectionId" as const;
848
+
676
849
  const SiteAuthConnectionConfiguration = z
677
850
  .object({
678
851
  name: z.string().trim().min(1).max(200),
@@ -693,6 +866,17 @@ function validateSiteAuthConfiguration(
693
866
  connection: z.infer<typeof SiteAuthConnectionConfiguration>,
694
867
  context: z.RefinementCtx,
695
868
  ): void {
869
+ if (
870
+ connection.preferredIdentityId &&
871
+ (connection.preferredPlacement?.kind === "attached_device" ||
872
+ connection.preferredPlacement?.kind === "external_provider")
873
+ ) {
874
+ context.addIssue({
875
+ code: "custom",
876
+ path: ["preferredIdentityId"],
877
+ message: "the preferred placement owns its live profile identity",
878
+ });
879
+ }
696
880
  if (new Set(connection.origins).size !== connection.origins.length) {
697
881
  context.addIssue({ code: "custom", path: ["origins"], message: "origins repeat" });
698
882
  }
@@ -767,6 +951,9 @@ export const SiteAuthConnection = SiteAuthConnectionConfiguration.extend({
767
951
  verificationState: SiteAuthVerificationState,
768
952
  lastVerifiedAt: z.string().datetime({ offset: true }).nullable(),
769
953
  lastVerifiedUrl: boundedUrl.nullable(),
954
+ lastCheckedAt: z.string().datetime({ offset: true }).nullable(),
955
+ nextCheckAt: z.string().datetime({ offset: true }).nullable(),
956
+ maintenance: SiteAuthMaintenance.nullable(),
770
957
  repairCode: boundedOpaqueId.nullable(),
771
958
  version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
772
959
  createdBySubjectId: z.string().min(1).max(1_024),
@@ -862,6 +1049,7 @@ export const AuthRun = z
862
1049
  controllerGeneration: opaqueGeneration,
863
1050
  targetGeneration: opaqueGeneration,
864
1051
  documentGeneration: opaqueGeneration.nullable(),
1052
+ purpose: z.enum(["authenticate", "health_check", "repair"]),
865
1053
  methodId: boundedOpaqueId.nullable(),
866
1054
  authorityId: boundedOpaqueId.nullable(),
867
1055
  state: AuthRunState,
@@ -945,15 +1133,15 @@ export const StartAuthRunRequest = z
945
1133
  targetId: boundedOpaqueId,
946
1134
  expectedTargetGeneration: opaqueGeneration,
947
1135
  expectedDocumentGeneration: opaqueGeneration.nullable(),
1136
+ purpose: z.enum(["authenticate", "health_check", "repair"]).optional(),
948
1137
  methodId: boundedOpaqueId.optional(),
949
1138
  authorityId: boundedOpaqueId.optional(),
950
1139
  })
951
1140
  .strict();
952
1141
  export type StartAuthRunRequest = z.infer<typeof StartAuthRunRequest>;
953
1142
 
954
- export const ReportAuthRunRequest = z
1143
+ export const ReportAuthRunPayload = z
955
1144
  .object({
956
- operationId: z.string().uuid(),
957
1145
  expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
958
1146
  methodId: boundedOpaqueId.optional(),
959
1147
  authorityId: boundedOpaqueId.optional(),
@@ -1001,6 +1189,11 @@ export const ReportAuthRunRequest = z
1001
1189
  });
1002
1190
  }
1003
1191
  });
1192
+ export type ReportAuthRunPayload = z.infer<typeof ReportAuthRunPayload>;
1193
+
1194
+ export const ReportAuthRunRequest = ReportAuthRunPayload.safeExtend({
1195
+ operationId: z.string().uuid(),
1196
+ });
1004
1197
  export type ReportAuthRunRequest = z.infer<typeof ReportAuthRunRequest>;
1005
1198
 
1006
1199
  export const ProtectedAuthField = z
@@ -1045,6 +1238,101 @@ export const ProtectedAuthFillResponse = z
1045
1238
  .strict();
1046
1239
  export type ProtectedAuthFillResponse = z.infer<typeof ProtectedAuthFillResponse>;
1047
1240
 
1241
+ /** Public request for advancing a provider-managed AuthRun. Provider secrets,
1242
+ * profile names, browser ids, and hosted-login URLs stay behind browserd. */
1243
+ export const ExternalAuthRunRequest = z
1244
+ .object({
1245
+ operationId: z.string().uuid(),
1246
+ expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
1247
+ action: z.enum(["start", "poll"]),
1248
+ })
1249
+ .strict();
1250
+ export type ExternalAuthRunRequest = z.infer<typeof ExternalAuthRunRequest>;
1251
+
1252
+ export const ExternalAuthRunResponse = z
1253
+ .object({
1254
+ run: AuthRun,
1255
+ status: z.enum(["working", "needs_human", "ready_to_verify", "failed"]),
1256
+ operationId: z.string().uuid(),
1257
+ replayed: z.boolean(),
1258
+ })
1259
+ .strict();
1260
+ export type ExternalAuthRunResponse = z.infer<typeof ExternalAuthRunResponse>;
1261
+
1262
+ /** Human-only request for opening the provider's ephemeral hosted login UI. */
1263
+ export const ExternalAuthInteractiveRequest = z
1264
+ .object({
1265
+ operationId: z.string().uuid(),
1266
+ expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
1267
+ })
1268
+ .strict();
1269
+ export type ExternalAuthInteractiveRequest = z.infer<typeof ExternalAuthInteractiveRequest>;
1270
+
1271
+ export const ExternalAuthInteractiveResponse = z
1272
+ .object({
1273
+ authRunId: z.string().uuid(),
1274
+ url: boundedHttpUrl,
1275
+ expiresAt: z.string().datetime({ offset: true }).nullable(),
1276
+ })
1277
+ .strict();
1278
+ export type ExternalAuthInteractiveResponse = z.infer<typeof ExternalAuthInteractiveResponse>;
1279
+
1280
+ /** Controller-private Kernel/host-adapter command. This crosses only the
1281
+ * authenticated API -> browserd control channel; it is not a model tool. */
1282
+ export const BrowserExternalAuthCommand = z
1283
+ .object({
1284
+ browserSessionId: z.string().uuid(),
1285
+ controllerGeneration: opaqueGeneration,
1286
+ operationId: z.string().uuid(),
1287
+ authRunId: z.string().uuid(),
1288
+ adapterId: boundedOpaqueId,
1289
+ connectionId: boundedOpaqueId,
1290
+ action: z.enum(["start", "poll", "interactive"]),
1291
+ })
1292
+ .strict();
1293
+ export type BrowserExternalAuthCommand = z.infer<typeof BrowserExternalAuthCommand>;
1294
+
1295
+ export const BrowserExternalAuthResult = z
1296
+ .object({
1297
+ state: z.enum(["authenticated", "in_progress", "needs_human", "failed"]),
1298
+ externalAction: AuthRunExternalAction.nullable(),
1299
+ interactiveUrl: boundedHttpUrl.nullable(),
1300
+ failureCode: boundedOpaqueId.nullable(),
1301
+ profileLoaded: z.boolean(),
1302
+ })
1303
+ .strict()
1304
+ .superRefine((result, context) => {
1305
+ if ((result.state === "needs_human") !== (result.externalAction !== null)) {
1306
+ context.addIssue({
1307
+ code: "custom",
1308
+ path: ["externalAction"],
1309
+ message: "human external-auth state requires an action",
1310
+ });
1311
+ }
1312
+ if ((result.state === "failed") !== (result.failureCode !== null)) {
1313
+ context.addIssue({
1314
+ code: "custom",
1315
+ path: ["failureCode"],
1316
+ message: "failed external-auth state requires a failure code",
1317
+ });
1318
+ }
1319
+ if (result.interactiveUrl !== null && result.state !== "needs_human") {
1320
+ context.addIssue({
1321
+ code: "custom",
1322
+ path: ["interactiveUrl"],
1323
+ message: "interactive URL requires a human external-auth state",
1324
+ });
1325
+ }
1326
+ if (result.profileLoaded && result.state !== "authenticated") {
1327
+ context.addIssue({
1328
+ code: "custom",
1329
+ path: ["profileLoaded"],
1330
+ message: "only authenticated external auth can load a profile",
1331
+ });
1332
+ }
1333
+ });
1334
+ export type BrowserExternalAuthResult = z.infer<typeof BrowserExternalAuthResult>;
1335
+
1048
1336
  export const VerifyAuthRunRequest = z
1049
1337
  .object({
1050
1338
  operationId: z.string().uuid(),
@@ -1140,6 +1428,38 @@ export type InteractionInterventionMutationResponse = z.infer<
1140
1428
  typeof InteractionInterventionMutationResponse
1141
1429
  >;
1142
1430
 
1431
+ /** Canonical model/Codemode input for one typed browser/computer human wait. */
1432
+ export const INTERACTION_REQUEST_HUMAN_MODEL_TOOL_NAME = "interaction__interaction_request_human";
1433
+
1434
+ export const RequestHumanInteractionToolInput = z.discriminatedUnion("operation", [
1435
+ z.object({ operation: z.literal("wait"), interventionId: z.string().uuid() }).strict(),
1436
+ z
1437
+ .object({
1438
+ operation: z.literal("request"),
1439
+ resourceKind: z.enum(["browser_session", "computer_session"]),
1440
+ resourceId: z.string().uuid(),
1441
+ targetId: boundedOpaqueId,
1442
+ expectedControllerGeneration: opaqueGeneration,
1443
+ expectedTargetGeneration: opaqueGeneration,
1444
+ expectedDocumentGeneration: opaqueGeneration.nullable(),
1445
+ kind: z.enum(["manual_login", "mfa", "external_action", "confirmation", "other"]),
1446
+ reason: z.string().trim().min(1).max(2_048),
1447
+ authRunId: z.string().uuid().optional(),
1448
+ expiresInSeconds: z.number().int().min(30).max(86_400).default(900),
1449
+ })
1450
+ .strict(),
1451
+ ]);
1452
+ export type RequestHumanInteractionToolInput = z.infer<typeof RequestHumanInteractionToolInput>;
1453
+
1454
+ export const RequestHumanInteractionToolOutput = z
1455
+ .object({
1456
+ intervention: InteractionIntervention,
1457
+ observation: z.lazy(() => z.union([BrowserObservation, ComputerObservation])).nullable(),
1458
+ observationErrorCode: z.string().min(1).max(128).nullable(),
1459
+ })
1460
+ .strict();
1461
+ export type RequestHumanInteractionToolOutput = z.infer<typeof RequestHumanInteractionToolOutput>;
1462
+
1143
1463
  export const BrowserIdentityListResponse = z
1144
1464
  .object({
1145
1465
  revision: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
@@ -1164,6 +1484,24 @@ export const CreateBrowserIdentityRequest = z
1164
1484
  .strict();
1165
1485
  export type CreateBrowserIdentityRequest = z.infer<typeof CreateBrowserIdentityRequest>;
1166
1486
 
1487
+ export const UpdateBrowserIdentityRequest = z
1488
+ .object({
1489
+ operationId: z.string().uuid(),
1490
+ expectedVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
1491
+ name: z.string().trim().min(1).max(200).optional(),
1492
+ status: BrowserIdentityStatus.optional(),
1493
+ defaultRevisionId: z.string().uuid().optional(),
1494
+ })
1495
+ .strict()
1496
+ .refine(
1497
+ (value) =>
1498
+ value.name !== undefined ||
1499
+ value.status !== undefined ||
1500
+ value.defaultRevisionId !== undefined,
1501
+ { message: "browser identity update is empty" },
1502
+ );
1503
+ export type UpdateBrowserIdentityRequest = z.infer<typeof UpdateBrowserIdentityRequest>;
1504
+
1167
1505
  export const BrowserIdentityMutationResponse = z
1168
1506
  .object({
1169
1507
  identity: BrowserIdentity,
@@ -1212,6 +1550,155 @@ export const BrowserTarget = z
1212
1550
  .strict();
1213
1551
  export type BrowserTarget = z.infer<typeof BrowserTarget>;
1214
1552
 
1553
+ export const BrowserDownloadStatus = z.enum([
1554
+ "in_progress",
1555
+ "completed",
1556
+ "cancelled",
1557
+ "failed",
1558
+ "unavailable",
1559
+ ]);
1560
+ export type BrowserDownloadStatus = z.infer<typeof BrowserDownloadStatus>;
1561
+
1562
+ /** One browser-produced file. Its bytes remain private to the exact controller
1563
+ * until an explicit save publishes and materializes them. No placement path or
1564
+ * source URL crosses this contract. */
1565
+ export const BrowserDownload = z
1566
+ .object({
1567
+ id: z.string().uuid(),
1568
+ browserSessionId: z.string().uuid(),
1569
+ controllerGeneration: opaqueGeneration,
1570
+ targetId: boundedOpaqueId.nullable(),
1571
+ filename: z
1572
+ .string()
1573
+ .min(1)
1574
+ .max(4_096)
1575
+ .regex(/^[^\u0000-\u001f\u007f]+$/u),
1576
+ status: BrowserDownloadStatus,
1577
+ receivedBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
1578
+ totalBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).nullable(),
1579
+ sha256: sha256Hex.nullable(),
1580
+ version: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
1581
+ startedAt: z.string().datetime({ offset: true }),
1582
+ settledAt: z.string().datetime({ offset: true }).nullable(),
1583
+ failureCode: boundedOpaqueId.nullable(),
1584
+ })
1585
+ .strict()
1586
+ .superRefine((download, context) => {
1587
+ const terminal = download.status !== "in_progress";
1588
+ if (terminal !== (download.settledAt !== null)) {
1589
+ context.addIssue({
1590
+ code: "custom",
1591
+ path: ["settledAt"],
1592
+ message: "download settlement must match its terminal status",
1593
+ });
1594
+ }
1595
+ if (
1596
+ (download.status === "failed" || download.status === "unavailable") !==
1597
+ (download.failureCode !== null)
1598
+ ) {
1599
+ context.addIssue({
1600
+ code: "custom",
1601
+ path: ["failureCode"],
1602
+ message: "failed or unavailable downloads require one failure code",
1603
+ });
1604
+ }
1605
+ if (download.status === "completed") {
1606
+ if (download.sha256 === null || download.totalBytes !== download.receivedBytes) {
1607
+ context.addIssue({
1608
+ code: "custom",
1609
+ path: ["sha256"],
1610
+ message: "completed downloads require exact bytes and SHA-256",
1611
+ });
1612
+ }
1613
+ } else if (download.sha256 !== null) {
1614
+ context.addIssue({
1615
+ code: "custom",
1616
+ path: ["sha256"],
1617
+ message: "only completed downloads carry a SHA-256",
1618
+ });
1619
+ }
1620
+ });
1621
+ export type BrowserDownload = z.infer<typeof BrowserDownload>;
1622
+
1623
+ export const BrowserDownloadListResponse = z
1624
+ .object({
1625
+ browserSessionId: z.string().uuid(),
1626
+ controllerGeneration: opaqueGeneration,
1627
+ downloads: z.array(BrowserDownload).max(10_000),
1628
+ })
1629
+ .strict();
1630
+ export type BrowserDownloadListResponse = z.infer<typeof BrowserDownloadListResponse>;
1631
+
1632
+ const workspaceRelativeFilePath = z
1633
+ .string()
1634
+ .min(1)
1635
+ .max(4_096)
1636
+ .refine((value) => {
1637
+ if (value !== value.trim() || value.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(value)) {
1638
+ return false;
1639
+ }
1640
+ if (value.includes("\\") || value.includes("\0")) return false;
1641
+ const segments = value.split("/");
1642
+ return segments.every(
1643
+ (segment) =>
1644
+ segment.length > 0 &&
1645
+ segment !== "." &&
1646
+ segment !== ".." &&
1647
+ !/[<>:"|?*\u0000-\u001f\u007f]/u.test(segment) &&
1648
+ !/[ .]$/u.test(segment) &&
1649
+ !/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(segment),
1650
+ );
1651
+ }, "destinationPath must be a portable workspace-relative file path");
1652
+
1653
+ export const BrowserDownloadSaveRequest = z
1654
+ .object({
1655
+ operationId: z.string().uuid(),
1656
+ destinationPath: workspaceRelativeFilePath,
1657
+ overwrite: z.boolean().default(false),
1658
+ })
1659
+ .strict();
1660
+ export type BrowserDownloadSaveRequest = z.infer<typeof BrowserDownloadSaveRequest>;
1661
+
1662
+ export const BrowserDownloadSaveResponse = z
1663
+ .object({
1664
+ download: BrowserDownload,
1665
+ destinationPath: workspaceRelativeFilePath,
1666
+ fileId: z.string().uuid(),
1667
+ operationId: z.string().uuid(),
1668
+ replayed: z.boolean(),
1669
+ })
1670
+ .strict();
1671
+ export type BrowserDownloadSaveResponse = z.infer<typeof BrowserDownloadSaveResponse>;
1672
+
1673
+ /** Controller-private, narrow object authority for publishing one exact
1674
+ * completed download. Signed URLs and headers never appear in public SDK or
1675
+ * durable controller receipts. */
1676
+ export const BrowserDownloadExportRequest = z
1677
+ .object({
1678
+ operationId: z.string().uuid(),
1679
+ downloadId: z.string().uuid(),
1680
+ upload: z
1681
+ .object({
1682
+ url: boundedHttpUrl,
1683
+ requiredHeaders: z.record(z.string().min(1).max(256), z.string().max(8_192)),
1684
+ expiresAt: z.string().datetime({ offset: true }),
1685
+ })
1686
+ .strict(),
1687
+ })
1688
+ .strict();
1689
+ export type BrowserDownloadExportRequest = z.infer<typeof BrowserDownloadExportRequest>;
1690
+
1691
+ export const BrowserDownloadExportReceipt = z
1692
+ .object({
1693
+ operationId: z.string().uuid(),
1694
+ downloadId: z.string().uuid(),
1695
+ sizeBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
1696
+ sha256: sha256Hex,
1697
+ replayed: z.boolean(),
1698
+ })
1699
+ .strict();
1700
+ export type BrowserDownloadExportReceipt = z.infer<typeof BrowserDownloadExportReceipt>;
1701
+
1215
1702
  export const InteractionRect = z
1216
1703
  .object({
1217
1704
  x: z.number().finite(),
@@ -1417,6 +1904,26 @@ export const ComputerLocator = z.discriminatedUnion("kind", [
1417
1904
  ]);
1418
1905
  export type ComputerLocator = z.infer<typeof ComputerLocator>;
1419
1906
 
1907
+ /** Web-platform permissions which a managed browser can set for the exact
1908
+ * top-level origin currently fenced by a BrowserActionCommand. Names remain
1909
+ * provider-neutral; drivers map them to their native permission descriptors. */
1910
+ export const BrowserPermission = z.enum([
1911
+ "geolocation",
1912
+ "notifications",
1913
+ "camera",
1914
+ "microphone",
1915
+ "midi",
1916
+ "midi_sysex",
1917
+ "sensors",
1918
+ "idle_detection",
1919
+ "local_fonts",
1920
+ "window_management",
1921
+ ]);
1922
+ export type BrowserPermission = z.infer<typeof BrowserPermission>;
1923
+
1924
+ export const BrowserPermissionSetting = z.enum(["granted", "denied", "prompt"]);
1925
+ export type BrowserPermissionSetting = z.infer<typeof BrowserPermissionSetting>;
1926
+
1420
1927
  const browserActionVariants = [
1421
1928
  z.object({ type: z.literal("navigate"), url: boundedUrl }).strict(),
1422
1929
  z
@@ -1489,6 +1996,7 @@ const browserActionVariants = [
1489
1996
  deltaX: z.number().finite().min(-1_000_000).max(1_000_000).optional(),
1490
1997
  deltaY: z.number().finite().min(-1_000_000).max(1_000_000).optional(),
1491
1998
  button: z.enum(["left", "right", "middle"]).optional(),
1999
+ clickCount: z.union([z.literal(1), z.literal(2)]).optional(),
1492
2000
  })
1493
2001
  .strict()
1494
2002
  .superRefine((action, context) => {
@@ -1522,6 +2030,12 @@ const browserActionVariants = [
1522
2030
  message: "pointer deltas require scroll",
1523
2031
  });
1524
2032
  }
2033
+ if (action.action !== "click" && action.clickCount !== undefined) {
2034
+ context.addIssue({
2035
+ code: "custom",
2036
+ message: "pointer clickCount requires click",
2037
+ });
2038
+ }
1525
2039
  }),
1526
2040
  z
1527
2041
  .object({
@@ -1537,6 +2051,84 @@ const browserActionVariants = [
1537
2051
  workspaceFileIds: z.array(z.string().uuid()).min(1).max(100),
1538
2052
  })
1539
2053
  .strict(),
2054
+ z
2055
+ .object({
2056
+ type: z.literal("clipboard"),
2057
+ operation: z.enum(["write", "clear", "copy", "paste"]),
2058
+ text: z
2059
+ .string()
2060
+ .max(INTERACTION_MAX_CLIPBOARD_BYTES)
2061
+ .refine(
2062
+ (value) => new TextEncoder().encode(value).byteLength <= INTERACTION_MAX_CLIPBOARD_BYTES,
2063
+ { message: "browser clipboard text exceeds its UTF-8 byte envelope" },
2064
+ )
2065
+ .optional(),
2066
+ locator: BrowserLocator.optional(),
2067
+ content: z.enum(["selection", "value", "text"]).optional(),
2068
+ })
2069
+ .strict()
2070
+ .superRefine((action, context) => {
2071
+ if (action.operation === "write") {
2072
+ if (action.text === undefined) {
2073
+ context.addIssue({
2074
+ code: "custom",
2075
+ path: ["text"],
2076
+ message: "clipboard write requires text",
2077
+ });
2078
+ }
2079
+ if (action.locator !== undefined || action.content !== undefined) {
2080
+ context.addIssue({
2081
+ code: "custom",
2082
+ message: "clipboard write accepts only text",
2083
+ });
2084
+ }
2085
+ return;
2086
+ }
2087
+ if (action.operation === "clear") {
2088
+ if (
2089
+ action.text !== undefined ||
2090
+ action.locator !== undefined ||
2091
+ action.content !== undefined
2092
+ ) {
2093
+ context.addIssue({
2094
+ code: "custom",
2095
+ message: "clipboard clear accepts no payload",
2096
+ });
2097
+ }
2098
+ return;
2099
+ }
2100
+ if (action.operation === "copy") {
2101
+ if (action.text !== undefined) {
2102
+ context.addIssue({
2103
+ code: "custom",
2104
+ path: ["text"],
2105
+ message: "clipboard copy does not accept text",
2106
+ });
2107
+ }
2108
+ if (action.content !== undefined && action.content !== "selection" && !action.locator) {
2109
+ context.addIssue({
2110
+ code: "custom",
2111
+ path: ["locator"],
2112
+ message: "copying element value or text requires a locator",
2113
+ });
2114
+ }
2115
+ return;
2116
+ }
2117
+ if (action.content !== undefined) {
2118
+ context.addIssue({
2119
+ code: "custom",
2120
+ path: ["content"],
2121
+ message: "clipboard paste does not accept content",
2122
+ });
2123
+ }
2124
+ }),
2125
+ z
2126
+ .object({
2127
+ type: z.literal("permission"),
2128
+ permission: BrowserPermission,
2129
+ setting: BrowserPermissionSetting,
2130
+ })
2131
+ .strict(),
1540
2132
  z
1541
2133
  .object({
1542
2134
  type: z.literal("wait"),
@@ -1555,7 +2147,19 @@ export const BrowserActionBatch = z
1555
2147
  type: z.literal("batch"),
1556
2148
  actions: z.array(BrowserAction).min(1).max(INTERACTION_MAX_ACTIONS_PER_BATCH),
1557
2149
  })
1558
- .strict();
2150
+ .strict()
2151
+ .superRefine((batch, context) => {
2152
+ const fileIds = new Set(
2153
+ batch.actions.flatMap((action) => (action.type === "upload" ? action.workspaceFileIds : [])),
2154
+ );
2155
+ if (fileIds.size > INTERACTION_MAX_WORKSPACE_FILES_PER_COMMAND) {
2156
+ context.addIssue({
2157
+ code: "custom",
2158
+ path: ["actions"],
2159
+ message: "browser action references too many workspace files",
2160
+ });
2161
+ }
2162
+ });
1559
2163
  export type BrowserActionBatch = z.infer<typeof BrowserActionBatch>;
1560
2164
 
1561
2165
  export const InteractionError = z
@@ -1600,11 +2204,70 @@ export const BrowserActionCommand = z
1600
2204
  expectedDocumentGeneration: opaqueGeneration.nullable(),
1601
2205
  expectedFrameId: opaqueGeneration.nullable(),
1602
2206
  actor: InteractionActor,
2207
+ /** Human live-control surfaces already receive the resulting pixels. They
2208
+ * may omit the expensive semantic snapshot; agent actions keep it. */
2209
+ observationMode: z.enum(["full", "none"]).optional(),
1603
2210
  action: z.union([BrowserAction, BrowserActionBatch]),
1604
2211
  })
1605
2212
  .strict();
1606
2213
  export type BrowserActionCommand = z.infer<typeof BrowserActionCommand>;
1607
2214
 
2215
+ /** Controller-private authority used to materialize immutable workspace files
2216
+ * beside a BrowserSession before an upload action dispatches. Signed URLs and
2217
+ * placement paths never appear in the public BrowserAction or its receipt. */
2218
+ export const BrowserWorkspaceFileAuthority = z
2219
+ .object({
2220
+ fileId: z.string().uuid(),
2221
+ safeFilename: z
2222
+ .string()
2223
+ .min(1)
2224
+ .max(240)
2225
+ .regex(/^[A-Za-z0-9._ -]+$/u)
2226
+ .refine((value) => value !== "." && value !== "..", {
2227
+ message: "safe filename must be one path segment",
2228
+ }),
2229
+ sizeBytes: z.number().int().nonnegative().max(5_000_000_000),
2230
+ sha256: sha256Hex.nullable(),
2231
+ download: z
2232
+ .object({
2233
+ url: boundedHttpUrl,
2234
+ expiresAt: z.string().datetime({ offset: true }),
2235
+ })
2236
+ .strict(),
2237
+ })
2238
+ .strict();
2239
+ export type BrowserWorkspaceFileAuthority = z.infer<typeof BrowserWorkspaceFileAuthority>;
2240
+
2241
+ export const BrowserWorkspaceFileStageRequest = z
2242
+ .object({
2243
+ operationId: z.string().uuid(),
2244
+ files: z.array(BrowserWorkspaceFileAuthority).min(1).max(100),
2245
+ })
2246
+ .strict()
2247
+ .superRefine((request, context) => {
2248
+ const seen = new Set<string>();
2249
+ for (const [index, file] of request.files.entries()) {
2250
+ if (seen.has(file.fileId)) {
2251
+ context.addIssue({
2252
+ code: "custom",
2253
+ path: ["files", index, "fileId"],
2254
+ message: "workspace file authority is duplicated",
2255
+ });
2256
+ }
2257
+ seen.add(file.fileId);
2258
+ }
2259
+ });
2260
+ export type BrowserWorkspaceFileStageRequest = z.infer<typeof BrowserWorkspaceFileStageRequest>;
2261
+
2262
+ export const BrowserWorkspaceFileStageResponse = z
2263
+ .object({
2264
+ operationId: z.string().uuid(),
2265
+ fileIds: z.array(z.string().uuid()).min(1).max(100),
2266
+ replayed: z.boolean(),
2267
+ })
2268
+ .strict();
2269
+ export type BrowserWorkspaceFileStageResponse = z.infer<typeof BrowserWorkspaceFileStageResponse>;
2270
+
1608
2271
  export const InteractionOperationState = z.enum([
1609
2272
  "prepared",
1610
2273
  "dispatched",
@@ -1671,6 +2334,9 @@ export const CreateBrowserSessionRequest = z
1671
2334
  name: z.string().trim().min(1).max(200).optional(),
1672
2335
  initialUrl: boundedUrl.optional(),
1673
2336
  headless: z.boolean().default(true),
2337
+ /** Managed engine choice. Attached Chrome continues to derive its engine
2338
+ * from the selected device rather than accepting an impersonated value. */
2339
+ engine: z.enum(["chromium", "lightpanda"]).default("chromium"),
1674
2340
  placement: InteractionPlacement.optional(),
1675
2341
  identityId: z.string().uuid().optional(),
1676
2342
  baseRevisionId: z.string().uuid().optional(),
@@ -1693,12 +2359,42 @@ export const CreateBrowserSessionRequest = z
1693
2359
  message: "a linked computer requires a headed browser",
1694
2360
  });
1695
2361
  }
1696
- if (value.placement?.kind === "attached_device") {
2362
+ if (value.engine === "lightpanda") {
2363
+ if (!value.headless) {
2364
+ context.addIssue({
2365
+ code: "custom",
2366
+ path: ["headless"],
2367
+ message: "Lightpanda is a headless semantic browser",
2368
+ });
2369
+ }
2370
+ if (value.identityId || value.baseRevisionId) {
2371
+ context.addIssue({
2372
+ code: "custom",
2373
+ path: ["identityId"],
2374
+ message: "Lightpanda does not support Chromium browser identities",
2375
+ });
2376
+ }
2377
+ if (value.networkRouteId) {
2378
+ context.addIssue({
2379
+ code: "custom",
2380
+ path: ["networkRouteId"],
2381
+ message: "Lightpanda network routes are not supported yet",
2382
+ });
2383
+ }
1697
2384
  if (value.linkedComputerSessionId) {
1698
2385
  context.addIssue({
1699
2386
  code: "custom",
1700
2387
  path: ["linkedComputerSessionId"],
1701
- message: "attached Chrome does not expose an exact linked computer yet",
2388
+ message: "Lightpanda has no linked desktop window",
2389
+ });
2390
+ }
2391
+ }
2392
+ if (value.placement?.kind === "attached_device") {
2393
+ if (value.engine !== "chromium") {
2394
+ context.addIssue({
2395
+ code: "custom",
2396
+ path: ["engine"],
2397
+ message: "attached browser placement uses the selected Chrome engine",
1702
2398
  });
1703
2399
  }
1704
2400
  if (value.headless) {
@@ -1716,6 +2412,29 @@ export const CreateBrowserSessionRequest = z
1716
2412
  });
1717
2413
  }
1718
2414
  }
2415
+ if (value.placement?.kind === "external_provider") {
2416
+ if (value.engine !== "chromium") {
2417
+ context.addIssue({
2418
+ code: "custom",
2419
+ path: ["engine"],
2420
+ message: "external browser providers use their Chromium-compatible engine",
2421
+ });
2422
+ }
2423
+ if (value.identityId || value.baseRevisionId) {
2424
+ context.addIssue({
2425
+ code: "custom",
2426
+ path: ["identityId"],
2427
+ message: "external browser providers do not yet support portable BrowserIdentity state",
2428
+ });
2429
+ }
2430
+ if (value.linkedComputerSessionId) {
2431
+ context.addIssue({
2432
+ code: "custom",
2433
+ path: ["linkedComputerSessionId"],
2434
+ message: "external browser providers cannot link a placement desktop",
2435
+ });
2436
+ }
2437
+ }
1719
2438
  });
1720
2439
  export type CreateBrowserSessionRequest = z.infer<typeof CreateBrowserSessionRequest>;
1721
2440
 
@@ -1783,6 +2502,14 @@ const DirectInteractionFrameStreamAttachment = z
1783
2502
  })
1784
2503
  .strict();
1785
2504
 
2505
+ const DirectComputerRfbAttachment = z
2506
+ .object({
2507
+ kind: z.literal("direct_rfb"),
2508
+ url: boundedUrl,
2509
+ protocols: z.array(z.string().min(1).max(2_048)).length(3),
2510
+ })
2511
+ .strict();
2512
+
1786
2513
  function relayInteractionFrameStreamAttachment<const Kind extends 3 | 4>(kind: Kind) {
1787
2514
  return z
1788
2515
  .object({
@@ -1810,6 +2537,7 @@ export type BrowserFrameStreamAttachment = z.infer<typeof BrowserFrameStreamAtta
1810
2537
 
1811
2538
  export const ComputerFrameStreamAttachment = z.discriminatedUnion("kind", [
1812
2539
  DirectInteractionFrameStreamAttachment,
2540
+ DirectComputerRfbAttachment,
1813
2541
  relayInteractionFrameStreamAttachment(4),
1814
2542
  ]);
1815
2543
  export type ComputerFrameStreamAttachment = z.infer<typeof ComputerFrameStreamAttachment>;
@@ -1850,6 +2578,7 @@ export const BrowserActionRequest = z
1850
2578
  expectedTargetGeneration: opaqueGeneration,
1851
2579
  expectedDocumentGeneration: opaqueGeneration.nullable(),
1852
2580
  expectedFrameId: opaqueGeneration.nullable(),
2581
+ observationMode: z.enum(["full", "none"]).default("full"),
1853
2582
  action: z.union([BrowserAction, BrowserActionBatch]),
1854
2583
  })
1855
2584
  .strict();
@@ -1903,6 +2632,104 @@ export const BrowserActionReceipt = z
1903
2632
  });
1904
2633
  export type BrowserActionReceipt = z.infer<typeof BrowserActionReceipt>;
1905
2634
 
2635
+ /** Controller-private protected-fill wire contract. Secret values cross only
2636
+ * the credential broker -> exact placement controller boundary; this command
2637
+ * is never projected into model MCP, Codemode, public action history, or UI. */
2638
+ export const BrowserProtectedAuthFieldValue = ProtectedAuthField.extend({
2639
+ purpose: SiteAuthFieldPurpose,
2640
+ value: z.string().min(1).max(65_536),
2641
+ }).strict();
2642
+ export type BrowserProtectedAuthFieldValue = z.infer<typeof BrowserProtectedAuthFieldValue>;
2643
+
2644
+ export const BrowserProtectedAuthFillCommand = z
2645
+ .object({
2646
+ protocolVersion: z.literal(INTERACTION_PROTOCOL_VERSION),
2647
+ operationId: z.string().uuid(),
2648
+ browserSessionId: z.string().uuid(),
2649
+ controllerGeneration: opaqueGeneration,
2650
+ targetId: boundedOpaqueId,
2651
+ expectedTargetGeneration: opaqueGeneration,
2652
+ expectedDocumentGeneration: opaqueGeneration,
2653
+ expectedFrameId: opaqueGeneration,
2654
+ actor: InteractionActor,
2655
+ authorityId: boundedOpaqueId,
2656
+ credentialVersion: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
2657
+ allowedOrigins: z.array(canonicalWebOrigin).min(1).max(64),
2658
+ fields: z.array(BrowserProtectedAuthFieldValue).min(1).max(32),
2659
+ submit: ProtectedAuthSubmit,
2660
+ })
2661
+ .strict()
2662
+ .superRefine((command, context) => {
2663
+ if (new Set(command.allowedOrigins).size !== command.allowedOrigins.length) {
2664
+ context.addIssue({
2665
+ code: "custom",
2666
+ path: ["allowedOrigins"],
2667
+ message: "protected-fill origins repeat",
2668
+ });
2669
+ }
2670
+ const fieldIds = command.fields.map((field) => field.fieldId);
2671
+ if (new Set(fieldIds).size !== fieldIds.length) {
2672
+ context.addIssue({
2673
+ code: "custom",
2674
+ path: ["fields"],
2675
+ message: "protected-fill field ids repeat",
2676
+ });
2677
+ }
2678
+ });
2679
+ export type BrowserProtectedAuthFillCommand = z.infer<typeof BrowserProtectedAuthFillCommand>;
2680
+
2681
+ export const BrowserProtectedAuthObservation = z
2682
+ .object({
2683
+ target: BrowserTarget,
2684
+ status: z.enum(["submitted", "working"]),
2685
+ })
2686
+ .strict();
2687
+ export type BrowserProtectedAuthObservation = z.infer<typeof BrowserProtectedAuthObservation>;
2688
+
2689
+ /** Secret-free placement receipt. It can be journaled, replayed, and returned
2690
+ * to the broker without retaining any field value or model-visible page tree. */
2691
+ export const BrowserProtectedAuthFillReceipt = z
2692
+ .object({
2693
+ protocolVersion: z.literal(INTERACTION_PROTOCOL_VERSION),
2694
+ operationId: z.string().uuid(),
2695
+ browserSessionId: z.string().uuid(),
2696
+ controllerGeneration: opaqueGeneration,
2697
+ targetId: boundedOpaqueId,
2698
+ state: InteractionOperationState,
2699
+ dispatchedAt: z.string().datetime({ offset: true }).nullable(),
2700
+ settledAt: z.string().datetime({ offset: true }).nullable(),
2701
+ observation: BrowserProtectedAuthObservation.nullable(),
2702
+ error: InteractionError.nullable(),
2703
+ })
2704
+ .strict()
2705
+ .superRefine((receipt, context) => {
2706
+ if (
2707
+ receipt.state === "completed" &&
2708
+ (receipt.error !== null || receipt.observation === null || receipt.settledAt === null)
2709
+ ) {
2710
+ context.addIssue({
2711
+ code: "custom",
2712
+ message: "completed protected-fill receipt requires a result and no error",
2713
+ });
2714
+ }
2715
+ if (
2716
+ (receipt.state === "failed" || receipt.state === "outcome_unknown") &&
2717
+ (receipt.error === null || receipt.observation !== null || receipt.settledAt === null)
2718
+ ) {
2719
+ context.addIssue({
2720
+ code: "custom",
2721
+ message: "terminal protected-fill error requires error, no result, and time",
2722
+ });
2723
+ }
2724
+ if (receipt.state === "dispatched" && receipt.dispatchedAt === null) {
2725
+ context.addIssue({
2726
+ code: "custom",
2727
+ message: "dispatched protected-fill receipt requires dispatch time",
2728
+ });
2729
+ }
2730
+ });
2731
+ export type BrowserProtectedAuthFillReceipt = z.infer<typeof BrowserProtectedAuthFillReceipt>;
2732
+
1906
2733
  export const ComputerSessionCapabilities = z
1907
2734
  .object({
1908
2735
  semanticObservation: z.boolean(),
@@ -1913,12 +2740,43 @@ export const ComputerSessionCapabilities = z
1913
2740
  semanticActions: z.boolean(),
1914
2741
  pointerInput: z.boolean(),
1915
2742
  keyboardInput: z.boolean(),
2743
+ clipboard: z.boolean(),
1916
2744
  backgroundActions: z.boolean(),
1917
2745
  parallelApps: z.boolean(),
1918
2746
  })
1919
2747
  .strict();
1920
2748
  export type ComputerSessionCapabilities = z.infer<typeof ComputerSessionCapabilities>;
1921
2749
 
2750
+ /** A fresh bounded read of the native clipboard for the ComputerSession's
2751
+ * graphical seat. Physical ComputerSessions on the same login seat intentionally
2752
+ * observe the same OS clipboard; BrowserSession private clipboards never do. */
2753
+ export const ComputerClipboard = z
2754
+ .object({
2755
+ computerSessionId: z.string().uuid(),
2756
+ controllerGeneration: opaqueGeneration,
2757
+ text: z
2758
+ .string()
2759
+ .max(INTERACTION_MAX_CLIPBOARD_BYTES)
2760
+ .refine(
2761
+ (value) => new TextEncoder().encode(value).byteLength <= INTERACTION_MAX_CLIPBOARD_BYTES,
2762
+ { message: "computer clipboard text exceeds its UTF-8 byte envelope" },
2763
+ )
2764
+ .nullable(),
2765
+ truncated: z.boolean(),
2766
+ observedAt: z.string().datetime({ offset: true }),
2767
+ })
2768
+ .strict()
2769
+ .superRefine((clipboard, context) => {
2770
+ if (clipboard.text === null && clipboard.truncated) {
2771
+ context.addIssue({
2772
+ code: "custom",
2773
+ path: ["truncated"],
2774
+ message: "an unavailable computer clipboard value cannot be truncated",
2775
+ });
2776
+ }
2777
+ });
2778
+ export type ComputerClipboard = z.infer<typeof ComputerClipboard>;
2779
+
1922
2780
  export const ComputerSession = z
1923
2781
  .object({
1924
2782
  id: z.string().uuid(),
@@ -2079,6 +2937,29 @@ export const ComputerAction = z.discriminatedUnion("type", [
2079
2937
  value: z.string().max(1_000_000),
2080
2938
  })
2081
2939
  .strict(),
2940
+ z
2941
+ .object({
2942
+ type: z.literal("clipboard"),
2943
+ operation: z.enum(["write", "clear", "copy", "paste"]),
2944
+ text: z
2945
+ .string()
2946
+ .max(INTERACTION_MAX_CLIPBOARD_BYTES)
2947
+ .refine(
2948
+ (value) => new TextEncoder().encode(value).byteLength <= INTERACTION_MAX_CLIPBOARD_BYTES,
2949
+ { message: "computer clipboard text exceeds its UTF-8 byte envelope" },
2950
+ )
2951
+ .optional(),
2952
+ })
2953
+ .strict()
2954
+ .superRefine((action, context) => {
2955
+ if ((action.operation === "write") !== (action.text !== undefined)) {
2956
+ context.addIssue({
2957
+ code: "custom",
2958
+ path: ["text"],
2959
+ message: "computer clipboard text is required exactly for write",
2960
+ });
2961
+ }
2962
+ }),
2082
2963
  z.object({ type: z.literal("focus"), targetId: boundedOpaqueId }).strict(),
2083
2964
  z
2084
2965
  .object({