@opengeni/core 0.28.1 → 2.1.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 (99) hide show
  1. package/dist/access/index.d.ts +37 -0
  2. package/dist/application/new-session-drafts.d.ts +8 -1
  3. package/dist/application/session-commands.d.ts +18 -11
  4. package/dist/application/session-tenancy.d.ts +18 -0
  5. package/dist/application/user-resource-grants.d.ts +20 -0
  6. package/dist/{chunk-6WKPWE5S.js → chunk-H7X52RI2.js} +27 -18
  7. package/dist/chunk-H7X52RI2.js.map +1 -0
  8. package/dist/{chunk-JJU6I5XD.js → chunk-VCZFFEPB.js} +21 -9
  9. package/dist/chunk-VCZFFEPB.js.map +1 -0
  10. package/dist/{chunk-NYPMQ7NO.js → chunk-ZYCNLYGH.js} +29 -27
  11. package/dist/chunk-ZYCNLYGH.js.map +1 -0
  12. package/dist/dependencies.d.ts +12 -1
  13. package/dist/domain/automations.d.ts +51 -0
  14. package/dist/domain/company-brain-governed-writes.d.ts +70 -0
  15. package/dist/domain/company-profile-agent-admin.d.ts +24 -0
  16. package/dist/domain/connection-authority.d.ts +97 -0
  17. package/dist/domain/connection-authority.js +169 -0
  18. package/dist/domain/connection-authority.js.map +1 -0
  19. package/dist/domain/editable-artifacts/service.d.ts +1 -2
  20. package/dist/domain/environments.d.ts +1 -1
  21. package/dist/domain/github-repository-bindings.d.ts +117 -0
  22. package/dist/domain/governed-learning-activation.d.ts +16 -0
  23. package/dist/domain/governed-learning-evaluator.d.ts +6 -0
  24. package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
  25. package/dist/domain/memory-slack-delivery.d.ts +1 -4
  26. package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
  27. package/dist/domain/packs.d.ts +5 -1
  28. package/dist/domain/personal-connection-delegations.d.ts +44 -2
  29. package/dist/domain/pr-review.d.ts +149 -0
  30. package/dist/domain/remember.d.ts +69 -0
  31. package/dist/domain/resources.d.ts +23 -2
  32. package/dist/domain/scheduled-tasks.d.ts +43 -2
  33. package/dist/domain/session-tool-policy.d.ts +1 -1
  34. package/dist/domain/sessions.d.ts +43 -9
  35. package/dist/editable-artifact-live/application.d.ts +10 -2
  36. package/dist/editable-artifact-live/ports.d.ts +0 -1
  37. package/dist/editable-artifact-live/server.d.ts +0 -1
  38. package/dist/editable-artifact-live/ticket.d.ts +0 -1
  39. package/dist/editable-artifact-live/types.d.ts +5 -2
  40. package/dist/editable-artifact-live.js +2 -2
  41. package/dist/editable-artifacts.js +2 -4
  42. package/dist/index.d.ts +13 -1
  43. package/dist/index.js +4973 -1243
  44. package/dist/index.js.map +1 -1
  45. package/dist/rigs/index.d.ts +22 -5
  46. package/dist/sandbox/fleet.d.ts +34 -6
  47. package/dist/sandbox/routing.d.ts +15 -1
  48. package/dist/sandbox/runtime-settings.d.ts +39 -0
  49. package/package.json +16 -11
  50. package/src/access/index.ts +151 -31
  51. package/src/application/new-session-drafts.ts +32 -5
  52. package/src/application/session-commands.ts +305 -71
  53. package/src/application/session-tenancy.ts +321 -0
  54. package/src/application/user-resource-grants.ts +140 -0
  55. package/src/billing/limits.ts +25 -15
  56. package/src/dependencies.ts +12 -1
  57. package/src/domain/automations.ts +196 -0
  58. package/src/domain/capabilities.ts +7 -1
  59. package/src/domain/company-brain-governed-writes.ts +330 -0
  60. package/src/domain/company-profile-agent-admin.ts +55 -0
  61. package/src/domain/connection-authority.ts +329 -0
  62. package/src/domain/editable-artifacts/agent-application.ts +17 -9
  63. package/src/domain/editable-artifacts/service.ts +12 -1
  64. package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
  65. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
  66. package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
  67. package/src/domain/environments.ts +16 -3
  68. package/src/domain/github-repository-bindings.ts +295 -0
  69. package/src/domain/governed-learning-activation.ts +44 -0
  70. package/src/domain/governed-learning-evaluator.ts +49 -0
  71. package/src/domain/governed-learning-slack-publication.ts +157 -0
  72. package/src/domain/insights.ts +9 -0
  73. package/src/domain/memory-slack-delivery.ts +0 -30
  74. package/src/domain/organization-membership-lifecycle.ts +9 -0
  75. package/src/domain/packs.ts +106 -3
  76. package/src/domain/personal-connection-delegations.ts +597 -42
  77. package/src/domain/pr-review.ts +671 -0
  78. package/src/domain/remember.ts +549 -0
  79. package/src/domain/resources.ts +144 -6
  80. package/src/domain/scheduled-tasks.ts +504 -77
  81. package/src/domain/session-tool-policy.ts +12 -9
  82. package/src/domain/sessions.ts +629 -176
  83. package/src/editable-artifact-live/application.ts +21 -3
  84. package/src/editable-artifact-live/ports.ts +0 -1
  85. package/src/editable-artifact-live/server.ts +13 -7
  86. package/src/editable-artifact-live/ticket.ts +1 -5
  87. package/src/editable-artifact-live/types.ts +6 -2
  88. package/src/editable-artifact-live/wire.ts +0 -10
  89. package/src/index.ts +13 -1
  90. package/src/rigs/index.ts +165 -48
  91. package/src/sandbox/fleet.ts +376 -30
  92. package/src/sandbox/routing.ts +91 -29
  93. package/src/sandbox/runtime-settings.ts +274 -0
  94. package/src/session-authorization.ts +17 -49
  95. package/dist/chunk-6WKPWE5S.js.map +0 -1
  96. package/dist/chunk-JJU6I5XD.js.map +0 -1
  97. package/dist/chunk-NYPMQ7NO.js.map +0 -1
  98. package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
  99. package/src/domain/durable-learning-slack-publication.ts +0 -202
@@ -13,24 +13,28 @@ import {
13
13
  CreateSessionRequest,
14
14
  DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
15
15
  DEFAULT_FIRST_PARTY_MCP_TOOLS,
16
+ DraftTimelineAnnotations,
16
17
  FIRST_PARTY_MCP_TOOL_NAMES,
17
18
  OPENGENI_SLACK_BOT_SESSION_METADATA_KEY,
18
19
  SessionSpawnDenial,
19
20
  ServiceTurnInitiator,
20
21
  ServiceTurnInitiatorContext,
21
22
  evaluateWorkspaceModelPolicy,
22
- latencyModeForMetadata,
23
- reasoningEffortForMetadata,
23
+ resolveWorkspaceSessionToolDefaults,
24
24
  stableJson,
25
25
  type AccessGrant,
26
+ type ComposerDraft,
26
27
  type CreateSessionResponse,
27
28
  type GoalSpec,
28
29
  type FirstPartyMcpToolName,
29
30
  type McpPersonalConnectionDelegation,
31
+ type McpConnectionAuthoritySelection,
30
32
  type Permission,
33
+ type PersonalResourceAttachmentIntent,
31
34
  type ReasoningEffort,
32
35
  type ResourceRef,
33
36
  type Session,
37
+ type SessionCommandReceipt,
34
38
  type SessionSkill,
35
39
  type SessionEvent,
36
40
  SessionMcpApprovalPolicy,
@@ -44,6 +48,8 @@ import {
44
48
  type SessionAuthorizationPort,
45
49
  type SessionToolPolicy,
46
50
  type SessionTurn,
51
+ type SessionPromptRouting,
52
+ type SessionGoalSnapshot,
47
53
  type ToolRef,
48
54
  type TurnInitiator,
49
55
  type TurnInitiatorContext,
@@ -63,13 +69,18 @@ import {
63
69
  listDistinctRigVersionIdsInGroup,
64
70
  getSandbox,
65
71
  getSession,
72
+ getSessionAuthorityProjection,
66
73
  SessionIdConflictError,
74
+ NewSessionDraftConflictError,
67
75
  getSessionSpawnDenialByIdempotencyKey,
68
76
  getWorkspaceControlEvent,
69
77
  getSessionLineage,
70
78
  getSessionTurn,
79
+ getSessionTurnForAttempt,
80
+ getSessionTurnPersonalConnectionDelegations,
71
81
  getSessionTurnXaiProviderAccountAuthoritySnapshot,
72
82
  getWorkspaceModelPolicy,
83
+ requireWorkspace,
73
84
  initializeSessionStartAtomically,
74
85
  listSessionTurns,
75
86
  listSessionMcpServersForChildInheritance,
@@ -87,7 +98,13 @@ import {
87
98
  SessionSpawnDeniedDbError,
88
99
  SessionControlConflictError,
89
100
  SessionToolPolicyVersionConflictError,
101
+ SessionCreateIdempotencyConflictError,
102
+ PersonalResourceAttachmentAcceptanceError,
103
+ sessionTenancyProductActivated,
104
+ workspaceControlRequestLockTimeoutMs,
105
+ WorkspaceControlBusyError,
90
106
  type SessionCommandActor,
107
+ type NewSessionDraftSnapshot,
91
108
  } from "@opengeni/db";
92
109
  import {
93
110
  appendAndPublishEvents,
@@ -96,7 +113,7 @@ import {
96
113
  type EventBus,
97
114
  } from "@opengeni/events";
98
115
  import { HTTPException } from "hono/http-exception";
99
- import { hasPermission, requirePermission } from "../access";
116
+ import { hasPermission, requirePermission, type AccessGrantAuthorization } from "../access";
100
117
  import { recordWorkspaceUsage, requireLimit } from "../billing/limits";
101
118
  import type {
102
119
  AcceptSessionUserMessageDependencies,
@@ -104,6 +121,7 @@ import type {
104
121
  SessionWorkflowClient,
105
122
  } from "../dependencies";
106
123
  import {
124
+ requireLiveAgentAttemptAuthorization,
107
125
  requireSessionAuthorization,
108
126
  SessionAuthorizationDeniedError,
109
127
  } from "../session-authorization";
@@ -116,6 +134,10 @@ import {
116
134
  personalConnectionDelegationSourceForGrant,
117
135
  } from "./personal-connection-delegations";
118
136
  import { hasReservedOpenGeniSlackBotSessionMetadata } from "./slack-bot";
137
+ import {
138
+ requireCanonicalManagedHuman,
139
+ requireManagedHumanPrivateSessionCreate,
140
+ } from "../application/session-tenancy";
119
141
  import {
120
142
  assertToolRefsSubset,
121
143
  availableToolRefs,
@@ -124,7 +146,7 @@ import {
124
146
  validateFileResources,
125
147
  validateGitHubRepositorySelection,
126
148
  validateToolRefs,
127
- withDefaultEnabledCapabilityMcpTools,
149
+ withWorkspaceDefaultMcpTools,
128
150
  } from "./resources";
129
151
 
130
152
  const reservedSessionMcpServerIds = new Set(["opengeni", "files", "docs", "codex_apps"]);
@@ -136,6 +158,39 @@ const maxToolPolicyAuditRefs = 40;
136
158
  // RFC 9110 field-name token characters.
137
159
  const sessionMcpCredentialHeaderName = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
138
160
 
161
+ async function requireAtomicPersonalResourceAttachment(
162
+ deps: Pick<ApiRouteDeps, "db">,
163
+ authorization: AccessGrantAuthorization | undefined,
164
+ workspaceId: string,
165
+ intent: PersonalResourceAttachmentIntent | undefined,
166
+ existingSession: boolean,
167
+ ): Promise<void> {
168
+ if (!intent) return;
169
+ if (!authorization) {
170
+ throw new HTTPException(403, {
171
+ message: "Personal resources require the owning managed-human session.",
172
+ });
173
+ }
174
+ try {
175
+ requireCanonicalManagedHuman(authorization, workspaceId);
176
+ } catch (error) {
177
+ throw new HTTPException(403, {
178
+ message: "Personal resources require the owning managed-human session.",
179
+ cause: error,
180
+ });
181
+ }
182
+ if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
183
+ throw new HTTPException(409, {
184
+ message: "Session tenancy is not activated for this organization.",
185
+ });
186
+ }
187
+ if (existingSession && intent.expectedAuthorityEpoch === undefined) {
188
+ throw new HTTPException(422, {
189
+ message: "Personal-resource attachment requires expectedAuthorityEpoch.",
190
+ });
191
+ }
192
+ }
193
+
139
194
  /** Transport-neutral typed denial raised only after its audit row committed. */
140
195
  export class SessionSpawnDeniedError extends Error {
141
196
  readonly denial: SessionSpawnDenial;
@@ -562,10 +617,11 @@ export async function createAndStartSessionWithOutcome(input: {
562
617
  beforeCreateCommit?: (tx: Database, sessionId: string) => Promise<void>;
563
618
  accountId: string;
564
619
  workspaceId: string;
620
+ visibility?: "user_private" | "workspace_shared";
565
621
  initialMessage: string;
566
622
  /** Create the session shell without an initial user event/agent turn. */
567
623
  deferInitialTurn?: boolean;
568
- turnInstructions?: string | null;
624
+ modelContext?: string | null;
569
625
  resources: ResourceRef[];
570
626
  skills?: SessionSkill[];
571
627
  tools: ToolRef[];
@@ -613,6 +669,7 @@ export async function createAndStartSessionWithOutcome(input: {
613
669
  mcpServers?: CreateSessionMcpServerInput[];
614
670
  sessionMcpServers?: SessionMcpServerMetadata[];
615
671
  personalConnectionDelegations?: McpPersonalConnectionDelegation[];
672
+ initialPersonalResourceAttachmentIntent?: PersonalResourceAttachmentIntent | null;
616
673
  xaiProviderAccountAuthoritySnapshot?: XaiProviderAccountAuthoritySnapshotV1;
617
674
  // The manager session spawning this worker (a worker-signed sessionId claim
618
675
  // on the creating grant); null for direct API creates and scheduled runs.
@@ -644,12 +701,27 @@ export async function createAndStartSessionWithOutcome(input: {
644
701
  sandboxId: string;
645
702
  settings: Settings;
646
703
  workingDir?: string | null;
704
+ resourceSubjectId?: string | null;
647
705
  } | null;
648
706
  // Exact actor-private pre-session draft represented by this create. The
649
707
  // initializer consumes it only after the first durable runnable unit commits.
650
708
  consumeNewSessionDraft?: {
651
709
  subjectId: string;
652
710
  expectedRevision: number;
711
+ expectedSnapshot: NewSessionDraftSnapshot;
712
+ acceptedSelection: {
713
+ channelId: string | null;
714
+ targetSandboxId: string | null;
715
+ workingDir: string | null;
716
+ };
717
+ } | null;
718
+ rememberNewSessionSelection?: {
719
+ subjectId: string;
720
+ acceptedSelection: {
721
+ channelId: string | null;
722
+ targetSandboxId: string | null;
723
+ workingDir: string | null;
724
+ };
653
725
  } | null;
654
726
  // A child may lower its inherited nested-agent depth limit freely; increases
655
727
  // are authorized by the caller's workspace:admin grant and checked again by
@@ -673,8 +745,9 @@ export async function createAndStartSessionWithOutcome(input: {
673
745
  ...(input.requestedSessionId ? { requestedSessionId: input.requestedSessionId } : {}),
674
746
  accountId: input.accountId,
675
747
  workspaceId: input.workspaceId,
748
+ visibility: input.visibility ?? "workspace_shared",
676
749
  initialMessage: input.initialMessage,
677
- initialTurnInstructions: input.turnInstructions ?? null,
750
+ initialModelContext: input.modelContext ?? null,
678
751
  resources: input.resources,
679
752
  skills: input.skills ?? [],
680
753
  tools: input.tools,
@@ -684,6 +757,8 @@ export async function createAndStartSessionWithOutcome(input: {
684
757
  ...(input.createdByContext ? { createdByContext: input.createdByContext } : {}),
685
758
  createdByActor: input.createdByActor ?? null,
686
759
  model: input.model,
760
+ reasoningEffort: input.reasoningEffort,
761
+ latencyMode: input.latencyMode ?? "standard",
687
762
  sandboxBackend: input.sandboxBackend,
688
763
  variableSetId: input.variableSet?.id ?? null,
689
764
  rigId: input.rigId ?? null,
@@ -699,6 +774,8 @@ export async function createAndStartSessionWithOutcome(input: {
699
774
  ...(input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}),
700
775
  mcpServers: input.mcpServers ?? [],
701
776
  personalConnectionDelegations: input.personalConnectionDelegations ?? [],
777
+ initialPersonalResourceAttachmentIntent:
778
+ input.initialPersonalResourceAttachmentIntent ?? null,
702
779
  ...(input.xaiProviderAccountAuthoritySnapshot
703
780
  ? {
704
781
  initialXaiProviderAccountAuthoritySnapshot: input.xaiProviderAccountAuthoritySnapshot,
@@ -739,8 +816,9 @@ export async function createAndStartSessionWithOutcome(input: {
739
816
  ...(input.requestedSessionId ? { requestedSessionId: input.requestedSessionId } : {}),
740
817
  accountId: input.accountId,
741
818
  workspaceId: input.workspaceId,
819
+ visibility: input.visibility ?? "workspace_shared",
742
820
  initialMessage: input.initialMessage,
743
- initialTurnInstructions: input.turnInstructions ?? null,
821
+ initialModelContext: input.modelContext ?? null,
744
822
  resources: input.resources,
745
823
  skills: input.skills ?? [],
746
824
  tools: input.tools,
@@ -750,6 +828,8 @@ export async function createAndStartSessionWithOutcome(input: {
750
828
  ...(input.createdByContext ? { createdByContext: input.createdByContext } : {}),
751
829
  createdByActor: input.createdByActor ?? null,
752
830
  model: input.model,
831
+ reasoningEffort: input.reasoningEffort,
832
+ latencyMode: input.latencyMode ?? "standard",
753
833
  sandboxBackend: input.sandboxBackend,
754
834
  variableSetId: input.variableSet?.id ?? null,
755
835
  rigId: input.rigId ?? null,
@@ -764,6 +844,8 @@ export async function createAndStartSessionWithOutcome(input: {
764
844
  ...(input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}),
765
845
  mcpServers: input.mcpServers ?? [],
766
846
  personalConnectionDelegations: input.personalConnectionDelegations ?? [],
847
+ initialPersonalResourceAttachmentIntent:
848
+ input.initialPersonalResourceAttachmentIntent ?? null,
767
849
  ...(input.xaiProviderAccountAuthoritySnapshot
768
850
  ? {
769
851
  initialXaiProviderAccountAuthoritySnapshot: input.xaiProviderAccountAuthoritySnapshot,
@@ -809,7 +891,7 @@ async function finishStartSession(
809
891
  workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
810
892
  initialMessage: string;
811
893
  deferInitialTurn?: boolean;
812
- turnInstructions?: string | null;
894
+ modelContext?: string | null;
813
895
  resources: ResourceRef[];
814
896
  tools: ToolRef[];
815
897
  toolPolicy: SessionToolPolicy;
@@ -825,10 +907,25 @@ async function finishStartSession(
825
907
  sandboxId: string;
826
908
  settings: Settings;
827
909
  workingDir?: string | null;
910
+ resourceSubjectId?: string | null;
828
911
  } | null;
829
912
  consumeNewSessionDraft?: {
830
913
  subjectId: string;
831
914
  expectedRevision: number;
915
+ expectedSnapshot: NewSessionDraftSnapshot;
916
+ acceptedSelection: {
917
+ channelId: string | null;
918
+ targetSandboxId: string | null;
919
+ workingDir: string | null;
920
+ };
921
+ } | null;
922
+ rememberNewSessionSelection?: {
923
+ subjectId: string;
924
+ acceptedSelection: {
925
+ channelId: string | null;
926
+ targetSandboxId: string | null;
927
+ workingDir: string | null;
928
+ };
832
929
  } | null;
833
930
  },
834
931
  session: Session,
@@ -850,6 +947,9 @@ async function finishStartSession(
850
947
  sessionId: session.id,
851
948
  sessionBackend: session.sandboxBackend,
852
949
  sessionGroupId: session.sandboxGroupId,
950
+ ...(input.seedTargetSandbox.resourceSubjectId
951
+ ? { subjectId: input.seedTargetSandbox.resourceSubjectId }
952
+ : {}),
853
953
  };
854
954
  const seeded = await swapActiveSandbox(
855
955
  {
@@ -892,12 +992,19 @@ async function finishStartSession(
892
992
  ...(input.goal.successCriteria !== undefined
893
993
  ? { successCriteria: input.goal.successCriteria }
894
994
  : {}),
995
+ ...(input.goal.rootConstraints !== undefined
996
+ ? { rootConstraints: input.goal.rootConstraints }
997
+ : {}),
895
998
  ...(input.goal.maxAutoContinuations !== undefined
896
999
  ? { maxAutoContinuations: input.goal.maxAutoContinuations }
897
1000
  : {}),
1001
+ ...(input.goal.mutationPolicy !== undefined
1002
+ ? { mutationPolicy: input.goal.mutationPolicy }
1003
+ : {}),
898
1004
  }
899
1005
  : null,
900
1006
  consumeNewSessionDraft: input.consumeNewSessionDraft ?? null,
1007
+ rememberNewSessionSelection: input.rememberNewSessionSelection ?? null,
901
1008
  deferInitialTurn: input.deferInitialTurn === true,
902
1009
  });
903
1010
  await publishDurableSessionEvents(input.bus, session.workspaceId, session.id, started.events);
@@ -1072,20 +1179,6 @@ export async function requireQueuedTurnForApi(
1072
1179
  return turn;
1073
1180
  }
1074
1181
 
1075
- export function reasoningEffortForSession(
1076
- metadata: Record<string, unknown>,
1077
- fallback: Settings["openaiReasoningEffort"],
1078
- ): Settings["openaiReasoningEffort"] {
1079
- return reasoningEffortForMetadata(metadata, fallback);
1080
- }
1081
-
1082
- export function latencyModeForSession(
1083
- metadata: Record<string, unknown>,
1084
- fallback: "standard" | "priority" | "fast" = "standard",
1085
- ): "standard" | "priority" | "fast" {
1086
- return latencyModeForMetadata(metadata, fallback);
1087
- }
1088
-
1089
1182
  /**
1090
1183
  * Appends a `user.message` to an existing session and enqueues the resulting
1091
1184
  * turn, merging requested resources/tools into the session and waking the
@@ -1103,7 +1196,7 @@ export async function postUserMessageTurn(input: {
1103
1196
  sessionId: string;
1104
1197
  text: string;
1105
1198
  annotations?: TimelineAnnotation[];
1106
- turnInstructions?: string | null;
1199
+ modelContext?: string | null;
1107
1200
  resources: ResourceRef[];
1108
1201
  model?: string | null;
1109
1202
  reasoningEffort?: Settings["openaiReasoningEffort"] | null;
@@ -1111,6 +1204,7 @@ export async function postUserMessageTurn(input: {
1111
1204
  clientEventId?: string;
1112
1205
  mcpCredentialUpdates?: UpdateSessionMcpServerCredentialsInput[];
1113
1206
  personalConnectionDelegations?: McpPersonalConnectionDelegation[];
1207
+ personalResourceAttachment?: PersonalResourceAttachmentIntent;
1114
1208
  delivery?: "send" | "steer";
1115
1209
  origin?: "human" | "operator";
1116
1210
  actor?: string;
@@ -1125,6 +1219,9 @@ export async function postUserMessageTurn(input: {
1125
1219
  }): Promise<{
1126
1220
  accepted: SessionEvent;
1127
1221
  turn: SessionTurn;
1222
+ draft: ComposerDraft | null;
1223
+ receipt: SessionCommandReceipt;
1224
+ routing: SessionPromptRouting;
1128
1225
  interruptionCount: number;
1129
1226
  replay: boolean;
1130
1227
  }> {
@@ -1176,7 +1273,7 @@ export async function postUserMessageTurn(input: {
1176
1273
  expectedDraftRevision: input.expectedDraftRevision ?? null,
1177
1274
  text: input.text,
1178
1275
  annotations: input.annotations ?? [],
1179
- turnInstructions: input.turnInstructions ?? null,
1276
+ modelContext: input.modelContext ?? null,
1180
1277
  resources: input.resources,
1181
1278
  model: requestedModel,
1182
1279
  reasoningEffort: requestedReasoningEffort,
@@ -1189,11 +1286,28 @@ export async function postUserMessageTurn(input: {
1189
1286
  ? { recordAgentRunUsage: input.recordAgentRunUsage }
1190
1287
  : {}),
1191
1288
  personalConnectionDelegations: input.personalConnectionDelegations ?? [],
1289
+ ...(input.personalResourceAttachment
1290
+ ? {
1291
+ personalResourceAttachment: input.personalResourceAttachment,
1292
+ }
1293
+ : {}),
1192
1294
  mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
1295
+ controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
1193
1296
  }),
1194
1297
  ),
1195
1298
  );
1196
1299
  } catch (error) {
1300
+ if (error instanceof WorkspaceControlBusyError) {
1301
+ // Bounded control-prefix wait expired before any write; the request may
1302
+ // be retried. The API layer renders the retryable 503 envelope.
1303
+ throw error;
1304
+ }
1305
+ if (error instanceof PersonalResourceAttachmentAcceptanceError) {
1306
+ throw new HTTPException(
1307
+ error.kind === "invalid" ? 422 : error.kind === "forbidden" ? 403 : 409,
1308
+ { message: error.message, cause: error },
1309
+ );
1310
+ }
1197
1311
  if (
1198
1312
  error instanceof QueueCommandConflictError ||
1199
1313
  error instanceof SessionControlConflictError
@@ -1209,46 +1323,52 @@ export async function postUserMessageTurn(input: {
1209
1323
  throw error;
1210
1324
  }
1211
1325
  const postCommitTask = async () => {
1212
- try {
1213
- await publishDurableSessionEvents(bus, workspaceId, sessionId, result.events);
1214
- if (result.workspaceControlEventId) {
1215
- const controlEvent = await getWorkspaceControlEvent(
1216
- db,
1217
- workspaceId,
1218
- result.workspaceControlEventId,
1219
- );
1220
- if (!controlEvent) {
1221
- throw new Error(
1222
- `Committed workspace control event disappeared: ${result.workspaceControlEventId}`,
1223
- );
1326
+ await Promise.all([
1327
+ (async () => {
1328
+ try {
1329
+ await publishDurableSessionEvents(bus, workspaceId, sessionId, result.events);
1330
+ if (result.workspaceControlEventId) {
1331
+ const controlEvent = await getWorkspaceControlEvent(
1332
+ db,
1333
+ workspaceId,
1334
+ result.workspaceControlEventId,
1335
+ );
1336
+ if (!controlEvent) {
1337
+ throw new Error(
1338
+ `Committed workspace control event disappeared: ${result.workspaceControlEventId}`,
1339
+ );
1340
+ }
1341
+ await publishDurableWorkspaceControlEvent(bus, workspaceId, controlEvent);
1342
+ }
1343
+ } catch {
1344
+ console.warn("[sessions] prompt event fanout failed; durable rows remain replayable", {
1345
+ errorClass: "PromptEventFanoutOperationError",
1346
+ errorCode: "session_prompt_event_fanout_failed",
1347
+ origin: "core",
1348
+ });
1224
1349
  }
1225
- await publishDurableWorkspaceControlEvent(bus, workspaceId, controlEvent);
1226
- }
1227
- } catch {
1228
- console.warn("[sessions] prompt event fanout failed; durable rows remain replayable", {
1229
- errorClass: "PromptEventFanoutOperationError",
1230
- errorCode: "session_prompt_event_fanout_failed",
1231
- origin: "core",
1232
- });
1233
- }
1234
- try {
1235
- await workflowClient.wakeSessionWorkflow({
1236
- accountId,
1237
- workspaceId,
1238
- sessionId,
1239
- workflowId: result.turn.temporalWorkflowId,
1240
- wakeRevision: result.wakeRevision,
1241
- ...((input.delivery ?? "send") === "steer" || result.interruptionCount > 0
1242
- ? { interruptionRequested: true }
1243
- : {}),
1244
- });
1245
- } catch {
1246
- console.warn("[sessions] workflow wake failed; durable outbox will retry", {
1247
- errorClass: "WorkflowWakeOperationError",
1248
- errorCode: "session_workflow_wake_failed",
1249
- origin: "core",
1250
- });
1251
- }
1350
+ })(),
1351
+ (async () => {
1352
+ try {
1353
+ await workflowClient.wakeSessionWorkflow({
1354
+ accountId,
1355
+ workspaceId,
1356
+ sessionId,
1357
+ workflowId: result.turn.temporalWorkflowId,
1358
+ wakeRevision: result.wakeRevision,
1359
+ ...((input.delivery ?? "send") === "steer" || result.interruptionCount > 0
1360
+ ? { interruptionRequested: true }
1361
+ : {}),
1362
+ });
1363
+ } catch {
1364
+ console.warn("[sessions] workflow wake failed; durable outbox will retry", {
1365
+ errorClass: "WorkflowWakeOperationError",
1366
+ errorCode: "session_workflow_wake_failed",
1367
+ origin: "core",
1368
+ });
1369
+ }
1370
+ })(),
1371
+ ]);
1252
1372
  };
1253
1373
  const schedulePostCommit =
1254
1374
  input.schedulePostCommit ??
@@ -1267,6 +1387,33 @@ export async function postUserMessageTurn(input: {
1267
1387
  return {
1268
1388
  accepted: result.accepted,
1269
1389
  turn: result.turn,
1390
+ receipt: {
1391
+ id: result.receipt.id,
1392
+ action: result.receipt.action,
1393
+ operationKey: result.receipt.operationKey,
1394
+ targetSessionId: result.receipt.targetSessionId,
1395
+ targetTurnId: result.receipt.targetTurnId,
1396
+ appliedControlRevision: result.receipt.appliedControlRevision,
1397
+ appliedQueueVersion: result.receipt.appliedQueueVersion,
1398
+ appliedTurnVersion: result.receipt.appliedTurnVersion,
1399
+ appliedDraftRevision: result.receipt.appliedDraftRevision,
1400
+ createdAt: result.receipt.createdAt.toISOString(),
1401
+ },
1402
+ routing: result.routing,
1403
+ draft: result.draft
1404
+ ? {
1405
+ revision: result.draft.revision,
1406
+ text: result.draft.text,
1407
+ annotations: DraftTimelineAnnotations.parse(result.draft.annotations),
1408
+ resources: result.draft.resources as ResourceRef[],
1409
+ model: result.draft.model,
1410
+ reasoningEffort: result.draft.reasoningEffort as ReasoningEffort,
1411
+ latencyMode: result.draft.latencyMode as ComposerDraft["latencyMode"],
1412
+ sourceTurnId: result.draft.sourceTurnId,
1413
+ sourceTurnVersion: result.draft.sourceTurnVersion,
1414
+ updatedAt: result.draft.updatedAt.toISOString(),
1415
+ }
1416
+ : null,
1270
1417
  interruptionCount: result.interruptionCount,
1271
1418
  replay: result.replay,
1272
1419
  };
@@ -1281,14 +1428,69 @@ export async function postUserMessageTurn(input: {
1281
1428
  * trusted immediate parent, while explicit arrays (including []) win. A
1282
1429
  * top-level create with omitted tools applies workspace-default capability MCPs.
1283
1430
  */
1431
+ export function resolveChildGoalFromAcceptedSnapshot(
1432
+ goal: GoalSpec,
1433
+ parentGoalSnapshot: SessionGoalSnapshot,
1434
+ ): GoalSpec {
1435
+ const inheritedRootConstraints =
1436
+ parentGoalSnapshot.state === "none" ? [] : parentGoalSnapshot.rootConstraints;
1437
+ const requestedRootConstraints = goal.rootConstraints;
1438
+ if (
1439
+ requestedRootConstraints?.some((constraint) => !inheritedRootConstraints.includes(constraint))
1440
+ ) {
1441
+ throw new Error(
1442
+ "child goal rootConstraints must be an exact subset of the calling turn's frozen root constraints",
1443
+ );
1444
+ }
1445
+ return {
1446
+ ...goal,
1447
+ rootConstraints: requestedRootConstraints ?? inheritedRootConstraints,
1448
+ };
1449
+ }
1450
+
1451
+ export function resolveSessionCreateVisibility(input: {
1452
+ requestedVisibility: "private" | "workspace";
1453
+ visibilityProvided: boolean;
1454
+ parentVisibility: "user_private" | "workspace_shared" | null;
1455
+ }): "user_private" | "workspace_shared" {
1456
+ if (input.parentVisibility === "user_private") {
1457
+ if (input.visibilityProvided && input.requestedVisibility !== "private") {
1458
+ throw new Error("A private parent cannot create a workspace-visible child");
1459
+ }
1460
+ return "user_private";
1461
+ }
1462
+ if (input.parentVisibility === "workspace_shared") {
1463
+ if (input.visibilityProvided && input.requestedVisibility === "private") {
1464
+ throw new Error("A workspace-visible parent cannot create a private child");
1465
+ }
1466
+ return "workspace_shared";
1467
+ }
1468
+ return input.requestedVisibility === "private" ? "user_private" : "workspace_shared";
1469
+ }
1470
+
1284
1471
  export async function createSessionForRequestWithOutcome(
1285
1472
  deps: ApiRouteDeps,
1286
1473
  grant: AccessGrant,
1287
1474
  workspaceId: string,
1288
1475
  rawPayload: unknown,
1476
+ authorization?: AccessGrantAuthorization,
1289
1477
  ): Promise<CreateSessionRequestOutcome> {
1290
1478
  const { settings, db, bus, workflowClient, objectStorage } = deps;
1291
1479
  const payload = CreateSessionRequest.parse(rawPayload);
1480
+ const visibilityProvided = hasOwnProperty(rawPayload, "visibility");
1481
+ if (payload.visibility === "private" && !grant.metadata?.["sessionId"]) {
1482
+ if (!authorization) {
1483
+ throw new HTTPException(403, {
1484
+ message: "managed human session required",
1485
+ });
1486
+ }
1487
+ await requireManagedHumanPrivateSessionCreate(deps, authorization, workspaceId);
1488
+ if (payload.sandbox === "shared" || typeof payload.sandbox === "object") {
1489
+ throw new HTTPException(422, {
1490
+ message: "Only-me sessions require their own sandbox",
1491
+ });
1492
+ }
1493
+ }
1292
1494
  if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
1293
1495
  throw new HTTPException(422, {
1294
1496
  message: `${OPENGENI_SLACK_BOT_SESSION_METADATA_KEY} is reserved for scheduler routing`,
@@ -1308,6 +1510,13 @@ export async function createSessionForRequestWithOutcome(
1308
1510
  throw new SessionSpawnDeniedError(SessionSpawnDenial.parse(denial));
1309
1511
  }
1310
1512
  }
1513
+ await requireAtomicPersonalResourceAttachment(
1514
+ deps,
1515
+ authorization,
1516
+ workspaceId,
1517
+ payload.personalResourceAttachment,
1518
+ false,
1519
+ );
1311
1520
  // Parent linkage and execution-context inheritance come ONLY from the
1312
1521
  // worker-signed sessionId claim. A caller cannot nominate a parent in the
1313
1522
  // payload, so inheriting an existing repository/tool/credential snapshot does
@@ -1336,10 +1545,37 @@ export async function createSessionForRequestWithOutcome(
1336
1545
  message: `parent session not found in workspace: ${parentSessionId}`,
1337
1546
  });
1338
1547
  }
1548
+ const workspace = await requireWorkspace(db, workspaceId);
1549
+ const workspaceSessionToolDefaults = resolveWorkspaceSessionToolDefaults(workspace.settings);
1550
+ const parentAuthority = parentSession
1551
+ ? await getSessionAuthorityProjection(db, workspaceId, parentSession.id)
1552
+ : null;
1553
+ if (parentSession && !parentAuthority) {
1554
+ throw new HTTPException(403, {
1555
+ message: "parent session authority is unavailable",
1556
+ });
1557
+ }
1558
+ let effectiveVisibility: "user_private" | "workspace_shared";
1559
+ try {
1560
+ effectiveVisibility = resolveSessionCreateVisibility({
1561
+ requestedVisibility: payload.visibility,
1562
+ visibilityProvided,
1563
+ parentVisibility: parentAuthority?.visibility ?? null,
1564
+ });
1565
+ } catch (error) {
1566
+ throw new HTTPException(422, {
1567
+ message: error instanceof Error ? error.message : "invalid child visibility",
1568
+ });
1569
+ }
1339
1570
  const creationInitiator = creationInitiatorForGrant(grant);
1340
1571
  const parentCallingTurn =
1341
1572
  parentSession && creationInitiator.actor
1342
- ? await getSessionTurn(db, workspaceId, creationInitiator.actor.turnId)
1573
+ ? await getSessionTurnForAttempt(
1574
+ db,
1575
+ workspaceId,
1576
+ parentSession.id,
1577
+ creationInitiator.actor.attemptId,
1578
+ )
1343
1579
  : null;
1344
1580
  if (
1345
1581
  creationInitiator.actor &&
@@ -1349,6 +1585,26 @@ export async function createSessionForRequestWithOutcome(
1349
1585
  message: "caller attempt does not belong to the parent session",
1350
1586
  });
1351
1587
  }
1588
+ let effectiveGoal = payload.goal;
1589
+ if (parentSession && payload.goal) {
1590
+ try {
1591
+ effectiveGoal = resolveChildGoalFromAcceptedSnapshot(
1592
+ payload.goal,
1593
+ parentCallingTurn?.goalSnapshot ?? {
1594
+ state: "none",
1595
+ capturedAt: "unavailable",
1596
+ },
1597
+ );
1598
+ } catch (error) {
1599
+ throw new HTTPException(422, {
1600
+ message: error instanceof Error ? error.message : "invalid child goal root constraints",
1601
+ });
1602
+ }
1603
+ }
1604
+ const personalResourceSubjectId = creationInitiator.actor
1605
+ ? (await requireLiveAgentAttemptAuthorization(db, grant, creationInitiator.actor.sessionId))
1606
+ .initiatingHumanSubjectId
1607
+ : grant.subjectId;
1352
1608
  const xaiProviderAccountAuthoritySnapshot =
1353
1609
  parentSession && creationInitiator.actor
1354
1610
  ? await getSessionTurnXaiProviderAccountAuthoritySnapshot(
@@ -1358,13 +1614,25 @@ export async function createSessionForRequestWithOutcome(
1358
1614
  creationInitiator.actor.turnId,
1359
1615
  )
1360
1616
  : undefined;
1617
+ const connectionDelegationSource = personalConnectionDelegationSourceForGrant(grant);
1618
+ const inheritedPersonalConnectionDelegations =
1619
+ connectionDelegationSource.kind === "turn"
1620
+ ? await getSessionTurnPersonalConnectionDelegations(
1621
+ db,
1622
+ workspaceId,
1623
+ connectionDelegationSource.sessionId,
1624
+ connectionDelegationSource.turnId,
1625
+ )
1626
+ : null;
1361
1627
  const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
1362
1628
  db,
1363
1629
  workspaceId,
1364
1630
  settings,
1365
- {
1366
- subjectId: grant.subjectId,
1367
- },
1631
+ inheritedPersonalConnectionDelegations
1632
+ ? {
1633
+ personalConnectionDelegations: inheritedPersonalConnectionDelegations,
1634
+ }
1635
+ : { subjectId: grant.subjectId },
1368
1636
  );
1369
1637
  const sessionMcpServers = hasOwnProperty(rawPayload, "mcpServers")
1370
1638
  ? validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers)
@@ -1386,6 +1654,10 @@ export async function createSessionForRequestWithOutcome(
1386
1654
  ? payload.skills
1387
1655
  : (parentSession?.skills ?? payload.skills);
1388
1656
  const toolsProvided = hasOwnProperty(rawPayload, "tools");
1657
+ // Visibility became durable draft state after older clients had already
1658
+ // written rows without it. Compare it only when the create request supplied
1659
+ // the field explicitly; the parsed schema default must not manufacture a
1660
+ // mismatch for a legacy draft.
1389
1661
  const requestedTools = validateToolRefs(
1390
1662
  toolsProvided ? payload.tools : (parentSession?.tools ?? payload.tools),
1391
1663
  runtimeSettings,
@@ -1396,10 +1668,11 @@ export async function createSessionForRequestWithOutcome(
1396
1668
  const parentTracksWorkspaceDefaults = parentSession.toolPolicy?.mode === "workspace_default";
1397
1669
  const parentEffective = withFirstPartyTools(
1398
1670
  parentTracksWorkspaceDefaults
1399
- ? withDefaultEnabledCapabilityMcpTools(
1671
+ ? withWorkspaceDefaultMcpTools(
1400
1672
  availableToolRefs(parentSession.tools, runtimeSettings),
1401
1673
  settings,
1402
1674
  runtimeSettings,
1675
+ workspaceSessionToolDefaults,
1403
1676
  )
1404
1677
  : parentSession.tools,
1405
1678
  runtimeSettings,
@@ -1426,10 +1699,11 @@ export async function createSessionForRequestWithOutcome(
1426
1699
  selectedTools = requestedTools;
1427
1700
  toolPolicy = { mode: "explicit", inheritedFromSessionId: null };
1428
1701
  } else {
1429
- selectedTools = withDefaultEnabledCapabilityMcpTools(
1702
+ selectedTools = withWorkspaceDefaultMcpTools(
1430
1703
  requestedTools,
1431
1704
  settings,
1432
1705
  capabilityRuntimeSettings,
1706
+ workspaceSessionToolDefaults,
1433
1707
  );
1434
1708
  toolPolicy = { mode: "workspace_default", inheritedFromSessionId: null };
1435
1709
  }
@@ -1438,20 +1712,13 @@ export async function createSessionForRequestWithOutcome(
1438
1712
  // tool's permission/target authorization predicate, so attachment alone
1439
1713
  // exposes nothing.
1440
1714
  const tools = withFirstPartyTools(selectedTools, runtimeSettings);
1441
- const personalConnectionDelegations = await freezePersonalConnectionDelegations({
1442
- db,
1443
- workspaceId,
1444
- settings: runtimeSettings,
1445
- tools,
1446
- source: personalConnectionDelegationSourceForGrant(grant),
1447
- });
1448
1715
  await validateGitHubRepositorySelection(db, workspaceId, resources);
1449
1716
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
1450
1717
  throw new HTTPException(503, {
1451
1718
  message: "object storage is not configured",
1452
1719
  });
1453
1720
  }
1454
- await validateFileResources(db, workspaceId, resources);
1721
+ await validateFileResources(db, grant.accountId, workspaceId, grant.subjectId, resources);
1455
1722
  // VariableSet attachment requires variable-sets:use on the calling grant
1456
1723
  // (validateVariableSetAttachment enforces it), preserving the invariant
1457
1724
  // that sandboxed agents cannot self-attach workspace secrets.
@@ -1479,7 +1746,7 @@ export async function createSessionForRequestWithOutcome(
1479
1746
  let frozenRigId: string | null = null;
1480
1747
  let frozenRigVersionId: string | null = null;
1481
1748
  if (requestedRigId) {
1482
- const rig = await getRig(db, workspaceId, requestedRigId);
1749
+ const rig = await getRig(db, grant, requestedRigId);
1483
1750
  if (!rig || !rig.activeVersion) {
1484
1751
  if (payload.rigId) {
1485
1752
  throw new HTTPException(422, {
@@ -1490,6 +1757,14 @@ export async function createSessionForRequestWithOutcome(
1490
1757
  }
1491
1758
  // else: workspace-default fallback that no longer resolves → rig-less.
1492
1759
  } else {
1760
+ for (const defaultVariableSetId of new Set(rig.activeVersion.defaultVariableSetIds)) {
1761
+ await validateVariableSetAttachment(
1762
+ { settings, db },
1763
+ grant,
1764
+ workspaceId,
1765
+ defaultVariableSetId,
1766
+ );
1767
+ }
1493
1768
  frozenRigId = rig.id;
1494
1769
  frozenRigVersionId = rig.activeVersion.id;
1495
1770
  }
@@ -1526,14 +1801,44 @@ export async function createSessionForRequestWithOutcome(
1526
1801
  await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
1527
1802
  const inheritedReasoningEffort =
1528
1803
  parentCallingTurn?.reasoningEffort ??
1529
- (parentSession
1530
- ? reasoningEffortForSession(parentSession.metadata, settings.openaiReasoningEffort)
1531
- : settings.openaiReasoningEffort);
1804
+ parentSession?.reasoningEffort ??
1805
+ settings.openaiReasoningEffort;
1532
1806
  const inheritedLatencyMode =
1533
- parentCallingTurn?.latencyMode ??
1534
- (parentSession ? latencyModeForSession(parentSession.metadata, "standard") : "standard");
1807
+ parentCallingTurn?.latencyMode ?? parentSession?.latencyMode ?? "standard";
1535
1808
  const reasoningEffort = payload.reasoningEffort ?? inheritedReasoningEffort;
1536
1809
  const latencyMode = payload.latencyMode ?? inheritedLatencyMode;
1810
+ if (payload.expectedNewSessionDraftRevision !== undefined && payload.rigId === null) {
1811
+ throw new HTTPException(409, {
1812
+ message: "The submitted session options are not represented by the new-session draft",
1813
+ });
1814
+ }
1815
+ const expectedNewSessionDraftSnapshot: NewSessionDraftSnapshot | null =
1816
+ payload.expectedNewSessionDraftRevision !== undefined
1817
+ ? {
1818
+ text: payload.initialMessage ?? "",
1819
+ resources,
1820
+ tools: toolsProvided ? requestedTools : [],
1821
+ toolsProvided,
1822
+ model,
1823
+ reasoningEffort,
1824
+ latencyMode,
1825
+ options: {
1826
+ ...(visibilityProvided ? { visibility: payload.visibility } : {}),
1827
+ ...(payload.sandboxBackend ? { sandboxBackend: payload.sandboxBackend } : {}),
1828
+ ...(payload.targetSandboxId ? { targetSandboxId: payload.targetSandboxId } : {}),
1829
+ ...(payload.workingDir ? { workingDir: payload.workingDir } : {}),
1830
+ ...(payload.variableSetId ? { variableSetId: payload.variableSetId } : {}),
1831
+ ...(payload.rigId ? { rigId: payload.rigId } : {}),
1832
+ ...(payload.goal ? { goal: payload.goal } : {}),
1833
+ ...(payload.firstPartyMcpPermissions
1834
+ ? { firstPartyMcpPermissions: payload.firstPartyMcpPermissions }
1835
+ : {}),
1836
+ ...(payload.firstPartyMcpTools
1837
+ ? { firstPartyMcpTools: payload.firstPartyMcpTools }
1838
+ : {}),
1839
+ },
1840
+ }
1841
+ : null;
1537
1842
  const inheritedFromParent = parentSession !== null;
1538
1843
  const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
1539
1844
  modelId: model,
@@ -1615,7 +1920,7 @@ export async function createSessionForRequestWithOutcome(
1615
1920
  // and never gains an unrequested permission. Top-level omission remains the
1616
1921
  // deployment's worker default, which includes the goal tools.
1617
1922
  if (
1618
- payload.goal &&
1923
+ effectiveGoal &&
1619
1924
  firstPartyMcpPermissions &&
1620
1925
  !firstPartyMcpPermissions.includes("goals:manage")
1621
1926
  ) {
@@ -1626,7 +1931,8 @@ export async function createSessionForRequestWithOutcome(
1626
1931
  }
1627
1932
  // Tool visibility is independent from permission authority. A child that
1628
1933
  // omits the field inherits the parent's exact effective selection; a
1629
- // top-level omission selects the safe non-connector default catalog.
1934
+ // top-level omission selects the workspace's exact default catalog (or the
1935
+ // deployment default when the workspace has not configured one).
1630
1936
  const deploymentFirstPartyMcpToolPolicy = resolveFirstPartyMcpToolPolicy(settings);
1631
1937
  const disallowedFirstPartyMcpTool = payload.firstPartyMcpTools?.find(
1632
1938
  (tool) => !deploymentFirstPartyMcpToolPolicy.allowed.includes(tool),
@@ -1636,12 +1942,37 @@ export async function createSessionForRequestWithOutcome(
1636
1942
  message: `first-party MCP tool is disabled by deployment policy: ${disallowedFirstPartyMcpTool}`,
1637
1943
  });
1638
1944
  }
1945
+ const workspaceFirstPartyDefaults = workspaceSessionToolDefaults?.firstPartyMcpTools.filter(
1946
+ (tool) => deploymentFirstPartyMcpToolPolicy.allowed.includes(tool),
1947
+ );
1639
1948
  const firstPartyMcpTools = resolveFirstPartyMcpToolsForCreate(
1640
1949
  payload.firstPartyMcpTools,
1641
1950
  parentSession ? parentSession.firstPartyMcpTools : undefined,
1642
- deploymentFirstPartyMcpToolPolicy,
1951
+ workspaceFirstPartyDefaults && !parentSession
1952
+ ? { ...deploymentFirstPartyMcpToolPolicy, default: workspaceFirstPartyDefaults }
1953
+ : deploymentFirstPartyMcpToolPolicy,
1643
1954
  );
1644
- if (payload.goal) {
1955
+ const googleDrivePublicationEnabled =
1956
+ firstPartyMcpTools.includes("editable_artifact_export") &&
1957
+ firstPartyMcpTools.includes("editable_artifact_export_status") &&
1958
+ (!firstPartyMcpPermissions?.length ||
1959
+ (firstPartyMcpPermissions.includes("artifacts:read") &&
1960
+ firstPartyMcpPermissions.includes("artifacts:publish")));
1961
+ const atlassianEnabled =
1962
+ firstPartyMcpTools.some((tool) => tool.startsWith("atlassian_")) &&
1963
+ (!firstPartyMcpPermissions?.length || firstPartyMcpPermissions.includes("connections:read"));
1964
+ const personalConnectionDelegations = await freezePersonalConnectionDelegations({
1965
+ db,
1966
+ workspaceId,
1967
+ settings: runtimeSettings,
1968
+ tools,
1969
+ resources,
1970
+ source: connectionDelegationSource,
1971
+ authoritySelections: payload.connectionAuthorities,
1972
+ googleDrivePublicationEnabled,
1973
+ atlassianEnabled,
1974
+ });
1975
+ if (effectiveGoal) {
1645
1976
  const missingGoalTools = ["goal_update", "goal_progress", "goal_complete", "goal_pause"].filter(
1646
1977
  (name) => !firstPartyMcpTools.includes(name as FirstPartyMcpToolName),
1647
1978
  );
@@ -1669,7 +2000,12 @@ export async function createSessionForRequestWithOutcome(
1669
2000
  // spawned FROM INSIDE a session (parentSessionId present ⇒ a worker-signed
1670
2001
  // sessionId claim) defaults to "shared" (join the creator's box); a top-level
1671
2002
  // create (no parent) defaults to "new" (a private singleton box). Explicit
1672
- // values always win.
2003
+ // values always win — except a named `targetSandboxId` is a different compute
2004
+ // home, not a share of the creator's box. Omission plus a machine target
2005
+ // therefore defaults to "new" so the honest-label selfhosted home can fire
2006
+ // (a backend:"none" parent has no box to share; inheriting "none" then 422s
2007
+ // at seed). Explicit shared/{groupId} plus a machine target is contradictory
2008
+ // and 422s rather than silently dropping the target.
1673
2009
  //
1674
2010
  // null sandboxGroupId ⇒ createSession seeds the new row's own id (singleton,
1675
2011
  // today's 1:1 behavior). A shared/{groupId} spawn inherits the box's backend
@@ -1678,7 +2014,14 @@ export async function createSessionForRequestWithOutcome(
1678
2014
  // getAnySessionInGroup are RLS-workspace-scoped, so a foreign parent/group
1679
2015
  // returns null → 404; the group uuid is NOT an access boundary, the workspace
1680
2016
  // filter is (stress (e)).
1681
- const sandboxChoice = payload.sandbox ?? (parentSessionId ? "shared" : "new");
2017
+ if (payload.targetSandboxId && payload.sandbox !== undefined && payload.sandbox !== "new") {
2018
+ throw new HTTPException(422, {
2019
+ message:
2020
+ "targetSandboxId requires an own sandbox (omit sandbox or pass 'new'); it cannot join a shared group",
2021
+ });
2022
+ }
2023
+ const sandboxChoice =
2024
+ payload.sandbox ?? (payload.targetSandboxId ? "new" : parentSessionId ? "shared" : "new");
1682
2025
  let sandboxGroupId: string | null = null;
1683
2026
  let inheritedBackend: Session["sandboxBackend"] | undefined;
1684
2027
  // ENV-AWARE GROUPING: under the CURRENT mechanics the workspace VariableSet is
@@ -1816,19 +2159,22 @@ export async function createSessionForRequestWithOutcome(
1816
2159
  "workingDir requires targetSandboxId (it is the targeted machine's working directory)",
1817
2160
  });
1818
2161
  }
1819
- // Honest-label (Stage-D closure): a top-level session TARGETED at a Connected
2162
+ // Honest-label (Stage-D closure): a session TARGETED at a Connected
1820
2163
  // Machine (a selfhosted sandbox) runs machine-primary every turn, so its HOME
1821
2164
  // sandbox_backend must read "selfhosted" — not the deployment cloud default —
1822
2165
  // so the session row + first turn honestly reflect where the agent runs (the
1823
2166
  // Machines dashboard, the turn's warm-metering, and the file-download plane all
1824
- // key off this). GUARDS: (1) only at a TOP-LEVEL create (inheritedBackend
1825
- // undefined) a shared/{groupId} spawn is literally the creator's box and must
1826
- // NOT be relabeled; (2) only when the target's kind is actually "selfhosted" —
1827
- // targetSandboxId also accepts a first-class MODAL sandbox id (resolveTarget),
1828
- // which must never be mislabeled. A not-found / non-selfhosted / modal target
1829
- // falls through to the default; the seed swap in createAndStartSession still
1830
- // validates ownership/liveness and 422s a bad target. (3) only when the feature
1831
- // flags that make the worker actually take the machine-primary path are ON
2167
+ // key off this). GUARDS: (1) only when not inheriting a shared box
2168
+ // (inheritedBackend undefined). Named targetSandboxId already 422s
2169
+ // shared/{groupId} and defaults omission to own-box above, so this check is a
2170
+ // backstop if those placement rules change; a shared spawn without a target
2171
+ // is still literally the creator's box and must NOT be relabeled; (2) only
2172
+ // when the target's kind is actually "selfhosted" targetSandboxId also
2173
+ // accepts a first-class MODAL sandbox id (resolveTarget), which must never be
2174
+ // mislabeled. A not-found / non-selfhosted / modal target falls through to the
2175
+ // default; the seed swap in createAndStartSession still validates
2176
+ // ownership/liveness and 422s a bad target. (3) only when the feature flags
2177
+ // that make the worker actually take the machine-primary path are ON
1832
2178
  // (sandboxOwnershipEnabled + sandboxSelfhostedEnabled/routing) — otherwise the
1833
2179
  // worker ignores the active pointer and a home="selfhosted" turn would fall to
1834
2180
  // the registry client with no bound agentId and throw; with the flags off we
@@ -1849,11 +2195,19 @@ export async function createSessionForRequestWithOutcome(
1849
2195
  settings.sandboxOwnershipEnabled &&
1850
2196
  settings.sandboxSelfhostedEnabled
1851
2197
  ) {
1852
- const targetSandbox = await getSandbox(db, workspaceId, payload.targetSandboxId);
2198
+ const targetSandbox = await getSandbox(
2199
+ db,
2200
+ personalResourceSubjectId ? { ...grant, subjectId: personalResourceSubjectId } : grant,
2201
+ payload.targetSandboxId,
2202
+ );
1853
2203
  if (targetSandbox?.kind === "selfhosted") {
1854
2204
  machineHomeBackend = "selfhosted";
1855
2205
  if (targetSandbox.enrollmentId) {
1856
- const enrollment = await getEnrollment(db, workspaceId, targetSandbox.enrollmentId);
2206
+ const enrollment = await getEnrollment(
2207
+ db,
2208
+ targetSandbox.workspaceId,
2209
+ targetSandbox.enrollmentId,
2210
+ );
1857
2211
  if (
1858
2212
  enrollment &&
1859
2213
  (enrollment.os === "macos" || enrollment.os === "windows" || enrollment.os === "linux")
@@ -1881,9 +2235,10 @@ export async function createSessionForRequestWithOutcome(
1881
2235
  workflowClient,
1882
2236
  accountId: grant.accountId,
1883
2237
  workspaceId,
2238
+ visibility: effectiveVisibility,
1884
2239
  initialMessage: payload.initialMessage ?? "",
1885
2240
  deferInitialTurn: payload.startMode === "realtime",
1886
- turnInstructions: payload.turnInstructions ?? null,
2241
+ modelContext: payload.modelContext ?? null,
1887
2242
  resources,
1888
2243
  skills,
1889
2244
  tools,
@@ -1895,14 +2250,13 @@ export async function createSessionForRequestWithOutcome(
1895
2250
  turnExecutionPolicy,
1896
2251
  // A shared spawn inherits the box's backend; a caller-supplied
1897
2252
  // sandboxBackend on a shared spawn is ignored (it is the same box). A
1898
- // machine-targeted top-level create labels the home "selfhosted"
1899
- // (machineHomeBackend), overriding the caller/deployment default so the row
1900
- // matches where the session actually runs.
2253
+ // machine-targeted create (top-level or own-box child) labels the home
2254
+ // "selfhosted" (machineHomeBackend), overriding the caller/deployment
2255
+ // default so the row matches where the session actually runs.
1901
2256
  sandboxBackend:
1902
2257
  inheritedBackend ?? machineHomeBackend ?? payload.sandboxBackend ?? settings.sandboxBackend,
1903
- // Mirror the backend relabel on the OS axis: only a machine-targeted
1904
- // top-level create carries a derived OS; everything else is omitted and the
1905
- // "linux" default holds (shared spawns keep the parent-box behavior).
2258
+ // Mirror the backend relabel on the OS axis: a machine-targeted own-box
2259
+ // create carries a derived OS; shared spawns keep the parent-box behavior.
1906
2260
  ...(machineHomeOs ? { sandboxOs: machineHomeOs } : {}),
1907
2261
  sandboxGroupId,
1908
2262
  metadata: payload.metadata,
@@ -1914,7 +2268,7 @@ export async function createSessionForRequestWithOutcome(
1914
2268
  rigId: frozenRigId,
1915
2269
  rigVersionId: frozenRigVersionId,
1916
2270
  channelId,
1917
- goal: payload.goal ?? null,
2271
+ goal: effectiveGoal ?? null,
1918
2272
  // Per-session persona instructions (already trimmed/validated by the
1919
2273
  // contracts schema). Persisted on the row; composed system-level at turn
1920
2274
  // time. Not surfaced as an event.
@@ -1925,6 +2279,7 @@ export async function createSessionForRequestWithOutcome(
1925
2279
  mcpServers: sessionMcpServers.dbServers,
1926
2280
  sessionMcpServers: sessionMcpServers.metadata,
1927
2281
  personalConnectionDelegations,
2282
+ initialPersonalResourceAttachmentIntent: payload.personalResourceAttachment ?? null,
1928
2283
  ...(xaiProviderAccountAuthoritySnapshot ? { xaiProviderAccountAuthoritySnapshot } : {}),
1929
2284
  parentSessionId,
1930
2285
  createIdempotencyKey: payload.idempotencyKey ?? null,
@@ -1940,17 +2295,41 @@ export async function createSessionForRequestWithOutcome(
1940
2295
  sandboxId: payload.targetSandboxId,
1941
2296
  settings,
1942
2297
  workingDir: payload.workingDir ?? null,
2298
+ resourceSubjectId: personalResourceSubjectId,
1943
2299
  }
1944
2300
  : null,
1945
2301
  consumeNewSessionDraft:
1946
- payload.expectedNewSessionDraftRevision !== undefined
2302
+ payload.expectedNewSessionDraftRevision !== undefined && payload.startMode !== "realtime"
1947
2303
  ? {
1948
2304
  subjectId: grant.subjectId,
1949
2305
  expectedRevision: payload.expectedNewSessionDraftRevision,
2306
+ expectedSnapshot: expectedNewSessionDraftSnapshot!,
2307
+ acceptedSelection: {
2308
+ channelId,
2309
+ targetSandboxId: payload.targetSandboxId ?? null,
2310
+ workingDir: payload.targetSandboxId ? (payload.workingDir ?? null) : null,
2311
+ },
2312
+ }
2313
+ : null,
2314
+ rememberNewSessionSelection:
2315
+ payload.expectedNewSessionDraftRevision !== undefined && payload.startMode === "realtime"
2316
+ ? {
2317
+ subjectId: grant.subjectId,
2318
+ acceptedSelection: {
2319
+ channelId,
2320
+ targetSandboxId: payload.targetSandboxId ?? null,
2321
+ workingDir: payload.targetSandboxId ? (payload.workingDir ?? null) : null,
2322
+ },
1950
2323
  }
1951
2324
  : null,
1952
2325
  });
1953
2326
  } catch (error) {
2327
+ if (error instanceof PersonalResourceAttachmentAcceptanceError) {
2328
+ throw new HTTPException(
2329
+ error.kind === "invalid" ? 422 : error.kind === "forbidden" ? 403 : 409,
2330
+ { message: error.message, cause: error },
2331
+ );
2332
+ }
1954
2333
  if (error instanceof AgentCommandAuthorityError) {
1955
2334
  throw new HTTPException(403, { message: error.message });
1956
2335
  }
@@ -1959,6 +2338,15 @@ export async function createSessionForRequestWithOutcome(
1959
2338
  message: "requested session id is already in use",
1960
2339
  });
1961
2340
  }
2341
+ if (error instanceof NewSessionDraftConflictError) {
2342
+ throw new HTTPException(409, {
2343
+ message: error.message,
2344
+ cause: error,
2345
+ });
2346
+ }
2347
+ if (error instanceof SessionCreateIdempotencyConflictError) {
2348
+ throw new HTTPException(409, { message: error.message, cause: error });
2349
+ }
1962
2350
  throw error;
1963
2351
  }
1964
2352
  let usageRecording: CreateSessionRequestOutcome["usageRecording"] = "recorded";
@@ -2005,8 +2393,11 @@ export async function createSessionForRequest(
2005
2393
  grant: AccessGrant,
2006
2394
  workspaceId: string,
2007
2395
  rawPayload: unknown,
2396
+ authorization?: AccessGrantAuthorization,
2008
2397
  ): Promise<CreateSessionResponse> {
2009
- return (await createSessionForRequestWithOutcome(deps, grant, workspaceId, rawPayload)).session;
2398
+ return (
2399
+ await createSessionForRequestWithOutcome(deps, grant, workspaceId, rawPayload, authorization)
2400
+ ).session;
2010
2401
  }
2011
2402
 
2012
2403
  /**
@@ -2024,21 +2415,27 @@ export async function acceptSessionUserMessageWithOutcome(
2024
2415
  input: {
2025
2416
  text: string;
2026
2417
  annotations?: SubmittedTimelineAnnotation[];
2027
- turnInstructions?: string | null;
2418
+ modelContext?: string | null;
2028
2419
  resources?: ResourceRef[];
2029
2420
  model?: string | null;
2030
2421
  reasoningEffort?: ReasoningEffort | null;
2031
2422
  latencyMode?: "standard" | "priority" | "fast" | null;
2032
2423
  clientEventId?: string;
2033
2424
  mcpCredentialUpdates?: SessionMcpCredentialUpdateInput[];
2425
+ connectionAuthorities?: McpConnectionAuthoritySelection[];
2034
2426
  delivery?: "send" | "steer";
2035
2427
  origin?: "human" | "operator";
2036
2428
  controlEtag?: string | null;
2037
2429
  expectedDraftRevision?: number | null;
2430
+ personalResourceAttachment?: PersonalResourceAttachmentIntent;
2431
+ authorization?: AccessGrantAuthorization;
2038
2432
  },
2039
2433
  ): Promise<{
2040
2434
  accepted: SessionEvent;
2041
2435
  turn: SessionTurn;
2436
+ draft: ComposerDraft | null;
2437
+ receipt: SessionCommandReceipt;
2438
+ routing: SessionPromptRouting;
2042
2439
  interruptionCount: number;
2043
2440
  replay: boolean;
2044
2441
  }> {
@@ -2049,6 +2446,13 @@ export async function acceptSessionUserMessageWithOutcome(
2049
2446
  operation: input.delivery === "steer" ? "session.steer" : "session.append",
2050
2447
  surface: "core",
2051
2448
  });
2449
+ await requireAtomicPersonalResourceAttachment(
2450
+ deps,
2451
+ input.authorization,
2452
+ workspaceId,
2453
+ input.personalResourceAttachment,
2454
+ true,
2455
+ );
2052
2456
  // Hoisted above requireLimit so the codex-billed predicate can resolve the
2053
2457
  // turn's effective model (a follow-up turn inherits the session's model). A
2054
2458
  // pure read with no side effects.
@@ -2067,12 +2471,9 @@ export async function acceptSessionUserMessageWithOutcome(
2067
2471
  }
2068
2472
  throw error;
2069
2473
  }
2070
- const sessionReasoningEffort = reasoningEffortForSession(
2071
- existingSession.metadata,
2072
- settings.openaiReasoningEffort,
2073
- );
2474
+ const sessionReasoningEffort = existingSession.reasoningEffort;
2074
2475
  const effectiveReasoningEffort = input.reasoningEffort ?? sessionReasoningEffort;
2075
- const sessionLatencyMode = latencyModeForSession(existingSession.metadata, "standard");
2476
+ const sessionLatencyMode = existingSession.latencyMode;
2076
2477
  const effectiveLatencyMode = input.latencyMode ?? sessionLatencyMode;
2077
2478
  const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
2078
2479
  modelId: effectiveModel,
@@ -2102,7 +2503,13 @@ export async function acceptSessionUserMessageWithOutcome(
2102
2503
  message: "object storage is not configured",
2103
2504
  });
2104
2505
  }
2105
- await validateFileResources(db, workspaceId, requestedResources);
2506
+ await validateFileResources(
2507
+ db,
2508
+ grant.accountId,
2509
+ workspaceId,
2510
+ grant.subjectId,
2511
+ requestedResources,
2512
+ );
2106
2513
  await validateGitHubRepositorySelection(db, workspaceId, [
2107
2514
  ...existingSession.resources,
2108
2515
  ...requestedResources,
@@ -2113,60 +2520,96 @@ export async function acceptSessionUserMessageWithOutcome(
2113
2520
  session: existingSession,
2114
2521
  updates: input.mcpCredentialUpdates ?? [],
2115
2522
  });
2116
- const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings, {
2117
- subjectId: grant.subjectId,
2118
- });
2119
- const personalConnectionDelegations = await freezePersonalConnectionDelegations({
2523
+ const connectionDelegationSource = personalConnectionDelegationSourceForGrant(grant);
2524
+ const inheritedPersonalConnectionDelegations =
2525
+ connectionDelegationSource.kind === "turn"
2526
+ ? await getSessionTurnPersonalConnectionDelegations(
2527
+ db,
2528
+ workspaceId,
2529
+ connectionDelegationSource.sessionId,
2530
+ connectionDelegationSource.turnId,
2531
+ )
2532
+ : null;
2533
+ const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
2120
2534
  db,
2121
2535
  workspaceId,
2122
- settings: runtimeSettings,
2123
- tools: existingSession.tools,
2124
- source: personalConnectionDelegationSourceForGrant(grant),
2125
- });
2126
- const { accepted, turn, interruptionCount, replay } = await postUserMessageTurn({
2127
- db,
2128
- bus,
2129
- workflowClient,
2130
2536
  settings,
2131
- accountId: grant.accountId,
2132
- workspaceId,
2133
- sessionId,
2134
- text: input.text,
2135
- annotations,
2136
- turnInstructions: input.turnInstructions ?? null,
2137
- resources: requestedResources,
2138
- model: input.model ?? null,
2139
- reasoningEffort: input.reasoningEffort ?? null,
2140
- latencyMode: input.latencyMode ?? null,
2141
- reasoningEffortFallback: sessionReasoningEffort,
2142
- turnExecutionPolicy,
2143
- mcpCredentialUpdates,
2144
- personalConnectionDelegations,
2145
- delivery: input.delivery ?? "send",
2146
- origin: delegatedServiceInitiator ? "operator" : (input.origin ?? "human"),
2147
- actor: grant.subjectId,
2148
- ...(grant.subjectLabel ? { actorLabel: grant.subjectLabel } : {}),
2149
- ...(delegatedServiceInitiator
2537
+ inheritedPersonalConnectionDelegations
2150
2538
  ? {
2151
- commandActor: {
2152
- type: "service" as const,
2153
- subjectId: delegatedServiceInitiator.initiator.subjectId,
2154
- ...(delegatedServiceInitiator.initiator.label
2155
- ? { subjectLabel: delegatedServiceInitiator.initiator.label }
2156
- : {}),
2157
- context: delegatedServiceInitiator.context,
2158
- },
2539
+ personalConnectionDelegations: inheritedPersonalConnectionDelegations,
2159
2540
  }
2160
- : {}),
2161
- ...(input.controlEtag !== undefined ? { controlEtag: input.controlEtag } : {}),
2162
- ...(input.expectedDraftRevision !== undefined
2163
- ? { expectedDraftRevision: input.expectedDraftRevision }
2164
- : {}),
2165
- ...(input.clientEventId ? { clientEventId: input.clientEventId } : {}),
2166
- recordAgentRunUsage: true,
2167
- ...(deps.schedulePromptPostCommit ? { schedulePostCommit: deps.schedulePromptPostCommit } : {}),
2541
+ : { subjectId: grant.subjectId },
2542
+ );
2543
+ const personalConnectionDelegations = await freezePersonalConnectionDelegations({
2544
+ db,
2545
+ workspaceId,
2546
+ settings: runtimeSettings,
2547
+ tools: existingSession.tools,
2548
+ resources: [...existingSession.resources, ...requestedResources],
2549
+ source: connectionDelegationSource,
2550
+ targetSessionId: sessionId,
2551
+ googleDrivePublicationEnabled:
2552
+ existingSession.firstPartyMcpTools.includes("editable_artifact_export") &&
2553
+ existingSession.firstPartyMcpTools.includes("editable_artifact_export_status") &&
2554
+ (!existingSession.firstPartyMcpPermissions?.length ||
2555
+ (existingSession.firstPartyMcpPermissions.includes("artifacts:read") &&
2556
+ existingSession.firstPartyMcpPermissions.includes("artifacts:publish"))),
2557
+ atlassianEnabled:
2558
+ existingSession.firstPartyMcpTools.some((tool) => tool.startsWith("atlassian_")) &&
2559
+ (!existingSession.firstPartyMcpPermissions?.length ||
2560
+ existingSession.firstPartyMcpPermissions.includes("connections:read")),
2561
+ ...(input.connectionAuthorities ? { authoritySelections: input.connectionAuthorities } : {}),
2168
2562
  });
2169
- return { accepted, turn, interruptionCount, replay };
2563
+ const { accepted, turn, draft, receipt, routing, interruptionCount, replay } =
2564
+ await postUserMessageTurn({
2565
+ db,
2566
+ bus,
2567
+ workflowClient,
2568
+ settings,
2569
+ accountId: grant.accountId,
2570
+ workspaceId,
2571
+ sessionId,
2572
+ text: input.text,
2573
+ annotations,
2574
+ modelContext: input.modelContext ?? null,
2575
+ resources: requestedResources,
2576
+ model: input.model ?? null,
2577
+ reasoningEffort: input.reasoningEffort ?? null,
2578
+ latencyMode: input.latencyMode ?? null,
2579
+ reasoningEffortFallback: sessionReasoningEffort,
2580
+ turnExecutionPolicy,
2581
+ mcpCredentialUpdates,
2582
+ personalConnectionDelegations,
2583
+ ...(input.personalResourceAttachment
2584
+ ? { personalResourceAttachment: input.personalResourceAttachment }
2585
+ : {}),
2586
+ delivery: input.delivery ?? "send",
2587
+ origin: delegatedServiceInitiator ? "operator" : (input.origin ?? "human"),
2588
+ actor: grant.subjectId,
2589
+ ...(grant.subjectLabel ? { actorLabel: grant.subjectLabel } : {}),
2590
+ ...(delegatedServiceInitiator
2591
+ ? {
2592
+ commandActor: {
2593
+ type: "service" as const,
2594
+ subjectId: delegatedServiceInitiator.initiator.subjectId,
2595
+ ...(delegatedServiceInitiator.initiator.label
2596
+ ? { subjectLabel: delegatedServiceInitiator.initiator.label }
2597
+ : {}),
2598
+ context: delegatedServiceInitiator.context,
2599
+ },
2600
+ }
2601
+ : {}),
2602
+ ...(input.controlEtag !== undefined ? { controlEtag: input.controlEtag } : {}),
2603
+ ...(input.expectedDraftRevision !== undefined
2604
+ ? { expectedDraftRevision: input.expectedDraftRevision }
2605
+ : {}),
2606
+ ...(input.clientEventId ? { clientEventId: input.clientEventId } : {}),
2607
+ recordAgentRunUsage: true,
2608
+ ...(deps.schedulePromptPostCommit
2609
+ ? { schedulePostCommit: deps.schedulePromptPostCommit }
2610
+ : {}),
2611
+ });
2612
+ return { accepted, turn, draft, receipt, routing, interruptionCount, replay };
2170
2613
  }
2171
2614
 
2172
2615
  /** Backward-compatible entity-returning path used by existing REST callers. */
@@ -2179,17 +2622,14 @@ export async function acceptSessionUserMessage(
2179
2622
  ): Promise<{
2180
2623
  accepted: SessionEvent;
2181
2624
  turn: SessionTurn;
2625
+ receipt: SessionCommandReceipt;
2626
+ routing: SessionPromptRouting;
2182
2627
  interruptionCount: number;
2183
2628
  replay: boolean;
2184
2629
  }> {
2185
- const { accepted, turn, interruptionCount, replay } = await acceptSessionUserMessageWithOutcome(
2186
- deps,
2187
- grant,
2188
- workspaceId,
2189
- sessionId,
2190
- input,
2191
- );
2192
- return { accepted, turn, interruptionCount, replay };
2630
+ const { accepted, turn, receipt, routing, interruptionCount, replay } =
2631
+ await acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, sessionId, input);
2632
+ return { accepted, turn, receipt, routing, interruptionCount, replay };
2193
2633
  }
2194
2634
 
2195
2635
  /**
@@ -2336,6 +2776,7 @@ function toolPolicyAuditSnapshot(
2336
2776
  kind: tool.kind,
2337
2777
  id: tool.id,
2338
2778
  ...(tool.optional === undefined ? {} : { optional: tool.optional }),
2779
+ ...(tool.eager === undefined ? {} : { eager: tool.eager }),
2339
2780
  }));
2340
2781
  const toolRefs = allToolRefs.slice(0, maxToolPolicyAuditRefs);
2341
2782
  return {
@@ -2378,6 +2819,8 @@ export async function updateSessionToolPolicy(
2378
2819
  requirePermission(grant, "sessions:control");
2379
2820
 
2380
2821
  const existingSession = await requireSession(deps.db, grant.workspaceId, sessionId);
2822
+ const workspace = await requireWorkspace(deps.db, grant.workspaceId);
2823
+ const workspaceSessionToolDefaults = resolveWorkspaceSessionToolDefaults(workspace.settings);
2381
2824
  const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
2382
2825
  deps.db,
2383
2826
  grant.workspaceId,
@@ -2418,10 +2861,19 @@ export async function updateSessionToolPolicy(
2418
2861
  });
2419
2862
  }
2420
2863
  const workspaceDefaultTools = withFirstPartyTools(
2421
- withDefaultEnabledCapabilityMcpTools([], deps.settings, capabilityRuntimeSettings),
2864
+ withWorkspaceDefaultMcpTools(
2865
+ [],
2866
+ deps.settings,
2867
+ capabilityRuntimeSettings,
2868
+ workspaceSessionToolDefaults,
2869
+ ),
2422
2870
  runtimeSettings,
2423
2871
  );
2424
- const workspaceDefaultFirstPartyTools = [...deploymentFirstPartyMcpToolPolicy.default];
2872
+ const workspaceDefaultFirstPartyTools = [
2873
+ ...(workspaceSessionToolDefaults?.firstPartyMcpTools.filter((tool) =>
2874
+ deploymentFirstPartyMcpToolPolicy.allowed.includes(tool),
2875
+ ) ?? deploymentFirstPartyMcpToolPolicy.default),
2876
+ ];
2425
2877
  const events = await appendSessionEventsWithLockedSessionUpdate(
2426
2878
  deps.db,
2427
2879
  grant.workspaceId,
@@ -2445,10 +2897,11 @@ export async function updateSessionToolPolicy(
2445
2897
  const parentTracksWorkspaceDefaults = parent.toolPolicy?.mode === "workspace_default";
2446
2898
  const parentEffective = withFirstPartyTools(
2447
2899
  parentTracksWorkspaceDefaults
2448
- ? withDefaultEnabledCapabilityMcpTools(
2900
+ ? withWorkspaceDefaultMcpTools(
2449
2901
  availableToolRefs(parent.tools, runtimeSettings),
2450
2902
  deps.settings,
2451
2903
  runtimeSettings,
2904
+ workspaceSessionToolDefaults,
2452
2905
  )
2453
2906
  : parent.tools,
2454
2907
  runtimeSettings,