@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/dist/domain/personal-connection-delegations.d.ts +48 -0
- package/dist/domain/scheduled-tasks.d.ts +8 -3
- package/dist/domain/sessions.d.ts +11 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +240 -1
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/domain/personal-connection-delegations.ts +247 -0
- package/src/domain/scheduled-tasks.ts +79 -3
- package/src/domain/sessions.ts +29 -2
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/core",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
39
|
-
"@opengeni/contracts": "^0.
|
|
40
|
-
"@opengeni/db": "^0.
|
|
41
|
-
"@opengeni/documents": "^0.2.
|
|
42
|
-
"@opengeni/events": "^0.3.
|
|
43
|
-
"@opengeni/observability": "^0.4.
|
|
44
|
-
"@opengeni/runtime": "^0.16.
|
|
45
|
-
"@opengeni/storage": "^0.2.
|
|
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 {
|
|
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
|
|
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:
|
|
353
|
+
previous: ScheduledTaskRestoreState;
|
|
278
354
|
task: ScheduledTask;
|
|
279
355
|
}): Promise<void> {
|
|
280
356
|
try {
|
package/src/domain/sessions.ts
CHANGED
|
@@ -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";
|