@pstdio/pocketcoder-sdk 0.6.0 → 0.7.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
@@ -1,4 +1,6 @@
1
1
  import { z } from "zod";
2
+ import { createHash } from "node:crypto";
3
+ import { posix } from "node:path";
2
4
  import { isIP } from "node:net";
3
5
  //#region ../contracts/src/attachments/attachment.ts
4
6
  const ATTACHMENT_MAX_FILE_BYTES = 26214400;
@@ -93,14 +95,160 @@ const AttachmentResolvedPayload = z.object({
93
95
  missing_id: z.uuid().optional()
94
96
  });
95
97
  //#endregion
98
+ //#region ../contracts/src/common/canonical.ts
99
+ function canonicalJson(value) {
100
+ return JSON.stringify(sortValue(value));
101
+ }
102
+ function sortValue(value) {
103
+ if (Array.isArray(value)) return value.map(sortValue);
104
+ if (value !== null && typeof value === "object") {
105
+ const out = {};
106
+ for (const key of Object.keys(value).sort()) {
107
+ const v = value[key];
108
+ if (v !== void 0) out[key] = sortValue(v);
109
+ }
110
+ return out;
111
+ }
112
+ return value;
113
+ }
114
+ function sha256Hex(input) {
115
+ return createHash("sha256").update(input).digest("hex");
116
+ }
117
+ function digestOf(value) {
118
+ return `sha256:${sha256Hex(canonicalJson(value))}`;
119
+ }
120
+ //#endregion
121
+ //#region ../contracts/src/templates/workload-capabilities.ts
122
+ const WorkloadDigestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/);
123
+ const WorkloadImageSchema = z.string().regex(/^[^\s@]+@sha256:[0-9a-f]{64}$/);
124
+ const WorkloadNameSchema = z.string().regex(/^[a-z][a-z0-9-]{0,62}$/);
125
+ const WorkloadBoundSchema = z.number().int().positive().max(Number.MAX_SAFE_INTEGER);
126
+ const WorkloadCapabilitySchema = z.enum([
127
+ "ready-without-task",
128
+ "live-inputs",
129
+ "immutable-inputs",
130
+ "serial-operations",
131
+ "retained-exports",
132
+ "explicit-start",
133
+ "exact-exit",
134
+ "targeted-cancel",
135
+ "stop-before-export",
136
+ "request-lookup",
137
+ "deduplicated-accept",
138
+ "explicit-publication",
139
+ "workspace-services",
140
+ "revocable-preview-grants",
141
+ "workspace-cancel",
142
+ "unknown-outcome"
143
+ ]);
144
+ const WorkloadProfileSchema = z.enum([
145
+ "finite-process",
146
+ "cloud-interactive",
147
+ "service"
148
+ ]);
149
+ const common$1 = [
150
+ "ready-without-task",
151
+ "live-inputs",
152
+ "immutable-inputs",
153
+ "serial-operations",
154
+ "retained-exports"
155
+ ];
156
+ [...common$1], [...common$1];
157
+ const WorkloadLimitsSchema = z.strictObject({
158
+ deadlineSeconds: WorkloadBoundSchema,
159
+ finalizationSeconds: WorkloadBoundSchema,
160
+ inputFileBytes: WorkloadBoundSchema,
161
+ stagedInputBytes: WorkloadBoundSchema,
162
+ inputFiles: WorkloadBoundSchema,
163
+ inFlightTransfers: WorkloadBoundSchema,
164
+ exportBytes: WorkloadBoundSchema,
165
+ exportFiles: WorkloadBoundSchema,
166
+ unreferencedInputRetentionSeconds: WorkloadBoundSchema,
167
+ exportRetentionSeconds: WorkloadBoundSchema
168
+ }).superRefine((limits, ctx) => {
169
+ for (const [smaller, larger] of [["inputFileBytes", "stagedInputBytes"], ["finalizationSeconds", "deadlineSeconds"]]) if (limits[smaller] > limits[larger]) ctx.addIssue({
170
+ code: "custom",
171
+ path: [smaller],
172
+ message: `${smaller} must not exceed ${larger}`
173
+ });
174
+ });
175
+ const WorkloadRuntimePinSchema = z.strictObject({
176
+ digest: WorkloadDigestSchema,
177
+ protocol: z.literal("pocketcoder.workload/v1")
178
+ });
179
+ const InstalledWorkloadSchema = z.strictObject({
180
+ workspaceId: z.uuid(),
181
+ incarnationId: z.string().min(1).max(256),
182
+ templateDigest: WorkloadDigestSchema,
183
+ image: WorkloadImageSchema,
184
+ runtime: WorkloadRuntimePinSchema,
185
+ storage: z.strictObject({
186
+ identity: z.string().min(1).max(256),
187
+ profile: z.enum(["controller-readonly/v1", "shared-writer-readonly/v1"])
188
+ }),
189
+ capabilities: z.array(WorkloadCapabilitySchema),
190
+ adapters: z.record(WorkloadNameSchema, z.strictObject({
191
+ kind: z.enum([
192
+ "process",
193
+ "service",
194
+ "pi",
195
+ "agentapi"
196
+ ]),
197
+ digest: WorkloadDigestSchema,
198
+ protocol: z.string().min(1).max(128),
199
+ capabilities: z.array(WorkloadCapabilitySchema)
200
+ }))
201
+ });
202
+ //#endregion
203
+ //#region ../contracts/src/attachments/input-reference.ts
204
+ const InputObjectReferenceSchema = z.strictObject({
205
+ workspaceId: z.uuid(),
206
+ fileId: z.uuid(),
207
+ objectVersion: WorkloadDigestSchema,
208
+ name: z.string().min(1).max(255),
209
+ mediaType: AttachmentMediaTypeSchema,
210
+ sizeBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
211
+ digest: WorkloadDigestSchema,
212
+ visibility: z.enum([
213
+ "ready",
214
+ "unavailable",
215
+ "expired"
216
+ ])
217
+ });
218
+ const AcceptedInputManifestSchema = z.strictObject({
219
+ workspaceId: z.uuid(),
220
+ incarnationId: z.string().min(1).max(256),
221
+ digest: WorkloadDigestSchema,
222
+ objects: z.array(InputObjectReferenceSchema)
223
+ }).superRefine((manifest, ctx) => {
224
+ const { digest, ...contents } = manifest;
225
+ if (digest !== digestOf(contents)) ctx.addIssue({
226
+ code: "custom",
227
+ path: ["digest"],
228
+ message: "input manifest digest does not match its objects"
229
+ });
230
+ const ids = /* @__PURE__ */ new Set();
231
+ for (const [index, object] of manifest.objects.entries()) {
232
+ if (object.workspaceId !== manifest.workspaceId || object.visibility !== "ready" || ids.has(object.fileId)) ctx.addIssue({
233
+ code: "custom",
234
+ path: ["objects", index],
235
+ message: "accepted inputs must be unique, ready and workspace-scoped"
236
+ });
237
+ ids.add(object.fileId);
238
+ }
239
+ });
240
+ //#endregion
96
241
  //#region ../contracts/src/common/scopes.ts
97
242
  const SCOPES = [
98
243
  "templates:read",
244
+ "templates:write",
245
+ "secrets:write",
99
246
  "workspaces:create",
100
247
  "workspaces:read",
101
248
  "workspaces:cancel",
102
249
  "workspaces:purge",
103
250
  "workspaces:recover",
251
+ "principals:admin",
104
252
  "keys:read",
105
253
  "keys:write",
106
254
  "workspaces:preserve",
@@ -109,8 +257,12 @@ const SCOPES = [
109
257
  "checkpoints:delete",
110
258
  "outputs:read",
111
259
  "conversations:read",
260
+ "conversations:write",
112
261
  "conversations:delete",
113
262
  "services:relay",
263
+ "previews:open",
264
+ "display:view",
265
+ "display:control",
114
266
  "attachments:write",
115
267
  "logs:read",
116
268
  "network:read",
@@ -121,13 +273,20 @@ const SCOPES = [
121
273
  z.strictObject({
122
274
  request_id: z.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/),
123
275
  scopes: z.array(z.enum(SCOPES)).min(1).max(SCOPES.length),
276
+ templates: z.array(z.string().min(1).max(128)).max(100).optional(),
277
+ managed_principal_ids: z.array(z.uuid()).max(100).optional(),
124
278
  expires_at: z.iso.datetime()
125
- });
279
+ }).pick({
280
+ request_id: true,
281
+ expires_at: true
282
+ }).partial({ expires_at: true });
126
283
  const KeyResourceSchema = z.object({
127
284
  id: z.uuid(),
128
285
  principal_id: z.uuid(),
129
286
  scopes: z.array(z.string()),
130
287
  effective_scopes: z.array(z.string()),
288
+ templates: z.array(z.string()).nullable(),
289
+ effective_templates: z.array(z.string()),
131
290
  managed_principal_ids: z.array(z.uuid()),
132
291
  issuance_request_id: z.string().nullable(),
133
292
  created_at: z.iso.datetime(),
@@ -143,12 +302,265 @@ const KeyListResponseSchema = z.object({
143
302
  items: z.array(KeyResourceSchema),
144
303
  next_cursor: z.uuid().nullable()
145
304
  });
305
+ const ExpiringKeySchema = KeyResourceSchema.extend({ principal_name: z.string() });
306
+ const ExpiringKeyListResponseSchema = z.object({ items: z.array(ExpiringKeySchema) });
307
+ //#endregion
308
+ //#region ../contracts/src/auth/principals.ts
309
+ const PrincipalGrantsSchema = z.strictObject({
310
+ scopes: z.array(z.enum(SCOPES)).max(SCOPES.length),
311
+ templates: z.array(z.string().min(1).max(128)).max(100)
312
+ });
313
+ PrincipalGrantsSchema.extend({ name: z.string().min(1).max(128) });
314
+ const PrincipalResourceSchema = z.object({
315
+ id: z.uuid(),
316
+ name: z.string(),
317
+ scopes: z.array(z.string()),
318
+ templates: z.array(z.string()),
319
+ disabled_at: z.iso.datetime().nullable(),
320
+ created_at: z.iso.datetime()
321
+ });
322
+ const PrincipalListResponseSchema = z.object({ items: z.array(PrincipalResourceSchema) });
323
+ PrincipalGrantsSchema.partial().extend({ disabled: z.boolean().optional() }).refine((value) => Object.keys(value).length > 0, { message: "Provide a grant or disabled status." });
146
324
  //#endregion
147
325
  //#region ../contracts/src/common/duration.ts
148
326
  const DURATION_RE = /^(\d+)(ms|s|m|h)$/;
327
+ const UNIT_MS = {
328
+ ms: 1,
329
+ s: 1e3,
330
+ m: 6e4,
331
+ h: 36e5
332
+ };
149
333
  function isDuration(value) {
150
334
  return DURATION_RE.test(value);
151
335
  }
336
+ function parseDurationMs(value) {
337
+ const match = DURATION_RE.exec(value);
338
+ if (!match) throw new Error(`invalid duration: ${value}`);
339
+ const amount = Number(match[1]);
340
+ const unit = UNIT_MS[match[2]];
341
+ if (unit === void 0) throw new Error(`invalid duration unit: ${value}`);
342
+ return amount * unit;
343
+ }
344
+ //#endregion
345
+ //#region ../contracts/src/persistence/artifacts.ts
346
+ const SEGMENT = /^(?!\.\.?$)[A-Za-z0-9._-]{1,128}$/;
347
+ const PrivateArtifactSchema = z.object({
348
+ name: z.string().regex(/^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/),
349
+ mount: z.string().regex(/^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/),
350
+ pattern: z.string().max(512).refine((pattern) => {
351
+ const segments = pattern.split("/");
352
+ return segments.every((segment) => segment === "*" || SEGMENT.test(segment)) && segments.filter((segment) => segment === "*").length <= 1;
353
+ }, "pattern must be relative path segments with at most one * segment"),
354
+ maxBytes: z.number().int().positive().max(67108864).default(8388608)
355
+ });
356
+ //#endregion
357
+ //#region ../contracts/src/persistence/persistence.ts
358
+ const DurationValueSchema = z.string().refine(isDuration, { message: "expected a duration like 15s, 20m, or 2h" });
359
+ const LAUNCH_MODES = ["create", "restore"];
360
+ const CONVERSATION_RESTORE_CAPABILITIES = [
361
+ "supported",
362
+ "filesystem_only",
363
+ "unknown"
364
+ ];
365
+ const SourceDescriptorSchema = z.object({
366
+ kind: z.literal("git"),
367
+ repository: z.string().min(1).max(64).regex(/^[a-z0-9]([a-z0-9._-]{0,62}[a-z0-9])?$/),
368
+ revision: z.string().min(1).max(256).refine((value) => !value.startsWith("-") && !value.startsWith("/") && !value.endsWith("/") && !value.endsWith(".") && !value.endsWith(".lock") && value !== "@" && !value.includes("..") && !value.includes("//") && !value.includes("@{") && !/[~^:?*[\]\\\s]/u.test(value) && [...value].every((character) => {
369
+ const code = character.codePointAt(0) ?? 0;
370
+ return code >= 32 && code !== 127;
371
+ }), { message: "revision is not a safe branch, tag, or commit name" })
372
+ });
373
+ const ResolvedSourceSchema = z.object({
374
+ kind: z.literal("git"),
375
+ repository: z.string().min(1).max(64),
376
+ requested_revision: z.string().min(1).max(256),
377
+ resolved_commit: z.string().regex(/^[0-9a-f]{40,64}$/)
378
+ });
379
+ const PersistenceMountSchema = z.object({
380
+ name: z.string().min(1).max(64).regex(/^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/),
381
+ target: z.string().min(1),
382
+ maxBytes: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
383
+ maxFiles: z.number().int().positive().max(1e7)
384
+ });
385
+ const CheckpointPolicySchema = z.object({
386
+ onIdle: z.enum(["preserve", "destroy"]).default("destroy"),
387
+ onDeadline: z.enum(["preserve", "destroy"]).default("destroy"),
388
+ onCleanExit: z.enum(["preserve", "destroy"]).default("destroy"),
389
+ onFailure: z.enum([
390
+ "preserve",
391
+ "retain-for-recovery",
392
+ "destroy"
393
+ ]).default("destroy"),
394
+ retention: DurationValueSchema.default("168h")
395
+ });
396
+ const PersistenceSpecSchema = z.object({
397
+ mounts: z.array(PersistenceMountSchema).max(16).default([]),
398
+ conversationRestore: z.enum(CONVERSATION_RESTORE_CAPABILITIES).default("filesystem_only"),
399
+ conversationRetention: DurationValueSchema.default("168h"),
400
+ sessionCompatibility: z.string().min(1).max(128).optional(),
401
+ checkpoint: CheckpointPolicySchema.prefault({}),
402
+ artifacts: z.array(PrivateArtifactSchema).max(16).optional()
403
+ });
404
+ const OutputDeclarationSchema = z.discriminatedUnion("type", [
405
+ z.object({ type: z.literal("gitSha") }),
406
+ z.object({
407
+ type: z.literal("string"),
408
+ maxLength: z.number().int().positive().max(4096).default(512)
409
+ }),
410
+ z.object({
411
+ type: z.literal("httpsUrl"),
412
+ maxLength: z.number().int().positive().max(4096).default(2048)
413
+ })
414
+ ]);
415
+ const CHECKPOINT_STATES = [
416
+ "creating",
417
+ "ready",
418
+ "failed",
419
+ "deleting",
420
+ "deleted"
421
+ ];
422
+ const OPERATION_KINDS = [
423
+ "preserve",
424
+ "restore",
425
+ "verify",
426
+ "delete",
427
+ "purge"
428
+ ];
429
+ const OPERATION_STATES = [
430
+ "pending",
431
+ "running",
432
+ "succeeded",
433
+ "failed"
434
+ ];
435
+ const CheckpointResourceSchema = z.object({
436
+ id: z.uuid(),
437
+ workspace_id: z.uuid(),
438
+ state: z.enum(CHECKPOINT_STATES),
439
+ reason_code: z.string().nullable(),
440
+ template: z.object({
441
+ name: z.string(),
442
+ version: z.string(),
443
+ digest: z.string()
444
+ }),
445
+ manifest_digest: z.string().nullable(),
446
+ logical_bytes: z.number().int().nonnegative().nullable(),
447
+ stored_bytes: z.number().int().nonnegative().nullable(),
448
+ file_count: z.number().int().nonnegative().nullable(),
449
+ mounts: z.array(z.string()),
450
+ conversation_restore: z.enum(CONVERSATION_RESTORE_CAPABILITIES),
451
+ label: z.string().nullable(),
452
+ created_at: z.iso.datetime(),
453
+ ready_at: z.iso.datetime().nullable(),
454
+ expires_at: z.iso.datetime().nullable()
455
+ });
456
+ const OperationResourceSchema = z.object({
457
+ id: z.uuid(),
458
+ kind: z.enum(OPERATION_KINDS),
459
+ state: z.enum(OPERATION_STATES),
460
+ workspace_id: z.uuid().nullable(),
461
+ checkpoint_id: z.uuid().nullable(),
462
+ result_workspace_id: z.uuid().nullable(),
463
+ reason_code: z.string().nullable(),
464
+ created_at: z.iso.datetime(),
465
+ updated_at: z.iso.datetime(),
466
+ completed_at: z.iso.datetime().nullable()
467
+ });
468
+ z.object({
469
+ retention: DurationValueSchema.optional(),
470
+ label: z.string().min(1).max(128).optional()
471
+ });
472
+ z.object({
473
+ external_id: z.string().min(1).max(256),
474
+ metadata: z.record(z.string().max(64), z.string().max(512)).optional(),
475
+ launch_input: z.record(z.string(), z.unknown()).optional()
476
+ });
477
+ const CheckpointManifestEntrySchema = z.object({
478
+ path: z.string(),
479
+ kind: z.enum([
480
+ "directory",
481
+ "file",
482
+ "symlink"
483
+ ]),
484
+ mode: z.number().int().nonnegative(),
485
+ uid: z.number().int().nonnegative(),
486
+ gid: z.number().int().nonnegative(),
487
+ mtime_ns: z.string().regex(/^\d+$/),
488
+ size: z.number().int().nonnegative(),
489
+ digest: z.string().optional(),
490
+ link_target: z.string().optional()
491
+ });
492
+ z.object({
493
+ format: z.literal("pocketcoder-checkpoint/v1"),
494
+ checkpoint_id: z.uuid(),
495
+ template_digest: z.string(),
496
+ mounts: z.array(z.object({
497
+ name: z.string(),
498
+ entries: z.array(CheckpointManifestEntrySchema)
499
+ })),
500
+ logical_bytes: z.number().int().nonnegative(),
501
+ file_count: z.number().int().nonnegative()
502
+ });
503
+ //#endregion
504
+ //#region ../contracts/src/checkpoints/archive-format.ts
505
+ const CHECKPOINT_ARCHIVE_FORMAT = "pocketcoder-checkpoint-tar/v1";
506
+ const count = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
507
+ const digest$1 = z.string().regex(/^sha256:[a-f0-9]{64}$/);
508
+ const mount = z.strictObject({
509
+ name: PersistenceMountSchema.shape.name,
510
+ logical_bytes: count,
511
+ file_count: count
512
+ });
513
+ const mounts = z.array(mount).max(16).refine((items) => new Set(items.map((item) => item.name)).size === items.length);
514
+ const CheckpointArchiveHeaderSchema = z.strictObject({
515
+ format: z.literal(CHECKPOINT_ARCHIVE_FORMAT),
516
+ checkpoint_id: z.uuid(),
517
+ workspace_id: z.uuid(),
518
+ template_digest: digest$1,
519
+ mounts
520
+ });
521
+ function validUnicode(value) {
522
+ return Buffer.from(value).toString("utf8") === value;
523
+ }
524
+ function safeCheckpointPath(value) {
525
+ return validUnicode(value) && value === value.normalize("NFC") && !value.includes("\0") && !value.includes("\\") && value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
526
+ }
527
+ function safeCheckpointLink(path, target) {
528
+ if (!target || !validUnicode(target) || target.startsWith("/") || target.includes("\0") || target.includes("\\")) return false;
529
+ const resolved = posix.normalize(posix.join(posix.dirname(path), target));
530
+ return resolved !== ".." && !resolved.startsWith("../") && !posix.isAbsolute(resolved);
531
+ }
532
+ const common = {
533
+ mount: z.number().int().min(0).max(15),
534
+ path: z.string().refine(safeCheckpointPath),
535
+ mode: z.number().int().min(0).max(511),
536
+ mtime_ns: z.string().regex(/^(0|[1-9]\d*)$/)
537
+ };
538
+ z.discriminatedUnion("kind", [
539
+ z.strictObject({
540
+ ...common,
541
+ kind: z.literal("directory"),
542
+ size: z.literal(0)
543
+ }),
544
+ z.strictObject({
545
+ ...common,
546
+ kind: z.literal("file"),
547
+ size: count,
548
+ digest: digest$1
549
+ }),
550
+ z.strictObject({
551
+ ...common,
552
+ kind: z.literal("symlink"),
553
+ mode: z.literal(511),
554
+ size: count,
555
+ digest: digest$1,
556
+ link_target: z.string()
557
+ }).refine((entry) => safeCheckpointLink(entry.path, entry.link_target) && entry.size === Buffer.byteLength(entry.link_target) && entry.digest === `sha256:${sha256Hex(entry.link_target)}`)
558
+ ]);
559
+ z.strictObject({
560
+ mounts,
561
+ manifest_digest: digest$1,
562
+ content_digest: digest$1
563
+ });
152
564
  //#endregion
153
565
  //#region ../contracts/src/common/errors.ts
154
566
  const ERROR_CODES = {
@@ -156,13 +568,16 @@ const ERROR_CODES = {
156
568
  "auth.missing_scope": 403,
157
569
  "auth.disabled_principal": 403,
158
570
  "principal.not_found": 404,
571
+ "principal.name_conflict": 409,
159
572
  "key.not_found": 404,
573
+ "request.body_too_large": 413,
160
574
  "validation.invalid": 400,
161
575
  "idempotency.conflict": 409,
162
576
  "capacity.queue_full": 429,
163
577
  "capacity.waiters_full": 429,
164
578
  "template.not_found": 404,
165
579
  "template.version_not_found": 404,
580
+ "template.version_immutable": 409,
166
581
  "template.not_authorized": 403,
167
582
  "workspace.not_found": 404,
168
583
  "workspace.external_id_conflict": 409,
@@ -177,6 +592,10 @@ const ERROR_CODES = {
177
592
  "workspace.persistence_not_enabled": 409,
178
593
  "workspace.preserving": 409,
179
594
  "checkpoint.not_found": 404,
595
+ "checkpoint.unsupported": 409,
596
+ "artifact.not_found": 404,
597
+ "artifact.workspace_active": 409,
598
+ "artifact.too_large": 413,
180
599
  "checkpoint.not_ready": 409,
181
600
  "checkpoint.none_ready": 409,
182
601
  "checkpoint.corrupt": 409,
@@ -186,24 +605,35 @@ const ERROR_CODES = {
186
605
  "restore.image_unavailable": 409,
187
606
  "restore.incompatible": 409,
188
607
  "resume.unsupported": 409,
608
+ "conversation.not_found": 404,
189
609
  "conversation.expired": 410,
190
610
  "conversation.deleted": 410,
191
611
  "operation.conflict": 409,
192
612
  "source.not_allowed": 422,
193
613
  "source.invalid_revision": 422,
194
614
  "secret.unavailable": 503,
615
+ "secret.not_found": 404,
195
616
  "storage.capacity_exhausted": 507,
196
617
  "relay.body_too_large": 413,
197
618
  "relay.route_not_allowed": 422,
198
619
  "relay.streaming_unsupported": 409,
199
620
  "relay.deadline_exceeded": 504,
200
621
  "relay.upstream_error": 502,
622
+ "agent.interrupt_unsupported": 409,
201
623
  "attachment.invalid": 400,
202
624
  "attachment.too_large": 413,
203
625
  "attachment.not_found": 404,
204
626
  "attachment.conflict": 409,
205
627
  "attachment.unsupported": 409,
206
628
  "attachment.interrupted": 503,
629
+ "maintenance.active": 503,
630
+ "maintenance.timeout": 503,
631
+ "backup.failed": 409,
632
+ "journal.pending": 503,
633
+ "account.purged": 409,
634
+ "account.purge_pending": 503,
635
+ "recovery.required": 503,
636
+ "recovery.failed": 409,
207
637
  "internal.error": 500
208
638
  };
209
639
  const errorCodes = Object.keys(ERROR_CODES);
@@ -214,6 +644,9 @@ const ErrorEnvelopeSchema = z.object({ error: z.object({
214
644
  details: z.record(z.string(), z.unknown()).optional()
215
645
  }) });
216
646
  //#endregion
647
+ //#region ../contracts/src/common/json-value.ts
648
+ const JsonValueSchema = z.json();
649
+ //#endregion
217
650
  //#region ../contracts/src/common/pagination.ts
218
651
  const CursorSchema = z.string().min(1).max(2048);
219
652
  function CursorPageSchema(item) {
@@ -229,6 +662,77 @@ function CursorQuerySchema(maxLimit, defaultLimit) {
229
662
  });
230
663
  }
231
664
  //#endregion
665
+ //#region ../contracts/src/conversations/facts.ts
666
+ const ProducerCodeSchema = z.string().regex(/^[a-z][a-z0-9_]{0,63}$/);
667
+ const ConversationFactSchema = z.discriminatedUnion("type", [
668
+ z.object({ type: z.literal("intermediate") }),
669
+ z.object({ type: z.literal("final_response") }),
670
+ z.object({ type: z.literal("tool_only") }),
671
+ z.object({
672
+ type: z.literal("activity_started"),
673
+ activity_id: z.uuid(),
674
+ kind: z.enum([
675
+ "tool",
676
+ "attachment",
677
+ "workspace",
678
+ "retrieval"
679
+ ]),
680
+ label: z.string().min(1).max(160),
681
+ started_at: z.iso.datetime().nullable(),
682
+ action: ProducerCodeSchema.optional(),
683
+ subject: ProducerCodeSchema.optional()
684
+ }),
685
+ z.object({
686
+ type: z.literal("activity_finished"),
687
+ activity_id: z.uuid(),
688
+ ended_at: z.iso.datetime().nullable(),
689
+ outcome: z.enum([
690
+ "completed",
691
+ "failed",
692
+ "interrupted",
693
+ "canceled"
694
+ ])
695
+ }),
696
+ z.object({
697
+ type: z.literal("turn_terminal"),
698
+ outcome: z.enum([
699
+ "completed",
700
+ "failed",
701
+ "interrupted",
702
+ "canceled",
703
+ "lost"
704
+ ]),
705
+ reason: ProducerCodeSchema.optional()
706
+ })
707
+ ]);
708
+ //#endregion
709
+ //#region ../contracts/src/conversations/identity.ts
710
+ const ConversationRequestIdentitySchema = z.object({
711
+ schema: z.literal("pocketcoder.conversation/v2"),
712
+ authority: z.enum(["external", "generic"]),
713
+ conversation_id: z.uuid(),
714
+ request_id: z.uuid(),
715
+ turn_id: z.uuid(),
716
+ request_fingerprint: z.string().regex(/^[0-9a-f]{64}$/)
717
+ });
718
+ const ConversationRequestReceiptSchema = z.object({
719
+ kind: z.enum(["accepted", "replayed"]),
720
+ identity: ConversationRequestIdentitySchema,
721
+ accepted_at: z.iso.datetime()
722
+ });
723
+ const ConversationMessageIdentitySchema = ConversationRequestIdentitySchema.extend({
724
+ event_id: z.uuid(),
725
+ message_id: z.string().min(1).max(256),
726
+ sequence: z.number().int().nonnegative(),
727
+ revision: z.number().int().positive(),
728
+ fact: ConversationFactSchema.optional()
729
+ });
730
+ const ConversationProtocolSupportSchema = z.object({
731
+ schema: z.literal("pocketcoder.conversation/v2"),
732
+ request_identity_authorities: z.array(z.enum(["external", "generic"])),
733
+ producer_capabilities: z.null()
734
+ });
735
+ //#endregion
232
736
  //#region ../contracts/src/conversations/conversation.ts
233
737
  const CONVERSATION_ROLES = [
234
738
  "user",
@@ -242,14 +746,21 @@ const ConversationMessageInputSchema = z.object({
242
746
  role: z.enum(CONVERSATION_ROLES),
243
747
  content: z.string().max(262144),
244
748
  occurred_at: z.iso.datetime(),
245
- metadata: ConversationMetadataSchema.default({})
749
+ metadata: ConversationMetadataSchema.default({}),
750
+ identity: ConversationMessageIdentitySchema.nullable().optional()
751
+ });
752
+ const ConversationMessageResourceSchema = ConversationMessageInputSchema.extend({
753
+ seq: z.number().int().positive(),
754
+ identity: ConversationMessageIdentitySchema.nullable().default(null)
246
755
  });
247
- const ConversationMessageResourceSchema = ConversationMessageInputSchema.extend({ seq: z.number().int().positive() });
248
756
  CursorQuerySchema(200, 100);
249
- const ConversationPageSchema = CursorPageSchema(ConversationMessageResourceSchema).extend({ retention: z.object({
250
- status: z.literal("retained"),
251
- expires_at: z.iso.datetime().nullable()
252
- }) });
757
+ const ConversationPageSchema = CursorPageSchema(ConversationMessageResourceSchema).extend({
758
+ protocol: ConversationProtocolSupportSchema.nullable().default(null),
759
+ retention: z.object({
760
+ status: z.literal("retained"),
761
+ expires_at: z.iso.datetime().nullable()
762
+ })
763
+ });
253
764
  const ConversationResumeOutcomeSchema = z.object({
254
765
  status: z.literal("supported"),
255
766
  reason: z.null(),
@@ -257,6 +768,160 @@ const ConversationResumeOutcomeSchema = z.object({
257
768
  checkpoint_id: z.uuid()
258
769
  });
259
770
  //#endregion
771
+ //#region ../contracts/src/conversations/document.ts
772
+ const CONVERSATION_RECORD_STATUSES = [
773
+ "completed",
774
+ "failed",
775
+ "interrupted",
776
+ "canceled",
777
+ "lost",
778
+ "message",
779
+ "legacy"
780
+ ];
781
+ const ConversationActivitySchema = z.object({
782
+ activity_id: z.uuid(),
783
+ kind: z.string(),
784
+ label: z.string(),
785
+ action: z.string().nullable(),
786
+ subject: z.string().nullable(),
787
+ started_at: z.iso.datetime().nullable(),
788
+ ended_at: z.iso.datetime().nullable(),
789
+ outcome: z.string().nullable()
790
+ });
791
+ z.object({
792
+ schema: z.literal("pocketcoder.conversation-record/v1"),
793
+ turn_id: z.uuid().nullable(),
794
+ request: ConversationRequestIdentitySchema.nullable(),
795
+ status: z.enum(CONVERSATION_RECORD_STATUSES),
796
+ reason: z.string().nullable(),
797
+ messages: z.array(ConversationMessageResourceSchema),
798
+ activities: z.array(ConversationActivitySchema),
799
+ final_response: z.string().nullable(),
800
+ started_at: z.iso.datetime().nullable(),
801
+ ended_at: z.iso.datetime().nullable()
802
+ });
803
+ const ConversationSummarySchema = z.object({
804
+ conversation_id: z.uuid().nullable(),
805
+ workspace_id: z.uuid(),
806
+ legacy: z.boolean(),
807
+ status: z.enum([
808
+ "retained",
809
+ "deleted",
810
+ "expired"
811
+ ]),
812
+ message_count: z.number().int().nonnegative(),
813
+ turn_count: z.number().int().nonnegative(),
814
+ last_activity_at: z.iso.datetime(),
815
+ expires_at: z.iso.datetime().nullable()
816
+ });
817
+ const ConversationListSchema = z.object({ items: z.array(ConversationSummarySchema) });
818
+ const ConversationDocumentPageSchema = CursorPageSchema(ConversationMessageResourceSchema).extend({
819
+ conversation: ConversationSummarySchema,
820
+ protocol: ConversationProtocolSupportSchema
821
+ });
822
+ const ConversationLiveEventSchema = z.discriminatedUnion("type", [z.object({
823
+ type: z.literal("turn"),
824
+ conversation_id: z.uuid(),
825
+ turn_id: z.uuid(),
826
+ messages: z.array(ConversationMessageResourceSchema.omit({ seq: true }))
827
+ }), z.object({
828
+ type: z.literal("committed"),
829
+ conversation_id: z.uuid().nullable(),
830
+ turn_id: z.uuid().nullable(),
831
+ status: z.enum(CONVERSATION_RECORD_STATUSES),
832
+ last_seq: z.number().int().nonnegative()
833
+ })]);
834
+ //#endregion
835
+ //#region ../contracts/src/previews/view-session.ts
836
+ const HttpsOriginSchema = z.string().refine((value) => {
837
+ try {
838
+ const url = new URL(value);
839
+ return url.protocol === "https:" && url.origin === value && !url.username && !url.password;
840
+ } catch {
841
+ return false;
842
+ }
843
+ }, "An exact HTTPS origin is required.");
844
+ const ViewSessionSchema = z.discriminatedUnion("mode", [
845
+ z.strictObject({ mode: z.literal("local") }),
846
+ z.strictObject({ mode: z.literal("top_level") }),
847
+ z.strictObject({
848
+ mode: z.literal("embedded"),
849
+ parentOrigin: HttpsOriginSchema
850
+ })
851
+ ]);
852
+ z.strictObject({ session: ViewSessionSchema.default({ mode: "local" }) });
853
+ //#endregion
854
+ //#region ../contracts/src/displays/display.ts
855
+ const DisplaySchema = z.strictObject({ mode: z.enum(["desktop", "browser"]) });
856
+ z.strictObject({
857
+ control: z.boolean().default(false),
858
+ session: ViewSessionSchema.default({ mode: "local" })
859
+ });
860
+ const BROWSER_WIDTH = 1280;
861
+ const coordinate = (max) => z.number().int().min(0).max(max - 1);
862
+ const url = z.string().max(2048).refine((value) => {
863
+ try {
864
+ const parsed = new URL(value);
865
+ return ["http:", "https:"].includes(parsed.protocol) && !parsed.username && !parsed.password;
866
+ } catch {
867
+ return false;
868
+ }
869
+ }, "Browser navigation requires an HTTP or HTTPS URL without credentials.");
870
+ z.discriminatedUnion("action", [
871
+ z.strictObject({
872
+ action: z.literal("navigate"),
873
+ url
874
+ }),
875
+ z.strictObject({
876
+ action: z.literal("click"),
877
+ x: coordinate(BROWSER_WIDTH),
878
+ y: coordinate(800)
879
+ }),
880
+ z.strictObject({
881
+ action: z.literal("text"),
882
+ text: z.string().min(1).max(1024)
883
+ }),
884
+ z.strictObject({
885
+ action: z.literal("key"),
886
+ key: z.enum([
887
+ "Enter",
888
+ "Tab",
889
+ "Backspace",
890
+ "Delete",
891
+ "Escape",
892
+ "ArrowLeft",
893
+ "ArrowRight",
894
+ "ArrowUp",
895
+ "ArrowDown",
896
+ "Home",
897
+ "End",
898
+ "PageUp",
899
+ "PageDown"
900
+ ])
901
+ }),
902
+ z.strictObject({
903
+ action: z.literal("scroll"),
904
+ x: coordinate(BROWSER_WIDTH),
905
+ y: coordinate(800),
906
+ deltaY: z.number().int().min(-1e3).max(1e3)
907
+ })
908
+ ]);
909
+ const ScreenshotResourceSchema = z.strictObject({
910
+ kind: z.literal("screenshot"),
911
+ id: z.uuid(),
912
+ workspace_id: z.uuid(),
913
+ content_type: z.literal("image/png"),
914
+ bytes: z.number().int().min(1).max(4 * 1024 ** 2),
915
+ digest: z.string().regex(/^sha256:[a-f0-9]{64}$/),
916
+ expires_at: z.iso.datetime()
917
+ });
918
+ const ScreenshotCapturePayload = z.strictObject({
919
+ output_id: z.uuid(),
920
+ credential: z.string().min(32).max(128),
921
+ url: z.url(),
922
+ expires_at: z.iso.datetime()
923
+ });
924
+ //#endregion
260
925
  //#region ../contracts/src/network/network.ts
261
926
  const DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
262
927
  function isDomain(value) {
@@ -289,173 +954,175 @@ const NETWORK_STATES = [
289
954
  const NetworkEventInputSchema = z.object({
290
955
  source_seq: z.number().int().positive(),
291
956
  occurred_at: z.iso.datetime(),
292
- decision: z.enum(["allow", "deny"]),
293
- transport: z.enum(["http", "https"]),
294
- host: z.string().min(1).max(253),
295
- port: z.number().int().min(1).max(65535),
296
- method: z.string().max(32).nullable().default(null),
297
- path: z.string().max(2048).refine((path) => !path.includes("?") && !path.includes("#"), "path cannot contain query or fragment").nullable().default(null),
298
- matched_rule: z.string().max(512).nullable().default(null),
299
- reason: z.string().min(1).max(128)
300
- });
301
- z.object({
302
- source_session_id: z.uuid(),
303
- events: z.array(NetworkEventInputSchema).min(1).max(100)
304
- });
305
- const NetworkEventSchema = NetworkEventInputSchema.omit({ source_seq: true }).extend({
306
- seq: z.number().int().positive(),
307
- workspace_id: z.uuid(),
308
- source_session_id: z.uuid(),
309
- source_seq: z.number().int().positive()
310
- });
311
- CursorQuerySchema(200, 100);
312
- const NetworkEventPageSchema = CursorPageSchema(NetworkEventSchema);
313
- //#endregion
314
- //#region ../contracts/src/persistence/persistence.ts
315
- const DurationValueSchema = z.string().refine(isDuration, { message: "expected a duration like 15s, 20m, or 2h" });
316
- const LAUNCH_MODES = ["create", "restore"];
317
- const CONVERSATION_RESTORE_CAPABILITIES = [
318
- "supported",
319
- "filesystem_only",
320
- "unknown"
321
- ];
322
- const SourceDescriptorSchema = z.object({
323
- kind: z.literal("git"),
324
- repository: z.string().min(1).max(64).regex(/^[a-z0-9]([a-z0-9._-]{0,62}[a-z0-9])?$/),
325
- revision: z.string().min(1).max(256).refine((value) => !value.startsWith("-") && !value.startsWith("/") && !value.endsWith("/") && !value.endsWith(".") && !value.endsWith(".lock") && value !== "@" && !value.includes("..") && !value.includes("//") && !value.includes("@{") && !/[~^:?*[\]\\\s]/u.test(value) && [...value].every((character) => {
326
- const code = character.codePointAt(0) ?? 0;
327
- return code >= 32 && code !== 127;
328
- }), { message: "revision is not a safe branch, tag, or commit name" })
329
- });
330
- const ResolvedSourceSchema = z.object({
331
- kind: z.literal("git"),
332
- repository: z.string().min(1).max(64),
333
- requested_revision: z.string().min(1).max(256),
334
- resolved_commit: z.string().regex(/^[0-9a-f]{40,64}$/)
335
- });
336
- const PersistenceMountSchema = z.object({
337
- name: z.string().min(1).max(64).regex(/^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/),
338
- target: z.string().min(1),
339
- maxBytes: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
340
- maxFiles: z.number().int().positive().max(1e7)
341
- });
342
- const CheckpointPolicySchema = z.object({
343
- onIdle: z.enum(["preserve", "destroy"]).default("destroy"),
344
- onDeadline: z.enum(["preserve", "destroy"]).default("destroy"),
345
- onCleanExit: z.enum(["preserve", "destroy"]).default("destroy"),
346
- onFailure: z.enum([
347
- "preserve",
348
- "retain-for-recovery",
349
- "destroy"
350
- ]).default("destroy"),
351
- retention: DurationValueSchema.default("168h")
352
- });
353
- const PersistenceSpecSchema = z.object({
354
- mounts: z.array(PersistenceMountSchema).max(16).default([]),
355
- conversationRestore: z.enum(CONVERSATION_RESTORE_CAPABILITIES).default("filesystem_only"),
356
- conversationRetention: DurationValueSchema.default("168h"),
357
- sessionCompatibility: z.string().min(1).max(128).optional(),
358
- checkpoint: CheckpointPolicySchema.prefault({})
359
- });
360
- const OutputDeclarationSchema = z.discriminatedUnion("type", [
361
- z.object({ type: z.literal("gitSha") }),
362
- z.object({
363
- type: z.literal("string"),
364
- maxLength: z.number().int().positive().max(4096).default(512)
365
- }),
366
- z.object({
367
- type: z.literal("httpsUrl"),
368
- maxLength: z.number().int().positive().max(4096).default(2048)
369
- })
370
- ]);
371
- const CHECKPOINT_STATES = [
372
- "creating",
373
- "ready",
374
- "failed",
375
- "deleting",
376
- "deleted"
377
- ];
378
- const OPERATION_KINDS = [
379
- "preserve",
380
- "restore",
381
- "verify",
382
- "delete",
383
- "purge"
384
- ];
385
- const OPERATION_STATES = [
386
- "pending",
387
- "running",
388
- "succeeded",
389
- "failed"
390
- ];
391
- const CheckpointResourceSchema = z.object({
392
- id: z.uuid(),
393
- workspace_id: z.uuid(),
394
- state: z.enum(CHECKPOINT_STATES),
395
- reason_code: z.string().nullable(),
396
- template: z.object({
397
- name: z.string(),
398
- version: z.string(),
399
- digest: z.string()
400
- }),
401
- manifest_digest: z.string().nullable(),
402
- logical_bytes: z.number().int().nonnegative().nullable(),
403
- stored_bytes: z.number().int().nonnegative().nullable(),
404
- file_count: z.number().int().nonnegative().nullable(),
405
- mounts: z.array(z.string()),
406
- conversation_restore: z.enum(CONVERSATION_RESTORE_CAPABILITIES),
407
- label: z.string().nullable(),
408
- created_at: z.iso.datetime(),
409
- ready_at: z.iso.datetime().nullable(),
410
- expires_at: z.iso.datetime().nullable()
411
- });
412
- const OperationResourceSchema = z.object({
413
- id: z.uuid(),
414
- kind: z.enum(OPERATION_KINDS),
415
- state: z.enum(OPERATION_STATES),
416
- workspace_id: z.uuid().nullable(),
417
- checkpoint_id: z.uuid().nullable(),
418
- result_workspace_id: z.uuid().nullable(),
419
- reason_code: z.string().nullable(),
420
- created_at: z.iso.datetime(),
421
- updated_at: z.iso.datetime(),
422
- completed_at: z.iso.datetime().nullable()
957
+ decision: z.enum(["allow", "deny"]),
958
+ transport: z.enum(["http", "https"]),
959
+ host: z.string().min(1).max(253),
960
+ port: z.number().int().min(1).max(65535),
961
+ method: z.string().max(32).nullable().default(null),
962
+ path: z.string().max(2048).refine((path) => !path.includes("?") && !path.includes("#"), "path cannot contain query or fragment").nullable().default(null),
963
+ matched_rule: z.string().max(512).nullable().default(null),
964
+ reason: z.string().min(1).max(128)
423
965
  });
424
966
  z.object({
425
- retention: DurationValueSchema.optional(),
426
- label: z.string().min(1).max(128).optional()
967
+ source_session_id: z.uuid(),
968
+ events: z.array(NetworkEventInputSchema).min(1).max(100)
427
969
  });
428
- z.object({
429
- external_id: z.string().min(1).max(256),
430
- metadata: z.record(z.string().max(64), z.string().max(512)).optional(),
431
- launch_input: z.record(z.string(), z.unknown()).optional()
970
+ const NetworkEventSchema = NetworkEventInputSchema.omit({ source_seq: true }).extend({
971
+ seq: z.number().int().positive(),
972
+ workspace_id: z.uuid(),
973
+ source_session_id: z.uuid(),
974
+ source_seq: z.number().int().positive()
432
975
  });
433
- const CheckpointManifestEntrySchema = z.object({
434
- path: z.string(),
435
- kind: z.enum([
436
- "directory",
437
- "file",
438
- "symlink"
976
+ CursorQuerySchema(200, 100);
977
+ const NetworkEventPageSchema = CursorPageSchema(NetworkEventSchema);
978
+ //#endregion
979
+ //#region ../contracts/src/workspaces/workload-operation.ts
980
+ const WorkloadExecutionReferenceSchema = z.strictObject({
981
+ workspaceId: z.uuid(),
982
+ incarnationId: z.string().min(1).max(256),
983
+ owner: z.enum(["process", "conversation-request"]),
984
+ requestId: z.uuid()
985
+ });
986
+ const WorkloadAcceptanceSchema = z.strictObject({
987
+ execution: WorkloadExecutionReferenceSchema,
988
+ callerPrincipalId: z.uuid(),
989
+ templateDigest: WorkloadDigestSchema,
990
+ adapterDigest: WorkloadDigestSchema,
991
+ operation: WorkloadNameSchema,
992
+ input: z.record(z.string(), JsonValueSchema),
993
+ inputs: AcceptedInputManifestSchema,
994
+ bindingSetDigest: WorkloadDigestSchema,
995
+ fingerprint: WorkloadDigestSchema
996
+ }).superRefine((value, ctx) => {
997
+ const { fingerprint, ...facts } = value;
998
+ if (fingerprint !== digestOf(facts)) ctx.addIssue({
999
+ code: "custom",
1000
+ path: ["fingerprint"],
1001
+ message: "acceptance fingerprint does not match its facts"
1002
+ });
1003
+ if (value.inputs.workspaceId !== value.execution.workspaceId || value.inputs.incarnationId !== value.execution.incarnationId) ctx.addIssue({
1004
+ code: "custom",
1005
+ path: ["inputs"],
1006
+ message: "accepted input identity must match the execution"
1007
+ });
1008
+ });
1009
+ const WorkloadCancelSchema = z.strictObject({
1010
+ execution: WorkloadExecutionReferenceSchema,
1011
+ cancelId: z.uuid(),
1012
+ scope: z.enum(["operation", "workspace"])
1013
+ });
1014
+ //#endregion
1015
+ //#region ../contracts/src/outputs/export-reference.ts
1016
+ const ExportObjectReferenceSchema = z.strictObject({
1017
+ objectId: z.uuid(),
1018
+ objectVersion: WorkloadDigestSchema,
1019
+ name: z.string().min(1).max(255),
1020
+ sizeBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
1021
+ mediaType: AttachmentMediaTypeSchema,
1022
+ digest: WorkloadDigestSchema
1023
+ });
1024
+ z.strictObject({
1025
+ exportId: z.uuid(),
1026
+ execution: WorkloadExecutionReferenceSchema,
1027
+ manifestDigest: WorkloadDigestSchema,
1028
+ completeness: z.enum([
1029
+ "complete",
1030
+ "partial",
1031
+ "lost"
439
1032
  ]),
440
- mode: z.number().int().nonnegative(),
441
- uid: z.number().int().nonnegative(),
442
- gid: z.number().int().nonnegative(),
443
- mtime_ns: z.string().regex(/^\d+$/),
444
- size: z.number().int().nonnegative(),
445
- digest: z.string().optional(),
446
- link_target: z.string().optional()
1033
+ publication: z.enum([
1034
+ "published",
1035
+ "unavailable",
1036
+ "expired"
1037
+ ]),
1038
+ objects: z.array(ExportObjectReferenceSchema),
1039
+ errors: z.array(z.string().min(1).max(512)),
1040
+ publishedAt: z.iso.datetime(),
1041
+ expiresAt: z.iso.datetime()
1042
+ }).superRefine((value, ctx) => {
1043
+ const { execution, objects, completeness, errors } = value;
1044
+ if (value.manifestDigest !== digestOf({
1045
+ execution,
1046
+ objects,
1047
+ completeness,
1048
+ errors
1049
+ })) ctx.addIssue({
1050
+ code: "custom",
1051
+ path: ["manifestDigest"],
1052
+ message: "export digest does not match its frozen manifest"
1053
+ });
1054
+ if (Date.parse(value.expiresAt) <= Date.parse(value.publishedAt)) ctx.addIssue({
1055
+ code: "custom",
1056
+ path: ["expiresAt"],
1057
+ message: "export retention must be positive"
1058
+ });
1059
+ const ids = value.objects.map((object) => object.objectId);
1060
+ if (new Set(ids).size !== ids.length) ctx.addIssue({
1061
+ code: "custom",
1062
+ path: ["objects"],
1063
+ message: "export object IDs must be unique"
1064
+ });
447
1065
  });
448
- z.object({
449
- format: z.literal("pocketcoder-checkpoint/v1"),
450
- checkpoint_id: z.uuid(),
451
- template_digest: z.string(),
452
- mounts: z.array(z.object({
453
- name: z.string(),
454
- entries: z.array(CheckpointManifestEntrySchema)
455
- })),
456
- logical_bytes: z.number().int().nonnegative(),
457
- file_count: z.number().int().nonnegative()
1066
+ //#endregion
1067
+ //#region ../contracts/src/previews/preview.ts
1068
+ const PreviewNameSchema = z.string().regex(/^[a-z][a-z0-9-]{0,19}$/).refine((name) => !(/* @__PURE__ */ new Set([
1069
+ "display",
1070
+ "control",
1071
+ "agent",
1072
+ "supervisor",
1073
+ "vnc",
1074
+ "cdp"
1075
+ ])).has(name) && !name.startsWith("pc-"), "reserved preview name");
1076
+ const RESERVED_PORTS = /* @__PURE__ */ new Set([
1077
+ 3284,
1078
+ 5900,
1079
+ 5901,
1080
+ 6080,
1081
+ 9222,
1082
+ 9229
1083
+ ]);
1084
+ const PreviewSchema = z.strictObject({ port: z.number().int().min(1024).max(65535).refine((port) => !RESERVED_PORTS.has(port), "reserved port") });
1085
+ const PreviewsSchema = z.record(PreviewNameSchema, PreviewSchema).refine((previews) => Object.keys(previews).length <= 16, "at most 16 previews");
1086
+ const PreviewOpenResponseSchema = z.object({
1087
+ url: z.url(),
1088
+ expires_at: z.iso.datetime()
458
1089
  });
1090
+ const PreviewListSchema = z.array(z.object({
1091
+ name: PreviewNameSchema,
1092
+ port: z.number().int()
1093
+ }));
1094
+ const PreviewSocketPayload = z.discriminatedUnion("op", [
1095
+ z.object({
1096
+ op: z.literal("open"),
1097
+ request_id: z.uuid(),
1098
+ name: z.union([PreviewNameSchema, z.literal("display")]),
1099
+ path: z.string().max(8192),
1100
+ origin: z.url(),
1101
+ cookie: z.string().max(8192).optional(),
1102
+ protocols: z.array(z.string().max(128)).max(16)
1103
+ }),
1104
+ z.object({
1105
+ op: z.literal("ready"),
1106
+ request_id: z.uuid(),
1107
+ protocol: z.string().max(128)
1108
+ }),
1109
+ z.object({
1110
+ op: z.literal("data"),
1111
+ request_id: z.uuid(),
1112
+ seq: z.number().int().nonnegative(),
1113
+ binary: z.boolean(),
1114
+ content_b64: z.string().max(87384)
1115
+ }),
1116
+ z.object({
1117
+ op: z.literal("ack"),
1118
+ request_id: z.uuid(),
1119
+ seq: z.number().int().nonnegative()
1120
+ }),
1121
+ z.object({
1122
+ op: z.literal("close"),
1123
+ request_id: z.uuid()
1124
+ })
1125
+ ]);
459
1126
  //#endregion
460
1127
  //#region ../contracts/src/workspaces/workspace.ts
461
1128
  const WORKSPACE_STATES = [
@@ -479,6 +1146,15 @@ const TERMINAL_STATES = [
479
1146
  "preserved"
480
1147
  ];
481
1148
  const REASON_CODES = [
1149
+ ...[
1150
+ "admission_global_capacity",
1151
+ "admission_principal_capacity",
1152
+ "admission_template_capacity",
1153
+ "admission_policy_global_capacity",
1154
+ "admission_actor_capacity",
1155
+ "admission_denied",
1156
+ "admission_unavailable"
1157
+ ],
482
1158
  "child_exit_success",
483
1159
  "child_exit_failure",
484
1160
  "setup_failed",
@@ -513,6 +1189,7 @@ const AGENT_STATES = [
513
1189
  "running",
514
1190
  "stable"
515
1191
  ];
1192
+ const AgentInterruptResponseSchema = z.object({ status: z.enum(["requested", "idle"]) });
516
1193
  z.object({
517
1194
  external_id: z.string().min(1).max(256),
518
1195
  template: z.object({
@@ -528,10 +1205,15 @@ const TemplateRefSchema = z.object({
528
1205
  version: z.string(),
529
1206
  digest: z.string()
530
1207
  });
1208
+ const AgentMetadataSchema = z.object({
1209
+ type: z.string(),
1210
+ model: z.string().min(1).max(255).nullable()
1211
+ });
531
1212
  const WorkspaceResourceSchema = z.object({
532
1213
  id: z.uuid(),
533
1214
  external_id: z.string(),
534
1215
  template: TemplateRefSchema,
1216
+ agent: AgentMetadataSchema.nullable(),
535
1217
  state: z.enum(WORKSPACE_STATES),
536
1218
  reason_code: z.enum(REASON_CODES).nullable(),
537
1219
  agent_state: z.enum(AGENT_STATES),
@@ -585,19 +1267,21 @@ z.object({
585
1267
  limit: z.coerce.number().int().positive().max(200).default(50),
586
1268
  cursor: z.string().optional()
587
1269
  });
588
- //#endregion
589
- //#region ../contracts/src/protocol/api.ts
590
- const TemplatePageSchema = CursorPageSchema(z.object({
1270
+ const TemplateListItemSchema = z.object({
591
1271
  name: z.string(),
592
1272
  version: z.string(),
593
1273
  digest: z.string(),
1274
+ agent: AgentMetadataSchema.nullable(),
594
1275
  description: z.string().optional(),
595
1276
  status: z.enum([
596
1277
  "active",
597
1278
  "available",
598
1279
  "retired"
599
1280
  ])
600
- }));
1281
+ });
1282
+ //#endregion
1283
+ //#region ../contracts/src/protocol/api.ts
1284
+ const TemplatePageSchema = CursorPageSchema(TemplateListItemSchema);
601
1285
  CursorQuerySchema(200, 100);
602
1286
  const CheckpointPageSchema = CursorPageSchema(CheckpointResourceSchema);
603
1287
  CursorQuerySchema(200, 50).extend({ state: z.enum(CHECKPOINT_STATES).optional() });
@@ -687,6 +1371,52 @@ const LogChunkSchema = z.object({
687
1371
  });
688
1372
  CursorQuerySchema(1e3, 200);
689
1373
  const LogPageSchema = CursorPageSchema(LogChunkSchema);
1374
+ const SecretTypeSchema = z.enum([
1375
+ "setup-issuer",
1376
+ "runtime-issuer",
1377
+ "registry"
1378
+ ]);
1379
+ const SecretNameSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/);
1380
+ z.object({ name: SecretNameSchema });
1381
+ const issuerUrl = z.url().refine((value) => {
1382
+ if (!URL.canParse(value)) return false;
1383
+ const url = new URL(value);
1384
+ return url.protocol === "https:" && !url.username && !url.password && !url.search && !url.hash;
1385
+ });
1386
+ const IssuerConfigSchema = z.strictObject({
1387
+ url: issuerUrl,
1388
+ authorization: z.string().min(1).max(16384),
1389
+ policy: z.record(z.string(), z.json()).meta({
1390
+ type: "object",
1391
+ additionalProperties: true
1392
+ })
1393
+ });
1394
+ const RegistryConfigSchema = z.strictObject({
1395
+ server: z.string().max(255).regex(/^[A-Za-z0-9.-]+(?::[0-9]{1,5})?$/),
1396
+ username: z.string().min(1).max(1024),
1397
+ password: z.string().min(1).max(16384)
1398
+ });
1399
+ z.discriminatedUnion("type", [
1400
+ z.strictObject({
1401
+ type: z.literal("setup-issuer"),
1402
+ value: IssuerConfigSchema
1403
+ }),
1404
+ z.strictObject({
1405
+ type: z.literal("runtime-issuer"),
1406
+ value: IssuerConfigSchema
1407
+ }),
1408
+ z.strictObject({
1409
+ type: z.literal("registry"),
1410
+ value: RegistryConfigSchema
1411
+ })
1412
+ ]);
1413
+ const SecretResourceSchema = z.strictObject({
1414
+ name: SecretNameSchema,
1415
+ type: SecretTypeSchema,
1416
+ updated_at: z.iso.datetime(),
1417
+ retired_at: z.iso.datetime().nullable()
1418
+ });
1419
+ const SecretListSchema = z.strictObject({ items: z.array(SecretResourceSchema) });
690
1420
  //#endregion
691
1421
  //#region ../contracts/src/templates/template-constants.ts
692
1422
  function isAbsolutePath(value) {
@@ -695,7 +1425,7 @@ function isAbsolutePath(value) {
695
1425
  const SECRET_ENV_PATTERN = /(SECRET|TOKEN|PASSWORD|PASSWD|API_?KEY|PRIVATE_?KEY|CREDENTIAL)/i;
696
1426
  const SECRET_REFERENCE_PREFIX = "secretRef:";
697
1427
  //#endregion
698
- //#region ../contracts/src/templates/template-schema.ts
1428
+ //#region ../contracts/src/templates/template-alpha-schema.ts
699
1429
  const NAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
700
1430
  const SEMVER_RE = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
701
1431
  const IMAGE_DIGEST_RE = /^[^\s@]+@sha256:[0-9a-f]{64}$/;
@@ -722,10 +1452,12 @@ const HarnessSchema = z.object({
722
1452
  cwd: z.string().refine(isAbsolutePath, "expected an absolute path").optional()
723
1453
  });
724
1454
  const AgentSchema = HarnessSchema.extend({
1455
+ model: z.string().min(1).max(255).optional().describe("Display metadata only; does not configure the agent model."),
725
1456
  type: z.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/).default("custom"),
726
1457
  transport: z.enum(["pty", "acp"]).default("pty"),
727
1458
  termWidth: z.number().int().min(10).max(65535).optional(),
728
- stateFile: z.string().refine(isAbsolutePath, "expected an absolute path").optional()
1459
+ stateFile: z.string().refine(isAbsolutePath, "expected an absolute path").optional(),
1460
+ interruptKeys: z.string().min(1).max(16).optional()
729
1461
  });
730
1462
  const TerminalSchema = z.object({
731
1463
  command: CommandSchema,
@@ -808,6 +1540,7 @@ const CheckpointHookSchema = z.object({
808
1540
  const TemplateSpecSchema = z.object({
809
1541
  version: z.string().regex(SEMVER_RE),
810
1542
  image: z.string().regex(IMAGE_DIGEST_RE, { message: "image must be digest-pinned (repo@sha256:<64 hex>)" }),
1543
+ imagePullSecret: z.string().refine((value) => value.startsWith("secretRef:") && SecretNameSchema.safeParse(value.slice(10)).success, "imagePullSecret must name a stored secret").optional(),
811
1544
  command: CommandSchema.default([
812
1545
  "/usr/local/bin/pocketcoder-supervisor",
813
1546
  "supervise",
@@ -818,6 +1551,8 @@ const TemplateSpecSchema = z.object({
818
1551
  agent: AgentSchema.optional(),
819
1552
  harness: HarnessSchema.optional(),
820
1553
  terminal: TerminalSchema.optional(),
1554
+ previews: PreviewsSchema.optional(),
1555
+ display: DisplaySchema.optional(),
821
1556
  env: EnvSchema.default({}),
822
1557
  resources: ResourcesSchema,
823
1558
  timeouts: TimeoutsSchema.prefault({}),
@@ -840,7 +1575,11 @@ const TemplateSpecSchema = z.object({
840
1575
  message: "either agent or harness is required"
841
1576
  });
842
1577
  if (!spec.agent) return;
843
- if (spec.agent.transport === "acp") for (const field of ["termWidth", "stateFile"]) {
1578
+ if (spec.agent.transport === "acp") for (const field of [
1579
+ "termWidth",
1580
+ "stateFile",
1581
+ "interruptKeys"
1582
+ ]) {
844
1583
  if (spec.agent[field] === void 0) continue;
845
1584
  ctx.addIssue({
846
1585
  code: "custom",
@@ -876,7 +1615,7 @@ const TemplateSpecSchema = z.object({
876
1615
  };
877
1616
  });
878
1617
  const TemplateManifestBaseSchema = z.object({
879
- apiVersion: z.literal("pocketcoder.dev/v1alpha1"),
1618
+ apiVersion: z.literal("pocketcoder.dev/v1alpha1", { error: "unsupported template format for the alpha runtime; canonical workloads require an installed workload runtime" }),
880
1619
  kind: z.literal("Template"),
881
1620
  metadata: z.object({
882
1621
  name: z.string().regex(NAME_RE),
@@ -885,61 +1624,105 @@ const TemplateManifestBaseSchema = z.object({
885
1624
  spec: TemplateSpecSchema
886
1625
  });
887
1626
  //#endregion
888
- //#region ../contracts/src/templates/template-runtime.ts
889
- const LEGACY_AGENTAPI_SERVICE = ServiceSchema.parse({
890
- baseUrl: "http://127.0.0.1:3284",
891
- routes: [
892
- {
893
- method: "GET",
894
- path: "/status"
895
- },
896
- {
897
- method: "GET",
898
- path: "/messages",
899
- query: ["after"]
900
- },
901
- {
902
- method: "POST",
903
- path: "/message"
904
- }
905
- ]
1627
+ //#region ../contracts/src/workspaces/finite-process.ts
1628
+ const ProcessReferenceSchema = WorkloadExecutionReferenceSchema.extend({ owner: z.literal("process") });
1629
+ const FiniteProcessFactSchema = z.strictObject({
1630
+ execution: ProcessReferenceSchema,
1631
+ fingerprint: WorkloadDigestSchema,
1632
+ phase: z.enum([
1633
+ "claimed",
1634
+ "running",
1635
+ "exited",
1636
+ "lost"
1637
+ ]),
1638
+ processGroupId: z.number().int().positive().nullable(),
1639
+ claimedAt: z.iso.datetime(),
1640
+ startedAt: z.iso.datetime().nullable(),
1641
+ endedAt: z.iso.datetime().nullable(),
1642
+ exitCode: z.number().int().nullable(),
1643
+ signal: z.string().min(1).nullable()
1644
+ }).superRefine((fact, ctx) => {
1645
+ if ((fact.phase === "exited" || fact.phase === "lost") !== (fact.endedAt !== null)) ctx.addIssue({
1646
+ code: "custom",
1647
+ message: "terminal facts require an end time"
1648
+ });
1649
+ if (fact.phase === "exited" && fact.exitCode === null === (fact.signal === null)) ctx.addIssue({
1650
+ code: "custom",
1651
+ message: "exit requires exactly one observed code or signal"
1652
+ });
1653
+ if (fact.phase !== "exited" && (fact.exitCode !== null || fact.signal !== null)) ctx.addIssue({
1654
+ code: "custom",
1655
+ message: "unobserved exits cannot have a code or signal"
1656
+ });
1657
+ if (fact.phase === "running" && (!fact.processGroupId || !fact.startedAt)) ctx.addIssue({
1658
+ code: "custom",
1659
+ message: "running facts require an owned process group and start time"
1660
+ });
906
1661
  });
907
- const AGENTAPI_SERVICE = ServiceSchema.parse({
908
- ...LEGACY_AGENTAPI_SERVICE,
909
- routes: [...LEGACY_AGENTAPI_SERVICE.routes, {
910
- method: "GET",
911
- path: "/events",
912
- responseMode: "stream",
913
- maxResponseBytes: 4194304,
914
- deadlineSeconds: 300
915
- }]
1662
+ const WaitingRuntimeSpecSchema = z.strictObject({
1663
+ incarnationId: z.string().min(1).max(256),
1664
+ profile: WorkloadProfileSchema,
1665
+ operations: z.record(z.string(), HarnessSchema),
1666
+ infrastructure: z.record(z.string(), HarnessSchema)
1667
+ });
1668
+ const ProcessStartPayload = z.strictObject({
1669
+ execution: ProcessReferenceSchema,
1670
+ fingerprint: WorkloadDigestSchema,
1671
+ input: z.record(z.string(), JsonValueSchema).default({}),
1672
+ inputDirectory: z.string().min(1).optional(),
1673
+ operation: z.string().min(1).max(64)
1674
+ });
1675
+ const ProcessTargetPayload = z.strictObject({ execution: ProcessReferenceSchema });
1676
+ const ProcessCancelPayload = ProcessTargetPayload.extend({
1677
+ cancelId: z.uuid(),
1678
+ signal: z.enum(["SIGTERM", "SIGKILL"]),
1679
+ deadlineAt: z.iso.datetime()
1680
+ });
1681
+ const ProcessCancelResultPayload = ProcessTargetPayload.extend({
1682
+ cancelId: z.uuid(),
1683
+ status: z.enum([
1684
+ "canceled",
1685
+ "requested",
1686
+ "terminal"
1687
+ ])
1688
+ });
1689
+ //#endregion
1690
+ //#region ../contracts/src/secrets/credential-slot.ts
1691
+ const CredentialSlotSchema = z.strictObject({
1692
+ policyRef: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,254}$/).refine((value) => !value.includes("..")),
1693
+ policyVersion: z.string().min(1).max(128),
1694
+ binding: z.enum(["fixed-scope", "post-create-grant"]),
1695
+ delivery: z.literal("workspace-expiring-file"),
1696
+ maxLeaseSeconds: z.number().int().positive().max(Number.MAX_SAFE_INTEGER)
1697
+ });
1698
+ //#endregion
1699
+ //#region ../contracts/src/templates/workload-input-schema.ts
1700
+ const WorkloadInputPropertySchema = z.lazy(() => z.discriminatedUnion("type", [
1701
+ z.strictObject({ type: z.enum([
1702
+ "string",
1703
+ "number",
1704
+ "integer",
1705
+ "boolean",
1706
+ "null"
1707
+ ]) }),
1708
+ z.strictObject({
1709
+ type: z.literal("array"),
1710
+ items: WorkloadInputPropertySchema
1711
+ }),
1712
+ WorkloadInputSchema
1713
+ ]));
1714
+ const WorkloadInputSchema = z.strictObject({
1715
+ type: z.literal("object"),
1716
+ properties: z.record(z.string(), WorkloadInputPropertySchema),
1717
+ required: z.array(z.string()).default([]),
1718
+ additionalProperties: z.literal(false)
1719
+ }).superRefine((schema, ctx) => {
1720
+ if (new Set(schema.required).size !== schema.required.length || schema.required.some((name) => !Object.hasOwn(schema.properties, name))) ctx.addIssue({
1721
+ code: "custom",
1722
+ path: ["required"],
1723
+ message: "required input properties must be declared and unique"
1724
+ });
916
1725
  });
917
- function isAgentApiNative(spec) {
918
- return "agent" in spec && spec.agent !== void 0;
919
- }
920
- function agentApiHarness(spec) {
921
- if (!isAgentApiNative(spec)) return spec.harness;
922
- return {
923
- command: [
924
- "/usr/local/bin/agentapi",
925
- "server",
926
- "--type",
927
- spec.agent.type,
928
- ...spec.agent.transport === "acp" ? ["--experimental-acp"] : [],
929
- ...spec.agent.termWidth === void 0 ? [] : ["--term-width", String(spec.agent.termWidth)],
930
- ...spec.agent.stateFile === void 0 ? [] : ["--state-file", spec.agent.stateFile],
931
- "--port",
932
- "3284",
933
- "--",
934
- ...spec.agent.command
935
- ],
936
- env: spec.agent.env,
937
- ...spec.agent.cwd ? { cwd: spec.agent.cwd } : {}
938
- };
939
- }
940
- function templateServices(spec) {
941
- return isAgentApiNative(spec) ? { agent: AGENTAPI_SERVICE } : spec.services;
942
- }
943
1726
  //#endregion
944
1727
  //#region ../contracts/src/templates/template-validation.ts
945
1728
  function envSources(spec) {
@@ -987,7 +1770,7 @@ function validateNetworkEnvironment(spec, ctx) {
987
1770
  });
988
1771
  }
989
1772
  }
990
- function validateServices(spec, ctx) {
1773
+ function validateServices$1(spec, ctx) {
991
1774
  for (const [serviceName, service] of Object.entries(templateServices(spec))) {
992
1775
  if (!isLoopbackBaseUrl(service.baseUrl)) ctx.addIssue({
993
1776
  code: "custom",
@@ -1130,7 +1913,7 @@ const FORBIDDEN_PERSISTENCE_ROOTS = [
1130
1913
  function pathContains(parent, child) {
1131
1914
  return child === parent || child.startsWith(`${parent}/`);
1132
1915
  }
1133
- function validatePersistence(spec, ctx) {
1916
+ function validateTemplatePersistence(spec, ctx) {
1134
1917
  const seenNames = /* @__PURE__ */ new Set();
1135
1918
  const mounts = spec.persistence.mounts;
1136
1919
  for (const [index, mount] of mounts.entries()) validatePersistenceMount(spec, mounts, mount, index, seenNames, ctx);
@@ -1147,7 +1930,7 @@ function validatePersistence(spec, ctx) {
1147
1930
  });
1148
1931
  }
1149
1932
  function validateAgentStateFile(spec, mounts, ctx) {
1150
- if (!isAgentApiNative(spec)) return;
1933
+ if (!spec.agent) return;
1151
1934
  const stateFile = spec.agent.stateFile;
1152
1935
  const normalized = stateFile !== void 0 && isNormalizedFilesystemPath(stateFile);
1153
1936
  if (stateFile !== void 0 && !normalized) ctx.addIssue({
@@ -1227,59 +2010,477 @@ function validateMountOverlap(mounts, mount, index, path, ctx) {
1227
2010
  });
1228
2011
  }
1229
2012
  }
1230
- function validateMemoryPathOverlap(spec, mount, path, ctx) {
1231
- for (const memoryPath of spec.security.writableMemoryPaths) {
1232
- if (!pathContains(memoryPath, mount.target) && !pathContains(mount.target, memoryPath)) continue;
1233
- ctx.addIssue({
1234
- code: "custom",
1235
- path,
1236
- message: `target overlaps writableMemoryPath ${memoryPath}`
1237
- });
2013
+ function validateMemoryPathOverlap(spec, mount, path, ctx) {
2014
+ for (const memoryPath of spec.security.writableMemoryPaths) {
2015
+ if (!pathContains(memoryPath, mount.target) && !pathContains(mount.target, memoryPath)) continue;
2016
+ ctx.addIssue({
2017
+ code: "custom",
2018
+ path,
2019
+ message: `target overlaps writableMemoryPath ${memoryPath}`
2020
+ });
2021
+ }
2022
+ }
2023
+ function validateSourceMount(spec, mounts, ctx) {
2024
+ if (!spec.source) return;
2025
+ if (mounts.find((mount) => mount.name === spec.source?.destinationMount)) return;
2026
+ ctx.addIssue({
2027
+ code: "custom",
2028
+ path: [
2029
+ "spec",
2030
+ "source",
2031
+ "destinationMount"
2032
+ ],
2033
+ message: "source destinationMount must name a persistence mount"
2034
+ });
2035
+ }
2036
+ function isNormalizedFilesystemPath(path) {
2037
+ if (!isAbsolutePath(path) || path === "/") return false;
2038
+ if (path.endsWith("/") || path.includes("//") || path.includes("\\") || path.includes(",")) return false;
2039
+ if ([...path].some((character) => {
2040
+ const code = character.codePointAt(0) ?? 0;
2041
+ return code < 32 || code === 127;
2042
+ })) return false;
2043
+ return !path.split("/").some((part) => part === "." || part === "..");
2044
+ }
2045
+ function isNormalizedPath(path) {
2046
+ if (!path.startsWith("/")) return false;
2047
+ if (path.includes("..") || path.includes("//")) return false;
2048
+ if (/[?#\s]/.test(path)) return false;
2049
+ if (/%2e|%2f|%5c/i.test(path)) return false;
2050
+ if ([...path].some((character) => character.charCodeAt(0) <= 31)) return false;
2051
+ return true;
2052
+ }
2053
+ function validateTemplateSpec(spec, ctx) {
2054
+ validateServices$1(spec, ctx);
2055
+ validatePreviews(spec, ctx);
2056
+ validateEnvironment(spec, ctx);
2057
+ validateOutputs(spec, ctx);
2058
+ validateWritableMemoryPaths(spec, ctx);
2059
+ validateTemplatePersistence(spec, ctx);
2060
+ validateNetworkEnvironment(spec, ctx);
2061
+ validateTerminal(spec, ctx);
2062
+ }
2063
+ function validatePreviews(spec, ctx) {
2064
+ const servicePorts = new Set(Object.values(templateServices(spec)).filter((service) => isLoopbackBaseUrl(service.baseUrl)).map((service) => {
2065
+ const url = new URL(service.baseUrl);
2066
+ return Number(url.port || 80);
2067
+ }));
2068
+ for (const [name, preview] of Object.entries(spec.previews ?? {})) if (servicePorts.has(preview.port)) ctx.addIssue({
2069
+ code: "custom",
2070
+ path: [
2071
+ "spec",
2072
+ "previews",
2073
+ name,
2074
+ "port"
2075
+ ],
2076
+ message: "preview port is reserved by a template service"
2077
+ });
2078
+ }
2079
+ //#endregion
2080
+ //#region ../contracts/src/templates/workload-validation.ts
2081
+ function isWorkloadPath(path) {
2082
+ return path.startsWith("/") && !/[\\,\s]/.test(path) && !path.includes("//") && !path.endsWith("/") && ![...path].some((character) => {
2083
+ const code = character.charCodeAt(0);
2084
+ return code < 32 || code === 127;
2085
+ }) && !path.split("/").some((part) => part === "." || part === "..");
2086
+ }
2087
+ function overlaps(left, right) {
2088
+ return left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
2089
+ }
2090
+ function validateProfile(spec, issue) {
2091
+ const operations = Object.values(spec.operations);
2092
+ if (operations.length === 0 && spec.profile !== "service") issue(["operations"], "at least one operation is required");
2093
+ if (spec.profile === "service" && Object.keys(spec.services).length === 0) issue(["services"], "service profile requires declared transport");
2094
+ if (spec.profile === "finite-process" && (operations.length !== 1 || !Object.hasOwn(spec.adapters, operations[0]?.adapter ?? "") || spec.adapters[operations[0]?.adapter ?? ""]?.kind !== "process")) issue(["operations"], "finite-process requires one process operation");
2095
+ }
2096
+ function validateSecurity(spec, issue) {
2097
+ if (!spec.security.readOnlyRoot || !spec.security.dropCapabilities.includes("ALL")) issue(["security"], "workload profiles require a read-only root and all capabilities dropped");
2098
+ for (const path of spec.security.writableMemoryPaths) if (!isWorkloadPath(path) || [
2099
+ "/run/pocketcoder",
2100
+ "/proc",
2101
+ "/sys",
2102
+ "/dev"
2103
+ ].some((root) => overlaps(path, root))) issue(["security", "writableMemoryPaths"], "writable path overlaps protected storage or is not normalized");
2104
+ }
2105
+ function validateAdapters(spec, issue) {
2106
+ for (const [name, adapter] of Object.entries(spec.adapters)) {
2107
+ const path = ["adapters", name];
2108
+ if (adapter.kind === "service") {
2109
+ if (!adapter.service || !Object.hasOwn(spec.services, adapter.service) || adapter.command) issue(path, "service adapters require a declared service and no process command");
2110
+ } else if (!adapter.command || adapter.service) issue(path, "process, Pi and AgentAPI adapters require a command and no service reference");
2111
+ }
2112
+ }
2113
+ function validateProcessEnvironment(spec, issue) {
2114
+ const processes = [
2115
+ ...spec.lifecycle?.checkpointHook ? [{
2116
+ path: ["lifecycle", "checkpointHook"],
2117
+ process: spec.lifecycle.checkpointHook
2118
+ }] : [],
2119
+ ...Object.entries(spec.adapters).map(([name, adapter]) => ({
2120
+ path: ["adapters", name],
2121
+ process: adapter
2122
+ })),
2123
+ ...Object.entries(spec.services).flatMap(([name, service]) => service.process ? [{
2124
+ path: [
2125
+ "services",
2126
+ name,
2127
+ "process"
2128
+ ],
2129
+ process: service.process
2130
+ }] : [])
2131
+ ];
2132
+ for (const { path, process } of processes) {
2133
+ if (process.cwd && process.cwd !== "/" && !isWorkloadPath(process.cwd)) issue(path, "cwd must be normalized");
2134
+ validateEnv(spec, process.env, path, issue);
2135
+ }
2136
+ }
2137
+ function validateEnv(spec, env, path, issue) {
2138
+ for (const [key, value] of Object.entries(env)) {
2139
+ if (value.startsWith("credentialSlot:")) {
2140
+ if (!Object.hasOwn(spec.credentialSlots, value.slice(15))) issue([
2141
+ ...path,
2142
+ "env",
2143
+ key
2144
+ ], "unknown credential slot");
2145
+ } else if (SECRET_ENV_PATTERN.test(key) && value !== "" || value.startsWith("secretRef:")) issue([
2146
+ ...path,
2147
+ "env",
2148
+ key
2149
+ ], "runtime credentials require a credentialSlot: reference");
2150
+ if (spec.network.mode === "restricted" && /^(http_proxy|https_proxy|all_proxy|no_proxy)$/i.test(key)) issue([
2151
+ ...path,
2152
+ "env",
2153
+ key
2154
+ ], "proxy variables are reserved by restricted networking");
2155
+ }
2156
+ }
2157
+ function validateOperations(spec, issue) {
2158
+ for (const [name, operation] of Object.entries(spec.operations)) {
2159
+ const adapter = Object.hasOwn(spec.adapters, operation.adapter) ? spec.adapters[operation.adapter] : void 0;
2160
+ if (!adapter) issue([
2161
+ "operations",
2162
+ name,
2163
+ "adapter"
2164
+ ], "operation must name a pinned adapter");
2165
+ if (spec.profile === "cloud-interactive" && adapter?.kind === "process") issue(["operations", name], "cloud-interactive requires request-aware adapters");
2166
+ for (const root of operation.outputRoots) if (!isWorkloadPath(root) || [
2167
+ "/run",
2168
+ "/proc",
2169
+ "/sys",
2170
+ "/dev"
2171
+ ].some((path) => overlaps(path, root))) issue([
2172
+ "operations",
2173
+ name,
2174
+ "outputRoots"
2175
+ ], "output roots must be normalized and outside protected storage");
2176
+ }
2177
+ }
2178
+ function serviceUrl(value) {
2179
+ try {
2180
+ return new URL(value);
2181
+ } catch {
2182
+ return null;
2183
+ }
2184
+ }
2185
+ function isLoopbackService(url) {
2186
+ return url.protocol === "http:" && [
2187
+ "127.0.0.1",
2188
+ "localhost",
2189
+ "[::1]"
2190
+ ].includes(url.hostname) && !url.username && !url.password && url.pathname === "/" && !url.search && !url.hash;
2191
+ }
2192
+ function validateServices(spec, issue) {
2193
+ for (const [name, service] of Object.entries(spec.services)) {
2194
+ const url = serviceUrl(service.baseUrl);
2195
+ if (!url || !isLoopbackService(url)) {
2196
+ issue(["services", name], "service must use loopback HTTP without credentials or a path");
2197
+ continue;
2198
+ }
2199
+ const routes = /* @__PURE__ */ new Set();
2200
+ if (!isNormalizedPath(service.healthPath)) issue([
2201
+ "services",
2202
+ name,
2203
+ "healthPath"
2204
+ ], "health path must be normalized");
2205
+ for (const route of service.routes) {
2206
+ const key = `${route.method} ${route.path}`;
2207
+ if (!isNormalizedPath(route.path) || routes.has(key)) issue([
2208
+ "services",
2209
+ name,
2210
+ "routes"
2211
+ ], "routes must be normalized and unique");
2212
+ routes.add(key);
2213
+ }
2214
+ for (const [previewName, preview] of Object.entries(spec.previews)) if (preview.port === Number(url.port || 80)) issue(["previews", previewName], "preview port is reserved by a service");
1238
2215
  }
1239
2216
  }
1240
- function validateSourceMount(spec, mounts, ctx) {
1241
- if (!spec.source) return;
1242
- if (mounts.find((mount) => mount.name === spec.source?.destinationMount)) return;
1243
- ctx.addIssue({
2217
+ function validateWorkloadTemplate(manifest, ctx) {
2218
+ const spec = manifest.spec;
2219
+ const issue = (path, message) => ctx.addIssue({
1244
2220
  code: "custom",
1245
- path: [
1246
- "spec",
1247
- "source",
1248
- "destinationMount"
1249
- ],
1250
- message: "source destinationMount must name a persistence mount"
2221
+ path: ["spec", ...path],
2222
+ message
1251
2223
  });
2224
+ validateTemplatePersistence({
2225
+ security: spec.security,
2226
+ source: null,
2227
+ persistence: spec.lifecycle?.persistence ?? PersistenceSpecSchema.parse({})
2228
+ }, ctx);
2229
+ const timeouts = spec.lifecycle?.timeouts;
2230
+ if (timeouts && (parseDurationMs(timeouts.maxAge) > spec.limits.deadlineSeconds * 1e3 || parseDurationMs(timeouts.terminateGrace) > spec.limits.finalizationSeconds * 1e3)) issue(["lifecycle", "timeouts"], "Lifecycle timeouts exceed the workload limits.");
2231
+ validateProfile(spec, issue);
2232
+ validateSecurity(spec, issue);
2233
+ validateAdapters(spec, issue);
2234
+ validateProcessEnvironment(spec, issue);
2235
+ validateOperations(spec, issue);
2236
+ validateServices(spec, issue);
2237
+ for (const [name, slot] of Object.entries(spec.credentialSlots)) if (slot.maxLeaseSeconds > spec.limits.deadlineSeconds) issue(["credentialSlots", name], "credential lifetime exceeds the workspace deadline");
1252
2238
  }
1253
- function isNormalizedFilesystemPath(path) {
1254
- if (!isAbsolutePath(path) || path === "/") return false;
1255
- if (path.endsWith("/") || path.includes("//") || path.includes("\\") || path.includes(",")) return false;
1256
- if ([...path].some((character) => {
1257
- const code = character.codePointAt(0) ?? 0;
1258
- return code < 32 || code === 127;
1259
- })) return false;
1260
- return !path.split("/").some((part) => part === "." || part === "..");
2239
+ //#endregion
2240
+ //#region ../contracts/src/templates/workload-template.ts
2241
+ const WorkloadAdapterSchema = z.strictObject({
2242
+ kind: z.enum([
2243
+ "process",
2244
+ "service",
2245
+ "pi",
2246
+ "agentapi"
2247
+ ]),
2248
+ digest: WorkloadDigestSchema,
2249
+ protocol: z.string().min(1).max(128),
2250
+ command: HarnessSchema.shape.command.optional(),
2251
+ cwd: HarnessSchema.shape.cwd,
2252
+ env: HarnessSchema.shape.env,
2253
+ service: WorkloadNameSchema.optional()
2254
+ });
2255
+ const WorkloadOperationSchema = z.strictObject({
2256
+ adapter: WorkloadNameSchema,
2257
+ inputSchema: WorkloadInputSchema,
2258
+ requiredCapabilities: z.array(WorkloadCapabilitySchema),
2259
+ outputRoots: z.array(z.string()).max(16)
2260
+ });
2261
+ const WorkloadTemplateSchema = z.strictObject({
2262
+ apiVersion: z.literal("pocketcoder.dev/v1"),
2263
+ kind: z.literal("Template"),
2264
+ metadata: z.strictObject({
2265
+ name: WorkloadNameSchema,
2266
+ description: z.string().max(512).optional()
2267
+ }),
2268
+ spec: z.strictObject({
2269
+ version: z.string().regex(/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/),
2270
+ image: WorkloadImageSchema,
2271
+ runtime: WorkloadRuntimePinSchema,
2272
+ profile: WorkloadProfileSchema,
2273
+ adapters: z.record(WorkloadNameSchema, WorkloadAdapterSchema),
2274
+ operations: z.record(WorkloadNameSchema, WorkloadOperationSchema),
2275
+ services: z.record(WorkloadNameSchema, ServiceSchema.extend({ process: HarnessSchema.strict().optional() }).strict()),
2276
+ previews: PreviewsSchema,
2277
+ credentialSlots: z.record(WorkloadNameSchema, CredentialSlotSchema),
2278
+ resources: ResourcesSchema.strict(),
2279
+ network: NetworkPolicySchema,
2280
+ security: SecuritySchema.strict(),
2281
+ limits: WorkloadLimitsSchema,
2282
+ lifecycle: z.strictObject({
2283
+ persistence: PersistenceSpecSchema.prefault({}),
2284
+ checkpointHook: CheckpointHookSchema.optional(),
2285
+ timeouts: TimeoutsSchema.optional(),
2286
+ maxLaunchInputBytes: z.number().int().positive().max(1048576).default(65536)
2287
+ }).optional()
2288
+ })
2289
+ }).superRefine(validateWorkloadTemplate);
2290
+ const WorkloadTemplateSnapshotSchema = z.strictObject({
2291
+ format: z.literal("pocketcoder.dev/v1"),
2292
+ name: WorkloadNameSchema,
2293
+ version: z.string(),
2294
+ digest: WorkloadDigestSchema,
2295
+ manifest: WorkloadTemplateSchema
2296
+ }).superRefine((snapshot, ctx) => {
2297
+ if (snapshot.name !== snapshot.manifest.metadata.name || snapshot.version !== snapshot.manifest.spec.version || snapshot.digest !== digestOf(snapshot.manifest)) ctx.addIssue({
2298
+ code: "custom",
2299
+ message: "snapshot identity does not match its immutable manifest"
2300
+ });
2301
+ });
2302
+ //#endregion
2303
+ //#region ../contracts/src/templates/template-runtime.ts
2304
+ const LEGACY_AGENTAPI_SERVICE = ServiceSchema.parse({
2305
+ baseUrl: "http://127.0.0.1:3284",
2306
+ routes: [
2307
+ {
2308
+ method: "GET",
2309
+ path: "/status"
2310
+ },
2311
+ {
2312
+ method: "GET",
2313
+ path: "/messages",
2314
+ query: ["after"]
2315
+ },
2316
+ {
2317
+ method: "POST",
2318
+ path: "/message"
2319
+ }
2320
+ ]
2321
+ });
2322
+ const AGENTAPI_SERVICE = ServiceSchema.parse({
2323
+ ...LEGACY_AGENTAPI_SERVICE,
2324
+ routes: [...LEGACY_AGENTAPI_SERVICE.routes, {
2325
+ method: "GET",
2326
+ path: "/events",
2327
+ responseMode: "stream",
2328
+ maxResponseBytes: 4194304,
2329
+ deadlineSeconds: 300
2330
+ }]
2331
+ });
2332
+ function isAgentApiNative(spec) {
2333
+ return "agent" in spec && spec.agent !== void 0;
1261
2334
  }
1262
- function isNormalizedPath(path) {
1263
- if (!path.startsWith("/")) return false;
1264
- if (path.includes("..") || path.includes("//")) return false;
1265
- if (/[?#\s]/.test(path)) return false;
1266
- if (/%2e|%2f|%5c/i.test(path)) return false;
1267
- if ([...path].some((character) => character.charCodeAt(0) <= 31)) return false;
1268
- return true;
2335
+ function agentApiHarness(spec) {
2336
+ if (!isAgentApiNative(spec)) {
2337
+ if (!spec.harness) throw new Error("A workload infrastructure process is not an alpha harness.");
2338
+ return spec.harness;
2339
+ }
2340
+ return {
2341
+ command: [
2342
+ "/usr/local/bin/agentapi",
2343
+ "server",
2344
+ "--type",
2345
+ spec.agent.type,
2346
+ ...spec.agent.transport === "acp" ? ["--experimental-acp"] : [],
2347
+ ...spec.agent.termWidth === void 0 ? [] : ["--term-width", String(spec.agent.termWidth)],
2348
+ ...spec.agent.stateFile === void 0 ? [] : ["--state-file", spec.agent.stateFile],
2349
+ "--port",
2350
+ "3284",
2351
+ "--",
2352
+ ...spec.agent.command
2353
+ ],
2354
+ env: spec.agent.env,
2355
+ ...spec.agent.cwd ? { cwd: spec.agent.cwd } : {}
2356
+ };
1269
2357
  }
1270
- function validateTemplateSpec(spec, ctx) {
1271
- validateServices(spec, ctx);
1272
- validateEnvironment(spec, ctx);
1273
- validateOutputs(spec, ctx);
1274
- validateWritableMemoryPaths(spec, ctx);
1275
- validatePersistence(spec, ctx);
1276
- validateNetworkEnvironment(spec, ctx);
1277
- validateTerminal(spec, ctx);
2358
+ function templateServices(spec) {
2359
+ return isAgentApiNative(spec) ? { agent: AGENTAPI_SERVICE } : spec.services ?? {};
1278
2360
  }
1279
- TemplateManifestBaseSchema.superRefine((manifest, ctx) => {
2361
+ z.object({
2362
+ name: z.string(),
2363
+ version: z.string(),
2364
+ digest: WorkloadDigestSchema,
2365
+ spec: TemplateSpecSchema
2366
+ });
2367
+ //#endregion
2368
+ //#region ../contracts/src/templates/template.ts
2369
+ const TemplateManifestSchema = TemplateManifestBaseSchema.superRefine((manifest, ctx) => {
1280
2370
  validateTemplateSpec(manifest.spec, ctx);
1281
2371
  });
1282
2372
  //#endregion
2373
+ //#region ../contracts/src/protocol/protocol-checkpoint.ts
2374
+ const bytes = z.number().int().positive().max(Number.MAX_SAFE_INTEGER);
2375
+ const digest = z.string().regex(/^sha256:[a-f0-9]{64}$/);
2376
+ const PrepareCheckpointArchivePayload = z.object({
2377
+ operation_id: z.uuid(),
2378
+ checkpoint_id: z.uuid(),
2379
+ deadline_ms: bytes,
2380
+ mounts: z.array(PersistenceMountSchema).max(16),
2381
+ max_archive_bytes: bytes,
2382
+ max_index_bytes: bytes,
2383
+ max_queue_bytes: bytes
2384
+ });
2385
+ const CheckpointPreparedPayload = z.object({
2386
+ operation_id: z.uuid(),
2387
+ checkpoint_id: z.uuid(),
2388
+ header: CheckpointArchiveHeaderSchema,
2389
+ archive_bytes: bytes
2390
+ }).refine((value) => value.header.checkpoint_id === value.checkpoint_id, "Checkpoint declaration identity differs.");
2391
+ const grant = z.object({
2392
+ operation_id: z.uuid(),
2393
+ transfer_id: z.uuid(),
2394
+ checkpoint_id: z.uuid(),
2395
+ credential: z.string().min(1).max(4096),
2396
+ url: z.url(),
2397
+ expires_at: z.iso.datetime()
2398
+ });
2399
+ const CheckpointUploadPayload = grant;
2400
+ const RestoreTransferSpecSchema = grant.extend({
2401
+ source: z.object({
2402
+ checkpoint_id: z.uuid(),
2403
+ workspace_id: z.uuid(),
2404
+ template_digest: digest,
2405
+ archive_digest: digest
2406
+ }),
2407
+ mounts: z.array(PersistenceMountSchema).max(16),
2408
+ max_archive_bytes: bytes,
2409
+ max_index_bytes: bytes,
2410
+ max_ledger_bytes: bytes
2411
+ }).refine((value) => value.source.checkpoint_id === value.checkpoint_id, "Restore source identity differs.");
2412
+ const CheckpointInstalledPayload = z.object({
2413
+ operation_id: z.uuid(),
2414
+ transfer_id: z.uuid(),
2415
+ checkpoint_id: z.uuid(),
2416
+ archive_digest: digest,
2417
+ phase: z.enum(["installed", "failed"])
2418
+ });
2419
+ const CheckpointUploadStatusPayload = z.object({
2420
+ operation_id: z.uuid(),
2421
+ transfer_id: z.uuid(),
2422
+ checkpoint_id: z.uuid(),
2423
+ phase: z.enum(["uploaded", "failed"])
2424
+ });
2425
+ //#endregion
2426
+ //#region ../contracts/src/protocol/protocol-exec.ts
2427
+ const SOURCE_CREDENTIAL_MAX_BYTES = 65536;
2428
+ const RestoreModeSchema = z.enum(["provider_installed", "controller_archive"]);
2429
+ const ExecSpecSchema = z.object({
2430
+ agentapi_native: z.boolean().default(false),
2431
+ setup: z.array(SetupStepSchema),
2432
+ harness: HarnessSchema.nullable(),
2433
+ workload: WaitingRuntimeSpecSchema.optional(),
2434
+ env: z.record(z.string(), z.string()),
2435
+ services: z.record(z.string(), ServiceSchema),
2436
+ previews: PreviewsSchema.optional(),
2437
+ display: DisplaySchema.optional(),
2438
+ terminal: z.object({
2439
+ command: z.array(z.string().min(1)).min(1),
2440
+ cwd: z.string().optional(),
2441
+ env: z.record(z.string(), z.string()),
2442
+ max_sessions: z.number().int().min(1).max(8),
2443
+ idle_timeout_seconds: z.number().int().positive(),
2444
+ replay_buffer_bytes: z.number().int().positive()
2445
+ }).nullable().default(null),
2446
+ timeouts: TimeoutsSchema,
2447
+ security: z.object({ writable_memory_paths: z.array(z.string()) }).default({ writable_memory_paths: [] }),
2448
+ network: z.discriminatedUnion("mode", [z.object({ mode: z.literal("unrestricted") }), z.object({
2449
+ mode: z.literal("restricted"),
2450
+ proxy_url: z.url(),
2451
+ health_url: z.url()
2452
+ })]).default({ mode: "unrestricted" }),
2453
+ launch_mode: z.enum(LAUNCH_MODES).default("create"),
2454
+ source: SourceDescriptorSchema.extend({
2455
+ url: z.url(),
2456
+ destination: z.string(),
2457
+ max_bytes: z.number().int().positive().optional(),
2458
+ max_files: z.number().int().positive().optional(),
2459
+ credential_expires_at: z.iso.datetime().nullable().default(null),
2460
+ credential: z.string().min(1).max(SOURCE_CREDENTIAL_MAX_BYTES).refine((value) => !value.includes("\0"), "credential must not contain NUL bytes").nullable().default(null)
2461
+ }).nullable().default(null),
2462
+ restore: z.object({
2463
+ mode: RestoreModeSchema,
2464
+ checkpoint_id: z.uuid(),
2465
+ origin_workspace_id: z.uuid(),
2466
+ transfer: RestoreTransferSpecSchema.nullable().default(null)
2467
+ }).nullable().default(null),
2468
+ persistence: z.object({
2469
+ mounts: z.array(z.object({
2470
+ name: z.string(),
2471
+ target: z.string()
2472
+ })),
2473
+ conversation_restore: z.enum(CONVERSATION_RESTORE_CAPABILITIES)
2474
+ }),
2475
+ checkpoint_hook: z.object({
2476
+ command: z.array(z.string().min(1)).min(1),
2477
+ timeout_seconds: z.number().int().positive(),
2478
+ env: z.record(z.string(), z.string()),
2479
+ cwd: z.string().optional()
2480
+ }).nullable(),
2481
+ outputs: z.record(z.string(), z.unknown())
2482
+ });
2483
+ //#endregion
1283
2484
  //#region ../contracts/src/terminals/terminal.ts
1284
2485
  const TERMINAL_CHUNK_BYTES = 32768;
1285
2486
  const TERMINAL_REPLAY_BUFFER_BYTES = 65536;
@@ -1394,11 +2595,34 @@ const TerminalSessionSchema = z.object({
1394
2595
  CursorQuerySchema(200, 50);
1395
2596
  const TerminalSessionPageSchema = CursorPageSchema(TerminalSessionSchema);
1396
2597
  //#endregion
2598
+ //#region ../contracts/src/protocol/protocol-credentials.ts
2599
+ const WorkspaceCredentialSchema = z.object({
2600
+ lease_id: z.uuid(),
2601
+ path: z.string().regex(/^\/run\/pocketcoder\/secrets\/leases\/[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/),
2602
+ credential: z.string().min(1).max(SOURCE_CREDENTIAL_MAX_BYTES).refine((value) => !value.includes("\0")),
2603
+ expires_at: z.iso.datetime(),
2604
+ purpose: z.enum(["setup-issuer", "runtime-issuer"])
2605
+ });
2606
+ const CredentialRenewPayload = z.object({
2607
+ lease_id: z.uuid(),
2608
+ request_id: z.uuid()
2609
+ });
2610
+ const CredentialRenewedPayload = z.object({
2611
+ previous_lease_id: z.uuid(),
2612
+ request_id: z.uuid(),
2613
+ credential: WorkspaceCredentialSchema
2614
+ });
2615
+ const CredentialInstalledPayload = z.object({
2616
+ previous_lease_id: z.uuid(),
2617
+ lease_id: z.uuid()
2618
+ });
2619
+ //#endregion
1397
2620
  //#region ../contracts/src/protocol/protocol-stream.ts
1398
2621
  const ProxyStreamStartPayload = z.object({
1399
2622
  request_id: z.uuid(),
1400
2623
  status: z.number().int().min(100).max(599),
1401
- headers: z.record(z.string(), z.string())
2624
+ headers: z.record(z.string(), z.string()),
2625
+ cookies: z.array(z.string().max(4096)).max(16).optional()
1402
2626
  });
1403
2627
  const ProxyStreamChunkPayload = z.object({
1404
2628
  request_id: z.uuid(),
@@ -1466,7 +2690,6 @@ z.object({
1466
2690
  type: z.literal("lease_assignment"),
1467
2691
  input: ProviderInputSchema
1468
2692
  });
1469
- const SOURCE_CREDENTIAL_MAX_BYTES = 65536;
1470
2693
  const EnvelopeBase = z.object({
1471
2694
  v: z.union([
1472
2695
  z.literal(1),
@@ -1474,7 +2697,14 @@ const EnvelopeBase = z.object({
1474
2697
  z.literal(3),
1475
2698
  z.literal(4),
1476
2699
  z.literal(5),
1477
- z.literal(6)
2700
+ z.literal(6),
2701
+ z.literal(7),
2702
+ z.literal(8),
2703
+ z.literal(9),
2704
+ z.literal(10),
2705
+ z.literal(11),
2706
+ z.literal(12),
2707
+ z.literal(13)
1478
2708
  ]),
1479
2709
  workspace_id: z.uuid(),
1480
2710
  connection_id: z.uuid(),
@@ -1496,6 +2726,7 @@ const HeartbeatPayload = z.object({
1496
2726
  child: z.enum([
1497
2727
  "starting",
1498
2728
  "setup",
2729
+ "waiting",
1499
2730
  "running",
1500
2731
  "exited",
1501
2732
  "terminating"
@@ -1510,6 +2741,7 @@ const ProcessStatePayload = z.object({
1510
2741
  phase: z.enum([
1511
2742
  "starting",
1512
2743
  "setup",
2744
+ "waiting",
1513
2745
  "running",
1514
2746
  "exited",
1515
2747
  "terminating"
@@ -1591,6 +2823,38 @@ const RestoreStatusPayload = z.object({
1591
2823
  });
1592
2824
  const ConversationMessagePayload = ConversationMessageInputSchema;
1593
2825
  z.discriminatedUnion("type", [
2826
+ EnvelopeBase.extend({
2827
+ type: z.literal("runtime_ready"),
2828
+ payload: z.object({ incarnationId: z.string().min(1) })
2829
+ }),
2830
+ EnvelopeBase.extend({
2831
+ type: z.literal("finite_process"),
2832
+ payload: FiniteProcessFactSchema
2833
+ }),
2834
+ EnvelopeBase.extend({
2835
+ type: z.literal("process_absent"),
2836
+ payload: ProcessTargetPayload
2837
+ }),
2838
+ EnvelopeBase.extend({
2839
+ type: z.literal("process_cancel_result"),
2840
+ payload: ProcessCancelResultPayload
2841
+ }),
2842
+ EnvelopeBase.extend({
2843
+ type: z.literal("preview_socket"),
2844
+ payload: PreviewSocketPayload
2845
+ }),
2846
+ EnvelopeBase.extend({
2847
+ type: z.literal("credential_renew"),
2848
+ payload: CredentialRenewPayload
2849
+ }),
2850
+ EnvelopeBase.extend({
2851
+ type: z.literal("credential_installed"),
2852
+ payload: CredentialInstalledPayload
2853
+ }),
2854
+ EnvelopeBase.extend({
2855
+ type: z.literal("setup_complete"),
2856
+ payload: z.object({ request_id: z.uuid() })
2857
+ }),
1594
2858
  EnvelopeBase.extend({
1595
2859
  type: z.literal("registered"),
1596
2860
  payload: RegisteredPayload
@@ -1660,77 +2924,44 @@ z.discriminatedUnion("type", [
1660
2924
  payload: CheckpointStatusPayload
1661
2925
  }),
1662
2926
  EnvelopeBase.extend({
1663
- type: z.literal("output_published"),
1664
- payload: OutputPublishedPayload
1665
- }),
1666
- EnvelopeBase.extend({
1667
- type: z.literal("restore_status"),
1668
- payload: RestoreStatusPayload
2927
+ type: z.literal("checkpoint_prepared"),
2928
+ payload: CheckpointPreparedPayload
1669
2929
  }),
1670
2930
  EnvelopeBase.extend({
1671
- type: z.literal("conversation_message"),
1672
- payload: ConversationMessagePayload
2931
+ type: z.literal("checkpoint_installed"),
2932
+ payload: CheckpointInstalledPayload
1673
2933
  }),
1674
2934
  EnvelopeBase.extend({
1675
- type: z.literal("attachment_ack"),
1676
- payload: AttachmentAckPayload
2935
+ type: z.literal("checkpoint_upload_status"),
2936
+ payload: CheckpointUploadStatusPayload
1677
2937
  }),
1678
2938
  EnvelopeBase.extend({
1679
- type: z.literal("attachment_result"),
1680
- payload: AttachmentResultPayload
2939
+ type: z.literal("output_published"),
2940
+ payload: OutputPublishedPayload
1681
2941
  }),
1682
- EnvelopeBase.extend({
1683
- type: z.literal("attachment_resolved"),
1684
- payload: AttachmentResolvedPayload
1685
- })
1686
- ]);
1687
- const ExecSpecSchema = z.object({
1688
- agentapi_native: z.boolean().default(false),
1689
- setup: z.array(SetupStepSchema),
1690
- harness: HarnessSchema,
1691
- env: z.record(z.string(), z.string()),
1692
- services: z.record(z.string(), ServiceSchema),
1693
- terminal: z.object({
1694
- command: z.array(z.string().min(1)).min(1),
1695
- cwd: z.string().optional(),
1696
- env: z.record(z.string(), z.string()),
1697
- max_sessions: z.number().int().min(1).max(8),
1698
- idle_timeout_seconds: z.number().int().positive(),
1699
- replay_buffer_bytes: z.number().int().positive()
1700
- }).nullable().default(null),
1701
- timeouts: TimeoutsSchema,
1702
- security: z.object({ writable_memory_paths: z.array(z.string()) }).default({ writable_memory_paths: [] }),
1703
- network: z.discriminatedUnion("mode", [z.object({ mode: z.literal("unrestricted") }), z.object({
1704
- mode: z.literal("restricted"),
1705
- proxy_url: z.url(),
1706
- health_url: z.url()
1707
- })]).default({ mode: "unrestricted" }),
1708
- launch_mode: z.enum(LAUNCH_MODES).default("create"),
1709
- source: SourceDescriptorSchema.extend({
1710
- url: z.url(),
1711
- destination: z.string(),
1712
- credential: z.string().min(1).max(SOURCE_CREDENTIAL_MAX_BYTES).refine((value) => !value.includes("\0"), "credential must not contain NUL bytes").nullable().default(null)
1713
- }).nullable().default(null),
1714
- restore: z.object({
1715
- checkpoint_id: z.uuid(),
1716
- origin_workspace_id: z.uuid()
1717
- }).nullable().default(null),
1718
- persistence: z.object({
1719
- mounts: z.array(z.object({
1720
- name: z.string(),
1721
- target: z.string()
1722
- })),
1723
- conversation_restore: z.enum(CONVERSATION_RESTORE_CAPABILITIES)
2942
+ EnvelopeBase.extend({
2943
+ type: z.literal("restore_status"),
2944
+ payload: RestoreStatusPayload
1724
2945
  }),
1725
- checkpoint_hook: z.object({
1726
- command: z.array(z.string().min(1)).min(1),
1727
- timeout_seconds: z.number().int().positive(),
1728
- env: z.record(z.string(), z.string()),
1729
- cwd: z.string().optional()
1730
- }).nullable(),
1731
- outputs: z.record(z.string(), z.unknown())
1732
- });
2946
+ EnvelopeBase.extend({
2947
+ type: z.literal("conversation_message"),
2948
+ payload: ConversationMessagePayload
2949
+ }),
2950
+ EnvelopeBase.extend({
2951
+ type: z.literal("attachment_ack"),
2952
+ payload: AttachmentAckPayload
2953
+ }),
2954
+ EnvelopeBase.extend({
2955
+ type: z.literal("attachment_result"),
2956
+ payload: AttachmentResultPayload
2957
+ }),
2958
+ EnvelopeBase.extend({
2959
+ type: z.literal("attachment_resolved"),
2960
+ payload: AttachmentResolvedPayload
2961
+ })
2962
+ ]);
1733
2963
  const RegisteredAckPayload = z.object({
2964
+ credentials: z.array(WorkspaceCredentialSchema).max(64).default([]),
1734
2965
  epoch: z.number().int().positive(),
1735
2966
  reconnect_credential: z.string().optional(),
1736
2967
  limits: z.object({
@@ -1749,7 +2980,9 @@ const ProxyRequestPayload = z.object({
1749
2980
  "POST",
1750
2981
  "PUT",
1751
2982
  "PATCH",
1752
- "DELETE"
2983
+ "DELETE",
2984
+ "HEAD",
2985
+ "OPTIONS"
1753
2986
  ]),
1754
2987
  path: z.string(),
1755
2988
  query: z.record(z.string(), z.string()).default({}),
@@ -1765,6 +2998,42 @@ const PrepareCheckpointPayload = z.object({
1765
2998
  deadline_ms: z.number().int().positive()
1766
2999
  });
1767
3000
  z.discriminatedUnion("type", [
3001
+ EnvelopeBase.extend({
3002
+ type: z.literal("process_start"),
3003
+ payload: ProcessStartPayload
3004
+ }),
3005
+ EnvelopeBase.extend({
3006
+ type: z.literal("process_lookup"),
3007
+ payload: ProcessTargetPayload
3008
+ }),
3009
+ EnvelopeBase.extend({
3010
+ type: z.literal("process_cancel"),
3011
+ payload: ProcessCancelPayload
3012
+ }),
3013
+ EnvelopeBase.extend({
3014
+ type: z.literal("process_ack"),
3015
+ payload: ProcessTargetPayload
3016
+ }),
3017
+ EnvelopeBase.extend({
3018
+ type: z.literal("screenshot_capture"),
3019
+ payload: ScreenshotCapturePayload
3020
+ }),
3021
+ EnvelopeBase.extend({
3022
+ type: z.literal("preview_socket"),
3023
+ payload: PreviewSocketPayload
3024
+ }),
3025
+ EnvelopeBase.extend({
3026
+ type: z.literal("credential_renewed"),
3027
+ payload: CredentialRenewedPayload
3028
+ }),
3029
+ EnvelopeBase.extend({
3030
+ type: z.literal("credential_installed_ack"),
3031
+ payload: CredentialInstalledPayload
3032
+ }),
3033
+ EnvelopeBase.extend({
3034
+ type: z.literal("setup_complete_ack"),
3035
+ payload: z.object({ request_id: z.uuid() })
3036
+ }),
1768
3037
  EnvelopeBase.extend({
1769
3038
  type: z.literal("registered_ack"),
1770
3039
  payload: RegisteredAckPayload
@@ -1813,6 +3082,14 @@ z.discriminatedUnion("type", [
1813
3082
  type: z.literal("prepare_checkpoint"),
1814
3083
  payload: PrepareCheckpointPayload
1815
3084
  }),
3085
+ EnvelopeBase.extend({
3086
+ type: z.literal("prepare_checkpoint_archive"),
3087
+ payload: PrepareCheckpointArchivePayload
3088
+ }),
3089
+ EnvelopeBase.extend({
3090
+ type: z.literal("checkpoint_upload"),
3091
+ payload: CheckpointUploadPayload
3092
+ }),
1816
3093
  EnvelopeBase.extend({
1817
3094
  type: z.literal("attachment_start"),
1818
3095
  payload: AttachmentStartPayload
@@ -1835,6 +3112,107 @@ z.discriminatedUnion("type", [
1835
3112
  })
1836
3113
  ]);
1837
3114
  //#endregion
3115
+ //#region ../contracts/src/recovery/account-purge.ts
3116
+ const Digest = z.string().regex(/^[a-f0-9]{64}$/);
3117
+ const AccountPurgeIntentSchema = z.strictObject({
3118
+ format: z.literal("pocketcoder-account-purge/v1"),
3119
+ accountId: z.uuid(),
3120
+ operationId: z.uuid(),
3121
+ createRequestDigest: Digest,
3122
+ purgeRequestDigest: Digest,
3123
+ admittedAt: z.iso.datetime()
3124
+ });
3125
+ z.strictObject({
3126
+ format: z.literal("pocketcoder-account-purge-completion/v1"),
3127
+ intent: AccountPurgeIntentSchema,
3128
+ completedAt: z.iso.datetime()
3129
+ });
3130
+ //#endregion
3131
+ //#region ../contracts/src/recovery/source-writer.ts
3132
+ const fileIdentity = z.strictObject({
3133
+ device: z.string().regex(/^\d+$/).max(20),
3134
+ inode: z.string().regex(/^[1-9]\d*$/).max(20)
3135
+ });
3136
+ z.strictObject({
3137
+ format: z.literal("pocketcoder-source-writer/v1"),
3138
+ directory: z.string().startsWith("/").max(4096),
3139
+ root: fileIdentity,
3140
+ lock: fileIdentity
3141
+ });
3142
+ z.strictObject({ manifest: z.union([TemplateManifestSchema, WorkloadTemplateSchema]) });
3143
+ z.object({
3144
+ name: z.string().regex(/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/),
3145
+ version: z.string().min(1).max(256)
3146
+ });
3147
+ z.strictObject({
3148
+ workspaceId: z.uuid(),
3149
+ incarnationId: z.string().min(1).max(256),
3150
+ profiles: z.array(WorkloadProfileSchema),
3151
+ storageProfiles: z.array(z.enum(["controller-readonly/v1", "shared-writer-readonly/v1"])),
3152
+ ceilings: WorkloadLimitsSchema,
3153
+ now: z.iso.datetime(),
3154
+ expiresAt: z.iso.datetime(),
3155
+ installed: InstalledWorkloadSchema.optional()
3156
+ });
3157
+ //#endregion
3158
+ //#region ../contracts/src/workspaces/execution-dispatch.ts
3159
+ const ExecutionPhaseSchema = z.enum([
3160
+ "accepted",
3161
+ "dispatching",
3162
+ "running",
3163
+ "terminal",
3164
+ "canceled",
3165
+ "unknown",
3166
+ "lost"
3167
+ ]);
3168
+ z.strictObject({
3169
+ acceptance: WorkloadAcceptanceSchema,
3170
+ conversationIdentity: ConversationRequestIdentitySchema.nullable(),
3171
+ outcome: JsonValueSchema.nullable(),
3172
+ acceptedAt: z.iso.datetime(),
3173
+ dispatchClaimedAt: z.iso.datetime().nullable(),
3174
+ phase: ExecutionPhaseSchema,
3175
+ cancel: z.strictObject({
3176
+ cancelId: z.uuid(),
3177
+ requestedAt: z.iso.datetime()
3178
+ }).nullable(),
3179
+ settledAt: z.iso.datetime().nullable(),
3180
+ acknowledgedAt: z.iso.datetime().nullable(),
3181
+ finalizedAt: z.iso.datetime().nullable()
3182
+ });
3183
+ z.strictObject({
3184
+ acceptance: WorkloadAcceptanceSchema,
3185
+ conversationIdentity: ConversationRequestIdentitySchema.optional()
3186
+ }).refine(({ acceptance, conversationIdentity }) => acceptance.execution.owner !== "conversation-request" || conversationIdentity !== void 0, {
3187
+ path: ["conversationIdentity"],
3188
+ message: "Conversation execution requires a conversation identity."
3189
+ });
3190
+ WorkloadCancelSchema.refine((cancel) => cancel.scope === "operation", { message: "Use workspace release for workspace cancellation" });
3191
+ z.strictObject({
3192
+ workspaceId: z.uuid(),
3193
+ callerPrincipalId: z.uuid(),
3194
+ incarnationId: z.string().min(1),
3195
+ template: WorkloadTemplateSnapshotSchema,
3196
+ producer: InstalledWorkloadSchema,
3197
+ readyAt: z.iso.datetime().nullable(),
3198
+ endedAt: z.iso.datetime().nullable()
3199
+ });
3200
+ z.discriminatedUnion("phase", [z.strictObject({
3201
+ execution: WorkloadExecutionReferenceSchema,
3202
+ phase: z.literal("absent")
3203
+ }), z.strictObject({
3204
+ execution: WorkloadExecutionReferenceSchema,
3205
+ fingerprint: WorkloadDigestSchema,
3206
+ phase: z.enum([
3207
+ "accepted",
3208
+ "running",
3209
+ "terminal",
3210
+ "canceled"
3211
+ ]),
3212
+ outcome: JsonValueSchema.nullable()
3213
+ })]);
3214
+ WorkloadCancelSchema.extend({ fingerprint: WorkloadDigestSchema });
3215
+ //#endregion
1838
3216
  //#region src/resources/admin/admin.ts
1839
3217
  var AdministrationApi = class {
1840
3218
  transport;
@@ -1912,44 +3290,8 @@ function responseError(response, body) {
1912
3290
  });
1913
3291
  }
1914
3292
  //#endregion
1915
- //#region src/resources/attachments/attachments.ts
3293
+ //#region src/resources/agent/agent.ts
1916
3294
  const DEFAULT_READY_TIMEOUT_MS = 12e4;
1917
- function contentDisposition(name) {
1918
- const clean = name.replace(/[\r\n]/g, "");
1919
- if (/^[ -~]*$/.test(clean)) return `attachment; filename="${clean.replace(/(["\\])/g, "\\$1")}"`;
1920
- return `attachment; filename*=UTF-8''${encodeURIComponent(clean)}`;
1921
- }
1922
- function progressStream(body, total, onProgress) {
1923
- const source = new Response(body).body ?? new ReadableStream({ start: (c) => c.close() });
1924
- let uploaded = 0;
1925
- return source.pipeThrough(new TransformStream({ transform(chunk, controller) {
1926
- uploaded += chunk.byteLength;
1927
- controller.enqueue(chunk);
1928
- onProgress(Math.min(uploaded, total), total);
1929
- } }));
1930
- }
1931
- var AttachmentsApi = class {
1932
- transport;
1933
- constructor(transport) {
1934
- this.transport = transport;
1935
- }
1936
- async upload(workspaceId, input) {
1937
- const id = (input.id ?? crypto.randomUUID()).toLowerCase();
1938
- const body = input.onProgress ? progressStream(input.body, input.sizeBytes, input.onProgress) : input.body;
1939
- const init = {
1940
- method: "PUT",
1941
- ...input.signal ? { signal: input.signal } : {},
1942
- headers: {
1943
- "content-type": input.mediaType ?? "application/octet-stream",
1944
- "content-disposition": contentDisposition(input.name),
1945
- "content-length": String(input.sizeBytes)
1946
- },
1947
- body,
1948
- ...body instanceof ReadableStream ? { duplex: "half" } : {}
1949
- };
1950
- return await this.transport.request(`/v1/workspaces/${encodeURIComponent(workspaceId)}/attachments/${id}`, AttachmentDescriptorSchema, init);
1951
- }
1952
- };
1953
3295
  var AgentApi = class {
1954
3296
  transport;
1955
3297
  workspaces;
@@ -1983,6 +3325,58 @@ var AgentApi = class {
1983
3325
  }
1984
3326
  throw responseError(response, body);
1985
3327
  }
3328
+ interrupt(workspaceId, options = {}) {
3329
+ return this.transport.request(`/v1/workspaces/${encodeURIComponent(workspaceId)}/agent/interrupt`, AgentInterruptResponseSchema, {
3330
+ method: "POST",
3331
+ signal: options.signal
3332
+ });
3333
+ }
3334
+ };
3335
+ //#endregion
3336
+ //#region src/resources/attachments/attachments.ts
3337
+ function contentDisposition(name) {
3338
+ const clean = name.replace(/[\r\n]/g, "");
3339
+ if (/^[ -~]*$/.test(clean)) return `attachment; filename="${clean.replace(/(["\\])/g, "\\$1")}"`;
3340
+ return `attachment; filename*=UTF-8''${encodeURIComponent(clean)}`;
3341
+ }
3342
+ function progressStream(body, total, onProgress) {
3343
+ const source = new Response(body).body ?? new ReadableStream({ start: (c) => c.close() });
3344
+ let uploaded = 0;
3345
+ return source.pipeThrough(new TransformStream({ transform(chunk, controller) {
3346
+ uploaded += chunk.byteLength;
3347
+ controller.enqueue(chunk);
3348
+ onProgress(Math.min(uploaded, total), total);
3349
+ } }));
3350
+ }
3351
+ var AttachmentsApi = class {
3352
+ transport;
3353
+ constructor(transport) {
3354
+ this.transport = transport;
3355
+ }
3356
+ async upload(workspaceId, input) {
3357
+ const id = (input.id ?? crypto.randomUUID()).toLowerCase();
3358
+ let sha256 = input.sha256;
3359
+ if (!sha256) {
3360
+ if (input.body instanceof ReadableStream) throw new Error("Stream uploads require sha256 before body consumption.");
3361
+ const digest = await crypto.subtle.digest("SHA-256", await new Response(input.body).arrayBuffer());
3362
+ sha256 = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
3363
+ }
3364
+ if (!/^[0-9a-f]{64}$/.test(sha256)) throw new Error("sha256 must be 64 lowercase hexadecimal characters.");
3365
+ const body = input.onProgress ? progressStream(input.body, input.sizeBytes, input.onProgress) : input.body;
3366
+ const init = {
3367
+ method: "PUT",
3368
+ ...input.signal ? { signal: input.signal } : {},
3369
+ headers: {
3370
+ "content-type": input.mediaType ?? "application/octet-stream",
3371
+ "content-disposition": contentDisposition(input.name),
3372
+ "content-length": String(input.sizeBytes),
3373
+ "x-content-sha256": sha256
3374
+ },
3375
+ body,
3376
+ ...body instanceof ReadableStream ? { duplex: "half" } : {}
3377
+ };
3378
+ return await this.transport.request(`/v1/workspaces/${encodeURIComponent(workspaceId)}/attachments/${id}`, InputObjectReferenceSchema, init);
3379
+ }
1986
3380
  };
1987
3381
  //#endregion
1988
3382
  //#region src/transport/common.ts
@@ -2047,16 +3441,72 @@ var OperationsApi = class {
2047
3441
  };
2048
3442
  //#endregion
2049
3443
  //#region src/resources/conversations/conversations.ts
3444
+ function workspacePath(id) {
3445
+ return `/v1/workspaces/${encodeURIComponent(id)}`;
3446
+ }
3447
+ async function* liveEvents(body) {
3448
+ const decoder = new TextDecoder();
3449
+ let buffer = "";
3450
+ for await (const chunk of body) {
3451
+ buffer += decoder.decode(chunk, { stream: true });
3452
+ let end = buffer.indexOf("\n\n");
3453
+ while (end !== -1) {
3454
+ const block = buffer.slice(0, end);
3455
+ buffer = buffer.slice(end + 2);
3456
+ const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
3457
+ const event = data ? ConversationLiveEventSchema.safeParse(JSON.parse(data)) : null;
3458
+ if (event?.success) yield event.data;
3459
+ end = buffer.indexOf("\n\n");
3460
+ }
3461
+ }
3462
+ }
2050
3463
  var ConversationsApi = class {
2051
3464
  transport;
2052
3465
  constructor(transport) {
2053
3466
  this.transport = transport;
2054
3467
  }
2055
3468
  async list(id, query = {}, options = {}) {
2056
- return page(await this.transport.request(`/v1/workspaces/${encodeURIComponent(id)}/conversation?${queryString({
3469
+ const body = await this.transport.request(`${workspacePath(id)}/conversation?${queryString({
3470
+ cursor: query.cursor,
3471
+ limit: query.limit ?? 100
3472
+ })}`, ConversationPageSchema, options);
3473
+ return {
3474
+ ...page(body),
3475
+ protocol: body.protocol,
3476
+ retention: body.retention
3477
+ };
3478
+ }
3479
+ async acceptRequest(id, identity, options = {}) {
3480
+ return this.transport.request(`${workspacePath(id)}/conversation/requests`, ConversationRequestReceiptSchema, {
3481
+ ...options,
3482
+ method: "POST",
3483
+ body: JSON.stringify(identity)
3484
+ });
3485
+ }
3486
+ async listConversations(id, options = {}) {
3487
+ return (await this.transport.request(`${workspacePath(id)}/conversations`, ConversationListSchema, options)).items;
3488
+ }
3489
+ async read(id, conversationId, query = {}, options = {}) {
3490
+ const body = await this.transport.request(`${workspacePath(id)}/conversations/${encodeURIComponent(conversationId)}?${queryString({
2057
3491
  cursor: query.cursor,
2058
3492
  limit: query.limit ?? 100
2059
- })}`, ConversationPageSchema, options));
3493
+ })}`, ConversationDocumentPageSchema, options);
3494
+ return {
3495
+ ...page(body),
3496
+ conversation: body.conversation,
3497
+ protocol: body.protocol
3498
+ };
3499
+ }
3500
+ async delete(id, conversationId, options = {}) {
3501
+ await this.transport.request(`${workspacePath(id)}/conversations/${encodeURIComponent(conversationId)}`, z.undefined(), {
3502
+ ...options,
3503
+ method: "DELETE"
3504
+ });
3505
+ }
3506
+ async *live(id, options = {}) {
3507
+ const path = options.conversationId ? `${workspacePath(id)}/conversations/${encodeURIComponent(options.conversationId)}/live` : `${workspacePath(id)}/conversation/live`;
3508
+ const response = await this.transport.stream(path, options.signal);
3509
+ if (response.body) yield* liveEvents(response.body);
2060
3510
  }
2061
3511
  };
2062
3512
  //#endregion
@@ -2088,8 +3538,40 @@ var NetworkEventsApi = class extends WorkspaceCursorApi {
2088
3538
  }
2089
3539
  };
2090
3540
  var OutputsApi = class extends WorkspaceCursorApi {
3541
+ binaryTransport;
3542
+ async download(workspaceId, outputId, options = {}) {
3543
+ const response = await this.binaryTransport.raw(`/v1/workspaces/${encodeURIComponent(workspaceId)}/outputs/${encodeURIComponent(outputId)}/content`, options);
3544
+ if (!response.ok) throw responseError(response, await response.json());
3545
+ return response;
3546
+ }
2091
3547
  constructor(transport) {
2092
3548
  super(transport, "outputs", OutputPageSchema);
3549
+ this.binaryTransport = transport;
3550
+ }
3551
+ };
3552
+ //#endregion
3553
+ //#region src/resources/displays/displays.ts
3554
+ var DisplaysApi = class {
3555
+ transport;
3556
+ constructor(transport) {
3557
+ this.transport = transport;
3558
+ }
3559
+ capture(workspaceId, options = {}) {
3560
+ return this.transport.request(`/v1/workspaces/${encodeURIComponent(workspaceId)}/display/screenshot`, ScreenshotResourceSchema, {
3561
+ method: "POST",
3562
+ body: "{}",
3563
+ signal: options.signal
3564
+ });
3565
+ }
3566
+ open(workspaceId, control = false, options = {}) {
3567
+ return this.transport.request(`/v1/workspaces/${encodeURIComponent(workspaceId)}/display`, PreviewOpenResponseSchema, {
3568
+ method: "POST",
3569
+ body: JSON.stringify({
3570
+ control,
3571
+ session: options.session ?? { mode: "local" }
3572
+ }),
3573
+ signal: options.signal
3574
+ });
2093
3575
  }
2094
3576
  };
2095
3577
  //#endregion
@@ -2117,6 +3599,9 @@ var KeysApi = class {
2117
3599
  cursor = page.next_cursor ?? void 0;
2118
3600
  } while (cursor);
2119
3601
  }
3602
+ expiring(query = {}, options = {}) {
3603
+ return this.transport.request(`/v1/keys/expiring?${queryString({ within_hours: query.withinHours })}`, ExpiringKeyListResponseSchema, options);
3604
+ }
2120
3605
  issue(principalId, input, options = {}) {
2121
3606
  return this.transport.request(`/v1/principals/${encodeURIComponent(principalId)}/keys`, KeyIssueResponseSchema, {
2122
3607
  method: "POST",
@@ -2124,6 +3609,13 @@ var KeysApi = class {
2124
3609
  signal: options.signal
2125
3610
  });
2126
3611
  }
3612
+ rotate(principalId, keyId, input, options = {}) {
3613
+ return this.transport.request(`/v1/principals/${encodeURIComponent(principalId)}/keys/${encodeURIComponent(keyId)}/rotate`, KeyIssueResponseSchema, {
3614
+ method: "POST",
3615
+ body: JSON.stringify(input),
3616
+ signal: options.signal
3617
+ });
3618
+ }
2127
3619
  revoke(principalId, keyId, options = {}) {
2128
3620
  return this.transport.request(`/v1/principals/${encodeURIComponent(principalId)}/keys/${encodeURIComponent(keyId)}`, z.object({ revoked: z.literal(true) }), {
2129
3621
  method: "DELETE",
@@ -2155,6 +3647,76 @@ var RecoveryApi = class {
2155
3647
  }
2156
3648
  };
2157
3649
  //#endregion
3650
+ //#region src/resources/previews/previews.ts
3651
+ var PreviewsApi = class {
3652
+ transport;
3653
+ constructor(transport) {
3654
+ this.transport = transport;
3655
+ }
3656
+ list(workspaceId, options = {}) {
3657
+ return this.transport.request(`/v1/workspaces/${encodeURIComponent(workspaceId)}/previews`, PreviewListSchema, options);
3658
+ }
3659
+ open(workspaceId, name, options = {}) {
3660
+ return this.transport.request(`/v1/workspaces/${encodeURIComponent(workspaceId)}/previews/${encodeURIComponent(name)}`, PreviewOpenResponseSchema, {
3661
+ method: "POST",
3662
+ body: JSON.stringify({ session: options.session ?? { mode: "local" } }),
3663
+ signal: options.signal
3664
+ });
3665
+ }
3666
+ };
3667
+ //#endregion
3668
+ //#region src/resources/principals/principals.ts
3669
+ var PrincipalsApi = class {
3670
+ transport;
3671
+ constructor(transport) {
3672
+ this.transport = transport;
3673
+ }
3674
+ list(options = {}) {
3675
+ return this.transport.request("/v1/principals", PrincipalListResponseSchema, options);
3676
+ }
3677
+ get(principalId, options = {}) {
3678
+ return this.transport.request(`/v1/principals/${encodeURIComponent(principalId)}`, PrincipalResourceSchema, options);
3679
+ }
3680
+ create(input, options = {}) {
3681
+ return this.transport.request("/v1/principals", PrincipalResourceSchema, {
3682
+ method: "POST",
3683
+ body: JSON.stringify(input),
3684
+ signal: options.signal
3685
+ });
3686
+ }
3687
+ update(principalId, input, options = {}) {
3688
+ return this.transport.request(`/v1/principals/${encodeURIComponent(principalId)}`, PrincipalResourceSchema, {
3689
+ method: "PATCH",
3690
+ body: JSON.stringify(input),
3691
+ signal: options.signal
3692
+ });
3693
+ }
3694
+ };
3695
+ //#endregion
3696
+ //#region src/resources/secrets/secrets.ts
3697
+ var SecretsApi = class {
3698
+ transport;
3699
+ constructor(transport) {
3700
+ this.transport = transport;
3701
+ }
3702
+ async put(name, input, options = {}) {
3703
+ return this.transport.request(`/v1/secrets/${encodeURIComponent(name)}`, SecretResourceSchema, {
3704
+ ...options,
3705
+ method: "PUT",
3706
+ body: JSON.stringify(input)
3707
+ });
3708
+ }
3709
+ async list(options = {}) {
3710
+ return (await this.transport.request("/v1/secrets", SecretListSchema, options)).items;
3711
+ }
3712
+ async retire(name, options = {}) {
3713
+ return this.transport.request(`/v1/secrets/${encodeURIComponent(name)}`, SecretResourceSchema, {
3714
+ ...options,
3715
+ method: "DELETE"
3716
+ });
3717
+ }
3718
+ };
3719
+ //#endregion
2158
3720
  //#region src/resources/templates/templates.ts
2159
3721
  var TemplatesApi = class {
2160
3722
  transport;
@@ -2168,7 +3730,27 @@ var TemplatesApi = class {
2168
3730
  })}`, TemplatePageSchema, options));
2169
3731
  }
2170
3732
  async list(options = {}) {
2171
- return (await this.page({}, options)).items;
3733
+ const items = [];
3734
+ let cursor;
3735
+ do {
3736
+ const result = await this.page({ cursor }, options);
3737
+ items.push(...result.items);
3738
+ cursor = result.nextCursor ?? void 0;
3739
+ } while (cursor);
3740
+ return items;
3741
+ }
3742
+ async publish(manifest, options = {}) {
3743
+ return this.transport.request("/v1/templates", TemplateListItemSchema, {
3744
+ ...options,
3745
+ method: "POST",
3746
+ body: JSON.stringify({ manifest })
3747
+ });
3748
+ }
3749
+ async retire(name, version, options = {}) {
3750
+ return this.transport.request(`/v1/templates/${encodeURIComponent(name)}/${encodeURIComponent(version)}`, TemplateListItemSchema, {
3751
+ ...options,
3752
+ method: "DELETE"
3753
+ });
2172
3754
  }
2173
3755
  };
2174
3756
  //#endregion
@@ -2432,6 +4014,20 @@ var PocketCoderTransport = class {
2432
4014
  await delay(100 * 2 ** attempt, signal);
2433
4015
  }
2434
4016
  }
4017
+ async stream(path, signal) {
4018
+ const response = await this.fetchImpl(`${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`, {
4019
+ headers: {
4020
+ authorization: `Bearer ${this.apiKey}`,
4021
+ accept: "text/event-stream"
4022
+ },
4023
+ ...signal ? { signal } : {}
4024
+ });
4025
+ if (!response.ok) {
4026
+ const text = await response.text();
4027
+ throw responseError(response, text ? JSON.parse(text) : void 0);
4028
+ }
4029
+ return response;
4030
+ }
2435
4031
  async request(path, schema, init = {}) {
2436
4032
  const response = await this.raw(path, init);
2437
4033
  const text = await response.text();
@@ -2496,8 +4092,11 @@ async function delay(milliseconds, signal) {
2496
4092
  //#region src/client.ts
2497
4093
  var PocketCoderClient = class {
2498
4094
  transport;
4095
+ secrets;
2499
4096
  templates;
2500
4097
  workspaces;
4098
+ previews;
4099
+ displays;
2501
4100
  attachments;
2502
4101
  agent;
2503
4102
  conversations;
@@ -2508,12 +4107,16 @@ var PocketCoderClient = class {
2508
4107
  outputs;
2509
4108
  administration;
2510
4109
  terminals;
4110
+ principals;
2511
4111
  keys;
2512
4112
  recovery;
2513
4113
  constructor(config, fetchImpl = fetch) {
2514
4114
  this.transport = new PocketCoderTransport(config, fetchImpl);
4115
+ this.secrets = new SecretsApi(this.transport);
2515
4116
  this.templates = new TemplatesApi(this.transport);
2516
4117
  this.workspaces = new WorkspacesApi(this.transport);
4118
+ this.previews = new PreviewsApi(this.transport);
4119
+ this.displays = new DisplaysApi(this.transport);
2517
4120
  this.attachments = new AttachmentsApi(this.transport);
2518
4121
  this.agent = new AgentApi(this.transport, this.workspaces);
2519
4122
  this.conversations = new ConversationsApi(this.transport);
@@ -2524,6 +4127,7 @@ var PocketCoderClient = class {
2524
4127
  this.outputs = new OutputsApi(this.transport);
2525
4128
  this.administration = new AdministrationApi(this.transport);
2526
4129
  this.terminals = new TerminalsApi(this.transport);
4130
+ this.principals = new PrincipalsApi(this.transport);
2527
4131
  this.keys = new KeysApi(this.transport);
2528
4132
  this.recovery = new RecoveryApi(this.transport);
2529
4133
  }
@@ -2676,4 +4280,4 @@ var WorkspaceTurnResolver = class {
2676
4280
  }
2677
4281
  };
2678
4282
  //#endregion
2679
- export { AdministrationApi, AgentApi, AgentNotReadyError, AttachmentsApi, CheckpointsApi, ConversationGoneError, ConversationsApi, KeysApi, LogsApi, NetworkEventsApi, OperationsApi, OutputsApi, PocketCoderClient, PocketCoderError, RecoveryApi, TERMINAL_WORKSPACE_STATES, TemplatesApi, TerminalConnection, TerminalsApi, WorkspaceTerminalError, WorkspaceTurnResolutionError, WorkspaceTurnResolver, WorkspacesApi, isPocketCoderErrorCode, splitAttachmentManifest };
4283
+ export { AdministrationApi, AgentApi, AgentNotReadyError, AttachmentsApi, CheckpointsApi, ConversationGoneError, ConversationsApi, KeysApi, LogsApi, NetworkEventsApi, OperationsApi, OutputsApi, PocketCoderClient, PocketCoderError, PrincipalsApi, RecoveryApi, SecretsApi, TERMINAL_WORKSPACE_STATES, TemplatesApi, TerminalConnection, TerminalsApi, WorkspaceTerminalError, WorkspaceTurnResolutionError, WorkspaceTurnResolver, WorkspacesApi, isPocketCoderErrorCode, splitAttachmentManifest };