@opengeni/contracts 2.15.2 → 3.0.1-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/atlassian.js +10 -9
  2. package/dist/{chunk-NPYLOZAP.js → chunk-5LYFSZ7O.js} +12 -12
  3. package/dist/{chunk-QP3BIGSC.js → chunk-FBXZZSXA.js} +7668 -7654
  4. package/dist/chunk-FBXZZSXA.js.map +1 -0
  5. package/dist/chunk-FY5DZL7T.js +348 -0
  6. package/dist/chunk-FY5DZL7T.js.map +1 -0
  7. package/dist/{chunk-6TCVR53X.js → chunk-O5LEQHDM.js} +2 -2
  8. package/dist/chunk-P3HLDT77.js +59 -0
  9. package/dist/chunk-P3HLDT77.js.map +1 -0
  10. package/dist/chunk-TEMFILR7.js +143 -0
  11. package/dist/chunk-TEMFILR7.js.map +1 -0
  12. package/dist/connect.d.ts +325 -0
  13. package/dist/connect.js +182 -0
  14. package/dist/connect.js.map +1 -0
  15. package/dist/connection-authority.js +28 -334
  16. package/dist/connection-authority.js.map +1 -1
  17. package/dist/editable-artifact-codec-registry.js +2 -2
  18. package/dist/editable-artifact-live.js +2 -2
  19. package/dist/editable-artifact-serialized-commit.js +3 -3
  20. package/dist/editable-artifacts.js +24 -24
  21. package/dist/external-identities.d.ts +620 -0
  22. package/dist/external-identities.js +34 -0
  23. package/dist/external-identities.js.map +1 -0
  24. package/dist/google-drive.js +12 -11
  25. package/dist/google-drive.js.map +1 -1
  26. package/dist/host-mcp-bindings.d.ts +297 -0
  27. package/dist/host-mcp-bindings.js +195 -0
  28. package/dist/host-mcp-bindings.js.map +1 -0
  29. package/dist/index.d.ts +150 -72
  30. package/dist/index.js +89 -81
  31. package/dist/personal-github.js +10 -9
  32. package/dist/personal-github.js.map +1 -1
  33. package/dist/sandbox-snapshots.d.ts +18 -0
  34. package/package.json +13 -1
  35. package/src/connect.ts +202 -0
  36. package/src/external-identities.ts +213 -0
  37. package/src/host-mcp-bindings.ts +226 -0
  38. package/src/index.ts +165 -69
  39. package/src/sandbox-snapshots.ts +67 -3
  40. package/dist/chunk-QP3BIGSC.js.map +0 -1
  41. /package/dist/{chunk-NPYLOZAP.js.map → chunk-5LYFSZ7O.js.map} +0 -0
  42. /package/dist/{chunk-6TCVR53X.js.map → chunk-O5LEQHDM.js.map} +0 -0
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SkillReviewReference } from "./skills";
1
+ import { SkillReviewReference, skillReviewHumanInput } from "./skills";
2
2
  export * from "./skills";
3
3
  export * from "./bundled-skills";
4
4
  import { BundledSkillSelection } from "./bundled-skills";
@@ -8,6 +8,25 @@ import { isSafeSkillRelativePath, validateSkillTextFiles } from "./skill-files";
8
8
  export * from "./model-connection-access";
9
9
  export * from "./sandbox-provider-command";
10
10
  import { z } from "zod";
11
+ export const HostMcpCreateSelections = z
12
+ .array(
13
+ z
14
+ .object({
15
+ serverId: z.string().min(1).max(256),
16
+ delegationId: z
17
+ .string()
18
+ .uuid()
19
+ .transform((value) => value.toLowerCase()),
20
+ generation: z.number().int().positive().safe(),
21
+ })
22
+ .strict(),
23
+ )
24
+ .max(128)
25
+ .superRefine((values, ctx) => {
26
+ if (new Set(values.map((value) => value.serverId)).size !== values.length)
27
+ ctx.addIssue({ code: "custom", message: "Duplicate host server selection" });
28
+ });
29
+ export type HostMcpCreateSelection = z.input<typeof HostMcpCreateSelections>[number];
11
30
  import { Permission } from "./permissions";
12
31
  import { ScopedKnowledgeScope } from "./scoped-knowledge";
13
32
  export { siteSessionPath, SiteSessionPathError } from "./site-session-http";
@@ -161,7 +180,9 @@ export {
161
180
  encodeNativeSnapshotRef,
162
181
  omitInlineWorkspaceArchiveWhenObjectRefPresent,
163
182
  parseWorkspaceArchiveDescriptor,
183
+ parseWorkspaceArchiveObjectKey,
164
184
  parseWorkspaceArchiveObjectRef,
185
+ validateWorkspaceArchiveObjectRef,
165
186
  workspaceArchiveObjectKey,
166
187
  workspaceArchivePayloadPresent,
167
188
  type NativeSnapshotDescriptor,
@@ -170,6 +191,7 @@ export {
170
191
  type TarWorkspaceArchiveDescriptor,
171
192
  type WorkspaceArchiveDescriptor,
172
193
  type WorkspaceArchiveObjectRef,
194
+ type WorkspaceArchiveObjectKey,
173
195
  type WorkspaceTreeFingerprint,
174
196
  } from "./sandbox-snapshots";
175
197
 
@@ -4093,6 +4115,11 @@ export const McpServerConnectionRef = z
4093
4115
  connectionId: z.string().min(1).optional(),
4094
4116
  /** Host-owned credential authority; omission keeps OpenGeni's native connection authority. */
4095
4117
  authoritySource: z.literal("host").optional(),
4118
+ /** Opt-in durable reference. A live execution validator is mandatory. */
4119
+ hostBinding: z
4120
+ .object({ bindingId: z.string().uuid(), generation: z.number().int().positive().safe() })
4121
+ .strict()
4122
+ .optional(),
4096
4123
  /** Stable provider family (for example github, gitlab, or azure_devops). */
4097
4124
  provider: z.string().min(1).max(128).optional(),
4098
4125
  /** Provider host or tenant domain. */
@@ -4114,6 +4141,12 @@ export const McpServerConnectionRef = z
4114
4141
  path: ["connectionId"],
4115
4142
  });
4116
4143
  }
4144
+ if (reference.hostBinding && reference.authoritySource !== "host")
4145
+ context.addIssue({
4146
+ code: "custom",
4147
+ path: ["hostBinding"],
4148
+ message: "Durable binding requires host authority",
4149
+ });
4117
4150
  if (!reference.selectedResources) return;
4118
4151
  if (!reference.connectionId) {
4119
4152
  context.addIssue({
@@ -4407,7 +4440,39 @@ export type McpCredentialResolution =
4407
4440
  authorizationUrl?: string;
4408
4441
  };
4409
4442
 
4443
+ /** Non-turn authority captured by the authenticated API gateway, never by caller JSON. */
4444
+ export type McpGatewayCredentialAuthority = {
4445
+ kind: "external_user" | "organization_service";
4446
+ subjectId: string;
4447
+ permissions: AccessGrant["permissions"];
4448
+ };
4449
+
4450
+ export type McpGatewayCredentialsRequest = Pick<
4451
+ McpCredentialsRequest,
4452
+ | "accountId"
4453
+ | "workspaceId"
4454
+ | "destinationUrl"
4455
+ | "credentialTarget"
4456
+ | "serverId"
4457
+ | "toolName"
4458
+ | "connectionRef"
4459
+ | "forceRefresh"
4460
+ > & {
4461
+ surface: "workspace_gateway";
4462
+ requestId: string;
4463
+ authority: McpGatewayCredentialAuthority;
4464
+ };
4465
+
4466
+ export type McpGatewayCredentialResolution =
4467
+ | (Omit<Extract<McpCredentialResolution, { status: "ok" }>, "sessionId"> & { requestId: string })
4468
+ | (Omit<Extract<McpCredentialResolution, { status: "auth_needed" }>, "sessionId"> & {
4469
+ requestId: string;
4470
+ });
4471
+
4410
4472
  export type ConnectionCredentialsPort = {
4473
+ /** Restrict an optional remote adapter to explicit host refs. Omission keeps
4474
+ * existing in-process host override and legacy-reference behavior. */
4475
+ mcpAuthoritySource?: "host";
4411
4476
  // Every leg is optional: a host may drive only the credential classes it
4412
4477
  // owns. An unset leg falls through to today's standalone implementation for
4413
4478
  // that leg only.
@@ -4426,6 +4491,11 @@ export type ConnectionCredentialsPort = {
4426
4491
  * used by model-visible MCP tools and the exact-attempt Codemode projection.
4427
4492
  */
4428
4493
  mcpCredentials?(input: McpCredentialsRequest): Promise<McpCredentialResolution>;
4494
+ /** Explicit opt-in for authenticated pre-session discovery and invocation.
4495
+ * Does not call the turn-based mcpCredentials fallback or grant durable use. */
4496
+ mcpGatewayCredentials?(
4497
+ input: McpGatewayCredentialsRequest,
4498
+ ): Promise<McpGatewayCredentialResolution>;
4429
4499
  };
4430
4500
 
4431
4501
  // ============ connection-credential provider — GitHub App API port (BYO-App, §7.6 / GitHub credential prototype remainder) ===
@@ -6736,7 +6806,10 @@ export const SessionAuthorizationActor = z.discriminatedUnion("kind", [
6736
6806
  /** Frozen authority that admitted the calling turn. */
6737
6807
  initiator: TurnInitiator,
6738
6808
  initiatorContext: TurnInitiatorContext,
6739
- /** Durable causal human for delegated/service work; null for pure service work. */
6809
+ /**
6810
+ * Durable causal-human selector for named human-bound capabilities. It never
6811
+ * authorizes by itself and is null for pure service work.
6812
+ */
6740
6813
  initiatingHumanSubjectId: z.string().min(1).max(1024).nullable(),
6741
6814
  }),
6742
6815
  ]);
@@ -6789,57 +6862,46 @@ export const SESSION_AUTHORIZATION_LIST_SCOPE_MAX_IDS = 10_000;
6789
6862
 
6790
6863
  /**
6791
6864
  * How far a live agent attempt on a session may reach across the workspace,
6792
- * and how far peer attempts may reach into it. `workspace` is the platform
6793
- * default. `user` limits both directions to sessions carrying the same
6794
- * {@link SessionEndUser} label; `session` limits both to the own root tree.
6795
- * The most restrictive side of a caller/target pair wins. Humans and API keys
6865
+ * under ordinary resource authorization. `workspace` is the platform default.
6866
+ * `user` limits outgoing reach to the same canonical {@link SessionScopeSubjectId};
6867
+ * `session` limits it to the own root tree. Target task scope does not restrict
6868
+ * incoming access; private ownership remains enforced. Humans and API keys
6796
6869
  * are unaffected: this is an agent-to-agent fence enforced only in the core
6797
6870
  * session-authorization seam.
6798
6871
  */
6799
6872
  export const SessionAgentAccess = z.enum(["session", "user", "workspace"]);
6800
6873
  export type SessionAgentAccess = z.infer<typeof SessionAgentAccess>;
6801
6874
 
6802
- export const SESSION_END_USER_SOURCE_MAX_CHARS = 200;
6803
- export const SESSION_END_USER_ID_MAX_CHARS = 1_024;
6804
-
6805
- const NUL_CHARACTER = String.fromCharCode(0);
6806
- const UNPAIRED_SURROGATE_PATTERN =
6807
- /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u;
6808
-
6809
- function opaqueEndUserSegment(maxChars: number) {
6810
- return z
6811
- .string()
6812
- .min(1)
6813
- .max(maxChars)
6814
- .refine((value) => !value.includes(NUL_CHARACTER), "must not contain NUL")
6815
- .refine((value) => !UNPAIRED_SURROGATE_PATTERN.test(value), "must be well-formed UTF-16");
6816
- }
6817
-
6818
6875
  /**
6819
- * Opaque end-user label attached to a session by the embedding product. It
6820
- * shares the external-identity shape (`source` + product-owned `id`) so a
6821
- * later join is by pair. It is NOT a subject and grants NO authority: it only
6822
- * scopes `agentAccess: "user"` reach, `memoryScope: "user"` Memory rows, and
6823
- * the session-list `endUserSource`/`endUserId` filter.
6876
+ * Server-derived canonical user for the session's agent-reach boundary.
6877
+ * This is an output/filter value, never caller-supplied creation authority.
6878
+ * Human visibility remains an independent resource authorization check.
6824
6879
  */
6825
- export const SessionEndUser = z
6826
- .object({
6827
- source: opaqueEndUserSegment(SESSION_END_USER_SOURCE_MAX_CHARS),
6828
- id: opaqueEndUserSegment(SESSION_END_USER_ID_MAX_CHARS),
6829
- })
6830
- .strict();
6831
- export type SessionEndUser = z.infer<typeof SessionEndUser>;
6880
+ export const SessionScopeSubjectId = z
6881
+ .string()
6882
+ .min(1)
6883
+ .max(1024)
6884
+ .regex(/^(?:user:|external_user:).+/);
6885
+ export type SessionScopeSubjectId = z.infer<typeof SessionScopeSubjectId>;
6832
6886
 
6833
6887
  /**
6834
6888
  * The typed Workspace Memory selector an agent reads and writes. `workspace`
6835
- * is today's shared memory; `user` and `session` are ADDITIVE private layers
6889
+ * is shared memory; `user` adds a private layer
6836
6890
  * (the agent still reads workspace facts and saves to its narrowest scope);
6837
6891
  * `off` registers no Memory tools for the session. `user` requires an
6838
- * end-user label.
6892
+ * authenticated canonical user on the active turn. Use task notes for task-local data.
6839
6893
  */
6840
- export const SessionMemoryScope = z.enum(["workspace", "user", "session", "off"]);
6894
+ export const SessionMemoryScope = z.enum(["workspace", "user", "off"]);
6841
6895
  export type SessionMemoryScope = z.infer<typeof SessionMemoryScope>;
6842
6896
 
6897
+ /** Read old persisted selectors without promoting task-local data or authority.
6898
+ * New requests must use SessionMemoryScope directly and reject `session`.
6899
+ * Historical Memory rows remain retained; task notes own new task-local facts. */
6900
+ export function storedSessionMemoryScope(value: unknown): SessionMemoryScope {
6901
+ if (value === "session") return "off";
6902
+ return SessionMemoryScope.parse(value ?? "workspace");
6903
+ }
6904
+
6843
6905
  /**
6844
6906
  * The calling agent attempt's own access scope, resolved by OpenGeni from the
6845
6907
  * caller session row (never from the request) and applied as one SQL
@@ -6849,7 +6911,7 @@ export const SessionAgentAccessViewer = z
6849
6911
  .object({
6850
6912
  callerRootSessionId: z.string().uuid(),
6851
6913
  agentAccess: SessionAgentAccess,
6852
- endUser: SessionEndUser.nullable(),
6914
+ scopeSubjectId: SessionScopeSubjectId.nullable(),
6853
6915
  })
6854
6916
  .strict();
6855
6917
  export type SessionAgentAccessViewer = z.infer<typeof SessionAgentAccessViewer>;
@@ -9403,6 +9465,7 @@ const CreateAgentScheduledTaskRequest = /* @__PURE__ */ withVariableSetIdAlias({
9403
9465
  overlapPolicy: ScheduledTaskOverlapPolicy.default("allow_concurrent"),
9404
9466
  targetSessionId: z.string().uuid().nullable().optional(),
9405
9467
  connectionAuthorities: McpConnectionAuthoritySelections.default([]),
9468
+ selectedHostMcpDelegations: HostMcpCreateSelections.optional(),
9406
9469
  agentConfig: ScheduledTaskAgentConfigInput,
9407
9470
  status: ScheduledTaskStatus.default("active"),
9408
9471
  variableSetId: z.string().uuid().nullable().optional(),
@@ -9482,6 +9545,7 @@ export const UpdateScheduledTaskRequest =
9482
9545
  action: ScheduledTaskAction.optional(),
9483
9546
  targetSessionId: z.string().uuid().nullable().optional(),
9484
9547
  connectionAuthorities: McpConnectionAuthoritySelections.optional(),
9548
+ selectedHostMcpDelegations: HostMcpCreateSelections.optional(),
9485
9549
  agentConfig: ScheduledTaskAgentConfigInput.optional(),
9486
9550
  status: ScheduledTaskStatus.optional(),
9487
9551
  variableSetId: z.string().uuid().nullable().optional(),
@@ -10714,6 +10778,8 @@ export type ConnectionOwnership = z.infer<typeof ConnectionOwnership>;
10714
10778
 
10715
10779
  export const SocialConnection = z.object({
10716
10780
  id: z.string().uuid(),
10781
+ /** Present on version-aware deployments; required for observed reconnect. */
10782
+ version: z.number().int().positive().optional(),
10717
10783
  accountId: z.string().uuid(),
10718
10784
  workspaceId: z.string().uuid(),
10719
10785
  provider: SocialProvider,
@@ -10845,6 +10911,8 @@ export const FikenInstallRequest = z.object({
10845
10911
  export type FikenInstallRequest = z.infer<typeof FikenInstallRequest>;
10846
10912
 
10847
10913
  export const FikenOAuthStartRequest = z.object({
10914
+ /** Same-origin product route to return to after provider consent. */
10915
+ returnPath: z.string().min(1).max(2048).optional(),
10848
10916
  /** Existing Fiken connection to re-authorize in place (reconnect). */
10849
10917
  connectionId: z.string().uuid().optional(),
10850
10918
  });
@@ -11046,6 +11114,7 @@ export const OAuthStartRequest = z
11046
11114
  resource: z.string().url().optional(),
11047
11115
  requestedScopes: z.array(z.string().min(1)).default([]),
11048
11116
  returnPath: z.string().min(1).optional(),
11117
+ returnUrl: z.string().min(1).max(4096).optional(),
11049
11118
  connectionId: z.string().uuid().optional(),
11050
11119
  ownership: ConnectionOwnership.optional(),
11051
11120
  oauthClient: z
@@ -12270,8 +12339,8 @@ export const Session = /* @__PURE__ */ defineSkillContractSchema(() =>
12270
12339
  policyRole: WorkspaceInstructionPolicyRoleKeyInput.nullable().default(null),
12271
12340
  /** Agent-to-agent reach declared at create; see {@link SessionAgentAccess}. */
12272
12341
  agentAccess: SessionAgentAccess.default("workspace"),
12273
- /** Opaque product label; null when the create carried none. */
12274
- endUser: SessionEndUser.nullable().default(null),
12342
+ /** Canonical native/asUser scope identity; null for unscoped service work. */
12343
+ scopeSubjectId: SessionScopeSubjectId.nullable().default(null),
12275
12344
  /** Typed Memory selector frozen at create; see {@link SessionMemoryScope}. */
12276
12345
  memoryScope: SessionMemoryScope.default("workspace"),
12277
12346
  resources: z.array(ResourceRef),
@@ -12487,27 +12556,17 @@ export const SessionListResponse = /* @__PURE__ */ defineSkillContractSchema(()
12487
12556
  export type SessionListResponse = z.infer<typeof SessionListResponse>;
12488
12557
 
12489
12558
  /**
12490
- * `GET /v1/organizations/:organizationId/sessions` query. `endUserSource` and
12491
- * `endUserId` must be supplied together; `status` keeps only sessions in that
12492
- * exact lifecycle state.
12559
+ * Organization session queries may filter a canonical scope subject and lifecycle
12560
+ * state. Legacy end-user label filters are rejected.
12493
12561
  */
12494
- export const ListOrganizationSessionsQuery = z
12495
- .object({
12496
- limit: z.coerce.number().int().min(1).max(200).default(50),
12497
- cursor: z.string().min(1).optional(),
12498
- endUserSource: z.string().trim().min(1).max(200).optional(),
12499
- endUserId: z.string().trim().min(1).max(1024).optional(),
12500
- status: SessionStatus.optional(),
12501
- })
12502
- .superRefine((value, context) => {
12503
- if ((value.endUserSource === undefined) !== (value.endUserId === undefined)) {
12504
- context.addIssue({
12505
- code: z.ZodIssueCode.custom,
12506
- message: "endUserSource and endUserId must be supplied together",
12507
- path: ["endUserId"],
12508
- });
12509
- }
12510
- });
12562
+ export const ListOrganizationSessionsQuery = z.object({
12563
+ limit: z.coerce.number().int().min(1).max(200).default(50),
12564
+ cursor: z.string().min(1).optional(),
12565
+ scopeSubjectId: SessionScopeSubjectId.optional(),
12566
+ endUserSource: z.never().optional(),
12567
+ endUserId: z.never().optional(),
12568
+ status: SessionStatus.optional(),
12569
+ });
12511
12570
  export type ListOrganizationSessionsQuery = z.infer<typeof ListOrganizationSessionsQuery>;
12512
12571
 
12513
12572
  /**
@@ -14838,6 +14897,8 @@ export const CreateSessionRequest = /* @__PURE__ */ defineSkillContractSchema(()
14838
14897
  * identity or authorization from the UUID.
14839
14898
  */
14840
14899
  requestedSessionId: z.string().uuid().optional(),
14900
+ /** Explicit external-owner grants for the direct initial turn only. */
14901
+ selectedHostMcpDelegations: HostMcpCreateSelections.optional(),
14841
14902
  /** Top-level omission is workspace-visible. Agent-child omission inherits
14842
14903
  * the exact parent visibility; cross-visibility child creation is rejected.
14843
14904
  * Top-level private creation is an activated managed-cookie owning-human
@@ -14849,12 +14910,11 @@ export const CreateSessionRequest = /* @__PURE__ */ defineSkillContractSchema(()
14849
14910
  * omission and may only narrow it (workspace > user > session); a wider
14850
14911
  * explicit child value is rejected. Never widens human or API-key access. */
14851
14912
  agentAccess: SessionAgentAccess.default("workspace"),
14852
- /** Opaque product label for the human this session serves. A child
14853
- * inherits its parent's label; naming a different pair is rejected. */
14854
- endUser: SessionEndUser.optional(),
14855
- /** Typed Memory selector. `user` requires an end-user label (own or
14856
- * inherited; 422 otherwise). A child inherits its parent's value on
14857
- * omission and may only narrow it (workspace > user > session > off). */
14913
+ /** Identity comes from verified native/asUser authority, never request fields. */
14914
+ scopeSubjectId: z.never().optional(),
14915
+ endUser: z.never().optional(),
14916
+ /** Typed Memory selector. `user` requires canonical user authority. Children
14917
+ * inherit omission and may only narrow (workspace > user > off). */
14858
14918
  memoryScope: SessionMemoryScope.default("workspace"),
14859
14919
  initialMessage: z.string().min(1).optional(),
14860
14920
  // Creates the durable session shell without fabricating a user message or
@@ -15051,9 +15111,8 @@ export const CreateSessionRequest = /* @__PURE__ */ defineSkillContractSchema(()
15051
15111
  message: "new-session attachment authority epoch is derived by the server",
15052
15112
  });
15053
15113
  }
15054
- // memoryScope "user" requires an end-user label, but an agent-created child
15055
- // inherits its parent's label on omission, so that rule is enforced by the
15056
- // core create resolver (422) after inheritance rather than at parse time.
15114
+ // Canonical user authority and child inheritance are resolved by the core
15115
+ // create resolver, which rejects user Memory without that authority.
15057
15116
  }),
15058
15117
  );
15059
15118
  export type CreateSessionRequest = z.infer<typeof CreateSessionRequest>;
@@ -15149,6 +15208,41 @@ export const HumanInputQuestion = z
15149
15208
  });
15150
15209
  export type HumanInputQuestion = z.infer<typeof HumanInputQuestion>;
15151
15210
 
15211
+ /** Only known presentation wire differences are equivalent. This is not
15212
+ * authorization: response admission still binds the immutable source receipt.
15213
+ * Compare original keys as well as parsed values so unknown fields cannot be
15214
+ * hidden by Zod's ordinary object projection during a pending-card re-freeze. */
15215
+ export function canonicalSkillReviewQuestion(
15216
+ question: HumanInputQuestion,
15217
+ ): HumanInputQuestion | null {
15218
+ if (
15219
+ !HumanInputQuestion.safeParse(question).success ||
15220
+ !SkillReviewReference.strict().safeParse(question.skillReview).success
15221
+ )
15222
+ return null;
15223
+ const canonical = skillReviewHumanInput(question.skillReview!).questions[0]!;
15224
+ const normalized = {
15225
+ ...question,
15226
+ allowOther: false,
15227
+ options: question.options.map((option) => {
15228
+ if (option.description != null) return option;
15229
+ const { description: _description, ...rest } = option;
15230
+ return rest;
15231
+ }),
15232
+ };
15233
+ if (
15234
+ normalized.validation == null ||
15235
+ (Object.keys(normalized.validation).every((key) =>
15236
+ ["minSelections", "maxSelections"].includes(key),
15237
+ ) &&
15238
+ normalized.validation.minSelections == null &&
15239
+ normalized.validation.maxSelections == null)
15240
+ ) {
15241
+ delete normalized.validation;
15242
+ }
15243
+ return stableJson(normalized) === stableJson(canonical) ? canonical : null;
15244
+ }
15245
+
15152
15246
  export const HumanInputRequestStatus = z.enum([
15153
15247
  "pending",
15154
15248
  "answered",
@@ -15266,6 +15360,7 @@ export const SessionUserMessagePayload = z
15266
15360
  mcpCredentialUpdates: z.array(SessionMcpCredentialUpdateInput).optional(),
15267
15361
  /** Explicit personal-connection grants for this exact logical turn. */
15268
15362
  connectionAuthorities: McpConnectionAuthoritySelections.default([]),
15363
+ selectedHostMcpDelegations: HostMcpCreateSelections.optional(),
15269
15364
  personalResourceAttachment: PersonalResourceAttachmentIntent.optional(),
15270
15365
  })
15271
15366
  .strict()
@@ -15315,6 +15410,7 @@ export const SteerSessionMessageRequest = z
15315
15410
  mcpCredentialUpdates: z.array(SessionMcpCredentialUpdateInput).optional(),
15316
15411
  /** Explicit personal-connection grants for this exact steered turn. */
15317
15412
  connectionAuthorities: McpConnectionAuthoritySelections.default([]),
15413
+ selectedHostMcpDelegations: HostMcpCreateSelections.optional(),
15318
15414
  personalResourceAttachment: PersonalResourceAttachmentIntent.optional(),
15319
15415
  })
15320
15416
  .strict()
@@ -64,8 +64,35 @@ export type WorkspaceArchiveObjectRef = {
64
64
  backend: string;
65
65
  };
66
66
 
67
+ // Keep legacy scope/key spelling readable. New physical upload identities must
68
+ // be a complete RFC UUID (including version and variant), never arbitrary text.
69
+ const UPLOAD_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
67
70
  const OBJECT_KEY =
68
- /^sandbox-archives\/v1\/[0-9a-f-]{36}\/[0-9a-f-]{36}\/[0-9a-f-]{36}\/wa1:[0-9]{13}:[0-9a-f]{64}\.tar$/i;
71
+ /^sandbox-archives\/v1\/([0-9a-f-]{36})\/([0-9a-f-]{36})\/([0-9a-f-]{36})\/(wa1:[0-9]{13}:[0-9a-f]{64})(?:\.([0-9a-f-]+))?\.tar$/i;
72
+
73
+ export type WorkspaceArchiveObjectKey = {
74
+ accountId: string;
75
+ workspaceId: string;
76
+ sandboxGroupId: string;
77
+ revision: string;
78
+ uploadId: string | null;
79
+ };
80
+
81
+ export function parseWorkspaceArchiveObjectKey(value: unknown): WorkspaceArchiveObjectKey | null {
82
+ if (typeof value !== "string") return null;
83
+ const match = OBJECT_KEY.exec(value);
84
+ // JS $ may match before a final newline; a locator must match in full.
85
+ if (!match || match[0] !== value || (match[5] !== undefined && !UPLOAD_ID.test(match[5]))) {
86
+ return null;
87
+ }
88
+ return {
89
+ accountId: match[1]!,
90
+ workspaceId: match[2]!,
91
+ sandboxGroupId: match[3]!,
92
+ revision: match[4]!,
93
+ uploadId: match[5] ?? null,
94
+ };
95
+ }
69
96
 
70
97
  export function parseWorkspaceArchiveObjectRef(value: unknown): WorkspaceArchiveObjectRef | null {
71
98
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
@@ -73,7 +100,7 @@ export function parseWorkspaceArchiveObjectRef(value: unknown): WorkspaceArchive
73
100
  if (
74
101
  candidate.schema !== WORKSPACE_ARCHIVE_OBJECT_REF_SCHEMA ||
75
102
  typeof candidate.key !== "string" ||
76
- !OBJECT_KEY.test(candidate.key) ||
103
+ !parseWorkspaceArchiveObjectKey(candidate.key) ||
77
104
  typeof candidate.sha256 !== "string" ||
78
105
  !SHA256.test(candidate.sha256) ||
79
106
  !nonnegativeInteger(candidate.bytes) ||
@@ -98,8 +125,45 @@ export function workspaceArchiveObjectKey(input: {
98
125
  workspaceId: string;
99
126
  sandboxGroupId: string;
100
127
  revision: string;
128
+ /** New upload invocations supply a fresh randomUUID; omission is legacy-only. */
129
+ uploadId?: string;
101
130
  }): string {
102
- return `sandbox-archives/v1/${input.accountId}/${input.workspaceId}/${input.sandboxGroupId}/${input.revision}.tar`;
131
+ if (
132
+ input.uploadId !== undefined &&
133
+ (!UPLOAD_ID.test(input.uploadId) || input.uploadId.length !== 36)
134
+ ) {
135
+ throw new Error("Invalid workspace archive upload UUID");
136
+ }
137
+ return `sandbox-archives/v1/${input.accountId}/${input.workspaceId}/${input.sandboxGroupId}/${input.revision}${input.uploadId === undefined ? "" : `.${input.uploadId}`}.tar`;
138
+ }
139
+
140
+ /** Bind a stored locator to its publication/restore scope and verified tar
141
+ * descriptor. A logical revision never substitutes for the exact physical key. */
142
+ export function validateWorkspaceArchiveObjectRef(
143
+ value: unknown,
144
+ input: {
145
+ accountId: string;
146
+ workspaceId: string;
147
+ sandboxGroupId: string;
148
+ descriptor: WorkspaceArchiveDescriptor;
149
+ },
150
+ ): WorkspaceArchiveObjectRef | null {
151
+ const ref = parseWorkspaceArchiveObjectRef(value);
152
+ const key = ref && parseWorkspaceArchiveObjectKey(ref.key);
153
+ const descriptor = parseWorkspaceArchiveDescriptor(input.descriptor);
154
+ if (
155
+ !ref ||
156
+ !key ||
157
+ descriptor?.version !== 1 ||
158
+ key.accountId.toLowerCase() !== input.accountId.toLowerCase() ||
159
+ key.workspaceId.toLowerCase() !== input.workspaceId.toLowerCase() ||
160
+ key.sandboxGroupId.toLowerCase() !== input.sandboxGroupId.toLowerCase() ||
161
+ key.revision !== descriptor.revision ||
162
+ ref.sha256 !== descriptor.archiveSha256 ||
163
+ ref.bytes !== descriptor.archiveBytes
164
+ )
165
+ return null;
166
+ return ref;
103
167
  }
104
168
 
105
169
  export function workspaceArchivePayloadPresent(