@opengeni/contracts 0.2.0 → 0.4.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.
package/dist/index.js CHANGED
@@ -8,7 +8,348 @@ var SessionStatus = z.enum([
8
8
  "failed",
9
9
  "cancelled"
10
10
  ]);
11
- var SandboxBackend = z.enum(["docker", "modal", "local", "none"]);
11
+ var SandboxBackend = z.enum([
12
+ "docker",
13
+ "modal",
14
+ "local",
15
+ "none",
16
+ "daytona",
17
+ "runloop",
18
+ "e2b",
19
+ "blaxel",
20
+ "cloudflare",
21
+ "vercel",
22
+ "selfhosted"
23
+ ]);
24
+ var SandboxOs = z.enum(["linux", "macos", "windows"]);
25
+ var SandboxCapabilityName = z.enum([
26
+ "FileSystem",
27
+ // Channel A: list/read/write/search (Pierre tree)
28
+ "Terminal",
29
+ // Channel A: command-output firehose (+ future pty-ws)
30
+ "Git",
31
+ // Channel A: status/diff/log/show (Pierre diff)
32
+ "DesktopStream",
33
+ // Channel B: noVNC pixels over a scoped tunnel URL
34
+ "Recording"
35
+ // ffmpeg x11grab -> object storage
36
+ ]);
37
+ var DESKTOP_STREAM_PORT = 6080;
38
+ var TERMINAL_STREAM_PORT = 7681;
39
+ var CAPABILITY_DESCRIPTORS = {
40
+ modal: {
41
+ backend: "modal",
42
+ backendId: "modal",
43
+ tier: "desktop",
44
+ os: { supported: ["linux"], default: "linux" },
45
+ capabilities: {
46
+ FileSystem: { available: true, readOnly: false },
47
+ Terminal: { available: true, transport: "sse-events", pty: true },
48
+ Git: { available: true },
49
+ DesktopStream: { available: true, transport: "vnc-ws" },
50
+ Recording: { available: true }
51
+ },
52
+ lifetime: {
53
+ hardLifetimeMs: 24 * 60 * 60 * 1e3,
54
+ requiresSnapshotRollover: true,
55
+ hasIdleKiller: true,
56
+ supportsSuspendResume: true,
57
+ resumeIsLockFree: true
58
+ },
59
+ snapshot: { kind: "native-fs", hasTarFallback: true },
60
+ portExposure: { kind: "provider-tunnel", supportsOnDemandPorts: false },
61
+ // pre-declare 6080
62
+ workspaceRoot: "/workspace",
63
+ nativeBucketMount: true,
64
+ persistable: true,
65
+ supportsRunAs: true
66
+ },
67
+ daytona: {
68
+ backend: "daytona",
69
+ backendId: "daytona",
70
+ tier: "desktop",
71
+ os: { supported: ["linux"], default: "linux" },
72
+ capabilities: {
73
+ FileSystem: { available: true, readOnly: false },
74
+ Terminal: { available: true, transport: "sse-events", pty: true },
75
+ Git: { available: true },
76
+ DesktopStream: { available: true, transport: "vnc-ws" },
77
+ Recording: { available: true }
78
+ },
79
+ lifetime: {
80
+ requiresSnapshotRollover: false,
81
+ hasIdleKiller: true,
82
+ supportsSuspendResume: true,
83
+ resumeIsLockFree: false
84
+ },
85
+ snapshot: { kind: "native-snapshot-id", hasTarFallback: true },
86
+ portExposure: { kind: "preview-url", supportsOnDemandPorts: false },
87
+ workspaceRoot: "/workspace",
88
+ nativeBucketMount: false,
89
+ persistable: true,
90
+ supportsRunAs: true
91
+ },
92
+ runloop: {
93
+ backend: "runloop",
94
+ backendId: "runloop",
95
+ tier: "desktop",
96
+ os: { supported: ["linux"], default: "linux" },
97
+ capabilities: {
98
+ FileSystem: { available: true, readOnly: false },
99
+ Terminal: { available: true, transport: "sse-events", pty: false },
100
+ Git: { available: true },
101
+ DesktopStream: { available: true, transport: "vnc-ws" },
102
+ Recording: { available: true }
103
+ },
104
+ lifetime: {
105
+ requiresSnapshotRollover: false,
106
+ hasIdleKiller: true,
107
+ supportsSuspendResume: true,
108
+ resumeIsLockFree: false
109
+ },
110
+ snapshot: { kind: "native-snapshot-id", hasTarFallback: true },
111
+ portExposure: { kind: "provider-tunnel", supportsOnDemandPorts: false },
112
+ // CR9: pre-declare 6080
113
+ workspaceRoot: "/workspace",
114
+ nativeBucketMount: false,
115
+ persistable: true,
116
+ supportsRunAs: false
117
+ },
118
+ e2b: {
119
+ backend: "e2b",
120
+ backendId: "e2b",
121
+ tier: "desktop",
122
+ os: { supported: ["linux"], default: "linux" },
123
+ capabilities: {
124
+ FileSystem: { available: true, readOnly: false },
125
+ Terminal: { available: true, transport: "sse-events", pty: false },
126
+ // pty-until-proven=no
127
+ Git: { available: true },
128
+ DesktopStream: { available: true, transport: "vnc-ws" },
129
+ Recording: { available: true }
130
+ },
131
+ lifetime: {
132
+ requiresSnapshotRollover: false,
133
+ hasIdleKiller: true,
134
+ supportsSuspendResume: true,
135
+ resumeIsLockFree: false
136
+ },
137
+ snapshot: { kind: "native-snapshot-id", hasTarFallback: true },
138
+ portExposure: { kind: "preview-url", supportsOnDemandPorts: false },
139
+ workspaceRoot: "/home/user",
140
+ nativeBucketMount: false,
141
+ persistable: true,
142
+ supportsRunAs: false
143
+ },
144
+ blaxel: {
145
+ backend: "blaxel",
146
+ backendId: "blaxel",
147
+ tier: "desktop",
148
+ os: { supported: ["linux"], default: "linux" },
149
+ capabilities: {
150
+ FileSystem: { available: true, readOnly: false },
151
+ Terminal: { available: true, transport: "sse-events", pty: false },
152
+ // pty-until-proven=no
153
+ Git: { available: true },
154
+ DesktopStream: { available: true, transport: "vnc-ws" },
155
+ Recording: { available: true }
156
+ },
157
+ lifetime: {
158
+ requiresSnapshotRollover: false,
159
+ hasIdleKiller: true,
160
+ supportsSuspendResume: false,
161
+ resumeIsLockFree: false
162
+ },
163
+ snapshot: { kind: "tar-only", hasTarFallback: true },
164
+ portExposure: { kind: "provider-tunnel", supportsOnDemandPorts: true },
165
+ // only on-demand backend
166
+ workspaceRoot: "/workspace",
167
+ nativeBucketMount: false,
168
+ persistable: true,
169
+ supportsRunAs: false
170
+ },
171
+ cloudflare: {
172
+ backend: "cloudflare",
173
+ backendId: "cloudflare",
174
+ tier: "headless",
175
+ os: { supported: ["linux"], default: "linux" },
176
+ capabilities: {
177
+ FileSystem: { available: true, readOnly: false },
178
+ Terminal: { available: true, transport: "sse-events", pty: true },
179
+ Git: { available: true },
180
+ DesktopStream: { available: false, transport: null },
181
+ Recording: { available: false }
182
+ },
183
+ lifetime: {
184
+ requiresSnapshotRollover: false,
185
+ hasIdleKiller: true,
186
+ supportsSuspendResume: false,
187
+ resumeIsLockFree: false
188
+ },
189
+ snapshot: { kind: "tar-only", hasTarFallback: true },
190
+ portExposure: { kind: "provider-tunnel", supportsOnDemandPorts: false },
191
+ workspaceRoot: "/workspace",
192
+ nativeBucketMount: false,
193
+ persistable: true,
194
+ supportsRunAs: true
195
+ },
196
+ vercel: {
197
+ backend: "vercel",
198
+ backendId: "vercel",
199
+ tier: "headless",
200
+ os: { supported: ["linux"], default: "linux" },
201
+ capabilities: {
202
+ FileSystem: { available: true, readOnly: false },
203
+ Terminal: { available: true, transport: "sse-events", pty: false },
204
+ Git: { available: true },
205
+ DesktopStream: { available: false, transport: null },
206
+ Recording: { available: false }
207
+ },
208
+ lifetime: {
209
+ hardLifetimeMs: 5 * 60 * 60 * 1e3,
210
+ requiresSnapshotRollover: true,
211
+ hasIdleKiller: true,
212
+ supportsSuspendResume: true,
213
+ resumeIsLockFree: false
214
+ },
215
+ snapshot: { kind: "tar-only", hasTarFallback: true },
216
+ portExposure: { kind: "preview-url", supportsOnDemandPorts: false },
217
+ workspaceRoot: "/vercel/sandbox",
218
+ nativeBucketMount: false,
219
+ persistable: true,
220
+ supportsRunAs: false
221
+ },
222
+ docker: {
223
+ backend: "docker",
224
+ backendId: "docker",
225
+ tier: "dev",
226
+ os: { supported: ["linux"], default: "linux" },
227
+ capabilities: {
228
+ FileSystem: { available: true, readOnly: false },
229
+ Terminal: { available: true, transport: "sse-events", pty: true },
230
+ Git: { available: true },
231
+ DesktopStream: { available: false, transport: null },
232
+ // local
233
+ Recording: { available: false }
234
+ },
235
+ lifetime: {
236
+ requiresSnapshotRollover: false,
237
+ hasIdleKiller: false,
238
+ supportsSuspendResume: false,
239
+ resumeIsLockFree: true
240
+ },
241
+ snapshot: { kind: "native-dir", hasTarFallback: true },
242
+ portExposure: { kind: "local-port", supportsOnDemandPorts: false },
243
+ workspaceRoot: "/workspace",
244
+ nativeBucketMount: false,
245
+ persistable: true,
246
+ supportsRunAs: true
247
+ },
248
+ local: {
249
+ backend: "local",
250
+ // The SDK's UnixLocalSandboxClient reports backendId "unix_local" — this MUST
251
+ // match it (it is the resume-fence field compared against client.backendId).
252
+ backendId: "unix_local",
253
+ tier: "dev",
254
+ os: { supported: ["linux"], default: "linux" },
255
+ capabilities: {
256
+ FileSystem: { available: true, readOnly: false },
257
+ Terminal: { available: true, transport: "sse-events", pty: true },
258
+ Git: { available: true },
259
+ DesktopStream: { available: false, transport: null },
260
+ Recording: { available: false }
261
+ },
262
+ lifetime: {
263
+ requiresSnapshotRollover: false,
264
+ hasIdleKiller: false,
265
+ supportsSuspendResume: false,
266
+ resumeIsLockFree: true
267
+ },
268
+ snapshot: { kind: "native-dir", hasTarFallback: true },
269
+ portExposure: { kind: "local-port", supportsOnDemandPorts: false },
270
+ workspaceRoot: "/workspace",
271
+ nativeBucketMount: false,
272
+ persistable: false,
273
+ supportsRunAs: false
274
+ },
275
+ none: {
276
+ backend: "none",
277
+ backendId: "none",
278
+ tier: "none",
279
+ os: { supported: ["linux"], default: "linux" },
280
+ capabilities: {
281
+ FileSystem: { available: false, readOnly: true },
282
+ Terminal: { available: false, transport: null, pty: false },
283
+ Git: { available: false },
284
+ DesktopStream: { available: false, transport: null },
285
+ Recording: { available: false }
286
+ },
287
+ lifetime: {
288
+ requiresSnapshotRollover: false,
289
+ hasIdleKiller: false,
290
+ supportsSuspendResume: false,
291
+ resumeIsLockFree: true
292
+ },
293
+ snapshot: { kind: "none", hasTarFallback: false },
294
+ portExposure: { kind: "none", supportsOnDemandPorts: false },
295
+ workspaceRoot: "/workspace",
296
+ nativeBucketMount: false,
297
+ persistable: false,
298
+ supportsRunAs: false
299
+ },
300
+ // Bring-your-own-compute: the user's OWN machine, enrolled via a Rust agent,
301
+ // becomes ONE shared whole-machine sandbox (the agent IS the box). It is the
302
+ // first backend to make macOS/Windows reachable (default linux). Desktop is
303
+ // capability-PROCLAIMED ("vnc-ws") — the agent serves a native display stack
304
+ // (Linux X11/Xvfb, macOS CGEvent/ScreenCaptureKit) consent-gated at enroll;
305
+ // the online/offline/consent/display negotiation lives in select.ts (M3), this
306
+ // row is the static feasibility ceiling. Always-on (process-lifetime, never
307
+ // idle-reaped) and NOT persistable — OpenGeni cannot snapshot the user's disk,
308
+ // so resume = "is the agent's subject live?", never a cold re-create. Ports
309
+ // surface on-demand through the stateless relay edge, which lands behind the
310
+ // `resolveExposedPort` swap-seam later; until then it reuses the existing
311
+ // `provider-tunnel` exposure kind (the relay IS the provider tunnel for the
312
+ // agent) so no new PortExposureKind literal — and no new switch arms — are
313
+ // introduced. supportsOnDemandPorts:true: the agent opens a stream channel for
314
+ // a port on request rather than pre-declaring 6080/7681 at construction.
315
+ selfhosted: {
316
+ backend: "selfhosted",
317
+ backendId: "selfhosted",
318
+ tier: "desktop",
319
+ os: { supported: ["linux", "macos", "windows"], default: "linux" },
320
+ capabilities: {
321
+ FileSystem: { available: true, readOnly: false },
322
+ Terminal: { available: true, transport: "pty-ws", pty: true },
323
+ // real PTY over the relay
324
+ Git: { available: true },
325
+ DesktopStream: { available: true, transport: "vnc-ws" },
326
+ // proclaimed; consent-gated at enroll
327
+ Recording: { available: true }
328
+ // boot invariant: == DesktopStream.available
329
+ },
330
+ lifetime: {
331
+ // Whole-machine, always-there: online while the agent process runs, offline
332
+ // when it stops. The lease is NEVER idle-killed (it's the user's machine,
333
+ // not a reapable cloud box) and there is nothing to suspend/resume — the
334
+ // machine simply is or isn't reachable.
335
+ requiresSnapshotRollover: false,
336
+ hasIdleKiller: false,
337
+ supportsSuspendResume: false,
338
+ resumeIsLockFree: true
339
+ // resume = address the live NATS subject; no provider lock
340
+ },
341
+ // persistable:false forces snapshot.kind:"none" (the descriptor invariant
342
+ // `persistable ⇒ snapshot.kind!=="none"`): OpenGeni cannot snapshot the
343
+ // user's disk — the machine itself is the persistence.
344
+ snapshot: { kind: "none", hasTarFallback: false },
345
+ portExposure: { kind: "provider-tunnel", supportsOnDemandPorts: true },
346
+ workspaceRoot: "/",
347
+ // agent-reported machine root (the whole machine is the sandbox)
348
+ nativeBucketMount: false,
349
+ persistable: false,
350
+ supportsRunAs: false
351
+ }
352
+ };
12
353
  var ReasoningEffort = z.enum(["none", "minimal", "low", "medium", "high", "xhigh"]);
13
354
  var ErrorCode = z.enum([
14
355
  "unauthenticated",
@@ -53,8 +394,28 @@ var Permission = z.enum([
53
394
  "sessions:create",
54
395
  "sessions:read",
55
396
  "sessions:control",
397
+ // Sandbox-surfacing (master-spine §C.3 / crosscut PART 1.2). stream:view is a
398
+ // REAL, distinct permission — strictly BROADER than sessions:read — because the
399
+ // pixel plane (Channel B) is UN-REDACTED: a viewer of raw pixels can see cloud
400
+ // creds the agent cat's into a terminal, which the redacted Channel-A event log
401
+ // never exposes. sessions:read is NOT permission to watch raw pixels.
402
+ "stream:view",
403
+ // SEPARATE from stream:view: raw input to the desktop (bypasses approvalQueue /
404
+ // interrupt). NEVER granted by default in v1 (the input plane is OFF —
405
+ // streamControlEnabled=false); the permission exists so later hardening is a
406
+ // flag flip, not a redesign.
407
+ "stream:control",
408
+ // Accept the pixel-plane secret-leak acknowledgment (consent gate before the
409
+ // un-redacted desktop URL is handed out).
410
+ "stream:acknowledge",
56
411
  "files:upload",
57
412
  "files:read",
413
+ // Channel-A structured write surface (FS writes / apply-patch); distinct from
414
+ // files:read so a read-only viewer can't mutate the box filesystem.
415
+ "files:write",
416
+ // Attach to an interactive PTY (terminal-as-pty, Channel A); distinct from
417
+ // sessions:read which only reads the command-output firehose.
418
+ "terminal:attach",
58
419
  "documents:manage",
59
420
  "documents:search",
60
421
  "scheduled_tasks:manage",
@@ -64,7 +425,14 @@ var Permission = z.enum([
64
425
  "api_keys:manage",
65
426
  "environments:manage",
66
427
  "environments:use",
67
- "goals:manage"
428
+ "goals:manage",
429
+ // Bring-your-own-compute (M5). enrollments:read lists a workspace's machines;
430
+ // enrollments:manage approves a device-flow enrollment (the LOUD whole-machine
431
+ // consent) + revokes a machine. Distinct from sessions/stream perms because an
432
+ // enrollment grants WHOLE-MACHINE access to a user's own hardware — a high-trust,
433
+ // admin-shaped action. workspace:admin is the super-wildcard over both.
434
+ "enrollments:read",
435
+ "enrollments:manage"
68
436
  ]);
69
437
  var ProductAccessMode = z.enum(["local", "configured", "managed"]);
70
438
  var BillingMode = z.enum(["disabled", "stripe"]);
@@ -157,6 +525,172 @@ async function verifyDelegatedAccessToken(secret, token, nowSeconds = Math.floor
157
525
  }
158
526
  return payload.data;
159
527
  }
528
+ var EnrollmentBearerPayload = z.object({
529
+ workspaceId: z.string().uuid(),
530
+ agentId: z.string().uuid(),
531
+ enrollmentId: z.string().uuid(),
532
+ // The Account-scoped control-plane subject prefix the agent subscribes to.
533
+ subjectPrefix: z.string().min(1),
534
+ exp: z.number().int().positive()
535
+ });
536
+ async function signEnrollmentBearer(secret, payload) {
537
+ const encodedPayload = base64UrlEncode(JSON.stringify(EnrollmentBearerPayload.parse(payload)));
538
+ const signature = await hmacSha256Base64Url(secret, encodedPayload);
539
+ return `oge_${encodedPayload}.${signature}`;
540
+ }
541
+ async function verifyEnrollmentBearer(secret, token, nowSeconds = Math.floor(Date.now() / 1e3)) {
542
+ if (!token.startsWith("oge_")) {
543
+ return null;
544
+ }
545
+ const withoutPrefix = token.slice("oge_".length);
546
+ const dot = withoutPrefix.lastIndexOf(".");
547
+ if (dot <= 0) {
548
+ return null;
549
+ }
550
+ const encodedPayload = withoutPrefix.slice(0, dot);
551
+ const signature = withoutPrefix.slice(dot + 1);
552
+ const expected = await hmacSha256Base64Url(secret, encodedPayload);
553
+ if (!constantTimeEqual(signature, expected)) {
554
+ return null;
555
+ }
556
+ const payload = EnrollmentBearerPayload.safeParse(JSON.parse(base64UrlDecode(encodedPayload)));
557
+ if (!payload.success || payload.data.exp < nowSeconds) {
558
+ return null;
559
+ }
560
+ return payload.data;
561
+ }
562
+ var EnrollTokenPayload = z.object({
563
+ // Domain-separation claim — fixed "enroll" so an `oge_`/`ogd_`/`ogs_` payload (no
564
+ // typ, or a different typ) can never satisfy verifyEnrollToken even past the prefix.
565
+ typ: z.literal("enroll"),
566
+ workspaceId: z.string().uuid(),
567
+ accountId: z.string().uuid(),
568
+ // The screen-control consent baked into the token at mint (the minting user's
569
+ // decision); the exchange records it as consentedScreenControl on the enrollment.
570
+ allowScreenControl: z.boolean(),
571
+ iat: z.number().int().nonnegative(),
572
+ exp: z.number().int().positive()
573
+ });
574
+ async function signEnrollToken(secret, payload) {
575
+ const encodedPayload = base64UrlEncode(JSON.stringify(EnrollTokenPayload.parse(payload)));
576
+ const signature = await hmacSha256Base64Url(secret, encodedPayload);
577
+ return `oget_${encodedPayload}.${signature}`;
578
+ }
579
+ async function verifyEnrollToken(secret, token, nowSeconds = Math.floor(Date.now() / 1e3)) {
580
+ if (!token.startsWith("oget_")) {
581
+ return null;
582
+ }
583
+ const withoutPrefix = token.slice("oget_".length);
584
+ const dot = withoutPrefix.lastIndexOf(".");
585
+ if (dot <= 0) {
586
+ return null;
587
+ }
588
+ const encodedPayload = withoutPrefix.slice(0, dot);
589
+ const signature = withoutPrefix.slice(dot + 1);
590
+ const expected = await hmacSha256Base64Url(secret, encodedPayload);
591
+ if (!constantTimeEqual(signature, expected)) {
592
+ return null;
593
+ }
594
+ let decoded;
595
+ try {
596
+ decoded = JSON.parse(base64UrlDecode(encodedPayload));
597
+ } catch {
598
+ return null;
599
+ }
600
+ const payload = EnrollTokenPayload.safeParse(decoded);
601
+ if (!payload.success || payload.data.exp < nowSeconds) {
602
+ return null;
603
+ }
604
+ return payload.data;
605
+ }
606
+ var StreamTokenPayload = z.object({
607
+ workspaceId: z.string().uuid(),
608
+ sessionId: z.string().uuid(),
609
+ // Identifies the sandbox_lease_holders row (the viewer holder).
610
+ viewerId: z.string().uuid(),
611
+ // Fence: the token logically dies when the box is re-elected (epoch++).
612
+ leaseEpoch: z.number().int().nonnegative(),
613
+ // v1 is always "view"; "control" is the never-granted raw-input plane.
614
+ mode: z.enum(["view", "control"]),
615
+ // 6080 (noVNC); pins the token to ONE exposed port.
616
+ port: z.number().int().positive(),
617
+ // Short TTL (120s default); rotation is event-driven under the epoch fence,
618
+ // not on a keepalive clock.
619
+ exp: z.number().int().positive()
620
+ });
621
+ async function signStreamToken(secret, payload) {
622
+ const encodedPayload = base64UrlEncode(JSON.stringify(StreamTokenPayload.parse(payload)));
623
+ const signature = await hmacSha256Base64Url(secret, encodedPayload);
624
+ return `ogs_${encodedPayload}.${signature}`;
625
+ }
626
+ async function verifyStreamToken(secret, token, nowSeconds = Math.floor(Date.now() / 1e3)) {
627
+ if (!token.startsWith("ogs_")) {
628
+ return null;
629
+ }
630
+ const withoutPrefix = token.slice("ogs_".length);
631
+ const dot = withoutPrefix.lastIndexOf(".");
632
+ if (dot <= 0) {
633
+ return null;
634
+ }
635
+ const encodedPayload = withoutPrefix.slice(0, dot);
636
+ const signature = withoutPrefix.slice(dot + 1);
637
+ const expected = await hmacSha256Base64Url(secret, encodedPayload);
638
+ if (!constantTimeEqual(signature, expected)) {
639
+ return null;
640
+ }
641
+ let decoded;
642
+ try {
643
+ decoded = JSON.parse(base64UrlDecode(encodedPayload));
644
+ } catch {
645
+ return null;
646
+ }
647
+ const payload = StreamTokenPayload.safeParse(decoded);
648
+ if (!payload.success || payload.data.exp < nowSeconds) {
649
+ return null;
650
+ }
651
+ return payload.data;
652
+ }
653
+ var RelayTokenPayload = z.object({
654
+ // The workspace the agent (and its channels) belong to — the relay asserts this
655
+ // equals the channel-key's ws so a producer can only register its own channels.
656
+ workspaceId: z.string().uuid(),
657
+ // The agent (machine) id — the relay asserts this equals the channel-key's agent.
658
+ agentId: z.string().uuid(),
659
+ // Expiry (unix seconds). Enrollment-scoped horizon (re-minted on re-enroll).
660
+ exp: z.number().int().positive()
661
+ });
662
+ async function signRelayToken(secret, payload) {
663
+ const encodedPayload = base64UrlEncode(JSON.stringify(RelayTokenPayload.parse(payload)));
664
+ const signature = await hmacSha256Base64Url(secret, encodedPayload);
665
+ return `ogr_${encodedPayload}.${signature}`;
666
+ }
667
+ async function verifyRelayToken(secret, token, nowSeconds = Math.floor(Date.now() / 1e3)) {
668
+ if (!token.startsWith("ogr_")) {
669
+ return null;
670
+ }
671
+ const withoutPrefix = token.slice("ogr_".length);
672
+ const dot = withoutPrefix.lastIndexOf(".");
673
+ if (dot <= 0) {
674
+ return null;
675
+ }
676
+ const encodedPayload = withoutPrefix.slice(0, dot);
677
+ const signature = withoutPrefix.slice(dot + 1);
678
+ const expected = await hmacSha256Base64Url(secret, encodedPayload);
679
+ if (!constantTimeEqual(signature, expected)) {
680
+ return null;
681
+ }
682
+ let decoded;
683
+ try {
684
+ decoded = JSON.parse(base64UrlDecode(encodedPayload));
685
+ } catch {
686
+ return null;
687
+ }
688
+ const payload = RelayTokenPayload.safeParse(decoded);
689
+ if (!payload.success || payload.data.exp < nowSeconds) {
690
+ return null;
691
+ }
692
+ return payload.data;
693
+ }
160
694
  var CreateWorkspaceRequest = z.object({
161
695
  accountId: z.string().uuid().optional(),
162
696
  name: z.string().min(1),
@@ -197,6 +731,27 @@ var CreateApiKeyResponse = z.object({
197
731
  apiKey: ApiKey,
198
732
  token: z.string().min(1)
199
733
  });
734
+ var WorkspaceMember = z.object({
735
+ subjectId: z.string().min(1),
736
+ subjectLabel: z.string().nullable(),
737
+ role: z.string(),
738
+ permissions: z.array(Permission),
739
+ createdAt: z.string()
740
+ });
741
+ var ListWorkspaceMembersResponse = z.object({
742
+ members: z.array(WorkspaceMember)
743
+ });
744
+ var AddWorkspaceMemberRequest = z.object({
745
+ // Resolved against the managed (Better Auth) users; email invites for
746
+ // not-yet-registered users are deferred, so an unknown email returns 404.
747
+ email: z.string().email(),
748
+ role: z.string().min(1).optional(),
749
+ permissions: z.array(Permission)
750
+ });
751
+ var UpdateWorkspaceMemberRequest = z.object({
752
+ role: z.string().min(1).optional(),
753
+ permissions: z.array(Permission)
754
+ });
200
755
  var UsageEventType = z.enum([
201
756
  "agent_run.created",
202
757
  "agent_run.completed",
@@ -206,7 +761,16 @@ var UsageEventType = z.enum([
206
761
  "file.deleted",
207
762
  "document.indexed",
208
763
  "scheduled_task.fired",
209
- "api_key.request"
764
+ "api_key.request",
765
+ // --- sandbox warm-time metering (P2.1) ---
766
+ // Wall-clock seconds a box was warm — the billable warm-time meter. Accrued on
767
+ // the two stateless ticks (turn heartbeat + reaper sweep), idempotent on
768
+ // (sandbox_group_id, lease_epoch, tick) so a shared box (N sessions) is metered
769
+ // EXACTLY ONCE per tick (N sessions != N x bill). Orthogonal to model.tokens /
770
+ // model.cost (model API cost vs provider compute cost — both real, no overlap).
771
+ "sandbox.warm_seconds",
772
+ // usd_micros: warm-seconds x the per-provider per-second warm rate.
773
+ "sandbox.warm_cost"
210
774
  ]);
211
775
  var UsageEvent = z.object({
212
776
  id: z.string().uuid(),
@@ -249,6 +813,10 @@ var LimitDecision = z.discriminatedUnion("allowed", [
249
813
  z.object({ allowed: z.literal(true) }),
250
814
  z.object({ allowed: z.literal(false), code: z.string(), message: z.string() })
251
815
  ]);
816
+ var EntitlementDecision = z.discriminatedUnion("allowed", [
817
+ z.object({ allowed: z.literal(true), quantity: z.number().optional() }),
818
+ z.object({ allowed: z.literal(false), reason: z.string(), code: z.string().optional(), quantity: z.number().optional() })
819
+ ]);
252
820
  var BillingBalance = z.object({
253
821
  accountId: z.string().uuid(),
254
822
  balanceMicros: z.number().int(),
@@ -367,7 +935,17 @@ var DocumentSearchRequest = z.object({
367
935
  });
368
936
  var ToolRef = z.object({
369
937
  kind: z.literal("mcp"),
370
- id: z.string().min(1)
938
+ id: z.string().min(1),
939
+ // Non-fatal-on-connect marker for an AUTO-ATTACHED (workspace-default)
940
+ // capability MCP server: when true, a connect / tools-list failure (e.g. an
941
+ // expired capability credential returning 401) must SKIP that server with a
942
+ // logged warning and let the turn proceed, rather than failing the whole
943
+ // turn before the model runs. Absent/false ⇒ STRICT: an unavailable server
944
+ // fails the turn (the contract for EXPLICITLY-requested tools). This flag is
945
+ // set server-side only, at the default-capability auto-attach seam; it is
946
+ // stripped from client-supplied tool refs so an explicit request always
947
+ // stays strict.
948
+ optional: z.boolean().optional()
371
949
  });
372
950
  var ResourceRefConflictError = class extends Error {
373
951
  constructor(message) {
@@ -376,17 +954,22 @@ var ResourceRefConflictError = class extends Error {
376
954
  }
377
955
  };
378
956
  function mergeToolRefs(existing, additions) {
379
- const seen = /* @__PURE__ */ new Set();
380
- const out = [];
957
+ const byKey = /* @__PURE__ */ new Map();
958
+ const order = [];
381
959
  for (const tool of [...existing, ...additions]) {
382
960
  const key = `${tool.kind}:${tool.id}`;
383
- if (seen.has(key)) {
961
+ const prior = byKey.get(key);
962
+ if (!prior) {
963
+ byKey.set(key, tool);
964
+ order.push(key);
384
965
  continue;
385
966
  }
386
- seen.add(key);
387
- out.push(tool);
967
+ if (prior.optional === true && tool.optional !== true) {
968
+ const { optional: _dropped, ...strict } = prior;
969
+ byKey.set(key, strict);
970
+ }
388
971
  }
389
- return out;
972
+ return order.map((key) => byKey.get(key));
390
973
  }
391
974
  function mergeResourceRefs(existing, additions, options = {}) {
392
975
  const out = [...existing];
@@ -481,6 +1064,9 @@ var UpdateSessionGoalRequest = z.object({
481
1064
  status: z.enum(["paused", "active"]),
482
1065
  rationale: z.string().min(1).optional()
483
1066
  });
1067
+ var UpdateSessionRequest = z.object({
1068
+ title: z.string().min(1).max(200)
1069
+ });
484
1070
  var ClearSessionContextRequest = z.object({
485
1071
  confirm: z.literal(true)
486
1072
  });
@@ -519,6 +1105,8 @@ var SessionTurn = z.object({
519
1105
  model: z.string().min(1),
520
1106
  reasoningEffort: ReasoningEffort,
521
1107
  sandboxBackend: SandboxBackend,
1108
+ // Per-turn OS override. NULL = inherit the session's sandboxOs.
1109
+ sandboxOs: SandboxOs.nullable(),
522
1110
  metadata: z.record(z.string(), z.unknown()),
523
1111
  startedAt: z.string().nullable(),
524
1112
  finishedAt: z.string().nullable(),
@@ -943,11 +1531,26 @@ var Session = z.object({
943
1531
  accountId: z.string().uuid(),
944
1532
  status: SessionStatus,
945
1533
  initialMessage: z.string(),
1534
+ title: z.string().nullable(),
1535
+ titleSource: z.enum(["user", "agent"]).nullable(),
946
1536
  resources: z.array(ResourceRef),
947
1537
  tools: z.array(ToolRef),
948
1538
  metadata: z.record(z.string(), z.unknown()),
949
1539
  model: z.string(),
950
1540
  sandboxBackend: SandboxBackend,
1541
+ // The OS the session's box runs. Defaults to 'linux' (today's only OS).
1542
+ sandboxOs: SandboxOs,
1543
+ // The shared-sandbox group the session's box belongs to. Equals the session's
1544
+ // own id for a singleton group (today's 1:1 default); equals the parent's
1545
+ // group when spawned shared (both sessions run in ONE box).
1546
+ sandboxGroupId: z.string().uuid(),
1547
+ // The first-class swappable-sandbox POINTER (bring-your-own-compute M2). NULL
1548
+ // resolves to the session's own group sandbox (the backward-compat default);
1549
+ // a swap sets it to the target sandbox row. active_epoch is the second epoch
1550
+ // ABOVE the lease epoch, bumped on every swap so the routing proxy can fence a
1551
+ // stale in-flight op and retry against the new active sandbox.
1552
+ activeSandboxId: z.string().uuid().nullable(),
1553
+ activeEpoch: z.number().int().nonnegative(),
951
1554
  environmentId: z.string().uuid().nullable(),
952
1555
  // Non-default first-party MCP token permissions (manager-style sessions);
953
1556
  // null means the fixed worker default set.
@@ -968,6 +1571,12 @@ var Session = z.object({
968
1571
  // signal. Null until a turn with usage has completed.
969
1572
  lastInputTokens: z.number().int().nonnegative().nullable(),
970
1573
  lastSequence: z.number().int().nonnegative(),
1574
+ // Multi-account Codex (P1). codexPinnedCredentialId: the account this session is
1575
+ // manually PINNED to (null ⇒ follow the workspace active pointer).
1576
+ // codexLastCredentialId: the account the most recent turn actually ran on (the
1577
+ // "Running on:" indicator's source). Both are credential-row ids, null until set.
1578
+ codexPinnedCredentialId: z.string().uuid().nullable(),
1579
+ codexLastCredentialId: z.string().uuid().nullable(),
971
1580
  createdAt: z.string(),
972
1581
  updatedAt: z.string()
973
1582
  });
@@ -1003,8 +1612,437 @@ var SessionEventType = z.enum([
1003
1612
  "goal.completed",
1004
1613
  "goal.paused",
1005
1614
  "goal.resumed",
1006
- "goal.continuation"
1615
+ "goal.continuation",
1616
+ // Channel-B desktop pixel-plane signals (07-channel-b §1.2). The pixel socket
1617
+ // carries opaque RFB and cannot carry a control message the client can act on,
1618
+ // so these ride the durable, sequenced, gap-filled Channel-A SSE spine.
1619
+ "stream.url.rotated",
1620
+ // re-minted {url,token,expiresAt} on box rollover (event-driven)
1621
+ "stream.opened",
1622
+ // a viewer attached (audit + refcount visibility)
1623
+ "stream.closed",
1624
+ // a viewer detached / was reaped
1625
+ "stream.revoked",
1626
+ // a grant was revoked → connected clients MUST disconnect now
1627
+ // Channel-B recording signals (P4.3 / module 05 §3.4). The "agent films itself
1628
+ // proving the fix" loop: ffmpeg x11grab of the SAME :0 humans watch → artifact
1629
+ // → storage. The artifact ref rides the AVAILABLE event (storageKey, NOT a
1630
+ // long-lived URL — clients mint a short-TTL signed GET via the route).
1631
+ "recording.started",
1632
+ // ffmpeg launched on :0 (mode/codec/dimensions)
1633
+ "recording.available",
1634
+ // finalized: bytes PUT to storage, replayable
1635
+ "recording.failed",
1636
+ // ffmpeg/box-death/rollover/upload error — no artifact
1637
+ // Channel-A structured-service notifications (P4.4 / modules/08-channel-a.md
1638
+ // §2.2). The A2 reads (fs/git/terminal exec) are SYNCHRONOUS API-direct point
1639
+ // queries (their result is the HTTP response, NEVER an event). What rides A1
1640
+ // here are the side-effect NOTIFICATIONS — a path changed, git state changed,
1641
+ // a pty opened/printed/exited — durable, sequenced, gap-filled like every
1642
+ // other session event, so any viewer's Pierre tree / diff / terminal stays
1643
+ // live. fs.changed/git.changed are cache-invalidation signals; the pty.*
1644
+ // events carry the interactive terminal byte stream.
1645
+ "fs.changed",
1646
+ // a path was created/modified/deleted (write or agent mutation)
1647
+ "git.changed",
1648
+ // working-tree/index/HEAD changed (debounced re-probe)
1649
+ "terminal.pty.started",
1650
+ // an interactive PTY session opened (carries ptyId)
1651
+ "terminal.pty.output.delta",
1652
+ // PTY stdout/stderr bytes (separate from command.output)
1653
+ "terminal.pty.exited",
1654
+ // PTY session ended (exitCode/reason)
1655
+ "session.title_set",
1656
+ // Multi-account Codex (P1): the account a session's turn runs on changed
1657
+ // (manual switch in P1; failover/rotation in P3 reuse the same event). Drives
1658
+ // the in-session "Running on:" indicator's live flip.
1659
+ "codex.account.switched"
1660
+ ]);
1661
+ var StreamUrlRotatedPayload = z.object({
1662
+ url: z.string().url(),
1663
+ token: z.string().nullable(),
1664
+ expiresAt: z.string().datetime().nullable(),
1665
+ // The epoch the new URL was minted under (the box-rollover fence the client
1666
+ // reconciles against). A client must drop a rotation event whose epoch it has
1667
+ // already advanced past.
1668
+ leaseEpoch: z.number().int().nonnegative(),
1669
+ transport: z.literal("vnc-ws"),
1670
+ // The viewer holder this URL is for (so a client filters out other viewers').
1671
+ viewerId: z.string().uuid().nullable().default(null)
1672
+ });
1673
+ var StreamOpenedPayload = z.object({
1674
+ viewerId: z.string().uuid(),
1675
+ shared: z.boolean().default(false),
1676
+ viewerCount: z.number().int().nonnegative()
1677
+ });
1678
+ var StreamClosedPayload = z.object({
1679
+ viewerId: z.string().uuid(),
1680
+ reason: z.enum(["client-disconnect", "reaped", "revoked", "box-rollover"]),
1681
+ viewerCount: z.number().int().nonnegative()
1682
+ });
1683
+ var StreamRevokedPayload = z.object({
1684
+ viewerId: z.string().uuid().nullable().default(null),
1685
+ reason: z.enum(["grant-revoked", "session-failed", "admin"])
1686
+ });
1687
+ var RecordingMode = z.enum(["manual", "on-turn", "on-verify"]);
1688
+ var RecordingCodec = z.enum(["h264-mp4", "vp9-webm"]);
1689
+ var RecordingContentType = z.enum(["video/mp4", "video/webm"]);
1690
+ var RecordingStartedPayload = z.object({
1691
+ recordingId: z.string().uuid(),
1692
+ turnId: z.string().uuid().nullable(),
1693
+ mode: RecordingMode,
1694
+ codec: RecordingCodec,
1695
+ dimensions: z.tuple([z.number().int().positive(), z.number().int().positive()]),
1696
+ framerate: z.number().int().positive(),
1697
+ startedAt: z.string(),
1698
+ // ISO
1699
+ // The verification rationale ("agent-verification: tf apply succeeded"). Agent-
1700
+ // authored free text — the producer caps + scrubs it before emit.
1701
+ reason: z.string().nullable().optional()
1702
+ });
1703
+ var RecordingAvailablePayload = z.object({
1704
+ recordingId: z.string().uuid(),
1705
+ turnId: z.string().uuid().nullable(),
1706
+ codec: RecordingCodec,
1707
+ contentType: RecordingContentType,
1708
+ // The @opengeni/storage object key. NO long-lived URL in the event — clients
1709
+ // mint a short-TTL signed GET via GET …/recordings/:id/url.
1710
+ storageKey: z.string(),
1711
+ durationSeconds: z.number().nonnegative().nullable(),
1712
+ sizeBytes: z.number().int().nonnegative(),
1713
+ dimensions: z.tuple([z.number().int().positive(), z.number().int().positive()])
1714
+ });
1715
+ var RecordingFailedReason = z.enum([
1716
+ "ffmpeg-error",
1717
+ "box-death",
1718
+ "box-rollover",
1719
+ "upload-failed",
1720
+ "max-bytes-exceeded",
1721
+ "display-unavailable"
1722
+ ]);
1723
+ var RecordingFailedPayload = z.object({
1724
+ recordingId: z.string().uuid(),
1725
+ turnId: z.string().uuid().nullable(),
1726
+ reason: RecordingFailedReason,
1727
+ // ffmpeg-stderr tail / error detail — agent/ffmpeg-controlled, so the producer
1728
+ // caps + scrubs it before emit (it rides redact() like every payload).
1729
+ detail: z.string().nullable().optional()
1730
+ });
1731
+ var SandboxCommandOutputDeltaPayload = z.object({
1732
+ stream: z.enum(["stdout", "stderr"]).default("stdout"),
1733
+ chunk: z.string(),
1734
+ // raw bytes, utf-8 (lossy) — terminal is opaque-ish
1735
+ commandId: z.string().optional(),
1736
+ // groups deltas to one agent command
1737
+ seq: z.number().int().nonnegative().optional()
1738
+ // intra-command ordering hint
1739
+ });
1740
+ var FsChangeKind = z.enum(["created", "modified", "deleted", "renamed"]);
1741
+ var FsChangedPayload = z.object({
1742
+ changes: z.array(z.object({
1743
+ path: z.string(),
1744
+ // workspace-relative POSIX path
1745
+ kind: FsChangeKind,
1746
+ isDir: z.boolean().default(false),
1747
+ sizeBytes: z.number().int().nonnegative().nullable().default(null),
1748
+ oldPath: z.string().optional()
1749
+ // for "renamed"
1750
+ })).min(1),
1751
+ source: z.enum(["write", "watch", "agent"]).default("write"),
1752
+ // Monotonic FS revision (per-lease, paired with leaseEpoch for staleness).
1753
+ revision: z.number().int().nonnegative(),
1754
+ // The lease epoch the revision was minted under: a client invalidates on a
1755
+ // (leaseEpoch, revision) tuple change, never a bare revision compare (H3 —
1756
+ // revision resets to 0 on box re-key, so a bare monotonic compare goes stale).
1757
+ leaseEpoch: z.number().int().nonnegative().default(0)
1758
+ });
1759
+ var GitChangedPayload = z.object({
1760
+ head: z.string().nullable(),
1761
+ // current branch or detached SHA
1762
+ dirty: z.boolean(),
1763
+ // working tree has uncommitted changes
1764
+ ahead: z.number().int().nonnegative().default(0),
1765
+ behind: z.number().int().nonnegative().default(0),
1766
+ changedFileCount: z.number().int().nonnegative(),
1767
+ reason: z.enum(["commit", "checkout", "stage", "worktree", "fetch", "unknown"]).default("unknown"),
1768
+ revision: z.number().int().nonnegative().default(0),
1769
+ leaseEpoch: z.number().int().nonnegative().default(0)
1770
+ });
1771
+ var TerminalPtyStartedPayload = z.object({
1772
+ ptyId: z.string().uuid(),
1773
+ cols: z.number().int().positive(),
1774
+ rows: z.number().int().positive(),
1775
+ shell: z.string(),
1776
+ // resolved shell, e.g. "/bin/bash"
1777
+ cwd: z.string()
1778
+ });
1779
+ var TerminalPtyOutputDeltaPayload = z.object({
1780
+ ptyId: z.string().uuid(),
1781
+ stream: z.enum(["stdout", "stderr"]).default("stdout"),
1782
+ chunk: z.string(),
1783
+ // raw terminal bytes (incl. ANSI), utf-8 lossy
1784
+ seq: z.number().int().nonnegative()
1785
+ // strict per-pty ordering (owner-assigned)
1786
+ });
1787
+ var TerminalPtyExitedPayload = z.object({
1788
+ ptyId: z.string().uuid(),
1789
+ exitCode: z.number().int().nullable(),
1790
+ reason: z.enum(["exit", "killed", "owner_gone", "timeout"])
1791
+ });
1792
+ var FsNodeType = z.enum(["file", "dir", "symlink", "other"]);
1793
+ var FsTreeNode = z.lazy(() => z.object({
1794
+ name: z.string(),
1795
+ path: z.string(),
1796
+ type: FsNodeType,
1797
+ sizeBytes: z.number().int().nonnegative().nullable(),
1798
+ mtimeMs: z.number().int().nonnegative().nullable(),
1799
+ mode: z.number().int().nullable(),
1800
+ children: z.array(FsTreeNode).optional(),
1801
+ truncated: z.boolean().default(false)
1802
+ }));
1803
+ var FsListRequest = z.object({
1804
+ path: z.string().default(""),
1805
+ // "" = workspace root
1806
+ depth: z.number().int().min(0).max(8).default(1),
1807
+ maxEntries: z.number().int().positive().max(2e4).default(2e3),
1808
+ includeHidden: z.boolean().default(true)
1809
+ });
1810
+ var FsListResponse = z.object({
1811
+ root: FsTreeNode,
1812
+ revision: z.number().int().nonnegative(),
1813
+ truncated: z.boolean()
1814
+ // global cap hit
1815
+ });
1816
+ var FsEncoding = z.enum(["utf8", "base64"]);
1817
+ var FsReadRequest = z.object({
1818
+ path: z.string(),
1819
+ encoding: FsEncoding.default("utf8"),
1820
+ maxBytes: z.number().int().positive().max(25 * 1024 * 1024).default(5 * 1024 * 1024)
1821
+ });
1822
+ var FsReadResponse = z.object({
1823
+ path: z.string(),
1824
+ encoding: FsEncoding,
1825
+ content: z.string(),
1826
+ // text or base64 per encoding
1827
+ sizeBytes: z.number().int().nonnegative(),
1828
+ // bytes returned (== content size)
1829
+ truncated: z.boolean(),
1830
+ // sizeBytes hit maxBytes; content is the prefix
1831
+ isBinary: z.boolean(),
1832
+ // sniffed NUL byte in first 8KB
1833
+ revision: z.number().int().nonnegative()
1834
+ });
1835
+ var FsWriteRequest = z.object({
1836
+ path: z.string(),
1837
+ encoding: FsEncoding.default("utf8"),
1838
+ content: z.string(),
1839
+ overwrite: z.boolean().default(true),
1840
+ // false + existing path => 409
1841
+ createParents: z.boolean().default(true)
1842
+ });
1843
+ var FsWriteResponse = z.object({
1844
+ path: z.string(),
1845
+ sizeBytes: z.number().int().nonnegative(),
1846
+ revision: z.number().int().nonnegative()
1847
+ // == the fs.changed revision
1848
+ });
1849
+ var FsDeleteRequest = z.object({
1850
+ path: z.string(),
1851
+ recursive: z.boolean().default(false)
1852
+ // required true to delete a non-empty dir
1853
+ });
1854
+ var FsDeleteResponse = z.object({ revision: z.number().int().nonnegative() });
1855
+ var FsMoveRequest = z.object({
1856
+ path: z.string(),
1857
+ newPath: z.string(),
1858
+ overwrite: z.boolean().default(false),
1859
+ // false + existing destination => 409
1860
+ createParents: z.boolean().default(true)
1861
+ });
1862
+ var FsMoveResponse = z.object({
1863
+ path: z.string(),
1864
+ newPath: z.string(),
1865
+ revision: z.number().int().nonnegative()
1866
+ // == the fs.changed revision
1867
+ });
1868
+ var FsMkdirRequest = z.object({
1869
+ path: z.string(),
1870
+ recursive: z.boolean().default(true)
1871
+ // false + existing path => 400
1872
+ });
1873
+ var FsMkdirResponse = z.object({
1874
+ path: z.string(),
1875
+ revision: z.number().int().nonnegative()
1876
+ // == the fs.changed revision
1877
+ });
1878
+ var GitFileStatusCode = z.enum([
1879
+ "added",
1880
+ "modified",
1881
+ "deleted",
1882
+ "renamed",
1883
+ "copied",
1884
+ "untracked",
1885
+ "ignored",
1886
+ "conflicted",
1887
+ "typechange"
1007
1888
  ]);
1889
+ var GitFileStatus = z.object({
1890
+ path: z.string(),
1891
+ oldPath: z.string().nullable(),
1892
+ // for renamed/copied
1893
+ index: GitFileStatusCode.nullable(),
1894
+ // staged change (X in porcelain XY)
1895
+ worktree: GitFileStatusCode.nullable(),
1896
+ // unstaged change (Y in porcelain XY)
1897
+ isConflicted: z.boolean().default(false)
1898
+ });
1899
+ var GitStatusRequest = z.object({
1900
+ path: z.string().default("")
1901
+ // repo root within workspace (multi-repo support)
1902
+ });
1903
+ var GitStatusResponse = z.object({
1904
+ isRepo: z.boolean(),
1905
+ head: z.string().nullable(),
1906
+ // branch name
1907
+ detached: z.boolean().default(false),
1908
+ upstream: z.string().nullable(),
1909
+ ahead: z.number().int().nonnegative().default(0),
1910
+ behind: z.number().int().nonnegative().default(0),
1911
+ files: z.array(GitFileStatus),
1912
+ revision: z.number().int().nonnegative()
1913
+ });
1914
+ var GitDiffLineType = z.enum(["context", "add", "del", "meta"]);
1915
+ var GitDiffLine = z.object({
1916
+ type: GitDiffLineType,
1917
+ // null on the side that doesn't have the line (add => oldNo null; del => newNo null)
1918
+ oldNo: z.number().int().positive().nullable(),
1919
+ newNo: z.number().int().positive().nullable(),
1920
+ text: z.string()
1921
+ // line WITHOUT leading +/-/space marker
1922
+ });
1923
+ var GitDiffHunk = z.object({
1924
+ oldStart: z.number().int().nonnegative(),
1925
+ oldLines: z.number().int().nonnegative(),
1926
+ newStart: z.number().int().nonnegative(),
1927
+ newLines: z.number().int().nonnegative(),
1928
+ header: z.string(),
1929
+ // the @@ ... @@ section heading
1930
+ lines: z.array(GitDiffLine)
1931
+ });
1932
+ var GitFileDiff = z.object({
1933
+ path: z.string(),
1934
+ oldPath: z.string().nullable(),
1935
+ status: GitFileStatusCode,
1936
+ isBinary: z.boolean().default(false),
1937
+ isImage: z.boolean().default(false),
1938
+ additions: z.number().int().nonnegative(),
1939
+ deletions: z.number().int().nonnegative(),
1940
+ hunks: z.array(GitDiffHunk),
1941
+ // empty if binary or truncated
1942
+ truncated: z.boolean().default(false)
1943
+ // diff exceeded maxBytes; hunks omitted
1944
+ });
1945
+ var GitDiffRequest = z.object({
1946
+ path: z.string().default(""),
1947
+ // repo root
1948
+ // diff selectors, mutually exclusive precedence: refs > staged > worktree
1949
+ staged: z.boolean().default(false),
1950
+ // --cached (index vs HEAD)
1951
+ fromRef: z.string().optional(),
1952
+ toRef: z.string().optional(),
1953
+ pathspec: z.array(z.string()).default([]),
1954
+ contextLines: z.number().int().min(0).max(10).default(3),
1955
+ maxBytesPerFile: z.number().int().positive().max(2 * 1024 * 1024).default(512 * 1024)
1956
+ });
1957
+ var GitDiffResponse = z.object({
1958
+ files: z.array(GitFileDiff),
1959
+ revision: z.number().int().nonnegative()
1960
+ });
1961
+ var GitLogRequest = z.object({
1962
+ path: z.string().default(""),
1963
+ ref: z.string().default("HEAD"),
1964
+ maxCount: z.number().int().positive().max(1e3).default(100),
1965
+ skip: z.number().int().nonnegative().default(0),
1966
+ pathspec: z.array(z.string()).default([])
1967
+ });
1968
+ var GitCommit = z.object({
1969
+ sha: z.string(),
1970
+ shortSha: z.string(),
1971
+ parents: z.array(z.string()),
1972
+ author: z.object({ name: z.string(), email: z.string(), timestamp: z.number().int() }),
1973
+ committer: z.object({ name: z.string(), email: z.string(), timestamp: z.number().int() }),
1974
+ subject: z.string(),
1975
+ body: z.string(),
1976
+ refs: z.array(z.string()).default([])
1977
+ // decorations: branch/tag pointers
1978
+ });
1979
+ var GitLogResponse = z.object({ commits: z.array(GitCommit), hasMore: z.boolean() });
1980
+ var GitShowRequest = z.object({
1981
+ path: z.string().default(""),
1982
+ ref: z.string(),
1983
+ // a commit/tag/tree-ish
1984
+ filePath: z.string().optional(),
1985
+ // ref + filePath => raw blob ("open file at commit")
1986
+ encoding: FsEncoding.default("utf8"),
1987
+ maxBytesPerFile: z.number().int().positive().max(2 * 1024 * 1024).default(512 * 1024)
1988
+ });
1989
+ var GitShowResponse = z.object({
1990
+ commit: GitCommit.nullable(),
1991
+ // null when fetching a raw blob
1992
+ files: z.array(GitFileDiff),
1993
+ // commit diff vs first parent
1994
+ blob: z.object({ content: z.string(), encoding: FsEncoding, sizeBytes: z.number().int(), truncated: z.boolean() }).nullable(),
1995
+ revision: z.number().int().nonnegative()
1996
+ });
1997
+ var TerminalExecRequest = z.object({
1998
+ command: z.string().min(1),
1999
+ cwd: z.string().default(""),
2000
+ // workspace-relative
2001
+ // Soft per-call wall-clock bound (the box yields output back when reached).
2002
+ timeoutMs: z.number().int().positive().max(12e4).default(3e4),
2003
+ // Stream the deltas onto A1 as the agent firehose (so other viewers see it),
2004
+ // in addition to returning the buffered result inline.
2005
+ emitStream: z.boolean().default(true)
2006
+ });
2007
+ var TerminalExecResponse = z.object({
2008
+ stdout: z.string(),
2009
+ stderr: z.string(),
2010
+ exitCode: z.number().int().nullable(),
2011
+ // True when the process was still running when the call yielded (a long
2012
+ // command); the remaining output drains onto A1 if emitStream was set.
2013
+ running: z.boolean(),
2014
+ wallTimeSeconds: z.number().nonnegative()
2015
+ });
2016
+ var PtyOpenRequest = z.object({
2017
+ cols: z.number().int().positive().max(500).default(80),
2018
+ rows: z.number().int().positive().max(300).default(24),
2019
+ cwd: z.string().default(""),
2020
+ // workspace-relative
2021
+ shell: z.string().optional()
2022
+ // default: resolved login shell
2023
+ });
2024
+ var PtyOpenResponse = z.object({
2025
+ ptyId: z.string().uuid(),
2026
+ // output streams as terminal.pty.output.delta on the SSE channel the client holds
2027
+ streamVia: z.literal("sse-events"),
2028
+ supportsInput: z.boolean()
2029
+ // false on backends without writeStdin
2030
+ });
2031
+ var PtyWriteRequest = z.object({ ptyId: z.string().uuid(), data: z.string() });
2032
+ var PtyResizeRequest = z.object({ ptyId: z.string().uuid(), cols: z.number().int().positive(), rows: z.number().int().positive() });
2033
+ var PtyCloseRequest = z.object({ ptyId: z.string().uuid() });
2034
+ var SessionStructuredCapabilities = z.object({
2035
+ FileSystem: z.object({ available: z.boolean(), readOnly: z.boolean(), root: z.string() }),
2036
+ Terminal: z.object({
2037
+ events: z.boolean(),
2038
+ // command.output firehose (always on if a box exists)
2039
+ exec: z.boolean(),
2040
+ // synchronous terminal exec
2041
+ pty: z.object({ available: z.boolean() })
2042
+ // interactive stdin (writeStdin)
2043
+ }),
2044
+ Git: z.object({ available: z.boolean(), repos: z.array(z.string()) })
2045
+ });
1008
2046
  var SessionEvent = z.object({
1009
2047
  id: z.string().uuid(),
1010
2048
  workspaceId: z.string().uuid(),
@@ -1024,6 +2062,17 @@ var CreateSessionRequest = z.object({
1024
2062
  model: z.string().min(1).optional(),
1025
2063
  reasoningEffort: ReasoningEffort.optional(),
1026
2064
  sandboxBackend: SandboxBackend.optional(),
2065
+ // The enrolled machine (a sandbox id) to run this session on; seeds the
2066
+ // active-sandbox pointer at creation so the FIRST turn routes to the chosen
2067
+ // machine (race-free: the pointer is committed before the worker turn
2068
+ // workflow can read it). An invalid/unowned/offline target fails the create.
2069
+ targetSandboxId: z.string().uuid().optional(),
2070
+ // The working directory the targeted machine runs the session under — the
2071
+ // path/cwd base for its agent exec, terminal, and file dock. Free-form pass-
2072
+ // through: a launch-workspace_root-relative subdir or an absolute machine path
2073
+ // (the agent's resolve_cwd handles both). Only valid WITH targetSandboxId
2074
+ // (workingDir alone is a 422); omitted ⇒ the machine's default workspace_root.
2075
+ workingDir: z.string().min(1).optional(),
1027
2076
  // Workspace environment attachment is fixed at session creation; follow-up
1028
2077
  // user.message events cannot switch or add one.
1029
2078
  environmentId: z.string().uuid().optional(),
@@ -1040,7 +2089,29 @@ var CreateSessionRequest = z.object({
1040
2089
  // the fixed worker default — how an operator hands a manager-style session
1041
2090
  // the orchestration/environment/github tools. Capped at creation: every
1042
2091
  // requested permission must be held by the creating grant (no escalation).
1043
- firstPartyMcpPermissions: z.array(Permission).optional()
2092
+ firstPartyMcpPermissions: z.array(Permission).optional(),
2093
+ // Shared-sandbox placement (addendum 05 §D.1). Three-way union; OMITTED ⇒
2094
+ // today's behavior (a context-dependent default resolved server-side: from
2095
+ // inside a session → "shared" with the creator's box, top-level → "new").
2096
+ // - "shared": join the CREATOR's box. Requires a parent session (inferred
2097
+ // from the worker-signed sessionId claim, never caller-supplied);
2098
+ // top-level "shared" is a 422.
2099
+ // - "new": mint a fresh singleton box (group ≡ the new session's id).
2100
+ // - {groupId}: join a SPECIFIC sibling group in THIS workspace (manager
2101
+ // fan-out). Validated workspace-scoped (cross-workspace → 404).
2102
+ // A shared spawn inherits the box's (backend, os) — it is literally the same
2103
+ // box; the child cannot pick its own backend. Cross-workspace sharing is
2104
+ // forbidden by construction (the parent/group reads are RLS-workspace-scoped).
2105
+ // ENV-AWARE: the box's environment is fixed at creation, so a share requires
2106
+ // the SAME environmentId as the creator's box. On a mismatch the inherited
2107
+ // default silently falls back to an own box; an explicit "shared"/{groupId}
2108
+ // request 422s at create (instead of the first turn dying on the SDK's
2109
+ // manifest-env guard).
2110
+ sandbox: z.union([
2111
+ z.literal("shared"),
2112
+ z.literal("new"),
2113
+ z.object({ groupId: z.string().uuid() })
2114
+ ]).optional()
1044
2115
  });
1045
2116
  var ClientSessionEvent = z.discriminatedUnion("type", [
1046
2117
  z.object({
@@ -1110,10 +2181,358 @@ var ClientAuthConfig = z.discriminatedUnion("mode", [
1110
2181
  session: z.literal("cookie")
1111
2182
  })
1112
2183
  ]);
2184
+ var CapabilityUnavailableReason = z.enum([
2185
+ "backend_unsupported",
2186
+ "os_unsupported",
2187
+ "not_provisioned",
2188
+ "disabled_by_policy",
2189
+ "lease_cold",
2190
+ "tier_headless",
2191
+ // Selfhosted (bring-your-own-compute) negotiation states (M1 additive; the
2192
+ // selfhosted negotiation in select.ts wires them in M3):
2193
+ "agent_offline",
2194
+ // the enrolled agent process is not running / unreachable
2195
+ "agent_reconnecting",
2196
+ // a transient blip — the agent is reconnecting (warmable)
2197
+ "consent_required",
2198
+ // whole-machine / screen-control consent not yet acknowledged
2199
+ "display_unavailable"
2200
+ // headless machine with no display stack (no DesktopStream)
2201
+ ]);
2202
+ var SessionCapabilities = z.object({
2203
+ sessionId: z.string().uuid(),
2204
+ backend: SandboxBackend,
2205
+ os: SandboxOs,
2206
+ liveness: z.enum(["cold", "warming", "warm", "draining"]),
2207
+ // Echoed on viewer heartbeats (the split-brain fence).
2208
+ leaseEpoch: z.number().int().nonnegative(),
2209
+ viewerHeartbeatIntervalMs: z.number().int().positive().default(3e4),
2210
+ FileSystem: z.object({
2211
+ available: z.boolean(),
2212
+ readOnly: z.boolean(),
2213
+ root: z.string(),
2214
+ pathSep: z.enum(["/", "\\"]),
2215
+ treeMode: z.enum(["lazy", "snapshot"]),
2216
+ reason: CapabilityUnavailableReason.nullable()
2217
+ }),
2218
+ Terminal: z.object({
2219
+ transport: z.enum(["sse-events", "pty-ws"]).nullable(),
2220
+ ptyCapable: z.boolean(),
2221
+ shell: z.string(),
2222
+ // The direct-to-provider ttyd PTY-over-websocket URL (pty-ws) resolved on the
2223
+ // SAME tunnel as the desktop; null on a cold lease / read-only sse-events
2224
+ // firehose / degraded terminal. The scoped stream token is recorded against
2225
+ // the holder (NEVER a URL query param), symmetric with DesktopStream.
2226
+ url: z.string().url().nullable(),
2227
+ token: z.string().nullable(),
2228
+ // ISO absolute expiry of the minted stream token (symmetric with
2229
+ // DesktopStream.expiresAt). Null when no live URL/token is minted.
2230
+ expiresAt: z.string().nullable(),
2231
+ reason: CapabilityUnavailableReason.nullable()
2232
+ }),
2233
+ Git: z.object({
2234
+ available: z.boolean(),
2235
+ repos: z.array(z.string()),
2236
+ reason: CapabilityUnavailableReason.nullable()
2237
+ }),
2238
+ DesktopStream: z.object({
2239
+ // "relay-frames" is the selfhosted framebuffer stream: PNG-per-frame protobuf
2240
+ // datagrams spliced over the relay (NOT RFB). The viewer renders it with the
2241
+ // "frames" client (a canvas painter), distinct from Modal's "vnc-ws"/"novnc".
2242
+ transport: z.enum(["vnc-ws", "rdp-ws", "webrtc", "relay-frames"]).nullable(),
2243
+ client: z.enum(["novnc", "web-rdp", "frames"]).nullable(),
2244
+ mode: z.enum(["read-only", "interactive"]).default("read-only"),
2245
+ url: z.string().url().nullable(),
2246
+ token: z.string().nullable(),
2247
+ expiresAt: z.string().nullable(),
2248
+ resolution: z.tuple([z.number().int().positive(), z.number().int().positive()]).default([1024, 768]),
2249
+ // REQUIRED, no default (the server must assert un-redacted pixels).
2250
+ unredacted: z.boolean(),
2251
+ requiresAcknowledgment: z.boolean(),
2252
+ acknowledged: z.boolean(),
2253
+ // SHARED-EXPOSURE disclosure (addendum E.1). `shared` is true when the box's
2254
+ // group has >1 session: watching this desktop ALSO shows the sibling
2255
+ // sessions' agents on the one :0 framebuffer (the pixels cannot be redacted).
2256
+ // `sharedSessionIds` lists the OTHER sessions whose agents may appear — IDS
2257
+ // ONLY, never their goal/metadata/conversation (a viewer of A must not be
2258
+ // able to use "I can see B's id" to subscribe to B's events; stress g). When
2259
+ // shared, the consent gate requires the shared-exposure acknowledgment (409
2260
+ // shared_acknowledgment_required) before the desktop path is handed out.
2261
+ shared: z.boolean().default(false),
2262
+ sharedSessionIds: z.array(z.string().uuid()).default([]),
2263
+ reason: CapabilityUnavailableReason.nullable()
2264
+ }),
2265
+ Recording: z.object({
2266
+ available: z.boolean(),
2267
+ modes: z.array(z.enum(["manual", "on-turn", "on-verify"])),
2268
+ codecs: z.array(z.enum(["h264-mp4", "vp9-webm"])),
2269
+ reason: CapabilityUnavailableReason.nullable()
2270
+ }),
2271
+ // The AGENT drives the SAME :0 (xdotool/XTEST + scrot) the human watches; the
2272
+ // human viewer plane is read-only by default (§6). `available` == desktop-
2273
+ // capable backend && computerUseEnabled; `readOnly` reports whether the agent
2274
+ // driver itself is gated to no-op input (v1 default false — the agent clicks).
2275
+ ComputerUse: z.object({
2276
+ available: z.boolean(),
2277
+ readOnly: z.boolean(),
2278
+ reason: CapabilityUnavailableReason.nullable()
2279
+ }),
2280
+ negotiatedAt: z.string()
2281
+ });
2282
+ var AttachViewerRequest = z.object({
2283
+ viewerId: z.string().uuid().optional(),
2284
+ desktop: z.boolean().optional()
2285
+ });
2286
+ var ViewerHolder = z.object({
2287
+ viewerId: z.string().uuid(),
2288
+ sandboxGroupId: z.string().uuid(),
2289
+ liveness: z.enum(["cold", "warming", "warm", "draining"]),
2290
+ // The epoch the viewer is fenced on; echoed back on heartbeats.
2291
+ leaseEpoch: z.number().int().nonnegative(),
2292
+ viewerHeartbeatIntervalMs: z.number().int().positive(),
2293
+ // The desktop pixel tunnel URL the viewer connects to directly; null until
2294
+ // P4 mints it (gated until then).
2295
+ dataPlaneUrl: z.string().nullable()
2296
+ });
2297
+ var AcknowledgeStreamRequest = z.object({
2298
+ // The principal accepts that the desktop pixel plane is un-redacted (can show
2299
+ // cloud creds the agent cat's into a terminal). Always true to record consent;
2300
+ // present for self-documentation + a future explicit withdraw.
2301
+ acknowledgeUnredacted: z.boolean().default(true),
2302
+ // The principal accepts the shared-exposure disclosure: watching this desktop
2303
+ // also shows sibling sessions' agents on the one framebuffer.
2304
+ acknowledgeShared: z.boolean().default(false)
2305
+ });
2306
+ var AcknowledgeStreamResponse = z.object({
2307
+ acknowledged: z.boolean(),
2308
+ acknowledgedShared: z.boolean()
2309
+ });
2310
+ var ViewerHeartbeatRequest = z.object({
2311
+ leaseEpoch: z.number().int().nonnegative()
2312
+ });
2313
+ var ViewerHeartbeatResponse = z.object({
2314
+ // false ⇒ the holder was reaped or the epoch is stale; the client re-attaches.
2315
+ alive: z.boolean()
2316
+ });
2317
+ var EnrollmentOs = z.enum(["linux", "macos", "windows"]);
2318
+ var EnrollmentArch = z.enum(["x86_64", "aarch64"]);
2319
+ var DeviceEnrollmentStartRequest = z.object({
2320
+ // The agent's ed25519 public key (the machine identity the enrollment binds to).
2321
+ publicKey: z.string().min(1).max(1024),
2322
+ os: EnrollmentOs.default("linux"),
2323
+ arch: EnrollmentArch.default("x86_64"),
2324
+ // Human-friendly machine name (hostname by default).
2325
+ machineName: z.string().min(1).max(256).optional(),
2326
+ // v1 only supports whole-machine; kept explicit so the consent is recorded.
2327
+ exposure: z.literal("whole-machine").default("whole-machine"),
2328
+ // The agent can offer a display (a real screen / Xvfb is available).
2329
+ canOfferDisplay: z.boolean().default(false),
2330
+ // The agent requests screen control (computer-use); the user's allow_screen_control
2331
+ // at approve is the AUTHORITATIVE consent.
2332
+ requestsScreenControl: z.boolean().default(false),
2333
+ // The workspace this machine is enrolling into. The agent is told this at install
2334
+ // (the user picks the workspace, or the install/enroll token carries it). The user
2335
+ // who approves must hold a grant in THIS workspace — that binding is what makes
2336
+ // the (user-unauthenticated) start safe: it cannot grant access to a workspace no
2337
+ // authorized user later approves in.
2338
+ workspaceId: z.string().uuid()
2339
+ });
2340
+ var DeviceEnrollmentStartResponse = z.object({
2341
+ deviceCode: z.string(),
2342
+ userCode: z.string(),
2343
+ verificationUri: z.string(),
2344
+ verificationUriComplete: z.string(),
2345
+ intervalSeconds: z.number().int().positive(),
2346
+ expiresInSeconds: z.number().int().positive()
2347
+ });
2348
+ var DeviceEnrollmentApproveRequest = z.object({
2349
+ userCode: z.string().min(1).max(64),
2350
+ allowScreenControl: z.boolean().default(false)
2351
+ });
2352
+ var DeviceEnrollmentApproveResponse = z.object({
2353
+ approved: z.boolean(),
2354
+ enrollmentId: z.string().uuid(),
2355
+ sandboxId: z.string().uuid(),
2356
+ allowScreenControl: z.boolean()
2357
+ });
2358
+ var DeviceEnrollmentPollRequest = z.object({
2359
+ deviceCode: z.string().min(1).max(256)
2360
+ });
2361
+ var DeviceEnrollmentState = z.enum(["pending", "authorized", "denied", "expired", "disabled"]);
2362
+ var EnrollmentCredentialsResponse = z.object({
2363
+ agentId: z.string().uuid(),
2364
+ workspaceId: z.string().uuid(),
2365
+ // The signed bearer the agent presents to the control plane (the `oge_` token).
2366
+ bearer: z.string(),
2367
+ // The Account-scoped control-plane subject prefix the agent subscribes to:
2368
+ // agent.<workspaceId>.<agentId>.
2369
+ subjectPrefix: z.string(),
2370
+ // Connect info for the control plane + stream relay (may be empty when not yet
2371
+ // configured for this deployment — the agent surfaces "control plane unconfigured").
2372
+ natsUrls: z.array(z.string()),
2373
+ relayUrl: z.string(),
2374
+ // The agent's PRODUCER token for the relay edge (the `ogr_` token; M8b). Presented
2375
+ // as StreamOpen.token when the agent registers a pty/desktop channel; the relay
2376
+ // verifies it then pairs the producer with the viewer (whose `ogs_` token the
2377
+ // relay also verifies). Empty when the relay-token plane is unconfigured for this
2378
+ // deployment (graceful degrade — the agent then presents an empty token the relay
2379
+ // rejects, surfacing the gap loudly rather than silently producing a dead stream).
2380
+ relayToken: z.string(),
2381
+ // VESTIGIAL (M-AUTH): there is no per-machine NATS Account creds file. The agent
2382
+ // presents the `bearer` above as the NATS connect AUTH-TOKEN; the server's
2383
+ // auth-callout responder validates it and mints a workspace-scoped user JWT. This
2384
+ // field echoes the bearer so a consumer reading it as the connect credential still
2385
+ // works; new consumers should read `bearer` directly.
2386
+ natsAccountCreds: z.string(),
2387
+ // The minisign public key the agent pins for self-update verification.
2388
+ updatePublicKey: z.string(),
2389
+ consentedWholeMachine: z.boolean(),
2390
+ consentedScreenControl: z.boolean()
2391
+ });
2392
+ var DeviceEnrollmentPollResponse = z.object({
2393
+ state: DeviceEnrollmentState,
2394
+ // Present only when state === "authorized".
2395
+ credentials: EnrollmentCredentialsResponse.optional()
2396
+ });
2397
+ var EnrollmentSummary = z.object({
2398
+ id: z.string().uuid(),
2399
+ pubkey: z.string(),
2400
+ exposure: z.literal("whole-machine"),
2401
+ hasDisplay: z.boolean(),
2402
+ allowScreenControl: z.boolean(),
2403
+ status: z.enum(["active", "revoked"]),
2404
+ os: EnrollmentOs,
2405
+ arch: z.string(),
2406
+ lastSeenAt: z.string().nullable(),
2407
+ createdAt: z.string(),
2408
+ revokedAt: z.string().nullable()
2409
+ });
2410
+ var ListEnrollmentsResponse = z.object({
2411
+ enrollments: z.array(EnrollmentSummary)
2412
+ });
2413
+ var RevokeEnrollmentResponse = z.object({
2414
+ revoked: z.boolean()
2415
+ });
2416
+ var DeviceEnrollmentLookupRequest = z.object({
2417
+ userCode: z.string().min(1).max(64)
2418
+ });
2419
+ var DeviceEnrollmentLookupMachine = z.object({
2420
+ machineName: z.string().nullable(),
2421
+ os: EnrollmentOs,
2422
+ arch: z.string(),
2423
+ canOfferDisplay: z.boolean(),
2424
+ requestsScreenControl: z.boolean()
2425
+ });
2426
+ var DeviceEnrollmentLookupResponse = z.object({
2427
+ workspaceId: z.string().uuid(),
2428
+ userCode: z.string(),
2429
+ machine: DeviceEnrollmentLookupMachine,
2430
+ expiresAt: z.string()
2431
+ });
2432
+ var DeviceEnrollmentDenyRequest = z.object({
2433
+ userCode: z.string().min(1).max(64)
2434
+ });
2435
+ var DeviceEnrollmentDenyResponse = z.object({
2436
+ denied: z.boolean()
2437
+ });
2438
+ var MintEnrollTokenRequest = z.object({
2439
+ allowScreenControl: z.boolean().default(false)
2440
+ });
2441
+ var MintEnrollTokenResponse = z.object({
2442
+ // The `oget_` token. SECRET — the UI shows it once with a copy-now warning.
2443
+ token: z.string(),
2444
+ expiresAt: z.string(),
2445
+ expiresInSeconds: z.number().int().positive()
2446
+ });
2447
+ var EnrollTokenExchangeRequest = z.object({
2448
+ // The `oget_` enroll token (the auth + the workspace/account/consent grant).
2449
+ token: z.string().min(1),
2450
+ // The agent's ed25519 public key (the machine identity the enrollment binds to).
2451
+ publicKey: z.string().min(1).max(1024),
2452
+ os: EnrollmentOs.default("linux"),
2453
+ arch: EnrollmentArch.default("x86_64"),
2454
+ machineName: z.string().min(1).max(256).optional(),
2455
+ // v1 only supports whole-machine; kept explicit so the consent is recorded.
2456
+ exposure: z.literal("whole-machine").default("whole-machine"),
2457
+ canOfferDisplay: z.boolean().default(false),
2458
+ // The agent's REQUEST; the token's allowScreenControl is the AUTHORITATIVE consent.
2459
+ requestsScreenControl: z.boolean().default(false)
2460
+ });
2461
+ var EnrollTokenExchangeResponse = z.object({
2462
+ credentials: EnrollmentCredentialsResponse
2463
+ });
2464
+ var MetricSample = z.object({
2465
+ cpuPct: z.number(),
2466
+ load1: z.number(),
2467
+ load5: z.number(),
2468
+ load15: z.number(),
2469
+ memUsedBytes: z.number().int(),
2470
+ memTotalBytes: z.number().int(),
2471
+ diskUsedBytes: z.number().int(),
2472
+ diskTotalBytes: z.number().int(),
2473
+ gpuUtilPct: z.number().nullable(),
2474
+ gpuMemBytes: z.number().int().nullable(),
2475
+ runQueue: z.number(),
2476
+ sampledAt: z.string()
2477
+ });
2478
+ var MachineState = z.enum([
2479
+ "online",
2480
+ "reconnecting",
2481
+ "offline",
2482
+ "consent_required",
2483
+ "display_unavailable",
2484
+ "enrolling"
2485
+ ]);
2486
+ var MachineKind = z.enum(["modal", "selfhosted"]);
2487
+ var MachineView = z.object({
2488
+ sandboxId: z.string(),
2489
+ enrollmentId: z.string().nullable(),
2490
+ name: z.string(),
2491
+ kind: MachineKind,
2492
+ state: MachineState,
2493
+ active: z.boolean(),
2494
+ isSessionGroup: z.boolean(),
2495
+ os: z.string(),
2496
+ arch: z.string(),
2497
+ hasDisplay: z.boolean(),
2498
+ allowScreenControl: z.boolean(),
2499
+ sharedSessionCount: z.number().int(),
2500
+ lastSeenAt: z.string().nullable(),
2501
+ metrics: MetricSample.nullable()
2502
+ });
2503
+ var MachinesResponse = z.object({
2504
+ activeSandboxId: z.string().nullable(),
2505
+ activeEpoch: z.number().int(),
2506
+ machines: z.array(MachineView)
2507
+ });
2508
+ var SwapActiveSandboxRequest = z.object({
2509
+ target: z.string().min(1)
2510
+ });
2511
+ var SwapActiveSandboxResponse = z.object({
2512
+ swapped: z.boolean(),
2513
+ activeSandboxId: z.string().nullable(),
2514
+ activeEpoch: z.number().int(),
2515
+ reason: z.string().optional()
2516
+ });
2517
+ var MachineMetricsSeriesResponse = z.object({
2518
+ samples: z.array(MetricSample)
2519
+ });
2520
+ var ClientModel = z.object({
2521
+ id: z.string(),
2522
+ label: z.string(),
2523
+ provider: z.string(),
2524
+ // provider id
2525
+ providerLabel: z.string(),
2526
+ api: z.enum(["responses", "chat"]),
2527
+ contextWindowTokens: z.number().int().positive().optional()
2528
+ });
1113
2529
  var ClientConfig = z.object({
1114
2530
  deploymentRevision: z.string(),
1115
2531
  defaultModel: z.string(),
1116
2532
  allowedModels: z.array(z.string()).min(1),
2533
+ // Richer model list (provider-grouped) for the picker. Defaults to [] for
2534
+ // back-compat: callers that only read allowedModels are unaffected.
2535
+ models: z.array(ClientModel).default([]),
1117
2536
  defaultReasoningEffort: ReasoningEffort,
1118
2537
  allowedReasoningEfforts: z.array(ReasoningEffort).min(1),
1119
2538
  mcpServers: z.array(z.object({
@@ -1125,7 +2544,16 @@ var ClientConfig = z.object({
1125
2544
  maxSizeBytes: z.number().int().positive()
1126
2545
  }),
1127
2546
  productAccessMode: ProductAccessMode,
1128
- auth: ClientAuthConfig.default({ mode: "none" })
2547
+ auth: ClientAuthConfig.default({ mode: "none" }),
2548
+ // Server-wide hint: does this deployment support Channel-A structured services
2549
+ // at all (P4.4). Per-session availability is negotiated on /stream-capabilities
2550
+ // (it depends on the session's pinned backend); this is the coarse on/off the
2551
+ // client uses to decide whether to even attempt the fs/git/terminal panels.
2552
+ structuredServices: z.object({
2553
+ fileSystem: z.boolean(),
2554
+ git: z.boolean(),
2555
+ terminalEvents: z.boolean()
2556
+ }).default({ fileSystem: false, git: false, terminalEvents: false })
1129
2557
  });
1130
2558
  function base64UrlEncode(value) {
1131
2559
  return Buffer.from(value, "utf8").toString("base64url");
@@ -1161,10 +2589,15 @@ export {
1161
2589
  AccessGrant,
1162
2590
  AccountGrant,
1163
2591
  AccountRole,
2592
+ AcknowledgeStreamRequest,
2593
+ AcknowledgeStreamResponse,
1164
2594
  AddDocumentRequest,
2595
+ AddWorkspaceMemberRequest,
1165
2596
  ApiKey,
2597
+ AttachViewerRequest,
1166
2598
  BillingBalance,
1167
2599
  BillingMode,
2600
+ CAPABILITY_DESCRIPTORS,
1168
2601
  CLEARED_RUN_STATE_BLOB,
1169
2602
  CLEARED_RUN_STATE_MARKER,
1170
2603
  CapabilityCatalogItem,
@@ -1181,9 +2614,11 @@ export {
1181
2614
  CapabilityPackSkillFile,
1182
2615
  CapabilityRuntime,
1183
2616
  CapabilitySource,
2617
+ CapabilityUnavailableReason,
1184
2618
  ClearSessionContextRequest,
1185
2619
  ClientAuthConfig,
1186
2620
  ClientConfig,
2621
+ ClientModel,
1187
2622
  ClientSessionEvent,
1188
2623
  CompactSessionContextRequest,
1189
2624
  CompactSessionContextResult,
@@ -1202,7 +2637,20 @@ export {
1202
2637
  CreateSocialPostRequest,
1203
2638
  CreateWorkspaceEnvironmentRequest,
1204
2639
  CreateWorkspaceRequest,
2640
+ DESKTOP_STREAM_PORT,
1205
2641
  DelegatedAccessTokenPayload,
2642
+ DeviceEnrollmentApproveRequest,
2643
+ DeviceEnrollmentApproveResponse,
2644
+ DeviceEnrollmentDenyRequest,
2645
+ DeviceEnrollmentDenyResponse,
2646
+ DeviceEnrollmentLookupMachine,
2647
+ DeviceEnrollmentLookupRequest,
2648
+ DeviceEnrollmentLookupResponse,
2649
+ DeviceEnrollmentPollRequest,
2650
+ DeviceEnrollmentPollResponse,
2651
+ DeviceEnrollmentStartRequest,
2652
+ DeviceEnrollmentStartResponse,
2653
+ DeviceEnrollmentState,
1206
2654
  DiscoverMcpCapabilitiesResponse,
1207
2655
  Document,
1208
2656
  DocumentBase,
@@ -1211,6 +2659,15 @@ export {
1211
2659
  DocumentStatus,
1212
2660
  EnableCapabilityRequest,
1213
2661
  EnablePackRequest,
2662
+ EnrollTokenExchangeRequest,
2663
+ EnrollTokenExchangeResponse,
2664
+ EnrollTokenPayload,
2665
+ EnrollmentArch,
2666
+ EnrollmentBearerPayload,
2667
+ EnrollmentCredentialsResponse,
2668
+ EnrollmentOs,
2669
+ EnrollmentSummary,
2670
+ EntitlementDecision,
1214
2671
  EntitlementValue,
1215
2672
  Entitlements,
1216
2673
  EntitlementsMode,
@@ -1221,25 +2678,85 @@ export {
1221
2678
  FileResourceRef,
1222
2679
  FileStatus,
1223
2680
  FileUploadStatus,
2681
+ FsChangeKind,
2682
+ FsChangedPayload,
2683
+ FsDeleteRequest,
2684
+ FsDeleteResponse,
2685
+ FsEncoding,
2686
+ FsListRequest,
2687
+ FsListResponse,
2688
+ FsMkdirRequest,
2689
+ FsMkdirResponse,
2690
+ FsMoveRequest,
2691
+ FsMoveResponse,
2692
+ FsNodeType,
2693
+ FsReadRequest,
2694
+ FsReadResponse,
2695
+ FsTreeNode,
2696
+ FsWriteRequest,
2697
+ FsWriteResponse,
2698
+ GitChangedPayload,
2699
+ GitCommit,
2700
+ GitDiffHunk,
2701
+ GitDiffLine,
2702
+ GitDiffLineType,
2703
+ GitDiffRequest,
2704
+ GitDiffResponse,
2705
+ GitFileDiff,
2706
+ GitFileStatus,
2707
+ GitFileStatusCode,
1224
2708
  GitHubAppManifestCreate,
1225
2709
  GitHubRepository,
2710
+ GitLogRequest,
2711
+ GitLogResponse,
2712
+ GitShowRequest,
2713
+ GitShowResponse,
2714
+ GitStatusRequest,
2715
+ GitStatusResponse,
1226
2716
  GoalSpec,
1227
2717
  LimitAction,
1228
2718
  LimitDecision,
2719
+ ListEnrollmentsResponse,
2720
+ ListWorkspaceMembersResponse,
2721
+ MachineKind,
2722
+ MachineMetricsSeriesResponse,
2723
+ MachineState,
2724
+ MachineView,
2725
+ MachinesResponse,
1229
2726
  ManagedAccount,
1230
2727
  MarketingDailyAnalysisTaskRequest,
2728
+ MetricSample,
2729
+ MintEnrollTokenRequest,
2730
+ MintEnrollTokenResponse,
1231
2731
  PackInstallation,
1232
2732
  PackInstallationStatus,
1233
2733
  PageInfo,
1234
2734
  Permission,
1235
2735
  ProductAccessMode,
2736
+ PtyCloseRequest,
2737
+ PtyOpenRequest,
2738
+ PtyOpenResponse,
2739
+ PtyResizeRequest,
2740
+ PtyWriteRequest,
1236
2741
  ReasoningEffort,
2742
+ RecordingAvailablePayload,
2743
+ RecordingCodec,
2744
+ RecordingContentType,
2745
+ RecordingFailedPayload,
2746
+ RecordingFailedReason,
2747
+ RecordingMode,
2748
+ RecordingStartedPayload,
1237
2749
  RegisterCapabilityPackRequest,
2750
+ RelayTokenPayload,
1238
2751
  ReorderSessionTurnsRequest,
1239
2752
  RepositoryResourceRef,
1240
2753
  ResourceRef,
1241
2754
  ResourceRefConflictError,
2755
+ RevokeEnrollmentResponse,
1242
2756
  SandboxBackend,
2757
+ SandboxCapabilityName,
2758
+ SandboxCommandOutputDeltaPayload,
2759
+ SandboxOs,
1243
2760
  ScheduledTask,
1244
2761
  ScheduledTaskAgentConfig,
1245
2762
  ScheduledTaskOverlapPolicy,
@@ -1251,6 +2768,7 @@ export {
1251
2768
  ScheduledTaskTriggerType,
1252
2769
  Session,
1253
2770
  SessionBusMessage,
2771
+ SessionCapabilities,
1254
2772
  SessionEvent,
1255
2773
  SessionEventType,
1256
2774
  SessionGoal,
@@ -1258,6 +2776,7 @@ export {
1258
2776
  SessionGoalPausedReason,
1259
2777
  SessionGoalStatus,
1260
2778
  SessionStatus,
2779
+ SessionStructuredCapabilities,
1261
2780
  SessionTurn,
1262
2781
  SessionTurnSource,
1263
2782
  SessionTurnStatus,
@@ -1267,20 +2786,39 @@ export {
1267
2786
  SocialPost,
1268
2787
  SocialProvider,
1269
2788
  StaticUsageLimits,
2789
+ StreamClosedPayload,
2790
+ StreamOpenedPayload,
2791
+ StreamRevokedPayload,
2792
+ StreamTokenPayload,
2793
+ StreamUrlRotatedPayload,
2794
+ SwapActiveSandboxRequest,
2795
+ SwapActiveSandboxResponse,
2796
+ TERMINAL_STREAM_PORT,
2797
+ TerminalExecRequest,
2798
+ TerminalExecResponse,
2799
+ TerminalPtyExitedPayload,
2800
+ TerminalPtyOutputDeltaPayload,
2801
+ TerminalPtyStartedPayload,
1270
2802
  ToolRef,
1271
2803
  TriggerScheduledTaskRequest,
1272
2804
  UpdateScheduledTaskRequest,
1273
2805
  UpdateSessionGoalRequest,
2806
+ UpdateSessionRequest,
1274
2807
  UpdateSessionTurnRequest,
1275
2808
  UpdateWorkspaceEnvironmentRequest,
2809
+ UpdateWorkspaceMemberRequest,
1276
2810
  UpdateWorkspaceRequest,
1277
2811
  UsageEvent,
1278
2812
  UsageEventType,
1279
2813
  UsageLimitsMode,
2814
+ ViewerHeartbeatRequest,
2815
+ ViewerHeartbeatResponse,
2816
+ ViewerHolder,
1280
2817
  Workspace,
1281
2818
  WorkspaceEnvironment,
1282
2819
  WorkspaceEnvironmentVariableMetadata,
1283
2820
  WorkspaceEnvironmentVariableName,
2821
+ WorkspaceMember,
1284
2822
  WorkspaceRegisteredPack,
1285
2823
  isClearedRunStateBlob,
1286
2824
  mergeResourceRefs,
@@ -1289,7 +2827,15 @@ export {
1289
2827
  reasoningEffortForMetadata,
1290
2828
  resourceIdentityKey,
1291
2829
  signDelegatedAccessToken,
2830
+ signEnrollToken,
2831
+ signEnrollmentBearer,
2832
+ signRelayToken,
2833
+ signStreamToken,
1292
2834
  stableJson,
1293
- verifyDelegatedAccessToken
2835
+ verifyDelegatedAccessToken,
2836
+ verifyEnrollToken,
2837
+ verifyEnrollmentBearer,
2838
+ verifyRelayToken,
2839
+ verifyStreamToken
1294
2840
  };
1295
2841
  //# sourceMappingURL=index.js.map