@opengeni/core 0.4.6 → 0.4.7
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/index.d.ts +179 -30
- package/dist/index.js +1318 -475
- package/dist/index.js.map +1 -1
- package/package.json +17 -17
- package/src/access/index.ts +132 -57
- package/src/billing/limits.ts +76 -34
- package/src/dependencies.ts +76 -14
- package/src/domain/capabilities.ts +380 -181
- package/src/domain/environments.ts +58 -38
- package/src/domain/packs.ts +49 -16
- package/src/domain/resources.ts +71 -28
- package/src/domain/scheduled-tasks.ts +107 -41
- package/src/domain/sessions.ts +514 -258
- package/src/domain/workspace-members.ts +6 -2
- package/src/index.ts +1 -0
- package/src/rigs/index.ts +540 -0
- package/src/sandbox/fleet.ts +97 -18
- package/src/sandbox/routing.ts +6 -1
- package/src/sandbox-types.ts +17 -5
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
import {
|
|
10
10
|
createScheduledTask,
|
|
11
11
|
deleteScheduledTask,
|
|
12
|
+
getRig,
|
|
12
13
|
getScheduledTask,
|
|
13
14
|
updateScheduledTask,
|
|
14
15
|
type Database,
|
|
@@ -19,8 +20,8 @@ import { requirePermission } from "../access";
|
|
|
19
20
|
import type { SessionWorkflowClient } from "../dependencies";
|
|
20
21
|
import type { ObjectStorageDependency } from "../dependencies";
|
|
21
22
|
import { settingsWithEnabledCapabilityMcpServers } from "./capabilities";
|
|
22
|
-
import {
|
|
23
|
-
import { assertConfiguredModel } from "./sessions";
|
|
23
|
+
import { validateVariableSetAttachment } from "./environments";
|
|
24
|
+
import { assertConfiguredModel, assertWorkspaceModelPolicyAllows } from "./sessions";
|
|
24
25
|
import {
|
|
25
26
|
normalizeResources,
|
|
26
27
|
validateFileResources,
|
|
@@ -43,9 +44,9 @@ export function scheduledTaskToolsProvided(rawPayload: unknown): boolean {
|
|
|
43
44
|
}
|
|
44
45
|
const agentConfig = (rawPayload as { agentConfig?: unknown }).agentConfig;
|
|
45
46
|
return Boolean(
|
|
46
|
-
agentConfig
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
agentConfig &&
|
|
48
|
+
typeof agentConfig === "object" &&
|
|
49
|
+
Object.prototype.hasOwnProperty.call(agentConfig, "tools"),
|
|
49
50
|
);
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -60,21 +61,31 @@ export async function createValidatedScheduledTask(input: {
|
|
|
60
61
|
// capability MCP servers, mirroring session creation.
|
|
61
62
|
toolsProvided?: boolean;
|
|
62
63
|
// Set for pack-installation-inherited attachments that were already
|
|
63
|
-
// authorized with
|
|
64
|
-
|
|
64
|
+
// authorized with variable-sets:use when the pack was enabled.
|
|
65
|
+
variableSetPreauthorized?: boolean;
|
|
65
66
|
}): Promise<ScheduledTask> {
|
|
66
|
-
const agentConfig = await validateScheduledTaskAgentConfig({
|
|
67
|
+
const agentConfig = await validateScheduledTaskAgentConfig({
|
|
68
|
+
...input,
|
|
69
|
+
workspaceId: input.grant.workspaceId,
|
|
70
|
+
});
|
|
67
71
|
const id = crypto.randomUUID();
|
|
68
72
|
validateScheduledTaskSchedule(input.payload.schedule);
|
|
69
|
-
if (input.payload.
|
|
70
|
-
await
|
|
73
|
+
if (input.payload.variableSetId) {
|
|
74
|
+
await validateVariableSetAttachment(
|
|
71
75
|
{ settings: input.settings, db: input.db },
|
|
72
76
|
input.grant,
|
|
73
77
|
input.grant.workspaceId,
|
|
74
|
-
input.payload.
|
|
75
|
-
{ preauthorized: input.
|
|
78
|
+
input.payload.variableSetId,
|
|
79
|
+
{ preauthorized: input.variableSetPreauthorized ?? false },
|
|
76
80
|
);
|
|
77
81
|
}
|
|
82
|
+
// The rig is stored on the task and resolved to its ACTIVE version per fire
|
|
83
|
+
// (at dispatch), so validate only that the id names a rig in the workspace —
|
|
84
|
+
// NOT that it has an active version now (that is a fire-time concern). RLS
|
|
85
|
+
// makes a cross-workspace id indistinguishable from missing → both 422.
|
|
86
|
+
if (input.payload.rigId) {
|
|
87
|
+
await requireScheduledTaskRig(input.db, input.grant.workspaceId, input.payload.rigId);
|
|
88
|
+
}
|
|
78
89
|
return await createScheduledTask(input.db, {
|
|
79
90
|
id,
|
|
80
91
|
accountId: input.grant.accountId,
|
|
@@ -86,11 +97,25 @@ export async function createValidatedScheduledTask(input: {
|
|
|
86
97
|
runMode: input.payload.runMode,
|
|
87
98
|
overlapPolicy: input.payload.overlapPolicy,
|
|
88
99
|
agentConfig,
|
|
89
|
-
|
|
100
|
+
variableSetId: input.payload.variableSetId ?? null,
|
|
101
|
+
rigId: input.payload.rigId ?? null,
|
|
90
102
|
metadata: input.payload.metadata,
|
|
91
103
|
});
|
|
92
104
|
}
|
|
93
105
|
|
|
106
|
+
// Validate a scheduled task's rig reference: it must name a rig in the
|
|
107
|
+
// workspace. A missing/cross-workspace id is a 422 (RLS-invisible == missing).
|
|
108
|
+
async function requireScheduledTaskRig(
|
|
109
|
+
db: Database,
|
|
110
|
+
workspaceId: string,
|
|
111
|
+
rigId: string,
|
|
112
|
+
): Promise<void> {
|
|
113
|
+
const rig = await getRig(db, workspaceId, rigId);
|
|
114
|
+
if (!rig) {
|
|
115
|
+
throw new HTTPException(422, { message: `unknown rigId: ${rigId}` });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
94
119
|
export async function validatedScheduledTaskUpdate(input: {
|
|
95
120
|
settings: Settings;
|
|
96
121
|
db: Database;
|
|
@@ -121,39 +146,54 @@ export async function validatedScheduledTaskUpdate(input: {
|
|
|
121
146
|
if (input.payload.metadata !== undefined) {
|
|
122
147
|
update.metadata = input.payload.metadata;
|
|
123
148
|
}
|
|
124
|
-
if (input.payload.
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
149
|
+
if (input.payload.variableSetId !== undefined) {
|
|
150
|
+
const nextVariableSetId = input.payload.variableSetId;
|
|
151
|
+
if (
|
|
152
|
+
(input.existing.variableSetId ?? null) !== (nextVariableSetId ?? null) &&
|
|
153
|
+
input.existing.runMode === "reusable_session" &&
|
|
154
|
+
input.existing.reusableSessionId
|
|
155
|
+
) {
|
|
156
|
+
throw new HTTPException(409, {
|
|
157
|
+
message:
|
|
158
|
+
"cannot change variableSet of a task with a live reusable session; recreate the task",
|
|
159
|
+
});
|
|
130
160
|
}
|
|
131
|
-
if (
|
|
132
|
-
if (input.existing.
|
|
161
|
+
if (nextVariableSetId === null) {
|
|
162
|
+
if (input.existing.variableSetId !== null) {
|
|
133
163
|
// Detaching is also an attachment change: it strips the secrets a
|
|
134
164
|
// task's instructions were designed around.
|
|
135
|
-
requirePermission(input.grant, "
|
|
165
|
+
requirePermission(input.grant, "variable-sets:use");
|
|
136
166
|
}
|
|
137
|
-
update.
|
|
167
|
+
update.variableSetId = null;
|
|
138
168
|
} else {
|
|
139
|
-
await
|
|
169
|
+
await validateVariableSetAttachment(
|
|
140
170
|
{ settings: input.settings, db: input.db },
|
|
141
171
|
input.grant,
|
|
142
172
|
input.existing.workspaceId,
|
|
143
|
-
|
|
173
|
+
nextVariableSetId,
|
|
144
174
|
);
|
|
145
|
-
update.
|
|
175
|
+
update.variableSetId = nextVariableSetId;
|
|
146
176
|
}
|
|
147
177
|
}
|
|
178
|
+
if (input.payload.rigId !== undefined) {
|
|
179
|
+
// The rig binds fresh per fire, so changing it on a reusable-session task is
|
|
180
|
+
// harmless for the LIVE session (which keeps its own frozen version) and
|
|
181
|
+
// only affects subsequent new-session fires — no live-session guard needed.
|
|
182
|
+
if (input.payload.rigId !== null) {
|
|
183
|
+
await requireScheduledTaskRig(input.db, input.existing.workspaceId, input.payload.rigId);
|
|
184
|
+
}
|
|
185
|
+
update.rigId = input.payload.rigId;
|
|
186
|
+
}
|
|
148
187
|
if (input.payload.agentConfig !== undefined) {
|
|
149
188
|
// Editing the instructions of a task that injects workspace secrets is
|
|
150
189
|
// equivalent to attaching those secrets to new instructions, so it
|
|
151
|
-
// requires
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
190
|
+
// requires variable-sets:use even though plain task edits do not.
|
|
191
|
+
const willHaveVariableSet =
|
|
192
|
+
input.payload.variableSetId !== undefined
|
|
193
|
+
? input.payload.variableSetId !== null
|
|
194
|
+
: Boolean(input.existing.variableSetId);
|
|
195
|
+
if (willHaveVariableSet) {
|
|
196
|
+
requirePermission(input.grant, "variable-sets:use");
|
|
157
197
|
}
|
|
158
198
|
update.agentConfig = await validateScheduledTaskAgentConfig({
|
|
159
199
|
settings: input.settings,
|
|
@@ -167,7 +207,11 @@ export async function validatedScheduledTaskUpdate(input: {
|
|
|
167
207
|
return update;
|
|
168
208
|
}
|
|
169
209
|
|
|
170
|
-
export async function requireScheduledTaskForApi(
|
|
210
|
+
export async function requireScheduledTaskForApi(
|
|
211
|
+
db: Database,
|
|
212
|
+
workspaceId: string,
|
|
213
|
+
taskId: string,
|
|
214
|
+
): Promise<ScheduledTask> {
|
|
171
215
|
const task = await getScheduledTask(db, workspaceId, taskId);
|
|
172
216
|
if (!task) {
|
|
173
217
|
throw new HTTPException(404, { message: "scheduled task not found" });
|
|
@@ -175,7 +219,10 @@ export async function requireScheduledTaskForApi(db: Database, workspaceId: stri
|
|
|
175
219
|
return task;
|
|
176
220
|
}
|
|
177
221
|
|
|
178
|
-
export async function restoreScheduledTask(
|
|
222
|
+
export async function restoreScheduledTask(
|
|
223
|
+
db: Database,
|
|
224
|
+
task: ScheduledTask,
|
|
225
|
+
): Promise<ScheduledTask> {
|
|
179
226
|
return await updateScheduledTask(db, task.workspaceId, task.id, {
|
|
180
227
|
name: task.name,
|
|
181
228
|
status: task.status,
|
|
@@ -184,7 +231,7 @@ export async function restoreScheduledTask(db: Database, task: ScheduledTask): P
|
|
|
184
231
|
overlapPolicy: task.overlapPolicy,
|
|
185
232
|
agentConfig: task.agentConfig,
|
|
186
233
|
reusableSessionId: task.reusableSessionId,
|
|
187
|
-
|
|
234
|
+
variableSetId: task.variableSetId,
|
|
188
235
|
metadata: task.metadata,
|
|
189
236
|
});
|
|
190
237
|
}
|
|
@@ -197,7 +244,9 @@ export async function syncCreatedScheduledTask(input: {
|
|
|
197
244
|
try {
|
|
198
245
|
await input.workflowClient.syncScheduledTask({ task: input.task });
|
|
199
246
|
} catch (error) {
|
|
200
|
-
await deleteScheduledTask(input.db, input.task.workspaceId, input.task.id).catch(
|
|
247
|
+
await deleteScheduledTask(input.db, input.task.workspaceId, input.task.id).catch(
|
|
248
|
+
() => undefined,
|
|
249
|
+
);
|
|
201
250
|
throw error;
|
|
202
251
|
}
|
|
203
252
|
}
|
|
@@ -257,7 +306,11 @@ export function manualScheduledTaskTriggerWorkflowId(taskId: string, triggerToke
|
|
|
257
306
|
* charge. Shares the stable trigger token with the workflow id so the charge
|
|
258
307
|
* and the run dedupe together under retry.
|
|
259
308
|
*/
|
|
260
|
-
export function manualScheduledTaskTriggerUsageKey(
|
|
309
|
+
export function manualScheduledTaskTriggerUsageKey(
|
|
310
|
+
workspaceId: string,
|
|
311
|
+
taskId: string,
|
|
312
|
+
triggerToken: string,
|
|
313
|
+
): string {
|
|
261
314
|
return `agent_run.created:scheduled-trigger:${workspaceId}:${taskId}:${triggerToken}`;
|
|
262
315
|
}
|
|
263
316
|
|
|
@@ -275,17 +328,30 @@ async function validateScheduledTaskAgentConfig(input: {
|
|
|
275
328
|
// a model the host does not expose). An omitted model inherits the host
|
|
276
329
|
// default downstream, which is always configured.
|
|
277
330
|
assertConfiguredModel(input.settings, input.payload.agentConfig.model);
|
|
331
|
+
// Same policy vetting as the session choke points; an omitted model flows
|
|
332
|
+
// through session creation later, where the effective default is vetted.
|
|
333
|
+
await assertWorkspaceModelPolicyAllows(
|
|
334
|
+
input.db,
|
|
335
|
+
input.settings,
|
|
336
|
+
input.workspaceId,
|
|
337
|
+
input.payload.agentConfig.model,
|
|
338
|
+
);
|
|
278
339
|
const resources = normalizeResources(input.payload.agentConfig.resources ?? []);
|
|
279
|
-
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
340
|
+
const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(
|
|
341
|
+
input.db,
|
|
342
|
+
input.workspaceId,
|
|
343
|
+
input.settings,
|
|
344
|
+
);
|
|
280
345
|
const requestedTools = validateToolRefs(input.payload.agentConfig.tools ?? [], runtimeSettings);
|
|
281
346
|
// A task whose creator did not choose tools gets the workspace's enabled
|
|
282
347
|
// capability MCP servers, exactly like a session created without a tools
|
|
283
348
|
// key. Scheduled runs are sessions too; "no MCP servers at all" was a trap
|
|
284
349
|
// every pack/template instantiation path kept falling into (a maintenance
|
|
285
350
|
// task that cannot reach its workspace's notebook MCP cannot do its job).
|
|
286
|
-
const tools =
|
|
287
|
-
|
|
288
|
-
|
|
351
|
+
const tools =
|
|
352
|
+
(input.toolsProvided ?? true)
|
|
353
|
+
? requestedTools
|
|
354
|
+
: withDefaultEnabledCapabilityMcpTools(requestedTools, input.settings, runtimeSettings);
|
|
289
355
|
const prompt = input.payload.agentConfig.prompt.trim();
|
|
290
356
|
if (!prompt) {
|
|
291
357
|
throw new HTTPException(422, { message: "scheduled task prompt is required" });
|