@opengeni/worker-bundle 0.27.3-canary.0 → 0.28.1-canary.0
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/activities/mcp-credentials.d.ts +3 -1
- package/dist/activities/workspace-capture.d.ts +12 -1
- package/dist/{activities-control-WZRFEIJH.js → activities-control-VVK7RA7F.js} +5 -5
- package/dist/{activities-control-WZRFEIJH.js.map → activities-control-VVK7RA7F.js.map} +1 -1
- package/dist/{activities-turn-V7J34ZOF.js → activities-turn-FOIZYY56.js} +250 -174
- package/dist/activities-turn-FOIZYY56.js.map +1 -0
- package/dist/{chunk-C54VDT6I.js → chunk-AKP6BKM2.js} +3 -1
- package/dist/chunk-AKP6BKM2.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +17 -17
- package/src/activities/agent-turn/agent-build.ts +30 -1
- package/src/activities/agent-turn/finalization.ts +52 -36
- package/src/activities/agent-turn/skill-read.ts +2 -21
- package/src/activities/agent-turn/tool-environment.ts +52 -10
- package/src/activities/mcp-credentials.ts +42 -2
- package/src/activities/scheduled-tasks.ts +8 -6
- package/src/activities/workspace-capture.ts +148 -126
- package/src/pr-review-credentials.ts +2 -0
- package/dist/activities-turn-V7J34ZOF.js.map +0 -1
- package/dist/chunk-C54VDT6I.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -781,7 +781,7 @@ function temporalWorkflowExecutionNotFound(error) {
|
|
|
781
781
|
return isGrpcServiceError(error) && error.code === 5;
|
|
782
782
|
}
|
|
783
783
|
async function createDefaultWorkerActivities(role, dependencies = {}) {
|
|
784
|
-
return role === "control" ? (await import("./activities-control-
|
|
784
|
+
return role === "control" ? (await import("./activities-control-VVK7RA7F.js")).createControlActivities(dependencies) : (await import("./activities-turn-FOIZYY56.js")).createTurnActivities(dependencies);
|
|
785
785
|
}
|
|
786
786
|
function resolveOpenGeniWorkflowDefinition(moduleUrl = import.meta.url) {
|
|
787
787
|
const modulePath = fileURLToPath(moduleUrl).replaceAll("\\", "/");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/worker-bundle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.1-canary.0",
|
|
4
4
|
"description": "OpenGeni worker entry and reusable embedded lifecycle, shipped with a release-coherent pre-bundled Temporal workflow artifact.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -54,22 +54,22 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@llamaindex/liteparse": "2.14.2",
|
|
57
|
-
"@opengeni/agent-proto": "^0.5.1-canary.
|
|
58
|
-
"@opengeni/capabilities": "^0.3.3-canary.
|
|
59
|
-
"@opengeni/codemode": "^0.5.
|
|
60
|
-
"@opengeni/codex": "^0.2.22-canary.
|
|
61
|
-
"@opengeni/config": "^1.0
|
|
62
|
-
"@opengeni/contracts": "^
|
|
63
|
-
"@opengeni/core": "^2.
|
|
64
|
-
"@opengeni/db": "^4.
|
|
65
|
-
"@opengeni/documents": "^0.8.
|
|
66
|
-
"@opengeni/events": "^0.4.
|
|
67
|
-
"@opengeni/github": "^0.7.
|
|
68
|
-
"@opengeni/network": "^0.3.1-canary.
|
|
69
|
-
"@opengeni/observability": "^0.8.
|
|
70
|
-
"@opengeni/runtime": "^2.
|
|
71
|
-
"@opengeni/storage": "^0.2.
|
|
72
|
-
"@opengeni/xai-subscription": "^0.1.4-canary.
|
|
57
|
+
"@opengeni/agent-proto": "^0.5.1-canary.24",
|
|
58
|
+
"@opengeni/capabilities": "^0.3.3-canary.6",
|
|
59
|
+
"@opengeni/codemode": "^0.5.4-canary.0",
|
|
60
|
+
"@opengeni/codex": "^0.2.22-canary.6",
|
|
61
|
+
"@opengeni/config": "^1.1.0-canary.0",
|
|
62
|
+
"@opengeni/contracts": "^3.0.0-canary.0",
|
|
63
|
+
"@opengeni/core": "^2.9.1-canary.0",
|
|
64
|
+
"@opengeni/db": "^4.3.0-canary.0",
|
|
65
|
+
"@opengeni/documents": "^0.8.24-canary.0",
|
|
66
|
+
"@opengeni/events": "^0.4.22-canary.0",
|
|
67
|
+
"@opengeni/github": "^0.7.8-canary.0",
|
|
68
|
+
"@opengeni/network": "^0.3.1-canary.6",
|
|
69
|
+
"@opengeni/observability": "^0.8.24-canary.0",
|
|
70
|
+
"@opengeni/runtime": "^2.5.1-canary.0",
|
|
71
|
+
"@opengeni/storage": "^0.2.125-canary.0",
|
|
72
|
+
"@opengeni/xai-subscription": "^0.1.4-canary.6",
|
|
73
73
|
"@temporalio/activity": "^1.17.0",
|
|
74
74
|
"@temporalio/client": "^1.17.0",
|
|
75
75
|
"@temporalio/worker": "^1.17.0",
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
setXaiSessionAccountPin,
|
|
7
7
|
getWorkspaceVideoGenerationPolicy,
|
|
8
8
|
loadWorkspaceVercelAiGatewayCredentialLease,
|
|
9
|
+
getExternalLinkTurnAuthorization,
|
|
10
|
+
getSessionTurnForAttempt,
|
|
9
11
|
} from "@opengeni/db";
|
|
10
12
|
import {
|
|
11
13
|
formatSkillCatalog,
|
|
@@ -569,11 +571,39 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
|
|
|
569
571
|
outcome: "completed",
|
|
570
572
|
durationSeconds: (performance.now() - postToolPreparationStartedAt) / 1_000,
|
|
571
573
|
});
|
|
574
|
+
const linkedToolAuthority = await getExternalLinkTurnAuthorization(
|
|
575
|
+
db,
|
|
576
|
+
{
|
|
577
|
+
accountId: input.accountId,
|
|
578
|
+
workspaceId: input.workspaceId,
|
|
579
|
+
},
|
|
580
|
+
turn.id,
|
|
581
|
+
);
|
|
582
|
+
if (linkedToolAuthority && !linkedToolAuthority.authorized)
|
|
583
|
+
throw new Error("Native identity link was revoked");
|
|
572
584
|
const agent = (() => {
|
|
573
585
|
const agentConstructionStartedAt = performance.now();
|
|
574
586
|
let agentConstructionOutcome: "completed" | "failed" = "completed";
|
|
575
587
|
try {
|
|
576
588
|
return runtime.buildAgent(eventing.modelRunSettings, runtimeResources, {
|
|
589
|
+
...(linkedToolAuthority
|
|
590
|
+
? {
|
|
591
|
+
authorizeAttemptExecution: async () => {
|
|
592
|
+
const current = await getSessionTurnForAttempt(
|
|
593
|
+
db,
|
|
594
|
+
input.workspaceId,
|
|
595
|
+
input.sessionId,
|
|
596
|
+
input.attemptId,
|
|
597
|
+
);
|
|
598
|
+
if (
|
|
599
|
+
!current ||
|
|
600
|
+
current.id !== turn.id ||
|
|
601
|
+
current.executionGeneration !== turn.executionGeneration
|
|
602
|
+
)
|
|
603
|
+
throw new Error("The linked agent attempt is no longer authorized");
|
|
604
|
+
},
|
|
605
|
+
}
|
|
606
|
+
: {}),
|
|
577
607
|
...(preparedTools.inputWaitYield ? { inputWaitYield: preparedTools.inputWaitYield } : {}),
|
|
578
608
|
reasoningEffort: turn.reasoningEffort,
|
|
579
609
|
latencyMode: turnExecutionPolicy.latencyMode,
|
|
@@ -677,7 +707,6 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
|
|
|
677
707
|
}),
|
|
678
708
|
onRetainableSessionImageOutput: media.retainSessionImageAtToolBoundary,
|
|
679
709
|
skillCatalog: deps.skillCatalog,
|
|
680
|
-
serverSkillReading: true,
|
|
681
710
|
...(!structuredWorkspacePolicyActive && workspaceAgentInstructions
|
|
682
711
|
? { instructionsTemplate: workspaceAgentInstructions }
|
|
683
712
|
: {}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
commitSessionAttemptQuiescence,
|
|
3
|
+
listPendingSessionTurns,
|
|
3
4
|
recordCodexAccountUsageWithWakeTargets,
|
|
4
5
|
releaseCodexCredentialLease,
|
|
5
6
|
releaseXaiCredentialLease,
|
|
@@ -20,7 +21,11 @@ import type {
|
|
|
20
21
|
SessionAttemptQuiescenceProof,
|
|
21
22
|
TurnActivityServices as ActivityServices,
|
|
22
23
|
} from "../types";
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
captureWhileIdle,
|
|
26
|
+
captureWorkspaceRevision,
|
|
27
|
+
openFreshWorkspaceCaptureSession,
|
|
28
|
+
} from "../workspace-capture";
|
|
24
29
|
import { type ChannelASession } from "@opengeni/runtime/sandbox";
|
|
25
30
|
import { createModelCheckpointMemoryCollector } from "../../model-checkpoint-memory-collector";
|
|
26
31
|
import {
|
|
@@ -488,49 +493,60 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
|
|
|
488
493
|
deadlineRotationRequested: sandboxState.deadlineRotationRequested,
|
|
489
494
|
});
|
|
490
495
|
if (
|
|
496
|
+
settings.workspaceCaptureEnabled &&
|
|
491
497
|
runTurnEndPersistence &&
|
|
492
498
|
attempt.turnId &&
|
|
493
499
|
sandboxState.resolvedSandbox &&
|
|
494
500
|
sandboxState.setupBoxSession &&
|
|
495
501
|
sandboxState.sandboxGroupId
|
|
496
502
|
) {
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
// exceed the 90s holder TTL, and the reaper must remain unable to drain
|
|
500
|
-
// the exact box while this holder still reads it.
|
|
503
|
+
// Review reads yield to queued work. Recovery snapshots have their own
|
|
504
|
+
// physical-settlement requirement and are drained later in finalization.
|
|
501
505
|
sandboxState.turnEndCaptureInProgress = true;
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
const captureEstablished = sandboxState.resolvedSandbox.established;
|
|
510
|
-
await captureWorkspaceRevision({
|
|
511
|
-
db,
|
|
512
|
-
objectStorage,
|
|
513
|
-
settings,
|
|
514
|
-
publish: async (events) => {
|
|
515
|
-
await publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, events);
|
|
516
|
-
},
|
|
517
|
-
session: sandboxState.setupBoxSession as ChannelASession,
|
|
518
|
-
openReadSession: async () =>
|
|
519
|
-
await openFreshWorkspaceCaptureSession({
|
|
520
|
-
backendId: captureEstablished.backendId,
|
|
521
|
-
client: captureEstablished.client,
|
|
522
|
-
session: sandboxState.setupBoxSession as ChannelASession,
|
|
523
|
-
expectedInstanceId: captureEstablished.instanceId,
|
|
524
|
-
}),
|
|
525
|
-
leaseEpoch: sandboxState.resolvedSandbox.leaseEpoch,
|
|
526
|
-
sandboxGroupId: sandboxState.sandboxGroupId,
|
|
527
|
-
accountId: input.accountId,
|
|
528
|
-
workspaceId: input.workspaceId,
|
|
529
|
-
sessionId: input.sessionId,
|
|
530
|
-
turnId: attempt.turnId,
|
|
531
|
-
attemptId: input.attemptId,
|
|
532
|
-
observability,
|
|
506
|
+
const captureSandbox = sandboxState.resolvedSandbox;
|
|
507
|
+
const captureSession = sandboxState.setupBoxSession as ChannelASession;
|
|
508
|
+
const captureGroupId = sandboxState.sandboxGroupId;
|
|
509
|
+
const captureTurnId = attempt.turnId;
|
|
510
|
+
await captureWhileIdle({
|
|
533
511
|
...(finalizerSignal ? { signal: finalizerSignal } : {}),
|
|
512
|
+
hasPendingWork: async () =>
|
|
513
|
+
(await listPendingSessionTurns(db, input.workspaceId, input.sessionId)).some(
|
|
514
|
+
(turn) => turn.id !== captureTurnId,
|
|
515
|
+
),
|
|
516
|
+
capture: async (signal) => {
|
|
517
|
+
if (sandboxState.snapshotInFlight) {
|
|
518
|
+
await waitForWarmSnapshot(
|
|
519
|
+
sandboxState.snapshotInFlight,
|
|
520
|
+
settings.sandboxSnapshotTimeoutMs,
|
|
521
|
+
signal,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
if (signal.aborted) return;
|
|
525
|
+
await captureWorkspaceRevision({
|
|
526
|
+
db,
|
|
527
|
+
objectStorage,
|
|
528
|
+
settings,
|
|
529
|
+
publish: async (events) =>
|
|
530
|
+
publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, events),
|
|
531
|
+
session: captureSession,
|
|
532
|
+
openReadSession: async () =>
|
|
533
|
+
openFreshWorkspaceCaptureSession({
|
|
534
|
+
backendId: captureSandbox.established.backendId,
|
|
535
|
+
client: captureSandbox.established.client,
|
|
536
|
+
session: captureSession,
|
|
537
|
+
expectedInstanceId: captureSandbox.established.instanceId,
|
|
538
|
+
}),
|
|
539
|
+
leaseEpoch: captureSandbox.leaseEpoch,
|
|
540
|
+
sandboxGroupId: captureGroupId,
|
|
541
|
+
accountId: input.accountId,
|
|
542
|
+
workspaceId: input.workspaceId,
|
|
543
|
+
sessionId: input.sessionId,
|
|
544
|
+
turnId: captureTurnId,
|
|
545
|
+
attemptId: input.attemptId,
|
|
546
|
+
observability,
|
|
547
|
+
signal,
|
|
548
|
+
});
|
|
549
|
+
},
|
|
534
550
|
});
|
|
535
551
|
}
|
|
536
552
|
eventing.toolPreparationClosing = true;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { AttemptToolDefinition } from "@opengeni/codemode";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
listSkillPaths,
|
|
4
4
|
PORTABLE_SKILL_MAX_FILES,
|
|
5
5
|
readSkillFiles,
|
|
6
|
-
SKILL_READ_MAX_OUTPUT_BYTES,
|
|
7
6
|
SKILL_READ_MAX_PATHS,
|
|
8
7
|
type SkillTextFile,
|
|
9
8
|
} from "@opengeni/runtime/skill-library";
|
|
@@ -74,7 +73,7 @@ export function createSkillReadAttemptToolDefinition(input: {
|
|
|
74
73
|
const files = metadata ? metadata.files : (loaded as readonly SkillTextFile[]);
|
|
75
74
|
const selected =
|
|
76
75
|
args.listFiles === true
|
|
77
|
-
? listSkillPaths(files)
|
|
76
|
+
? listSkillPaths(files, PORTABLE_SKILL_MAX_FILES)
|
|
78
77
|
: readSkillFiles(files, args.paths as string[] | undefined);
|
|
79
78
|
const output = {
|
|
80
79
|
...(metadata
|
|
@@ -97,21 +96,3 @@ export function createSkillReadAttemptToolDefinition(input: {
|
|
|
97
96
|
},
|
|
98
97
|
};
|
|
99
98
|
}
|
|
100
|
-
|
|
101
|
-
/** Inventory never reads or serializes file bodies, including the entry point. */
|
|
102
|
-
function listSkillPaths(files: readonly SkillTextFile[]): { paths: string[] } {
|
|
103
|
-
if (files.length > PORTABLE_SKILL_MAX_FILES) {
|
|
104
|
-
throw new Error(`Skill inventory exceeds ${PORTABLE_SKILL_MAX_FILES} files.`);
|
|
105
|
-
}
|
|
106
|
-
const seen = new Set<string>();
|
|
107
|
-
for (const { path } of files) {
|
|
108
|
-
assertSkillRelativePath(path);
|
|
109
|
-
if (seen.has(path)) throw new Error(`Duplicate stored Skill path: ${path}`);
|
|
110
|
-
seen.add(path);
|
|
111
|
-
}
|
|
112
|
-
const result = { paths: [...seen].sort() };
|
|
113
|
-
if (Buffer.byteLength(JSON.stringify(result), "utf8") > SKILL_READ_MAX_OUTPUT_BYTES) {
|
|
114
|
-
throw new Error("Skill inventory exceeds the read output limit.");
|
|
115
|
-
}
|
|
116
|
-
return result;
|
|
117
|
-
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getWorkspaceConnectionModelRestrictions } from "@opengeni/db";
|
|
2
2
|
import {
|
|
3
3
|
beginConnectorActionExecution,
|
|
4
|
+
getExternalLinkTurnAuthorization,
|
|
5
|
+
getSessionTurnForAttempt,
|
|
4
6
|
getWorkspaceVideoGenerationPolicy,
|
|
5
7
|
listSkillDescriptors,
|
|
6
8
|
completeConnectorActionExecution,
|
|
@@ -51,6 +53,7 @@ import {
|
|
|
51
53
|
resolveWorkspaceModelSelection,
|
|
52
54
|
withFrozenPersonalConnectionDelegations,
|
|
53
55
|
resolveSessionToolPolicy,
|
|
56
|
+
hasPermission,
|
|
54
57
|
} from "@opengeni/core";
|
|
55
58
|
import { loadWorkspaceEnvironmentForRunWithCredentials } from "../environment";
|
|
56
59
|
import { withFirstPartyTools } from "../goals";
|
|
@@ -60,7 +63,10 @@ import { ToolResultSpill } from "./tool-result-spill";
|
|
|
60
63
|
import { createTurnMediaArtifacts } from "./media-artifacts";
|
|
61
64
|
import { SandboxChannelAService } from "@opengeni/runtime/sandbox";
|
|
62
65
|
import { sandboxRunAs } from "@opengeni/runtime";
|
|
63
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
|
|
68
|
+
type ToolAuthNeededPayload,
|
|
69
|
+
} from "@opengeni/contracts";
|
|
64
70
|
|
|
65
71
|
import {
|
|
66
72
|
rollingSafeToolAuthNeededPayload,
|
|
@@ -508,6 +514,21 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
508
514
|
};
|
|
509
515
|
})()
|
|
510
516
|
: undefined;
|
|
517
|
+
const linkedAuthority = await getExternalLinkTurnAuthorization(
|
|
518
|
+
db,
|
|
519
|
+
{
|
|
520
|
+
accountId: input.accountId,
|
|
521
|
+
workspaceId: input.workspaceId,
|
|
522
|
+
},
|
|
523
|
+
turn.id,
|
|
524
|
+
);
|
|
525
|
+
if (linkedAuthority && !linkedAuthority.authorized)
|
|
526
|
+
throw new Error("Native identity link was revoked");
|
|
527
|
+
const effectiveFirstPartyPermissions = linkedAuthority
|
|
528
|
+
? (session.firstPartyMcpPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS).filter(
|
|
529
|
+
(permission) => hasPermission(linkedAuthority.permissions, permission),
|
|
530
|
+
)
|
|
531
|
+
: session.firstPartyMcpPermissions;
|
|
511
532
|
const selectedFirstPartyMcpTools = allowedFirstPartyMcpToolsForSession(
|
|
512
533
|
runSettings,
|
|
513
534
|
session.firstPartyMcpTools,
|
|
@@ -519,16 +540,21 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
519
540
|
title: session.title,
|
|
520
541
|
titleSource: session.titleSource,
|
|
521
542
|
firstPartyMcpTools: selectedFirstPartyMcpTools,
|
|
522
|
-
firstPartyMcpPermissions:
|
|
543
|
+
firstPartyMcpPermissions: effectiveFirstPartyPermissions,
|
|
523
544
|
}),
|
|
524
545
|
parallelGenerationAvailable: typeof runtime.generateSessionTitle === "function",
|
|
525
546
|
});
|
|
526
547
|
const googleDrivePublicationAllowed =
|
|
527
548
|
selectedFirstPartyMcpTools.includes("editable_artifact_export") &&
|
|
528
549
|
selectedFirstPartyMcpTools.includes("editable_artifact_export_status") &&
|
|
529
|
-
(
|
|
530
|
-
(
|
|
531
|
-
|
|
550
|
+
hasPermission(
|
|
551
|
+
[...(effectiveFirstPartyPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS)],
|
|
552
|
+
"artifacts:read",
|
|
553
|
+
) &&
|
|
554
|
+
hasPermission(
|
|
555
|
+
[...(effectiveFirstPartyPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS)],
|
|
556
|
+
"artifacts:publish",
|
|
557
|
+
);
|
|
532
558
|
const googleDriveConnectorBindings: readonly AttemptConnectorActionBinding[] =
|
|
533
559
|
googleDrivePublicationTool && googleDrivePublicationTarget && googleDrivePublicationAllowed
|
|
534
560
|
? [
|
|
@@ -760,9 +786,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
760
786
|
attemptId: input.attemptId,
|
|
761
787
|
executionGeneration: attempt.executionGeneration,
|
|
762
788
|
},
|
|
763
|
-
...(
|
|
764
|
-
? { permissions: session.firstPartyMcpPermissions }
|
|
765
|
-
: {}),
|
|
789
|
+
...(effectiveFirstPartyPermissions ? { permissions: effectiveFirstPartyPermissions } : {}),
|
|
766
790
|
selectedTools: selectedFirstPartyMcpTools,
|
|
767
791
|
subjectId: "worker:first-party-mcp",
|
|
768
792
|
subjectLabel: "OpenGeni worker",
|
|
@@ -852,6 +876,24 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
852
876
|
...(credentialSubjectId ? { credentialSubjectId } : {}),
|
|
853
877
|
...(codexAppsAuth ? { codexAppsAuth } : {}),
|
|
854
878
|
resolveCredential,
|
|
879
|
+
...(linkedAuthority
|
|
880
|
+
? {
|
|
881
|
+
authorizeAttemptExecution: async () => {
|
|
882
|
+
const current = await getSessionTurnForAttempt(
|
|
883
|
+
db,
|
|
884
|
+
input.workspaceId,
|
|
885
|
+
input.sessionId,
|
|
886
|
+
input.attemptId,
|
|
887
|
+
);
|
|
888
|
+
if (
|
|
889
|
+
!current ||
|
|
890
|
+
current.id !== turn.id ||
|
|
891
|
+
current.executionGeneration !== attempt.executionGeneration
|
|
892
|
+
)
|
|
893
|
+
throw new Error("The linked agent attempt is no longer authorized");
|
|
894
|
+
},
|
|
895
|
+
}
|
|
896
|
+
: {}),
|
|
855
897
|
onAuthNeeded: publishToolAuthNeeded,
|
|
856
898
|
materializeConnectorAttachments,
|
|
857
899
|
refreshOwnedCommand: async (commandId) => {
|
|
@@ -885,8 +927,8 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
|
|
|
885
927
|
},
|
|
886
928
|
// Manager-style sessions carry a creation-validated permission set
|
|
887
929
|
// for their first-party MCP token; null keeps the fixed default.
|
|
888
|
-
...(
|
|
889
|
-
? { firstPartyPermissions:
|
|
930
|
+
...(effectiveFirstPartyPermissions
|
|
931
|
+
? { firstPartyPermissions: effectiveFirstPartyPermissions }
|
|
890
932
|
: {}),
|
|
891
933
|
firstPartyTools: titleToolPlan.remoteFirstPartyMcpTools,
|
|
892
934
|
nestedAgentDepth: session.nestedAgentDepth,
|
|
@@ -3,6 +3,8 @@ import type { ConnectionCredentialsPort } from "@opengeni/contracts";
|
|
|
3
3
|
import {
|
|
4
4
|
buildConnectionTokenResolver,
|
|
5
5
|
buildHostConnectionTokenResolver,
|
|
6
|
+
getSessionTurnForAttempt,
|
|
7
|
+
authorizeDirectHostMcpUse,
|
|
6
8
|
resolveAcceptedConnectionUse,
|
|
7
9
|
sessionTenancyProductActivated,
|
|
8
10
|
type Database,
|
|
@@ -26,13 +28,18 @@ export function connectionTokenResolverForTurn(input: {
|
|
|
26
28
|
attemptId: string;
|
|
27
29
|
turn: SessionTurnForExecution;
|
|
28
30
|
authorizeAcceptedUse?: typeof resolveAcceptedConnectionUse;
|
|
31
|
+
/** Test seam; production always reads the canonical active-attempt projection. */
|
|
32
|
+
getHostTurnForAttempt?: typeof getSessionTurnForAttempt;
|
|
29
33
|
/** Test seam for the activation fence on pre-snapshot workspace refs. */
|
|
30
34
|
isSessionTenancyProductActivated?: typeof sessionTenancyProductActivated;
|
|
31
35
|
/** Optional; used only for content-free compatibility-lane counters. */
|
|
32
36
|
observability?: Observability | null | undefined;
|
|
33
37
|
}): (request: ResolveConnectionCredentialInput) => Promise<ResolveConnectionCredentialResult> {
|
|
34
38
|
const hostResolver = input.connectionCredentials?.mcpCredentials;
|
|
35
|
-
const
|
|
39
|
+
const readHostTurn = input.getHostTurnForAttempt ?? getSessionTurnForAttempt;
|
|
40
|
+
const hostDb = input.db;
|
|
41
|
+
const nativeResolver = buildConnectionTokenResolver(input.db, input.settings);
|
|
42
|
+
const configuredResolver = hostResolver
|
|
36
43
|
? buildHostConnectionTokenResolver(hostResolver, {
|
|
37
44
|
accountId: input.accountId,
|
|
38
45
|
workspaceId: input.workspaceId,
|
|
@@ -44,8 +51,40 @@ export function connectionTokenResolverForTurn(input: {
|
|
|
44
51
|
initiator: input.turn.initiator,
|
|
45
52
|
initiatorContext: input.turn.initiatorContext,
|
|
46
53
|
surface: "model",
|
|
54
|
+
// Only immutable captured direct-turn authority is currently supported.
|
|
55
|
+
// Direct, scheduled and exact causal/child snapshots are validated;
|
|
56
|
+
// no authority follows merely from a binding or creator identity.
|
|
57
|
+
authorizeDurableBinding: (request) => authorizeDirectHostMcpUse(hostDb, request),
|
|
58
|
+
authorizeExecution: async (request) => {
|
|
59
|
+
// Native UUID references already pass the accepted-use boundary below.
|
|
60
|
+
// Legacy opaque host IDs retain their compatibility path, but not a
|
|
61
|
+
// right to execute after their accepted attempt has stopped.
|
|
62
|
+
if (
|
|
63
|
+
request.connectionRef.authoritySource !== "host" &&
|
|
64
|
+
(!request.connectionRef.connectionId ||
|
|
65
|
+
OPENGENI_CONNECTION_ID_PATTERN.test(request.connectionRef.connectionId))
|
|
66
|
+
)
|
|
67
|
+
return true;
|
|
68
|
+
if (!request.attemptId) return false;
|
|
69
|
+
const current = await readHostTurn(
|
|
70
|
+
hostDb,
|
|
71
|
+
request.workspaceId,
|
|
72
|
+
request.sessionId,
|
|
73
|
+
request.attemptId,
|
|
74
|
+
);
|
|
75
|
+
return (
|
|
76
|
+
current !== null &&
|
|
77
|
+
current.id === request.turnId &&
|
|
78
|
+
current.executionGeneration === request.executionGeneration
|
|
79
|
+
);
|
|
80
|
+
},
|
|
47
81
|
})
|
|
48
|
-
:
|
|
82
|
+
: nativeResolver;
|
|
83
|
+
const baseResolver = (request: ResolveConnectionCredentialInput) =>
|
|
84
|
+
input.connectionCredentials?.mcpAuthoritySource === "host" &&
|
|
85
|
+
request.connectionRef.authoritySource !== "host"
|
|
86
|
+
? nativeResolver(request)
|
|
87
|
+
: configuredResolver(request);
|
|
49
88
|
return async (request) => {
|
|
50
89
|
// Host-owned refs carry explicit provenance because their opaque ids may
|
|
51
90
|
// themselves be valid UUIDs. They never enter OpenGeni's native connection
|
|
@@ -76,6 +115,7 @@ export function connectionTokenResolverForTurn(input: {
|
|
|
76
115
|
// so UUID-shaped host ids are unambiguous.
|
|
77
116
|
if (
|
|
78
117
|
hostResolver &&
|
|
118
|
+
input.connectionCredentials?.mcpAuthoritySource !== "host" &&
|
|
79
119
|
request.connectionRef.connectionId &&
|
|
80
120
|
!OPENGENI_CONNECTION_ID_PATTERN.test(request.connectionRef.connectionId)
|
|
81
121
|
) {
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
SCHEDULED_TASK_OCCURRENCE_PAYLOAD_MAX_BYTES,
|
|
9
9
|
ScheduledTaskRunAcceptedExecution,
|
|
10
10
|
SessionAgentAccess,
|
|
11
|
-
SessionEndUser,
|
|
12
11
|
SessionMemoryScope,
|
|
13
12
|
normalizeAutomaticSessionTitle,
|
|
14
13
|
scheduledOccurrencePayloadUtf8Bytes,
|
|
@@ -2578,26 +2577,29 @@ async function replayScheduledTaskDispatch(input: {
|
|
|
2578
2577
|
* facts the creating session projection exposed at task creation are set;
|
|
2579
2578
|
* a null (or no longer valid) key leaves the generated session on its own
|
|
2580
2579
|
* default. The field names are the session access-scope contract
|
|
2581
|
-
* (`agentAccess`, `
|
|
2580
|
+
* (`agentAccess`, `scopeSubjectId`, `memoryScope`) consumed by session create.
|
|
2582
2581
|
*/
|
|
2583
2582
|
function scheduledCreatorSessionPolicyInput(
|
|
2584
2583
|
policy: {
|
|
2585
2584
|
agentAccess: string | null;
|
|
2586
|
-
|
|
2585
|
+
scopeSubjectId: string | null;
|
|
2587
2586
|
memoryScope: string | null;
|
|
2588
2587
|
} | null,
|
|
2589
2588
|
): {
|
|
2590
2589
|
agentAccess?: SessionAgentAccess;
|
|
2591
|
-
|
|
2590
|
+
scopeSubjectId?: string;
|
|
2592
2591
|
memoryScope?: SessionMemoryScope;
|
|
2593
2592
|
} {
|
|
2594
2593
|
if (!policy) return {};
|
|
2595
2594
|
const agentAccess = SessionAgentAccess.safeParse(policy.agentAccess);
|
|
2596
|
-
const endUser = SessionEndUser.safeParse(policy.endUser);
|
|
2597
2595
|
const memoryScope = SessionMemoryScope.safeParse(policy.memoryScope);
|
|
2598
2596
|
return {
|
|
2599
2597
|
...(agentAccess.success ? { agentAccess: agentAccess.data } : {}),
|
|
2600
|
-
|
|
2598
|
+
// This is a trusted DB admission input, not the public create payload.
|
|
2599
|
+
// Preserve the immutable creator policy through scheduled materialization.
|
|
2600
|
+
...(policy.scopeSubjectId && /^(?:user:|external_user:).+/u.test(policy.scopeSubjectId)
|
|
2601
|
+
? { scopeSubjectId: policy.scopeSubjectId }
|
|
2602
|
+
: {}),
|
|
2601
2603
|
...(memoryScope.success ? { memoryScope: memoryScope.data } : {}),
|
|
2602
2604
|
};
|
|
2603
2605
|
}
|