@opengeni/core 0.16.3 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/core",
3
- "version": "0.16.3",
3
+ "version": "0.17.1",
4
4
  "description": "OpenGeni framework-agnostic core: the domain, access, and billing layers (neutral access, off-HTTP V2 surface). Behavior-preserving extraction from apps/api — keeps Hono's HTTPException for error throwing (typed-errors cleanup deferred).",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -35,14 +35,14 @@
35
35
  "dependencies": {
36
36
  "@modelcontextprotocol/sdk": "^1.29.0",
37
37
  "@opengeni/codex": "^0.2.9",
38
- "@opengeni/config": "^0.9.1",
39
- "@opengeni/contracts": "^0.28.1",
40
- "@opengeni/db": "^0.19.0",
41
- "@opengeni/documents": "^0.2.64",
42
- "@opengeni/events": "^0.3.55",
43
- "@opengeni/observability": "^0.4.0",
44
- "@opengeni/runtime": "^0.16.0",
45
- "@opengeni/storage": "^0.2.50",
38
+ "@opengeni/config": "^0.9.3",
39
+ "@opengeni/contracts": "^0.30.0",
40
+ "@opengeni/db": "^0.21.0",
41
+ "@opengeni/documents": "^0.2.66",
42
+ "@opengeni/events": "^0.3.57",
43
+ "@opengeni/observability": "^0.4.2",
44
+ "@opengeni/runtime": "^0.16.2",
45
+ "@opengeni/storage": "^0.2.52",
46
46
  "hono": "^4.12.18"
47
47
  },
48
48
  "engines": {
@@ -0,0 +1,247 @@
1
+ import type { McpServerConfig, Settings } from "@opengeni/config";
2
+ import type {
3
+ AccessGrant,
4
+ ConnectionMetadata,
5
+ McpPersonalConnectionDelegation,
6
+ SessionTurn,
7
+ ToolRef,
8
+ } from "@opengeni/contracts";
9
+ import {
10
+ getSessionTurnPersonalConnectionDelegations,
11
+ getWorkspaceGrant,
12
+ listConnectionsMetadata,
13
+ type Database,
14
+ type ResolveConnectionCredentialInput,
15
+ type ResolveConnectionCredentialResult,
16
+ } from "@opengeni/db";
17
+
18
+ export type PersonalConnectionDelegationSource =
19
+ | { kind: "subject"; subjectId: string }
20
+ | { kind: "turn"; sessionId: string; turnId: string }
21
+ | { kind: "none" };
22
+
23
+ export function directPersonalConnectionSubjectId(
24
+ turn: Pick<SessionTurn, "source" | "initiator" | "initiatorContext">,
25
+ ): string | undefined {
26
+ if ((turn.source !== "user" && turn.source !== "api") || turn.initiator.kind !== "subject") {
27
+ return undefined;
28
+ }
29
+ return ["via", "viaTruncated", "provenanceError", "backfill"].some((key) =>
30
+ Object.prototype.hasOwnProperty.call(turn.initiatorContext, key),
31
+ )
32
+ ? undefined
33
+ : turn.initiator.subjectId;
34
+ }
35
+
36
+ export function personalConnectionDelegationSourceForGrant(
37
+ grant: AccessGrant,
38
+ ): PersonalConnectionDelegationSource {
39
+ const callerSessionId = grant.metadata?.["sessionId"];
40
+ const callerTurnId = grant.metadata?.["turnId"];
41
+ if (typeof callerSessionId === "string" && typeof callerTurnId === "string") {
42
+ return { kind: "turn", sessionId: callerSessionId, turnId: callerTurnId };
43
+ }
44
+ if (
45
+ grant.principalKind === "agent_attempt" ||
46
+ grant.principalKind === "service" ||
47
+ grant.serviceInitiator
48
+ ) {
49
+ return { kind: "none" };
50
+ }
51
+ return { kind: "subject", subjectId: grant.subjectId };
52
+ }
53
+
54
+ export function selectedPersonalConnectionServers(
55
+ settings: Pick<Settings, "mcpServers">,
56
+ tools: ToolRef[],
57
+ ): McpServerConfig[] {
58
+ const selected = new Set(tools.map((tool) => tool.id));
59
+ return settings.mcpServers.filter(
60
+ (server) => selected.has(server.id) && server.connectionRef?.subjectScope === "subject",
61
+ );
62
+ }
63
+
64
+ function canonicalPersonalConnections(connections: ConnectionMetadata[]): ConnectionMetadata[] {
65
+ return [...connections].sort((left, right) => {
66
+ const active = Number(right.status === "active") - Number(left.status === "active");
67
+ if (active !== 0) return active;
68
+ const updated = Date.parse(right.updatedAt) - Date.parse(left.updatedAt);
69
+ if (updated !== 0) return updated;
70
+ const created = Date.parse(right.createdAt) - Date.parse(left.createdAt);
71
+ if (created !== 0) return created;
72
+ return right.id.localeCompare(left.id);
73
+ });
74
+ }
75
+
76
+ function sameProviderDomain(left: string, right: string): boolean {
77
+ return left.toLowerCase() === right.toLowerCase();
78
+ }
79
+
80
+ export function personalConnectionDelegationsFromVisibleConnections(input: {
81
+ servers: McpServerConfig[];
82
+ subjectId: string;
83
+ connections: ConnectionMetadata[];
84
+ }): McpPersonalConnectionDelegation[] {
85
+ const delegations: McpPersonalConnectionDelegation[] = [];
86
+ const connections = canonicalPersonalConnections(input.connections);
87
+ for (const server of input.servers) {
88
+ const ref = server.connectionRef;
89
+ if (!ref || ref.subjectScope !== "subject") continue;
90
+ const connection = connections.find(
91
+ (candidate) =>
92
+ candidate.subjectId === input.subjectId &&
93
+ candidate.status === "active" &&
94
+ sameProviderDomain(candidate.providerDomain, ref.providerDomain) &&
95
+ (!ref.kind || candidate.kind === ref.kind) &&
96
+ (!ref.connectionId || candidate.id === ref.connectionId),
97
+ );
98
+ if (!connection) continue;
99
+ delegations.push({
100
+ serverId: server.id,
101
+ connectionId: connection.id,
102
+ ownerSubjectId: input.subjectId,
103
+ providerDomain: connection.providerDomain,
104
+ kind: connection.kind,
105
+ });
106
+ }
107
+ return delegations;
108
+ }
109
+
110
+ export function personalConnectionDelegationsFromParent(input: {
111
+ servers: McpServerConfig[];
112
+ parentDelegations: McpPersonalConnectionDelegation[];
113
+ }): McpPersonalConnectionDelegation[] {
114
+ return input.servers.flatMap((server) => {
115
+ const ref = server.connectionRef;
116
+ if (!ref || ref.subjectScope !== "subject") return [];
117
+ const delegation = input.parentDelegations.find(
118
+ (candidate) =>
119
+ candidate.serverId === server.id &&
120
+ sameProviderDomain(candidate.providerDomain, ref.providerDomain) &&
121
+ (!ref.kind || !candidate.kind || candidate.kind === ref.kind),
122
+ );
123
+ return delegation ? [{ ...delegation }] : [];
124
+ });
125
+ }
126
+
127
+ export function personalConnectionDelegationsEqual(
128
+ left: McpPersonalConnectionDelegation[],
129
+ right: McpPersonalConnectionDelegation[],
130
+ ): boolean {
131
+ if (left.length !== right.length) return false;
132
+ const byServer = new Map(right.map((delegation) => [delegation.serverId, delegation]));
133
+ return left.every((delegation) => {
134
+ const other = byServer.get(delegation.serverId);
135
+ return (
136
+ other?.connectionId === delegation.connectionId &&
137
+ other.ownerSubjectId === delegation.ownerSubjectId &&
138
+ sameProviderDomain(other.providerDomain, delegation.providerDomain) &&
139
+ other.kind === delegation.kind
140
+ );
141
+ });
142
+ }
143
+
144
+ export function personalConnectionDelegationForServer(
145
+ delegations: McpPersonalConnectionDelegation[],
146
+ server: Pick<McpServerConfig, "id" | "connectionRef">,
147
+ ): McpPersonalConnectionDelegation | null {
148
+ const ref = server.connectionRef;
149
+ if (!ref || ref.subjectScope !== "subject") return null;
150
+ return (
151
+ delegations.find(
152
+ (delegation) =>
153
+ delegation.serverId === server.id &&
154
+ sameProviderDomain(delegation.providerDomain, ref.providerDomain) &&
155
+ (!ref.kind || !delegation.kind || delegation.kind === ref.kind),
156
+ ) ?? null
157
+ );
158
+ }
159
+
160
+ type ConnectionCredentialResolver = (
161
+ request: ResolveConnectionCredentialInput,
162
+ ) => Promise<ResolveConnectionCredentialResult>;
163
+
164
+ function personalAuthorityUnavailable(
165
+ request: ResolveConnectionCredentialInput,
166
+ ): ResolveConnectionCredentialResult {
167
+ const ref = request.connectionRef;
168
+ return {
169
+ status: "auth_needed",
170
+ reason: "personal_authority_unavailable",
171
+ providerDomain: ref.providerDomain,
172
+ ...(ref.provider ? { provider: ref.provider } : {}),
173
+ ...(ref.scopes ? { scopes: ref.scopes } : {}),
174
+ ...(ref.resource ? { resource: ref.resource } : {}),
175
+ ...(ref.selectedResources ? { selectedResources: ref.selectedResources } : {}),
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Resolves subject-owned MCP credentials only through the exact authority
181
+ * frozen on the causal turn. A direct human subject, worker Toolspace caller,
182
+ * retry, or recovery can identify the caller, but none may widen or replace
183
+ * the persisted connection UUID.
184
+ */
185
+ export function withFrozenPersonalConnectionDelegations(input: {
186
+ resolveCredential: ConnectionCredentialResolver;
187
+ settings: Pick<Settings, "mcpServers">;
188
+ personalConnectionDelegations: McpPersonalConnectionDelegation[];
189
+ ownerHasWorkspaceMembership: (subjectId: string) => Promise<boolean>;
190
+ }): ConnectionCredentialResolver {
191
+ return async (request) => {
192
+ let effectiveRequest = request;
193
+ if (request.connectionRef.subjectScope === "subject") {
194
+ const config = input.settings.mcpServers.find((server) => server.id === request.serverId);
195
+ const delegation = config
196
+ ? personalConnectionDelegationForServer(input.personalConnectionDelegations, config)
197
+ : null;
198
+ if (!delegation || !(await input.ownerHasWorkspaceMembership(delegation.ownerSubjectId))) {
199
+ return personalAuthorityUnavailable(request);
200
+ }
201
+ effectiveRequest = {
202
+ ...request,
203
+ subjectId: delegation.ownerSubjectId,
204
+ connectionRef: {
205
+ ...request.connectionRef,
206
+ providerDomain: delegation.providerDomain,
207
+ connectionId: delegation.connectionId,
208
+ ...(delegation.kind ? { kind: delegation.kind } : {}),
209
+ },
210
+ };
211
+ }
212
+ const result = await input.resolveCredential(effectiveRequest);
213
+ if (result.status === "ok" || request.connectionRef.subjectScope !== "subject") {
214
+ return result;
215
+ }
216
+ return personalAuthorityUnavailable(request);
217
+ };
218
+ }
219
+
220
+ export async function freezePersonalConnectionDelegations(input: {
221
+ db: Database;
222
+ workspaceId: string;
223
+ settings: Pick<Settings, "mcpServers">;
224
+ tools: ToolRef[];
225
+ source: PersonalConnectionDelegationSource;
226
+ }): Promise<McpPersonalConnectionDelegation[]> {
227
+ const servers = selectedPersonalConnectionServers(input.settings, input.tools);
228
+ if (servers.length === 0 || input.source.kind === "none") return [];
229
+ if (input.source.kind === "turn") {
230
+ return personalConnectionDelegationsFromParent({
231
+ servers,
232
+ parentDelegations: await getSessionTurnPersonalConnectionDelegations(
233
+ input.db,
234
+ input.workspaceId,
235
+ input.source.sessionId,
236
+ input.source.turnId,
237
+ ),
238
+ });
239
+ }
240
+ const membership = await getWorkspaceGrant(input.db, input.source.subjectId, input.workspaceId);
241
+ if (!membership) return [];
242
+ return personalConnectionDelegationsFromVisibleConnections({
243
+ servers,
244
+ subjectId: input.source.subjectId,
245
+ connections: await listConnectionsMetadata(input.db, input.workspaceId, input.source.subjectId),
246
+ });
247
+ }
@@ -1,6 +1,7 @@
1
1
  import type { Settings } from "@opengeni/config";
2
2
  import type {
3
3
  AccessGrant,
4
+ McpPersonalConnectionDelegation,
4
5
  ScheduledTask,
5
6
  ScheduledTaskAgentConfig,
6
7
  CreateScheduledTaskRequest as CreateScheduledTaskPayload,
@@ -13,6 +14,7 @@ import {
13
14
  getNestedAgentDepthDeploymentPolicy,
14
15
  getRig,
15
16
  getScheduledTask,
17
+ getScheduledTaskPersonalConnectionDelegations,
16
18
  requireWorkspace,
17
19
  updateScheduledTask,
18
20
  type Database,
@@ -24,7 +26,16 @@ import type { SessionWorkflowClient } from "../dependencies";
24
26
  import type { ObjectStorageDependency } from "../dependencies";
25
27
  import { settingsWithEnabledCapabilityMcpServers } from "./capabilities";
26
28
  import { validateVariableSetAttachment } from "./environments";
27
- import { assertWorkspaceModelPolicyAllows, canonicalConfiguredModel } from "./sessions";
29
+ import {
30
+ freezePersonalConnectionDelegations,
31
+ personalConnectionDelegationSourceForGrant,
32
+ personalConnectionDelegationsEqual,
33
+ } from "./personal-connection-delegations";
34
+ import {
35
+ assertWorkspaceModelPolicyAllows,
36
+ canonicalConfiguredModel,
37
+ creationInitiatorForGrant,
38
+ } from "./sessions";
28
39
  import {
29
40
  hasReservedOpenGeniSlackBotSessionMetadata,
30
41
  validateOpenGeniSlackBotConnectionSelection,
@@ -93,6 +104,19 @@ export async function createValidatedScheduledTask(input: {
93
104
  if (input.payload.rigId) {
94
105
  await requireScheduledTaskRig(input.db, input.grant.workspaceId, input.payload.rigId);
95
106
  }
107
+ const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
108
+ input.db,
109
+ input.grant.workspaceId,
110
+ input.settings,
111
+ );
112
+ const personalConnectionDelegations = await freezePersonalConnectionDelegations({
113
+ db: input.db,
114
+ workspaceId: input.grant.workspaceId,
115
+ settings: runtimeSettings,
116
+ tools: agentConfig.tools,
117
+ source: personalConnectionDelegationSourceForGrant(input.grant),
118
+ });
119
+ const creationInitiator = creationInitiatorForGrant(input.grant);
96
120
  return await createScheduledTask(input.db, {
97
121
  id,
98
122
  accountId: input.grant.accountId,
@@ -104,6 +128,10 @@ export async function createValidatedScheduledTask(input: {
104
128
  runMode: input.payload.runMode,
105
129
  overlapPolicy: input.payload.overlapPolicy,
106
130
  agentConfig,
131
+ ...(creationInitiator.initiator ? { createdBy: creationInitiator.initiator } : {}),
132
+ ...(creationInitiator.context ? { createdByContext: creationInitiator.context } : {}),
133
+ createdByActor: creationInitiator.actor ?? null,
134
+ personalConnectionDelegations,
107
135
  variableSetId: input.payload.variableSetId ?? null,
108
136
  rigId: input.payload.rigId ?? null,
109
137
  metadata: input.payload.metadata,
@@ -223,6 +251,32 @@ export async function validatedScheduledTaskUpdate(input: {
223
251
  });
224
252
  }
225
253
  update.agentConfig = nextAgentConfig;
254
+ const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
255
+ input.db,
256
+ input.existing.workspaceId,
257
+ input.settings,
258
+ );
259
+ const personalConnectionDelegations = await freezePersonalConnectionDelegations({
260
+ db: input.db,
261
+ workspaceId: input.existing.workspaceId,
262
+ settings: runtimeSettings,
263
+ tools: nextAgentConfig.tools,
264
+ source: personalConnectionDelegationSourceForGrant(input.grant),
265
+ });
266
+ if (input.existing.reusableSessionId && input.existing.runMode === "reusable_session") {
267
+ const existingDelegations = await getScheduledTaskPersonalConnectionDelegations(
268
+ input.db,
269
+ input.existing.workspaceId,
270
+ input.existing.id,
271
+ );
272
+ if (!personalConnectionDelegationsEqual(existingDelegations, personalConnectionDelegations)) {
273
+ throw new HTTPException(409, {
274
+ message:
275
+ "cannot change personal MCP connections of a task with a live reusable session; recreate the task",
276
+ });
277
+ }
278
+ }
279
+ update.personalConnectionDelegations = personalConnectionDelegations;
226
280
  }
227
281
  return update;
228
282
  }
@@ -239,10 +293,30 @@ export async function requireScheduledTaskForApi(
239
293
  return task;
240
294
  }
241
295
 
242
- export async function restoreScheduledTask(
296
+ export type ScheduledTaskRestoreState = {
297
+ task: ScheduledTask;
298
+ personalConnectionDelegations: McpPersonalConnectionDelegation[];
299
+ };
300
+
301
+ export async function captureScheduledTaskRestoreState(
243
302
  db: Database,
244
303
  task: ScheduledTask,
304
+ ): Promise<ScheduledTaskRestoreState> {
305
+ return {
306
+ task,
307
+ personalConnectionDelegations: await getScheduledTaskPersonalConnectionDelegations(
308
+ db,
309
+ task.workspaceId,
310
+ task.id,
311
+ ),
312
+ };
313
+ }
314
+
315
+ export async function restoreScheduledTask(
316
+ db: Database,
317
+ previous: ScheduledTaskRestoreState,
245
318
  ): Promise<ScheduledTask> {
319
+ const { task } = previous;
246
320
  return await updateScheduledTask(db, task.workspaceId, task.id, {
247
321
  name: task.name,
248
322
  status: task.status,
@@ -250,8 +324,10 @@ export async function restoreScheduledTask(
250
324
  runMode: task.runMode,
251
325
  overlapPolicy: task.overlapPolicy,
252
326
  agentConfig: task.agentConfig,
327
+ personalConnectionDelegations: previous.personalConnectionDelegations,
253
328
  reusableSessionId: task.reusableSessionId,
254
329
  variableSetId: task.variableSetId,
330
+ rigId: task.rigId,
255
331
  metadata: task.metadata,
256
332
  });
257
333
  }
@@ -274,7 +350,7 @@ export async function syncCreatedScheduledTask(input: {
274
350
  export async function syncUpdatedScheduledTask(input: {
275
351
  db: Database;
276
352
  workflowClient: SessionWorkflowClient;
277
- previous: ScheduledTask;
353
+ previous: ScheduledTaskRestoreState;
278
354
  task: ScheduledTask;
279
355
  }): Promise<void> {
280
356
  try {
@@ -23,6 +23,7 @@ import {
23
23
  type CreateSessionResponse,
24
24
  type GoalSpec,
25
25
  type FirstPartyMcpToolName,
26
+ type McpPersonalConnectionDelegation,
26
27
  type Permission,
27
28
  type ReasoningEffort,
28
29
  type ResourceRef,
@@ -98,6 +99,10 @@ import { requireSessionAuthorization } from "../session-authorization";
98
99
  import { swapActiveSandbox, type FleetContext } from "../sandbox/fleet";
99
100
  import { settingsWithEnabledCapabilityMcpServers } from "./capabilities";
100
101
  import { requireVariableSetEncryption, validateVariableSetAttachment } from "./environments";
102
+ import {
103
+ freezePersonalConnectionDelegations,
104
+ personalConnectionDelegationSourceForGrant,
105
+ } from "./personal-connection-delegations";
101
106
  import { hasReservedOpenGeniSlackBotSessionMetadata } from "./slack-bot";
102
107
  import {
103
108
  assertToolRefsSubset,
@@ -168,7 +173,7 @@ type ValidatedSessionMcpServers = {
168
173
  metadata: SessionMcpServerMetadata[];
169
174
  };
170
175
 
171
- type FrozenCreationInitiator = {
176
+ export type FrozenCreationInitiator = {
172
177
  initiator?: TurnInitiator;
173
178
  context?: TurnInitiatorContext;
174
179
  actor?: Extract<SessionCommandActor, { type: "agent_attempt" }>;
@@ -216,7 +221,7 @@ function serviceInitiatorForGrant(grant: AccessGrant): {
216
221
  };
217
222
  }
218
223
 
219
- function creationInitiatorForGrant(grant: AccessGrant): FrozenCreationInitiator {
224
+ export function creationInitiatorForGrant(grant: AccessGrant): FrozenCreationInitiator {
220
225
  const serviceInitiator = serviceInitiatorForGrant(grant);
221
226
  const callerSessionId = grant.metadata?.["sessionId"];
222
227
  const callerTurnId = grant.metadata?.["turnId"];
@@ -549,6 +554,7 @@ export async function createAndStartSession(input: {
549
554
  // MCP servers. Metadata is the only shape emitted in events/responses.
550
555
  mcpServers?: CreateSessionMcpServerInput[];
551
556
  sessionMcpServers?: SessionMcpServerMetadata[];
557
+ personalConnectionDelegations?: McpPersonalConnectionDelegation[];
552
558
  // The manager session spawning this worker (a worker-signed sessionId claim
553
559
  // on the creating grant); null for direct API creates and scheduled runs.
554
560
  // When set, the worker's terminal-for-now transitions wake this parent.
@@ -624,6 +630,7 @@ export async function createAndStartSession(input: {
624
630
  sandboxGroupId: input.sandboxGroupId ?? null,
625
631
  ...(input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}),
626
632
  mcpServers: input.mcpServers ?? [],
633
+ personalConnectionDelegations: input.personalConnectionDelegations ?? [],
627
634
  maxNestedAgentDepthOverride: input.maxNestedAgentDepthOverride ?? null,
628
635
  allowNestedAgentDepthIncrease: input.allowNestedAgentDepthIncrease ?? false,
629
636
  subjectId: input.subjectId ?? null,
@@ -668,6 +675,7 @@ export async function createAndStartSession(input: {
668
675
  sandboxGroupId: input.sandboxGroupId ?? null,
669
676
  ...(input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}),
670
677
  mcpServers: input.mcpServers ?? [],
678
+ personalConnectionDelegations: input.personalConnectionDelegations ?? [],
671
679
  maxNestedAgentDepthOverride: input.maxNestedAgentDepthOverride ?? null,
672
680
  allowNestedAgentDepthIncrease: input.allowNestedAgentDepthIncrease ?? false,
673
681
  subjectId: input.subjectId ?? null,
@@ -966,6 +974,7 @@ export async function postUserMessageTurn(input: {
966
974
  latencyMode?: "standard" | "priority" | "fast" | null;
967
975
  clientEventId?: string;
968
976
  mcpCredentialUpdates?: UpdateSessionMcpServerCredentialsInput[];
977
+ personalConnectionDelegations?: McpPersonalConnectionDelegation[];
969
978
  delivery?: "send" | "steer";
970
979
  origin?: "human" | "operator";
971
980
  actor?: string;
@@ -1021,6 +1030,7 @@ export async function postUserMessageTurn(input: {
1021
1030
  reasoningEffortFallback: input.reasoningEffortFallback ?? settings.openaiReasoningEffort,
1022
1031
  turnExecutionPolicy: input.turnExecutionPolicy,
1023
1032
  source: input.origin === "operator" ? "api" : "user",
1033
+ personalConnectionDelegations: input.personalConnectionDelegations ?? [],
1024
1034
  mcpCredentialUpdates: input.mcpCredentialUpdates ?? [],
1025
1035
  }),
1026
1036
  ),
@@ -1218,6 +1228,13 @@ export async function createSessionForRequest(
1218
1228
  // tool's permission/target authorization predicate, so attachment alone
1219
1229
  // exposes nothing.
1220
1230
  const tools = withFirstPartyTools(selectedTools, runtimeSettings);
1231
+ const personalConnectionDelegations = await freezePersonalConnectionDelegations({
1232
+ db,
1233
+ workspaceId,
1234
+ settings: runtimeSettings,
1235
+ tools,
1236
+ source: personalConnectionDelegationSourceForGrant(grant),
1237
+ });
1221
1238
  await validateGitHubRepositorySelection(db, workspaceId, resources);
1222
1239
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
1223
1240
  throw new HTTPException(503, { message: "object storage is not configured" });
@@ -1634,6 +1651,7 @@ export async function createSessionForRequest(
1634
1651
  firstPartyMcpTools,
1635
1652
  mcpServers: sessionMcpServers.dbServers,
1636
1653
  sessionMcpServers: sessionMcpServers.metadata,
1654
+ personalConnectionDelegations,
1637
1655
  parentSessionId,
1638
1656
  createIdempotencyKey: payload.idempotencyKey ?? null,
1639
1657
  maxNestedAgentDepthOverride: payload.maxNestedAgentDepth ?? null,
@@ -1772,6 +1790,14 @@ export async function acceptSessionUserMessage(
1772
1790
  session: existingSession,
1773
1791
  updates: input.mcpCredentialUpdates ?? [],
1774
1792
  });
1793
+ const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
1794
+ const personalConnectionDelegations = await freezePersonalConnectionDelegations({
1795
+ db,
1796
+ workspaceId,
1797
+ settings: runtimeSettings,
1798
+ tools: existingSession.tools,
1799
+ source: personalConnectionDelegationSourceForGrant(grant),
1800
+ });
1775
1801
  const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
1776
1802
  const { accepted, turn } = await postUserMessageTurn({
1777
1803
  db,
@@ -1790,6 +1816,7 @@ export async function acceptSessionUserMessage(
1790
1816
  reasoningEffortFallback: sessionReasoningEffort,
1791
1817
  turnExecutionPolicy,
1792
1818
  mcpCredentialUpdates,
1819
+ personalConnectionDelegations,
1793
1820
  delivery: input.delivery ?? "send",
1794
1821
  origin: delegatedServiceInitiator ? "operator" : (input.origin ?? "human"),
1795
1822
  actor: grant.subjectId,
package/src/index.ts CHANGED
@@ -57,6 +57,7 @@ export * from "./domain/capabilities";
57
57
  export * from "./domain/environments";
58
58
  export * from "./rigs";
59
59
  export * from "./domain/packs";
60
+ export * from "./domain/personal-connection-delegations";
60
61
  export * from "./domain/resources";
61
62
  export * from "./domain/session-tool-policy";
62
63
  export * from "./domain/scheduled-tasks";