@opengeni/contracts 0.39.0 → 0.40.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/src/index.ts CHANGED
@@ -36,6 +36,19 @@ export {
36
36
  normalizeWorkspaceArtifactSlug,
37
37
  } from "./artifacts";
38
38
 
39
+ export {
40
+ MCP_MUTATION_RECEIPT_MAX_BYTES,
41
+ MCP_MUTATION_RECEIPT_VERSION,
42
+ McpMutationReceipt,
43
+ McpMutationReceiptIdempotencyStatus,
44
+ McpMutationReceiptOutcome,
45
+ McpMutationResource,
46
+ type McpMutationReceipt as McpMutationReceiptType,
47
+ type McpMutationReceiptIdempotencyStatus as McpMutationReceiptIdempotencyStatusType,
48
+ type McpMutationReceiptOutcome as McpMutationReceiptOutcomeType,
49
+ type McpMutationResource as McpMutationResourceType,
50
+ } from "./mcp-receipts";
51
+
39
52
  export {
40
53
  SESSION_EVENT_PAYLOAD_MAX_BYTES,
41
54
  approximateSessionEventTokens,
@@ -53,6 +66,11 @@ export {
53
66
  } from "./event-preview";
54
67
 
55
68
  export {
69
+ COMPUTER_SCREENSHOT_MAX_BYTES,
70
+ COMPUTER_SCREENSHOT_MAX_DIMENSION,
71
+ COMPUTER_SCREENSHOT_MAX_PIXELS,
72
+ COMPUTER_SCREENSHOT_RETENTION_MS,
73
+ COMPUTER_SCREENSHOT_WORKSPACE_QUOTA_BYTES,
56
74
  RETAINED_OUTPUT_DEFAULT_PAGE_BYTES,
57
75
  RETAINED_OUTPUT_MAX_PAGE_BYTES,
58
76
  RETAINED_OUTPUT_RECEIPT_MAX_BYTES,
@@ -63,12 +81,14 @@ export {
63
81
  RetainedOutputKind,
64
82
  RetainedOutputUnavailableReason,
65
83
  retainedArtifactReferenceFromFile,
84
+ retainedScreenshotReferenceFromFile,
66
85
  retainedOutputUnavailable,
67
86
  resolveRetainedOutputRange,
68
87
  validateRetainedOutputEvidence,
69
88
  type RetainedArtifactFileInput,
70
89
  type RetainedArtifactMetadata,
71
90
  type RetainedArtifactReference,
91
+ type RetainedScreenshotArtifactInput,
72
92
  type RetainedArtifactUnavailable,
73
93
  type RetainedOutputAvailableEvidence,
74
94
  type RetainedOutputEvidence,
@@ -596,9 +616,9 @@ export const Permission = z.enum([
596
616
  "sessions:control",
597
617
  // sandbox workspace (sandbox contract §C.3 / crosscut PART 1.2). stream:view is a
598
618
  // REAL, distinct permission — strictly BROADER than sessions:read — because the
599
- // pixel plane (Channel B) is UN-REDACTED: a viewer of raw pixels can see cloud
600
- // creds the agent cat's into a terminal, which the redacted Channel-A event log
601
- // never exposes. sessions:read is NOT permission to watch raw pixels.
619
+ // pixel plane (Channel B) exposes raw pixels: a viewer can see content the
620
+ // structured Channel-A event log never captured. sessions:read is NOT
621
+ // permission to watch raw pixels.
602
622
  "stream:view",
603
623
  // SEPARATE from stream:view: raw input to the desktop (bypasses approvalQueue /
604
624
  // interrupt). NEVER granted by default in v1 (the input plane is OFF —
@@ -629,8 +649,14 @@ export const Permission = z.enum([
629
649
  "environments:manage",
630
650
  /** @deprecated alias of variable-sets:use */
631
651
  "environments:use",
652
+ "variable-sets:list",
653
+ "variable-sets:read",
654
+ "variable-sets:write",
632
655
  "variable-sets:manage",
633
656
  "variable-sets:use",
657
+ "secrets:list",
658
+ "secrets:read",
659
+ "secrets:write",
634
660
  // Attach or rotate per-session third-party MCP server credentials. Deliberately
635
661
  // not part of the worker's default first-party MCP permission set: a sandboxed
636
662
  // agent must not be able to hand itself new bearer credentials.
@@ -715,6 +741,7 @@ export const FIRST_PARTY_MCP_TOOL_NAMES = [
715
741
  "sandbox_swap",
716
742
  "run_on",
717
743
  "sandbox_provision",
744
+ "connected_machine_remove",
718
745
  "rig_list",
719
746
  "rig_get",
720
747
  "rig_propose_change",
@@ -731,6 +758,7 @@ export const FIRST_PARTY_MCP_TOOL_NAMES = [
731
758
  "set_other_session_title",
732
759
  "variable_set_list",
733
760
  "environment_list",
761
+ "variable_set_get_variable",
734
762
  "variable_set_set_variable",
735
763
  "environment_set_variable",
736
764
  "github_connect_link",
@@ -1262,6 +1290,9 @@ export const WorkspaceSettingsSchema = z
1262
1290
  // Default compaction strategy for NEW Codex sessions created in this
1263
1291
  // workspace. Absent ⇒ remote_v2. Non-Codex sessions always freeze portable.
1264
1292
  codexCompactionDefault: CodexCompactionMode.optional(),
1293
+ // Whether agents may expose and invoke the built-in structured human-input
1294
+ // tool. Absent preserves the historical enabled behavior.
1295
+ agentHumanInputEnabled: z.boolean().optional(),
1265
1296
  // Optional Slack reaction invocation. Absent/invalid fails closed to the
1266
1297
  // disabled default via resolveWorkspaceSlackReactionSummonSettings.
1267
1298
  slackReactionSummon: WorkspaceSlackReactionSummonSettings.optional(),
@@ -1282,6 +1313,12 @@ export function resolveWorkspaceCodexCompactionDefault(settings: unknown): Codex
1282
1313
  return parsed.data.codexCompactionDefault ?? "remote_v2";
1283
1314
  }
1284
1315
 
1316
+ /** Whether agents may request structured human input (enabled when unset). */
1317
+ export function resolveWorkspaceAgentHumanInputEnabled(settings: unknown): boolean {
1318
+ const parsed = WorkspaceSettingsSchema.safeParse(settings ?? {});
1319
+ return parsed.success ? parsed.data.agentHumanInputEnabled !== false : true;
1320
+ }
1321
+
1285
1322
  /**
1286
1323
  * Resolve whether voice input is enabled for a workspace.
1287
1324
  *
@@ -1307,7 +1344,9 @@ export function resolveWorkspaceSlackReactionSummonSettings(
1307
1344
  return {
1308
1345
  enabled: DEFAULT_WORKSPACE_SLACK_REACTION_SUMMON_SETTINGS.enabled,
1309
1346
  emoji: DEFAULT_WORKSPACE_SLACK_REACTION_SUMMON_SETTINGS.emoji,
1310
- channelPolicy: { ...DEFAULT_WORKSPACE_SLACK_REACTION_SUMMON_SETTINGS.channelPolicy },
1347
+ channelPolicy: {
1348
+ ...DEFAULT_WORKSPACE_SLACK_REACTION_SUMMON_SETTINGS.channelPolicy,
1349
+ },
1311
1350
  };
1312
1351
  }
1313
1352
  return configured.channelPolicy.mode === "allowlist"
@@ -1342,6 +1381,7 @@ export const UpdateWorkspaceSettingsRequest = z
1342
1381
  transcription: WorkspaceTranscriptionPolicy.optional(),
1343
1382
  maxNestedAgentDepth: NestedAgentDepthValue.nullable().optional(),
1344
1383
  codexCompactionDefault: CodexCompactionMode.optional(),
1384
+ agentHumanInputEnabled: z.boolean().optional(),
1345
1385
  slackReactionSummon: WorkspaceSlackReactionSummonSettings.optional(),
1346
1386
  })
1347
1387
  .passthrough();
@@ -1669,6 +1709,11 @@ export const EnrollmentBearerPayload = z.object({
1669
1709
  workspaceId: z.string().uuid(),
1670
1710
  agentId: z.string().uuid(),
1671
1711
  enrollmentId: z.string().uuid(),
1712
+ // Backward-compatible credential-family fence. Generationless bearers minted
1713
+ // before migration 0061 parse ONLY as generation 1, matching the migration's
1714
+ // default for existing rows. signEnrollmentBearer serializes the parsed output,
1715
+ // so every newly signed bearer carries this claim explicitly.
1716
+ credentialGeneration: z.number().int().positive().default(1),
1672
1717
  // The Account-scoped control-plane subject prefix the agent subscribes to.
1673
1718
  subjectPrefix: z.string().min(1),
1674
1719
  exp: z.number().int().positive(),
@@ -1703,7 +1748,13 @@ export async function verifyEnrollmentBearer(
1703
1748
  if (!constantTimeEqual(signature, expected)) {
1704
1749
  return null;
1705
1750
  }
1706
- const payload = EnrollmentBearerPayload.safeParse(JSON.parse(base64UrlDecode(encodedPayload)));
1751
+ let decoded: unknown;
1752
+ try {
1753
+ decoded = JSON.parse(base64UrlDecode(encodedPayload));
1754
+ } catch {
1755
+ return null;
1756
+ }
1757
+ const payload = EnrollmentBearerPayload.safeParse(decoded);
1707
1758
  if (!payload.success || payload.data.exp < nowSeconds) {
1708
1759
  return null;
1709
1760
  }
@@ -2510,13 +2561,6 @@ export type RunCredentialAuthNeeded = {
2510
2561
  message?: string;
2511
2562
  };
2512
2563
 
2513
- export type RunCredentialRedaction = {
2514
- /** Bounded diagnostic label used only in the replacement marker. */
2515
- name: string;
2516
- /** One atomic secret value that must be removed from streamed/audit output. */
2517
- value: string;
2518
- };
2519
-
2520
2564
  export type RunCredentialsRequest = {
2521
2565
  accountId: string;
2522
2566
  workspaceId: string;
@@ -2562,12 +2606,6 @@ export type RunCredentialsResolution =
2562
2606
  files?: RunCredentialFile[];
2563
2607
  /** Environment name to one returned relative file path. */
2564
2608
  fileEnvironment?: Record<string, string>;
2565
- /**
2566
- * Atomic sensitive values embedded inside credential files or derived
2567
- * material. Environment values are registered automatically; hosts list
2568
- * additional file-contained values here so chunked output is redacted.
2569
- */
2570
- redactions?: RunCredentialRedaction[];
2571
2609
  /** Earliest material expiry. Null/omitted uses a bounded refresh cadence. */
2572
2610
  expiresAt?: string | null;
2573
2611
  /** Partial degradation: usable material may coexist with reconnect notices. */
@@ -2959,6 +2997,93 @@ export function gitCredentialBindingIdForRepository(
2959
2997
  );
2960
2998
  }
2961
2999
 
3000
+ type GitRemotePathSemantics = "dot_git_alias" | "exact";
3001
+
3002
+ /**
3003
+ * Provider-declared remote-path behavior. Keeping this exhaustive makes a new
3004
+ * provider choose its semantics instead of inheriting GitHub conventions.
3005
+ */
3006
+ const GIT_REMOTE_PATH_SEMANTICS = {
3007
+ github: "dot_git_alias",
3008
+ gitlab: "dot_git_alias",
3009
+ azure_devops: "exact",
3010
+ } as const satisfies Record<GitCredentialProvider, GitRemotePathSemantics>;
3011
+
3012
+ function gitRemotePathSemantics(
3013
+ provider: GitCredentialProvider | null | undefined,
3014
+ ): GitRemotePathSemantics {
3015
+ return provider ? GIT_REMOTE_PATH_SEMANTICS[provider] : "exact";
3016
+ }
3017
+
3018
+ export class RepositoryUriError extends Error {
3019
+ constructor(message: string) {
3020
+ super(message);
3021
+ this.name = "RepositoryUriError";
3022
+ }
3023
+ }
3024
+
3025
+ /**
3026
+ * Normalize only the safe, provider-neutral parts of an HTTPS clone URI.
3027
+ *
3028
+ * The provider-defined path is opaque: this helper never adds or removes a
3029
+ * `.git` suffix. Embedded user info, query parameters, and fragments are not
3030
+ * durable resource identity and are omitted, matching the existing secret-free
3031
+ * resource contract.
3032
+ */
3033
+ export function normalizeRepositoryTransportUri(uri: string): string {
3034
+ let url: URL;
3035
+ try {
3036
+ url = new URL(uri.trim());
3037
+ } catch {
3038
+ throw new RepositoryUriError(`invalid repository URI: ${uri}`);
3039
+ }
3040
+ if (url.protocol !== "https:" || !url.hostname) {
3041
+ throw new RepositoryUriError("repository resources must use HTTPS Git URLs");
3042
+ }
3043
+ const path = url.pathname.replace(/^\/+|\/+$/g, "");
3044
+ if (path.split("/").filter(Boolean).length < 2) {
3045
+ throw new RepositoryUriError("repository URL must include owner and repo");
3046
+ }
3047
+ return `https://${url.host.toLowerCase()}/${path}`;
3048
+ }
3049
+
3050
+ /**
3051
+ * Return every URI spelling that a provider explicitly declares equivalent.
3052
+ * Exact-path and unqualified providers return only the normalized input URI.
3053
+ */
3054
+ export function gitRemoteUriAliases(
3055
+ uri: string,
3056
+ provider: GitCredentialProvider | null | undefined,
3057
+ ): string[] {
3058
+ const normalizedUri = normalizeRepositoryTransportUri(uri);
3059
+ if (gitRemotePathSemantics(provider) === "exact") {
3060
+ return [normalizedUri];
3061
+ }
3062
+ const base = normalizedUri.replace(/\.git$/, "");
3063
+ return [...new Set([normalizedUri, base, `${base}.git`])];
3064
+ }
3065
+
3066
+ /** Stable remote identity for deduplication and credential-binding ownership. */
3067
+ export function gitRemoteIdentity(
3068
+ uri: string,
3069
+ provider: GitCredentialProvider | null | undefined,
3070
+ ): string {
3071
+ const normalizedUri = normalizeRepositoryTransportUri(uri);
3072
+ return gitRemotePathSemantics(provider) === "dot_git_alias"
3073
+ ? normalizedUri.replace(/\.git$/, "")
3074
+ : normalizedUri;
3075
+ }
3076
+
3077
+ /** Provider-aware Git credential-helper path aliases, without a leading slash. */
3078
+ export function gitRemotePathAliases(
3079
+ uri: string,
3080
+ provider: GitCredentialProvider | null | undefined,
3081
+ ): string[] {
3082
+ return gitRemoteUriAliases(uri, provider).map((alias) =>
3083
+ new URL(alias).pathname.replace(/^\/+|\/+$/g, ""),
3084
+ );
3085
+ }
3086
+
2962
3087
  export const FileResourceRef = z.object({
2963
3088
  kind: z.literal("file"),
2964
3089
  fileId: z.string().uuid(),
@@ -3039,7 +3164,10 @@ export function resourceMountPathCollisionKey(path: string): string {
3039
3164
  * GitLab, Azure DevOps, or a custom host do not collide. Encoding the host keeps
3040
3165
  * IPv6/custom-port identities inside one portable path segment.
3041
3166
  */
3042
- export function defaultRepositoryMountPath(uri: string): string {
3167
+ export function defaultRepositoryMountPath(
3168
+ uri: string,
3169
+ provider?: GitCredentialProvider | null,
3170
+ ): string {
3043
3171
  let url: URL;
3044
3172
  try {
3045
3173
  url = new URL(uri);
@@ -3049,7 +3177,11 @@ export function defaultRepositoryMountPath(uri: string): string {
3049
3177
  if (url.protocol !== "https:" || !url.host) {
3050
3178
  throw new ResourceMountPathError(`invalid repository URI for mount path: ${uri}`);
3051
3179
  }
3052
- const repositoryPath = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
3180
+ const remotePath = url.pathname.replace(/^\/+|\/+$/g, "");
3181
+ const repositoryPath =
3182
+ gitRemotePathSemantics(provider) === "dot_git_alias"
3183
+ ? remotePath.replace(/\.git$/, "")
3184
+ : remotePath;
3053
3185
  const segments = repositoryPath.split("/").filter(Boolean);
3054
3186
  if (segments.length < 2) {
3055
3187
  throw new ResourceMountPathError(`repository URI must include owner and repo: ${uri}`);
@@ -3066,7 +3198,7 @@ export function resourceMountPath(resource: ResourceRef): string {
3066
3198
  if (resource.mountPath) return normalizeResourceMountPath(resource.mountPath);
3067
3199
  return resource.kind === "file"
3068
3200
  ? normalizeResourceMountPath(`${DEFAULT_FILE_RESOURCE_MOUNT_ROOT}/${resource.fileId}`)
3069
- : defaultRepositoryMountPath(resource.uri);
3201
+ : defaultRepositoryMountPath(resource.uri, gitCredentialProviderForRepository(resource));
3070
3202
  }
3071
3203
 
3072
3204
  /** Fail before sandbox execution when two resources share a portable path. */
@@ -3757,7 +3889,10 @@ export function resourceIdentityKey(resource: ResourceRef): string {
3757
3889
  if (resource.kind === "file") {
3758
3890
  return `file:${resource.fileId}`;
3759
3891
  }
3760
- return `repository:${resource.uri}`;
3892
+ return `repository:${gitRemoteIdentity(
3893
+ resource.uri,
3894
+ gitCredentialProviderForRepository(resource),
3895
+ )}`;
3761
3896
  }
3762
3897
 
3763
3898
  function sortJson(value: unknown): unknown {
@@ -4314,6 +4449,7 @@ export const SessionAuthorizationOperation = z.enum([
4314
4449
  "session.viewer.read",
4315
4450
  "session.viewer.control",
4316
4451
  "session.first_party_mcp.call",
4452
+ "session.secret.read",
4317
4453
  "session.toolspace.call",
4318
4454
  "session.pin.write",
4319
4455
  "session.codex_account.write",
@@ -5035,9 +5171,9 @@ function withVariableSetIdAlias<T extends z.ZodRawShape>(shape: T) {
5035
5171
  }, z.object(shape));
5036
5172
  }
5037
5173
 
5038
- // Metadata only by design: no schema in this file ever carries a variable value
5039
- // back to a client. Values are write-only and decrypted exclusively inside the
5040
- // worker at sandbox materialization time.
5174
+ // Generic variable-set reads remain metadata-only. Exact plaintext has one
5175
+ // dedicated response schema so callers cannot accidentally widen another
5176
+ // workspace/session response with secret material.
5041
5177
  export const VariableSetVariableMetadata = z.object({
5042
5178
  name: VariableSetVariableName,
5043
5179
  version: z.number().int().positive(),
@@ -5050,6 +5186,14 @@ export const WorkspaceEnvironmentVariableMetadata = VariableSetVariableMetadata;
5050
5186
  /** @deprecated use VariableSetVariableMetadata */
5051
5187
  export type WorkspaceEnvironmentVariableMetadata = VariableSetVariableMetadata;
5052
5188
 
5189
+ export const VariableSetSecret = z.object({
5190
+ variableSetId: z.string().uuid(),
5191
+ name: VariableSetVariableName,
5192
+ version: z.number().int().positive(),
5193
+ value: z.string(),
5194
+ });
5195
+ export type VariableSetSecret = z.infer<typeof VariableSetSecret>;
5196
+
5053
5197
  export const VariableSet = z.object({
5054
5198
  id: z.string().uuid(),
5055
5199
  accountId: z.string().uuid(),
@@ -5251,7 +5395,11 @@ export type ScheduledTaskStatus = z.infer<typeof ScheduledTaskStatus>;
5251
5395
  export const ScheduledTaskRunStatus = z.enum(["queued", "dispatched", "failed"]);
5252
5396
  export type ScheduledTaskRunStatus = z.infer<typeof ScheduledTaskRunStatus>;
5253
5397
 
5254
- export const ScheduledTaskRunMode = z.enum(["new_session_per_run", "reusable_session"]);
5398
+ export const ScheduledTaskRunMode = z.enum([
5399
+ "new_session_per_run",
5400
+ "reusable_session",
5401
+ "existing_session",
5402
+ ]);
5255
5403
  export type ScheduledTaskRunMode = z.infer<typeof ScheduledTaskRunMode>;
5256
5404
 
5257
5405
  export const ScheduledTaskOverlapPolicy = z.enum(["allow_concurrent", "skip", "buffer_one"]);
@@ -5315,10 +5463,14 @@ export const ScheduledTask = z.object({
5315
5463
  runMode: ScheduledTaskRunMode,
5316
5464
  overlapPolicy: ScheduledTaskOverlapPolicy,
5317
5465
  agentConfig: ScheduledTaskAgentConfig,
5318
- createdBy: TurnInitiator.default({ kind: "service", subjectId: "unattributed-legacy" }),
5466
+ createdBy: TurnInitiator.default({
5467
+ kind: "service",
5468
+ subjectId: "unattributed-legacy",
5469
+ }),
5319
5470
  createdByContext: TurnInitiatorContext.default({}),
5320
5471
  personalConnections: z.array(McpPersonalConnectionSummary).default([]),
5321
5472
  reusableSessionId: z.string().uuid().nullable(),
5473
+ targetSessionId: z.string().uuid().nullable().default(null),
5322
5474
  variableSetId: z.string().uuid().nullable().default(null),
5323
5475
  /** @deprecated use variableSetId */
5324
5476
  environmentId: z.string().uuid().nullable().default(null),
@@ -5354,6 +5506,7 @@ export const CreateScheduledTaskRequest = withVariableSetIdAlias({
5354
5506
  schedule: ScheduledTaskScheduleSpec,
5355
5507
  runMode: ScheduledTaskRunMode.default("new_session_per_run"),
5356
5508
  overlapPolicy: ScheduledTaskOverlapPolicy.default("allow_concurrent"),
5509
+ targetSessionId: z.string().uuid().nullable().optional(),
5357
5510
  agentConfig: ScheduledTaskAgentConfig,
5358
5511
  status: ScheduledTaskStatus.default("active"),
5359
5512
  variableSetId: z.string().uuid().nullable().optional(),
@@ -5361,6 +5514,28 @@ export const CreateScheduledTaskRequest = withVariableSetIdAlias({
5361
5514
  // The rig each run binds to (M3); its active version is resolved per fire.
5362
5515
  rigId: z.string().uuid().nullable().optional(),
5363
5516
  metadata: z.record(z.string(), z.unknown()).default({}),
5517
+ }).superRefine((value, context) => {
5518
+ if (value.runMode === "existing_session" && !value.targetSessionId) {
5519
+ context.addIssue({
5520
+ code: "custom",
5521
+ path: ["targetSessionId"],
5522
+ message: "targetSessionId is required when runMode=existing_session",
5523
+ });
5524
+ }
5525
+ if (value.runMode !== "existing_session" && value.targetSessionId) {
5526
+ context.addIssue({
5527
+ code: "custom",
5528
+ path: ["targetSessionId"],
5529
+ message: "targetSessionId requires runMode=existing_session",
5530
+ });
5531
+ }
5532
+ if (value.runMode === "existing_session" && value.agentConfig.goal) {
5533
+ context.addIssue({
5534
+ code: "custom",
5535
+ path: ["agentConfig", "goal"],
5536
+ message: "agentConfig.goal cannot be used with an existing-session target",
5537
+ });
5538
+ }
5364
5539
  });
5365
5540
  export type CreateScheduledTaskRequest = z.infer<typeof CreateScheduledTaskRequest>;
5366
5541
 
@@ -5369,6 +5544,7 @@ export const UpdateScheduledTaskRequest = withVariableSetIdAlias({
5369
5544
  schedule: ScheduledTaskScheduleSpec.optional(),
5370
5545
  runMode: ScheduledTaskRunMode.optional(),
5371
5546
  overlapPolicy: ScheduledTaskOverlapPolicy.optional(),
5547
+ targetSessionId: z.string().uuid().nullable().optional(),
5372
5548
  agentConfig: ScheduledTaskAgentConfig.optional(),
5373
5549
  status: ScheduledTaskStatus.optional(),
5374
5550
  variableSetId: z.string().uuid().nullable().optional(),
@@ -5377,6 +5553,31 @@ export const UpdateScheduledTaskRequest = withVariableSetIdAlias({
5377
5553
  // resolved per fire, so an update takes effect on the next dispatch.
5378
5554
  rigId: z.string().uuid().nullable().optional(),
5379
5555
  metadata: z.record(z.string(), z.unknown()).optional(),
5556
+ }).superRefine((value, context) => {
5557
+ if (value.targetSessionId && value.runMode && value.runMode !== "existing_session") {
5558
+ context.addIssue({
5559
+ code: "custom",
5560
+ path: ["targetSessionId"],
5561
+ message: "targetSessionId requires runMode=existing_session",
5562
+ });
5563
+ }
5564
+ if (value.runMode === "existing_session" && value.targetSessionId === null) {
5565
+ context.addIssue({
5566
+ code: "custom",
5567
+ path: ["targetSessionId"],
5568
+ message: "targetSessionId cannot be null when runMode=existing_session",
5569
+ });
5570
+ }
5571
+ if (
5572
+ value.agentConfig?.goal &&
5573
+ (value.runMode === "existing_session" || Boolean(value.targetSessionId))
5574
+ ) {
5575
+ context.addIssue({
5576
+ code: "custom",
5577
+ path: ["agentConfig", "goal"],
5578
+ message: "agentConfig.goal cannot be used with an existing-session target",
5579
+ });
5580
+ }
5380
5581
  });
5381
5582
  export type UpdateScheduledTaskRequest = z.infer<typeof UpdateScheduledTaskRequest>;
5382
5583
 
@@ -6863,8 +7064,8 @@ export const RecordingFailedPayload = z.object({
6863
7064
  recordingId: z.string().uuid(),
6864
7065
  turnId: z.string().uuid().nullable(),
6865
7066
  reason: RecordingFailedReason,
6866
- // ffmpeg-stderr tail / error detail agent/ffmpeg-controlled, so the producer
6867
- // caps + scrubs it before emit (it rides redact() like every payload).
7067
+ // Exact ffmpeg stderr/error detail. Event transport limits must reject or
7068
+ // paginate rather than rewriting this canonical diagnostic.
6868
7069
  detail: z.string().nullable().optional(),
6869
7070
  });
6870
7071
  export type RecordingFailedPayload = z.infer<typeof RecordingFailedPayload>;
@@ -6992,6 +7193,18 @@ export const FsListResponse = z.object({
6992
7193
  });
6993
7194
  export type FsListResponse = z.infer<typeof FsListResponse>;
6994
7195
 
7196
+ /** Several independent directory listings served behind one Channel-A lease.
7197
+ * The response order exactly matches `requests`; callers can paint a root and
7198
+ * hydrate a bounded lazy-tree frontier without repeating provider attach work. */
7199
+ export const FsListBatchRequest = z.object({
7200
+ requests: z.array(FsListRequest).min(1).max(16),
7201
+ });
7202
+ export type FsListBatchRequest = z.infer<typeof FsListBatchRequest>;
7203
+ export const FsListBatchResponse = z.object({
7204
+ results: z.array(FsListResponse),
7205
+ });
7206
+ export type FsListBatchResponse = z.infer<typeof FsListBatchResponse>;
7207
+
6995
7208
  export const FsEncoding = z.enum(["utf8", "base64"]);
6996
7209
  export type FsEncoding = z.infer<typeof FsEncoding>;
6997
7210
  export const FsReadRequest = z.object({
@@ -7160,6 +7373,27 @@ export const GitDiffResponse = z.object({
7160
7373
  });
7161
7374
  export type GitDiffResponse = z.infer<typeof GitDiffResponse>;
7162
7375
 
7376
+ /** One repository read unit: status metadata plus an optional comparison.
7377
+ * Multiple units execute behind one Channel-A lease and preserve input order. */
7378
+ export const GitReadBatchItemRequest = z.object({
7379
+ status: GitStatusRequest,
7380
+ diff: GitDiffRequest.optional(),
7381
+ });
7382
+ export type GitReadBatchItemRequest = z.infer<typeof GitReadBatchItemRequest>;
7383
+ export const GitReadBatchRequest = z.object({
7384
+ requests: z.array(GitReadBatchItemRequest).min(1).max(32),
7385
+ });
7386
+ export type GitReadBatchRequest = z.infer<typeof GitReadBatchRequest>;
7387
+ export const GitReadBatchItemResponse = z.object({
7388
+ status: GitStatusResponse,
7389
+ diff: GitDiffResponse.optional(),
7390
+ });
7391
+ export type GitReadBatchItemResponse = z.infer<typeof GitReadBatchItemResponse>;
7392
+ export const GitReadBatchResponse = z.object({
7393
+ results: z.array(GitReadBatchItemResponse),
7394
+ });
7395
+ export type GitReadBatchResponse = z.infer<typeof GitReadBatchResponse>;
7396
+
7163
7397
  // ─── Workbench v2 turn-end workspace capture ────────────
7164
7398
  // A capture is a point-in-time snapshot of the session workspace's CHANGES,
7165
7399
  // probed live off the box at turn end (detectRepos → gitStatus/gitDiff → fsRead
@@ -7191,10 +7425,10 @@ export const WorkspaceCaptureFile = z.object({
7191
7425
  });
7192
7426
  export type WorkspaceCaptureFile = z.infer<typeof WorkspaceCaptureFile>;
7193
7427
 
7194
- // One repo discovered in the workspace. `diff` is `git diff HEAD` (combined
7195
- // staged+unstaged tracked changes vs HEAD the review diff); `status` is the
7196
- // full porcelain file list (drives the rail glyphs incl. untracked, which the
7197
- // HEAD diff omits). root "" = the workspace root repo.
7428
+ // One repo discovered in the workspace. `diff` is the working/index surface vs
7429
+ // HEAD; `branchDiff`, when available, is the complete current branch vs the
7430
+ // remote default branch and therefore retains committed agent work. `status`
7431
+ // is the full porcelain file list. root "" = the workspace root repo.
7198
7432
  export const WorkspaceCaptureRepo = z.object({
7199
7433
  root: z.string(),
7200
7434
  head: z.string().nullable(),
@@ -7204,6 +7438,7 @@ export const WorkspaceCaptureRepo = z.object({
7204
7438
  behind: z.number().int().nonnegative().default(0),
7205
7439
  status: z.array(GitFileStatus),
7206
7440
  diff: z.array(GitFileDiff),
7441
+ branchDiff: z.array(GitFileDiff).optional(),
7207
7442
  });
7208
7443
  export type WorkspaceCaptureRepo = z.infer<typeof WorkspaceCaptureRepo>;
7209
7444
 
@@ -9267,9 +9502,26 @@ export type ListEnrollmentsResponse = z.infer<typeof ListEnrollmentsResponse>;
9267
9502
 
9268
9503
  export const RevokeEnrollmentResponse = z.object({
9269
9504
  revoked: z.boolean(),
9505
+ outcome: z.enum(["removed", "already_removed", "blocked"]),
9506
+ enrollmentId: z.string().uuid(),
9507
+ machineName: z.string().nullable(),
9508
+ lastSeenAt: z.string().datetime({ offset: true }).nullable(),
9509
+ revokedAt: z.string().datetime({ offset: true }).nullable(),
9510
+ code: z
9511
+ .enum(["active_route", "active_commands", "active_lease", "recovery_pending", "not_selfhosted"])
9512
+ .nullable(),
9513
+ message: z.string(),
9514
+ action: z.string(),
9270
9515
  });
9271
9516
  export type RevokeEnrollmentResponse = z.infer<typeof RevokeEnrollmentResponse>;
9272
9517
 
9518
+ /** POST /v1/workspaces/:workspaceId/enrollments/:id/revoke body. */
9519
+ export const RemoveEnrollmentRequest = z.object({
9520
+ expectedUpdatedAt: z.string().datetime({ offset: true }).optional(),
9521
+ idempotencyKey: z.string().trim().min(1).max(200).optional(),
9522
+ });
9523
+ export type RemoveEnrollmentRequest = z.infer<typeof RemoveEnrollmentRequest>;
9524
+
9273
9525
  // =============================================================================
9274
9526
  // Enrollment UX (self-hosted enrollment UX, design 11): the click-Grant approve
9275
9527
  // page lookup/deny + the headless enroll-token mint/exchange. These sit beside the
@@ -9615,9 +9867,10 @@ export const TurnExecutionReasoningSourceV1 =
9615
9867
  );
9616
9868
  export type TurnExecutionReasoningSourceV1 = z.infer<typeof TurnExecutionReasoningSourceV1>;
9617
9869
 
9618
- export const TurnExecutionLatencyModeSourceV1 = /* @__PURE__ */ defineModelContractSchema(() =>
9619
- z.enum(["explicit", "session", "deployment", "continuation"]),
9620
- );
9870
+ export const TurnExecutionLatencyModeSourceV1 =
9871
+ /* @__PURE__ */ defineModelContractSchema(() =>
9872
+ z.enum(["explicit", "session", "deployment", "continuation"]),
9873
+ );
9621
9874
  export type TurnExecutionLatencyModeSourceV1 = z.infer<typeof TurnExecutionLatencyModeSourceV1>;
9622
9875
 
9623
9876
  /**
@@ -10082,7 +10335,6 @@ export function evaluateWorkspaceModelPolicy(
10082
10335
  }
10083
10336
 
10084
10337
  export * from "./codex-fleet-policy";
10085
- export * from "./secret-redaction";
10086
10338
  export * from "./workspace-instruction-policies";
10087
10339
  export * from "./workspace-state";
10088
10340
  export * from "./preference-registry";