@opengeni/contracts 1.0.1 → 2.3.0-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 (112) hide show
  1. package/dist/agent-authored-durable-text.d.ts +64 -0
  2. package/dist/atlassian.d.ts +1 -0
  3. package/dist/atlassian.js +14 -13
  4. package/dist/{chunk-Y3MLSLB4.js → chunk-3SIZGL3O.js} +42 -30
  5. package/dist/chunk-3SIZGL3O.js.map +1 -0
  6. package/dist/{chunk-JBCIY7QD.js → chunk-7RMTKFJW.js} +45 -22
  7. package/dist/chunk-7RMTKFJW.js.map +1 -0
  8. package/dist/{chunk-5UDNP7AO.js → chunk-MRWYRAS5.js} +56 -8
  9. package/dist/chunk-MRWYRAS5.js.map +1 -0
  10. package/dist/{chunk-LW7OH6CS.js → chunk-N6GRVXAJ.js} +6 -5
  11. package/dist/chunk-N6GRVXAJ.js.map +1 -0
  12. package/dist/{chunk-TZ6YEYN4.js → chunk-NVJMJWLL.js} +3 -1
  13. package/dist/chunk-NVJMJWLL.js.map +1 -0
  14. package/dist/chunk-PX3VCKAU.js +29 -0
  15. package/dist/chunk-PX3VCKAU.js.map +1 -0
  16. package/dist/{chunk-LSB7GRLG.js → chunk-TXTQIYCV.js} +15 -11
  17. package/dist/chunk-TXTQIYCV.js.map +1 -0
  18. package/dist/{chunk-NX5JNF4L.js → chunk-XW2RKNEU.js} +9410 -5799
  19. package/dist/chunk-XW2RKNEU.js.map +1 -0
  20. package/dist/{chunk-CADSX6UN.js → chunk-Y3KNXVTD.js} +2 -2
  21. package/dist/chunk-YBW5JSA3.js +251 -0
  22. package/dist/chunk-YBW5JSA3.js.map +1 -0
  23. package/dist/company-brain-governed-writes.d.ts +475 -0
  24. package/dist/company-brain.d.ts +398 -0
  25. package/dist/company-profile.d.ts +268 -0
  26. package/dist/connection-authority.d.ts +501 -0
  27. package/dist/connection-authority.js +364 -0
  28. package/dist/connection-authority.js.map +1 -0
  29. package/dist/connector-attachments.d.ts +95 -0
  30. package/dist/connector-attachments.js +51 -0
  31. package/dist/connector-attachments.js.map +1 -0
  32. package/dist/editable-artifact-codec-registry.d.ts +16 -7
  33. package/dist/editable-artifact-codec-registry.js +18 -8
  34. package/dist/editable-artifact-committed-transaction.d.ts +2 -2
  35. package/dist/editable-artifact-committed-transaction.js +2 -2
  36. package/dist/editable-artifact-live.d.ts +11 -12
  37. package/dist/editable-artifact-live.js +111 -46
  38. package/dist/editable-artifact-live.js.map +1 -1
  39. package/dist/editable-artifact-serialized-commit.d.ts +2 -1
  40. package/dist/editable-artifact-serialized-commit.js +9 -8
  41. package/dist/editable-artifact-versions.d.ts +20 -5
  42. package/dist/editable-artifact-versions.js +23 -5
  43. package/dist/editable-artifacts.d.ts +2 -0
  44. package/dist/editable-artifacts.js +51 -39
  45. package/dist/google-drive.d.ts +1 -0
  46. package/dist/google-drive.js +14 -13
  47. package/dist/google-drive.js.map +1 -1
  48. package/dist/governed-learning-activation.d.ts +154 -0
  49. package/dist/governed-learning-evaluator.d.ts +152 -0
  50. package/dist/index.d.ts +12426 -1313
  51. package/dist/index.js +855 -115
  52. package/dist/interaction.d.ts +68 -0
  53. package/dist/knowledge.d.ts +119 -0
  54. package/dist/organization-membership-lifecycle.d.ts +516 -0
  55. package/dist/permissions.d.ts +52 -0
  56. package/dist/personal-github.d.ts +319 -0
  57. package/dist/personal-github.js +209 -0
  58. package/dist/personal-github.js.map +1 -0
  59. package/dist/preference-registry.d.ts +17 -0
  60. package/dist/remember.d.ts +331 -0
  61. package/dist/sandbox-file-artifacts.d.ts +56 -0
  62. package/dist/sandbox-snapshots.d.ts +21 -0
  63. package/dist/slack-bot-scopes.d.ts +34 -14
  64. package/dist/slack-bot-scopes.js +5 -1
  65. package/dist/spreadsheet-artifact-commands.d.ts +2 -3
  66. package/dist/spreadsheet-artifact-commands.js +4 -2
  67. package/dist/spreadsheet-artifact-query.js +3 -2
  68. package/dist/task-notes.d.ts +42 -1
  69. package/dist/tool-result-spill.d.ts +16 -0
  70. package/dist/workspace-instruction-policies.d.ts +81 -0
  71. package/dist/workspace-learning-administration.d.ts +289 -0
  72. package/package.json +13 -1
  73. package/src/agent-authored-durable-text.ts +105 -0
  74. package/src/company-brain-governed-writes.ts +356 -0
  75. package/src/company-brain.ts +201 -0
  76. package/src/company-profile.ts +122 -1
  77. package/src/connection-authority.ts +413 -0
  78. package/src/connector-attachments.ts +327 -0
  79. package/src/editable-artifact-codec-registry.ts +55 -17
  80. package/src/editable-artifact-committed-transaction.ts +44 -31
  81. package/src/editable-artifact-live.ts +123 -57
  82. package/src/editable-artifact-serialized-commit.ts +2 -1
  83. package/src/editable-artifact-versions.ts +26 -5
  84. package/src/editable-artifacts.ts +2 -0
  85. package/src/governed-learning-activation.ts +191 -0
  86. package/src/governed-learning-evaluator.ts +176 -0
  87. package/src/index.ts +3148 -369
  88. package/src/interaction.ts +55 -1
  89. package/src/knowledge.ts +118 -6
  90. package/src/organization-membership-lifecycle.ts +330 -0
  91. package/src/permissions.ts +55 -0
  92. package/src/personal-github.ts +267 -0
  93. package/src/preference-registry.ts +17 -0
  94. package/src/remember.ts +242 -0
  95. package/src/sandbox-file-artifacts.ts +65 -0
  96. package/src/sandbox-snapshots.ts +78 -0
  97. package/src/slack-bot-scopes.ts +81 -9
  98. package/src/spreadsheet-artifact-commands.ts +12 -13
  99. package/src/task-notes.ts +14 -1
  100. package/src/tool-result-spill.ts +35 -0
  101. package/src/workspace-instruction-policies.ts +30 -0
  102. package/src/workspace-learning-administration.ts +88 -0
  103. package/dist/chunk-5UDNP7AO.js.map +0 -1
  104. package/dist/chunk-BVGIYW7D.js +0 -11
  105. package/dist/chunk-BVGIYW7D.js.map +0 -1
  106. package/dist/chunk-JBCIY7QD.js.map +0 -1
  107. package/dist/chunk-LSB7GRLG.js.map +0 -1
  108. package/dist/chunk-LW7OH6CS.js.map +0 -1
  109. package/dist/chunk-NX5JNF4L.js.map +0 -1
  110. package/dist/chunk-TZ6YEYN4.js.map +0 -1
  111. package/dist/chunk-Y3MLSLB4.js.map +0 -1
  112. /package/dist/{chunk-CADSX6UN.js.map → chunk-Y3KNXVTD.js.map} +0 -0
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { Permission } from "./permissions";
2
3
  import { ScopedKnowledgeScope } from "./scoped-knowledge";
3
4
  import {
4
5
  boundSessionEventPayload,
@@ -11,11 +12,13 @@ import { WorkspaceInstructionPolicyRoleKeyInput } from "./workspace-instruction-
11
12
  import { ClientResumableVoiceInputConfig } from "./transcription-recordings";
12
13
  import { MediaGenerationResult } from "./video-generation";
13
14
  import { KnowledgeProviderCitation } from "./knowledge";
15
+ import { XaiProviderAccountAuthoritySnapshotV1 } from "./xai-provider-account-authority";
14
16
 
15
17
  export * from "./slack-bot-scopes";
16
18
  export * from "./slack-task-policy";
17
19
  export * from "./atlassian";
18
20
  export * from "./connector-destinations";
21
+ export * from "./connector-attachments";
19
22
  export * from "./memory-slack-delivery";
20
23
  export * from "./image-generation";
21
24
  export * from "./video-generation";
@@ -23,7 +26,10 @@ export * from "./editable-artifacts";
23
26
  export * from "./editable-artifact-committed-transaction";
24
27
  export * from "./editable-artifact-serialized-commit";
25
28
  export * from "./tool-catalog";
29
+ export * from "./tool-result-spill";
26
30
  export * from "./interaction";
31
+ export * from "./sandbox-file-artifacts";
32
+ export * from "./permissions";
27
33
 
28
34
  export {
29
35
  CreateWorkspaceArtifactRequest,
@@ -119,15 +125,21 @@ export {
119
125
  export {
120
126
  NATIVE_SNAPSHOT_PREFIXES,
121
127
  WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION,
128
+ WORKSPACE_ARCHIVE_OBJECT_REF_SCHEMA,
122
129
  backendForNativeSnapshotProvider,
123
130
  decodeNativeSnapshotRef,
124
131
  encodeNativeSnapshotRef,
132
+ omitInlineWorkspaceArchiveWhenObjectRefPresent,
125
133
  parseWorkspaceArchiveDescriptor,
134
+ parseWorkspaceArchiveObjectRef,
135
+ workspaceArchiveObjectKey,
136
+ workspaceArchivePayloadPresent,
126
137
  type NativeSnapshotDescriptor,
127
138
  type NativeSnapshotProvider,
128
139
  type NativeSnapshotRef,
129
140
  type TarWorkspaceArchiveDescriptor,
130
141
  type WorkspaceArchiveDescriptor,
142
+ type WorkspaceArchiveObjectRef,
131
143
  type WorkspaceTreeFingerprint,
132
144
  } from "./sandbox-snapshots";
133
145
 
@@ -148,11 +160,12 @@ export const SessionStatus = z.enum([
148
160
  ]);
149
161
  export type SessionStatus = z.infer<typeof SessionStatus>;
150
162
 
151
- // 11 backends; 3-way enum parity (contracts / sdk / deployment) is pinned by
163
+ // 12 backends; 3-way enum parity (contracts / sdk / deployment) is pinned by
152
164
  // `packages/sdk/test/contract-parity.test.ts`. Every member is ADDITIVE AT THE
153
165
  // END (the parity test pins positions): the original four, then the six cloud
154
166
  // backends, then `selfhosted` (bring-your-own-compute — a user's own machine
155
- // enrolled as a first-class sandbox).
167
+ // enrolled as a first-class sandbox), then `opensandbox` (an optional
168
+ // Kubernetes-native provisioned sandbox provider).
156
169
  export const SandboxBackend = z.enum([
157
170
  "docker",
158
171
  "modal",
@@ -165,6 +178,7 @@ export const SandboxBackend = z.enum([
165
178
  "cloudflare",
166
179
  "vercel",
167
180
  "selfhosted",
181
+ "opensandbox",
168
182
  ]);
169
183
  export type SandboxBackend = z.infer<typeof SandboxBackend>;
170
184
 
@@ -190,6 +204,7 @@ export const SANDBOX_PROVIDER_INSTANCE_ID_FIELDS_BY_BACKEND = {
190
204
  cloudflare: ["sandboxId"],
191
205
  vercel: ["sandboxId"],
192
206
  selfhosted: ["agentId"],
207
+ opensandbox: ["sandboxId"],
193
208
  } as const satisfies Record<SandboxBackend, readonly string[]>;
194
209
  export const LEGACY_SANDBOX_PROVIDER_INSTANCE_ID_FIELDS = [
195
210
  "sandboxId",
@@ -594,6 +609,36 @@ export const CAPABILITY_DESCRIPTORS: Record<SandboxBackend, CapabilityDescriptor
594
609
  persistable: false,
595
610
  supportsRunAs: false,
596
611
  },
612
+ // Optional Kubernetes-native provisioned sandbox through OpenSandbox.
613
+ // Desktop-class when the box image includes ttyd/browserd/Xvfb: PTY over ttyd,
614
+ // noVNC and browserd over signed URI-mode ingress. OpenGeni owns persistence
615
+ // through its portable tar checkpoint path in object storage; native
616
+ // OpenSandbox pause/resume and snapshots are deliberately not used.
617
+ opensandbox: {
618
+ backend: "opensandbox",
619
+ backendId: "opensandbox",
620
+ tier: "desktop",
621
+ os: { supported: ["linux"], default: "linux" },
622
+ capabilities: {
623
+ FileSystem: { available: true, readOnly: false },
624
+ Terminal: { available: true, transport: "sse-events", pty: true },
625
+ Git: { available: true },
626
+ DesktopStream: { available: true, transport: "vnc-ws" },
627
+ Recording: { available: true },
628
+ },
629
+ lifetime: {
630
+ requiresSnapshotRollover: false,
631
+ hasIdleKiller: false,
632
+ supportsSuspendResume: false,
633
+ resumeIsLockFree: true,
634
+ },
635
+ snapshot: { kind: "tar-only", hasTarFallback: true },
636
+ portExposure: { kind: "provider-tunnel", supportsOnDemandPorts: true },
637
+ workspaceRoot: "/workspace",
638
+ nativeBucketMount: false,
639
+ persistable: true,
640
+ supportsRunAs: false,
641
+ },
597
642
  };
598
643
 
599
644
  export const ReasoningEffort = z.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max"]);
@@ -673,91 +718,6 @@ export const SessionSpawnDenial = z.object({
673
718
  });
674
719
  export type SessionSpawnDenial = z.infer<typeof SessionSpawnDenial>;
675
720
 
676
- export const Permission = z.enum([
677
- "account:read",
678
- "account:admin",
679
- "members:manage",
680
- "workspace:create",
681
- "billing:read",
682
- "billing:manage",
683
- "workspace:read",
684
- "workspace:admin",
685
- "sessions:create",
686
- "sessions:read",
687
- "sessions:control",
688
- // sandbox workspace (sandbox contract §C.3 / crosscut PART 1.2). stream:view is a
689
- // REAL, distinct permission — strictly BROADER than sessions:read — because the
690
- // pixel plane (Channel B) exposes raw pixels: a viewer can see content the
691
- // structured Channel-A event log never captured. sessions:read is NOT
692
- // permission to watch raw pixels.
693
- "stream:view",
694
- // SEPARATE from stream:view: raw input to the desktop (bypasses approvalQueue /
695
- // interrupt). NEVER granted by default in v1 (the input plane is OFF —
696
- // streamControlEnabled=false); the permission exists so later hardening is a
697
- // flag flip, not a redesign.
698
- "stream:control",
699
- // Accept the pixel-plane secret-leak acknowledgment (consent gate before the
700
- // un-redacted desktop URL is handed out).
701
- "stream:acknowledge",
702
- "files:upload",
703
- "files:read",
704
- // Channel-A structured write surface (FS writes / apply-patch); distinct from
705
- // files:read so a read-only viewer can't mutate the box filesystem.
706
- "files:write",
707
- // Attach to an interactive PTY (terminal-as-pty, Channel A); distinct from
708
- // sessions:read which only reads the command-output firehose.
709
- "terminal:attach",
710
- "documents:manage",
711
- "documents:search",
712
- "scheduled_tasks:manage",
713
- "scheduled_tasks:run",
714
- "github:manage",
715
- "github:use",
716
- "api_keys:manage",
717
- "connections:read",
718
- "connections:write",
719
- /** @deprecated alias of variable-sets:manage */
720
- "environments:manage",
721
- /** @deprecated alias of variable-sets:use */
722
- "environments:use",
723
- "variable-sets:list",
724
- "variable-sets:read",
725
- "variable-sets:write",
726
- "variable-sets:manage",
727
- "variable-sets:use",
728
- "secrets:list",
729
- "secrets:read",
730
- "secrets:write",
731
- // Attach or rotate per-session third-party MCP server credentials. Deliberately
732
- // not part of the worker's default first-party MCP permission set: a sandboxed
733
- // agent must not be able to hand itself new bearer credentials.
734
- "mcp_servers:attach",
735
- // Programmatic sandbox -> tool access through the first-party MCP gate. This is
736
- // intentionally narrow and is never part of first-party MCP defaults; callers
737
- // must receive it through an explicit delegated `ogd_` mint carrying sessionId.
738
- "codemode:call",
739
- "goals:manage",
740
- // Bring-your-own-compute (M5). enrollments:read lists a workspace's machines;
741
- // enrollments:manage approves a device-flow enrollment (the LOUD whole-machine
742
- // consent) + revokes a machine. Distinct from sessions/stream perms because an
743
- // enrollment grants WHOLE-MACHINE access to a user's own hardware — a high-trust,
744
- // admin-shaped action. workspace:admin is the super-wildcard over both.
745
- "enrollments:read",
746
- "enrollments:manage",
747
- // Rigs (workspace-scoped, versioned sandbox machine definitions). rigs:use is
748
- // read + propose-change (the agent-native, additive path a sandboxed session
749
- // is trusted with); rigs:manage is create/edit/activate/promote/delete (the
750
- // admin-shaped path that mints or rolls versions). workspace:admin is the
751
- // super-wildcard over both.
752
- "rigs:use",
753
- "rigs:manage",
754
- // Workspace-published HTML artifacts. Read permits listing/source retrieval;
755
- // publish permits create, version publication, and rollback.
756
- "artifacts:read",
757
- "artifacts:publish",
758
- ]);
759
- export type Permission = z.infer<typeof Permission>;
760
-
761
721
  /**
762
722
  * Capability-first permissions signed into a session's first-party OpenGeni
763
723
  * MCP token when a top-level creator does not explicitly narrow them.
@@ -781,8 +741,12 @@ export const DEFAULT_FIRST_PARTY_MCP_PERMISSIONS = [
781
741
  // tools resolve an already-installed workspace principal. Credentials stay
782
742
  // inside the broker and remain subject to each tool's own authorization.
783
743
  "connections:read",
744
+ "variable-sets:list",
745
+ "variable-sets:write",
746
+ "variable-sets:attach",
784
747
  "variable-sets:use",
785
- "variable-sets:manage",
748
+ "secrets:list",
749
+ "secrets:write",
786
750
  "rigs:use",
787
751
  "github:use",
788
752
  "artifacts:read",
@@ -802,6 +766,7 @@ export const FIRST_PARTY_MCP_TOOL_NAMES = [
802
766
  "goal_set",
803
767
  "goal_update",
804
768
  "goal_progress",
769
+ "goal_wait",
805
770
  "goal_complete",
806
771
  "goal_pause",
807
772
  "memory_search",
@@ -812,6 +777,18 @@ export const FIRST_PARTY_MCP_TOOL_NAMES = [
812
777
  "task_notes_list",
813
778
  "task_note_save",
814
779
  "task_note_archive",
780
+ "task_note_replace",
781
+ "knowledge_propose",
782
+ "knowledge_correct",
783
+ "task_note_promote_knowledge",
784
+ "task_note_promote_instruction_policy",
785
+ "task_note_promote_preference",
786
+ "instruction_policy_propose",
787
+ "preference_propose",
788
+ "remember",
789
+ "remember_confirm",
790
+ "company_profile_propose",
791
+ "company_profile_confirm",
815
792
  "sandboxes_list",
816
793
  "sandbox_attach",
817
794
  "sandbox_swap",
@@ -826,11 +803,13 @@ export const FIRST_PARTY_MCP_TOOL_NAMES = [
826
803
  "sessions_list",
827
804
  "session_get",
828
805
  "session_events",
806
+ "session_wait",
829
807
  "session_create",
830
808
  "session_send_message",
831
809
  "session_pause",
832
810
  "session_resume",
833
811
  "session_steer",
812
+ "session_human_input_respond",
834
813
  "set_other_session_title",
835
814
  "interaction_discover",
836
815
  "browser_open",
@@ -889,6 +868,7 @@ export const FIRST_PARTY_MCP_TOOL_NAMES = [
889
868
  "scheduled_tasks_delete",
890
869
  "scheduled_task_runs_list",
891
870
  "slack_bot_list_channels",
871
+ "slack_bot_search",
892
872
  "slack_bot_channel_history",
893
873
  "slack_bot_thread_replies",
894
874
  "slack_bot_list_users",
@@ -915,6 +895,7 @@ export const FIRST_PARTY_MCP_TOOL_NAMES = [
915
895
  "artifacts_create",
916
896
  "artifacts_publish",
917
897
  "artifacts_rollback",
898
+ "sandbox_file_publish",
918
899
  "editable_artifact_list",
919
900
  "editable_artifact_create",
920
901
  "editable_artifact_import",
@@ -965,8 +946,15 @@ const FIRST_PARTY_IN_PROCESS_TOOL_NAME_SET = new Set<FirstPartyMcpToolName>(
965
946
  * trustworthy logical-delivery identity. Server-owned Slack delivery paths
966
947
  * call the internal client directly with their own durable operation IDs.
967
948
  */
949
+ // Names kept so previously written data still parses - immutable scheduled-task
950
+ // execution snapshots recorded the tool set that was default at the time, and
951
+ // they are strictly re-parsed on replay. Retiring a tool must not strand an
952
+ // accepted occurrence. These are never registered, never default, and never
953
+ // authorized; they exist so history stays readable.
968
954
  const FIRST_PARTY_COMPATIBILITY_ONLY_TOOL_NAMES = [
969
955
  "slack_bot_post_message",
956
+ "memory_save",
957
+ "memory_correct",
970
958
  ] as const satisfies readonly FirstPartyMcpToolName[];
971
959
 
972
960
  const FIRST_PARTY_COMPATIBILITY_ONLY_TOOL_NAME_SET = new Set<FirstPartyMcpToolName>(
@@ -999,6 +987,10 @@ export const EDITABLE_ARTIFACT_MCP_CODEMODE_PATHS = {
999
987
  */
1000
988
  export const DEFAULT_FIRST_PARTY_MCP_TOOLS = FIRST_PARTY_MCP_TOOL_NAMES.filter(
1001
989
  (name) =>
990
+ // Memory V1 writes are retired entirely; `remember` and task-note
991
+ // promotion own durable agent writes.
992
+ name !== "memory_save" &&
993
+ name !== "memory_correct" &&
1002
994
  !name.startsWith("social_") &&
1003
995
  !name.startsWith("x_") &&
1004
996
  !name.startsWith("reddit_") &&
@@ -1054,6 +1046,178 @@ export const PersonalResourceRetentionMode = z.enum(["retain", "delete_after"]);
1054
1046
  export type PersonalResourceRetentionMode = z.infer<typeof PersonalResourceRetentionMode>;
1055
1047
 
1056
1048
  export const SessionTenancyVisibility = z.enum(["user_private", "workspace_shared"]);
1049
+
1050
+ export const UserResourceAuthorityScope = z.literal("user");
1051
+ export const UserResourceKind = z.enum([
1052
+ "connection",
1053
+ "document",
1054
+ "variable_set",
1055
+ "rig",
1056
+ "connected_machine",
1057
+ ]);
1058
+ export type UserResourceKind = z.infer<typeof UserResourceKind>;
1059
+
1060
+ export const USER_RESOURCE_ACTION_BY_KIND = {
1061
+ connection: "connection.use",
1062
+ document: "document.read",
1063
+ variable_set: "variable_set.use",
1064
+ rig: "rig.use",
1065
+ connected_machine: "connected_machine.use",
1066
+ } as const satisfies Record<UserResourceKind, string>;
1067
+
1068
+ export const UserResourceLifecycleGrantMode = z.enum(["once", "session", "always"]);
1069
+ export const PERSONAL_RESOURCE_SHARED_OUTPUT_WARNING_VERSION = 1 as const;
1070
+ export const PERSONAL_RESOURCE_SHARED_OUTPUT_WARNING =
1071
+ "Personal resources used in a workspace-shared session may influence outputs visible to other workspace members. The underlying credentials and secret values are not shared by the attachment itself.";
1072
+
1073
+ /**
1074
+ * Owner-authored issuance intent for the fixed personal Variable Set/Rig/
1075
+ * Connected Machine closure selected by one session. The server derives every resource,
1076
+ * authority and action from the locked session; callers never nominate grants.
1077
+ */
1078
+ export const PersonalResourceAttachmentIntent = z
1079
+ .object({
1080
+ mode: UserResourceLifecycleGrantMode,
1081
+ expectedAuthorityEpoch: z.number().int().positive().optional(),
1082
+ workspaceSharedAcknowledged: z.boolean().default(false),
1083
+ sharedOutputWarningVersion: z.literal(PERSONAL_RESOURCE_SHARED_OUTPUT_WARNING_VERSION),
1084
+ })
1085
+ .strict();
1086
+ export type PersonalResourceAttachmentIntent = z.infer<typeof PersonalResourceAttachmentIntent>;
1087
+
1088
+ function requireEstablishedPersonalResourceEpoch(
1089
+ value: {
1090
+ personalResourceAttachment?: PersonalResourceAttachmentIntent | undefined;
1091
+ },
1092
+ context: z.RefinementCtx,
1093
+ ): void {
1094
+ if (
1095
+ value.personalResourceAttachment !== undefined &&
1096
+ value.personalResourceAttachment.expectedAuthorityEpoch === undefined
1097
+ ) {
1098
+ context.addIssue({
1099
+ code: z.ZodIssueCode.custom,
1100
+ path: ["personalResourceAttachment", "expectedAuthorityEpoch"],
1101
+ message: "established-session attachment requires expectedAuthorityEpoch",
1102
+ });
1103
+ }
1104
+ }
1105
+
1106
+ /** Credential-free accepted-work projection. Resource ids remain private. */
1107
+ export const PersonalResourceAttachmentSummary = z
1108
+ .object({
1109
+ mode: UserResourceLifecycleGrantMode,
1110
+ context: SessionTenancyVisibility,
1111
+ resourceCount: z.number().int().positive(),
1112
+ resourceKinds: z
1113
+ .array(z.enum(["variable_set", "rig", "connected_machine"]))
1114
+ .min(1)
1115
+ .max(3),
1116
+ sharedOutputWarningVersion: z.literal(PERSONAL_RESOURCE_SHARED_OUTPUT_WARNING_VERSION),
1117
+ })
1118
+ .strict();
1119
+ export type PersonalResourceAttachmentSummary = z.infer<typeof PersonalResourceAttachmentSummary>;
1120
+ /** Public owner-management deliberately excludes race-prone standalone `once`. */
1121
+ export const ManagedUserResourceGrantMode = z.enum(["session", "always"]);
1122
+ export const UserResourceAuthorityGrant = z.object({
1123
+ grantId: z.string().uuid(),
1124
+ targetWorkspaceId: z.string().uuid(),
1125
+ targetSessionId: z.string().uuid().nullable(),
1126
+ action: z.enum([
1127
+ "connection.use",
1128
+ "document.read",
1129
+ "variable_set.use",
1130
+ "rig.use",
1131
+ "connected_machine.use",
1132
+ ]),
1133
+ mode: UserResourceLifecycleGrantMode,
1134
+ context: SessionTenancyVisibility,
1135
+ authorityEpoch: z.number().int().positive().nullable(),
1136
+ generation: z.number().int().positive(),
1137
+ status: z.enum(["active", "consumed", "revoked", "expired"]),
1138
+ expiresAt: z.string().datetime().nullable(),
1139
+ delegation: z.lazy(() => UserResourceDelegation),
1140
+ });
1141
+ export const UserResourceAuthoritySummary = z.object({
1142
+ authorityId: z.string().uuid(),
1143
+ resourceKind: UserResourceKind,
1144
+ resourceId: z.string().uuid(),
1145
+ originWorkspaceId: z.string().uuid().nullable(),
1146
+ generation: z.number().int().positive(),
1147
+ status: z.enum(["active", "retained", "revoked"]),
1148
+ grants: z.array(UserResourceAuthorityGrant),
1149
+ });
1150
+ export const ListUserResourceAuthoritiesQuery = z.object({
1151
+ scope: UserResourceAuthorityScope,
1152
+ resourceKind: UserResourceKind,
1153
+ cursor: z.string().uuid().optional(),
1154
+ limit: z.coerce.number().int().min(1).max(100).default(50),
1155
+ });
1156
+ export const ListUserResourceAuthoritiesResponse = z.object({
1157
+ scope: UserResourceAuthorityScope,
1158
+ authorities: z.array(UserResourceAuthoritySummary),
1159
+ nextCursor: z.string().uuid().nullable(),
1160
+ });
1161
+ export const IssueUserResourceGrantRequest = z
1162
+ .object({
1163
+ scope: UserResourceAuthorityScope,
1164
+ resourceKind: UserResourceKind,
1165
+ mode: ManagedUserResourceGrantMode,
1166
+ context: SessionTenancyVisibility,
1167
+ sessionId: z.string().uuid().nullable().optional(),
1168
+ expectedAuthorityEpoch: z.number().int().positive().nullable().optional(),
1169
+ workspaceSharedAcknowledged: z.boolean().default(false),
1170
+ })
1171
+ .superRefine((value, context) => {
1172
+ if (value.context === "workspace_shared" && !value.workspaceSharedAcknowledged) {
1173
+ context.addIssue({
1174
+ code: "custom",
1175
+ path: ["workspaceSharedAcknowledged"],
1176
+ message: "workspace_shared requires durable shared-output acknowledgement",
1177
+ });
1178
+ }
1179
+ if (value.mode === "always" && (value.sessionId || value.expectedAuthorityEpoch)) {
1180
+ context.addIssue({
1181
+ code: "custom",
1182
+ path: ["sessionId"],
1183
+ message: "always is unbound from session authority",
1184
+ });
1185
+ }
1186
+ if (value.mode === "session" && (!value.sessionId || !value.expectedAuthorityEpoch)) {
1187
+ context.addIssue({
1188
+ code: "custom",
1189
+ path: ["sessionId"],
1190
+ message: "session grants require a target session and expectedAuthorityEpoch",
1191
+ });
1192
+ }
1193
+ });
1194
+ export const UserResourceGrantMutationResponse = z.object({
1195
+ scope: UserResourceAuthorityScope,
1196
+ grant: UserResourceAuthorityGrant,
1197
+ });
1198
+ export const UserResourceGrantRevocationResponse = z.object({
1199
+ scope: UserResourceAuthorityScope,
1200
+ grant: z.object({
1201
+ grantId: z.string().uuid(),
1202
+ generation: z.number().int().positive(),
1203
+ status: z.literal("revoked"),
1204
+ revokedAt: z.string().datetime(),
1205
+ }),
1206
+ });
1207
+ export const RevokeUserResourceGrantQuery = z.object({
1208
+ scope: UserResourceAuthorityScope,
1209
+ });
1210
+ export type UserResourceAuthorityGrant = z.infer<typeof UserResourceAuthorityGrant>;
1211
+ export type UserResourceAuthoritySummary = z.infer<typeof UserResourceAuthoritySummary>;
1212
+ export type ListUserResourceAuthoritiesQuery = z.infer<typeof ListUserResourceAuthoritiesQuery>;
1213
+ export type ListUserResourceAuthoritiesResponse = z.infer<
1214
+ typeof ListUserResourceAuthoritiesResponse
1215
+ >;
1216
+ export type IssueUserResourceGrantRequest = z.infer<typeof IssueUserResourceGrantRequest>;
1217
+ export type UserResourceGrantMutationResponse = z.infer<typeof UserResourceGrantMutationResponse>;
1218
+ export type UserResourceGrantRevocationResponse = z.infer<
1219
+ typeof UserResourceGrantRevocationResponse
1220
+ >;
1057
1221
  export type SessionTenancyVisibility = z.infer<typeof SessionTenancyVisibility>;
1058
1222
 
1059
1223
  /** Public/session API vocabulary. Persistence keeps the explicit tenancy names. */
@@ -1275,6 +1439,100 @@ export const SessionTenancyPublicProjection = z.object({
1275
1439
  });
1276
1440
  export type SessionTenancyPublicProjection = z.infer<typeof SessionTenancyPublicProjection>;
1277
1441
 
1442
+ export const SessionTenancyBlocker = z.enum([
1443
+ "nonterminal_turn",
1444
+ "nonterminal_attempt",
1445
+ "unsettled_interruption",
1446
+ "pending_system_update",
1447
+ "pending_human_input",
1448
+ "pending_tool_receipt",
1449
+ "run_state",
1450
+ "active_goal",
1451
+ "capacity_waiter",
1452
+ "active_realtime",
1453
+ "active_scheduled_task",
1454
+ "workspace_mutation_admission",
1455
+ "retained_process",
1456
+ "active_sandbox_access",
1457
+ "shared_sandbox_group",
1458
+ ]);
1459
+ export type SessionTenancyBlocker = z.infer<typeof SessionTenancyBlocker>;
1460
+
1461
+ export const SESSION_OPERATION_KEY_MAX_CHARS = 256;
1462
+ const SessionTenancyIdempotencyKey = z.string().trim().min(1).max(SESSION_OPERATION_KEY_MAX_CHARS);
1463
+
1464
+ export const UpdateSessionVisibilityRequest = z
1465
+ .object({
1466
+ visibility: SessionVisibility,
1467
+ expectedAuthorityEpoch: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
1468
+ idempotencyKey: SessionTenancyIdempotencyKey,
1469
+ })
1470
+ .strict();
1471
+ export type UpdateSessionVisibilityRequest = z.infer<typeof UpdateSessionVisibilityRequest>;
1472
+
1473
+ export const UpdateSessionVisibilityResponse = z
1474
+ .object({
1475
+ operationId: z.string().uuid(),
1476
+ eventId: z.string().uuid().nullable(),
1477
+ eventSequence: z.number().int().positive().nullable(),
1478
+ visibility: SessionVisibility,
1479
+ authorityEpoch: z.number().int().positive(),
1480
+ changed: z.boolean(),
1481
+ replay: z.boolean(),
1482
+ revokedGrantCount: z.number().int().nonnegative(),
1483
+ })
1484
+ .strict()
1485
+ .superRefine((value, context) => {
1486
+ if (value.changed !== (value.eventId !== null && value.eventSequence !== null)) {
1487
+ context.addIssue({
1488
+ code: "custom",
1489
+ path: ["changed"],
1490
+ message: "changed must match the presence of the durable event receipt",
1491
+ });
1492
+ }
1493
+ if ((value.eventId === null) !== (value.eventSequence === null)) {
1494
+ context.addIssue({
1495
+ code: "custom",
1496
+ path: ["eventId"],
1497
+ message: "eventId and eventSequence must be present or absent together",
1498
+ });
1499
+ }
1500
+ });
1501
+ export type UpdateSessionVisibilityResponse = z.infer<typeof UpdateSessionVisibilityResponse>;
1502
+
1503
+ export const ForkSessionRequest = z
1504
+ .object({
1505
+ idempotencyKey: SessionTenancyIdempotencyKey,
1506
+ visibility: SessionVisibility,
1507
+ workspaceSharedAcknowledged: z.boolean(),
1508
+ })
1509
+ .strict()
1510
+ .superRefine((value, context) => {
1511
+ if (value.visibility === "private" && value.workspaceSharedAcknowledged) {
1512
+ context.addIssue({
1513
+ code: "custom",
1514
+ path: ["workspaceSharedAcknowledged"],
1515
+ message: "workspaceSharedAcknowledged must be false for a private destination",
1516
+ });
1517
+ }
1518
+ });
1519
+ export type ForkSessionRequest = z.infer<typeof ForkSessionRequest>;
1520
+
1521
+ export const ForkSessionResponse = z
1522
+ .object({
1523
+ operationId: z.string().uuid(),
1524
+ eventId: z.string().uuid(),
1525
+ eventSequence: z.number().int().positive(),
1526
+ sessionId: z.string().uuid(),
1527
+ workspaceId: z.string().uuid(),
1528
+ visibility: SessionVisibility,
1529
+ authorityEpoch: z.literal(1),
1530
+ copiedHistoryItemCount: z.number().int().nonnegative(),
1531
+ replay: z.boolean(),
1532
+ })
1533
+ .strict();
1534
+ export type ForkSessionResponse = z.infer<typeof ForkSessionResponse>;
1535
+
1278
1536
  export const ManagedAccount = z.object({
1279
1537
  id: z.string().uuid(),
1280
1538
  name: z.string(),
@@ -1622,6 +1880,37 @@ export const WorkspaceVoiceInputSettings = z
1622
1880
  .strict();
1623
1881
  export type WorkspaceVoiceInputSettings = z.infer<typeof WorkspaceVoiceInputSettings>;
1624
1882
 
1883
+ /** Model policy inherited by newly created chats and scheduled tasks. */
1884
+ export const WorkspaceSessionDefaults = z
1885
+ .object({
1886
+ model: z.string().trim().min(1).max(256),
1887
+ reasoningEffort: ReasoningEffort,
1888
+ })
1889
+ .strict();
1890
+ export type WorkspaceSessionDefaults = z.infer<typeof WorkspaceSessionDefaults>;
1891
+
1892
+ /**
1893
+ * Exact capability selection inherited by new top-level sessions.
1894
+ *
1895
+ * The product UI presents understandable capability groups, but persistence
1896
+ * stays source-of-truth exact: MCP server ids and first-party tool names. This
1897
+ * keeps the runtime independent from presentation labels and lets a session
1898
+ * narrow the resulting policy without changing the workspace default.
1899
+ */
1900
+ export const WorkspaceSessionToolDefaults = z
1901
+ .object({
1902
+ mcpServerIds: z
1903
+ .array(z.string().trim().min(1).max(128))
1904
+ .max(128)
1905
+ .transform((ids) => [...new Set(ids)]),
1906
+ firstPartyMcpTools: z
1907
+ .array(FirstPartyMcpToolName)
1908
+ .max(512)
1909
+ .transform((tools) => [...new Set(tools)]),
1910
+ })
1911
+ .strict();
1912
+ export type WorkspaceSessionToolDefaults = z.infer<typeof WorkspaceSessionToolDefaults>;
1913
+
1625
1914
  /** Client-safe voice-input capability projection. Never includes provider secrets. */
1626
1915
  export const ClientVoiceInputConfig = z
1627
1916
  .object({
@@ -1709,23 +1998,122 @@ export const SlackReactionChannelListResponse = z.object({
1709
1998
  });
1710
1999
  export type SlackReactionChannelListResponse = z.infer<typeof SlackReactionChannelListResponse>;
1711
2000
 
2001
+ /** Where one Slack channel starts work. */
2002
+ export const SlackChannelRoute = z.object({
2003
+ slackChannelId: z.string().min(1).max(64),
2004
+ targetWorkspaceId: z.string().uuid(),
2005
+ // Unbounded like `Workspace.name` itself: a cap here would reject a row the
2006
+ // database happily holds.
2007
+ targetWorkspaceName: z.string().min(1).nullable(),
2008
+ source: z.enum(["picker", "admin"]),
2009
+ updatedAt: z.string(),
2010
+ });
2011
+ export type SlackChannelRoute = z.infer<typeof SlackChannelRoute>;
2012
+
2013
+ export const SlackChannelRouteListResponse = z.object({
2014
+ /** Every stored route. Bounded in practice by the channels the bot is in. */
2015
+ routes: z.array(SlackChannelRoute),
2016
+ /**
2017
+ * Whether routing is switched on for this deployment. With it off the stored
2018
+ * routes are inert, so the surface says so instead of implying they apply.
2019
+ */
2020
+ routingEnabled: z.boolean(),
2021
+ });
2022
+ export type SlackChannelRouteListResponse = z.infer<typeof SlackChannelRouteListResponse>;
2023
+
2024
+ export const UpdateSlackChannelRoutesRequest = z.object({
2025
+ connectionId: z.string().uuid(),
2026
+ routes: z
2027
+ .array(
2028
+ z.object({
2029
+ slackChannelId: z.string().min(1).max(64),
2030
+ /** Null clears the route, so the channel asks once again. */
2031
+ targetWorkspaceId: z.string().uuid().nullable(),
2032
+ }),
2033
+ )
2034
+ .max(200),
2035
+ });
2036
+ export type UpdateSlackChannelRoutesRequest = z.infer<typeof UpdateSlackChannelRoutesRequest>;
2037
+
1712
2038
  export const DEFAULT_WORKSPACE_SLACK_REACTION_SUMMON_SETTINGS = {
1713
2039
  enabled: false,
1714
2040
  emoji: "genie",
1715
2041
  channelPolicy: { mode: "bot_member" },
1716
2042
  } as const satisfies WorkspaceSlackReactionSummonSettings;
1717
2043
 
2044
+ /**
2045
+ * Per-workspace switches for the two Slack orchestration notices: a pointer
2046
+ * card when a child worker of a Slack-originated session blocks on human input
2047
+ * or a tool approval, and a one-line notice when that session's goal pauses for
2048
+ * budget or the continuation cap.
2049
+ *
2050
+ * Both are OFF unless this workspace explicitly turned them on. An unsolicited
2051
+ * Slack post is worse than a missed one, and the in-app rail and priority feed
2052
+ * already surface a blocked child, so an absent, malformed, or partially
2053
+ * invalid setting resolves to both disabled - see
2054
+ * `resolveWorkspaceSlackOrchestrationNoticeSettings`.
2055
+ *
2056
+ * Deliberately not `.strict()`: this is a KNOWN key of the stored settings bag,
2057
+ * so rejecting it rejects the whole bag and silently reverts memoryEnabled,
2058
+ * agentHumanInputEnabled, codexCompactionDefault, voiceInput, and the Slack
2059
+ * reaction shortcut to their defaults too. An unknown key here is a notice a
2060
+ * newer release added, and a rollback must not cost five unrelated settings.
2061
+ * An invalid VALUE still fails the bag, and therefore fails closed to silence.
2062
+ */
2063
+ export const WorkspaceSlackOrchestrationNoticeSettings = z.object({
2064
+ childRequiresAction: z.boolean().optional(),
2065
+ goalPaused: z.boolean().optional(),
2066
+ });
2067
+ export type WorkspaceSlackOrchestrationNoticeSettings = z.infer<
2068
+ typeof WorkspaceSlackOrchestrationNoticeSettings
2069
+ >;
2070
+
2071
+ /**
2072
+ * The resolved answer: every notice is an explicit boolean, never absent.
2073
+ * Spelled out rather than derived with `Required<>`, which would keep the
2074
+ * `| undefined` that the optional zod fields carry into their inferred type.
2075
+ */
2076
+ export type ResolvedWorkspaceSlackOrchestrationNoticeSettings = {
2077
+ childRequiresAction: boolean;
2078
+ goalPaused: boolean;
2079
+ };
2080
+
2081
+ export const DEFAULT_WORKSPACE_SLACK_ORCHESTRATION_NOTICE_SETTINGS = {
2082
+ childRequiresAction: false,
2083
+ goalPaused: false,
2084
+ } as const satisfies ResolvedWorkspaceSlackOrchestrationNoticeSettings;
2085
+
2086
+ // Memory V1's standing prompt block is retired, but the enum deliberately still
2087
+ // ACCEPTS `legacy_standing`. `.passthrough()` only preserves unknown keys - it
2088
+ // does not rescue a known key holding a value the enum rejects - so collapsing
2089
+ // this to one value would fail the whole settings parse for any workspace that
2090
+ // opted in. Every resolver here is "parse the bag, fall back on failure", so
2091
+ // that single rejection would silently revert memoryEnabled,
2092
+ // agentHumanInputEnabled, codexCompactionDefault, voiceInput and Slack summon
2093
+ // to their defaults. The value is accepted and then ignored: see
2094
+ // resolveWorkspaceMemoryPromptMode.
1718
2095
  export const WorkspaceMemoryPromptMode = z.enum(["legacy_standing", "retrieval_only"]);
1719
2096
  export type WorkspaceMemoryPromptMode = z.infer<typeof WorkspaceMemoryPromptMode>;
1720
2097
 
2098
+ /**
2099
+ * What an already-accepted turn recorded. Snapshots and receipts are immutable,
2100
+ * so turns accepted before the standing block was retired still read back
2101
+ * `legacy_standing`. That is a fact about history, not a mode anyone can pick -
2102
+ * keep the two apart so retiring the setting never rewrites the record.
2103
+ */
2104
+ export const HistoricalMemoryPromptMode = z.enum(["legacy_standing", "retrieval_only"]);
2105
+ export type HistoricalMemoryPromptMode = z.infer<typeof HistoricalMemoryPromptMode>;
2106
+
1721
2107
  // Validates the KNOWN keys of workspaces.settings; passthrough keeps unknown
1722
2108
  // (future) keys rather than stripping them. memoryEnabled defaults off and the
1723
- // reversible Memory V1 containment rollout defaults to legacy standing context;
2109
+ // Memory V1 prompt mode is always retrieval-only composition;
1724
2110
  // voiceInput defaults to enabled when the deployment has a provider.
1725
2111
  export const WorkspaceSettingsSchema = z
1726
2112
  .object({
1727
2113
  memoryEnabled: z.boolean().optional(),
1728
2114
  memoryPromptMode: WorkspaceMemoryPromptMode.optional(),
2115
+ sessionDefaults: WorkspaceSessionDefaults.optional(),
2116
+ sessionToolDefaults: WorkspaceSessionToolDefaults.optional(),
1729
2117
  /** Preferred workspace voice-input toggle. */
1730
2118
  voiceInput: WorkspaceVoiceInputSettings.optional(),
1731
2119
  /**
@@ -1745,6 +2133,11 @@ export const WorkspaceSettingsSchema = z
1745
2133
  // Optional Slack reaction invocation. Absent/invalid fails closed to the
1746
2134
  // disabled default via resolveWorkspaceSlackReactionSummonSettings.
1747
2135
  slackReactionSummon: WorkspaceSlackReactionSummonSettings.optional(),
2136
+ // Optional Slack orchestration notices (blocked child worker, paused goal).
2137
+ // BOTH DEFAULT OFF: absent, malformed, or partially invalid settings fail
2138
+ // closed to disabled via resolveWorkspaceSlackOrchestrationNoticeSettings,
2139
+ // because an unsolicited Slack post is worse than a missed one.
2140
+ slackOrchestrationNotices: WorkspaceSlackOrchestrationNoticeSettings.optional(),
1748
2141
  })
1749
2142
  .passthrough();
1750
2143
  export type WorkspaceSettings = z.infer<typeof WorkspaceSettingsSchema>;
@@ -1755,10 +2148,31 @@ export function resolveWorkspaceMemoryEnabled(settings: unknown): boolean {
1755
2148
  return parsed.success ? parsed.data.memoryEnabled === true : false;
1756
2149
  }
1757
2150
 
1758
- /** Reversible Memory V1 prompt rollout. Absent preserves legacy composition. */
1759
- export function resolveWorkspaceMemoryPromptMode(settings: unknown): WorkspaceMemoryPromptMode {
2151
+ /** Explicit defaults for new chats/schedules, or null for deployment defaults. */
2152
+ export function resolveWorkspaceSessionDefaults(
2153
+ settings: unknown,
2154
+ ): WorkspaceSessionDefaults | null {
2155
+ const parsed = WorkspaceSettingsSchema.safeParse(settings ?? {});
2156
+ return parsed.success ? (parsed.data.sessionDefaults ?? null) : null;
2157
+ }
2158
+
2159
+ /** Exact capability defaults for new sessions, or null for deployment defaults. */
2160
+ export function resolveWorkspaceSessionToolDefaults(
2161
+ settings: unknown,
2162
+ ): WorkspaceSessionToolDefaults | null {
1760
2163
  const parsed = WorkspaceSettingsSchema.safeParse(settings ?? {});
1761
- return parsed.success ? (parsed.data.memoryPromptMode ?? "legacy_standing") : "legacy_standing";
2164
+ return parsed.success ? (parsed.data.sessionToolDefaults ?? null) : null;
2165
+ }
2166
+
2167
+ /**
2168
+ * Memory V1 prompt mode. Always `retrieval_only`: the standing pinned/recency
2169
+ * block is retired, so an absent, unrecognized, or stored-`legacy_standing`
2170
+ * setting all resolve the same way. Kept as a function so the call sites and
2171
+ * the frozen SQL snapshot keep agreeing without each one having to learn that
2172
+ * the choice is gone.
2173
+ */
2174
+ export function resolveWorkspaceMemoryPromptMode(): "retrieval_only" {
2175
+ return "retrieval_only";
1762
2176
  }
1763
2177
 
1764
2178
  /** Default Codex compaction mode for new Codex sessions (remote_v2 when unset). */
@@ -1815,6 +2229,30 @@ export function resolveWorkspaceSlackReactionSummonSettings(
1815
2229
  : { ...configured, channelPolicy: { mode: "bot_member" } };
1816
2230
  }
1817
2231
 
2232
+ /**
2233
+ * Resolve the two Slack orchestration notice switches, failing closed.
2234
+ *
2235
+ * Off is the product decision, not a placeholder: the in-app rail and priority
2236
+ * feed already surface a blocked child worker and a paused goal, so a missed
2237
+ * Slack post costs a delay while an unsolicited one costs the thread's
2238
+ * credibility. Absent settings, a settings bag this schema rejects, and a
2239
+ * partially invalid notice object therefore all resolve to both disabled -
2240
+ * only an explicit `true` on a valid bag turns a notice on.
2241
+ */
2242
+ export function resolveWorkspaceSlackOrchestrationNoticeSettings(
2243
+ settings: unknown,
2244
+ ): ResolvedWorkspaceSlackOrchestrationNoticeSettings {
2245
+ const parsed = WorkspaceSettingsSchema.safeParse(settings ?? {});
2246
+ const configured = parsed.success ? parsed.data.slackOrchestrationNotices : undefined;
2247
+ return {
2248
+ childRequiresAction:
2249
+ configured?.childRequiresAction ??
2250
+ DEFAULT_WORKSPACE_SLACK_ORCHESTRATION_NOTICE_SETTINGS.childRequiresAction,
2251
+ goalPaused:
2252
+ configured?.goalPaused ?? DEFAULT_WORKSPACE_SLACK_ORCHESTRATION_NOTICE_SETTINGS.goalPaused,
2253
+ };
2254
+ }
2255
+
1818
2256
  export function workspaceSlackReactionChannelAllowed(
1819
2257
  settings: WorkspaceSlackReactionSummonSettings,
1820
2258
  channelId: string,
@@ -1832,6 +2270,8 @@ export const UpdateWorkspaceSettingsRequest = z
1832
2270
  .object({
1833
2271
  memoryEnabled: z.boolean().optional(),
1834
2272
  memoryPromptMode: WorkspaceMemoryPromptMode.optional(),
2273
+ sessionDefaults: WorkspaceSessionDefaults.optional(),
2274
+ sessionToolDefaults: WorkspaceSessionToolDefaults.optional(),
1835
2275
  voiceInput: WorkspaceVoiceInputSettings.optional(),
1836
2276
  /** @deprecated Prefer `voiceInput`. Kept for one compatibility release. */
1837
2277
  transcription: WorkspaceTranscriptionPolicy.optional(),
@@ -1839,6 +2279,7 @@ export const UpdateWorkspaceSettingsRequest = z
1839
2279
  codexCompactionDefault: CodexCompactionMode.optional(),
1840
2280
  agentHumanInputEnabled: z.boolean().optional(),
1841
2281
  slackReactionSummon: WorkspaceSlackReactionSummonSettings.optional(),
2282
+ slackOrchestrationNotices: WorkspaceSlackOrchestrationNoticeSettings.optional(),
1842
2283
  })
1843
2284
  .passthrough();
1844
2285
  export type UpdateWorkspaceSettingsRequest = z.infer<typeof UpdateWorkspaceSettingsRequest>;
@@ -2349,6 +2790,17 @@ export const StreamTokenPayload = z.object({
2349
2790
  // Short TTL (120s default); rotation is event-driven under the epoch fence,
2350
2791
  // not on a keepalive clock.
2351
2792
  exp: z.number().int().positive(),
2793
+ // The authenticated subject the token was minted for (0281). Optional for
2794
+ // rolling compatibility: old verifiers strip it, old mints omit it.
2795
+ subjectId: z.string().min(1).max(512).optional(),
2796
+ // The session authority epoch observed at mint (0281). The relay tracks the
2797
+ // highest value presented per LIVE channel and rejects tokens below that
2798
+ // floor. The floor is defense-in-depth, not the revocation authority: it
2799
+ // lives only as long as the channel does (both sides detached, the
2800
+ // half-open reaper, or a relay restart reset it), so a stale in-TTL token
2801
+ // can still attach to a fresh channel. Real revocation is the mint refusing
2802
+ // to issue new tokens plus the 120 s TTL bounding the old ones.
2803
+ authorityEpoch: z.number().int().positive().optional(),
2352
2804
  });
2353
2805
  export type StreamTokenPayload = z.infer<typeof StreamTokenPayload>;
2354
2806
 
@@ -2510,6 +2962,7 @@ export const ApiKey = z.object({
2510
2962
  accountId: z.string().uuid(),
2511
2963
  workspaceId: z.string().uuid().nullable(),
2512
2964
  name: z.string(),
2965
+ description: z.string().nullable(),
2513
2966
  prefix: z.string(),
2514
2967
  permissions: z.array(Permission),
2515
2968
  expiresAt: z.string().nullable(),
@@ -2522,6 +2975,7 @@ export type ApiKey = z.infer<typeof ApiKey>;
2522
2975
 
2523
2976
  export const CreateApiKeyRequest = z.object({
2524
2977
  name: z.string().min(1),
2978
+ description: z.string().trim().min(1).max(500).optional(),
2525
2979
  workspaceId: z.string().uuid().optional(),
2526
2980
  permissions: z.array(Permission).min(1),
2527
2981
  expiresAt: z.string().datetime({ offset: true }).optional(),
@@ -2675,6 +3129,59 @@ export type InsightsBillingPath = z.infer<typeof InsightsBillingPath>;
2675
3129
  export const InsightsPricingSource = z.enum(["configured_list_price", "gateway_reported"]);
2676
3130
  export type InsightsPricingSource = z.infer<typeof InsightsPricingSource>;
2677
3131
 
3132
+ export const ModelContextContributionSource = z.enum([
3133
+ "workspace_instruction_policy",
3134
+ "legacy_workspace_instructions",
3135
+ "preference_registry_descriptor",
3136
+ "company_profile",
3137
+ "legacy_memory_v1",
3138
+ "runtime_skill_catalog",
3139
+ ]);
3140
+ export type ModelContextContributionSource = z.infer<typeof ModelContextContributionSource>;
3141
+
3142
+ /** Content-free per-call summary of model-visible Company Brain material. */
3143
+ export const ModelContextContributionSummary = z
3144
+ .object({
3145
+ source: ModelContextContributionSource,
3146
+ items: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
3147
+ utf8Bytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
3148
+ estimatedTokens: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
3149
+ })
3150
+ .strict();
3151
+ export type ModelContextContributionSummary = z.infer<typeof ModelContextContributionSummary>;
3152
+
3153
+ export const ModelContextContributionSummaries = z
3154
+ .array(ModelContextContributionSummary)
3155
+ .max(6)
3156
+ .superRefine((summaries, context) => {
3157
+ const seen = new Set<ModelContextContributionSource>();
3158
+ for (const [index, summary] of summaries.entries()) {
3159
+ if (seen.has(summary.source)) {
3160
+ context.addIssue({
3161
+ code: "custom",
3162
+ message: "Model context contribution sources must be unique",
3163
+ path: [index, "source"],
3164
+ });
3165
+ }
3166
+ seen.add(summary.source);
3167
+ }
3168
+ });
3169
+
3170
+ export const InsightsPromptContributionRow = ModelContextContributionSummary.extend({
3171
+ calls: z.number().int().nonnegative(),
3172
+ });
3173
+ export type InsightsPromptContributionRow = z.infer<typeof InsightsPromptContributionRow>;
3174
+
3175
+ export const InsightsPromptContributions = z.object({
3176
+ /** Sum of UTF-8 byte / 4 estimates across calls with contribution receipts. */
3177
+ estimatedTokens: z.number().int().nonnegative(),
3178
+ utf8Bytes: z.number().int().nonnegative(),
3179
+ coveredCalls: z.number().int().nonnegative(),
3180
+ totalCalls: z.number().int().nonnegative(),
3181
+ sources: z.array(InsightsPromptContributionRow),
3182
+ });
3183
+ export type InsightsPromptContributions = z.infer<typeof InsightsPromptContributions>;
3184
+
2678
3185
  export const InsightsModelUsageRow = z.object({
2679
3186
  id: z.string().min(1),
2680
3187
  model: z.string().min(1),
@@ -2841,6 +3348,13 @@ export const WorkspaceInsightsSnapshot = z.object({
2841
3348
  drivers: z.array(InsightsSpendDriver),
2842
3349
  schedules: z.array(InsightsScheduleRow),
2843
3350
  recentCalls: z.array(InsightsModelCallRow),
3351
+ promptContributions: InsightsPromptContributions.default({
3352
+ estimatedTokens: 0,
3353
+ utf8Bytes: 0,
3354
+ coveredCalls: 0,
3355
+ totalCalls: 0,
3356
+ sources: [],
3357
+ }),
2844
3358
  warmSeconds: z.number().nonnegative(),
2845
3359
  priorWarmSeconds: z.number().nonnegative(),
2846
3360
  warmGroups: z.array(InsightsWarmGroupRow),
@@ -2989,6 +3503,11 @@ export const GitCredentialRepositoryRef = z.object({
2989
3503
  access: GitRepositoryAccess.optional(),
2990
3504
  uri: z.string().min(1),
2991
3505
  ref: z.string().min(1),
3506
+ /** Immutable commit the caller expects `ref` to materialize. */
3507
+ expectedCommitSha: z
3508
+ .string()
3509
+ .regex(/^[0-9a-f]{40}$/)
3510
+ .optional(),
2992
3511
  repositoryId: GitProviderRepositoryId.optional(),
2993
3512
  installationId: GitProviderRepositoryId.optional(),
2994
3513
  projectId: GitProviderRepositoryId.optional(),
@@ -3113,9 +3632,25 @@ export type GitCredentials = {
3113
3632
  export type SandboxSecretsRequest = {
3114
3633
  accountId: string;
3115
3634
  workspaceId: string;
3116
- // The variable set the run's session declares (null = unattached;
3117
- // the provider, like the self-mint path, returns null values for it).
3635
+ // Exact live attempt authority. Hosts MUST revalidate this tuple, the
3636
+ // session's selected resource identity, and any personal-resource grant
3637
+ // immediately before returning plaintext.
3638
+ sessionId: string;
3639
+ turnId: string;
3640
+ attemptId: string;
3641
+ executionGeneration: number;
3642
+ // Organization/workspace Variable Sets may be materialized by a pure
3643
+ // service turn (for example, the scheduler). Personal Variable Sets still
3644
+ // require this causal human and an exact personal-resource grant.
3645
+ initiator: TurnInitiator;
3646
+ initiatingHumanSubjectId: string | null;
3118
3647
  variableSetId: string;
3648
+ /**
3649
+ * Exact accepted scheduled-occurrence generation. Present only for scheduled
3650
+ * attempts; ordinary live turns omit it, so hosts that predate this field keep
3651
+ * working unchanged for those.
3652
+ */
3653
+ expectedGeneration?: number | null;
3119
3654
  };
3120
3655
 
3121
3656
  export type SandboxSecrets = {
@@ -3123,8 +3658,19 @@ export type SandboxSecrets = {
3123
3658
  // `environmentsEncryptionKeyBytes` decrypt. Same shape the self-mint path
3124
3659
  // produces (plaintext name→value).
3125
3660
  values: Record<string, string>;
3126
- // workspace-scope cross-check echo: the workspace the provider scoped these secrets to.
3661
+ // Exact scope/resource echoes are mandatory so a host routing bug cannot
3662
+ // inject a sibling tenant, resource, or stale attempt's plaintext.
3663
+ accountId: string;
3127
3664
  workspaceId: string;
3665
+ sessionId: string;
3666
+ turnId: string;
3667
+ attemptId: string;
3668
+ executionGeneration: number;
3669
+ variableSetId: string;
3670
+ /** Must echo the request's `expectedGeneration` when one was supplied. */
3671
+ expectedGeneration?: number | null;
3672
+ scope: VariableSetScope;
3673
+ generation: number;
3128
3674
  // Optional variableSet metadata; when omitted the activity uses the
3129
3675
  // variableSetId as both id and name (the local decrypt carries the row's
3130
3676
  // id/name/description, but only `id` is load-bearing downstream).
@@ -3288,12 +3834,127 @@ export const McpPersonalConnectionDelegation = z
3288
3834
  .object({
3289
3835
  serverId: z.string().min(1).max(256),
3290
3836
  connectionId: z.string().uuid(),
3837
+ /**
3838
+ * Immutable physical workspace that owns an activated common-user
3839
+ * connection. It is server-resolved from the selected authority and lets
3840
+ * provider adapters load only that exact row when the accepted turn runs
3841
+ * in another workspace of the same organization.
3842
+ */
3843
+ originWorkspaceId: z.string().uuid().optional(),
3291
3844
  ownerSubjectId: z.string().min(1).max(512),
3292
3845
  providerDomain: z.string().min(1).max(2048),
3293
3846
  kind: z.enum(["oauth2", "api_key", "app_install", "delegated"]).optional(),
3294
- connectionType: z.enum(["mcp", "social", "atlassian"]).optional(),
3295
- })
3296
- .strict();
3847
+ connectionType: z.enum(["mcp", "social", "atlassian", "github_personal"]).optional(),
3848
+ /**
3849
+ * Explicit common-authority grant selected by the owning human. Omission is
3850
+ * retained only for legacy_user connections that cannot yet participate in
3851
+ * the organization-user authority lifecycle.
3852
+ */
3853
+ userDelegation: UserResourceDelegation.optional(),
3854
+ /**
3855
+ * Personal GitHub only: the exact repository authority frozen when this
3856
+ * accepted work was admitted. The credential binding is evidence, not an
3857
+ * authorization; physical provider use must revalidate every mutable
3858
+ * connection/grant/selection fence against this snapshot.
3859
+ */
3860
+ personalGitHubRepositorySelection: z
3861
+ .object({
3862
+ credentialBindingId: z.string().uuid(),
3863
+ connectionAuthorityGeneration: z.number().int().positive(),
3864
+ selectionGeneration: z.number().int().positive(),
3865
+ repositories: z
3866
+ .array(
3867
+ z
3868
+ .object({
3869
+ repositoryId: z.string().regex(/^[1-9]\d*$/u),
3870
+ fullName: z.string().min(3).max(140),
3871
+ canonicalUrl: z.string().url().max(512),
3872
+ ref: z.string().min(1).max(255),
3873
+ access: GitRepositoryAccess,
3874
+ selectionGeneration: z.number().int().positive(),
3875
+ })
3876
+ .strict(),
3877
+ )
3878
+ .min(1)
3879
+ .max(100),
3880
+ })
3881
+ .strict()
3882
+ .optional(),
3883
+ /**
3884
+ * Google Drive publication only: the exact output destination frozen when
3885
+ * this delegation was accepted, so a later connection-settings change can
3886
+ * never redirect an already-accepted turn's publication. Structurally
3887
+ * mirrors GoogleDriveOutputDestination (defined in ./google-drive, which
3888
+ * imports this module - hence the inline shape). Absent on pre-freeze
3889
+ * turns, which keep the bounded legacy live-resolution behavior.
3890
+ */
3891
+ outputDestination: z
3892
+ .object({
3893
+ folderId: z.string().min(1).max(256),
3894
+ folderName: z.string().min(1).max(1024),
3895
+ driveId: z.string().min(1).max(256).nullable(),
3896
+ location: z.enum(["my_drive", "shared_drive"]),
3897
+ selectedAt: z.string().datetime({ offset: true }),
3898
+ })
3899
+ .strict()
3900
+ .optional(),
3901
+ })
3902
+ .strict()
3903
+ .superRefine((delegation, context) => {
3904
+ const personalGitHub = delegation.connectionType === "github_personal";
3905
+ if (personalGitHub !== (delegation.personalGitHubRepositorySelection !== undefined)) {
3906
+ context.addIssue({
3907
+ code: "custom",
3908
+ path: ["personalGitHubRepositorySelection"],
3909
+ message: "personal GitHub delegation requires one repository authority snapshot",
3910
+ });
3911
+ }
3912
+ if (!personalGitHub) return;
3913
+ if (
3914
+ delegation.serverId !== "github:personal" ||
3915
+ delegation.providerDomain !== "github.com" ||
3916
+ delegation.kind !== "oauth2" ||
3917
+ !delegation.originWorkspaceId ||
3918
+ !delegation.userDelegation
3919
+ ) {
3920
+ context.addIssue({
3921
+ code: "custom",
3922
+ message: "personal GitHub delegation requires exact user-owned connection authority",
3923
+ });
3924
+ }
3925
+ const repositories = delegation.personalGitHubRepositorySelection?.repositories ?? [];
3926
+ const repositoryIds = new Set<string>();
3927
+ const canonicalUrls = new Set<string>();
3928
+ repositories.forEach((repository, index) => {
3929
+ const canonicalUrl = repository.canonicalUrl.toLowerCase();
3930
+ if (
3931
+ !/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})\/[A-Za-z0-9_.-]{1,100}$/u.test(repository.fullName) ||
3932
+ repository.canonicalUrl !== `https://github.com/${repository.fullName}`
3933
+ ) {
3934
+ context.addIssue({
3935
+ code: "custom",
3936
+ path: ["personalGitHubRepositorySelection", "repositories", index, "canonicalUrl"],
3937
+ message: "personal GitHub repository identity must be canonical",
3938
+ });
3939
+ }
3940
+ if (repositoryIds.has(repository.repositoryId)) {
3941
+ context.addIssue({
3942
+ code: "custom",
3943
+ path: ["personalGitHubRepositorySelection", "repositories", index, "repositoryId"],
3944
+ message: "personal GitHub repositories must be unique by provider id",
3945
+ });
3946
+ }
3947
+ if (canonicalUrls.has(canonicalUrl)) {
3948
+ context.addIssue({
3949
+ code: "custom",
3950
+ path: ["personalGitHubRepositorySelection", "repositories", index, "canonicalUrl"],
3951
+ message: "personal GitHub repositories must be unique by canonical URL",
3952
+ });
3953
+ }
3954
+ repositoryIds.add(repository.repositoryId);
3955
+ canonicalUrls.add(canonicalUrl);
3956
+ });
3957
+ });
3297
3958
  export type McpPersonalConnectionDelegation = z.infer<typeof McpPersonalConnectionDelegation>;
3298
3959
 
3299
3960
  /**
@@ -3318,12 +3979,44 @@ export const McpPersonalConnectionDelegations = z
3318
3979
  }
3319
3980
  });
3320
3981
 
3321
- export const McpPersonalConnectionSummary = McpPersonalConnectionDelegation.pick({
3322
- serverId: true,
3323
- providerDomain: true,
3324
- });
3982
+ export const McpPersonalConnectionSummary = z
3983
+ .object({
3984
+ serverId: z.string().min(1).max(256),
3985
+ providerDomain: z.string().min(1).max(2048),
3986
+ })
3987
+ .strict();
3325
3988
  export type McpPersonalConnectionSummary = z.infer<typeof McpPersonalConnectionSummary>;
3326
3989
 
3990
+ /**
3991
+ * Public, credential-free selection of one exact personal connection grant for
3992
+ * one selected MCP/first-party server. The owner is always derived server-side.
3993
+ */
3994
+ export const McpConnectionAuthoritySelection = z
3995
+ .object({
3996
+ serverId: z.string().min(1).max(256),
3997
+ connectionId: z.string().uuid(),
3998
+ userDelegation: UserResourceDelegation,
3999
+ })
4000
+ .strict();
4001
+ export type McpConnectionAuthoritySelection = z.infer<typeof McpConnectionAuthoritySelection>;
4002
+
4003
+ export const McpConnectionAuthoritySelections = z
4004
+ .array(McpConnectionAuthoritySelection)
4005
+ .max(128)
4006
+ .superRefine((selections, context) => {
4007
+ const seen = new Set<string>();
4008
+ for (const [index, selection] of selections.entries()) {
4009
+ if (seen.has(selection.serverId)) {
4010
+ context.addIssue({
4011
+ code: "custom",
4012
+ message: "connection authority selections must be unique by serverId",
4013
+ path: [index, "serverId"],
4014
+ });
4015
+ }
4016
+ seen.add(selection.serverId);
4017
+ }
4018
+ });
4019
+
3327
4020
  export type McpCredentialsRequest = {
3328
4021
  accountId: string;
3329
4022
  workspaceId: string;
@@ -3354,6 +4047,10 @@ export type McpCredentialsRequest = {
3354
4047
  toolName?: string;
3355
4048
  connectionRef: McpServerConnectionRef;
3356
4049
  forceRefresh: boolean;
4050
+ /** Canonical credential-free authority frozen on the accepted turn. */
4051
+ connectionUseAuthority?: unknown;
4052
+ /** Stable idempotency key for this one physical provider request. */
4053
+ connectionUseRequestId?: string;
3357
4054
  };
3358
4055
 
3359
4056
  export type McpCredentialAuthNeededReason =
@@ -3552,13 +4249,35 @@ export const CreateCheckoutResponse = z.object({
3552
4249
  });
3553
4250
  export type CreateCheckoutResponse = z.infer<typeof CreateCheckoutResponse>;
3554
4251
 
4252
+ export const CreateBillingPortalRequest = z.object({
4253
+ accountId: z.string().uuid().optional(),
4254
+ returnUrl: z.string().url().optional(),
4255
+ });
4256
+ export type CreateBillingPortalRequest = z.infer<typeof CreateBillingPortalRequest>;
4257
+
4258
+ export const CreateBillingPortalResponse = z.object({
4259
+ portalSessionId: z.string(),
4260
+ url: z.string().url(),
4261
+ });
4262
+ export type CreateBillingPortalResponse = z.infer<typeof CreateBillingPortalResponse>;
4263
+
3555
4264
  export const RepositoryResourceRef = z.object({
3556
4265
  kind: z.literal("repository"),
3557
4266
  uri: z.string().min(1),
3558
4267
  ref: z.string().min(1),
4268
+ /**
4269
+ * Optional immutable Git object fence. Repository materialization must fail
4270
+ * when the checked-out HEAD is not this exact commit. Event-driven sessions
4271
+ * use it to prevent a mutable PR branch from changing underneath a review.
4272
+ */
4273
+ expectedCommitSha: z
4274
+ .string()
4275
+ .regex(/^[0-9a-f]{40}$/)
4276
+ .optional(),
3559
4277
  mountPath: z.string().min(1).optional(),
3560
4278
  subpath: z.string().min(1).optional(),
3561
4279
  provider: GitCredentialProvider.optional(),
4280
+ connectionType: z.literal("github_personal").optional(),
3562
4281
  credentialBindingId: GitCredentialBindingId.optional(),
3563
4282
  access: GitRepositoryAccess.optional(),
3564
4283
  repositoryId: GitProviderRepositoryId.optional(),
@@ -3718,6 +4437,8 @@ export type FileResourceRef = z.infer<typeof FileResourceRef>;
3718
4437
  * reconstruct the same typed attachment input after a model switch or retry.
3719
4438
  */
3720
4439
  export const MODEL_ATTACHMENT_REFS_FIELD = "opengeni_attachment_refs" as const;
4440
+ /** Private marker for the compact attachment-reference carrier created by compaction. */
4441
+ export const MODEL_ATTACHMENT_CATALOG_MARKER = "opengeni_attachment_catalog" as const;
3721
4442
  /**
3722
4443
  * Structured timeline annotations retained beside the deterministic user-text
3723
4444
  * projection in canonical history. Provider adapters remove this OpenGeni
@@ -3817,6 +4538,25 @@ export function defaultRepositoryMountPath(
3817
4538
  );
3818
4539
  }
3819
4540
 
4541
+ /** Durable SDK/UI artifact root. Provisioned boxes also mount this path;
4542
+ * Connected Machine execution does not treat it as an alias. */
4543
+ export const VIRTUAL_WORKSPACE_ROOT = "/workspace" as const;
4544
+
4545
+ /**
4546
+ * Cwd-relative path for shell/exec prompts. Durable receipts and `sandbox:` UI
4547
+ * links keep the durable `/workspace/...` form. Tool receipts use the relative
4548
+ * projection so they remain usable from either a provisioned box root or a
4549
+ * Connected Machine's truthful host-native cwd.
4550
+ */
4551
+ export function sandboxShellPath(virtualPath: string): string {
4552
+ if (virtualPath === VIRTUAL_WORKSPACE_ROOT) return ".";
4553
+ if (virtualPath.startsWith(`${VIRTUAL_WORKSPACE_ROOT}/`)) {
4554
+ const relative = virtualPath.slice(VIRTUAL_WORKSPACE_ROOT.length + 1);
4555
+ return relative.length > 0 ? relative : ".";
4556
+ }
4557
+ return virtualPath;
4558
+ }
4559
+
3820
4560
  /** Resolve the exact mount used by API normalization, manifests, and clone hooks. */
3821
4561
  export const DEFAULT_FILE_RESOURCE_MOUNT_ROOT = ".opengeni/files" as const;
3822
4562
 
@@ -3985,6 +4725,9 @@ export const Document = z.object({
3985
4725
  authorityKind: DocumentAuthorityKind,
3986
4726
  authorityWorkspaceId: z.string().uuid().nullable(),
3987
4727
  authoritySubjectId: z.string().nullable(),
4728
+ // Opaque handle used by the owning human to issue explicit personal-scope
4729
+ // grants. Organization/workspace and legacy anchored personal rows are null.
4730
+ authorityId: z.string().uuid().nullable().optional(),
3988
4731
  visibility: DocumentVisibility,
3989
4732
  createdBy: z.string().nullable(),
3990
4733
  agentAccess: z.boolean(),
@@ -4137,6 +4880,178 @@ export const MoveDocumentRequest = z.object({
4137
4880
  });
4138
4881
  export type MoveDocumentRequest = z.infer<typeof MoveDocumentRequest>;
4139
4882
 
4883
+ export const DocumentAuthorityTuple = z.object({
4884
+ kind: DocumentAuthorityKind,
4885
+ workspaceId: z.string().uuid().nullable(),
4886
+ subjectId: z.string().nullable(),
4887
+ authorityId: z.string().uuid().nullable(),
4888
+ });
4889
+ export type DocumentAuthorityTuple = z.infer<typeof DocumentAuthorityTuple>;
4890
+
4891
+ export const ReclassifyDocumentAuthorityRequest = z.object({
4892
+ operationId: z.string().uuid(),
4893
+ expectedAuthority: DocumentAuthorityTuple,
4894
+ targetAuthorityKind: DocumentAuthorityKind,
4895
+ });
4896
+ export type ReclassifyDocumentAuthorityRequest = z.infer<typeof ReclassifyDocumentAuthorityRequest>;
4897
+
4898
+ export const DocumentAuthorityReclassification = z.object({
4899
+ operationId: z.string().uuid(),
4900
+ documentId: z.string().uuid(),
4901
+ previousAuthority: DocumentAuthorityTuple,
4902
+ authority: DocumentAuthorityTuple,
4903
+ createdAt: z.string().datetime({ offset: true }),
4904
+ });
4905
+ export type DocumentAuthorityReclassification = z.infer<typeof DocumentAuthorityReclassification>;
4906
+
4907
+ export const DOCUMENT_AUTHORITY_RECLASSIFICATION_LIST_DEFAULT_LIMIT = 50;
4908
+ export const DOCUMENT_AUTHORITY_RECLASSIFICATION_LIST_MAX_LIMIT = 100;
4909
+ export const DOCUMENT_AUTHORITY_RECLASSIFICATION_CURSOR_MAX_CHARS = 1_024;
4910
+
4911
+ export const ListDocumentAuthorityReclassificationsQuery = z.object({
4912
+ limit: z.coerce
4913
+ .number()
4914
+ .int()
4915
+ .positive()
4916
+ .max(DOCUMENT_AUTHORITY_RECLASSIFICATION_LIST_MAX_LIMIT)
4917
+ .default(DOCUMENT_AUTHORITY_RECLASSIFICATION_LIST_DEFAULT_LIMIT),
4918
+ cursor: z.string().min(1).max(DOCUMENT_AUTHORITY_RECLASSIFICATION_CURSOR_MAX_CHARS).optional(),
4919
+ });
4920
+ export type ListDocumentAuthorityReclassificationsQuery = z.infer<
4921
+ typeof ListDocumentAuthorityReclassificationsQuery
4922
+ >;
4923
+
4924
+ export const ListDocumentAuthorityReclassificationsResponse = z.object({
4925
+ receipts: z.array(DocumentAuthorityReclassification),
4926
+ hasMore: z.boolean(),
4927
+ nextCursor: z.string().max(DOCUMENT_AUTHORITY_RECLASSIFICATION_CURSOR_MAX_CHARS).nullable(),
4928
+ });
4929
+ export type ListDocumentAuthorityReclassificationsResponse = z.infer<
4930
+ typeof ListDocumentAuthorityReclassificationsResponse
4931
+ >;
4932
+
4933
+ export const RunDocumentDefaultCollectionBackfillRequest = z.object({
4934
+ runId: z.string().uuid(),
4935
+ operationId: z.string().uuid(),
4936
+ batchSize: z.number().int().min(1).max(100).default(50),
4937
+ });
4938
+ export type RunDocumentDefaultCollectionBackfillRequest = z.infer<
4939
+ typeof RunDocumentDefaultCollectionBackfillRequest
4940
+ >;
4941
+
4942
+ export const DocumentDefaultCollectionBackfill = z.object({
4943
+ runId: z.string().uuid(),
4944
+ operationId: z.string().uuid(),
4945
+ status: z.enum(["running", "completed"]),
4946
+ lastWorkspaceId: z.string().uuid().nullable(),
4947
+ processedCount: z.number().int().nonnegative(),
4948
+ createdCount: z.number().int().nonnegative(),
4949
+ adoptedCount: z.number().int().nonnegative(),
4950
+ completedAt: z.string().datetime({ offset: true }).nullable(),
4951
+ });
4952
+ export type DocumentDefaultCollectionBackfill = z.infer<typeof DocumentDefaultCollectionBackfill>;
4953
+
4954
+ export const DocumentDefaultCollectionBackfillRunAudit = DocumentDefaultCollectionBackfill.omit({
4955
+ operationId: true,
4956
+ }).extend({
4957
+ actorSubjectId: z.string().min(1),
4958
+ startedAt: z.string().datetime({ offset: true }),
4959
+ updatedAt: z.string().datetime({ offset: true }),
4960
+ });
4961
+ export type DocumentDefaultCollectionBackfillRunAudit = z.infer<
4962
+ typeof DocumentDefaultCollectionBackfillRunAudit
4963
+ >;
4964
+
4965
+ export const DocumentDefaultCollectionBackfillOperationAudit = z.object({
4966
+ operationId: z.string().uuid(),
4967
+ result: DocumentDefaultCollectionBackfill,
4968
+ createdAt: z.string().datetime({ offset: true }),
4969
+ });
4970
+ export type DocumentDefaultCollectionBackfillOperationAudit = z.infer<
4971
+ typeof DocumentDefaultCollectionBackfillOperationAudit
4972
+ >;
4973
+
4974
+ export const DocumentDefaultCollectionBackfillReceiptAudit = z.object({
4975
+ workspaceId: z.string().uuid(),
4976
+ baseId: z.string().uuid(),
4977
+ outcome: z.enum(["created", "adopted"]),
4978
+ createdAt: z.string().datetime({ offset: true }),
4979
+ });
4980
+ export type DocumentDefaultCollectionBackfillReceiptAudit = z.infer<
4981
+ typeof DocumentDefaultCollectionBackfillReceiptAudit
4982
+ >;
4983
+
4984
+ export const ListDocumentMigrationAuditQuery = ListDocumentAuthorityReclassificationsQuery;
4985
+ export type ListDocumentMigrationAuditQuery = z.infer<typeof ListDocumentMigrationAuditQuery>;
4986
+
4987
+ export const ListDocumentDefaultCollectionBackfillRunsResponse = z.object({
4988
+ runs: z.array(DocumentDefaultCollectionBackfillRunAudit),
4989
+ hasMore: z.boolean(),
4990
+ nextCursor: z.string().max(DOCUMENT_AUTHORITY_RECLASSIFICATION_CURSOR_MAX_CHARS).nullable(),
4991
+ });
4992
+ export type ListDocumentDefaultCollectionBackfillRunsResponse = z.infer<
4993
+ typeof ListDocumentDefaultCollectionBackfillRunsResponse
4994
+ >;
4995
+
4996
+ export const GetDocumentDefaultCollectionBackfillAuditQuery = z.object({
4997
+ limit: z.coerce
4998
+ .number()
4999
+ .int()
5000
+ .positive()
5001
+ .max(DOCUMENT_AUTHORITY_RECLASSIFICATION_LIST_MAX_LIMIT)
5002
+ .default(DOCUMENT_AUTHORITY_RECLASSIFICATION_LIST_DEFAULT_LIMIT),
5003
+ operationCursor: z
5004
+ .string()
5005
+ .min(1)
5006
+ .max(DOCUMENT_AUTHORITY_RECLASSIFICATION_CURSOR_MAX_CHARS)
5007
+ .optional(),
5008
+ receiptCursor: z
5009
+ .string()
5010
+ .min(1)
5011
+ .max(DOCUMENT_AUTHORITY_RECLASSIFICATION_CURSOR_MAX_CHARS)
5012
+ .optional(),
5013
+ });
5014
+ export type GetDocumentDefaultCollectionBackfillAuditQuery = z.infer<
5015
+ typeof GetDocumentDefaultCollectionBackfillAuditQuery
5016
+ >;
5017
+
5018
+ export const DocumentDefaultCollectionBackfillAudit = z.object({
5019
+ run: DocumentDefaultCollectionBackfillRunAudit,
5020
+ operations: z.array(DocumentDefaultCollectionBackfillOperationAudit),
5021
+ receipts: z.array(DocumentDefaultCollectionBackfillReceiptAudit),
5022
+ operationsHasMore: z.boolean(),
5023
+ operationsNextCursor: z
5024
+ .string()
5025
+ .max(DOCUMENT_AUTHORITY_RECLASSIFICATION_CURSOR_MAX_CHARS)
5026
+ .nullable(),
5027
+ receiptsHasMore: z.boolean(),
5028
+ receiptsNextCursor: z
5029
+ .string()
5030
+ .max(DOCUMENT_AUTHORITY_RECLASSIFICATION_CURSOR_MAX_CHARS)
5031
+ .nullable(),
5032
+ });
5033
+ export type DocumentDefaultCollectionBackfillAudit = z.infer<
5034
+ typeof DocumentDefaultCollectionBackfillAudit
5035
+ >;
5036
+
5037
+ export const OrganizationDocumentAuthorityReclassification =
5038
+ DocumentAuthorityReclassification.extend({
5039
+ actorSubjectId: z.string().min(1),
5040
+ requestWorkspaceId: z.string().uuid(),
5041
+ });
5042
+ export type OrganizationDocumentAuthorityReclassification = z.infer<
5043
+ typeof OrganizationDocumentAuthorityReclassification
5044
+ >;
5045
+
5046
+ export const ListOrganizationDocumentAuthorityReclassificationsResponse = z.object({
5047
+ receipts: z.array(OrganizationDocumentAuthorityReclassification),
5048
+ hasMore: z.boolean(),
5049
+ nextCursor: z.string().max(DOCUMENT_AUTHORITY_RECLASSIFICATION_CURSOR_MAX_CHARS).nullable(),
5050
+ });
5051
+ export type ListOrganizationDocumentAuthorityReclassificationsResponse = z.infer<
5052
+ typeof ListOrganizationDocumentAuthorityReclassificationsResponse
5053
+ >;
5054
+
4140
5055
  export const DocumentSearchRequest = z.object({
4141
5056
  query: z.string().min(1),
4142
5057
  baseIds: z.array(z.string().uuid()).optional(),
@@ -4282,9 +5197,15 @@ export type WorkspaceMemorySearchResponse = z.infer<typeof WorkspaceMemorySearch
4282
5197
  export const ToolRef = z.object({
4283
5198
  kind: z.literal("mcp"),
4284
5199
  id: z.string().min(1),
5200
+ // Session-scoped startup choice. `true` prepares this server's authorized
5201
+ // schemas before the first model request; absent/false keeps it behind
5202
+ // progressive discovery. This never grants a server or tool permission.
5203
+ eager: z.boolean().optional(),
4285
5204
  // Non-fatal-on-connect marker for MCP server refs that can degrade
4286
5205
  // gracefully. On new input, absent/false is STRICT: the id must be configured
4287
- // and an unavailable registered server fails the turn. Persisted refs are
5206
+ // and an unavailable registered server fails closed when its preparation is
5207
+ // demanded. Only the independent eager marker makes that a startup failure.
5208
+ // Persisted refs are
4288
5209
  // intersected with the current registry at each turn boundary, so a server
4289
5210
  // disconnected after admission is retained in policy/audit truth but skipped
4290
5211
  // until it is registered again. `optional:true` additionally makes runtime
@@ -4481,10 +5402,14 @@ export function mergeToolRefs(existing: ToolRef[], additions: ToolRef[]): ToolRe
4481
5402
  // strict occurrence upgrades the merged ref so an unavailable server fails
4482
5403
  // the turn. This preserves the fail-loud default when defaults, packs, and
4483
5404
  // per-turn tool selections are combined.
4484
- if (prior.optional === true && tool.optional !== true) {
4485
- const { optional: _dropped, ...strict } = prior;
4486
- byKey.set(key, strict);
4487
- }
5405
+ const optional = prior.optional === true && tool.optional === true ? true : undefined;
5406
+ const eager = prior.eager === true || tool.eager === true ? true : undefined;
5407
+ byKey.set(key, {
5408
+ kind: "mcp",
5409
+ id: prior.id,
5410
+ ...(optional ? { optional } : {}),
5411
+ ...(eager ? { eager } : {}),
5412
+ });
4488
5413
  }
4489
5414
  return order.map((key) => byKey.get(key)!);
4490
5415
  }
@@ -4638,6 +5563,9 @@ export const SESSION_GOAL_TEXT_MAX_BYTES = 8 * 1024;
4638
5563
  export const SESSION_GOAL_SUCCESS_CRITERIA_MAX_BYTES = 8 * 1024;
4639
5564
  export const SESSION_GOAL_RATIONALE_MAX_BYTES = 2 * 1024;
4640
5565
  export const SESSION_GOAL_PROGRESS_MAX_BYTES = 4 * 1024;
5566
+ export const SESSION_GOAL_ROOT_CONSTRAINT_MAX_BYTES = 512;
5567
+ export const SESSION_GOAL_ROOT_CONSTRAINTS_MAX_BYTES = 4 * 1024;
5568
+ export const SESSION_GOAL_ROOT_CONSTRAINTS_MAX_ITEMS = 16;
4641
5569
 
4642
5570
  export function sessionGoalUtf8Bytes(value: string): number {
4643
5571
  return new TextEncoder().encode(value).byteLength;
@@ -4652,6 +5580,43 @@ function boundedSessionGoalString(maxBytes: number, field: string) {
4652
5580
  });
4653
5581
  }
4654
5582
 
5583
+ function compareUtf8(left: string, right: string): number {
5584
+ const encoder = new TextEncoder();
5585
+ const leftBytes = encoder.encode(left);
5586
+ const rightBytes = encoder.encode(right);
5587
+ const length = Math.min(leftBytes.length, rightBytes.length);
5588
+ for (let index = 0; index < length; index += 1) {
5589
+ const difference = leftBytes[index]! - rightBytes[index]!;
5590
+ if (difference !== 0) return difference;
5591
+ }
5592
+ return leftBytes.length - rightBytes.length;
5593
+ }
5594
+
5595
+ export function normalizeSessionGoalRootConstraints(values: readonly string[]): string[] {
5596
+ // PostgreSQL btrim(text) removes U+0020 at both ends. Keep the public
5597
+ // projector byte-equivalent with the storage trigger instead of using
5598
+ // JavaScript trim(), whose Unicode whitespace set is broader.
5599
+ return [...new Set(values.map((value) => value.replace(/^ +| +$/g, "")))].sort(compareUtf8);
5600
+ }
5601
+
5602
+ export const SessionGoalRootConstraintsWrite = z
5603
+ .array(boundedSessionGoalString(SESSION_GOAL_ROOT_CONSTRAINT_MAX_BYTES, "goal root constraint"))
5604
+ .transform(normalizeSessionGoalRootConstraints)
5605
+ .pipe(
5606
+ z
5607
+ .array(z.string().min(1))
5608
+ .max(SESSION_GOAL_ROOT_CONSTRAINTS_MAX_ITEMS)
5609
+ .refine(
5610
+ (values) =>
5611
+ values.reduce((total, value) => total + sessionGoalUtf8Bytes(value), 0) <=
5612
+ SESSION_GOAL_ROOT_CONSTRAINTS_MAX_BYTES,
5613
+ {
5614
+ message: `goal root constraints exceed ${SESSION_GOAL_ROOT_CONSTRAINTS_MAX_BYTES} aggregate UTF-8 bytes`,
5615
+ },
5616
+ ),
5617
+ );
5618
+ export type SessionGoalRootConstraintsWrite = z.infer<typeof SessionGoalRootConstraintsWrite>;
5619
+
4655
5620
  const SessionGoalTextWrite = boundedSessionGoalString(SESSION_GOAL_TEXT_MAX_BYTES, "goal text");
4656
5621
  const SessionGoalSuccessCriteriaWrite = boundedSessionGoalString(
4657
5622
  SESSION_GOAL_SUCCESS_CRITERIA_MAX_BYTES,
@@ -4670,6 +5635,7 @@ export const SessionGoalSnapshot = z.discriminatedUnion("state", [
4670
5635
  objectiveRevision: z.number().int().positive(),
4671
5636
  text: SessionGoalTextWrite,
4672
5637
  successCriteria: SessionGoalSuccessCriteriaWrite.nullable(),
5638
+ rootConstraints: SessionGoalRootConstraintsWrite.default([]),
4673
5639
  mutationPolicy: SessionGoalMutationPolicy,
4674
5640
  capturedAt: z.string(),
4675
5641
  }),
@@ -4688,22 +5654,57 @@ export const SessionGoalRevision = z.object({
4688
5654
  resultObjectiveRevision: z.number().int().positive().nullable(),
4689
5655
  text: z.string().min(1),
4690
5656
  successCriteria: z.string().nullable(),
5657
+ rootConstraints: z.array(z.string().min(1)).default([]),
4691
5658
  mutationPolicy: SessionGoalMutationPolicy,
4692
5659
  rationale: z.string().min(1),
4693
5660
  actor: z.enum(["agent", "api", "scheduled_task"]),
4694
5661
  actorTurnId: z.string().uuid().nullable(),
4695
5662
  actorAttemptId: z.string().uuid().nullable(),
4696
5663
  proposalId: z.string().uuid().nullable(),
5664
+ rollbackOfRevisionId: z.string().uuid().nullable(),
4697
5665
  createdAt: z.string(),
4698
5666
  });
4699
5667
  export type SessionGoalRevision = z.infer<typeof SessionGoalRevision>;
4700
5668
 
5669
+ export const SESSION_GOAL_REVISION_LIST_DEFAULT_LIMIT = 50;
5670
+ export const SESSION_GOAL_REVISION_LIST_MAX_LIMIT = 100;
5671
+
5672
+ export const ListSessionGoalRevisionsQuery = z.object({
5673
+ limit: z.coerce
5674
+ .number()
5675
+ .int()
5676
+ .positive()
5677
+ .max(SESSION_GOAL_REVISION_LIST_MAX_LIMIT)
5678
+ .default(SESSION_GOAL_REVISION_LIST_DEFAULT_LIMIT),
5679
+ before: z.string().uuid().optional(),
5680
+ });
5681
+ export type ListSessionGoalRevisionsQuery = z.infer<typeof ListSessionGoalRevisionsQuery>;
5682
+
5683
+ export const ListSessionGoalRevisionsResponse = z.object({
5684
+ revisions: z.array(SessionGoalRevision),
5685
+ hasMore: z.boolean(),
5686
+ nextCursor: z.string().uuid().nullable(),
5687
+ });
5688
+ export type ListSessionGoalRevisionsResponse = z.infer<typeof ListSessionGoalRevisionsResponse>;
5689
+
4701
5690
  export const ApplySessionGoalRevisionRequest = z.object({
4702
5691
  expectedObjectiveRevision: z.number().int().positive(),
4703
5692
  rationale: SessionGoalRationaleWrite.optional(),
4704
5693
  });
4705
5694
  export type ApplySessionGoalRevisionRequest = z.infer<typeof ApplySessionGoalRevisionRequest>;
4706
5695
 
5696
+ export const RejectSessionGoalRevisionRequest = z.object({
5697
+ expectedObjectiveRevision: z.number().int().positive(),
5698
+ rationale: SessionGoalRationaleWrite,
5699
+ });
5700
+ export type RejectSessionGoalRevisionRequest = z.infer<typeof RejectSessionGoalRevisionRequest>;
5701
+
5702
+ export const RollbackSessionGoalRevisionRequest = z.object({
5703
+ expectedObjectiveRevision: z.number().int().positive(),
5704
+ rationale: SessionGoalRationaleWrite,
5705
+ });
5706
+ export type RollbackSessionGoalRevisionRequest = z.infer<typeof RollbackSessionGoalRevisionRequest>;
5707
+
4707
5708
  export const SessionGoalPausedReason = z.enum([
4708
5709
  "agent",
4709
5710
  "user_pause",
@@ -4735,10 +5736,41 @@ export const SessionGoalContinuationReason = z.enum([
4735
5736
  "provider_backpressure",
4736
5737
  "session_cancelled",
4737
5738
  "system_work_pending",
5739
+ "held_for_input",
5740
+ // Idle backoff: consecutive no-input continuations are paced, and the next
5741
+ // evaluation is armed as a delayed workflow wake at `nextAttemptAt`.
5742
+ "backoff_pending",
4738
5743
  "missing_obligation",
4739
5744
  ]);
4740
5745
  export type SessionGoalContinuationReason = z.infer<typeof SessionGoalContinuationReason>;
4741
5746
 
5747
+ /**
5748
+ * Why a paused goal became active again. `api` is the operator PATCH; the
5749
+ * system resumes only a `max_auto_continuations` pause, and only because new
5750
+ * external input arrived: a child result, scheduled occurrence, media result,
5751
+ * Agent message, Agent Steer, or human/API Send/Steer. The cap is pacing,
5752
+ * never user intent, so a `user_pause`/`api`/`agent`/`limits` pause is never
5753
+ * auto-resumed.
5754
+ */
5755
+ export const SessionGoalResumedReason = z.enum(["api", "external_input"]);
5756
+ export type SessionGoalResumedReason = z.infer<typeof SessionGoalResumedReason>;
5757
+
5758
+ export const SessionGoalResumedEventPayload = z
5759
+ .object({
5760
+ goalId: z.string().uuid(),
5761
+ actor: z.enum(["api", "system"]),
5762
+ reason: SessionGoalResumedReason,
5763
+ cause: z
5764
+ .object({
5765
+ kind: z.string().min(1),
5766
+ updateId: z.string().uuid().optional(),
5767
+ turnId: z.string().uuid().optional(),
5768
+ })
5769
+ .optional(),
5770
+ })
5771
+ .passthrough();
5772
+ export type SessionGoalResumedEventPayload = z.infer<typeof SessionGoalResumedEventPayload>;
5773
+
4742
5774
  export const SessionGoalContinuation = z.object({
4743
5775
  state: SessionGoalContinuationState,
4744
5776
  reason: SessionGoalContinuationReason,
@@ -4746,6 +5778,12 @@ export const SessionGoalContinuation = z.object({
4746
5778
  observedRevision: z.number().int().nonnegative(),
4747
5779
  nextAttemptAt: z.string().datetime({ offset: true }).nullable(),
4748
5780
  lastError: z.string().nullable(),
5781
+ /**
5782
+ * The agent's stated reason for a `held_for_input` hold (`goal_wait`), so a
5783
+ * human can see why the goal is waiting and until when (`nextAttemptAt`).
5784
+ * Null for every other state; omitted by older servers.
5785
+ */
5786
+ holdReason: z.string().nullable().optional(),
4749
5787
  });
4750
5788
  export type SessionGoalContinuation = z.infer<typeof SessionGoalContinuation>;
4751
5789
 
@@ -4757,6 +5795,7 @@ export const SessionGoal = z.object({
4757
5795
  status: SessionGoalStatus,
4758
5796
  text: z.string(),
4759
5797
  successCriteria: z.string().nullable(),
5798
+ rootConstraints: z.array(z.string().min(1)).default([]),
4760
5799
  evidence: z.string().nullable(),
4761
5800
  rationale: z.string().nullable(),
4762
5801
  pausedReason: z.string().nullable(),
@@ -4779,6 +5818,7 @@ export type SessionGoal = z.infer<typeof SessionGoal>;
4779
5818
  export const GoalSpec = z.object({
4780
5819
  text: SessionGoalTextWrite,
4781
5820
  successCriteria: SessionGoalSuccessCriteriaWrite.optional(),
5821
+ rootConstraints: SessionGoalRootConstraintsWrite.optional(),
4782
5822
  maxAutoContinuations: z.number().int().positive().optional(),
4783
5823
  mutationPolicy: SessionGoalMutationPolicy.optional(),
4784
5824
  });
@@ -4792,6 +5832,7 @@ export const UpdateSessionGoalRequest = z.union([
4792
5832
  z.object({
4793
5833
  text: SessionGoalTextWrite,
4794
5834
  successCriteria: SessionGoalSuccessCriteriaWrite.nullable().optional(),
5835
+ rootConstraints: SessionGoalRootConstraintsWrite.optional(),
4795
5836
  mutationPolicy: SessionGoalMutationPolicy.optional(),
4796
5837
  rationale: SessionGoalRationaleWrite,
4797
5838
  expectedObjectiveRevision: z.number().int().positive(),
@@ -4839,6 +5880,40 @@ export const UpdateSessionPinRequest = z.object({
4839
5880
  });
4840
5881
  export type UpdateSessionPinRequest = z.infer<typeof UpdateSessionPinRequest>;
4841
5882
 
5883
+ /**
5884
+ * A member's durable follow-up state for one session. Reading the session does
5885
+ * not acknowledge it: `unread` changes only through this explicit mutation.
5886
+ * A foreground reader may provide `acknowledgedThroughSequence` with
5887
+ * `unread: false` so later server events remain unread instead of being
5888
+ * consumed by a delayed acknowledgement request.
5889
+ * `activelyWorking` is an independent personal label that survives read state.
5890
+ */
5891
+ export const UpdateSessionAttentionRequest = z
5892
+ .object({
5893
+ unread: z.boolean().optional(),
5894
+ acknowledgedThroughSequence: z.number().int().nonnegative().optional(),
5895
+ activelyWorking: z.boolean().optional(),
5896
+ expectedVersion: z.number().int().nonnegative().optional(),
5897
+ })
5898
+ .strict()
5899
+ .refine((value) => value.unread !== undefined || value.activelyWorking !== undefined, {
5900
+ message: "unread or activelyWorking is required",
5901
+ })
5902
+ .refine((value) => value.acknowledgedThroughSequence === undefined || value.unread === false, {
5903
+ message: "acknowledgedThroughSequence requires unread false",
5904
+ path: ["acknowledgedThroughSequence"],
5905
+ });
5906
+ export type UpdateSessionAttentionRequest = z.infer<typeof UpdateSessionAttentionRequest>;
5907
+
5908
+ /** A member's personal archive state for a root chat. */
5909
+ export const UpdateSessionArchiveRequest = z
5910
+ .object({
5911
+ archived: z.boolean(),
5912
+ expectedVersion: z.number().int().nonnegative().optional(),
5913
+ })
5914
+ .strict();
5915
+ export type UpdateSessionArchiveRequest = z.infer<typeof UpdateSessionArchiveRequest>;
5916
+
4842
5917
  // Operator context controls (slash-command palette: /clear, /compact). These
4843
5918
  // are session/operator actions, NOT a structured way to talk to the agent —
4844
5919
  // the human↔agent channel stays plain chat. Both require `sessions:control`.
@@ -4893,6 +5968,34 @@ export function isClearedRunStateBlob(serialized: string | null | undefined): bo
4893
5968
  }
4894
5969
  }
4895
5970
 
5971
+ /**
5972
+ * Sentinel written on a requires_action pause. Resume must not call
5973
+ * `RunState.fromString` on it; the open suffix on `session_pending_tool_calls`
5974
+ * plus paired history is the resume authority.
5975
+ */
5976
+ export const OPEN_SUFFIX_RUN_STATE_MARKER = "$opengeniOpenSuffix" as const;
5977
+
5978
+ /** Canonical sentinel serializedRunState for a requires_action pause. */
5979
+ export const OPEN_SUFFIX_RUN_STATE_BLOB = JSON.stringify({
5980
+ [OPEN_SUFFIX_RUN_STATE_MARKER]: true,
5981
+ });
5982
+
5983
+ export function isOpenSuffixRunStateBlob(serialized: string | null | undefined): boolean {
5984
+ if (!serialized) {
5985
+ return false;
5986
+ }
5987
+ try {
5988
+ const parsed = JSON.parse(serialized) as unknown;
5989
+ return (
5990
+ typeof parsed === "object" &&
5991
+ parsed !== null &&
5992
+ (parsed as Record<string, unknown>)[OPEN_SUFFIX_RUN_STATE_MARKER] === true
5993
+ );
5994
+ } catch {
5995
+ return false;
5996
+ }
5997
+ }
5998
+
4896
5999
  /** Trigger conversation compaction now. No body fields today (forward-room). */
4897
6000
  export const CompactSessionContextRequest = z.object({}).strict();
4898
6001
  export type CompactSessionContextRequest = z.infer<typeof CompactSessionContextRequest>;
@@ -5174,7 +6277,12 @@ export type WorkspaceRealtimeModelCatalogResponse = z.infer<
5174
6277
  export const SessionRealtimeState = z.enum(["active", "ended"]);
5175
6278
  export type SessionRealtimeState = z.infer<typeof SessionRealtimeState>;
5176
6279
 
5177
- export const SessionRealtimeEndReason = z.enum(["user_stop", "browser_unload", "lease_expired"]);
6280
+ export const SessionRealtimeEndReason = z.enum([
6281
+ "user_stop",
6282
+ "browser_unload",
6283
+ "lease_expired",
6284
+ "authority_revoked",
6285
+ ]);
5178
6286
  export type SessionRealtimeEndReason = z.infer<typeof SessionRealtimeEndReason>;
5179
6287
 
5180
6288
  export const SessionRealtimeMode = z.object({
@@ -5242,6 +6350,9 @@ export const SessionAuthorizationOperation = z.enum([
5242
6350
  "session.secret.read",
5243
6351
  "session.codemode.call",
5244
6352
  "session.pin.write",
6353
+ "session.attention.write",
6354
+ "session.archive.write",
6355
+ "session.delete",
5245
6356
  "session.codex_account.write",
5246
6357
  "session.realtime.start",
5247
6358
  "session.realtime.control",
@@ -5258,6 +6369,7 @@ export const SessionAuthorizationOperation = z.enum([
5258
6369
  "session.child.create",
5259
6370
  "session.visibility.write",
5260
6371
  "session.fork.create",
6372
+ "session.personal_resource.grant",
5261
6373
  ]);
5262
6374
  export type SessionAuthorizationOperation = z.infer<typeof SessionAuthorizationOperation>;
5263
6375
 
@@ -5581,23 +6693,64 @@ export function renderTimelineAnnotationsForModel(
5581
6693
  }
5582
6694
 
5583
6695
  export const MODEL_CONTEXT_LABEL = "[Application context attached to this user message]" as const;
6696
+ export const SESSION_GOAL_CONTEXT_LABEL =
6697
+ "[Session goal frozen when this turn was accepted]" as const;
6698
+
6699
+ /**
6700
+ * Render the exact goal authority frozen with one accepted logical turn. Goal
6701
+ * state belongs at the chronological input boundary, not in the mutable
6702
+ * Agent.instructions prefix.
6703
+ */
6704
+ export function renderSessionGoalContext(snapshot?: SessionGoalSnapshot): string | undefined {
6705
+ if (!snapshot || snapshot.state === "none") return undefined;
6706
+ const rootConstraints = snapshot.rootConstraints.length
6707
+ ? `\nRoot constraints (must remain satisfied):\n${snapshot.rootConstraints.map((constraint) => `- ${constraint}`).join("\n")}`
6708
+ : "";
6709
+ if (snapshot.state === "completed") {
6710
+ return `Previous session goal (frozen at logical-turn acceptance; objective revision ${snapshot.objectiveRevision}; status completed): ${snapshot.text}\nSuccess criteria: ${snapshot.successCriteria ?? "none specified"}.${rootConstraints} This goal is complete and remains as historical context. If the user provides a new long-running objective, create it with opengeni__goal_set; goal_update cannot revise a completed goal.`;
6711
+ }
6712
+ const policy =
6713
+ snapshot.mutationPolicy === "review_changes"
6714
+ ? "Semantic changes are proposals until a user applies them."
6715
+ : snapshot.mutationPolicy === "preserve_intent"
6716
+ ? "You may directly refine wording without changing intent; adaptations and replacements are proposals until a user applies them."
6717
+ : "You may autonomously refine, adapt, or replace the goal when explicit user direction or material new evidence justifies it.";
6718
+ return `Standing session goal (frozen at logical-turn acceptance; objective revision ${snapshot.objectiveRevision}; status ${snapshot.state}): ${snapshot.text}\nSuccess criteria: ${snapshot.successCriteria ?? "none specified"}.${rootConstraints}\nMutation policy: ${snapshot.mutationPolicy}. ${policy} Treat later ordinary messages as additional context unless they explicitly redirect this objective. Root constraints are user/API authority and cannot be widened, removed, or rewritten by an agent. Semantic goal changes use opengeni__goal_update with the expected objective revision, change kind, and rationale.`;
6719
+ }
5584
6720
 
5585
6721
  /**
5586
6722
  * Build one canonical user-role message body. `modelContext` is ordinary
5587
- * message content: it is model-visible in the same chronological position as
5588
- * the visible text, but presentation layers may omit it. It is not a secret or
5589
- * an instruction-authority boundary.
6723
+ * message content, while `goalSnapshot` is the exact goal authority frozen at
6724
+ * turn acceptance. Both remain in the visible message's chronological
6725
+ * position, and presentation layers may omit their leading parts.
5590
6726
  */
5591
6727
  export function renderUserMessageContentForModel(
5592
6728
  text: string,
5593
6729
  annotations: readonly TimelineAnnotation[],
5594
6730
  modelContext?: string | null,
6731
+ goalSnapshot?: SessionGoalSnapshot,
5595
6732
  ): string | Array<{ type: "input_text"; text: string }> {
5596
6733
  const visibleContent = renderTimelineAnnotationsForModel(text, annotations);
5597
6734
  const context = modelContext?.trim();
5598
- if (!context) return visibleContent;
6735
+ const goalContext = renderSessionGoalContext(goalSnapshot);
6736
+ if (!context && !goalContext) return visibleContent;
5599
6737
  return [
5600
- { type: "input_text", text: `${MODEL_CONTEXT_LABEL}\n${context}` },
6738
+ ...(goalContext
6739
+ ? [
6740
+ {
6741
+ type: "input_text" as const,
6742
+ text: `${SESSION_GOAL_CONTEXT_LABEL}\n${goalContext}`,
6743
+ },
6744
+ ]
6745
+ : []),
6746
+ ...(context
6747
+ ? [
6748
+ {
6749
+ type: "input_text" as const,
6750
+ text: `${MODEL_CONTEXT_LABEL}\n${context}`,
6751
+ },
6752
+ ]
6753
+ : []),
5601
6754
  { type: "input_text", text: visibleContent },
5602
6755
  ];
5603
6756
  }
@@ -5622,7 +6775,7 @@ export const SessionTurn = z
5622
6775
  toolsProvided: z.boolean().optional(),
5623
6776
  model: z.string().min(1),
5624
6777
  reasoningEffort: ReasoningEffort,
5625
- latencyMode: LatencyMode.default("standard"),
6778
+ latencyMode: LatencyMode,
5626
6779
  sandboxBackend: SandboxBackend,
5627
6780
  // Per-turn OS override. NULL = inherit the session's sandboxOs.
5628
6781
  sandboxOs: SandboxOs.nullable(),
@@ -5635,6 +6788,8 @@ export const SessionTurn = z
5635
6788
  initiatorContext: TurnInitiatorContext,
5636
6789
  /** Secret-safe projection of the exact personal authority frozen on this turn. */
5637
6790
  personalConnections: z.array(McpPersonalConnectionSummary).default([]),
6791
+ /** Safe summary only; opaque resource/grant identity remains private. */
6792
+ personalResources: PersonalResourceAttachmentSummary.nullable().default(null),
5638
6793
  cancelledBy: z.string().nullable(),
5639
6794
  cancelReason: z.string().nullable(),
5640
6795
  startedAt: z.string().nullable(),
@@ -5696,10 +6851,18 @@ export const EffectiveSessionControl = z.object({
5696
6851
  quiescencePendingCount: z.number().int().nonnegative(),
5697
6852
  })
5698
6853
  .nullable(),
6854
+ /** Independent command-cancellation settlement. Session pause/turn state is
6855
+ * intentionally not overloaded with process lifecycle. */
6856
+ backgroundCommandSettlement: z
6857
+ .object({
6858
+ state: z.literal("stopping"),
6859
+ commandCount: z.number().int().positive(),
6860
+ })
6861
+ .nullable()
6862
+ .optional(),
5699
6863
  });
5700
6864
  export type EffectiveSessionControl = z.infer<typeof EffectiveSessionControl>;
5701
6865
 
5702
- export const SESSION_OPERATION_KEY_MAX_CHARS = 256;
5703
6866
  const SessionOperationKey = z.string().min(1).max(SESSION_OPERATION_KEY_MAX_CHARS);
5704
6867
 
5705
6868
  export const SessionCommandReceipt = z.object({
@@ -5716,6 +6879,14 @@ export const SessionCommandReceipt = z.object({
5716
6879
  });
5717
6880
  export type SessionCommandReceipt = z.infer<typeof SessionCommandReceipt>;
5718
6881
 
6882
+ /** The server-owned destination of an accepted human prompt at admission time. */
6883
+ export const SessionPromptRouting = z.enum([
6884
+ "accepted_for_execution",
6885
+ "queued_for_execution",
6886
+ "accepted_for_steering",
6887
+ ]);
6888
+ export type SessionPromptRouting = z.infer<typeof SessionPromptRouting>;
6889
+
5719
6890
  export const ComposerDraft = z.object({
5720
6891
  revision: z.number().int().nonnegative(),
5721
6892
  text: z.string(),
@@ -5723,7 +6894,7 @@ export const ComposerDraft = z.object({
5723
6894
  resources: z.array(ResourceRef),
5724
6895
  model: z.string().min(1),
5725
6896
  reasoningEffort: ReasoningEffort,
5726
- latencyMode: LatencyMode.default("standard"),
6897
+ latencyMode: LatencyMode,
5727
6898
  sourceTurnId: z.string().uuid().nullable(),
5728
6899
  sourceTurnVersion: z.number().int().positive().nullable(),
5729
6900
  updatedAt: z.string().nullable(),
@@ -5770,13 +6941,40 @@ export const SaveComposerDraftRequest = ComposerDraft.pick({
5770
6941
  export type SaveComposerDraftRequest = z.infer<typeof SaveComposerDraftRequest>;
5771
6942
 
5772
6943
  /**
5773
- * Create-only options saved with an actor's private pre-session draft. This is
5774
- * deliberately narrower than CreateSessionRequest: idempotency/event keys and
5775
- * credential-bearing MCP server inputs are per-attempt data, never draft state.
6944
+ * Submit one exact established-session draft. Content is repeated only as an
6945
+ * integrity fence for outcome-unknown idempotent replay; the matching durable
6946
+ * draft revision remains authoritative and is atomically rotated on acceptance.
5776
6947
  */
5777
- export const NewSessionDraftOptions = z.object({
5778
- sandboxBackend: SandboxBackend.optional(),
5779
- targetSandboxId: z.string().uuid().optional(),
6948
+ export const SubmitComposerDraftRequest = ComposerDraft.pick({
6949
+ text: true,
6950
+ annotations: true,
6951
+ resources: true,
6952
+ model: true,
6953
+ reasoningEffort: true,
6954
+ latencyMode: true,
6955
+ })
6956
+ .extend({
6957
+ expectedDraftRevision: z.number().int().positive(),
6958
+ clientEventId: SessionOperationKey,
6959
+ delivery: z.enum(["send", "steer"]),
6960
+ controlEtag: z.string().min(1).optional(),
6961
+ modelContext: z.string().trim().min(1).max(32768).optional(),
6962
+ mcpCredentialUpdates: z.array(SessionMcpCredentialUpdateInput).optional(),
6963
+ connectionAuthorities: McpConnectionAuthoritySelections.default([]),
6964
+ personalResourceAttachment: PersonalResourceAttachmentIntent.optional(),
6965
+ })
6966
+ .superRefine(requireEstablishedPersonalResourceEpoch);
6967
+ export type SubmitComposerDraftRequest = z.infer<typeof SubmitComposerDraftRequest>;
6968
+
6969
+ /**
6970
+ * Create-only options saved with an actor's private pre-session draft. This is
6971
+ * deliberately narrower than CreateSessionRequest: idempotency/event keys and
6972
+ * credential-bearing MCP server inputs are per-attempt data, never draft state.
6973
+ */
6974
+ export const NewSessionDraftOptions = z.object({
6975
+ visibility: SessionVisibility.optional(),
6976
+ sandboxBackend: SandboxBackend.optional(),
6977
+ targetSandboxId: z.string().uuid().optional(),
5780
6978
  workingDir: z.string().min(1).optional(),
5781
6979
  variableSetId: z.string().uuid().optional(),
5782
6980
  rigId: z.string().uuid().optional(),
@@ -5786,6 +6984,31 @@ export const NewSessionDraftOptions = z.object({
5786
6984
  });
5787
6985
  export type NewSessionDraftOptions = z.infer<typeof NewSessionDraftOptions>;
5788
6986
 
6987
+ /**
6988
+ * Actor-private successful-create history for the new-session composer. Project
6989
+ * entries and their machine entries are MRU ordered. A null target means the
6990
+ * managed sandbox; a null working directory means the machine's launch root.
6991
+ */
6992
+ export const NewSessionSelectionHistory = z.object({
6993
+ projects: z
6994
+ .array(
6995
+ z.object({
6996
+ channelId: z.string().uuid().nullable(),
6997
+ targetSandboxId: z.string().uuid().nullable(),
6998
+ machines: z
6999
+ .array(
7000
+ z.object({
7001
+ sandboxId: z.string().uuid(),
7002
+ workingDir: z.string().min(1).max(4096).nullable(),
7003
+ }),
7004
+ )
7005
+ .max(20),
7006
+ }),
7007
+ )
7008
+ .max(50),
7009
+ });
7010
+ export type NewSessionSelectionHistory = z.infer<typeof NewSessionSelectionHistory>;
7011
+
5789
7012
  /** Actor-private, server-authoritative composer state before a session exists. */
5790
7013
  export const NewSessionDraft = z.object({
5791
7014
  revision: z.number().int().nonnegative(),
@@ -5796,8 +7019,9 @@ export const NewSessionDraft = z.object({
5796
7019
  toolsProvided: z.boolean().default(false),
5797
7020
  model: z.string().min(1),
5798
7021
  reasoningEffort: ReasoningEffort,
5799
- latencyMode: LatencyMode.default("standard"),
7022
+ latencyMode: LatencyMode,
5800
7023
  options: NewSessionDraftOptions,
7024
+ selectionHistory: NewSessionSelectionHistory.default({ projects: [] }),
5801
7025
  updatedAt: z.string().nullable(),
5802
7026
  });
5803
7027
  export type NewSessionDraft = z.infer<typeof NewSessionDraft>;
@@ -6036,9 +7260,180 @@ export const SessionSystemUpdateKind = z.enum([
6036
7260
  "agent_steer_instruction",
6037
7261
  "child_terminal_result",
6038
7262
  "media_generation_result",
7263
+ "child_requires_action",
7264
+ "child_requires_action_resolved",
7265
+ "child_paused",
7266
+ "child_waiting_capacity",
7267
+ "child_progress",
6039
7268
  ]);
6040
7269
  export type SessionSystemUpdateKind = z.infer<typeof SessionSystemUpdateKind>;
6041
7270
 
7271
+ /**
7272
+ * How a newly pending machine input affects an idle receiving session.
7273
+ * `immediate` registers a workflow wake in the same commit (the behaviour of
7274
+ * every pre-existing kind) and ends a `goal_wait` hold at the next idle
7275
+ * evaluation; `deferred` only inserts the durable pending row plus its
7276
+ * `system.update.pending` event and is delivered coalesced with the next claim.
7277
+ */
7278
+ export type SessionSystemUpdateWakeClass = "immediate" | "deferred";
7279
+
7280
+ export const SESSION_SYSTEM_UPDATE_WAKE_CLASS: Record<
7281
+ SessionSystemUpdateKind,
7282
+ SessionSystemUpdateWakeClass
7283
+ > = {
7284
+ scheduled_occurrence: "immediate",
7285
+ goal_continuation: "immediate",
7286
+ agent_message: "immediate",
7287
+ agent_steer_instruction: "immediate",
7288
+ child_terminal_result: "immediate",
7289
+ media_generation_result: "immediate",
7290
+ child_requires_action: "immediate",
7291
+ child_requires_action_resolved: "deferred",
7292
+ child_paused: "deferred",
7293
+ child_waiting_capacity: "deferred",
7294
+ child_progress: "deferred",
7295
+ };
7296
+
7297
+ /**
7298
+ * Kinds a child session's lifecycle produces for its parent. Every one of them
7299
+ * travels through `session_system_update_outbox`, and none of them may
7300
+ * autonomously wake a parent whose goal is not active.
7301
+ */
7302
+ export const CHILD_LIFECYCLE_SYSTEM_UPDATE_KINDS = [
7303
+ "child_terminal_result",
7304
+ "child_requires_action",
7305
+ "child_requires_action_resolved",
7306
+ "child_paused",
7307
+ "child_waiting_capacity",
7308
+ "child_progress",
7309
+ ] as const satisfies readonly SessionSystemUpdateKind[];
7310
+ export type ChildLifecycleSystemUpdateKind = (typeof CHILD_LIFECYCLE_SYSTEM_UPDATE_KINDS)[number];
7311
+
7312
+ const CHILD_LIFECYCLE_SYSTEM_UPDATE_KIND_SET: ReadonlySet<string> = new Set(
7313
+ CHILD_LIFECYCLE_SYSTEM_UPDATE_KINDS,
7314
+ );
7315
+
7316
+ export function isChildLifecycleSystemUpdateKind(
7317
+ kind: string,
7318
+ ): kind is ChildLifecycleSystemUpdateKind {
7319
+ return CHILD_LIFECYCLE_SYSTEM_UPDATE_KIND_SET.has(kind);
7320
+ }
7321
+
7322
+ /** A child_paused notice requested by a human/API is action-required; an agent pause is informational. */
7323
+ export function childPausedClassification(
7324
+ actorKind: "human" | "api" | "agent",
7325
+ ): "action_required" | "info" {
7326
+ return actorKind === "agent" ? "info" : "action_required";
7327
+ }
7328
+
7329
+ /** Whole child_requires_action payload bound (UTF-8 JSON bytes). */
7330
+ export const CHILD_REQUIRES_ACTION_PAYLOAD_MAX_BYTES = 8 * 1024;
7331
+ /** Bounded first-question preview inside a child_requires_action notice. */
7332
+ export const CHILD_REQUIRES_ACTION_QUESTION_PREVIEW_MAX_BYTES = 512;
7333
+ export const CHILD_REQUIRES_ACTION_MAX_REQUESTS = 20;
7334
+ export const CHILD_PAUSED_REASON_MAX_BYTES = 2 * 1024;
7335
+ export const CHILD_PROGRESS_NOTE_MAX_BYTES = 4 * 1024;
7336
+
7337
+ const boundedUtf8String = (maxBytes: number) =>
7338
+ z.string().refine((value) => new TextEncoder().encode(value).byteLength <= maxBytes, {
7339
+ message: `must be at most ${maxBytes} UTF-8 bytes`,
7340
+ });
7341
+
7342
+ export const ChildRequiresActionRequest = z.discriminatedUnion("kind", [
7343
+ z.object({
7344
+ kind: z.literal("human_input"),
7345
+ requestId: z.string().uuid(),
7346
+ questionCount: z.number().int().nonnegative(),
7347
+ firstQuestion: boundedUtf8String(CHILD_REQUIRES_ACTION_QUESTION_PREVIEW_MAX_BYTES),
7348
+ allowSkip: z.boolean(),
7349
+ expiresAt: z.string().nullable(),
7350
+ }),
7351
+ z.object({
7352
+ kind: z.literal("approval"),
7353
+ approvalId: boundedUtf8String(256),
7354
+ toolName: boundedUtf8String(128).nullable(),
7355
+ }),
7356
+ ]);
7357
+ export type ChildRequiresActionRequest = z.infer<typeof ChildRequiresActionRequest>;
7358
+
7359
+ export const ChildRequiresActionPayload = z
7360
+ .object({
7361
+ type: z.literal("child_requires_action"),
7362
+ childSessionId: z.string().uuid(),
7363
+ childTurnId: z.string().uuid(),
7364
+ childTurnGeneration: z.number().int().positive(),
7365
+ requests: z.array(ChildRequiresActionRequest).max(CHILD_REQUIRES_ACTION_MAX_REQUESTS),
7366
+ truncated: z.boolean(),
7367
+ })
7368
+ .passthrough();
7369
+ export type ChildRequiresActionPayload = z.infer<typeof ChildRequiresActionPayload>;
7370
+
7371
+ export const ChildRequiresActionResolvedOutcome = z.enum([
7372
+ "answered",
7373
+ "skipped",
7374
+ "expired",
7375
+ "cancelled",
7376
+ "approved",
7377
+ "rejected",
7378
+ ]);
7379
+ export type ChildRequiresActionResolvedOutcome = z.infer<typeof ChildRequiresActionResolvedOutcome>;
7380
+
7381
+ export const ChildRequiresActionRespondedByKind = z.enum([
7382
+ "human",
7383
+ "api",
7384
+ "agent_attempt",
7385
+ "system",
7386
+ ]);
7387
+ export type ChildRequiresActionRespondedByKind = z.infer<typeof ChildRequiresActionRespondedByKind>;
7388
+
7389
+ export const ChildRequiresActionResolvedPayload = z
7390
+ .object({
7391
+ type: z.literal("child_requires_action_resolved"),
7392
+ childSessionId: z.string().uuid(),
7393
+ childTurnId: z.string().uuid(),
7394
+ childTurnGeneration: z.number().int().positive(),
7395
+ requestId: z.string().uuid().nullable(),
7396
+ approvalId: boundedUtf8String(256).nullable(),
7397
+ outcome: ChildRequiresActionResolvedOutcome,
7398
+ respondedByKind: ChildRequiresActionRespondedByKind,
7399
+ })
7400
+ .passthrough();
7401
+ export type ChildRequiresActionResolvedPayload = z.infer<typeof ChildRequiresActionResolvedPayload>;
7402
+
7403
+ export const ChildPausedPayload = z
7404
+ .object({
7405
+ type: z.literal("child_paused"),
7406
+ childSessionId: z.string().uuid(),
7407
+ operationId: z.string().uuid(),
7408
+ actorKind: z.enum(["human", "api", "agent"]),
7409
+ reason: boundedUtf8String(CHILD_PAUSED_REASON_MAX_BYTES).nullable(),
7410
+ })
7411
+ .passthrough();
7412
+ export type ChildPausedPayload = z.infer<typeof ChildPausedPayload>;
7413
+
7414
+ export const ChildWaitingCapacityPayload = z
7415
+ .object({
7416
+ type: z.literal("child_waiting_capacity"),
7417
+ childSessionId: z.string().uuid(),
7418
+ childTurnId: z.string().uuid(),
7419
+ provider: z.enum(["codex", "xai"]),
7420
+ nextCheckAt: z.string().nullable(),
7421
+ })
7422
+ .passthrough();
7423
+ export type ChildWaitingCapacityPayload = z.infer<typeof ChildWaitingCapacityPayload>;
7424
+
7425
+ export const ChildProgressPayload = z
7426
+ .object({
7427
+ type: z.literal("child_progress"),
7428
+ childSessionId: z.string().uuid(),
7429
+ goalId: z.string().uuid(),
7430
+ objectiveRevision: z.number().int().nonnegative(),
7431
+ operationId: z.string().uuid(),
7432
+ progressNote: boundedUtf8String(CHILD_PROGRESS_NOTE_MAX_BYTES),
7433
+ })
7434
+ .passthrough();
7435
+ export type ChildProgressPayload = z.infer<typeof ChildProgressPayload>;
7436
+
6042
7437
  export const SessionSystemUpdatePayload = z.discriminatedUnion("type", [
6043
7438
  z
6044
7439
  .object({
@@ -6081,6 +7476,11 @@ export const SessionSystemUpdatePayload = z.discriminatedUnion("type", [
6081
7476
  })
6082
7477
  .passthrough(),
6083
7478
  MediaGenerationResult,
7479
+ ChildRequiresActionPayload,
7480
+ ChildRequiresActionResolvedPayload,
7481
+ ChildPausedPayload,
7482
+ ChildWaitingCapacityPayload,
7483
+ ChildProgressPayload,
6084
7484
  ]);
6085
7485
  export type SessionSystemUpdatePayload = z.infer<typeof SessionSystemUpdatePayload>;
6086
7486
 
@@ -6197,11 +7597,16 @@ export function renderSessionSystemUpdateBatch(
6197
7597
 
6198
7598
  export function sessionSystemUpdateBatchHistoryItem(
6199
7599
  updates: Parameters<typeof renderSessionSystemUpdateBatch>[0],
7600
+ goalSnapshot?: SessionGoalSnapshot,
6200
7601
  ): { type: "message"; role: "system"; content: string } {
7602
+ const goalContext = renderSessionGoalContext(goalSnapshot);
6201
7603
  return {
6202
7604
  type: "message",
6203
7605
  role: "system",
6204
- content: renderSessionSystemUpdateBatch(updates),
7606
+ content: [
7607
+ ...(goalContext ? [`${SESSION_GOAL_CONTEXT_LABEL}\n${goalContext}`] : []),
7608
+ renderSessionSystemUpdateBatch(updates),
7609
+ ].join("\n\n"),
6205
7610
  };
6206
7611
  }
6207
7612
 
@@ -6251,10 +7656,16 @@ export const VariableSetSecret = z.object({
6251
7656
  });
6252
7657
  export type VariableSetSecret = z.infer<typeof VariableSetSecret>;
6253
7658
 
7659
+ export const VariableSetScope = z.enum(["organization", "workspace", "user"]);
7660
+ export type VariableSetScope = z.infer<typeof VariableSetScope>;
7661
+
6254
7662
  export const VariableSet = z.object({
6255
7663
  id: z.string().uuid(),
6256
7664
  accountId: z.string().uuid(),
6257
7665
  workspaceId: z.string().uuid(),
7666
+ scope: VariableSetScope,
7667
+ generation: z.number().int().positive(),
7668
+ status: z.enum(["active", "revoked"]),
6258
7669
  name: z.string(),
6259
7670
  description: z.string().nullable(),
6260
7671
  variables: z.array(VariableSetVariableMetadata),
@@ -6268,6 +7679,8 @@ export const WorkspaceEnvironment = VariableSet;
6268
7679
  export type WorkspaceEnvironment = VariableSet;
6269
7680
 
6270
7681
  export const CreateVariableSetRequest = z.object({
7682
+ // Omitted remains the legacy workspace-owned creation path.
7683
+ scope: VariableSetScope.default("workspace"),
6271
7684
  name: z.string().min(1).max(120),
6272
7685
  description: z.string().max(2000).optional(),
6273
7686
  variables: z
@@ -6466,6 +7879,9 @@ export const Rig = z.object({
6466
7879
  id: z.string().uuid(),
6467
7880
  accountId: z.string().uuid(),
6468
7881
  workspaceId: z.string().uuid(),
7882
+ scope: ResourceAuthorityScope,
7883
+ generation: z.number().int().positive(),
7884
+ status: z.enum(["active", "revoked"]),
6469
7885
  name: z.string(),
6470
7886
  description: z.string().nullable(),
6471
7887
  createdBy: z.string().nullable(),
@@ -6524,6 +7940,8 @@ export const RigChange = z.object({
6524
7940
  export type RigChange = z.infer<typeof RigChange>;
6525
7941
 
6526
7942
  export const CreateRigRequest = z.object({
7943
+ // Omitted remains the compatibility workspace-owned creation path.
7944
+ scope: ResourceAuthorityScope.default("workspace"),
6527
7945
  name: z.string().min(1).max(120),
6528
7946
  description: z.string().max(2000).optional(),
6529
7947
  // Initial (version 1) content, inline.
@@ -6550,6 +7968,8 @@ export const Channel = z.object({
6550
7968
  workspaceId: z.string().uuid(),
6551
7969
  name: z.string(),
6552
7970
  description: z.string().nullable(),
7971
+ pinned: z.boolean(),
7972
+ sortOrder: z.number().int().nonnegative(),
6553
7973
  createdBy: z.string().nullable(),
6554
7974
  createdAt: z.string(),
6555
7975
  updatedAt: z.string(),
@@ -6565,9 +7985,16 @@ export type CreateChannelRequest = z.infer<typeof CreateChannelRequest>;
6565
7985
  export const UpdateChannelRequest = z.object({
6566
7986
  name: z.string().trim().min(1).max(80).optional(),
6567
7987
  description: z.string().max(2000).nullable().optional(),
7988
+ pinned: z.boolean().optional(),
6568
7989
  });
6569
7990
  export type UpdateChannelRequest = z.infer<typeof UpdateChannelRequest>;
6570
7991
 
7992
+ /** Complete workspace project order. It is replaced atomically after a drag. */
7993
+ export const ReorderChannelsRequest = z
7994
+ .object({ channelIds: z.array(z.string().uuid()).min(1).max(200) })
7995
+ .strict();
7996
+ export type ReorderChannelsRequest = z.infer<typeof ReorderChannelsRequest>;
7997
+
6571
7998
  // Re-files one session (rail organization only). null moves it back to the
6572
7999
  // unfiled inbox.
6573
8000
  export const UpdateSessionChannelRequest = z.object({
@@ -6733,25 +8160,313 @@ export const ScheduledTaskScheduleSpec = /* @__PURE__ */ z.discriminatedUnion("t
6733
8160
  ]);
6734
8161
  export type ScheduledTaskScheduleSpec = z.infer<typeof ScheduledTaskScheduleSpec>;
6735
8162
 
6736
- export const ScheduledTaskAgentConfig = /* @__PURE__ */ z.object({
6737
- prompt: z.string().min(1),
6738
- resources: z.array(ResourceRef).default([]),
6739
- tools: z.array(ToolRef).default([]),
6740
- metadata: z.record(z.string(), z.unknown()).default({}),
6741
- // Explicit workspace-shared OpenGeni Slack bot binding for scheduled runs.
6742
- // The worker copies this non-secret pointer into session metadata; the
6743
- // first-party Slack tools never fall back to a personal hosted-MCP grant.
6744
- slackBotConnectionId: z.string().uuid().optional(),
6745
- model: z.string().min(1).optional(),
6746
- reasoningEffort: ReasoningEffort.optional(),
6747
- sandboxBackend: SandboxBackend.optional(),
6748
- goal: GoalSpec.optional(),
6749
- // Durable task override. Scheduled dispatch is trusted to preserve this
6750
- // snapshot even if the workspace/deployment policy narrows later.
6751
- maxNestedAgentDepth: NestedAgentDepthValue.optional(),
6752
- });
8163
+ export const IncidentTelemetryExecutionClass = z.literal("incident_telemetry");
8164
+ export type IncidentTelemetryExecutionClass = z.infer<typeof IncidentTelemetryExecutionClass>;
8165
+
8166
+ const IncidentTelemetryIdentifier = z.string().trim().min(1).max(200);
8167
+ const IncidentTelemetryMetricName = z
8168
+ .string()
8169
+ .trim()
8170
+ .min(1)
8171
+ .max(200)
8172
+ .regex(/^[A-Za-z_:][A-Za-z0-9_:]*$/);
8173
+ const IncidentTelemetryLabelName = z
8174
+ .string()
8175
+ .trim()
8176
+ .min(1)
8177
+ .max(200)
8178
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/);
8179
+
8180
+ export const IncidentTelemetrySeriesMetadata = z
8181
+ .object({
8182
+ metric: IncidentTelemetryMetricName,
8183
+ labels: z.array(IncidentTelemetryLabelName).min(1).max(64),
8184
+ })
8185
+ .strict();
8186
+ export type IncidentTelemetrySeriesMetadata = z.infer<typeof IncidentTelemetrySeriesMetadata>;
8187
+
8188
+ export const IncidentTelemetryDataRoute = z.discriminatedUnion("kind", [
8189
+ z
8190
+ .object({
8191
+ kind: z.literal("mcp"),
8192
+ serverId: SessionMcpServerId,
8193
+ })
8194
+ .strict(),
8195
+ z
8196
+ .object({
8197
+ kind: z.literal("first_party"),
8198
+ tool: FirstPartyMcpToolName,
8199
+ })
8200
+ .strict(),
8201
+ z
8202
+ .object({
8203
+ kind: z.literal("variable_set"),
8204
+ variableSetName: z.string().trim().min(1).max(120),
8205
+ variableNames: z.array(VariableSetVariableName).min(1).max(100),
8206
+ })
8207
+ .strict(),
8208
+ z
8209
+ .object({
8210
+ kind: z.literal("rig_credential_hook"),
8211
+ credentialHookId: IncidentTelemetryIdentifier,
8212
+ })
8213
+ .strict(),
8214
+ ]);
8215
+ export type IncidentTelemetryDataRoute = z.infer<typeof IncidentTelemetryDataRoute>;
8216
+
8217
+ export const IncidentTelemetryPreflight = z
8218
+ .object({
8219
+ // These are exact selected resource refs, not discovery hints. The core
8220
+ // validator requires every entry to already be present in agentConfig.
8221
+ requiredResources: z.array(ResourceRef).max(100).default([]),
8222
+ requiredMcpServerIds: z.array(SessionMcpServerId).max(64).default([]),
8223
+ requiredFirstPartyMcpTools: z.array(FirstPartyMcpToolName).max(100).default([]),
8224
+ requiredFirstPartyMcpPermissions: z.array(Permission).max(100).default([]),
8225
+ requiredRig: z
8226
+ .object({
8227
+ name: z.string().trim().min(1).max(120),
8228
+ credentialHookIds: z.array(IncidentTelemetryIdentifier).max(50).default([]),
8229
+ })
8230
+ .strict()
8231
+ .nullable()
8232
+ .default(null),
8233
+ // Names only. The dispatch preflight never reads or decrypts values.
8234
+ requiredVariableSetNames: z.array(z.string().trim().min(1).max(120)).max(25).default([]),
8235
+ requiredVariableNames: z.array(VariableSetVariableName).max(100).default([]),
8236
+ dataSource: z
8237
+ .object({
8238
+ kind: z.literal("prometheus"),
8239
+ // Exposition endpoints such as /metrics are deliberately excluded.
8240
+ queryPath: z.enum(["/api/v1/query", "/api/v1/query_range"]),
8241
+ workspaceLabel: IncidentTelemetryLabelName,
8242
+ // Exact non-workspace labels whose values come from the validated
8243
+ // structured alert occurrence and bound every telemetry query.
8244
+ alertSelectorLabels: z.array(IncidentTelemetryLabelName).min(1).max(16),
8245
+ route: IncidentTelemetryDataRoute,
8246
+ requiredSeries: z.array(IncidentTelemetrySeriesMetadata).min(1).max(100),
8247
+ availableSeries: z.array(IncidentTelemetrySeriesMetadata).min(1).max(500),
8248
+ })
8249
+ .strict(),
8250
+ })
8251
+ .strict()
8252
+ .superRefine((value, context) => {
8253
+ for (const [index, label] of value.dataSource.alertSelectorLabels.entries()) {
8254
+ if (label === value.dataSource.workspaceLabel) {
8255
+ context.addIssue({
8256
+ code: "custom",
8257
+ path: ["dataSource", "alertSelectorLabels", index],
8258
+ message: "alert selector labels must be non-workspace labels",
8259
+ });
8260
+ }
8261
+ }
8262
+ for (const [index, series] of value.dataSource.requiredSeries.entries()) {
8263
+ if (!series.labels.includes(value.dataSource.workspaceLabel)) {
8264
+ context.addIssue({
8265
+ code: "custom",
8266
+ path: ["dataSource", "requiredSeries", index, "labels"],
8267
+ message: "required incident series must include the workspace label",
8268
+ });
8269
+ }
8270
+ for (const label of value.dataSource.alertSelectorLabels) {
8271
+ if (!series.labels.includes(label)) {
8272
+ context.addIssue({
8273
+ code: "custom",
8274
+ path: ["dataSource", "requiredSeries", index, "labels"],
8275
+ message: "required incident series must include every alert selector label",
8276
+ });
8277
+ }
8278
+ }
8279
+ }
8280
+ });
8281
+ export type IncidentTelemetryPreflight = z.infer<typeof IncidentTelemetryPreflight>;
8282
+
8283
+ /**
8284
+ * Canonical UTF-8 ingress limits for newly written scheduled execution truth.
8285
+ * They bound create/update requests and freshly accepted occurrence snapshots;
8286
+ * already-stored tasks are read back through the unbounded storage shape so a
8287
+ * legacy row can never become unreadable or undispatchable by a later cap.
8288
+ */
8289
+ export const SCHEDULED_TASK_NAME_MAX_BYTES = 512;
8290
+ export const SCHEDULED_TASK_PROMPT_MAX_BYTES = 64 * 1024;
8291
+ export const SCHEDULED_TASK_METADATA_MAX_BYTES = 32 * 1024;
8292
+ export const SCHEDULED_TASK_AGENT_CONFIG_MAX_BYTES = 128 * 1024;
8293
+ export const SCHEDULED_TASK_ACCEPTED_EXECUTION_MAX_BYTES = 512 * 1024;
8294
+ /**
8295
+ * A scheduled occurrence is delivered as one durable internal update whose
8296
+ * payload (`scheduled_occurrence` text + resources + tools + ids) must fit the
8297
+ * canonical internal-update bound. Bounding it here at ingress means every
8298
+ * stored task that passed validation can also be delivered.
8299
+ */
8300
+ export const SCHEDULED_TASK_OCCURRENCE_PAYLOAD_MAX_BYTES = 64 * 1024;
8301
+ /**
8302
+ * Ingress reserves headroom below that bound for what the worker adds to the
8303
+ * delivered payload (the first-party `opengeni` MCP tool ref and real ids), so
8304
+ * a request that passes validation can always be delivered.
8305
+ */
8306
+ export const SCHEDULED_TASK_OCCURRENCE_PAYLOAD_INGRESS_HEADROOM_BYTES = 1024;
8307
+ export const SCHEDULED_TASK_PRODUCER_KEY_MAX_BYTES = 512;
8308
+ export const SCHEDULED_TASK_RESOURCE_MAX_COUNT = 100;
8309
+ export const SCHEDULED_TASK_TOOL_MAX_COUNT = 128;
8310
+
8311
+ const scheduledTaskUtf8Encoder = new TextEncoder();
8312
+
8313
+ function scheduledTaskUtf8Bytes(value: string): number {
8314
+ return scheduledTaskUtf8Encoder.encode(value).byteLength;
8315
+ }
8316
+
8317
+ function scheduledTaskJsonUtf8Bytes(value: unknown): number {
8318
+ try {
8319
+ const encoded = JSON.stringify(value);
8320
+ return encoded === undefined ? Number.POSITIVE_INFINITY : scheduledTaskUtf8Bytes(encoded);
8321
+ } catch {
8322
+ return Number.POSITIVE_INFINITY;
8323
+ }
8324
+ }
8325
+
8326
+ /**
8327
+ * UTF-8 size of the exact `scheduled_occurrence` internal-update payload the
8328
+ * worker delivers for one occurrence of this agent config (ids use fixed-width
8329
+ * placeholder UUIDs). Shared by ingress validation and worker admission.
8330
+ */
8331
+ export function scheduledOccurrencePayloadUtf8Bytes(agentConfig: {
8332
+ prompt: string;
8333
+ resources?: readonly unknown[] | undefined;
8334
+ tools?: readonly unknown[] | undefined;
8335
+ }): number {
8336
+ const placeholder = "00000000-0000-4000-8000-000000000000";
8337
+ return scheduledTaskJsonUtf8Bytes({
8338
+ type: "scheduled_occurrence",
8339
+ text: agentConfig.prompt,
8340
+ scheduledTaskId: placeholder,
8341
+ scheduledTaskRunId: placeholder,
8342
+ ...(agentConfig.resources?.length ? { resources: agentConfig.resources } : {}),
8343
+ ...(agentConfig.tools?.length ? { tools: agentConfig.tools } : {}),
8344
+ });
8345
+ }
8346
+
8347
+ function scheduledTaskBoundedJsonObject(maxBytes: number, label: string) {
8348
+ return z.record(z.string(), z.unknown()).superRefine((value, context) => {
8349
+ if (scheduledTaskJsonUtf8Bytes(value) > maxBytes) {
8350
+ context.addIssue({
8351
+ code: "custom",
8352
+ message: `${label} exceeds ${maxBytes} UTF-8 bytes`,
8353
+ });
8354
+ }
8355
+ });
8356
+ }
8357
+
8358
+ function scheduledTaskBoundedString(maxBytes: number, label: string) {
8359
+ return z
8360
+ .string()
8361
+ .min(1)
8362
+ .superRefine((value, context) => {
8363
+ if (scheduledTaskUtf8Bytes(value) > maxBytes) {
8364
+ context.addIssue({
8365
+ code: "custom",
8366
+ message: `${label} exceeds ${maxBytes} UTF-8 bytes`,
8367
+ });
8368
+ }
8369
+ });
8370
+ }
8371
+
8372
+ /** Ingress-bounded task name for create/update requests. */
8373
+ export const ScheduledTaskNameInput = /* @__PURE__ */ scheduledTaskBoundedString(
8374
+ SCHEDULED_TASK_NAME_MAX_BYTES,
8375
+ "scheduled task name",
8376
+ );
8377
+ /** Ingress-bounded task metadata for create/update requests. */
8378
+ export const ScheduledTaskMetadataInput = /* @__PURE__ */ scheduledTaskBoundedJsonObject(
8379
+ SCHEDULED_TASK_METADATA_MAX_BYTES,
8380
+ "scheduled task metadata",
8381
+ );
8382
+
8383
+ function scheduledTaskAgentConfigShape(bounded: boolean) {
8384
+ return {
8385
+ prompt: bounded
8386
+ ? scheduledTaskBoundedString(SCHEDULED_TASK_PROMPT_MAX_BYTES, "scheduled task prompt")
8387
+ : z.string().min(1),
8388
+ resources: bounded
8389
+ ? z.array(ResourceRef).max(SCHEDULED_TASK_RESOURCE_MAX_COUNT).default([])
8390
+ : z.array(ResourceRef).default([]),
8391
+ tools: bounded
8392
+ ? z
8393
+ .array(ToolRef)
8394
+ .max(SCHEDULED_TASK_TOOL_MAX_COUNT - 1)
8395
+ .default([])
8396
+ : z.array(ToolRef).default([]),
8397
+ metadata: bounded
8398
+ ? ScheduledTaskMetadataInput.default({})
8399
+ : z.record(z.string(), z.unknown()).default({}),
8400
+ // Explicit workspace-shared OpenGeni Slack bot binding for scheduled runs.
8401
+ // The worker copies this non-secret pointer into session metadata; the
8402
+ // first-party Slack tools never fall back to a personal hosted-MCP grant.
8403
+ slackBotConnectionId: z.string().uuid().optional(),
8404
+ model: bounded
8405
+ ? scheduledTaskBoundedString(512, "scheduled task model").optional()
8406
+ : z.string().min(1).optional(),
8407
+ reasoningEffort: ReasoningEffort.optional(),
8408
+ sandboxBackend: SandboxBackend.optional(),
8409
+ goal: GoalSpec.optional(),
8410
+ // Incident telemetry is the only special execution class. Omission keeps
8411
+ // every existing task on the byte-compatible ordinary dispatch path.
8412
+ executionClass: IncidentTelemetryExecutionClass.optional(),
8413
+ incidentTelemetryPreflight: IncidentTelemetryPreflight.optional(),
8414
+ // Durable task override. Scheduled dispatch is trusted to preserve this
8415
+ // snapshot even if the workspace/deployment policy narrows later.
8416
+ maxNestedAgentDepth: NestedAgentDepthValue.optional(),
8417
+ };
8418
+ }
8419
+
8420
+ function refineScheduledTaskAgentConfig(
8421
+ value: { executionClass?: unknown; incidentTelemetryPreflight?: unknown },
8422
+ context: z.RefinementCtx,
8423
+ ): void {
8424
+ if (value.executionClass === "incident_telemetry" && !value.incidentTelemetryPreflight) {
8425
+ context.addIssue({
8426
+ code: "custom",
8427
+ path: ["incidentTelemetryPreflight"],
8428
+ message: "incident telemetry tasks require incidentTelemetryPreflight",
8429
+ });
8430
+ }
8431
+ if (value.executionClass !== "incident_telemetry" && value.incidentTelemetryPreflight) {
8432
+ context.addIssue({
8433
+ code: "custom",
8434
+ path: ["executionClass"],
8435
+ message: "incidentTelemetryPreflight requires executionClass=incident_telemetry",
8436
+ });
8437
+ }
8438
+ }
8439
+
8440
+ /** Storage/projection shape. Deliberately unbounded so stored rows always parse. */
8441
+ export const ScheduledTaskAgentConfig = /* @__PURE__ */ z
8442
+ .object(scheduledTaskAgentConfigShape(false))
8443
+ .superRefine(refineScheduledTaskAgentConfig);
6753
8444
  export type ScheduledTaskAgentConfig = z.infer<typeof ScheduledTaskAgentConfig>;
6754
8445
 
8446
+ /** Ingress-bounded agent config for create/update requests. */
8447
+ export const ScheduledTaskAgentConfigInput = /* @__PURE__ */ z
8448
+ .object(scheduledTaskAgentConfigShape(true))
8449
+ .superRefine((value, context) => {
8450
+ if (scheduledTaskJsonUtf8Bytes(value) > SCHEDULED_TASK_AGENT_CONFIG_MAX_BYTES) {
8451
+ context.addIssue({
8452
+ code: "custom",
8453
+ message: `scheduled task agent config exceeds ${SCHEDULED_TASK_AGENT_CONFIG_MAX_BYTES} UTF-8 bytes`,
8454
+ });
8455
+ }
8456
+ if (
8457
+ scheduledOccurrencePayloadUtf8Bytes(value) >
8458
+ SCHEDULED_TASK_OCCURRENCE_PAYLOAD_MAX_BYTES -
8459
+ SCHEDULED_TASK_OCCURRENCE_PAYLOAD_INGRESS_HEADROOM_BYTES
8460
+ ) {
8461
+ context.addIssue({
8462
+ code: "custom",
8463
+ message: `scheduled task prompt, resources, and tools exceed the ${SCHEDULED_TASK_OCCURRENCE_PAYLOAD_MAX_BYTES - SCHEDULED_TASK_OCCURRENCE_PAYLOAD_INGRESS_HEADROOM_BYTES} UTF-8 byte occurrence payload`,
8464
+ });
8465
+ }
8466
+ refineScheduledTaskAgentConfig(value, context);
8467
+ });
8468
+ export type ScheduledTaskAgentConfigInput = z.infer<typeof ScheduledTaskAgentConfigInput>;
8469
+
6755
8470
  export const ScheduledTask = /* @__PURE__ */ z.object({
6756
8471
  id: z.string().uuid(),
6757
8472
  accountId: z.string().uuid(),
@@ -6770,6 +8485,8 @@ export const ScheduledTask = /* @__PURE__ */ z.object({
6770
8485
  }),
6771
8486
  createdByContext: TurnInitiatorContext.default({}),
6772
8487
  personalConnections: z.array(McpPersonalConnectionSummary).default([]),
8488
+ authorityRevision: z.number().int().positive().default(1),
8489
+ executionDigest: z.string().regex(/^[0-9a-f]{64}$/u),
6773
8490
  reusableSessionId: z.string().uuid().nullable(),
6774
8491
  targetSessionId: z.string().uuid().nullable().default(null),
6775
8492
  variableSetId: z.string().uuid().nullable().default(null),
@@ -6785,32 +8502,178 @@ export const ScheduledTask = /* @__PURE__ */ z.object({
6785
8502
  });
6786
8503
  export type ScheduledTask = z.infer<typeof ScheduledTask>;
6787
8504
 
6788
- export const KnowledgeSourceSyncFailure = /* @__PURE__ */ z.object({
6789
- externalObjectId: KnowledgeSourceSyncSubject,
6790
- code: z.enum([
6791
- "authority_changed",
6792
- "connection_reconnect_required",
6793
- "provider_unavailable",
6794
- "provider_rejected",
6795
- "provider_payload_invalid",
6796
- "content_unsupported",
6797
- "content_too_large",
6798
- "resource_limit",
6799
- "item_processing_failed",
6800
- "indexing_failed",
6801
- "internal_failure",
6802
- ]),
6803
- retryable: z.boolean(),
6804
- message: z.string().min(1).max(1000),
6805
- });
6806
- export type KnowledgeSourceSyncFailure = z.infer<typeof KnowledgeSourceSyncFailure>;
6807
-
6808
- export const KnowledgeSourceSyncRunSummary = /* @__PURE__ */ z.object({
6809
- phase: z
6810
- .enum(["queued", "inventory", "transfer", "index", "checkpoint", "completed", "failed"])
6811
- .default("queued"),
6812
- scanned: KnowledgeSourceSyncZeroInteger,
6813
- imported: KnowledgeSourceSyncZeroInteger,
8505
+ /**
8506
+ * Complete credential-free execution truth accepted for one scheduled agent
8507
+ * occurrence. Retries consume this immutable snapshot instead of a mutable
8508
+ * scheduled-task head.
8509
+ */
8510
+ export const ScheduledTaskRunAcceptedExecution = /* @__PURE__ */ z
8511
+ .object({
8512
+ version: z.literal(1),
8513
+ task: ScheduledTask,
8514
+ resolvedModel: z.string().min(1),
8515
+ resolvedReasoningEffort: ReasoningEffort,
8516
+ resolvedLatencyMode: LatencyMode,
8517
+ resolvedSandboxBackend: SandboxBackend,
8518
+ resolvedSandboxOs: SandboxOs,
8519
+ resolvedTools: z.array(ToolRef).max(SCHEDULED_TASK_TOOL_MAX_COUNT),
8520
+ resolvedFirstPartyMcpTools: z.array(FirstPartyMcpToolName),
8521
+ resolvedFirstPartyMcpPermissions: z.array(Permission),
8522
+ resolvedVariableSet: z
8523
+ .object({
8524
+ id: z.string().uuid(),
8525
+ generation: z.number().int().positive(),
8526
+ })
8527
+ .strict()
8528
+ .nullable(),
8529
+ resolvedRig: z
8530
+ .object({
8531
+ id: z.string().uuid(),
8532
+ versionId: z.string().uuid(),
8533
+ defaultVariableSets: z
8534
+ .array(
8535
+ z
8536
+ .object({
8537
+ id: z.string().uuid(),
8538
+ generation: z.number().int().positive(),
8539
+ })
8540
+ .strict(),
8541
+ )
8542
+ .max(25),
8543
+ })
8544
+ .strict()
8545
+ .nullable(),
8546
+ resolvedSlackBotConnection: z
8547
+ .object({
8548
+ id: z.string().uuid(),
8549
+ version: z.number().int().positive(),
8550
+ verifiedInstallVersion: z.number().int().positive(),
8551
+ metadata: z
8552
+ .object({
8553
+ credentialRole: z.literal("opengeni_slack_bot"),
8554
+ credentialLabel: z.literal("OpenGeni Slack bot"),
8555
+ slackTeamId: z.string().min(1).max(64),
8556
+ slackTeamName: z.string().min(1).max(256),
8557
+ botUserId: z.string().min(1).max(64),
8558
+ botId: z.string().min(1).max(64),
8559
+ botDisplayName: z.enum(["OpenGeni", "OpenGeni Staging"]),
8560
+ verifiedAt: z.string().datetime({ offset: true }),
8561
+ })
8562
+ .passthrough(),
8563
+ })
8564
+ .strict()
8565
+ .nullable(),
8566
+ /**
8567
+ * The effective policy that an existing or already-materialized reusable
8568
+ * session will execute. Generated sessions carry null because their
8569
+ * accepted task policy is used to create the session itself.
8570
+ */
8571
+ targetSessionExecution: z
8572
+ .object({
8573
+ sessionId: z.string().uuid(),
8574
+ visibility: z.enum(["user_private", "workspace_shared"]),
8575
+ authorityEpoch: z.number().int().positive(),
8576
+ model: z.string().min(1),
8577
+ reasoningEffort: ReasoningEffort,
8578
+ latencyMode: LatencyMode,
8579
+ tools: z.array(ToolRef).max(SCHEDULED_TASK_TOOL_MAX_COUNT),
8580
+ sandboxBackend: SandboxBackend,
8581
+ sandboxOs: SandboxOs,
8582
+ firstPartyMcpTools: z.array(FirstPartyMcpToolName),
8583
+ firstPartyMcpPermissions: z.array(Permission).nullable(),
8584
+ toolPolicy: SessionToolPolicy,
8585
+ mcpServerIds: z.array(z.string().min(1).max(256)).max(SCHEDULED_TASK_TOOL_MAX_COUNT),
8586
+ effectiveMcpServerIds: z
8587
+ .array(z.string().min(1).max(256))
8588
+ .max(SCHEDULED_TASK_TOOL_MAX_COUNT),
8589
+ toolPolicyVersion: z.number().int().nonnegative(),
8590
+ variableSetId: z.string().uuid().nullable(),
8591
+ variableSetGeneration: z.number().int().positive().nullable(),
8592
+ rigId: z.string().uuid().nullable(),
8593
+ rigVersionId: z.string().uuid().nullable(),
8594
+ rigDefaultVariableSets: z
8595
+ .array(
8596
+ z
8597
+ .object({
8598
+ id: z.string().uuid(),
8599
+ generation: z.number().int().positive(),
8600
+ })
8601
+ .strict(),
8602
+ )
8603
+ .max(25),
8604
+ maxNestedAgentDepthOverride: NestedAgentDepthValue.nullable(),
8605
+ effectiveMaxNestedAgentDepth: NestedAgentDepthValue,
8606
+ })
8607
+ .strict()
8608
+ .nullable(),
8609
+ generatedSessionBinding: z
8610
+ .object({
8611
+ createIdempotencyKey: z.string().min(1).max(512),
8612
+ effectiveMaxNestedAgentDepth: NestedAgentDepthValue,
8613
+ nestedAgentDepthPolicySource: NestedAgentDepthPolicySource,
8614
+ codexCompactionMode: CodexCompactionMode,
8615
+ })
8616
+ .strict()
8617
+ .nullable(),
8618
+ personalConnectionDelegations: McpPersonalConnectionDelegations,
8619
+ personalResourceAuthoritySubjectId: z.string().min(1).nullable(),
8620
+ /** One accepted human principal for every resource-bearing scheduled run. */
8621
+ causalHumanSubjectId: z.string().min(1).nullable().default(null),
8622
+ /** Exact revision-bound human membership proof; never inferred at execution. */
8623
+ causalHumanAuthority: z
8624
+ .object({
8625
+ subjectId: z.string().min(1),
8626
+ organizationMembershipId: z.string().uuid(),
8627
+ membershipAuthorizationRevision: z.number().int().positive(),
8628
+ })
8629
+ .strict()
8630
+ .nullable()
8631
+ .default(null),
8632
+ xaiProviderAccountAuthoritySnapshot: XaiProviderAccountAuthoritySnapshotV1,
8633
+ xaiAuthoritySubjectId: z.string().min(1).nullable(),
8634
+ connectionAuthoritySubjectId: z.string().min(1).nullable(),
8635
+ triggerInitiator: TurnInitiator,
8636
+ agentRunUsageIdempotencyKey: z.string().min(1).max(512).nullable(),
8637
+ incidentPreflightRequired: z.boolean(),
8638
+ alertOccurrenceLabels: z.record(z.string(), z.string()).nullable(),
8639
+ })
8640
+ .strict()
8641
+ .superRefine((value, context) => {
8642
+ if (scheduledTaskJsonUtf8Bytes(value) > SCHEDULED_TASK_ACCEPTED_EXECUTION_MAX_BYTES) {
8643
+ context.addIssue({
8644
+ code: "custom",
8645
+ message: `scheduled accepted execution exceeds ${SCHEDULED_TASK_ACCEPTED_EXECUTION_MAX_BYTES} UTF-8 bytes`,
8646
+ });
8647
+ }
8648
+ });
8649
+ export type ScheduledTaskRunAcceptedExecution = z.infer<typeof ScheduledTaskRunAcceptedExecution>;
8650
+
8651
+ export const KnowledgeSourceSyncFailure = /* @__PURE__ */ z.object({
8652
+ externalObjectId: KnowledgeSourceSyncSubject,
8653
+ code: z.enum([
8654
+ "authority_changed",
8655
+ "connection_reconnect_required",
8656
+ "provider_unavailable",
8657
+ "provider_rejected",
8658
+ "provider_payload_invalid",
8659
+ "content_unsupported",
8660
+ "content_too_large",
8661
+ "resource_limit",
8662
+ "item_processing_failed",
8663
+ "indexing_failed",
8664
+ "internal_failure",
8665
+ ]),
8666
+ retryable: z.boolean(),
8667
+ message: z.string().min(1).max(1000),
8668
+ });
8669
+ export type KnowledgeSourceSyncFailure = z.infer<typeof KnowledgeSourceSyncFailure>;
8670
+
8671
+ export const KnowledgeSourceSyncRunSummary = /* @__PURE__ */ z.object({
8672
+ phase: z
8673
+ .enum(["queued", "inventory", "transfer", "index", "checkpoint", "completed", "failed"])
8674
+ .default("queued"),
8675
+ scanned: KnowledgeSourceSyncZeroInteger,
8676
+ imported: KnowledgeSourceSyncZeroInteger,
6814
8677
  unchanged: KnowledgeSourceSyncZeroInteger,
6815
8678
  skipped: KnowledgeSourceSyncZeroInteger,
6816
8679
  failed: KnowledgeSourceSyncZeroInteger,
@@ -6835,6 +8698,12 @@ export const ScheduledTaskRun = /* @__PURE__ */ z.object({
6835
8698
  accountId: z.string().uuid(),
6836
8699
  workspaceId: z.string().uuid(),
6837
8700
  taskId: z.string().uuid(),
8701
+ taskAuthorityRevision: z.number().int().positive().nullable().default(null),
8702
+ taskExecutionDigest: z
8703
+ .string()
8704
+ .regex(/^[0-9a-f]{64}$/u)
8705
+ .nullable()
8706
+ .default(null),
6838
8707
  status: ScheduledTaskRunStatus,
6839
8708
  triggerType: ScheduledTaskTriggerType,
6840
8709
  scheduledAt: z.string().nullable(),
@@ -6852,7 +8721,7 @@ export const ScheduledTaskRun = /* @__PURE__ */ z.object({
6852
8721
  export type ScheduledTaskRun = z.infer<typeof ScheduledTaskRun>;
6853
8722
 
6854
8723
  const CreateAgentScheduledTaskRequest = /* @__PURE__ */ withVariableSetIdAlias({
6855
- name: z.string().min(1),
8724
+ name: ScheduledTaskNameInput,
6856
8725
  schedule: ScheduledTaskScheduleSpec,
6857
8726
  action: z
6858
8727
  .object({ kind: z.literal("agent_turn") })
@@ -6861,13 +8730,14 @@ const CreateAgentScheduledTaskRequest = /* @__PURE__ */ withVariableSetIdAlias({
6861
8730
  runMode: ScheduledTaskRunMode.default("new_session_per_run"),
6862
8731
  overlapPolicy: ScheduledTaskOverlapPolicy.default("allow_concurrent"),
6863
8732
  targetSessionId: z.string().uuid().nullable().optional(),
6864
- agentConfig: ScheduledTaskAgentConfig,
8733
+ connectionAuthorities: McpConnectionAuthoritySelections.default([]),
8734
+ agentConfig: ScheduledTaskAgentConfigInput,
6865
8735
  status: ScheduledTaskStatus.default("active"),
6866
8736
  variableSetId: z.string().uuid().nullable().optional(),
6867
8737
  environmentId: z.string().uuid().nullable().optional(),
6868
8738
  // The rig each run binds to (M3); its active version is resolved per fire.
6869
8739
  rigId: z.string().uuid().nullable().optional(),
6870
- metadata: z.record(z.string(), z.unknown()).default({}),
8740
+ metadata: ScheduledTaskMetadataInput.default({}),
6871
8741
  }).superRefine((value, context) => {
6872
8742
  if (value.runMode === "existing_session" && !value.targetSessionId) {
6873
8743
  context.addIssue({
@@ -6915,6 +8785,7 @@ const CreateKnowledgeSourceSyncScheduledTaskRequest = /* @__PURE__ */ z
6915
8785
  variableSetId: null,
6916
8786
  environmentId: null,
6917
8787
  rigId: null,
8788
+ connectionAuthorities: [],
6918
8789
  }));
6919
8790
 
6920
8791
  export const CreateScheduledTaskRequest = /* @__PURE__ */ z.union([
@@ -6923,59 +8794,393 @@ export const CreateScheduledTaskRequest = /* @__PURE__ */ z.union([
6923
8794
  ]);
6924
8795
  export type CreateScheduledTaskRequest = z.infer<typeof CreateScheduledTaskRequest>;
6925
8796
 
6926
- export const UpdateScheduledTaskRequest =
6927
- /* @__PURE__ */ withVariableSetIdAlias({
6928
- name: z.string().min(1).optional(),
6929
- schedule: ScheduledTaskScheduleSpec.optional(),
6930
- runMode: ScheduledTaskRunMode.optional(),
6931
- overlapPolicy: ScheduledTaskOverlapPolicy.optional(),
6932
- action: ScheduledTaskAction.optional(),
6933
- targetSessionId: z.string().uuid().nullable().optional(),
6934
- agentConfig: ScheduledTaskAgentConfig.optional(),
6935
- status: ScheduledTaskStatus.optional(),
6936
- variableSetId: z.string().uuid().nullable().optional(),
6937
- environmentId: z.string().uuid().nullable().optional(),
6938
- // The rig each run binds to (M3); null clears it. Its active version is
6939
- // resolved per fire, so an update takes effect on the next dispatch.
6940
- rigId: z.string().uuid().nullable().optional(),
6941
- metadata: z.record(z.string(), z.unknown()).optional(),
6942
- }).superRefine((value, context) => {
6943
- if (value.targetSessionId && value.runMode && value.runMode !== "existing_session") {
8797
+ export const UpdateScheduledTaskRequest = /* @__PURE__ */ withVariableSetIdAlias({
8798
+ name: ScheduledTaskNameInput.optional(),
8799
+ schedule: ScheduledTaskScheduleSpec.optional(),
8800
+ runMode: ScheduledTaskRunMode.optional(),
8801
+ overlapPolicy: ScheduledTaskOverlapPolicy.optional(),
8802
+ action: ScheduledTaskAction.optional(),
8803
+ targetSessionId: z.string().uuid().nullable().optional(),
8804
+ connectionAuthorities: McpConnectionAuthoritySelections.optional(),
8805
+ agentConfig: ScheduledTaskAgentConfigInput.optional(),
8806
+ status: ScheduledTaskStatus.optional(),
8807
+ variableSetId: z.string().uuid().nullable().optional(),
8808
+ environmentId: z.string().uuid().nullable().optional(),
8809
+ // The rig each run binds to (M3); null clears it. Its active version is
8810
+ // resolved per fire, so an update takes effect on the next dispatch.
8811
+ rigId: z.string().uuid().nullable().optional(),
8812
+ metadata: z.record(z.string(), z.unknown()).optional(),
8813
+ }).superRefine((value, context) => {
8814
+ if (value.targetSessionId && value.runMode && value.runMode !== "existing_session") {
8815
+ context.addIssue({
8816
+ code: "custom",
8817
+ path: ["targetSessionId"],
8818
+ message: "targetSessionId requires runMode=existing_session",
8819
+ });
8820
+ }
8821
+ if (value.runMode === "existing_session" && value.targetSessionId === null) {
8822
+ context.addIssue({
8823
+ code: "custom",
8824
+ path: ["targetSessionId"],
8825
+ message: "targetSessionId cannot be null when runMode=existing_session",
8826
+ });
8827
+ }
8828
+ if (
8829
+ value.agentConfig?.goal &&
8830
+ (value.runMode === "existing_session" || Boolean(value.targetSessionId))
8831
+ ) {
8832
+ context.addIssue({
8833
+ code: "custom",
8834
+ path: ["agentConfig", "goal"],
8835
+ message: "agentConfig.goal cannot be used with an existing-session target",
8836
+ });
8837
+ }
8838
+ });
8839
+ export type UpdateScheduledTaskRequest = z.infer<typeof UpdateScheduledTaskRequest>;
8840
+
8841
+ /**
8842
+ * Manual-trigger body. `triggerId` is a client-supplied idempotency token: a
8843
+ * retried trigger that reuses the same token charges once and starts one run.
8844
+ * Omitting it makes each call a distinct trigger (the server mints a token).
8845
+ */
8846
+ export const TriggerScheduledTaskRequest = z.object({
8847
+ triggerId: z.string().min(1).max(128).optional(),
8848
+ });
8849
+ export type TriggerScheduledTaskRequest = z.infer<typeof TriggerScheduledTaskRequest>;
8850
+
8851
+ // ============ Event-triggered automations ============
8852
+
8853
+ export const AUTOMATION_WEBHOOK_MAX_BYTES = 2 * 1024 * 1024;
8854
+ export const AUTOMATION_EVENT_PAYLOAD_MAX_BYTES = 256 * 1024;
8855
+ export const AUTOMATION_SESSION_TEMPLATE_MAX_BYTES = 512 * 1024;
8856
+ export const AUTOMATION_MAX_MATCHED_TRIGGERS = 32;
8857
+
8858
+ const AutomationBoundedJson = z.record(z.string(), z.unknown()).superRefine((value, context) => {
8859
+ let bytes = Number.POSITIVE_INFINITY;
8860
+ try {
8861
+ bytes = new TextEncoder().encode(JSON.stringify(value)).byteLength;
8862
+ } catch {
8863
+ // The shared JSON/protocol boundary reports the exact runtime path later;
8864
+ // this public ingress still fails closed on an unserializable value.
8865
+ }
8866
+ if (bytes > AUTOMATION_EVENT_PAYLOAD_MAX_BYTES) {
8867
+ context.addIssue({
8868
+ code: "custom",
8869
+ message: `automation JSON exceeds ${AUTOMATION_EVENT_PAYLOAD_MAX_BYTES} UTF-8 bytes`,
8870
+ });
8871
+ }
8872
+ });
8873
+
8874
+ export const AutomationAdapterId = z
8875
+ .string()
8876
+ .min(1)
8877
+ .max(128)
8878
+ .regex(/^[a-z0-9](?:[a-z0-9._/-]*[a-z0-9])?$/);
8879
+ export type AutomationAdapterId = z.infer<typeof AutomationAdapterId>;
8880
+
8881
+ export const AutomationSourceStatus = z.enum(["active", "disabled"]);
8882
+ export type AutomationSourceStatus = z.infer<typeof AutomationSourceStatus>;
8883
+
8884
+ export const AutomationTriggerStatus = z.enum(["active", "paused", "disabled"]);
8885
+ export type AutomationTriggerStatus = z.infer<typeof AutomationTriggerStatus>;
8886
+
8887
+ export const AutomationRunStatus = z.enum([
8888
+ "queued",
8889
+ "dispatching",
8890
+ "dispatched",
8891
+ "skipped",
8892
+ "failed",
8893
+ ]);
8894
+ export type AutomationRunStatus = z.infer<typeof AutomationRunStatus>;
8895
+
8896
+ export const AutomationEventStatus = z.enum(["accepted", "ignored", "failed"]);
8897
+ export type AutomationEventStatus = z.infer<typeof AutomationEventStatus>;
8898
+
8899
+ export const SignedJsonAutomationEnvelope = z
8900
+ .object({
8901
+ id: z.string().trim().min(1).max(1024).optional(),
8902
+ type: z.string().trim().min(1).max(256),
8903
+ occurrenceKey: z.string().trim().min(1).max(1024).optional(),
8904
+ occurredAt: z.string().datetime({ offset: true }).nullable().optional(),
8905
+ subject: z.string().trim().min(1).max(512).nullable().optional(),
8906
+ resource: z.string().trim().min(1).max(1024).nullable().optional(),
8907
+ data: z.record(z.string(), z.unknown()).default({}),
8908
+ })
8909
+ .strict();
8910
+ export type SignedJsonAutomationEnvelope = z.infer<typeof SignedJsonAutomationEnvelope>;
8911
+
8912
+ const AutomationSessionSkill = z
8913
+ .object({
8914
+ name: z
8915
+ .string()
8916
+ .min(1)
8917
+ .max(64)
8918
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/),
8919
+ description: z.string().min(1).max(2048).optional(),
8920
+ files: z
8921
+ .array(
8922
+ z.object({
8923
+ path: z
8924
+ .string()
8925
+ .min(1)
8926
+ .max(512)
8927
+ .refine(
8928
+ (path) =>
8929
+ !path.startsWith("/") &&
8930
+ !path.includes("\\") &&
8931
+ path
8932
+ .split("/")
8933
+ .every((segment) => segment.length > 0 && segment !== "." && segment !== ".."),
8934
+ "automation skill file path must be a safe relative POSIX path",
8935
+ ),
8936
+ content: z.string().max(256 * 1024),
8937
+ }),
8938
+ )
8939
+ .min(1)
8940
+ .max(64),
8941
+ })
8942
+ .superRefine((skill, context) => {
8943
+ if (!skill.files.some((file) => file.path === "SKILL.md")) {
6944
8944
  context.addIssue({
6945
8945
  code: "custom",
6946
- path: ["targetSessionId"],
6947
- message: "targetSessionId requires runMode=existing_session",
8946
+ path: ["files"],
8947
+ message: "automation skill must include a top-level SKILL.md file",
6948
8948
  });
6949
8949
  }
6950
- if (value.runMode === "existing_session" && value.targetSessionId === null) {
8950
+ });
8951
+
8952
+ export const AutomationSessionTemplate = z
8953
+ .object({
8954
+ prompt: z
8955
+ .string()
8956
+ .trim()
8957
+ .min(1)
8958
+ .max(64 * 1024),
8959
+ instructions: z
8960
+ .string()
8961
+ .trim()
8962
+ .min(1)
8963
+ .max(64 * 1024)
8964
+ .nullable()
8965
+ .default(null),
8966
+ resources: z.array(ResourceRef).max(100).default([]),
8967
+ skills: z.array(AutomationSessionSkill).max(32).default([]),
8968
+ tools: z.array(ToolRef).max(128).default([]),
8969
+ firstPartyMcpTools: z.array(FirstPartyMcpToolName).max(128).default([]),
8970
+ firstPartyMcpPermissions: z.array(Permission).max(128).default([]),
8971
+ model: z.string().trim().min(1).max(512).nullable().default(null),
8972
+ reasoningEffort: ReasoningEffort.nullable().default(null),
8973
+ sandboxBackend: SandboxBackend.nullable().default(null),
8974
+ policyRole: z.string().trim().min(1).max(128).nullable().default(null),
8975
+ metadata: AutomationBoundedJson.default({}),
8976
+ })
8977
+ .strict()
8978
+ .superRefine((value, context) => {
8979
+ let bytes = Number.POSITIVE_INFINITY;
8980
+ try {
8981
+ bytes = new TextEncoder().encode(JSON.stringify(value)).byteLength;
8982
+ } catch {
8983
+ // Fail through the size issue below.
8984
+ }
8985
+ if (bytes > AUTOMATION_SESSION_TEMPLATE_MAX_BYTES) {
6951
8986
  context.addIssue({
6952
8987
  code: "custom",
6953
- path: ["targetSessionId"],
6954
- message: "targetSessionId cannot be null when runMode=existing_session",
8988
+ message: `automation session template exceeds ${AUTOMATION_SESSION_TEMPLATE_MAX_BYTES} UTF-8 bytes`,
6955
8989
  });
6956
8990
  }
6957
- if (
6958
- value.agentConfig?.goal &&
6959
- (value.runMode === "existing_session" || Boolean(value.targetSessionId))
6960
- ) {
8991
+ });
8992
+ export type AutomationSessionTemplate = z.infer<typeof AutomationSessionTemplate>;
8993
+
8994
+ export const AutomationNormalizedEvent = z
8995
+ .object({
8996
+ adapterId: AutomationAdapterId,
8997
+ eventType: z.string().trim().min(1).max(256),
8998
+ occurrenceKey: z.string().trim().min(1).max(1024),
8999
+ occurredAt: z.string().datetime({ offset: true }).nullable().default(null),
9000
+ subject: z.string().trim().min(1).max(512).nullable().default(null),
9001
+ resource: z.string().trim().min(1).max(1024).nullable().default(null),
9002
+ payload: AutomationBoundedJson,
9003
+ })
9004
+ .strict();
9005
+ export type AutomationNormalizedEvent = z.infer<typeof AutomationNormalizedEvent>;
9006
+
9007
+ export const AutomationAcceptedExecution = z
9008
+ .object({
9009
+ version: z.literal(1),
9010
+ accountId: z.string().uuid(),
9011
+ workspaceId: z.string().uuid(),
9012
+ sourceId: z.string().uuid(),
9013
+ sourceVersion: z.number().int().positive(),
9014
+ triggerId: z.string().uuid(),
9015
+ triggerRevision: z.number().int().positive(),
9016
+ eventId: z.string().uuid(),
9017
+ adapterId: AutomationAdapterId,
9018
+ occurrenceKey: z.string().min(1).max(1024),
9019
+ initialMessage: z
9020
+ .string()
9021
+ .min(1)
9022
+ .max(256 * 1024),
9023
+ sessionTemplate: AutomationSessionTemplate,
9024
+ serviceSubjectId: z.string().min(1).max(512),
9025
+ serviceLabel: z.string().min(1).max(200),
9026
+ provenance: AutomationBoundedJson,
9027
+ })
9028
+ .strict();
9029
+ export type AutomationAcceptedExecution = z.infer<typeof AutomationAcceptedExecution>;
9030
+
9031
+ export const CreateAutomationSourceRequest = z
9032
+ .object({
9033
+ name: z.string().trim().min(1).max(200),
9034
+ adapterId: AutomationAdapterId.default("signed-json.v1"),
9035
+ webhookSecret: z.string().min(16).max(65_536),
9036
+ configuration: AutomationBoundedJson.default({}),
9037
+ })
9038
+ .strict();
9039
+ export type CreateAutomationSourceRequest = z.infer<typeof CreateAutomationSourceRequest>;
9040
+
9041
+ export const UpdateAutomationSourceRequest = z
9042
+ .object({
9043
+ name: z.string().trim().min(1).max(200).optional(),
9044
+ webhookSecret: z.string().min(16).max(65_536).optional(),
9045
+ configuration: AutomationBoundedJson.optional(),
9046
+ status: AutomationSourceStatus.optional(),
9047
+ })
9048
+ .strict()
9049
+ .refine((value) => Object.keys(value).length > 0, "automation source update is empty");
9050
+ export type UpdateAutomationSourceRequest = z.infer<typeof UpdateAutomationSourceRequest>;
9051
+
9052
+ export const AutomationSource = z.object({
9053
+ id: z.string().uuid(),
9054
+ accountId: z.string().uuid(),
9055
+ workspaceId: z.string().uuid(),
9056
+ name: z.string(),
9057
+ adapterId: AutomationAdapterId,
9058
+ configuration: z.record(z.string(), z.unknown()),
9059
+ status: AutomationSourceStatus,
9060
+ version: z.number().int().positive(),
9061
+ packInstallationId: z.string().uuid().nullable(),
9062
+ packConnectorId: z.string().min(1).max(128).nullable(),
9063
+ hasWebhookSecret: z.boolean(),
9064
+ webhookPath: z.string().min(1),
9065
+ createdBySubjectId: z.string(),
9066
+ createdAt: z.string(),
9067
+ updatedAt: z.string(),
9068
+ });
9069
+ export type AutomationSource = z.infer<typeof AutomationSource>;
9070
+
9071
+ export const CreateAutomationTriggerRequest = z
9072
+ .object({
9073
+ sourceId: z.string().uuid(),
9074
+ name: z.string().trim().min(1).max(200),
9075
+ eventTypes: z.array(z.string().trim().min(1).max(256)).min(1).max(64),
9076
+ configuration: AutomationBoundedJson.default({}),
9077
+ parameters: AutomationBoundedJson.default({}),
9078
+ sessionTemplate: AutomationSessionTemplate,
9079
+ status: AutomationTriggerStatus.default("active"),
9080
+ packInstallationId: z.string().uuid().nullable().default(null),
9081
+ packTemplateId: z.string().trim().min(1).max(128).nullable().default(null),
9082
+ })
9083
+ .strict()
9084
+ .superRefine((value, context) => {
9085
+ if ((value.packInstallationId === null) !== (value.packTemplateId === null)) {
6961
9086
  context.addIssue({
6962
9087
  code: "custom",
6963
- path: ["agentConfig", "goal"],
6964
- message: "agentConfig.goal cannot be used with an existing-session target",
9088
+ path: ["packTemplateId"],
9089
+ message: "packInstallationId and packTemplateId must be supplied together",
6965
9090
  });
6966
9091
  }
6967
9092
  });
6968
- export type UpdateScheduledTaskRequest = z.infer<typeof UpdateScheduledTaskRequest>;
9093
+ export type CreateAutomationTriggerRequest = z.infer<typeof CreateAutomationTriggerRequest>;
6969
9094
 
6970
- /**
6971
- * Manual-trigger body. `triggerId` is a client-supplied idempotency token: a
6972
- * retried trigger that reuses the same token charges once and starts one run.
6973
- * Omitting it makes each call a distinct trigger (the server mints a token).
6974
- */
6975
- export const TriggerScheduledTaskRequest = z.object({
6976
- triggerId: z.string().min(1).max(128).optional(),
9095
+ export const UpdateAutomationTriggerRequest = z
9096
+ .object({
9097
+ expectedRevision: z.number().int().positive(),
9098
+ name: z.string().trim().min(1).max(200).optional(),
9099
+ eventTypes: z.array(z.string().trim().min(1).max(256)).min(1).max(64).optional(),
9100
+ configuration: AutomationBoundedJson.optional(),
9101
+ parameters: AutomationBoundedJson.optional(),
9102
+ sessionTemplate: AutomationSessionTemplate.optional(),
9103
+ status: AutomationTriggerStatus.optional(),
9104
+ })
9105
+ .strict()
9106
+ .refine((value) => Object.keys(value).some((key) => key !== "expectedRevision"), {
9107
+ message: "automation trigger update is empty",
9108
+ });
9109
+ export type UpdateAutomationTriggerRequest = z.infer<typeof UpdateAutomationTriggerRequest>;
9110
+
9111
+ export const AutomationTrigger = z.object({
9112
+ id: z.string().uuid(),
9113
+ accountId: z.string().uuid(),
9114
+ workspaceId: z.string().uuid(),
9115
+ sourceId: z.string().uuid(),
9116
+ name: z.string(),
9117
+ adapterId: AutomationAdapterId,
9118
+ eventTypes: z.array(z.string()),
9119
+ configuration: z.record(z.string(), z.unknown()),
9120
+ parameters: z.record(z.string(), z.unknown()),
9121
+ sessionTemplate: AutomationSessionTemplate,
9122
+ status: AutomationTriggerStatus,
9123
+ revision: z.number().int().positive(),
9124
+ packInstallationId: z.string().uuid().nullable(),
9125
+ packTemplateId: z.string().nullable(),
9126
+ createdBySubjectId: z.string(),
9127
+ createdAt: z.string(),
9128
+ updatedAt: z.string(),
6977
9129
  });
6978
- export type TriggerScheduledTaskRequest = z.infer<typeof TriggerScheduledTaskRequest>;
9130
+ export type AutomationTrigger = z.infer<typeof AutomationTrigger>;
9131
+
9132
+ export const AutomationRun = z.object({
9133
+ id: z.string().uuid(),
9134
+ accountId: z.string().uuid(),
9135
+ workspaceId: z.string().uuid(),
9136
+ sourceId: z.string().uuid(),
9137
+ triggerId: z.string().uuid(),
9138
+ triggerRevision: z.number().int().positive(),
9139
+ eventId: z.string().uuid(),
9140
+ occurrenceKey: z.string(),
9141
+ status: AutomationRunStatus,
9142
+ sessionId: z.string().uuid().nullable(),
9143
+ errorCode: z.string().nullable(),
9144
+ createdAt: z.string(),
9145
+ updatedAt: z.string(),
9146
+ });
9147
+ export type AutomationRun = z.infer<typeof AutomationRun>;
9148
+
9149
+ export const AutomationWebhookResult = z.object({
9150
+ accepted: z.boolean(),
9151
+ duplicate: z.boolean(),
9152
+ ignoredReason: z.string().nullable(),
9153
+ eventId: z.string().uuid().nullable(),
9154
+ runIds: z.array(z.string().uuid()),
9155
+ });
9156
+ export type AutomationWebhookResult = z.infer<typeof AutomationWebhookResult>;
9157
+
9158
+ export const TriggerAutomationManuallyRequest = z
9159
+ .object({
9160
+ deliveryId: z.string().trim().min(1).max(512).optional(),
9161
+ eventType: z.string().trim().min(1).max(256),
9162
+ occurrenceKey: z.string().trim().min(1).max(1024),
9163
+ payload: AutomationBoundedJson.default({}),
9164
+ occurredAt: z.string().datetime({ offset: true }).nullable().default(null),
9165
+ subject: z.string().trim().min(1).max(512).nullable().default(null),
9166
+ resource: z.string().trim().min(1).max(1024).nullable().default(null),
9167
+ })
9168
+ .strict();
9169
+ export type TriggerAutomationManuallyRequest = z.infer<typeof TriggerAutomationManuallyRequest>;
9170
+
9171
+ export const CapabilityPackAutomationTemplate = z
9172
+ .object({
9173
+ id: z.string().min(1).max(128),
9174
+ name: z.string().min(1).max(200),
9175
+ description: z.string().min(1).max(4096),
9176
+ adapterId: AutomationAdapterId,
9177
+ eventTypes: z.array(z.string().min(1).max(256)).min(1).max(64),
9178
+ sessionTemplate: AutomationSessionTemplate,
9179
+ configuration: AutomationBoundedJson.default({}),
9180
+ connectionRequirement: z.string().min(1).max(128).nullable().default(null),
9181
+ })
9182
+ .strict();
9183
+ export type CapabilityPackAutomationTemplate = z.infer<typeof CapabilityPackAutomationTemplate>;
6979
9184
 
6980
9185
  export const CapabilityPackConnectorAuthModel = z.enum([
6981
9186
  "oauth2_authorization_code_pkce",
@@ -7282,6 +9487,7 @@ export const CapabilityPack = z.preprocess(
7282
9487
  connectors: z.array(CapabilityPackConnector).default([]),
7283
9488
  knowledge: z.array(CapabilityPackKnowledge).default([]),
7284
9489
  scheduledTaskTemplates: z.array(CapabilityPackScheduledTaskTemplate).default([]),
9490
+ automationTemplates: z.array(CapabilityPackAutomationTemplate).max(64).optional(),
7285
9491
  variableSet: CapabilityPackVariableSet.optional(),
7286
9492
  metadata: z.record(z.string(), z.unknown()).default({}),
7287
9493
  })
@@ -7308,6 +9514,17 @@ export const CapabilityPack = z.preprocess(
7308
9514
  }
7309
9515
  componentKeys.add(key);
7310
9516
  });
9517
+ const automationTemplateIds = new Set<string>();
9518
+ pack.automationTemplates?.forEach((template, index) => {
9519
+ if (automationTemplateIds.has(template.id)) {
9520
+ ctx.addIssue({
9521
+ code: "custom",
9522
+ message: `duplicate Pack automation template id: ${template.id}`,
9523
+ path: ["automationTemplates", index, "id"],
9524
+ });
9525
+ }
9526
+ automationTemplateIds.add(template.id);
9527
+ });
7311
9528
  if (!pack.sandboxProviderImages?.modal) {
7312
9529
  return;
7313
9530
  }
@@ -7345,33 +9562,231 @@ export const WorkspaceRegisteredPack = z.object({
7345
9562
  });
7346
9563
  export type WorkspaceRegisteredPack = z.infer<typeof WorkspaceRegisteredPack>;
7347
9564
 
7348
- export const PackInstallationStatus = z.enum([
7349
- "installing",
7350
- "active",
7351
- "needs_attention",
7352
- "disabled",
7353
- ]);
7354
- export type PackInstallationStatus = z.infer<typeof PackInstallationStatus>;
9565
+ export const PackInstallationStatus = z.enum([
9566
+ "installing",
9567
+ "active",
9568
+ "needs_attention",
9569
+ "disabled",
9570
+ ]);
9571
+ export type PackInstallationStatus = z.infer<typeof PackInstallationStatus>;
9572
+
9573
+ export const PackInstallation = z.object({
9574
+ id: z.string().uuid(),
9575
+ accountId: z.string().uuid(),
9576
+ workspaceId: z.string().uuid(),
9577
+ packId: z.string().min(1),
9578
+ status: PackInstallationStatus,
9579
+ version: z.number().int().positive(),
9580
+ manifestSnapshot: CapabilityPack.nullable(),
9581
+ manifestDigest: z
9582
+ .string()
9583
+ .regex(/^[0-9a-f]{64}$/)
9584
+ .nullable(),
9585
+ selectedRigId: z.string().uuid().nullable(),
9586
+ installedBySubjectId: z.string().min(1).max(1024).nullable(),
9587
+ metadata: z.record(z.string(), z.unknown()),
9588
+ enabledAt: z.string(),
9589
+ updatedAt: z.string(),
9590
+ });
9591
+ export type PackInstallation = z.infer<typeof PackInstallation>;
9592
+
9593
+ // ============ OpenGeni Review Bot — provider-neutral pull-request review automation ============
9594
+
9595
+ export const OPENGENI_PR_REVIEW_PACK_ID = "pr-review" as const;
9596
+ export const OPENGENI_PR_REVIEW_SESSION_ROLE = "pull_request_review" as const;
9597
+
9598
+ export const PrReviewProvider = GitCredentialProvider;
9599
+ export type PrReviewProvider = z.infer<typeof PrReviewProvider>;
9600
+
9601
+ export const PrReviewCredentialKind = /* @__PURE__ */ z.enum(["github_app", "provider_token"]);
9602
+ export type PrReviewCredentialKind = z.infer<typeof PrReviewCredentialKind>;
9603
+
9604
+ export const PrReviewWebhookAuthKind = /* @__PURE__ */ z.enum([
9605
+ "hmac_sha256",
9606
+ "shared_token",
9607
+ "basic",
9608
+ ]);
9609
+ export type PrReviewWebhookAuthKind = z.infer<typeof PrReviewWebhookAuthKind>;
9610
+
9611
+ const PrReviewSecretInput = /* @__PURE__ */ (() => z.string().min(16).max(65_536))();
9612
+
9613
+ export const CreatePrReviewAppRegistrationRequest = /* @__PURE__ */ (() =>
9614
+ z
9615
+ .object({
9616
+ name: z.string().trim().min(1).max(200),
9617
+ provider: PrReviewProvider,
9618
+ providerBaseUrl: z.string().url().max(2048).optional(),
9619
+ appId: z.string().trim().min(1).max(512).optional(),
9620
+ credentialKind: PrReviewCredentialKind,
9621
+ privateKey: PrReviewSecretInput.optional(),
9622
+ accessToken: PrReviewSecretInput.optional(),
9623
+ accessTokenExpiresAt: z.string().datetime().nullable().optional(),
9624
+ webhookSecret: PrReviewSecretInput,
9625
+ webhookUsername: z.string().min(1).max(512).optional(),
9626
+ })
9627
+ .strict()
9628
+ .superRefine((value, context) => {
9629
+ if (value.provider === "github" && value.credentialKind !== "github_app") {
9630
+ context.addIssue({
9631
+ code: "custom",
9632
+ path: ["credentialKind"],
9633
+ message: "GitHub PR Review registrations require a dedicated GitHub App",
9634
+ });
9635
+ }
9636
+ if (value.provider === "github" && (!value.appId || !value.privateKey)) {
9637
+ context.addIssue({
9638
+ code: "custom",
9639
+ path: [!value.appId ? "appId" : "privateKey"],
9640
+ message: "GitHub PR Review registrations require the dedicated App ID and private key",
9641
+ });
9642
+ }
9643
+ if (value.provider !== "github" && value.credentialKind !== "provider_token") {
9644
+ context.addIssue({
9645
+ code: "custom",
9646
+ path: ["credentialKind"],
9647
+ message: "GitLab and Azure DevOps PR Review registrations require a provider token",
9648
+ });
9649
+ }
9650
+ if (value.credentialKind === "provider_token" && !value.accessToken) {
9651
+ context.addIssue({
9652
+ code: "custom",
9653
+ path: ["accessToken"],
9654
+ message: "provider_token credentials require accessToken",
9655
+ });
9656
+ }
9657
+ if (
9658
+ value.credentialKind === "github_app" &&
9659
+ (value.accessToken !== undefined || value.privateKey === undefined)
9660
+ ) {
9661
+ context.addIssue({
9662
+ code: "custom",
9663
+ path: [value.accessToken !== undefined ? "accessToken" : "privateKey"],
9664
+ message: "GitHub App credentials require privateKey and cannot include accessToken",
9665
+ });
9666
+ }
9667
+ if (value.credentialKind === "provider_token" && value.privateKey !== undefined) {
9668
+ context.addIssue({
9669
+ code: "custom",
9670
+ path: ["privateKey"],
9671
+ message: "provider_token credentials cannot include a GitHub privateKey",
9672
+ });
9673
+ }
9674
+ if (value.provider === "github" && value.accessTokenExpiresAt !== undefined) {
9675
+ context.addIssue({
9676
+ code: "custom",
9677
+ path: ["accessTokenExpiresAt"],
9678
+ message: "GitHub App registrations do not use accessTokenExpiresAt",
9679
+ });
9680
+ }
9681
+ if (value.provider === "azure_devops" && !value.webhookUsername) {
9682
+ context.addIssue({
9683
+ code: "custom",
9684
+ path: ["webhookUsername"],
9685
+ message: "Azure DevOps service hooks require a Basic authentication username",
9686
+ });
9687
+ }
9688
+ }))();
9689
+ export type CreatePrReviewAppRegistrationRequest = z.infer<
9690
+ typeof CreatePrReviewAppRegistrationRequest
9691
+ >;
9692
+
9693
+ export const UpdatePrReviewAppRegistrationRequest = /* @__PURE__ */ (() =>
9694
+ z
9695
+ .object({
9696
+ name: z.string().trim().min(1).max(200).optional(),
9697
+ accessToken: PrReviewSecretInput.optional(),
9698
+ privateKey: PrReviewSecretInput.optional(),
9699
+ accessTokenExpiresAt: z.string().datetime().nullable().optional(),
9700
+ webhookSecret: PrReviewSecretInput.optional(),
9701
+ webhookUsername: z.string().min(1).max(512).optional(),
9702
+ status: z.enum(["active", "disabled"]).optional(),
9703
+ })
9704
+ .strict()
9705
+ .refine((value) => Object.keys(value).length > 0, {
9706
+ message: "PR Review registration update must change at least one field",
9707
+ }))();
9708
+ export type UpdatePrReviewAppRegistrationRequest = z.infer<
9709
+ typeof UpdatePrReviewAppRegistrationRequest
9710
+ >;
9711
+
9712
+ export const PrReviewAppRegistration = /* @__PURE__ */ (() =>
9713
+ z.object({
9714
+ id: z.string().uuid(),
9715
+ sourceId: z.string().uuid(),
9716
+ accountId: z.string().uuid(),
9717
+ workspaceId: z.string().uuid(),
9718
+ name: z.string(),
9719
+ provider: PrReviewProvider,
9720
+ providerBaseUrl: z.string().url(),
9721
+ appId: z.string().nullable(),
9722
+ credentialKind: PrReviewCredentialKind,
9723
+ hasCredential: z.boolean(),
9724
+ accessTokenExpiresAt: z.string().nullable(),
9725
+ webhookAuthKind: PrReviewWebhookAuthKind,
9726
+ hasWebhookSecret: z.boolean(),
9727
+ webhookUsername: z.string().nullable(),
9728
+ webhookPath: z.string(),
9729
+ status: z.enum(["active", "disabled"]),
9730
+ createdBySubjectId: z.string(),
9731
+ createdAt: z.string(),
9732
+ updatedAt: z.string(),
9733
+ }))();
9734
+ export type PrReviewAppRegistration = z.infer<typeof PrReviewAppRegistration>;
9735
+
9736
+ export const CreatePrReviewRepositoryBindingRequest = /* @__PURE__ */ (() =>
9737
+ z
9738
+ .object({
9739
+ registrationId: z.string().uuid(),
9740
+ repositoryUri: z.string().url().max(2048),
9741
+ repositoryFullName: z.string().trim().min(1).max(1024),
9742
+ providerRepositoryId: z.union([z.string().min(1).max(512), z.number().int().positive()]),
9743
+ installationId: z.union([z.string().min(1).max(512), z.number().int().positive()]).optional(),
9744
+ projectId: z.union([z.string().min(1).max(512), z.number().int().positive()]).optional(),
9745
+ model: z.string().min(1).max(512).nullable().optional(),
9746
+ additionalInstructions: z.string().max(16_384).nullable().optional(),
9747
+ status: z.enum(["active", "disabled"]).default("active"),
9748
+ })
9749
+ .strict())();
9750
+ export type CreatePrReviewRepositoryBindingRequest = z.infer<
9751
+ typeof CreatePrReviewRepositoryBindingRequest
9752
+ >;
7355
9753
 
7356
- export const PackInstallation = z.object({
7357
- id: z.string().uuid(),
7358
- accountId: z.string().uuid(),
7359
- workspaceId: z.string().uuid(),
7360
- packId: z.string().min(1),
7361
- status: PackInstallationStatus,
7362
- version: z.number().int().positive(),
7363
- manifestSnapshot: CapabilityPack.nullable(),
7364
- manifestDigest: z
7365
- .string()
7366
- .regex(/^[0-9a-f]{64}$/)
7367
- .nullable(),
7368
- selectedRigId: z.string().uuid().nullable(),
7369
- installedBySubjectId: z.string().min(1).max(1024).nullable(),
7370
- metadata: z.record(z.string(), z.unknown()),
7371
- enabledAt: z.string(),
7372
- updatedAt: z.string(),
7373
- });
7374
- export type PackInstallation = z.infer<typeof PackInstallation>;
9754
+ export const UpdatePrReviewRepositoryBindingRequest = /* @__PURE__ */ (() =>
9755
+ z
9756
+ .object({
9757
+ model: z.string().min(1).max(512).nullable().optional(),
9758
+ additionalInstructions: z.string().max(16_384).nullable().optional(),
9759
+ status: z.enum(["active", "disabled"]).optional(),
9760
+ })
9761
+ .strict()
9762
+ .refine((value) => Object.keys(value).length > 0, {
9763
+ message: "PR Review repository update must change at least one field",
9764
+ }))();
9765
+ export type UpdatePrReviewRepositoryBindingRequest = z.infer<
9766
+ typeof UpdatePrReviewRepositoryBindingRequest
9767
+ >;
9768
+
9769
+ export const PrReviewRepositoryBinding = /* @__PURE__ */ (() =>
9770
+ z.object({
9771
+ id: z.string().uuid(),
9772
+ triggerId: z.string().uuid(),
9773
+ accountId: z.string().uuid(),
9774
+ workspaceId: z.string().uuid(),
9775
+ registrationId: z.string().uuid(),
9776
+ provider: PrReviewProvider,
9777
+ repositoryUri: z.string(),
9778
+ repositoryFullName: z.string(),
9779
+ providerRepositoryId: z.string(),
9780
+ installationId: z.string().nullable(),
9781
+ projectId: z.string().nullable(),
9782
+ model: z.string().nullable(),
9783
+ additionalInstructions: z.string().nullable(),
9784
+ status: z.enum(["active", "disabled"]),
9785
+ createdBySubjectId: z.string(),
9786
+ createdAt: z.string(),
9787
+ updatedAt: z.string(),
9788
+ }))();
9789
+ export type PrReviewRepositoryBinding = z.infer<typeof PrReviewRepositoryBinding>;
7375
9790
 
7376
9791
  export const EnablePackRequest = withVariableSetIdAlias({
7377
9792
  variableSetId: z.string().uuid().optional(),
@@ -7594,6 +10009,8 @@ export const OPENGENI_PERSONAL_SLACK_MCP_URL = "https://mcp.slack.com/mcp" as co
7594
10009
  export const OPENGENI_SLACK_BOT_CREDENTIAL_ROLE = "opengeni_slack_bot" as const;
7595
10010
  export const OPENGENI_SLACK_BOT_CREDENTIAL_LABEL = "OpenGeni Slack bot" as const;
7596
10011
  export const OPENGENI_SLACK_BOT_SESSION_METADATA_KEY = "opengeniSlackBotConnectionId" as const;
10012
+ export const OpenGeniSlackBotDisplayName = z.enum(["OpenGeni", "OpenGeni Staging"]);
10013
+ export type OpenGeniSlackBotDisplayName = z.infer<typeof OpenGeniSlackBotDisplayName>;
7597
10014
  export const OpenGeniSlackBotConnectionMetadata = z
7598
10015
  .object({
7599
10016
  credentialRole: z.literal(OPENGENI_SLACK_BOT_CREDENTIAL_ROLE),
@@ -7602,7 +10019,7 @@ export const OpenGeniSlackBotConnectionMetadata = z
7602
10019
  slackTeamName: z.string().min(1).max(256),
7603
10020
  botUserId: z.string().min(1).max(64),
7604
10021
  botId: z.string().min(1).max(64),
7605
- botDisplayName: z.literal("OpenGeni"),
10022
+ botDisplayName: OpenGeniSlackBotDisplayName,
7606
10023
  verifiedAt: z.string().datetime({ offset: true }),
7607
10024
  })
7608
10025
  .passthrough();
@@ -7652,6 +10069,8 @@ export type FikenOAuthStartResponse = z.infer<typeof FikenOAuthStartResponse>;
7652
10069
 
7653
10070
  export const ConnectionMetadata = z.object({
7654
10071
  id: z.string().uuid(),
10072
+ /** Opaque owner-only handle used to manage this personal connection's grants. */
10073
+ authorityId: z.string().uuid().optional(),
7655
10074
  accountId: z.string().uuid(),
7656
10075
  workspaceId: z.string().uuid(),
7657
10076
  subjectId: z.string().nullable(),
@@ -7781,7 +10200,7 @@ export const SlackInstallationBinding = z.object({
7781
10200
  slackTeamName: z.string().min(1).max(256),
7782
10201
  botId: z.string().min(1).max(64),
7783
10202
  botUserId: z.string().min(1).max(64),
7784
- botDisplayName: z.literal("OpenGeni"),
10203
+ botDisplayName: OpenGeniSlackBotDisplayName,
7785
10204
  state: SlackInstallationBindingState,
7786
10205
  quarantineReason: z.string().min(1).nullable(),
7787
10206
  version: z.number().int().positive(),
@@ -8287,6 +10706,43 @@ export const IntegrationFacetDefinitionSummary = z
8287
10706
  .strict();
8288
10707
  export type IntegrationFacetDefinitionSummary = z.infer<typeof IntegrationFacetDefinitionSummary>;
8289
10708
 
10709
+ /**
10710
+ * Presentation-only consent copy for an integration or connector. Never grants
10711
+ * a scope, selects a connection, or replaces server-side authorization; the UI
10712
+ * falls back to generic copy for any omitted field.
10713
+ */
10714
+ export const IntegrationPresentation = z
10715
+ .object({
10716
+ providerName: z.string().min(1).max(120).optional(),
10717
+ icon: z.enum(["calendar", "cloud", "contacts", "files", "mail"]).optional(),
10718
+ introduction: z.string().min(1).max(500).optional(),
10719
+ capabilities: z
10720
+ .array(
10721
+ z
10722
+ .object({
10723
+ title: z.string().min(1).max(160),
10724
+ description: z.string().min(1).max(500),
10725
+ })
10726
+ .strict(),
10727
+ )
10728
+ .max(8)
10729
+ .optional(),
10730
+ permissionSummary: z.string().min(1).max(500).optional(),
10731
+ scopeLabels: z
10732
+ .record(
10733
+ z.string().min(1).max(1024),
10734
+ z
10735
+ .object({
10736
+ label: z.string().min(1).max(160),
10737
+ description: z.string().min(1).max(500),
10738
+ })
10739
+ .strict(),
10740
+ )
10741
+ .optional(),
10742
+ })
10743
+ .strict();
10744
+ export type IntegrationPresentation = z.infer<typeof IntegrationPresentation>;
10745
+
8290
10746
  export const IntegrationDefinitionSummary = z
8291
10747
  .object({
8292
10748
  id: z.string().min(1).max(128),
@@ -8305,6 +10761,7 @@ export const IntegrationDefinitionSummary = z
8305
10761
  scopes: z.array(z.string().min(1).max(1024)).max(256),
8306
10762
  })
8307
10763
  .strict(),
10764
+ presentation: IntegrationPresentation.optional(),
8308
10765
  facets: z.array(IntegrationFacetDefinitionSummary).max(128),
8309
10766
  })
8310
10767
  .strict();
@@ -8338,6 +10795,8 @@ export const IntegrationFacetBindingSummary = z
8338
10795
  lastErrorCode: z.string().min(1).max(120).nullable(),
8339
10796
  createdAt: z.string().datetime({ offset: true }),
8340
10797
  updatedAt: z.string().datetime({ offset: true }),
10798
+ directlyOwned: z.boolean(),
10799
+ owners: z.array(CapabilityComponentOwner),
8341
10800
  })
8342
10801
  .strict();
8343
10802
  export type IntegrationFacetBindingSummary = z.infer<typeof IntegrationFacetBindingSummary>;
@@ -8860,11 +11319,59 @@ export const UninstallPluginResult = z
8860
11319
  .strict();
8861
11320
  export type UninstallPluginResult = z.infer<typeof UninstallPluginResult>;
8862
11321
 
11322
+ export const SessionBackgroundCommandState = z.enum(["running", "stopping", "exited", "lost"]);
11323
+ export type SessionBackgroundCommandState = z.infer<typeof SessionBackgroundCommandState>;
11324
+
11325
+ export const SessionBackgroundCommandProvider = z.enum(["managed", "connected_machine"]);
11326
+ export type SessionBackgroundCommandProvider = z.infer<typeof SessionBackgroundCommandProvider>;
11327
+
11328
+ export const SessionBackgroundCommandActivity = z
11329
+ .object({
11330
+ state: z.enum(["running", "stopping"]),
11331
+ count: z.number().int().positive(),
11332
+ })
11333
+ .strict();
11334
+ export type SessionBackgroundCommandActivity = z.infer<typeof SessionBackgroundCommandActivity>;
11335
+
11336
+ export const SessionBackgroundCommand = z
11337
+ .object({
11338
+ id: z.string().uuid(),
11339
+ workspaceId: z.string().uuid(),
11340
+ sessionId: z.string().uuid(),
11341
+ provider: SessionBackgroundCommandProvider,
11342
+ state: SessionBackgroundCommandState,
11343
+ commandPreview: z.string().max(512),
11344
+ cancelRequestedAt: z.string().nullable(),
11345
+ exitCode: z.number().int().nullable(),
11346
+ settlementReason: z.string().nullable(),
11347
+ startedAt: z.string(),
11348
+ settledAt: z.string().nullable(),
11349
+ updatedAt: z.string(),
11350
+ })
11351
+ .strict();
11352
+ export type SessionBackgroundCommand = z.infer<typeof SessionBackgroundCommand>;
11353
+
11354
+ export const SessionBackgroundCommandListResponse = z
11355
+ .object({ commands: z.array(SessionBackgroundCommand).max(1000) })
11356
+ .strict();
11357
+ export type SessionBackgroundCommandListResponse = z.infer<
11358
+ typeof SessionBackgroundCommandListResponse
11359
+ >;
11360
+
11361
+ export const CancelSessionBackgroundCommandResult = z
11362
+ .object({ command: SessionBackgroundCommand, accepted: z.boolean() })
11363
+ .strict();
11364
+ export type CancelSessionBackgroundCommandResult = z.infer<
11365
+ typeof CancelSessionBackgroundCommandResult
11366
+ >;
11367
+
8863
11368
  export const Session = z.object({
8864
11369
  id: z.string().uuid(),
8865
11370
  workspaceId: z.string().uuid(),
8866
11371
  accountId: z.string().uuid(),
8867
11372
  status: SessionStatus,
11373
+ /** Additive list projection. Detail reads may omit it. */
11374
+ backgroundCommandActivity: SessionBackgroundCommandActivity.optional(),
8868
11375
  initialMessage: z.string(),
8869
11376
  title: z.string().nullable(),
8870
11377
  titleSource: z.enum(["user", "agent"]).nullable(),
@@ -8893,6 +11400,8 @@ export const Session = z.object({
8893
11400
  createdBy: TurnInitiator,
8894
11401
  createdByContext: TurnInitiatorContext,
8895
11402
  model: z.string(),
11403
+ reasoningEffort: ReasoningEffort,
11404
+ latencyMode: LatencyMode,
8896
11405
  sandboxBackend: SandboxBackend,
8897
11406
  // The OS the session's box runs. Defaults to 'linux' (today's only OS).
8898
11407
  sandboxOs: SandboxOs,
@@ -8976,6 +11485,17 @@ export const Session = z.object({
8976
11485
  pinnedAt: z.string().nullable().default(null),
8977
11486
  /** Optimistic pin-state revision; zero represents an absent pin relation. */
8978
11487
  pinVersion: z.number().int().nonnegative().default(0),
11488
+ /** Personal explicit acknowledgment state; opening the session never clears it. */
11489
+ unread: z.boolean().default(false),
11490
+ /** Personal power-user label for work the member intends to continue. */
11491
+ activelyWorking: z.boolean().default(false),
11492
+ /** Optimistic revision for unread/actively-working state. */
11493
+ attentionVersion: z.number().int().nonnegative().default(0),
11494
+ /** Personal archive state. Archived roots and their descendants leave the ordinary list. */
11495
+ archived: z.boolean().default(false),
11496
+ archivedAt: z.string().nullable().default(null),
11497
+ /** Optimistic archive-state revision; zero represents an absent personal relation. */
11498
+ archiveVersion: z.number().int().nonnegative().default(0),
8979
11499
  /**
8980
11500
  * Server-authoritative hierarchy summary populated on session-list reads.
8981
11501
  * Detail reads may omit it. The rail uses this instead of guessing a tree
@@ -8990,10 +11510,24 @@ export const Session = z.object({
8990
11510
  attentionDescendants: z.number().int().nonnegative(),
8991
11511
  pausedDescendants: z.number().int().nonnegative(),
8992
11512
  failedDescendants: z.number().int().nonnegative(),
11513
+ unreadDescendants: z.number().int().nonnegative().optional(),
11514
+ activelyWorkingDescendants: z.number().int().nonnegative().optional(),
11515
+ /**
11516
+ * Earliest moment one of the counted `attentionDescendants` entered
11517
+ * `requires_action` (the oldest still-open `requires_action` turn among
11518
+ * those descendants). Null when none is waiting; omitted by older servers.
11519
+ */
11520
+ attentionSince: z.string().nullable().optional(),
8993
11521
  /** Counts are lower bounds rather than exact totals when true. */
8994
11522
  truncated: z.boolean().default(false),
8995
11523
  })
8996
11524
  .optional(),
11525
+ /**
11526
+ * When this session's own open turn entered `requires_action`. Populated by
11527
+ * list and lineage reads for sessions whose status is `requires_action`;
11528
+ * null otherwise and omitted by older servers or detail reads.
11529
+ */
11530
+ requiresActionSince: z.string().nullable().optional(),
8997
11531
  createdAt: z.string(),
8998
11532
  updatedAt: z.string(),
8999
11533
  });
@@ -9102,6 +11636,7 @@ export type SessionLineageResponse = z.infer<typeof SessionLineageResponse>;
9102
11636
 
9103
11637
  export const SessionEventType = z.enum([
9104
11638
  "session.created",
11639
+ "session.personal_resources.attached",
9105
11640
  "session.visibility.changed",
9106
11641
  // Defensive read/transport projection for a malformed or historically
9107
11642
  // oversized retained event envelope. The original row stays durable; this
@@ -9130,13 +11665,20 @@ export const SessionEventType = z.enum([
9130
11665
  "turn.superseded",
9131
11666
  "turn.recovery.requested",
9132
11667
  "turn.capacity_waiting",
11668
+ // Compact, attempt-fenced user-visible worker preparation checkpoints. The
11669
+ // payload phase is a closed enum and terminal events carry durationMs; no
11670
+ // session-specific value is ever promoted into Prometheus labels.
11671
+ "turn.startup.phase.started",
11672
+ "turn.startup.phase.completed",
11673
+ "turn.startup.phase.failed",
9133
11674
  "agent.message.delta",
9134
11675
  "agent.message.completed",
9135
11676
  "agent.reasoning.delta",
9136
11677
  "agent.toolCall.created",
9137
11678
  "agent.toolCall.output",
9138
- // Attempt-fenced Codex Responses lifecycle metadata (request identity,
9139
- // deadlines, first-byte/terminal phase, provider request id). Never body/auth.
11679
+ // Attempt-fenced provider Responses lifecycle metadata (request identity,
11680
+ // liveness policy, first-event/terminal phase, provider request id). Never
11681
+ // request body, credentials, or model output.
9140
11682
  "agent.model.request",
9141
11683
  "agent.model.usage",
9142
11684
  "tool.auth_needed",
@@ -9149,6 +11691,8 @@ export const SessionEventType = z.enum([
9149
11691
  "sandbox.operation.started",
9150
11692
  "sandbox.operation.completed",
9151
11693
  "sandbox.operation.failed",
11694
+ "session.command.backgrounded",
11695
+ "session.command.finished",
9152
11696
  "sandbox.command.output.delta",
9153
11697
  "artifact.created",
9154
11698
  "goal.set",
@@ -9160,6 +11704,7 @@ export const SessionEventType = z.enum([
9160
11704
  "goal.paused",
9161
11705
  "goal.resumed",
9162
11706
  "goal.cleared",
11707
+ "goal.held",
9163
11708
  "goal.continuation",
9164
11709
  "system.update.pending",
9165
11710
  "system.update.delivered",
@@ -9352,6 +11897,7 @@ export const SESSION_EVENT_RAW_DELTA_TYPES = [
9352
11897
  export const SESSION_EVENT_SEMANTIC_CLASS_TYPES = {
9353
11898
  control: [
9354
11899
  "session.status.changed",
11900
+ "session.command.backgrounded",
9355
11901
  "session.requiresAction",
9356
11902
  "session.humanInput.requested",
9357
11903
  "user.pause",
@@ -9366,6 +11912,7 @@ export const SESSION_EVENT_SEMANTIC_CLASS_TYPES = {
9366
11912
  "goal.paused",
9367
11913
  "goal.resumed",
9368
11914
  "goal.cleared",
11915
+ "goal.held",
9369
11916
  "goal.continuation",
9370
11917
  "system.update.pending",
9371
11918
  "system.update.delivered",
@@ -9398,6 +11945,7 @@ export const SESSION_EVENT_SEMANTIC_CLASS_TYPES = {
9398
11945
  "recording.available",
9399
11946
  "recording.failed",
9400
11947
  "terminal.pty.exited",
11948
+ "session.command.finished",
9401
11949
  ],
9402
11950
  failure: [
9403
11951
  "session.event.envelope_omitted",
@@ -9724,7 +12272,9 @@ export type FsNodeType = z.infer<typeof FsNodeType>;
9724
12272
  // depth>0; the tree lazy-expands via repeated depth-1 lists at deeper paths.
9725
12273
  export interface FsTreeNode {
9726
12274
  name: string;
9727
- path: string; // workspace-relative POSIX, no leading slash
12275
+ // Same namespace as the request: workspace-relative for relative requests,
12276
+ // canonical target path for requests rooted at FileSystem.root.
12277
+ path: string;
9728
12278
  type: z.infer<typeof FsNodeType>;
9729
12279
  sizeBytes: number | null; // null for dirs
9730
12280
  mtimeMs: number | null;
@@ -9746,7 +12296,9 @@ export const FsTreeNode: z.ZodType<FsTreeNode> = z.lazy(() =>
9746
12296
  ) as z.ZodType<FsTreeNode>;
9747
12297
 
9748
12298
  export const FsListRequest = z.object({
9749
- path: z.string().default(""), // "" = workspace root
12299
+ // "" = workspace root; canonical absolute paths must stay within the
12300
+ // selected target's advertised FileSystem.root.
12301
+ path: z.string().default(""),
9750
12302
  depth: z.number().int().min(0).max(8).default(1),
9751
12303
  maxEntries: z.number().int().positive().max(20_000).default(2_000),
9752
12304
  includeHidden: z.boolean().default(true),
@@ -11268,6 +13820,8 @@ export const SessionControlResponse = z.object({
11268
13820
  });
11269
13821
  export type SessionControlResponse = z.infer<typeof SessionControlResponse>;
11270
13822
 
13823
+ export const SESSION_INSTRUCTIONS_MAX_CHARACTERS = 65_536;
13824
+
11271
13825
  export const CreateSessionRequest = withVariableSetIdAlias(
11272
13826
  {
11273
13827
  /**
@@ -11277,6 +13831,12 @@ export const CreateSessionRequest = withVariableSetIdAlias(
11277
13831
  * identity or authorization from the UUID.
11278
13832
  */
11279
13833
  requestedSessionId: z.string().uuid().optional(),
13834
+ /** Top-level omission is workspace-visible. Agent-child omission inherits
13835
+ * the exact parent visibility; cross-visibility child creation is rejected.
13836
+ * Top-level private creation is an activated managed-cookie owning-human
13837
+ * capability, while a private child uses an exact live-parent-attempt
13838
+ * database capability. Both commit atomically. */
13839
+ visibility: SessionVisibility.default("workspace"),
11280
13840
  initialMessage: z.string().min(1).optional(),
11281
13841
  // Creates the durable session shell without fabricating a user message or
11282
13842
  // starting an underlying agent turn. Realtime can then become the first
@@ -11291,10 +13851,10 @@ export const CreateSessionRequest = withVariableSetIdAlias(
11291
13851
  // agentInstructions rides, composed AFTER the workspace persona so it refines
11292
13852
  // it for this one session — how a host delivers per-agent-type prompts without
11293
13853
  // leaking them into the user-visible timeline (it is NEVER emitted as an
11294
- // event, unlike goal/initialMessage). Trimmed, non-empty. The 32768-char cap
11295
- // matches the codebase's largest free-form string convention (workspace
11296
- // variable set variable values). Absent ⇒ byte-identical to today.
11297
- instructions: z.string().trim().min(1).max(32768).optional(),
13854
+ // event, unlike goal/initialMessage). Trimmed, non-empty, and bounded by the
13855
+ // shared durable session-instruction contract. Absent byte-identical to
13856
+ // today.
13857
+ instructions: z.string().trim().min(1).max(SESSION_INSTRUCTIONS_MAX_CHARACTERS).optional(),
11298
13858
  // Immutable prompt-policy role binding for matching one activated role
11299
13859
  // policy. This never derives from or grants a human workspace membership
11300
13860
  // role. Existing callers may continue to use normalized metadata.role as a
@@ -11322,11 +13882,11 @@ export const CreateSessionRequest = withVariableSetIdAlias(
11322
13882
  // machine (race-free: the pointer is committed before the worker turn
11323
13883
  // workflow can read it). An invalid/unowned/offline target fails the create.
11324
13884
  targetSandboxId: z.string().uuid().optional(),
11325
- // The working directory the targeted machine runs the session under the
11326
- // path/cwd base for its agent exec, terminal, and file dock. Free-form pass-
11327
- // through: a launch-workspace_root-relative subdir or an absolute machine path
11328
- // (the agent's resolve_cwd handles both). Only valid WITH targetSandboxId
11329
- // (workingDir alone is a 422); omitted the machine's default workspace_root.
13885
+ // The working directory the targeted machine runs the session under. It may
13886
+ // be absolute or relative to the machine's persisted Hello root; the server
13887
+ // stores the resolved absolute value. Tilde is rejected because the control
13888
+ // plane has no authenticated home-directory fact. Only valid WITH
13889
+ // targetSandboxId; omitted selects the reported root.
11330
13890
  workingDir: z.string().min(1).optional(),
11331
13891
  // Variable set attachment is fixed at session creation; follow-up
11332
13892
  // user.message events cannot switch or add one.
@@ -11351,9 +13911,10 @@ export const CreateSessionRequest = withVariableSetIdAlias(
11351
13911
  // is an independent create).
11352
13912
  idempotencyKey: z.string().min(1).max(200).optional(),
11353
13913
  // The exact actor-private pre-session draft revision represented by this
11354
- // create. The durable initializer consumes only this revision. A newer draft
11355
- // written by a sibling tab survives, while every failed pre-initialization
11356
- // create leaves the submitted draft intact.
13914
+ // create. An ordinary create consumes only this revision. A realtime create
13915
+ // preserves the editable draft and atomically updates only successful-create
13916
+ // selection history. A newer sibling draft survives, while every failed
13917
+ // pre-initialization create leaves the submitted draft intact.
11357
13918
  expectedNewSessionDraftRevision: z.number().int().nonnegative().optional(),
11358
13919
  // A child may lower its inherited limit freely; an increase requires
11359
13920
  // workspace:admin and is checked again at the DB transaction boundary.
@@ -11377,9 +13938,16 @@ export const CreateSessionRequest = withVariableSetIdAlias(
11377
13938
  // permission. Credential headers are write-only: create responses and events
11378
13939
  // expose only SessionMcpServerMetadata.
11379
13940
  mcpServers: z.array(SessionMcpServerInput).max(SESSION_MCP_SERVERS_MAX).default([]),
13941
+ /** Explicit personal-connection grants for the initial accepted turn. */
13942
+ connectionAuthorities: McpConnectionAuthoritySelections.default([]),
13943
+ /** Atomic owner issuance for the selected personal Variable Set/Rig closure. */
13944
+ personalResourceAttachment: PersonalResourceAttachmentIntent.optional(),
11380
13945
  // Shared-sandbox placement (addendum 05 §D.1). Three-way union; OMITTED ⇒
11381
13946
  // today's behavior (a context-dependent default resolved server-side: from
11382
- // inside a session → "shared" with the creator's box, top-level → "new").
13947
+ // inside a session → "shared" with the creator's box, top-level → "new"),
13948
+ // except a named targetSandboxId is always an own-box create ("new") because
13949
+ // a machine target is a different compute home. Explicit "shared"/{groupId}
13950
+ // plus a machine target is a 422.
11383
13951
  // - "shared": join the CREATOR's box. Requires a parent session (inferred
11384
13952
  // from the worker-signed sessionId claim, never caller-supplied);
11385
13953
  // top-level "shared" is a 422.
@@ -11421,9 +13989,39 @@ export const CreateSessionRequest = withVariableSetIdAlias(
11421
13989
  message: "modelContext requires an initialMessage; attach it to a realtime entry instead",
11422
13990
  });
11423
13991
  }
13992
+ if (value.startMode === "realtime" && value.connectionAuthorities.length > 0) {
13993
+ context.addIssue({
13994
+ code: z.ZodIssueCode.custom,
13995
+ path: ["connectionAuthorities"],
13996
+ message:
13997
+ "connectionAuthorities require an accepted initial turn and are not supported by realtime session staging",
13998
+ });
13999
+ }
14000
+ if (value.startMode === "realtime" && value.personalResourceAttachment !== undefined) {
14001
+ context.addIssue({
14002
+ code: z.ZodIssueCode.custom,
14003
+ path: ["personalResourceAttachment"],
14004
+ message:
14005
+ "personalResourceAttachment requires an accepted initial turn and is not supported by realtime session staging",
14006
+ });
14007
+ }
14008
+ if (value.personalResourceAttachment?.expectedAuthorityEpoch !== undefined) {
14009
+ context.addIssue({
14010
+ code: z.ZodIssueCode.custom,
14011
+ path: ["personalResourceAttachment", "expectedAuthorityEpoch"],
14012
+ message: "new-session attachment authority epoch is derived by the server",
14013
+ });
14014
+ }
11424
14015
  });
11425
14016
  export type CreateSessionRequest = z.infer<typeof CreateSessionRequest>;
11426
14017
 
14018
+ export const SessionTenancyCreateCapabilities = z.object({
14019
+ activated: z.boolean(),
14020
+ canCreatePrivate: z.boolean(),
14021
+ reason: z.enum(["available", "not_activated", "managed_session_required", "unavailable"]),
14022
+ });
14023
+ export type SessionTenancyCreateCapabilities = z.infer<typeof SessionTenancyCreateCapabilities>;
14024
+
11427
14025
  // Generic, host-neutral structured human input. One model tool call creates one
11428
14026
  // request containing one or more questions; the durable response resumes that
11429
14027
  // exact call. This is deliberately distinct from tool approval: an answer,
@@ -11447,6 +14045,9 @@ export const HumanInputQuestion = z
11447
14045
  helpText: z.string().max(2048).nullable().optional(),
11448
14046
  options: z.array(HumanInputOption).max(20).default([]),
11449
14047
  required: z.boolean().default(true),
14048
+ // Retained on the wire for older hosts. OpenGeni's stock runtime and
14049
+ // surfaces always expose Other for choice questions, including requests
14050
+ // that were persisted before that became the default behavior.
11450
14051
  allowOther: z.boolean().default(false),
11451
14052
  // Selection bounds only — agents invent useless text char mins/maxes.
11452
14053
  // Answer strings stay platform-capped on HumanInputAnswer (~8192).
@@ -11619,9 +14220,13 @@ export const SessionUserMessagePayload = z
11619
14220
  // Header-value rotation only. URL/name/tool settings are immutable after
11620
14221
  // session create; persisted events expose metadata, never header values.
11621
14222
  mcpCredentialUpdates: z.array(SessionMcpCredentialUpdateInput).optional(),
14223
+ /** Explicit personal-connection grants for this exact logical turn. */
14224
+ connectionAuthorities: McpConnectionAuthoritySelections.default([]),
14225
+ personalResourceAttachment: PersonalResourceAttachmentIntent.optional(),
11622
14226
  })
11623
14227
  .strict()
11624
- .superRefine(requireMessageTextOrAnnotations);
14228
+ .superRefine(requireMessageTextOrAnnotations)
14229
+ .superRefine(requireEstablishedPersonalResourceEpoch);
11625
14230
  export type SessionUserMessagePayload = z.infer<typeof SessionUserMessagePayload>;
11626
14231
 
11627
14232
  export const ClientSessionEvent = z.discriminatedUnion("type", [
@@ -11664,17 +14269,36 @@ export const SteerSessionMessageRequest = z
11664
14269
  controlEtag: z.string().min(1).optional(),
11665
14270
  expectedDraftRevision: z.number().int().nonnegative().optional(),
11666
14271
  mcpCredentialUpdates: z.array(SessionMcpCredentialUpdateInput).optional(),
14272
+ /** Explicit personal-connection grants for this exact steered turn. */
14273
+ connectionAuthorities: McpConnectionAuthoritySelections.default([]),
14274
+ personalResourceAttachment: PersonalResourceAttachmentIntent.optional(),
11667
14275
  })
11668
14276
  .strict()
11669
- .superRefine(requireMessageTextOrAnnotations);
14277
+ .superRefine(requireMessageTextOrAnnotations)
14278
+ .superRefine(requireEstablishedPersonalResourceEpoch);
11670
14279
  export type SteerSessionMessageRequest = z.infer<typeof SteerSessionMessageRequest>;
11671
14280
 
11672
14281
  export const SteerSessionMessageResponse = z.object({
11673
14282
  accepted: SessionEvent,
11674
14283
  turn: SessionTurn,
14284
+ receipt: SessionCommandReceipt,
14285
+ routing: SessionPromptRouting,
14286
+ interruptionCount: z.number().int().nonnegative(),
14287
+ replay: z.boolean(),
11675
14288
  });
11676
14289
  export type SteerSessionMessageResponse = z.infer<typeof SteerSessionMessageResponse>;
11677
14290
 
14291
+ export const SubmitComposerDraftResponse = z.object({
14292
+ accepted: SessionEvent,
14293
+ turn: SessionTurn,
14294
+ draft: ComposerDraft,
14295
+ receipt: SessionCommandReceipt,
14296
+ routing: SessionPromptRouting,
14297
+ interruptionCount: z.number().int().nonnegative(),
14298
+ replay: z.boolean(),
14299
+ });
14300
+ export type SubmitComposerDraftResponse = z.infer<typeof SubmitComposerDraftResponse>;
14301
+
11678
14302
  export const SessionBusMessage = z.object({
11679
14303
  workspaceId: z.string().uuid(),
11680
14304
  sessionId: z.string().uuid(),
@@ -11765,6 +14389,7 @@ export const ClientAuthConfig = z.discriminatedUnion("mode", [
11765
14389
  z.object({
11766
14390
  mode: z.literal("managedSession"),
11767
14391
  session: z.literal("cookie"),
14392
+ emailVerificationRequired: z.boolean().default(true),
11768
14393
  }),
11769
14394
  ]);
11770
14395
  export type ClientAuthConfig = z.infer<typeof ClientAuthConfig>;
@@ -12029,6 +14654,9 @@ export type DeviceEnrollmentStartResponse = z.infer<typeof DeviceEnrollmentStart
12029
14654
  export const DeviceEnrollmentApproveRequest = z.object({
12030
14655
  userCode: z.string().min(1).max(64),
12031
14656
  allowScreenControl: z.boolean().default(false),
14657
+ // Human-approved machines are private by default. Workspace/organization
14658
+ // publication is an explicit consent choice at this same loud boundary.
14659
+ scope: ResourceAuthorityScope.default("user"),
12032
14660
  });
12033
14661
  export type DeviceEnrollmentApproveRequest = z.infer<typeof DeviceEnrollmentApproveRequest>;
12034
14662
 
@@ -12101,6 +14729,8 @@ export type DeviceEnrollmentPollResponse = z.infer<typeof DeviceEnrollmentPollRe
12101
14729
  // GET /enrollments — a workspace's machines (the Machines dashboard surface).
12102
14730
  export const EnrollmentSummary = z.object({
12103
14731
  id: z.string().uuid(),
14732
+ scope: ResourceAuthorityScope,
14733
+ generation: z.number().int().positive(),
12104
14734
  pubkey: z.string(),
12105
14735
  exposure: z.literal("whole-machine"),
12106
14736
  hasDisplay: z.boolean(),
@@ -12297,9 +14927,137 @@ export const MachineState = z.enum([
12297
14927
  ]);
12298
14928
  export type MachineState = z.infer<typeof MachineState>;
12299
14929
 
12300
- export const MachineKind = z.enum(["modal", "selfhosted"]);
14930
+ export const MachineKind = z.enum(["modal", "selfhosted", "opensandbox"]);
12301
14931
  export type MachineKind = z.infer<typeof MachineKind>;
12302
14932
 
14933
+ /** Diagnostic projection of the single live Connected-Machine runner authority.
14934
+ * It contains no bearer or NATS subject material. `supersededCount` is derived
14935
+ * from the monotonic generation; duplicate-denial evidence records valid
14936
+ * competing processes that were prevented from receiving work. */
14937
+ export const MachineConnectionAuthority = z.object({
14938
+ state: z.enum(["not_applicable", "unclaimed", "active", "expired"]),
14939
+ generation: z.number().int().nonnegative(),
14940
+ supersededCount: z.number().int().nonnegative(),
14941
+ leaseExpiresAt: z.string().nullable(),
14942
+ duplicateRunnerDeniedCount: z.number().int().nonnegative(),
14943
+ duplicateRunnerDeniedAt: z.string().nullable(),
14944
+ });
14945
+ export type MachineConnectionAuthority = z.infer<typeof MachineConnectionAuthority>;
14946
+
14947
+ export const MachineRuntimeCapabilities = z.object({
14948
+ exec: z.boolean(),
14949
+ filesystem: z.boolean(),
14950
+ git: z.boolean(),
14951
+ pty: z.boolean(),
14952
+ desktop: z.boolean(),
14953
+ opStream: z.boolean(),
14954
+ browserBridge: z.boolean(),
14955
+ operationResourcePolicy: z.boolean(),
14956
+ operationCpuQuota: z.boolean(),
14957
+ });
14958
+ export type MachineRuntimeCapabilities = z.infer<typeof MachineRuntimeCapabilities>;
14959
+
14960
+ export const MachineUpdateStatus = z.enum([
14961
+ "requested",
14962
+ "accepted",
14963
+ "waiting_for_idle",
14964
+ "downloading",
14965
+ "verifying",
14966
+ "applying",
14967
+ "restarting",
14968
+ "succeeded",
14969
+ "failed",
14970
+ ]);
14971
+ export type MachineUpdateStatus = z.infer<typeof MachineUpdateStatus>;
14972
+
14973
+ export const MachineUpdateState = z.object({
14974
+ operationId: z.string().uuid(),
14975
+ status: MachineUpdateStatus,
14976
+ targetVersion: z.string(),
14977
+ expectedBinarySha256: z
14978
+ .string()
14979
+ .regex(/^[0-9a-f]{64}$/)
14980
+ .nullable(),
14981
+ errorCode: z.string().nullable(),
14982
+ retryable: z.boolean(),
14983
+ rolledBack: z.boolean(),
14984
+ requestedAt: z.string(),
14985
+ updatedAt: z.string(),
14986
+ completedAt: z.string().nullable(),
14987
+ });
14988
+ export type MachineUpdateState = z.infer<typeof MachineUpdateState>;
14989
+
14990
+ export const MachineRuntime = z.object({
14991
+ installedVersion: z.string().nullable(),
14992
+ binarySha256: z
14993
+ .string()
14994
+ .regex(/^[0-9a-f]{64}$/)
14995
+ .nullable(),
14996
+ updateChannel: z.enum(["stable", "beta"]).nullable(),
14997
+ desiredVersion: z.string().nullable(),
14998
+ versionState: z.enum(["unknown", "current", "outdated", "ahead", "updating", "update_failed"]),
14999
+ capabilities: MachineRuntimeCapabilities,
15000
+ update: MachineUpdateState.nullable(),
15001
+ });
15002
+ export type MachineRuntime = z.infer<typeof MachineRuntime>;
15003
+
15004
+ export const UpdateMachineAgentResponse = z.object({
15005
+ operationId: z.string().uuid(),
15006
+ accepted: z.boolean(),
15007
+ targetVersion: z.string(),
15008
+ });
15009
+ export type UpdateMachineAgentResponse = z.infer<typeof UpdateMachineAgentResponse>;
15010
+
15011
+ const OperationMemoryBytes = z.number().int().positive().max(Number.MAX_SAFE_INTEGER).nullable();
15012
+ const OperationCpuMillicores = z.number().int().positive().max(0xffff_ffff).nullable();
15013
+
15014
+ function operationPolicyShape(
15015
+ value: {
15016
+ memoryMaxBytes: number | null;
15017
+ memoryHighBytes: number | null;
15018
+ cpuMaxMillicores?: number | null | undefined;
15019
+ },
15020
+ ctx: z.RefinementCtx,
15021
+ ): void {
15022
+ if (
15023
+ value.memoryMaxBytes !== null &&
15024
+ value.memoryHighBytes !== null &&
15025
+ value.memoryHighBytes > value.memoryMaxBytes
15026
+ ) {
15027
+ ctx.addIssue({
15028
+ code: z.ZodIssueCode.custom,
15029
+ path: ["memoryHighBytes"],
15030
+ message: "memoryHighBytes cannot exceed memoryMaxBytes",
15031
+ });
15032
+ }
15033
+ }
15034
+
15035
+ /** Optional per-connection command resource policy. CPU is a period-independent
15036
+ * millicore ratio; runners choose a documented representable cgroup period. Null
15037
+ * values are unrestricted; revision is the optimistic concurrency fence. */
15038
+ export const MachineOperationPolicy = z
15039
+ .object({
15040
+ memoryMaxBytes: OperationMemoryBytes,
15041
+ memoryHighBytes: OperationMemoryBytes,
15042
+ cpuMaxMillicores: OperationCpuMillicores,
15043
+ revision: z.number().int().nonnegative(),
15044
+ updatedAt: z.string().nullable(),
15045
+ })
15046
+ .superRefine(operationPolicyShape);
15047
+ export type MachineOperationPolicy = z.infer<typeof MachineOperationPolicy>;
15048
+
15049
+ export const UpdateMachineOperationPolicyRequest = z
15050
+ .object({
15051
+ memoryMaxBytes: OperationMemoryBytes,
15052
+ memoryHighBytes: OperationMemoryBytes,
15053
+ cpuMaxMillicores: OperationCpuMillicores.optional(),
15054
+ expectedRevision: z.number().int().nonnegative(),
15055
+ })
15056
+ .superRefine(operationPolicyShape);
15057
+ export type UpdateMachineOperationPolicyRequest = z.infer<
15058
+ typeof UpdateMachineOperationPolicyRequest
15059
+ >;
15060
+
12303
15061
  /**
12304
15062
  * A machine as the Machines dashboard renders it. The workspace's enrolled
12305
15063
  * selfhosted machines PLUS the session's synthetic Modal group box
@@ -12311,6 +15069,8 @@ export type MachineKind = z.infer<typeof MachineKind>;
12311
15069
  export const MachineView = z.object({
12312
15070
  sandboxId: z.string(),
12313
15071
  enrollmentId: z.string().nullable(),
15072
+ scope: ResourceAuthorityScope.default("workspace"),
15073
+ generation: z.number().int().positive().default(1),
12314
15074
  name: z.string(),
12315
15075
  kind: MachineKind,
12316
15076
  state: MachineState,
@@ -12329,6 +15089,12 @@ export const MachineView = z.object({
12329
15089
  allowScreenControl: z.boolean(),
12330
15090
  sharedSessionCount: z.number().int(),
12331
15091
  lastSeenAt: z.string().nullable(),
15092
+ connectionAuthority: MachineConnectionAuthority,
15093
+ // Exact connected-agent build/capabilities and current update operation. Null
15094
+ // for managed/session sandboxes and pre-runtime-reporting synthetic rows.
15095
+ runtime: MachineRuntime.nullable().default(null),
15096
+ // Per-enrollment desired policy. Null for managed/session sandboxes.
15097
+ operationPolicy: MachineOperationPolicy.nullable().default(null),
12332
15098
  metrics: MetricSample.nullable(),
12333
15099
  });
12334
15100
  export type MachineView = z.infer<typeof MachineView>;
@@ -12405,8 +15171,9 @@ function defineModelContractSchema<Schema>(factory: () => Schema): Schema {
12405
15171
  return factory();
12406
15172
  }
12407
15173
 
12408
- export const ModelCapabilitySupportV1 =
12409
- /* @__PURE__ */ defineModelContractSchema(() => z.enum(["supported", "unsupported", "unknown"]));
15174
+ export const ModelCapabilitySupportV1 = /* @__PURE__ */ defineModelContractSchema(() =>
15175
+ z.enum(["supported", "unsupported", "unknown"]),
15176
+ );
12410
15177
  export type ModelCapabilitySupportV1 = z.infer<typeof ModelCapabilitySupportV1>;
12411
15178
 
12412
15179
  export const ModelCapabilityStateV1 = /* @__PURE__ */ defineModelContractSchema(() =>
@@ -12454,60 +15221,65 @@ export const ModelCapabilitiesV1 = /* @__PURE__ */ defineModelContractSchema(()
12454
15221
  );
12455
15222
  export type ModelCapabilitiesV1 = z.infer<typeof ModelCapabilitiesV1>;
12456
15223
 
12457
- export const ModelCredentialSourceV1 =
12458
- /* @__PURE__ */ defineModelContractSchema(() =>
12459
- z.union([
12460
- z
12461
- .object({
12462
- kind: z.literal("deployment"),
12463
- mechanism: z.enum(["api_key", "azure_ad_bearer"]),
12464
- })
12465
- .strict(),
12466
- z
12467
- .object({
12468
- kind: z.literal("connected_subscription"),
12469
- provider: z.enum(["codex", "xai"]),
12470
- })
12471
- .strict(),
12472
- z
12473
- .object({
12474
- kind: z.literal("workspace_connection"),
12475
- mechanism: z.literal("api_key"),
12476
- })
12477
- .strict(),
12478
- ]),
12479
- );
12480
- export type ModelCredentialSourceV1 = z.infer<typeof ModelCredentialSourceV1>;
12481
-
12482
- export const ModelBillingAttributionV1 =
12483
- /* @__PURE__ */ defineModelContractSchema(() =>
15224
+ export const ModelCredentialSourceV1 = /* @__PURE__ */ defineModelContractSchema(() =>
15225
+ z.union([
12484
15226
  z
12485
15227
  .object({
12486
- upstreamPayer: z.enum(["deployment", "workspace", "connected_subscription"]),
12487
- metering: z.enum(["opengeni_credits", "external"]),
15228
+ kind: z.literal("deployment"),
15229
+ mechanism: z.enum(["api_key", "azure_ad_bearer"]),
12488
15230
  })
12489
15231
  .strict(),
12490
- );
15232
+ z
15233
+ .object({
15234
+ kind: z.literal("connected_subscription"),
15235
+ provider: z.enum(["codex", "xai"]),
15236
+ })
15237
+ .strict(),
15238
+ z
15239
+ .object({
15240
+ kind: z.literal("workspace_connection"),
15241
+ mechanism: z.literal("api_key"),
15242
+ })
15243
+ .strict(),
15244
+ ]),
15245
+ );
15246
+ export type ModelCredentialSourceV1 = z.infer<typeof ModelCredentialSourceV1>;
15247
+
15248
+ const TurnExecutionCredentialSourceV1 = z.union([
15249
+ ModelCredentialSourceV1,
15250
+ z
15251
+ .object({
15252
+ kind: z.literal("deployment"),
15253
+ mechanism: z.literal("none"),
15254
+ })
15255
+ .strict(),
15256
+ ]);
15257
+
15258
+ export const ModelBillingAttributionV1 = /* @__PURE__ */ defineModelContractSchema(() =>
15259
+ z
15260
+ .object({
15261
+ upstreamPayer: z.enum(["deployment", "workspace", "connected_subscription"]),
15262
+ metering: z.enum(["opengeni_credits", "external"]),
15263
+ })
15264
+ .strict(),
15265
+ );
12491
15266
  export type ModelBillingAttributionV1 = z.infer<typeof ModelBillingAttributionV1>;
12492
15267
 
12493
15268
  export const TURN_EXECUTION_POLICY_METADATA_KEY = "turnExecutionPolicyV1" as const;
12494
15269
 
12495
- export const TurnExecutionModelSourceV1 =
12496
- /* @__PURE__ */ defineModelContractSchema(() =>
12497
- z.enum(["explicit", "session", "deployment", "continuation"]),
12498
- );
15270
+ export const TurnExecutionModelSourceV1 = /* @__PURE__ */ defineModelContractSchema(() =>
15271
+ z.enum(["explicit", "session", "deployment", "continuation"]),
15272
+ );
12499
15273
  export type TurnExecutionModelSourceV1 = z.infer<typeof TurnExecutionModelSourceV1>;
12500
15274
 
12501
- export const TurnExecutionReasoningSourceV1 =
12502
- /* @__PURE__ */ defineModelContractSchema(() =>
12503
- z.enum(["explicit", "session", "deployment", "continuation"]),
12504
- );
15275
+ export const TurnExecutionReasoningSourceV1 = /* @__PURE__ */ defineModelContractSchema(() =>
15276
+ z.enum(["explicit", "session", "deployment", "continuation"]),
15277
+ );
12505
15278
  export type TurnExecutionReasoningSourceV1 = z.infer<typeof TurnExecutionReasoningSourceV1>;
12506
15279
 
12507
- export const TurnExecutionLatencyModeSourceV1 =
12508
- /* @__PURE__ */ defineModelContractSchema(() =>
12509
- z.enum(["explicit", "session", "deployment", "continuation"]),
12510
- );
15280
+ export const TurnExecutionLatencyModeSourceV1 = /* @__PURE__ */ defineModelContractSchema(() =>
15281
+ z.enum(["explicit", "session", "deployment", "continuation"]),
15282
+ );
12511
15283
  export type TurnExecutionLatencyModeSourceV1 = z.infer<typeof TurnExecutionLatencyModeSourceV1>;
12512
15284
 
12513
15285
  /**
@@ -12535,7 +15307,7 @@ export const TurnExecutionPolicyV1 = /* @__PURE__ */ defineModelContractSchema((
12535
15307
  providerId: z.string().min(1),
12536
15308
  upstreamModelId: z.string().min(1),
12537
15309
  wireApi: z.enum(["responses", "chat"]),
12538
- credentialSource: ModelCredentialSourceV1,
15310
+ credentialSource: TurnExecutionCredentialSourceV1,
12539
15311
  billing: ModelBillingAttributionV1,
12540
15312
  definitionVersion: z.string().regex(/^sha256:[a-f0-9]{64}$/u),
12541
15313
  })
@@ -12709,60 +15481,59 @@ export const ClientModel = /* @__PURE__ */ defineModelContractSchema(() =>
12709
15481
  );
12710
15482
  export type ClientModel = z.infer<typeof ClientModel>;
12711
15483
 
12712
- export const ModelCredentialReadinessV1 =
12713
- /* @__PURE__ */ defineModelContractSchema(() =>
12714
- z
12715
- .object({
12716
- status: z.enum(["ready", "not_ready", "error"]),
12717
- reason: z
12718
- .enum([
12719
- "missing_credential",
12720
- "needs_reauth",
12721
- "prerequisites_missing",
12722
- "resolver_error",
12723
- "observation_stale",
12724
- ])
12725
- .nullable(),
12726
- basis: z.enum(["configuration", "connection", "resolver"]),
12727
- checkedAt: z.string().datetime().nullable(),
12728
- })
12729
- .strict()
12730
- .superRefine((readiness, context) => {
12731
- if ((readiness.status === "ready") !== (readiness.reason === null)) {
12732
- context.addIssue({
12733
- code: "custom",
12734
- path: ["reason"],
12735
- message: "ready credential state requires no reason; non-ready state requires a reason",
12736
- });
12737
- }
12738
- if ((readiness.status === "error") !== (readiness.reason === "resolver_error")) {
12739
- context.addIssue({
12740
- code: "custom",
12741
- path: ["reason"],
12742
- message:
12743
- "credential errors require resolver_error and resolver_error requires error status",
12744
- });
12745
- }
12746
- if (
12747
- readiness.basis === "resolver" &&
12748
- readiness.status === "ready" &&
12749
- readiness.checkedAt === null
12750
- ) {
12751
- context.addIssue({
12752
- code: "custom",
12753
- path: ["checkedAt"],
12754
- message: "resolver readiness requires an observation timestamp",
12755
- });
12756
- }
12757
- if (readiness.reason === "observation_stale" && readiness.checkedAt === null) {
12758
- context.addIssue({
12759
- code: "custom",
12760
- path: ["checkedAt"],
12761
- message: "a stale observation requires its observation timestamp",
12762
- });
12763
- }
12764
- }),
12765
- );
15484
+ export const ModelCredentialReadinessV1 = /* @__PURE__ */ defineModelContractSchema(() =>
15485
+ z
15486
+ .object({
15487
+ status: z.enum(["ready", "not_ready", "error"]),
15488
+ reason: z
15489
+ .enum([
15490
+ "missing_credential",
15491
+ "needs_reauth",
15492
+ "prerequisites_missing",
15493
+ "resolver_error",
15494
+ "observation_stale",
15495
+ ])
15496
+ .nullable(),
15497
+ basis: z.enum(["configuration", "connection", "resolver"]),
15498
+ checkedAt: z.string().datetime().nullable(),
15499
+ })
15500
+ .strict()
15501
+ .superRefine((readiness, context) => {
15502
+ if ((readiness.status === "ready") !== (readiness.reason === null)) {
15503
+ context.addIssue({
15504
+ code: "custom",
15505
+ path: ["reason"],
15506
+ message: "ready credential state requires no reason; non-ready state requires a reason",
15507
+ });
15508
+ }
15509
+ if ((readiness.status === "error") !== (readiness.reason === "resolver_error")) {
15510
+ context.addIssue({
15511
+ code: "custom",
15512
+ path: ["reason"],
15513
+ message:
15514
+ "credential errors require resolver_error and resolver_error requires error status",
15515
+ });
15516
+ }
15517
+ if (
15518
+ readiness.basis === "resolver" &&
15519
+ readiness.status === "ready" &&
15520
+ readiness.checkedAt === null
15521
+ ) {
15522
+ context.addIssue({
15523
+ code: "custom",
15524
+ path: ["checkedAt"],
15525
+ message: "resolver readiness requires an observation timestamp",
15526
+ });
15527
+ }
15528
+ if (readiness.reason === "observation_stale" && readiness.checkedAt === null) {
15529
+ context.addIssue({
15530
+ code: "custom",
15531
+ path: ["checkedAt"],
15532
+ message: "a stale observation requires its observation timestamp",
15533
+ });
15534
+ }
15535
+ }),
15536
+ );
12766
15537
  export type ModelCredentialReadinessV1 = z.infer<typeof ModelCredentialReadinessV1>;
12767
15538
 
12768
15539
  export const ModelAvailabilityV1 = /* @__PURE__ */ defineModelContractSchema(() =>
@@ -12785,21 +15556,21 @@ export const ModelAvailabilityV1 = /* @__PURE__ */ defineModelContractSchema(()
12785
15556
  );
12786
15557
  export type ModelAvailabilityV1 = z.infer<typeof ModelAvailabilityV1>;
12787
15558
 
12788
- export const WorkspaceModelCatalogModel =
12789
- /* @__PURE__ */ defineModelContractSchema(() =>
12790
- ClientModel.extend({
12791
- credentialReadiness: ModelCredentialReadinessV1,
12792
- availability: ModelAvailabilityV1,
12793
- }),
12794
- );
15559
+ export const WorkspaceModelCatalogModel = /* @__PURE__ */ defineModelContractSchema(() =>
15560
+ ClientModel.extend({
15561
+ credentialReadiness: ModelCredentialReadinessV1,
15562
+ /** Exact workspace-policy verdict without exposing provider identity. */
15563
+ policyAllowed: z.boolean().optional(),
15564
+ availability: ModelAvailabilityV1,
15565
+ }),
15566
+ );
12795
15567
  export type WorkspaceModelCatalogModel = z.infer<typeof WorkspaceModelCatalogModel>;
12796
15568
 
12797
- export const WorkspaceModelCatalogResponse =
12798
- /* @__PURE__ */ defineModelContractSchema(() =>
12799
- z.object({
12800
- models: z.array(WorkspaceModelCatalogModel),
12801
- }),
12802
- );
15569
+ export const WorkspaceModelCatalogResponse = /* @__PURE__ */ defineModelContractSchema(() =>
15570
+ z.object({
15571
+ models: z.array(WorkspaceModelCatalogModel),
15572
+ }),
15573
+ );
12803
15574
  export type WorkspaceModelCatalogResponse = z.infer<typeof WorkspaceModelCatalogResponse>;
12804
15575
 
12805
15576
  /**
@@ -12810,7 +15581,7 @@ export type WorkspaceModelCatalogResponse = z.infer<typeof WorkspaceModelCatalog
12810
15581
  * that rollout boundary. Mutating clients send this value in
12811
15582
  * `x-opengeni-api-contract`; the API rejects any other value before routing.
12812
15583
  */
12813
- export const OPENGENI_API_CONTRACT_REVISION = "2026-08-model-context-v1" as const;
15584
+ export const OPENGENI_API_CONTRACT_REVISION = "2026-08-atomic-session-fork-visibility-v1" as const;
12814
15585
  export const OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract" as const;
12815
15586
  /** Bounded request/response identifier shared by browser, ingress, and API diagnostics. */
12816
15587
  export const OPENGENI_CORRELATION_HEADER = "x-opengeni-correlation-id" as const;
@@ -12984,10 +15755,18 @@ export * from "./codex-fleet-policy";
12984
15755
  export * from "./xai-provider-account-authority";
12985
15756
  export * from "./workspace-instruction-policies";
12986
15757
  export * from "./company-profile";
15758
+ export * from "./company-brain";
12987
15759
  export * from "./workspace-learning-policy";
15760
+ export * from "./workspace-learning-administration";
12988
15761
  export * from "./workspace-state";
12989
15762
  export * from "./preference-registry";
12990
15763
  export * from "./scoped-knowledge";
15764
+ export * from "./company-brain-governed-writes";
15765
+ export * from "./governed-learning-evaluator";
15766
+ export * from "./governed-learning-activation";
12991
15767
  export * from "./knowledge";
12992
15768
  export * from "./task-notes";
12993
15769
  export * from "./canonical-human-identities";
15770
+ export * from "./organization-membership-lifecycle";
15771
+ export * from "./remember";
15772
+ export * from "./agent-authored-durable-text";