@opengeni/core 2.10.0-canary.0 → 2.10.0-canary.2
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/application/external-workspace-members.d.ts +16 -0
- package/dist/application/host-mcp-resolvers.d.ts +13 -0
- package/dist/application/new-session-drafts.d.ts +4 -2
- package/dist/chunk-5WZYFL45.js +266 -0
- package/dist/chunk-5WZYFL45.js.map +1 -0
- package/dist/domain/file-owner.d.ts +8 -0
- package/dist/domain/knowledge-files.d.ts +20 -0
- package/dist/domain/knowledge-messages.d.ts +39 -0
- package/dist/domain/knowledge-messages.test.d.ts +1 -0
- package/dist/domain/knowledge-search.d.ts +80 -0
- package/dist/domain/knowledge-search.test.d.ts +1 -0
- package/dist/domain/knowledge.d.ts +9 -0
- package/dist/domain/pr-review.d.ts +2 -2
- package/dist/domain/resources.d.ts +2 -2
- package/dist/domain/scheduled-tasks.d.ts +12 -2
- package/dist/domain/sessions.d.ts +6 -2
- package/dist/domain/skills.d.ts +16 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3948 -3939
- package/dist/index.js.map +1 -1
- package/dist/remote-mcp-credentials.d.ts +16 -1
- package/dist/remote-mcp-credentials.js +7 -215
- package/dist/remote-mcp-credentials.js.map +1 -1
- package/package.json +12 -12
- package/src/application/external-workspace-members.ts +89 -0
- package/src/application/host-mcp-resolvers.ts +85 -0
- package/src/application/new-session-drafts.ts +37 -3
- package/src/domain/file-owner.ts +52 -0
- package/src/domain/host-mcp-task-admission.ts +27 -17
- package/src/domain/knowledge-files.ts +39 -0
- package/src/domain/knowledge-messages.test.ts +91 -0
- package/src/domain/knowledge-messages.ts +105 -0
- package/src/domain/knowledge-search.test.ts +45 -0
- package/src/domain/knowledge-search.ts +55 -0
- package/src/domain/knowledge.ts +133 -0
- package/src/domain/product-integration-skill.gen.ts +7 -7
- package/src/domain/resources.ts +36 -25
- package/src/domain/scheduled-tasks.ts +362 -139
- package/src/domain/sessions.ts +117 -20
- package/src/domain/skills.ts +8 -0
- package/src/index.ts +9 -4
- package/src/remote-mcp-credentials.ts +76 -8
- package/dist/domain/company-brain-governed-writes.d.ts +0 -79
- package/dist/domain/governed-learning-activation.d.ts +0 -16
- package/dist/domain/governed-learning-evaluator.d.ts +0 -6
- package/dist/domain/remember.d.ts +0 -72
- package/src/domain/company-brain-governed-writes.ts +0 -346
- package/src/domain/governed-learning-activation.ts +0 -44
- package/src/domain/governed-learning-evaluator.ts +0 -49
- package/src/domain/remember.ts +0 -577
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
scheduledTaskKnowledgeSource,
|
|
3
|
+
requireScheduledTaskKnowledgeSource,
|
|
4
|
+
} from "@opengeni/contracts";
|
|
1
5
|
import {
|
|
2
6
|
allowedFirstPartyMcpToolsForSession,
|
|
3
7
|
resolveFirstPartyMcpToolPolicy,
|
|
@@ -28,6 +32,10 @@ import {
|
|
|
28
32
|
} from "@opengeni/contracts";
|
|
29
33
|
import {
|
|
30
34
|
createScheduledTask,
|
|
35
|
+
getAgentLearningSettings,
|
|
36
|
+
saveAgentLearningSettings,
|
|
37
|
+
type KnowledgeContext,
|
|
38
|
+
dbSql,
|
|
31
39
|
deleteScheduledTask,
|
|
32
40
|
getConnectionMetadata,
|
|
33
41
|
getEnrollment,
|
|
@@ -42,6 +50,8 @@ import {
|
|
|
42
50
|
getSandbox,
|
|
43
51
|
getSessionTurnXaiProviderAccountAuthoritySnapshot,
|
|
44
52
|
getSession,
|
|
53
|
+
getSessionAuthorityProjection,
|
|
54
|
+
withSessionRlsActorContext,
|
|
45
55
|
nestedPostgresSqlState,
|
|
46
56
|
requireWorkspace,
|
|
47
57
|
scopedKnowledgeScopeKey,
|
|
@@ -54,6 +64,8 @@ import {
|
|
|
54
64
|
type UpdateScheduledTaskInput,
|
|
55
65
|
} from "@opengeni/db";
|
|
56
66
|
import { HTTPException } from "hono/http-exception";
|
|
67
|
+
import { knowledgeContextForAccess } from "./knowledge";
|
|
68
|
+
import { fileOwnerContextForAccess, fileOwnerContextForAgent } from "./file-owner";
|
|
57
69
|
import { isDeepStrictEqual } from "node:util";
|
|
58
70
|
import { hasPermission, requirePermission, type AccessGrantAuthorization } from "../access";
|
|
59
71
|
import {
|
|
@@ -173,6 +185,22 @@ export async function createValidatedScheduledTask(input: {
|
|
|
173
185
|
sessionAuthorization?: SessionAuthorizationPort | null | undefined;
|
|
174
186
|
authorizationSurface?: SessionAuthorizationSurface | undefined;
|
|
175
187
|
}): Promise<ScheduledTask> {
|
|
188
|
+
const learning = "agentLearning" in input.payload ? input.payload.agentLearning : undefined;
|
|
189
|
+
const learningContext =
|
|
190
|
+
learning && input.authorization
|
|
191
|
+
? await knowledgeContextForAccess(
|
|
192
|
+
{ db: input.db },
|
|
193
|
+
input.authorization,
|
|
194
|
+
"scheduled_tasks:manage",
|
|
195
|
+
)
|
|
196
|
+
: null;
|
|
197
|
+
if (learning && learningContext?.actor.kind !== "human") {
|
|
198
|
+
throw new HTTPException(403, {
|
|
199
|
+
message: "Only an authenticated person can configure Agent learning",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (learningContext?.actor.kind === "human" && learning)
|
|
203
|
+
learningContext.actor.settingsScopes = [learning.scope];
|
|
176
204
|
// API parsing fills this default, but pack installers and older internal
|
|
177
205
|
// callers can still invoke the shared validator with the pre-action shape.
|
|
178
206
|
const action = input.payload.action ?? ({ kind: "agent_turn" } as const);
|
|
@@ -180,7 +208,7 @@ export async function createValidatedScheduledTask(input: {
|
|
|
180
208
|
"selectedHostMcpDelegations" in input.payload
|
|
181
209
|
? input.payload.selectedHostMcpDelegations
|
|
182
210
|
: undefined;
|
|
183
|
-
const knowledgeAction =
|
|
211
|
+
const knowledgeAction = input.payload.agentConfig.knowledgeSource ?? null;
|
|
184
212
|
if (knowledgeAction) {
|
|
185
213
|
await validateKnowledgeSourceSyncAction({
|
|
186
214
|
db: input.db,
|
|
@@ -188,27 +216,46 @@ export async function createValidatedScheduledTask(input: {
|
|
|
188
216
|
action: knowledgeAction,
|
|
189
217
|
});
|
|
190
218
|
}
|
|
191
|
-
const agentConfig: ScheduledTaskAgentConfig =
|
|
192
|
-
|
|
193
|
-
:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
219
|
+
const agentConfig: ScheduledTaskAgentConfig = await validateScheduledTaskAgentConfig({
|
|
220
|
+
...input,
|
|
221
|
+
workspaceId: input.grant.workspaceId,
|
|
222
|
+
});
|
|
223
|
+
if (knowledgeAction && input.payload.overlapPolicy === "allow_concurrent")
|
|
224
|
+
throw new HTTPException(422, { message: "Source tasks require skip or buffer_one overlap" });
|
|
197
225
|
const id = crypto.randomUUID();
|
|
198
226
|
validateScheduledTaskSchedule(input.payload.schedule);
|
|
199
|
-
const target =
|
|
200
|
-
|
|
201
|
-
:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
227
|
+
const target = await validateScheduledTaskTarget({
|
|
228
|
+
db: input.db,
|
|
229
|
+
sessionAuthorization: input.sessionAuthorization,
|
|
230
|
+
authorizationSurface: input.authorizationSurface,
|
|
231
|
+
grant: input.grant,
|
|
232
|
+
targetSessionId: input.payload.targetSessionId,
|
|
233
|
+
runMode: input.payload.runMode,
|
|
234
|
+
variableSetId: input.payload.variableSetId,
|
|
235
|
+
rigId: input.payload.rigId,
|
|
236
|
+
agentConfig,
|
|
237
|
+
});
|
|
238
|
+
if (
|
|
239
|
+
knowledgeAction?.destination.kind === "personal" &&
|
|
240
|
+
input.payload.runMode !== "new_session_per_run"
|
|
241
|
+
)
|
|
242
|
+
throw new HTTPException(422, {
|
|
243
|
+
message: "Personal source tasks create a private session for each run",
|
|
244
|
+
});
|
|
245
|
+
if (learning) {
|
|
246
|
+
const workspace = await requireWorkspace(input.db, input.grant.workspaceId);
|
|
247
|
+
const destination =
|
|
248
|
+
knowledgeAction?.destination.kind === "personal" ||
|
|
249
|
+
workspace.kind === "personal" ||
|
|
250
|
+
target?.tenancy?.visibility === "private" ||
|
|
251
|
+
target?.memoryScope === "user"
|
|
252
|
+
? "personal"
|
|
253
|
+
: "workspace";
|
|
254
|
+
if (learning.scope !== destination)
|
|
255
|
+
throw new HTTPException(422, {
|
|
256
|
+
message: `This task saves Knowledge in the ${destination} scope. Set its Agent learning override for that scope.`,
|
|
211
257
|
});
|
|
258
|
+
}
|
|
212
259
|
if (!knowledgeAction) {
|
|
213
260
|
await validateScheduledTaskMachineTarget({
|
|
214
261
|
settings: input.settings,
|
|
@@ -218,7 +265,7 @@ export async function createValidatedScheduledTask(input: {
|
|
|
218
265
|
agentConfig,
|
|
219
266
|
});
|
|
220
267
|
}
|
|
221
|
-
if (
|
|
268
|
+
if (input.payload.variableSetId) {
|
|
222
269
|
await validateVariableSetAttachment(
|
|
223
270
|
{ settings: input.settings, db: input.db },
|
|
224
271
|
input.grant,
|
|
@@ -290,7 +337,7 @@ export async function createValidatedScheduledTask(input: {
|
|
|
290
337
|
subjectId: input.grant.subjectId,
|
|
291
338
|
});
|
|
292
339
|
const beforeCreateCommit =
|
|
293
|
-
|
|
340
|
+
input.payload.runMode !== "existing_session"
|
|
294
341
|
? workspaceCustomModelCommitGuard({
|
|
295
342
|
settings: input.settings,
|
|
296
343
|
accountId: input.grant.accountId,
|
|
@@ -299,49 +346,60 @@ export async function createValidatedScheduledTask(input: {
|
|
|
299
346
|
})
|
|
300
347
|
: undefined;
|
|
301
348
|
return await withScheduledTaskAuthorityWriteErrors(() =>
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
captureHostAuthority: prepareHostMcpTaskAdmission({
|
|
328
|
-
settings: runtimeSettings,
|
|
329
|
-
tools: target?.tools ?? agentConfig.tools,
|
|
330
|
-
grant: input.grant,
|
|
331
|
-
...(input.authorization ? { authorization: input.authorization } : {}),
|
|
332
|
-
selections: hostSelections,
|
|
333
|
-
}),
|
|
334
|
-
}
|
|
335
|
-
: hostSelections === undefined && creationInitiator.actor && runtimeSettings
|
|
349
|
+
input.db.transaction(async (transaction) => {
|
|
350
|
+
const task = await createScheduledTask(transaction, {
|
|
351
|
+
id,
|
|
352
|
+
accountId: input.grant.accountId,
|
|
353
|
+
workspaceId: input.grant.workspaceId,
|
|
354
|
+
name: trimmedScheduledTaskName(input.payload.name),
|
|
355
|
+
status: input.payload.status,
|
|
356
|
+
schedule: input.payload.schedule,
|
|
357
|
+
temporalScheduleId: scheduledTaskTemporalScheduleId(id),
|
|
358
|
+
runMode: input.payload.runMode,
|
|
359
|
+
overlapPolicy: input.payload.overlapPolicy,
|
|
360
|
+
action,
|
|
361
|
+
agentConfig,
|
|
362
|
+
...(creationInitiator.initiator ? { createdBy: creationInitiator.initiator } : {}),
|
|
363
|
+
...(creationInitiator.context ? { createdByContext: creationInitiator.context } : {}),
|
|
364
|
+
createdByActor: creationInitiator.actor ?? null,
|
|
365
|
+
...(captureLinkAuthority ? { captureLinkAuthority } : {}),
|
|
366
|
+
personalConnectionDelegations,
|
|
367
|
+
xaiProviderAccountAuthoritySnapshot,
|
|
368
|
+
creatorPolicy,
|
|
369
|
+
targetSessionId: target?.id ?? null,
|
|
370
|
+
variableSetId: input.payload.variableSetId ?? null,
|
|
371
|
+
rigId: input.payload.rigId ?? null,
|
|
372
|
+
metadata: input.payload.metadata,
|
|
373
|
+
...(hostSelections?.length && runtimeSettings
|
|
336
374
|
? {
|
|
337
|
-
captureHostAuthority:
|
|
338
|
-
runtimeSettings,
|
|
339
|
-
target?.tools ?? agentConfig.tools,
|
|
340
|
-
|
|
341
|
-
|
|
375
|
+
captureHostAuthority: prepareHostMcpTaskAdmission({
|
|
376
|
+
settings: runtimeSettings,
|
|
377
|
+
tools: target?.tools ?? agentConfig.tools,
|
|
378
|
+
grant: input.grant,
|
|
379
|
+
...(input.authorization ? { authorization: input.authorization } : {}),
|
|
380
|
+
selections: hostSelections,
|
|
381
|
+
}),
|
|
342
382
|
}
|
|
343
|
-
:
|
|
344
|
-
|
|
383
|
+
: hostSelections === undefined && creationInitiator.actor && runtimeSettings
|
|
384
|
+
? {
|
|
385
|
+
captureHostAuthority: prepareInheritedHostMcpTaskAdmission(
|
|
386
|
+
runtimeSettings,
|
|
387
|
+
target?.tools ?? agentConfig.tools,
|
|
388
|
+
creationInitiator.actor,
|
|
389
|
+
),
|
|
390
|
+
}
|
|
391
|
+
: {}),
|
|
392
|
+
...(beforeCreateCommit ? { beforeCreateCommit } : {}),
|
|
393
|
+
});
|
|
394
|
+
if (learning && learningContext)
|
|
395
|
+
await saveAgentLearningSettings(transaction, learningContext, {
|
|
396
|
+
scope: learning.scope,
|
|
397
|
+
source: { kind: "scheduled_task", id: task.id },
|
|
398
|
+
operationId: crypto.randomUUID(),
|
|
399
|
+
expectedVersion: 0,
|
|
400
|
+
settings: learning.settings,
|
|
401
|
+
});
|
|
402
|
+
return task;
|
|
345
403
|
}),
|
|
346
404
|
);
|
|
347
405
|
}
|
|
@@ -429,6 +487,10 @@ export async function withScheduledTaskAuthorityWriteErrors<T>(run: () => Promis
|
|
|
429
487
|
try {
|
|
430
488
|
return await run();
|
|
431
489
|
} catch (error) {
|
|
490
|
+
if (nestedPostgresSqlState(error) === "40001")
|
|
491
|
+
throw new HTTPException(409, {
|
|
492
|
+
message: "Scheduled task learning settings changed. Reload the task before saving.",
|
|
493
|
+
});
|
|
432
494
|
if (nestedPostgresSqlState(error) === "42501") {
|
|
433
495
|
const detail = nestedPostgresMessage(error);
|
|
434
496
|
throw new HTTPException(409, {
|
|
@@ -447,7 +509,73 @@ export async function updateScheduledTaskForApi(
|
|
|
447
509
|
workspaceId: string,
|
|
448
510
|
taskId: string,
|
|
449
511
|
update: UpdateScheduledTaskInput,
|
|
512
|
+
learning?: {
|
|
513
|
+
authorization: AccessGrantAuthorization;
|
|
514
|
+
request: NonNullable<UpdateScheduledTaskPayload["agentLearning"]>;
|
|
515
|
+
restoreState: ScheduledTaskRestoreState;
|
|
516
|
+
},
|
|
450
517
|
): Promise<ScheduledTask> {
|
|
518
|
+
if (learning) {
|
|
519
|
+
const context = await knowledgeContextForAccess(
|
|
520
|
+
{ db },
|
|
521
|
+
learning.authorization,
|
|
522
|
+
"scheduled_tasks:manage",
|
|
523
|
+
);
|
|
524
|
+
if (context.actor.kind !== "human")
|
|
525
|
+
throw new HTTPException(403, {
|
|
526
|
+
message: "Only an authenticated person can configure Agent learning",
|
|
527
|
+
});
|
|
528
|
+
context.actor.settingsScopes = [learning.request.scope];
|
|
529
|
+
return withScheduledTaskAuthorityWriteErrors(() =>
|
|
530
|
+
db.transaction(async (tx) => {
|
|
531
|
+
const source = { kind: "scheduled_task" as const, id: taskId };
|
|
532
|
+
const baselineScope = learning.request.baselineScope ?? learning.request.scope;
|
|
533
|
+
// Lock policy owners in one order before comparing the draft baseline.
|
|
534
|
+
// A destination change must not race an edit in its previous owner layer.
|
|
535
|
+
const subjectId = context.actor.kind === "human" ? context.actor.subjectId : "";
|
|
536
|
+
const owners = [
|
|
537
|
+
...new Set(
|
|
538
|
+
[baselineScope, learning.request.scope].map((scope) =>
|
|
539
|
+
scope === "personal" ? `personal:${subjectId}` : `workspace:${workspaceId}`,
|
|
540
|
+
),
|
|
541
|
+
),
|
|
542
|
+
].sort();
|
|
543
|
+
for (const owner of owners)
|
|
544
|
+
await tx.execute(
|
|
545
|
+
dbSql`SELECT pg_advisory_xact_lock(hashtextextended(${`agent-learning:${context.accountId}:${owner}`},0))`,
|
|
546
|
+
);
|
|
547
|
+
const baseline = await getAgentLearningSettings(tx, context, baselineScope, source);
|
|
548
|
+
if (baseline.version !== learning.request.expectedVersion)
|
|
549
|
+
throw new HTTPException(409, {
|
|
550
|
+
message: "Scheduled task learning settings changed. Reload the task before saving.",
|
|
551
|
+
});
|
|
552
|
+
const task = await updateScheduledTask(tx, workspaceId, taskId, update);
|
|
553
|
+
const prior = await getAgentLearningSettings(tx, context, learning.request.scope, source);
|
|
554
|
+
const saved = await saveAgentLearningSettings(tx, context, {
|
|
555
|
+
scope: learning.request.scope,
|
|
556
|
+
operationId: learning.request.operationId,
|
|
557
|
+
expectedVersion:
|
|
558
|
+
baselineScope === learning.request.scope
|
|
559
|
+
? learning.request.expectedVersion
|
|
560
|
+
: prior.version,
|
|
561
|
+
source,
|
|
562
|
+
settings: {
|
|
563
|
+
knowledge: "inherit",
|
|
564
|
+
instructions: "inherit",
|
|
565
|
+
skills: "inherit",
|
|
566
|
+
...learning.request.settings,
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
learning.restoreState.learning = {
|
|
570
|
+
context,
|
|
571
|
+
scope: learning.request.scope,
|
|
572
|
+
settings: prior.settings,
|
|
573
|
+
expectedVersion: saved.version,
|
|
574
|
+
};
|
|
575
|
+
return task;
|
|
576
|
+
}),
|
|
577
|
+
);
|
|
578
|
+
}
|
|
451
579
|
return await withScheduledTaskAuthorityWriteErrors(() =>
|
|
452
580
|
updateScheduledTask(db, workspaceId, taskId, update),
|
|
453
581
|
);
|
|
@@ -693,53 +821,68 @@ export async function validatedScheduledTaskUpdate(input: {
|
|
|
693
821
|
sessionAuthorization?: SessionAuthorizationPort | null | undefined;
|
|
694
822
|
authorizationSurface?: SessionAuthorizationSurface | undefined;
|
|
695
823
|
}): Promise<UpdateScheduledTaskInput> {
|
|
824
|
+
if (input.payload.agentLearning) {
|
|
825
|
+
const context = input.authorization
|
|
826
|
+
? await knowledgeContextForAccess(
|
|
827
|
+
{ db: input.db },
|
|
828
|
+
input.authorization,
|
|
829
|
+
"scheduled_tasks:manage",
|
|
830
|
+
)
|
|
831
|
+
: null;
|
|
832
|
+
if (context?.actor.kind !== "human")
|
|
833
|
+
throw new HTTPException(403, {
|
|
834
|
+
message: "Only an authenticated person can configure Agent learning",
|
|
835
|
+
});
|
|
836
|
+
}
|
|
696
837
|
const update: UpdateScheduledTaskInput = {};
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
838
|
+
const requestedKnowledgeSource = input.payload.agentConfig?.knowledgeSource ?? null;
|
|
839
|
+
const existingKnowledgeSource = scheduledTaskKnowledgeSource(input.existing);
|
|
840
|
+
// Editing an ordinary source task's prompt/settings must not orphan its
|
|
841
|
+
// connector binding. Deleting the task is the explicit source-disable path.
|
|
842
|
+
if (
|
|
843
|
+
input.payload.agentConfig &&
|
|
844
|
+
input.existing.agentConfig.knowledgeSource &&
|
|
845
|
+
!input.payload.agentConfig.knowledgeSource
|
|
846
|
+
) {
|
|
847
|
+
input = {
|
|
848
|
+
...input,
|
|
849
|
+
payload: {
|
|
850
|
+
...input.payload,
|
|
851
|
+
agentConfig: {
|
|
852
|
+
...input.payload.agentConfig,
|
|
853
|
+
knowledgeSource: input.existing.agentConfig.knowledgeSource,
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
};
|
|
702
857
|
}
|
|
703
|
-
if (
|
|
704
|
-
|
|
705
|
-
input.payload.agentConfig !== undefined ||
|
|
706
|
-
input.payload.runMode !== undefined ||
|
|
707
|
-
input.payload.targetSessionId !== undefined ||
|
|
708
|
-
input.payload.variableSetId !== undefined ||
|
|
709
|
-
input.payload.rigId !== undefined ||
|
|
710
|
-
input.payload.connectionAuthorities !== undefined ||
|
|
711
|
-
input.payload.selectedHostMcpDelegations !== undefined
|
|
712
|
-
) {
|
|
713
|
-
throw new HTTPException(422, {
|
|
714
|
-
message: "knowledge source schedules do not accept agent/session configuration",
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
if (input.payload.overlapPolicy === "allow_concurrent") {
|
|
718
|
-
throw new HTTPException(422, {
|
|
719
|
-
message: "knowledge source schedules require skip or buffer_one overlap",
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
if (input.payload.action?.kind === "knowledge_source_sync") {
|
|
723
|
-
await validateKnowledgeSourceSyncAction({
|
|
724
|
-
db: input.db,
|
|
725
|
-
grant: input.grant,
|
|
726
|
-
action: input.payload.action,
|
|
727
|
-
});
|
|
728
|
-
update.action = input.payload.action;
|
|
729
|
-
}
|
|
730
|
-
if (input.payload.name !== undefined)
|
|
731
|
-
update.name = trimmedScheduledTaskName(input.payload.name);
|
|
732
|
-
if (input.payload.status !== undefined) update.status = input.payload.status;
|
|
733
|
-
if (input.payload.schedule !== undefined) {
|
|
734
|
-
validateScheduledTaskSchedule(input.payload.schedule);
|
|
735
|
-
update.schedule = input.payload.schedule;
|
|
736
|
-
}
|
|
737
|
-
if (input.payload.overlapPolicy !== undefined) {
|
|
738
|
-
update.overlapPolicy = input.payload.overlapPolicy;
|
|
739
|
-
}
|
|
740
|
-
if (input.payload.metadata !== undefined) update.metadata = input.payload.metadata;
|
|
741
|
-
return update;
|
|
858
|
+
if (input.payload.action && input.payload.action.kind !== "agent_turn") {
|
|
859
|
+
throw new HTTPException(422, { message: "Source ingestion uses an ordinary agent task" });
|
|
742
860
|
}
|
|
861
|
+
const knowledgeSource =
|
|
862
|
+
input.payload.agentConfig?.knowledgeSource ?? scheduledTaskKnowledgeSource(input.existing);
|
|
863
|
+
if (
|
|
864
|
+
knowledgeSource &&
|
|
865
|
+
requestedKnowledgeSource &&
|
|
866
|
+
!isDeepStrictEqual(requestedKnowledgeSource, existingKnowledgeSource)
|
|
867
|
+
) {
|
|
868
|
+
await validateKnowledgeSourceSyncAction({
|
|
869
|
+
db: input.db,
|
|
870
|
+
grant: input.grant,
|
|
871
|
+
action: knowledgeSource,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
if (
|
|
875
|
+
knowledgeSource &&
|
|
876
|
+
(input.payload.overlapPolicy ?? input.existing.overlapPolicy) === "allow_concurrent"
|
|
877
|
+
)
|
|
878
|
+
throw new HTTPException(422, { message: "Source tasks require skip or buffer_one overlap" });
|
|
879
|
+
if (
|
|
880
|
+
knowledgeSource?.destination.kind === "personal" &&
|
|
881
|
+
(input.payload.runMode ?? input.existing.runMode) !== "new_session_per_run"
|
|
882
|
+
)
|
|
883
|
+
throw new HTTPException(422, {
|
|
884
|
+
message: "Personal source tasks create a private session for each run",
|
|
885
|
+
});
|
|
743
886
|
const existingTarget = input.existing.targetSessionId;
|
|
744
887
|
const nextRunMode = input.payload.runMode ?? input.existing.runMode;
|
|
745
888
|
const nextTargetSessionId =
|
|
@@ -846,7 +989,15 @@ export async function validatedScheduledTaskUpdate(input: {
|
|
|
846
989
|
objectStorage: input.objectStorage,
|
|
847
990
|
grant: input.grant,
|
|
848
991
|
workspaceId: input.existing.workspaceId,
|
|
849
|
-
|
|
992
|
+
...(input.authorization ? { authorization: input.authorization } : {}),
|
|
993
|
+
payload: {
|
|
994
|
+
agentConfig: input.payload.agentConfig,
|
|
995
|
+
runMode: nextRunMode,
|
|
996
|
+
targetSessionId:
|
|
997
|
+
nextRunMode === "existing_session"
|
|
998
|
+
? nextTargetSessionId
|
|
999
|
+
: input.existing.reusableSessionId,
|
|
1000
|
+
},
|
|
850
1001
|
...(input.toolsProvided !== undefined ? { toolsProvided: input.toolsProvided } : {}),
|
|
851
1002
|
});
|
|
852
1003
|
if (
|
|
@@ -1038,13 +1189,15 @@ export async function validatedScheduledTaskUpdate(input: {
|
|
|
1038
1189
|
rigId: input.payload.rigId !== undefined ? input.payload.rigId : input.existing.rigId,
|
|
1039
1190
|
agentConfig: update.agentConfig ?? input.existing.agentConfig,
|
|
1040
1191
|
});
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1192
|
+
if (!knowledgeSource) {
|
|
1193
|
+
await validateScheduledTaskMachineTarget({
|
|
1194
|
+
settings: input.settings,
|
|
1195
|
+
db: input.db,
|
|
1196
|
+
grant: input.grant,
|
|
1197
|
+
runMode: nextRunMode,
|
|
1198
|
+
agentConfig: update.agentConfig ?? input.existing.agentConfig,
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1048
1201
|
if (
|
|
1049
1202
|
input.payload.targetSessionId !== undefined ||
|
|
1050
1203
|
input.existing.runMode === "existing_session" ||
|
|
@@ -1099,6 +1252,12 @@ export async function requireScheduledTaskForApi(
|
|
|
1099
1252
|
export type ScheduledTaskRestoreState = {
|
|
1100
1253
|
task: ScheduledTask;
|
|
1101
1254
|
personalConnectionDelegations: McpPersonalConnectionDelegation[];
|
|
1255
|
+
learning?: {
|
|
1256
|
+
context: KnowledgeContext;
|
|
1257
|
+
scope: "workspace" | "personal";
|
|
1258
|
+
settings: import("@opengeni/contracts").AgentLearningOverridePatch;
|
|
1259
|
+
expectedVersion: number;
|
|
1260
|
+
};
|
|
1102
1261
|
};
|
|
1103
1262
|
|
|
1104
1263
|
export async function captureScheduledTaskRestoreState(
|
|
@@ -1239,29 +1398,28 @@ export async function deleteScheduledTaskLifecycle(input: {
|
|
|
1239
1398
|
if (!task) throw new HTTPException(404, { message: "Scheduled task not found" });
|
|
1240
1399
|
const wasLive = task.deletedAt === null;
|
|
1241
1400
|
if (
|
|
1242
|
-
task
|
|
1243
|
-
(task.
|
|
1244
|
-
task.
|
|
1401
|
+
scheduledTaskKnowledgeSource(task) &&
|
|
1402
|
+
(requireScheduledTaskKnowledgeSource(task).initiatingSubjectId !== input.subjectId ||
|
|
1403
|
+
requireScheduledTaskKnowledgeSource(task).connection.ownerSubjectId !== input.subjectId)
|
|
1245
1404
|
) {
|
|
1246
1405
|
throw new HTTPException(403, {
|
|
1247
1406
|
message: "knowledge source schedule requires the exact initiating subject",
|
|
1248
1407
|
});
|
|
1249
1408
|
}
|
|
1250
|
-
if (wasLive && task
|
|
1409
|
+
if (wasLive && scheduledTaskKnowledgeSource(task)) {
|
|
1251
1410
|
await input.preflightConnectorAuthorization(task);
|
|
1252
1411
|
}
|
|
1253
|
-
const deletion =
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
: await deleteScheduledTask(tx, input.workspaceId, task.id);
|
|
1412
|
+
const deletion = scheduledTaskKnowledgeSource(task)
|
|
1413
|
+
? await (async () => {
|
|
1414
|
+
await input.cleanupConnectorAuthorization(tx, task);
|
|
1415
|
+
return await deleteScheduledTask(tx, input.workspaceId, task.id, {
|
|
1416
|
+
connectorCleanupSubjectId: input.subjectId,
|
|
1417
|
+
connectorCleanupCompleted: true,
|
|
1418
|
+
expectedAuthorityRevision: task.authorityRevision,
|
|
1419
|
+
expectedExecutionDigest: task.executionDigest,
|
|
1420
|
+
});
|
|
1421
|
+
})()
|
|
1422
|
+
: await deleteScheduledTask(tx, input.workspaceId, task.id);
|
|
1265
1423
|
return { task, deletion };
|
|
1266
1424
|
},
|
|
1267
1425
|
);
|
|
@@ -1305,7 +1463,25 @@ export async function syncUpdatedScheduledTask(input: {
|
|
|
1305
1463
|
} catch (error) {
|
|
1306
1464
|
let persistenceRestored = true;
|
|
1307
1465
|
try {
|
|
1308
|
-
await
|
|
1466
|
+
await input.db.transaction(async (tx) => {
|
|
1467
|
+
const learning = input.previous.learning;
|
|
1468
|
+
// Compensate only our exact settings version, while the updated task
|
|
1469
|
+
// still names that owner scope. A concurrent edit is never overwritten.
|
|
1470
|
+
if (learning)
|
|
1471
|
+
await saveAgentLearningSettings(tx, learning.context, {
|
|
1472
|
+
scope: learning.scope,
|
|
1473
|
+
source: { kind: "scheduled_task", id: input.task.id },
|
|
1474
|
+
operationId: crypto.randomUUID(),
|
|
1475
|
+
expectedVersion: learning.expectedVersion,
|
|
1476
|
+
settings: {
|
|
1477
|
+
knowledge: "inherit",
|
|
1478
|
+
instructions: "inherit",
|
|
1479
|
+
skills: "inherit",
|
|
1480
|
+
...learning.settings,
|
|
1481
|
+
},
|
|
1482
|
+
});
|
|
1483
|
+
await restoreScheduledTask(tx, input.previous);
|
|
1484
|
+
});
|
|
1309
1485
|
} catch {
|
|
1310
1486
|
persistenceRestored = false;
|
|
1311
1487
|
}
|
|
@@ -1367,7 +1543,12 @@ async function validateScheduledTaskAgentConfig(input: {
|
|
|
1367
1543
|
db: Database;
|
|
1368
1544
|
objectStorage: ObjectStorageDependency;
|
|
1369
1545
|
grant: AccessGrant;
|
|
1370
|
-
|
|
1546
|
+
authorization?: AccessGrantAuthorization;
|
|
1547
|
+
payload: {
|
|
1548
|
+
agentConfig: ScheduledTaskAgentConfig;
|
|
1549
|
+
runMode?: string;
|
|
1550
|
+
targetSessionId?: string | null | undefined;
|
|
1551
|
+
};
|
|
1371
1552
|
workspaceId: string;
|
|
1372
1553
|
toolsProvided?: boolean;
|
|
1373
1554
|
}): Promise<ScheduledTaskAgentConfig> {
|
|
@@ -1407,6 +1588,17 @@ async function validateScheduledTaskAgentConfig(input: {
|
|
|
1407
1588
|
);
|
|
1408
1589
|
const requestedTools = validateToolRefs(input.payload.agentConfig.tools ?? [], runtimeSettings);
|
|
1409
1590
|
const workspace = await requireWorkspace(input.db, input.workspaceId);
|
|
1591
|
+
if (
|
|
1592
|
+
parent?.tenancy?.visibility === "private" &&
|
|
1593
|
+
workspace.kind !== "personal" &&
|
|
1594
|
+
input.payload.runMode !== "existing_session" &&
|
|
1595
|
+
!input.payload.targetSessionId
|
|
1596
|
+
) {
|
|
1597
|
+
throw new HTTPException(422, {
|
|
1598
|
+
message:
|
|
1599
|
+
"Schedule this private chat as the existing-session target, or create the task in your Personal workspace.",
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1410
1602
|
const workspaceSessionToolDefaults = resolveWorkspaceSessionToolDefaults(workspace.settings);
|
|
1411
1603
|
// A task whose creator did not choose tools gets the workspace's exact
|
|
1412
1604
|
// session defaults (or the deployment compatibility default), exactly like
|
|
@@ -1436,13 +1628,44 @@ async function validateScheduledTaskAgentConfig(input: {
|
|
|
1436
1628
|
if (resources.some((resource) => resource.kind === "file") && !input.objectStorage) {
|
|
1437
1629
|
throw new HTTPException(503, { message: "object storage is not configured" });
|
|
1438
1630
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
input.grant.
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1631
|
+
const fileActor =
|
|
1632
|
+
resources.some((resource) => resource.kind === "file") &&
|
|
1633
|
+
hasPermission(input.grant.permissions, "files:read")
|
|
1634
|
+
? input.authorization
|
|
1635
|
+
? await fileOwnerContextForAccess({ db: input.db }, input.authorization, "files:read")
|
|
1636
|
+
: input.grant.principalKind === "agent_attempt"
|
|
1637
|
+
? await fileOwnerContextForAgent({ db: input.db }, input.grant, "files:read")
|
|
1638
|
+
: { subjectId: input.grant.subjectId, privateFileOwnerSubjectId: null }
|
|
1639
|
+
: { subjectId: input.grant.subjectId, privateFileOwnerSubjectId: null };
|
|
1640
|
+
await withSessionRlsActorContext(fileActor, async () => {
|
|
1641
|
+
const target =
|
|
1642
|
+
input.payload.targetSessionId && input.payload.runMode !== "new_session_per_run"
|
|
1643
|
+
? await getSessionAuthorityProjection(
|
|
1644
|
+
input.db,
|
|
1645
|
+
input.workspaceId,
|
|
1646
|
+
input.payload.targetSessionId,
|
|
1647
|
+
)
|
|
1648
|
+
: null;
|
|
1649
|
+
const privateDestination =
|
|
1650
|
+
workspace.kind === "personal" ||
|
|
1651
|
+
(target?.visibility === "user_private" &&
|
|
1652
|
+
target.ownerSubjectId === fileActor.privateFileOwnerSubjectId) ||
|
|
1653
|
+
(target?.memoryScope === "user" &&
|
|
1654
|
+
target.scopeSubjectId === fileActor.privateFileOwnerSubjectId);
|
|
1655
|
+
await validateFileResources(
|
|
1656
|
+
input.db,
|
|
1657
|
+
input.grant.accountId,
|
|
1658
|
+
input.workspaceId,
|
|
1659
|
+
input.grant.subjectId,
|
|
1660
|
+
resources,
|
|
1661
|
+
{
|
|
1662
|
+
...fileActor,
|
|
1663
|
+
privateFileOwnerSubjectId: privateDestination
|
|
1664
|
+
? (fileActor.privateFileOwnerSubjectId ?? null)
|
|
1665
|
+
: null,
|
|
1666
|
+
},
|
|
1667
|
+
);
|
|
1668
|
+
});
|
|
1446
1669
|
if (input.payload.agentConfig.slackBotConnectionId) {
|
|
1447
1670
|
await validateOpenGeniSlackBotConnectionSelection(
|
|
1448
1671
|
input.db,
|