@opengeni/core 0.21.2 → 0.24.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/access/index.d.ts +9 -1
- package/dist/application/new-session-drafts.d.ts +1 -1
- package/dist/application/session-commands.d.ts +9 -4
- package/dist/billing/limits.d.ts +1 -1
- package/dist/chunk-6WKPWE5S.js +2380 -0
- package/dist/chunk-6WKPWE5S.js.map +1 -0
- package/dist/chunk-JJU6I5XD.js +2713 -0
- package/dist/chunk-JJU6I5XD.js.map +1 -0
- package/dist/chunk-NYPMQ7NO.js +2270 -0
- package/dist/chunk-NYPMQ7NO.js.map +1 -0
- package/dist/dependencies.d.ts +26 -6
- package/dist/domain/capabilities.d.ts +35 -3
- package/dist/domain/company-profile-durable-learning-adapter.d.ts +39 -0
- package/dist/domain/durable-learning-slack-publication.d.ts +72 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +192 -0
- package/dist/domain/editable-artifacts/durable-export-postgres.d.ts +4 -0
- package/dist/domain/editable-artifacts/durable-export.d.ts +212 -0
- package/dist/domain/editable-artifacts/errors.d.ts +47 -0
- package/dist/domain/editable-artifacts/hash.d.ts +11 -0
- package/dist/domain/editable-artifacts/in-memory.d.ts +99 -0
- package/dist/domain/editable-artifacts/index.d.ts +14 -0
- package/dist/domain/editable-artifacts/ports.d.ts +341 -0
- package/dist/domain/editable-artifacts/postgres-adapters.d.ts +13 -0
- package/dist/domain/editable-artifacts/service.d.ts +105 -0
- package/dist/domain/editable-artifacts/snapshot-compaction.d.ts +44 -0
- package/dist/domain/editable-artifacts/snapshot-verifier-genesis.d.ts +50 -0
- package/dist/domain/editable-artifacts/snapshot-verifier-materialization.d.ts +21 -0
- package/dist/domain/editable-artifacts/snapshot-verifier.d.ts +118 -0
- package/dist/domain/editable-artifacts/types.d.ts +410 -0
- package/dist/domain/memory-slack-delivery.d.ts +19 -0
- package/dist/domain/memory-slack-publication.d.ts +0 -2
- package/dist/domain/packs.d.ts +29 -1
- package/dist/domain/personal-connection-delegations.d.ts +9 -1
- package/dist/domain/scheduled-tasks.d.ts +27 -3
- package/dist/domain/session-tool-policy.d.ts +2 -2
- package/dist/domain/sessions.d.ts +44 -10
- package/dist/domain/skill-imports.d.ts +32 -0
- package/dist/domain/slack-publication-secret-safety.d.ts +8 -0
- package/dist/domain/timeline-annotations.d.ts +5 -0
- package/dist/domain/video-generation-capabilities.d.ts +9 -0
- package/dist/domain/video-generation.d.ts +47 -0
- package/dist/editable-artifact-live/application.d.ts +69 -0
- package/dist/editable-artifact-live/errors.d.ts +1 -0
- package/dist/editable-artifact-live/index.d.ts +8 -0
- package/dist/editable-artifact-live/outbox-dispatcher.d.ts +162 -0
- package/dist/editable-artifact-live/ports.d.ts +100 -0
- package/dist/editable-artifact-live/server.d.ts +71 -0
- package/dist/editable-artifact-live/ticket.d.ts +40 -0
- package/dist/editable-artifact-live/types.d.ts +213 -0
- package/dist/editable-artifact-live/wire.d.ts +34 -0
- package/dist/editable-artifact-live.d.ts +2 -0
- package/dist/editable-artifact-live.js +58 -0
- package/dist/editable-artifact-live.js.map +1 -0
- package/dist/editable-artifacts.d.ts +2 -0
- package/dist/editable-artifacts.js +179 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/index.d.ts +35 -25
- package/dist/index.js +3184 -652
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +53 -0
- package/dist/rigs/index.d.ts +1 -1
- package/dist/rigs/provider-images.d.ts +31 -0
- package/dist/sandbox/fleet.d.ts +9 -1
- package/dist/sandbox/routing.d.ts +1 -0
- package/dist/session-authorization.d.ts +10 -1
- package/dist/workflow-wake-contract.d.ts +7 -0
- package/package.json +18 -10
- package/src/access/index.ts +43 -3
- package/src/application/new-session-drafts.ts +2 -0
- package/src/application/session-commands.ts +217 -116
- package/src/dependencies.ts +30 -2
- package/src/domain/capabilities.ts +672 -218
- package/src/domain/company-profile-durable-learning-adapter.ts +221 -0
- package/src/domain/durable-learning-slack-publication.ts +202 -0
- package/src/domain/editable-artifacts/agent-application.ts +687 -0
- package/src/domain/editable-artifacts/durable-export-postgres.ts +139 -0
- package/src/domain/editable-artifacts/durable-export.ts +734 -0
- package/src/domain/editable-artifacts/errors.ts +115 -0
- package/src/domain/editable-artifacts/hash.ts +131 -0
- package/src/domain/editable-artifacts/in-memory.ts +1662 -0
- package/src/domain/editable-artifacts/index.ts +14 -0
- package/src/domain/editable-artifacts/ports.ts +462 -0
- package/src/domain/editable-artifacts/postgres-adapters.ts +49 -0
- package/src/domain/editable-artifacts/service.ts +2402 -0
- package/src/domain/editable-artifacts/snapshot-compaction.ts +246 -0
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +235 -0
- package/src/domain/editable-artifacts/snapshot-verifier-materialization.ts +74 -0
- package/src/domain/editable-artifacts/snapshot-verifier.ts +687 -0
- package/src/domain/editable-artifacts/types.ts +835 -0
- package/src/domain/insights.ts +136 -33
- package/src/domain/memory-slack-delivery.ts +204 -0
- package/src/domain/memory-slack-publication.ts +10 -19
- package/src/domain/packs.ts +266 -5
- package/src/domain/personal-connection-delegations.ts +97 -6
- package/src/domain/resources.ts +10 -20
- package/src/domain/scheduled-tasks.ts +365 -25
- package/src/domain/session-tool-policy.ts +8 -2
- package/src/domain/sessions.ts +388 -155
- package/src/domain/skill-imports.ts +350 -0
- package/src/domain/slack-publication-secret-safety.ts +42 -0
- package/src/domain/timeline-annotations.ts +204 -0
- package/src/domain/video-generation-capabilities.ts +69 -0
- package/src/domain/video-generation.ts +249 -0
- package/src/editable-artifact-live/application.ts +182 -0
- package/src/editable-artifact-live/errors.ts +4 -0
- package/src/editable-artifact-live/index.ts +8 -0
- package/src/editable-artifact-live/outbox-dispatcher.ts +916 -0
- package/src/editable-artifact-live/ports.ts +139 -0
- package/src/editable-artifact-live/server.ts +1707 -0
- package/src/editable-artifact-live/ticket.ts +217 -0
- package/src/editable-artifact-live/types.ts +286 -0
- package/src/editable-artifact-live/wire.ts +95 -0
- package/src/editable-artifact-live.ts +2 -0
- package/src/editable-artifacts.ts +2 -0
- package/src/index.ts +14 -0
- package/src/managed-auth-type.ts +4 -3
- package/src/managed-session.ts +36 -0
- package/src/rigs/index.ts +26 -19
- package/src/rigs/provider-images.ts +110 -0
- package/src/sandbox/fleet.ts +56 -1
- package/src/sandbox/routing.ts +32 -3
- package/src/session-authorization.ts +20 -0
- package/src/workflow-wake-contract.ts +8 -0
|
@@ -2,8 +2,12 @@ import type { Settings } from "@opengeni/config";
|
|
|
2
2
|
import type {
|
|
3
3
|
AccessGrant,
|
|
4
4
|
McpPersonalConnectionDelegation,
|
|
5
|
+
KnowledgeSourceSyncAction,
|
|
5
6
|
ScheduledTask,
|
|
6
7
|
ScheduledTaskAgentConfig,
|
|
8
|
+
Session,
|
|
9
|
+
SessionAuthorizationPort,
|
|
10
|
+
SessionAuthorizationSurface,
|
|
7
11
|
CreateScheduledTaskRequest as CreateScheduledTaskPayload,
|
|
8
12
|
UpdateScheduledTaskRequest as UpdateScheduledTaskPayload,
|
|
9
13
|
} from "@opengeni/contracts";
|
|
@@ -11,17 +15,26 @@ import { OPENGENI_SLACK_BOT_SESSION_METADATA_KEY } from "@opengeni/contracts";
|
|
|
11
15
|
import {
|
|
12
16
|
createScheduledTask,
|
|
13
17
|
deleteScheduledTask,
|
|
18
|
+
getConnectionMetadata,
|
|
19
|
+
getKnowledgeSourceForSyncAuthority,
|
|
14
20
|
getNestedAgentDepthDeploymentPolicy,
|
|
15
21
|
getRig,
|
|
16
22
|
getScheduledTask,
|
|
17
23
|
getScheduledTaskPersonalConnectionDelegations,
|
|
24
|
+
getSession,
|
|
18
25
|
requireWorkspace,
|
|
26
|
+
scopedKnowledgeScopeKey,
|
|
19
27
|
updateScheduledTask,
|
|
20
28
|
type Database,
|
|
21
29
|
type UpdateScheduledTaskInput,
|
|
22
30
|
} from "@opengeni/db";
|
|
23
31
|
import { HTTPException } from "hono/http-exception";
|
|
24
32
|
import { hasPermission, requirePermission } from "../access";
|
|
33
|
+
import {
|
|
34
|
+
requireSessionAuthorization,
|
|
35
|
+
SessionAuthorizationDeniedError,
|
|
36
|
+
SessionAuthorizationUnavailableError,
|
|
37
|
+
} from "../session-authorization";
|
|
25
38
|
import type { SessionWorkflowClient } from "../dependencies";
|
|
26
39
|
import type { ObjectStorageDependency } from "../dependencies";
|
|
27
40
|
import { settingsWithEnabledCapabilityMcpServers } from "./capabilities";
|
|
@@ -38,6 +51,7 @@ import {
|
|
|
38
51
|
} from "./sessions";
|
|
39
52
|
import {
|
|
40
53
|
hasReservedOpenGeniSlackBotSessionMetadata,
|
|
54
|
+
scheduledSlackBotConnectionId,
|
|
41
55
|
validateOpenGeniSlackBotConnectionSelection,
|
|
42
56
|
} from "./slack-bot";
|
|
43
57
|
import {
|
|
@@ -81,14 +95,42 @@ export async function createValidatedScheduledTask(input: {
|
|
|
81
95
|
// Set for pack-installation-inherited attachments that were already
|
|
82
96
|
// authorized with variable-sets:use when the pack was enabled.
|
|
83
97
|
variableSetPreauthorized?: boolean;
|
|
98
|
+
sessionAuthorization?: SessionAuthorizationPort | null | undefined;
|
|
99
|
+
authorizationSurface?: SessionAuthorizationSurface | undefined;
|
|
84
100
|
}): Promise<ScheduledTask> {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
// API parsing fills this default, but pack installers and older internal
|
|
102
|
+
// callers can still invoke the shared validator with the pre-action shape.
|
|
103
|
+
const action = input.payload.action ?? ({ kind: "agent_turn" } as const);
|
|
104
|
+
const knowledgeAction = action.kind === "knowledge_source_sync" ? action : null;
|
|
105
|
+
if (knowledgeAction) {
|
|
106
|
+
await validateKnowledgeSourceSyncAction({
|
|
107
|
+
db: input.db,
|
|
108
|
+
grant: input.grant,
|
|
109
|
+
action: knowledgeAction,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const agentConfig = knowledgeAction
|
|
113
|
+
? input.payload.agentConfig
|
|
114
|
+
: await validateScheduledTaskAgentConfig({
|
|
115
|
+
...input,
|
|
116
|
+
workspaceId: input.grant.workspaceId,
|
|
117
|
+
});
|
|
89
118
|
const id = crypto.randomUUID();
|
|
90
119
|
validateScheduledTaskSchedule(input.payload.schedule);
|
|
91
|
-
|
|
120
|
+
const target = knowledgeAction
|
|
121
|
+
? null
|
|
122
|
+
: await validateScheduledTaskTarget({
|
|
123
|
+
db: input.db,
|
|
124
|
+
sessionAuthorization: input.sessionAuthorization,
|
|
125
|
+
authorizationSurface: input.authorizationSurface,
|
|
126
|
+
grant: input.grant,
|
|
127
|
+
targetSessionId: input.payload.targetSessionId,
|
|
128
|
+
runMode: input.payload.runMode,
|
|
129
|
+
variableSetId: input.payload.variableSetId,
|
|
130
|
+
rigId: input.payload.rigId,
|
|
131
|
+
agentConfig,
|
|
132
|
+
});
|
|
133
|
+
if (!knowledgeAction && input.payload.variableSetId) {
|
|
92
134
|
await validateVariableSetAttachment(
|
|
93
135
|
{ settings: input.settings, db: input.db },
|
|
94
136
|
input.grant,
|
|
@@ -101,21 +143,23 @@ export async function createValidatedScheduledTask(input: {
|
|
|
101
143
|
// (at dispatch), so validate only that the id names a rig in the workspace —
|
|
102
144
|
// NOT that it has an active version now (that is a fire-time concern). RLS
|
|
103
145
|
// makes a cross-workspace id indistinguishable from missing → both 422.
|
|
104
|
-
if (input.payload.rigId) {
|
|
146
|
+
if (!knowledgeAction && input.payload.rigId) {
|
|
105
147
|
await requireScheduledTaskRig(input.db, input.grant.workspaceId, input.payload.rigId);
|
|
106
148
|
}
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
149
|
+
const personalConnectionDelegations = knowledgeAction
|
|
150
|
+
? []
|
|
151
|
+
: await freezePersonalConnectionDelegations({
|
|
152
|
+
db: input.db,
|
|
153
|
+
workspaceId: input.grant.workspaceId,
|
|
154
|
+
settings: await settingsWithEnabledCapabilityMcpServers(
|
|
155
|
+
input.db,
|
|
156
|
+
input.grant.workspaceId,
|
|
157
|
+
input.settings,
|
|
158
|
+
{ subjectId: input.grant.subjectId },
|
|
159
|
+
),
|
|
160
|
+
tools: [...agentConfig.tools, { kind: "mcp", id: "opengeni" }],
|
|
161
|
+
source: personalConnectionDelegationSourceForGrant(input.grant),
|
|
162
|
+
});
|
|
119
163
|
const creationInitiator = creationInitiatorForGrant(input.grant);
|
|
120
164
|
return await createScheduledTask(input.db, {
|
|
121
165
|
id,
|
|
@@ -127,17 +171,132 @@ export async function createValidatedScheduledTask(input: {
|
|
|
127
171
|
temporalScheduleId: scheduledTaskTemporalScheduleId(id),
|
|
128
172
|
runMode: input.payload.runMode,
|
|
129
173
|
overlapPolicy: input.payload.overlapPolicy,
|
|
174
|
+
action,
|
|
130
175
|
agentConfig,
|
|
131
176
|
...(creationInitiator.initiator ? { createdBy: creationInitiator.initiator } : {}),
|
|
132
177
|
...(creationInitiator.context ? { createdByContext: creationInitiator.context } : {}),
|
|
133
178
|
createdByActor: creationInitiator.actor ?? null,
|
|
134
179
|
personalConnectionDelegations,
|
|
180
|
+
targetSessionId: target?.id ?? null,
|
|
135
181
|
variableSetId: input.payload.variableSetId ?? null,
|
|
136
182
|
rigId: input.payload.rigId ?? null,
|
|
137
183
|
metadata: input.payload.metadata,
|
|
138
184
|
});
|
|
139
185
|
}
|
|
140
186
|
|
|
187
|
+
export async function validateScheduledTaskTarget(input: {
|
|
188
|
+
db: Database;
|
|
189
|
+
sessionAuthorization?: SessionAuthorizationPort | null | undefined;
|
|
190
|
+
authorizationSurface?: SessionAuthorizationSurface | undefined;
|
|
191
|
+
grant: AccessGrant;
|
|
192
|
+
targetSessionId: string | null | undefined;
|
|
193
|
+
runMode: ScheduledTask["runMode"];
|
|
194
|
+
variableSetId: string | null | undefined;
|
|
195
|
+
rigId: string | null | undefined;
|
|
196
|
+
agentConfig: ScheduledTaskAgentConfig;
|
|
197
|
+
missingTargetStatus?: 404 | 422;
|
|
198
|
+
}): Promise<Session | null> {
|
|
199
|
+
if (input.runMode !== "existing_session") {
|
|
200
|
+
if (input.targetSessionId) {
|
|
201
|
+
throw new HTTPException(422, {
|
|
202
|
+
message: "targetSessionId requires runMode=existing_session",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
if (!input.targetSessionId) {
|
|
208
|
+
throw new HTTPException(input.missingTargetStatus ?? 422, {
|
|
209
|
+
message:
|
|
210
|
+
input.missingTargetStatus === 404
|
|
211
|
+
? "target session not found"
|
|
212
|
+
: "targetSessionId is required when runMode=existing_session",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
requirePermission(input.grant, "sessions:control");
|
|
216
|
+
if (input.agentConfig.goal) {
|
|
217
|
+
throw new HTTPException(422, {
|
|
218
|
+
message: "agentConfig.goal cannot be used with an existing-session target",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
await requireSessionAuthorization(
|
|
223
|
+
{
|
|
224
|
+
db: input.db,
|
|
225
|
+
...(input.sessionAuthorization !== undefined
|
|
226
|
+
? { sessionAuthorization: input.sessionAuthorization }
|
|
227
|
+
: {}),
|
|
228
|
+
},
|
|
229
|
+
input.grant,
|
|
230
|
+
{
|
|
231
|
+
sessionId: input.targetSessionId,
|
|
232
|
+
operation: "session.control",
|
|
233
|
+
surface: input.authorizationSurface ?? "http",
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (error instanceof SessionAuthorizationDeniedError) {
|
|
238
|
+
throw new HTTPException(404, { message: "target session not found" });
|
|
239
|
+
}
|
|
240
|
+
if (error instanceof SessionAuthorizationUnavailableError) {
|
|
241
|
+
throw new HTTPException(503, { message: "session authorization is unavailable" });
|
|
242
|
+
}
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
const session = await getSession(input.db, input.grant.workspaceId, input.targetSessionId);
|
|
246
|
+
if (!session || session.accountId !== input.grant.accountId) {
|
|
247
|
+
throw new HTTPException(404, { message: "target session not found" });
|
|
248
|
+
}
|
|
249
|
+
if (session.status === "cancelled") {
|
|
250
|
+
throw new HTTPException(409, {
|
|
251
|
+
message: "target session is cancelled; choose a revivable session",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if ((session.variableSetId ?? null) !== (input.variableSetId ?? null)) {
|
|
255
|
+
throw new HTTPException(422, {
|
|
256
|
+
message: "target session variableSet attachment does not match the scheduled task",
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (input.rigId && input.rigId !== session.rigId) {
|
|
260
|
+
throw new HTTPException(422, {
|
|
261
|
+
message: "target session rig does not match the scheduled task",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (
|
|
265
|
+
input.agentConfig.sandboxBackend !== undefined &&
|
|
266
|
+
input.agentConfig.sandboxBackend !== session.sandboxBackend
|
|
267
|
+
) {
|
|
268
|
+
throw new HTTPException(422, {
|
|
269
|
+
message: "target session sandbox backend does not match the scheduled task",
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (
|
|
273
|
+
scheduledSlackBotConnectionId(session.metadata) !==
|
|
274
|
+
(input.agentConfig.slackBotConnectionId ?? null)
|
|
275
|
+
) {
|
|
276
|
+
throw new HTTPException(422, {
|
|
277
|
+
message: "target session OpenGeni Slack bot binding does not match the scheduled task",
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return session;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function scheduledTaskForGrant(task: ScheduledTask, grant: AccessGrant): ScheduledTask {
|
|
284
|
+
if (hasPermission(grant.permissions, "sessions:control") || task.targetSessionId === null) {
|
|
285
|
+
return task;
|
|
286
|
+
}
|
|
287
|
+
return { ...task, targetSessionId: null };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function scheduledTaskRunForGrant<T extends { sessionId: string | null }>(
|
|
291
|
+
run: T,
|
|
292
|
+
grant: AccessGrant,
|
|
293
|
+
): T {
|
|
294
|
+
if (hasPermission(grant.permissions, "sessions:control") || run.sessionId === null) {
|
|
295
|
+
return run;
|
|
296
|
+
}
|
|
297
|
+
return { ...run, sessionId: null };
|
|
298
|
+
}
|
|
299
|
+
|
|
141
300
|
// Validate a scheduled task's rig reference: it must name a rig in the
|
|
142
301
|
// workspace. A missing/cross-workspace id is a 422 (RLS-invisible == missing).
|
|
143
302
|
async function requireScheduledTaskRig(
|
|
@@ -160,8 +319,72 @@ export async function validatedScheduledTaskUpdate(input: {
|
|
|
160
319
|
payload: UpdateScheduledTaskPayload;
|
|
161
320
|
/** See createValidatedScheduledTask; only consulted when agentConfig is updated. */
|
|
162
321
|
toolsProvided?: boolean;
|
|
322
|
+
sessionAuthorization?: SessionAuthorizationPort | null | undefined;
|
|
323
|
+
authorizationSurface?: SessionAuthorizationSurface | undefined;
|
|
163
324
|
}): Promise<UpdateScheduledTaskInput> {
|
|
164
325
|
const update: UpdateScheduledTaskInput = {};
|
|
326
|
+
const existingKnowledge = input.existing.action.kind === "knowledge_source_sync";
|
|
327
|
+
if (input.payload.action && input.payload.action.kind !== input.existing.action.kind) {
|
|
328
|
+
throw new HTTPException(409, {
|
|
329
|
+
message: "scheduled task action kind is immutable; create a new schedule",
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
if (existingKnowledge) {
|
|
333
|
+
if (
|
|
334
|
+
input.payload.agentConfig !== undefined ||
|
|
335
|
+
input.payload.runMode !== undefined ||
|
|
336
|
+
input.payload.targetSessionId !== undefined ||
|
|
337
|
+
input.payload.variableSetId !== undefined ||
|
|
338
|
+
input.payload.rigId !== undefined
|
|
339
|
+
) {
|
|
340
|
+
throw new HTTPException(422, {
|
|
341
|
+
message: "knowledge source schedules do not accept agent/session configuration",
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
if (input.payload.overlapPolicy === "allow_concurrent") {
|
|
345
|
+
throw new HTTPException(422, {
|
|
346
|
+
message: "knowledge source schedules require skip or buffer_one overlap",
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
if (input.payload.action?.kind === "knowledge_source_sync") {
|
|
350
|
+
await validateKnowledgeSourceSyncAction({
|
|
351
|
+
db: input.db,
|
|
352
|
+
grant: input.grant,
|
|
353
|
+
action: input.payload.action,
|
|
354
|
+
});
|
|
355
|
+
update.action = input.payload.action;
|
|
356
|
+
}
|
|
357
|
+
if (input.payload.name !== undefined)
|
|
358
|
+
update.name = trimmedScheduledTaskName(input.payload.name);
|
|
359
|
+
if (input.payload.status !== undefined) update.status = input.payload.status;
|
|
360
|
+
if (input.payload.schedule !== undefined) {
|
|
361
|
+
validateScheduledTaskSchedule(input.payload.schedule);
|
|
362
|
+
update.schedule = input.payload.schedule;
|
|
363
|
+
}
|
|
364
|
+
if (input.payload.overlapPolicy !== undefined) {
|
|
365
|
+
update.overlapPolicy = input.payload.overlapPolicy;
|
|
366
|
+
}
|
|
367
|
+
if (input.payload.metadata !== undefined) update.metadata = input.payload.metadata;
|
|
368
|
+
return update;
|
|
369
|
+
}
|
|
370
|
+
const existingTarget = input.existing.targetSessionId;
|
|
371
|
+
const nextRunMode = input.payload.runMode ?? input.existing.runMode;
|
|
372
|
+
const nextTargetSessionId =
|
|
373
|
+
input.payload.targetSessionId !== undefined
|
|
374
|
+
? input.payload.targetSessionId
|
|
375
|
+
: nextRunMode === "existing_session"
|
|
376
|
+
? existingTarget
|
|
377
|
+
: null;
|
|
378
|
+
if (
|
|
379
|
+
input.existing.runMode === "reusable_session" &&
|
|
380
|
+
input.existing.reusableSessionId &&
|
|
381
|
+
nextRunMode === "existing_session"
|
|
382
|
+
) {
|
|
383
|
+
throw new HTTPException(409, {
|
|
384
|
+
message:
|
|
385
|
+
"cannot target an existing session after this task created its reusable session; create a new task",
|
|
386
|
+
});
|
|
387
|
+
}
|
|
165
388
|
if (input.payload.name !== undefined) {
|
|
166
389
|
update.name = trimmedScheduledTaskName(input.payload.name);
|
|
167
390
|
}
|
|
@@ -255,6 +478,7 @@ export async function validatedScheduledTaskUpdate(input: {
|
|
|
255
478
|
input.db,
|
|
256
479
|
input.existing.workspaceId,
|
|
257
480
|
input.settings,
|
|
481
|
+
{ subjectId: input.grant.subjectId },
|
|
258
482
|
);
|
|
259
483
|
const personalConnectionDelegations = await freezePersonalConnectionDelegations({
|
|
260
484
|
db: input.db,
|
|
@@ -278,6 +502,43 @@ export async function validatedScheduledTaskUpdate(input: {
|
|
|
278
502
|
}
|
|
279
503
|
update.personalConnectionDelegations = personalConnectionDelegations;
|
|
280
504
|
}
|
|
505
|
+
if (
|
|
506
|
+
existingTarget &&
|
|
507
|
+
(nextRunMode !== "existing_session" || nextTargetSessionId !== existingTarget)
|
|
508
|
+
) {
|
|
509
|
+
await validateScheduledTaskTarget({
|
|
510
|
+
db: input.db,
|
|
511
|
+
sessionAuthorization: input.sessionAuthorization,
|
|
512
|
+
authorizationSurface: input.authorizationSurface,
|
|
513
|
+
grant: input.grant,
|
|
514
|
+
targetSessionId: existingTarget,
|
|
515
|
+
runMode: "existing_session",
|
|
516
|
+
variableSetId: input.existing.variableSetId,
|
|
517
|
+
rigId: input.existing.rigId,
|
|
518
|
+
agentConfig: input.existing.agentConfig,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
await validateScheduledTaskTarget({
|
|
522
|
+
db: input.db,
|
|
523
|
+
sessionAuthorization: input.sessionAuthorization,
|
|
524
|
+
authorizationSurface: input.authorizationSurface,
|
|
525
|
+
grant: input.grant,
|
|
526
|
+
targetSessionId: nextTargetSessionId,
|
|
527
|
+
runMode: nextRunMode,
|
|
528
|
+
variableSetId:
|
|
529
|
+
input.payload.variableSetId !== undefined
|
|
530
|
+
? input.payload.variableSetId
|
|
531
|
+
: input.existing.variableSetId,
|
|
532
|
+
rigId: input.payload.rigId !== undefined ? input.payload.rigId : input.existing.rigId,
|
|
533
|
+
agentConfig: update.agentConfig ?? input.existing.agentConfig,
|
|
534
|
+
});
|
|
535
|
+
if (
|
|
536
|
+
input.payload.targetSessionId !== undefined ||
|
|
537
|
+
input.existing.runMode === "existing_session" ||
|
|
538
|
+
nextRunMode === "existing_session"
|
|
539
|
+
) {
|
|
540
|
+
update.targetSessionId = nextTargetSessionId;
|
|
541
|
+
}
|
|
281
542
|
return update;
|
|
282
543
|
}
|
|
283
544
|
|
|
@@ -323,15 +584,85 @@ export async function restoreScheduledTask(
|
|
|
323
584
|
schedule: task.schedule,
|
|
324
585
|
runMode: task.runMode,
|
|
325
586
|
overlapPolicy: task.overlapPolicy,
|
|
587
|
+
action: task.action,
|
|
326
588
|
agentConfig: task.agentConfig,
|
|
327
589
|
personalConnectionDelegations: previous.personalConnectionDelegations,
|
|
328
|
-
|
|
590
|
+
...(task.runMode === "existing_session"
|
|
591
|
+
? { targetSessionId: task.targetSessionId }
|
|
592
|
+
: { reusableSessionId: task.reusableSessionId }),
|
|
329
593
|
variableSetId: task.variableSetId,
|
|
330
594
|
rigId: task.rigId,
|
|
331
595
|
metadata: task.metadata,
|
|
332
596
|
});
|
|
333
597
|
}
|
|
334
598
|
|
|
599
|
+
async function validateKnowledgeSourceSyncAction(input: {
|
|
600
|
+
db: Database;
|
|
601
|
+
grant: AccessGrant;
|
|
602
|
+
action: KnowledgeSourceSyncAction;
|
|
603
|
+
}): Promise<void> {
|
|
604
|
+
if (input.action.initiatingSubjectId !== input.grant.subjectId) {
|
|
605
|
+
throw new HTTPException(403, {
|
|
606
|
+
message: "knowledge source sync must preserve the exact initiating subject",
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
if (input.action.connection.ownerSubjectId !== input.grant.subjectId) {
|
|
610
|
+
throw new HTTPException(403, {
|
|
611
|
+
message: "knowledge source connection must belong to the initiating subject",
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
const resolved = await getKnowledgeSourceForSyncAuthority(input.db, {
|
|
615
|
+
accountId: input.grant.accountId,
|
|
616
|
+
workspaceId: input.grant.workspaceId,
|
|
617
|
+
sourceId: input.action.sourceId,
|
|
618
|
+
initiatingSubjectId: input.grant.subjectId,
|
|
619
|
+
});
|
|
620
|
+
if (!resolved || resolved.source.lifecycleState !== "active") {
|
|
621
|
+
throw new HTTPException(404, { message: "knowledge source not found" });
|
|
622
|
+
}
|
|
623
|
+
if (
|
|
624
|
+
resolved.source.syncGeneration !== input.action.sourceGeneration ||
|
|
625
|
+
resolved.source.lifecycleGeneration !== input.action.sourceLifecycleGeneration ||
|
|
626
|
+
scopedKnowledgeScopeKey(resolved.source.scope) !==
|
|
627
|
+
scopedKnowledgeScopeKey(input.action.destination)
|
|
628
|
+
) {
|
|
629
|
+
throw new HTTPException(409, {
|
|
630
|
+
message: "knowledge source authority or generation changed",
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
const connection = await getConnectionMetadata(
|
|
634
|
+
input.db,
|
|
635
|
+
input.grant.workspaceId,
|
|
636
|
+
input.action.connection.connectionId,
|
|
637
|
+
input.grant.subjectId,
|
|
638
|
+
);
|
|
639
|
+
if (
|
|
640
|
+
!connection ||
|
|
641
|
+
connection.accountId !== input.grant.accountId ||
|
|
642
|
+
connection.workspaceId !== input.grant.workspaceId ||
|
|
643
|
+
connection.subjectId !== input.action.connection.ownerSubjectId ||
|
|
644
|
+
connection.version !== input.action.connection.connectionVersion ||
|
|
645
|
+
connection.providerDomain.toLowerCase() !==
|
|
646
|
+
input.action.connection.providerDomain.toLowerCase() ||
|
|
647
|
+
connection.kind !== input.action.connection.kind ||
|
|
648
|
+
connection.status !== "active"
|
|
649
|
+
) {
|
|
650
|
+
throw new HTTPException(409, {
|
|
651
|
+
message: "knowledge source connection authority changed or requires reconnect",
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
export class ScheduledTaskSyncError extends Error {
|
|
657
|
+
readonly persistenceRestored: boolean;
|
|
658
|
+
|
|
659
|
+
constructor(cause: unknown, persistenceRestored: boolean) {
|
|
660
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
661
|
+
this.name = "ScheduledTaskSyncError";
|
|
662
|
+
this.persistenceRestored = persistenceRestored;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
335
666
|
export async function syncCreatedScheduledTask(input: {
|
|
336
667
|
db: Database;
|
|
337
668
|
workflowClient: SessionWorkflowClient;
|
|
@@ -340,10 +671,13 @@ export async function syncCreatedScheduledTask(input: {
|
|
|
340
671
|
try {
|
|
341
672
|
await input.workflowClient.syncScheduledTask({ task: input.task });
|
|
342
673
|
} catch (error) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
674
|
+
let persistenceRestored = true;
|
|
675
|
+
try {
|
|
676
|
+
await deleteScheduledTask(input.db, input.task.workspaceId, input.task.id);
|
|
677
|
+
} catch {
|
|
678
|
+
persistenceRestored = false;
|
|
679
|
+
}
|
|
680
|
+
throw new ScheduledTaskSyncError(error, persistenceRestored);
|
|
347
681
|
}
|
|
348
682
|
}
|
|
349
683
|
|
|
@@ -356,8 +690,13 @@ export async function syncUpdatedScheduledTask(input: {
|
|
|
356
690
|
try {
|
|
357
691
|
await input.workflowClient.syncScheduledTask({ task: input.task });
|
|
358
692
|
} catch (error) {
|
|
359
|
-
|
|
360
|
-
|
|
693
|
+
let persistenceRestored = true;
|
|
694
|
+
try {
|
|
695
|
+
await restoreScheduledTask(input.db, input.previous);
|
|
696
|
+
} catch {
|
|
697
|
+
persistenceRestored = false;
|
|
698
|
+
}
|
|
699
|
+
throw new ScheduledTaskSyncError(error, persistenceRestored);
|
|
361
700
|
}
|
|
362
701
|
}
|
|
363
702
|
|
|
@@ -433,6 +772,7 @@ async function validateScheduledTaskAgentConfig(input: {
|
|
|
433
772
|
input.db,
|
|
434
773
|
input.workspaceId,
|
|
435
774
|
input.settings,
|
|
775
|
+
{ subjectId: input.grant.subjectId },
|
|
436
776
|
);
|
|
437
777
|
const requestedTools = validateToolRefs(input.payload.agentConfig.tools ?? [], runtimeSettings);
|
|
438
778
|
// A task whose creator did not choose tools gets the workspace's enabled
|
|
@@ -169,8 +169,11 @@ export async function workspaceSessionToolPolicyServerIds(
|
|
|
169
169
|
db: Database,
|
|
170
170
|
workspaceId: string,
|
|
171
171
|
settings: Settings,
|
|
172
|
+
subjectId?: string,
|
|
172
173
|
): Promise<string[]> {
|
|
173
|
-
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings
|
|
174
|
+
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings, {
|
|
175
|
+
...(subjectId ? { subjectId } : {}),
|
|
176
|
+
});
|
|
174
177
|
return sortedIds(runtimeSettings.mcpServers.map((server) => server.id));
|
|
175
178
|
}
|
|
176
179
|
|
|
@@ -179,8 +182,11 @@ export async function workspaceSessionToolPolicyDefaultServerIds(
|
|
|
179
182
|
db: Database,
|
|
180
183
|
workspaceId: string,
|
|
181
184
|
settings: Settings,
|
|
185
|
+
subjectId?: string,
|
|
182
186
|
): Promise<string[]> {
|
|
183
|
-
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings
|
|
187
|
+
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings, {
|
|
188
|
+
...(subjectId ? { subjectId } : {}),
|
|
189
|
+
});
|
|
184
190
|
return defaultSessionMcpServerIds(runtimeSettings.mcpServers);
|
|
185
191
|
}
|
|
186
192
|
|