@opengeni/worker-bundle 2.2.0 → 2.2.1-canary.36927100590001
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/agent-run-admission.d.ts +3 -1
- package/dist/activities/agent-turn/admission.d.ts +24 -3
- package/dist/activities/agent-turn/agent-build.d.ts +1 -0
- package/dist/activities/agent-turn/claude-usage-observer.d.ts +13 -1
- package/dist/activities/agent-turn/compaction-prep.d.ts +20 -0
- package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
- package/dist/activities/agent-turn/final-reply.d.ts +18 -0
- package/dist/activities/agent-turn/governance-model.d.ts +6 -0
- package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
- package/dist/activities/agent-turn/run-credentials.d.ts +4 -0
- package/dist/activities/agent-turn/sandbox-establish.d.ts +1 -0
- package/dist/activities/agent-turn/session-title.d.ts +3 -1
- package/dist/activities/agent-turn/stream-attempt.d.ts +1 -0
- package/dist/activities/agent-turn/turn-context.d.ts +1 -0
- package/dist/activities/automations.d.ts +3 -1
- package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
- package/dist/activities/goals.d.ts +8 -0
- package/dist/activities/run-credentials.d.ts +4 -2
- package/dist/activities/scheduled-tasks.d.ts +4 -35
- package/dist/activities/types.d.ts +4 -1
- package/dist/activities/workspace-capture.d.ts +2 -0
- package/dist/{activities-control-MHNA3CYV.js → activities-control-4F2A734B.js} +502 -133
- package/dist/activities-control-4F2A734B.js.map +1 -0
- package/dist/activities-control.d.ts +30 -0
- package/dist/{activities-turn-FQ5OO2WW.js → activities-turn-TESOIOEX.js} +696 -251
- package/dist/activities-turn-TESOIOEX.js.map +1 -0
- package/dist/activities.d.ts +30 -0
- package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
- package/dist/{chunk-RZAAGRFQ.js → chunk-4PJTUBWQ.js} +220 -88
- package/dist/chunk-4PJTUBWQ.js.map +1 -0
- package/dist/{chunk-IJZU752L.js → chunk-YG27NEMX.js} +34 -1
- package/dist/chunk-YG27NEMX.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +46 -24
- package/dist/index.js.map +1 -1
- package/dist/observability-metrics.d.ts +16 -1
- package/dist/sandbox-resume.d.ts +4 -1
- package/dist/workflow-bundle.js +89 -21
- package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
- package/dist/workflows/scheduled-tasks.d.ts +4 -0
- package/dist/workflows.d.ts +1 -0
- package/package.json +21 -20
- package/src/activities/agent-run-admission.ts +17 -1
- package/src/activities/agent-turn/admission.ts +64 -1
- package/src/activities/agent-turn/agent-build.ts +24 -3
- package/src/activities/agent-turn/claim.ts +69 -15
- package/src/activities/agent-turn/claude-usage-observer.ts +33 -1
- package/src/activities/agent-turn/compaction-prep.ts +67 -16
- package/src/activities/agent-turn/credential-restriction.ts +14 -0
- package/src/activities/agent-turn/errors.ts +55 -0
- package/src/activities/agent-turn/failure-settlement.ts +26 -6
- package/src/activities/agent-turn/final-reply.ts +46 -0
- package/src/activities/agent-turn/governance-model.ts +12 -0
- package/src/activities/agent-turn/model-call-admission.ts +94 -0
- package/src/activities/agent-turn/run-credentials.ts +20 -3
- package/src/activities/agent-turn/run.ts +44 -5
- package/src/activities/agent-turn/sandbox-establish.ts +9 -5
- package/src/activities/agent-turn/session-title.ts +8 -2
- package/src/activities/agent-turn/stream-attempt.ts +144 -38
- package/src/activities/agent-turn/tool-environment.ts +55 -39
- package/src/activities/agent-turn/turn-context.ts +1 -0
- package/src/activities/automations.ts +78 -4
- package/src/activities/browser-deadline-checkpoint.ts +279 -0
- package/src/activities/capabilities.ts +52 -14
- package/src/activities/goals.ts +95 -98
- package/src/activities/knowledge-indexing.ts +29 -0
- package/src/activities/open-suffix-resume.ts +2 -1
- package/src/activities/run-credentials.ts +22 -16
- package/src/activities/sandbox-lease.ts +22 -0
- package/src/activities/scheduled-tasks.ts +172 -38
- package/src/activities/site-auth-maintenance.ts +1 -0
- package/src/activities/types.ts +4 -0
- package/src/activities/workspace-capture.ts +13 -2
- package/src/activities/workspace-credential-provider.ts +12 -0
- package/src/activities-control.ts +2 -0
- package/src/activities.ts +3 -0
- package/src/browser-deadline-checkpoint-contract.ts +5 -0
- package/src/index.ts +53 -25
- package/src/observability-metrics.ts +55 -1
- package/src/sandbox-resume.ts +23 -8
- package/src/sandbox-routing.ts +4 -2
- package/src/workflows/browser-deadline-checkpoint.ts +50 -0
- package/src/workflows/scheduled-tasks.ts +26 -13
- package/src/workflows.ts +4 -0
- package/dist/activities-control-MHNA3CYV.js.map +0 -1
- package/dist/activities-turn-FQ5OO2WW.js.map +0 -1
- package/dist/chunk-IJZU752L.js.map +0 -1
- package/dist/chunk-RZAAGRFQ.js.map +0 -1
|
@@ -4,18 +4,23 @@ import {
|
|
|
4
4
|
WORKSPACE_OPENROUTER_MODEL_ID_PREFIX,
|
|
5
5
|
} from "@opengeni/config";
|
|
6
6
|
import {
|
|
7
|
+
agentConfigMayResolve,
|
|
8
|
+
applySessionAgentConfigWriteThrough,
|
|
7
9
|
assertWorkspaceModelPolicyAllows,
|
|
8
10
|
canonicalConfiguredModel,
|
|
11
|
+
resolveSessionAgentConfigForCreate,
|
|
9
12
|
createAndStartSessionWithOutcome,
|
|
10
13
|
recordWorkspaceUsage,
|
|
11
14
|
resolveCatalogSettings,
|
|
12
15
|
resolveWorkspaceCatalogSettings,
|
|
16
|
+
isDeveloperSetupDelegatedPermissionAllowed,
|
|
13
17
|
} from "@opengeni/core";
|
|
14
18
|
import {
|
|
15
19
|
AutomationAuthorityRevokedError,
|
|
16
20
|
assertAutomationRunAuthorityInTransaction,
|
|
17
21
|
claimAutomationRun,
|
|
18
22
|
settleAutomationRun,
|
|
23
|
+
requireWorkspace,
|
|
19
24
|
} from "@opengeni/db";
|
|
20
25
|
import { SessionSkills } from "@opengeni/contracts";
|
|
21
26
|
import { agentRunAdmissionDenial } from "./agent-run-admission";
|
|
@@ -30,6 +35,8 @@ export type AutomationActivityOptions = {
|
|
|
30
35
|
settle?: typeof settleAutomationRun;
|
|
31
36
|
assertAuthority?: typeof assertAutomationRunAuthorityInTransaction;
|
|
32
37
|
createSession?: typeof createAndStartSessionWithOutcome;
|
|
38
|
+
/** Workspace read for agent-configuration defaults (tests inject it). */
|
|
39
|
+
readWorkspace?: typeof requireWorkspace;
|
|
33
40
|
admit?: typeof agentRunAdmissionDenial;
|
|
34
41
|
assertModelPolicy?: typeof assertWorkspaceModelPolicyAllows;
|
|
35
42
|
recordUsage?: typeof recordWorkspaceUsage;
|
|
@@ -43,6 +50,7 @@ export function createAutomationActivities(
|
|
|
43
50
|
const settle = options.settle ?? settleAutomationRun;
|
|
44
51
|
const assertAuthority = options.assertAuthority ?? assertAutomationRunAuthorityInTransaction;
|
|
45
52
|
const createSession = options.createSession ?? createAndStartSessionWithOutcome;
|
|
53
|
+
const readWorkspace = options.readWorkspace ?? requireWorkspace;
|
|
46
54
|
const admit = options.admit ?? agentRunAdmissionDenial;
|
|
47
55
|
const assertModelPolicy = options.assertModelPolicy ?? assertWorkspaceModelPolicyAllows;
|
|
48
56
|
const recordUsage = options.recordUsage ?? recordWorkspaceUsage;
|
|
@@ -100,6 +108,20 @@ export function createAutomationActivities(
|
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
const template = accepted.sessionTemplate;
|
|
111
|
+
if (
|
|
112
|
+
template.credentialRestriction === "developer_setup" &&
|
|
113
|
+
template.firstPartyMcpPermissions.some(
|
|
114
|
+
(permission) => !isDeveloperSetupDelegatedPermissionAllowed(permission),
|
|
115
|
+
)
|
|
116
|
+
) {
|
|
117
|
+
await settle(service.db, {
|
|
118
|
+
workspaceId: input.workspaceId,
|
|
119
|
+
runId: run.id,
|
|
120
|
+
status: "failed",
|
|
121
|
+
errorCode: "credential_restriction_violation",
|
|
122
|
+
});
|
|
123
|
+
return { action: "failed", reason: "credential_restriction_violation" };
|
|
124
|
+
}
|
|
103
125
|
const requestedModel = template.model ?? deploymentCatalogSettings.openaiModel;
|
|
104
126
|
const catalogSettings =
|
|
105
127
|
requestedModel.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX) ||
|
|
@@ -158,7 +180,51 @@ export function createAutomationActivities(
|
|
|
158
180
|
reason: "interactive_compute_not_allowed",
|
|
159
181
|
};
|
|
160
182
|
}
|
|
161
|
-
|
|
183
|
+
// Agent configuration for the generated session (null = legacy). The
|
|
184
|
+
// template's explicit tools are its baseline; capabilities only narrow.
|
|
185
|
+
let agentWriteThrough: {
|
|
186
|
+
config: import("@opengeni/contracts").ResolvedAgentConfig | null;
|
|
187
|
+
instructions: string | undefined;
|
|
188
|
+
firstPartyMcpTools: typeof template.firstPartyMcpTools;
|
|
189
|
+
tools: typeof template.tools;
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
const workspaceSettings = agentConfigMayResolve(catalogSettings, template.agent)
|
|
193
|
+
? (await readWorkspace(service.db, input.workspaceId)).settings
|
|
194
|
+
: {};
|
|
195
|
+
const resolution = resolveSessionAgentConfigForCreate({
|
|
196
|
+
settings: catalogSettings,
|
|
197
|
+
creator: "automation",
|
|
198
|
+
request: template.agent,
|
|
199
|
+
instructions: template.instructions ?? undefined,
|
|
200
|
+
workspaceSettings,
|
|
201
|
+
parent: null,
|
|
202
|
+
goal: false,
|
|
203
|
+
});
|
|
204
|
+
const written = applySessionAgentConfigWriteThrough({
|
|
205
|
+
config: resolution.config,
|
|
206
|
+
firstPartyMcpTools: template.firstPartyMcpTools,
|
|
207
|
+
tools: template.tools,
|
|
208
|
+
toolPolicy: { mode: "explicit", inheritedFromSessionId: null },
|
|
209
|
+
productServerIds: template.tools.map((tool) => tool.id),
|
|
210
|
+
});
|
|
211
|
+
agentWriteThrough = {
|
|
212
|
+
config: resolution.config,
|
|
213
|
+
instructions: resolution.instructions,
|
|
214
|
+
firstPartyMcpTools: written.firstPartyMcpTools,
|
|
215
|
+
tools: written.tools,
|
|
216
|
+
};
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (!isUnprocessableEntity(error)) throw error;
|
|
219
|
+
await settle(service.db, {
|
|
220
|
+
workspaceId: input.workspaceId,
|
|
221
|
+
runId: run.id,
|
|
222
|
+
status: "failed",
|
|
223
|
+
errorCode: "dispatch_failed",
|
|
224
|
+
});
|
|
225
|
+
return { action: "failed", reason: "dispatch_failed" };
|
|
226
|
+
}
|
|
227
|
+
const resolvedTurnExecutionPolicy = resolveTurnExecutionPolicyV1(catalogSettings, {
|
|
162
228
|
modelId: model,
|
|
163
229
|
requestedModelId: template.model,
|
|
164
230
|
modelSource: template.model ? "explicit" : "deployment",
|
|
@@ -167,6 +233,12 @@ export function createAutomationActivities(
|
|
|
167
233
|
latencyMode: "standard",
|
|
168
234
|
latencyModeSource: "deployment",
|
|
169
235
|
});
|
|
236
|
+
const turnExecutionPolicy = template.credentialRestriction
|
|
237
|
+
? {
|
|
238
|
+
...resolvedTurnExecutionPolicy,
|
|
239
|
+
credentialRestriction: template.credentialRestriction,
|
|
240
|
+
}
|
|
241
|
+
: resolvedTurnExecutionPolicy;
|
|
170
242
|
const created = await createSession({
|
|
171
243
|
db: service.db,
|
|
172
244
|
bus: service.bus,
|
|
@@ -190,10 +262,11 @@ export function createAutomationActivities(
|
|
|
190
262
|
workspaceId: input.workspaceId,
|
|
191
263
|
initialMessage: accepted.initialMessage,
|
|
192
264
|
surface: "automation",
|
|
265
|
+
metrics: service.observability,
|
|
193
266
|
resources: template.resources,
|
|
194
267
|
skills: SessionSkills.parse(template.skills),
|
|
195
268
|
bundledSkillIds: template.bundledSkillIds,
|
|
196
|
-
tools:
|
|
269
|
+
tools: agentWriteThrough.tools,
|
|
197
270
|
toolPolicy: { mode: "explicit", inheritedFromSessionId: null },
|
|
198
271
|
model,
|
|
199
272
|
reasoningEffort,
|
|
@@ -212,10 +285,11 @@ export function createAutomationActivities(
|
|
|
212
285
|
label: accepted.serviceLabel,
|
|
213
286
|
},
|
|
214
287
|
createdByContext: accepted.provenance,
|
|
215
|
-
instructions: template.instructions,
|
|
288
|
+
instructions: agentWriteThrough.instructions ?? template.instructions,
|
|
216
289
|
policyRole: template.policyRole,
|
|
217
290
|
firstPartyMcpPermissions: template.firstPartyMcpPermissions,
|
|
218
|
-
firstPartyMcpTools:
|
|
291
|
+
firstPartyMcpTools: agentWriteThrough.firstPartyMcpTools,
|
|
292
|
+
...(agentWriteThrough.config ? { agentConfig: agentWriteThrough.config } : {}),
|
|
219
293
|
retainWorkspaceCustomModel:
|
|
220
294
|
model.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX) ||
|
|
221
295
|
model.startsWith(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX),
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { Context } from "@temporalio/activity";
|
|
2
|
+
import {
|
|
3
|
+
environmentsEncryptionKeyBytes,
|
|
4
|
+
resolveFirstPartyDelegationSecret,
|
|
5
|
+
type Settings,
|
|
6
|
+
} from "@opengeni/config";
|
|
7
|
+
import { BROWSER_STATE_ARTIFACT_CONTENT_TYPE } from "@opengeni/contracts";
|
|
8
|
+
import {
|
|
9
|
+
browserDeadlineCheckpoint,
|
|
10
|
+
clearSuspendedBrowserSessionController,
|
|
11
|
+
commitBrowserSessionSuspension,
|
|
12
|
+
dispatchBrowserSessionOperation,
|
|
13
|
+
failBrowserSessionSuspension,
|
|
14
|
+
listBrowserDeadlineCheckpoints,
|
|
15
|
+
readLease,
|
|
16
|
+
releaseLeaseHolder,
|
|
17
|
+
withRlsContext,
|
|
18
|
+
type BrowserDeadlineCheckpointTarget,
|
|
19
|
+
} from "@opengeni/db";
|
|
20
|
+
import { deriveBrowserControllerAdminToken } from "@opengeni/interaction/browser-controller-authority";
|
|
21
|
+
import {
|
|
22
|
+
browserStateArtifactAad,
|
|
23
|
+
browserStateManifestDigest,
|
|
24
|
+
browserStateObjectKey,
|
|
25
|
+
deriveBrowserStateDataKey,
|
|
26
|
+
wrapBrowserStateDataKey,
|
|
27
|
+
} from "@opengeni/interaction/browser-state-authority";
|
|
28
|
+
import {
|
|
29
|
+
BrowserControlClient,
|
|
30
|
+
BrowserControlRequestError,
|
|
31
|
+
createSandboxClientForBackend,
|
|
32
|
+
deserializeSandboxSessionStateEnvelope,
|
|
33
|
+
resumeExactSandboxSession,
|
|
34
|
+
type BrowserControlPlacementSession,
|
|
35
|
+
} from "@opengeni/runtime";
|
|
36
|
+
import type { ControlActivityServices } from "./types";
|
|
37
|
+
|
|
38
|
+
const UPLOAD_CLEANUP_GRACE_MS = 24 * 60 * 60_000;
|
|
39
|
+
|
|
40
|
+
function heartbeat(): void {
|
|
41
|
+
try {
|
|
42
|
+
Context.current().heartbeat();
|
|
43
|
+
} catch {
|
|
44
|
+
// Direct embedded calls have no Temporal context. In-flight controller
|
|
45
|
+
// operations still settle under their exact durable database claim.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function withActivityHeartbeat<T>(run: () => Promise<T>): Promise<T> {
|
|
50
|
+
heartbeat();
|
|
51
|
+
const timer = setInterval(heartbeat, 5_000);
|
|
52
|
+
try {
|
|
53
|
+
return await run();
|
|
54
|
+
} finally {
|
|
55
|
+
clearInterval(timer);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type CheckpointController = Pick<BrowserControlClient, "captureState" | "endSession">;
|
|
60
|
+
type CheckpointPlacement = {
|
|
61
|
+
target: BrowserDeadlineCheckpointTarget;
|
|
62
|
+
lease: NonNullable<Awaited<ReturnType<typeof readLease>>>;
|
|
63
|
+
settings: Settings;
|
|
64
|
+
controllerRoot: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
async function resumeCheckpointController({
|
|
68
|
+
target,
|
|
69
|
+
lease,
|
|
70
|
+
settings,
|
|
71
|
+
controllerRoot,
|
|
72
|
+
}: CheckpointPlacement): Promise<CheckpointController> {
|
|
73
|
+
// This edge cannot create a box or route through a user's current machine.
|
|
74
|
+
const provider = createSandboxClientForBackend("modal", settings) as Parameters<
|
|
75
|
+
typeof deserializeSandboxSessionStateEnvelope
|
|
76
|
+
>[0];
|
|
77
|
+
const envelope = lease.resumeState as { sessionState?: unknown };
|
|
78
|
+
const state = await deserializeSandboxSessionStateEnvelope(
|
|
79
|
+
provider,
|
|
80
|
+
envelope.sessionState ?? lease.resumeState,
|
|
81
|
+
target.instanceId,
|
|
82
|
+
);
|
|
83
|
+
const resumed = await resumeExactSandboxSession(provider, "modal", state, target.instanceId);
|
|
84
|
+
return new BrowserControlClient(resumed.session as BrowserControlPlacementSession, {
|
|
85
|
+
adminToken: deriveBrowserControllerAdminToken({
|
|
86
|
+
rootSecret: controllerRoot,
|
|
87
|
+
accountId: target.accountId,
|
|
88
|
+
workspaceId: target.workspaceId,
|
|
89
|
+
placement: { kind: "sandbox_group", sandboxGroupId: target.sandboxGroupId },
|
|
90
|
+
placementInstanceId: target.instanceId,
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function createBrowserDeadlineCheckpointActivities(
|
|
96
|
+
services: () => Promise<ControlActivityServices>,
|
|
97
|
+
connectController: (
|
|
98
|
+
placement: CheckpointPlacement,
|
|
99
|
+
) => Promise<CheckpointController> = resumeCheckpointController,
|
|
100
|
+
) {
|
|
101
|
+
return {
|
|
102
|
+
async listDueBrowserCheckpoints() {
|
|
103
|
+
return await withActivityHeartbeat(async () => {
|
|
104
|
+
const { db, settings, objectStorage } = await services();
|
|
105
|
+
if (
|
|
106
|
+
!settings.sandboxOwnershipEnabled ||
|
|
107
|
+
!objectStorage ||
|
|
108
|
+
!resolveFirstPartyDelegationSecret(settings)
|
|
109
|
+
)
|
|
110
|
+
return [];
|
|
111
|
+
const root = environmentsEncryptionKeyBytes(settings);
|
|
112
|
+
if (!root) return [];
|
|
113
|
+
root.fill(0);
|
|
114
|
+
return await listBrowserDeadlineCheckpoints(db);
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
async checkpointBrowserBeforeDeadline(target: BrowserDeadlineCheckpointTarget) {
|
|
118
|
+
return await withActivityHeartbeat(async () => {
|
|
119
|
+
const { db, settings, objectStorage } = await services();
|
|
120
|
+
const rootKey = environmentsEncryptionKeyBytes(settings);
|
|
121
|
+
const controllerRoot = resolveFirstPartyDelegationSecret(settings);
|
|
122
|
+
if (!rootKey || !objectStorage || !controllerRoot) {
|
|
123
|
+
rootKey?.fill(0);
|
|
124
|
+
throw new Error("browser deadline checkpoint storage or encryption is unavailable");
|
|
125
|
+
}
|
|
126
|
+
let dataKey: Buffer | null = null;
|
|
127
|
+
let aad: Buffer | null = null;
|
|
128
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
129
|
+
try {
|
|
130
|
+
const claim = await browserDeadlineCheckpoint(db, target, { prepare: true, touch: true });
|
|
131
|
+
if (!claim) return { status: "skipped" as const };
|
|
132
|
+
let pulseRunning = false;
|
|
133
|
+
let authorityLost = false;
|
|
134
|
+
const pulse = async () => {
|
|
135
|
+
if (pulseRunning) return;
|
|
136
|
+
pulseRunning = true;
|
|
137
|
+
try {
|
|
138
|
+
if (!(await browserDeadlineCheckpoint(db, target, { touch: true })))
|
|
139
|
+
authorityLost = true;
|
|
140
|
+
} finally {
|
|
141
|
+
pulseRunning = false;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
timer = setInterval(() => {
|
|
145
|
+
void pulse().catch(() => undefined);
|
|
146
|
+
}, 5_000);
|
|
147
|
+
const lease = await readLease(db, target.workspaceId, target.sandboxGroupId);
|
|
148
|
+
if (
|
|
149
|
+
!lease ||
|
|
150
|
+
lease.instanceId !== target.instanceId ||
|
|
151
|
+
lease.leaseEpoch !== target.leaseEpoch ||
|
|
152
|
+
!lease.resumeState
|
|
153
|
+
) {
|
|
154
|
+
throw new Error("browser deadline checkpoint placement changed");
|
|
155
|
+
}
|
|
156
|
+
const placement = {
|
|
157
|
+
kind: "sandbox_group" as const,
|
|
158
|
+
sandboxGroupId: target.sandboxGroupId,
|
|
159
|
+
};
|
|
160
|
+
const client = await connectController({ target, lease, settings, controllerRoot });
|
|
161
|
+
if (authorityLost || !(await browserDeadlineCheckpoint(db, target, { touch: true }))) {
|
|
162
|
+
throw new Error("browser deadline checkpoint authority changed");
|
|
163
|
+
}
|
|
164
|
+
if (claim.state !== "completed") {
|
|
165
|
+
const scope = { ...target, operationId: claim.operationId };
|
|
166
|
+
const objectKey = browserStateObjectKey(target.workspaceId, claim.operationId);
|
|
167
|
+
dataKey = deriveBrowserStateDataKey(rootKey, { ...scope, objectKey });
|
|
168
|
+
aad = browserStateArtifactAad({ ...scope, objectKey });
|
|
169
|
+
await dispatchBrowserSessionOperation(db, {
|
|
170
|
+
...scope,
|
|
171
|
+
deadlineTarget: target,
|
|
172
|
+
stateUpload: {
|
|
173
|
+
objectKey,
|
|
174
|
+
cleanupAfter: new Date(Date.now() + UPLOAD_CLEANUP_GRACE_MS),
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
const signed = await objectStorage.createPutUrl({
|
|
178
|
+
key: objectKey,
|
|
179
|
+
contentType: BROWSER_STATE_ARTIFACT_CONTENT_TYPE,
|
|
180
|
+
audience: "sandbox",
|
|
181
|
+
});
|
|
182
|
+
const receipt = await client
|
|
183
|
+
.captureState({
|
|
184
|
+
...scope,
|
|
185
|
+
objectKey,
|
|
186
|
+
afterCapture: "stop",
|
|
187
|
+
dataKey,
|
|
188
|
+
aad,
|
|
189
|
+
upload: {
|
|
190
|
+
url: signed.url,
|
|
191
|
+
requiredHeaders: signed.requiredHeaders,
|
|
192
|
+
expiresAt: signed.expiresAt.toISOString(),
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
.catch(async (error: unknown) => {
|
|
196
|
+
if (
|
|
197
|
+
error instanceof BrowserControlRequestError &&
|
|
198
|
+
(error.error.code === "outcome_unknown" || !error.error.retryable)
|
|
199
|
+
) {
|
|
200
|
+
await failBrowserSessionSuspension(db, { ...scope, error: error.error });
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
});
|
|
204
|
+
if (authorityLost) throw new Error("browser deadline checkpoint authority changed");
|
|
205
|
+
const portable = receipt.manifest.profileCrypto !== "platform_bound";
|
|
206
|
+
await commitBrowserSessionSuspension(db, {
|
|
207
|
+
...scope,
|
|
208
|
+
deadlineTarget: target,
|
|
209
|
+
artifact: {
|
|
210
|
+
kind: "chromium_profile",
|
|
211
|
+
format: receipt.format,
|
|
212
|
+
objectKey,
|
|
213
|
+
artifactDigest: receipt.artifactDigest,
|
|
214
|
+
contentDigest: receipt.contentDigest,
|
|
215
|
+
manifestDigest: browserStateManifestDigest(receipt.manifest),
|
|
216
|
+
sizeBytes: receipt.sizeBytes,
|
|
217
|
+
encryptedDataKey: wrapBrowserStateDataKey(rootKey, dataKey, {
|
|
218
|
+
...scope,
|
|
219
|
+
objectKey,
|
|
220
|
+
artifactDigest: receipt.artifactDigest,
|
|
221
|
+
contentDigest: receipt.contentDigest,
|
|
222
|
+
}),
|
|
223
|
+
materialization: {
|
|
224
|
+
portability: portable ? "portable" : "placement_bound",
|
|
225
|
+
reason: portable
|
|
226
|
+
? null
|
|
227
|
+
: "Profile encryption depends on the source operating-system credential store.",
|
|
228
|
+
platform: receipt.manifest.platform,
|
|
229
|
+
architecture: receipt.manifest.architecture,
|
|
230
|
+
engine: receipt.manifest.engine,
|
|
231
|
+
engineVersion: receipt.manifest.engineVersion,
|
|
232
|
+
driverId: receipt.manifest.driverId,
|
|
233
|
+
driverSchemaVersion: receipt.manifest.driverSchemaVersion,
|
|
234
|
+
profileCrypto: receipt.manifest.profileCrypto,
|
|
235
|
+
providerId: null,
|
|
236
|
+
placement: portable ? null : placement,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
// Only remove the local profile after the encrypted artifact and its
|
|
242
|
+
// authority committed. A crash resumes this cleanup, never capture.
|
|
243
|
+
await client.endSession(target, { removeState: true }).catch((error: unknown) => {
|
|
244
|
+
if (!(error instanceof BrowserControlRequestError && error.status === 404)) throw error;
|
|
245
|
+
});
|
|
246
|
+
await withRlsContext(
|
|
247
|
+
db,
|
|
248
|
+
target,
|
|
249
|
+
async (tx) => {
|
|
250
|
+
if ((await browserDeadlineCheckpoint(tx, target))?.state !== "completed") {
|
|
251
|
+
throw new Error("browser deadline checkpoint cleanup authority changed");
|
|
252
|
+
}
|
|
253
|
+
await clearSuspendedBrowserSessionController(tx, {
|
|
254
|
+
...target,
|
|
255
|
+
expectedControllerGeneration: target.controllerGeneration,
|
|
256
|
+
});
|
|
257
|
+
// One transaction prevents a human resume from acquiring a new
|
|
258
|
+
// same-browser holder between clearing and releasing the old one.
|
|
259
|
+
await releaseLeaseHolder(tx, {
|
|
260
|
+
...target,
|
|
261
|
+
kind: "interaction",
|
|
262
|
+
holderId: `browser-session:${target.browserSessionId}`,
|
|
263
|
+
idleGraceMs: settings.sandboxIdleGraceMs,
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
undefined,
|
|
267
|
+
"none",
|
|
268
|
+
);
|
|
269
|
+
return { status: "suspended" as const };
|
|
270
|
+
} finally {
|
|
271
|
+
if (timer) clearInterval(timer);
|
|
272
|
+
dataKey?.fill(0);
|
|
273
|
+
aad?.fill(0);
|
|
274
|
+
rootKey.fill(0);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
@@ -40,6 +40,8 @@ import {
|
|
|
40
40
|
listWorkspaceProviderCustomModels,
|
|
41
41
|
getWorkspaceProviderCustomModelForExecution,
|
|
42
42
|
loadWorkspaceProviderApiKey,
|
|
43
|
+
loadClaudeSubscriptionUsageCredential,
|
|
44
|
+
assertModelConnectionAllowsTurn,
|
|
43
45
|
type Database,
|
|
44
46
|
type SessionMcpServerForRun,
|
|
45
47
|
} from "@opengeni/db";
|
|
@@ -303,12 +305,30 @@ export async function settingsWithOrganizationProviderCredentials(
|
|
|
303
305
|
if (kind === "claude_subscription" && !settings.claudeSubscriptionEnabled) continue;
|
|
304
306
|
const models = await buildModels(kind, claudeProviderId(kind) + "/");
|
|
305
307
|
result = withClaudeConnectionCatalog(result, { [kind]: { models } });
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
308
|
+
const binding =
|
|
309
|
+
kind === "claude_subscription"
|
|
310
|
+
? await loadClaudeSubscriptionUsageCredential(db, settings, {
|
|
311
|
+
accountId,
|
|
312
|
+
workspaceId,
|
|
313
|
+
scope: "organization",
|
|
314
|
+
})
|
|
315
|
+
: null;
|
|
316
|
+
const credential =
|
|
317
|
+
kind === "claude_subscription"
|
|
318
|
+
? binding?.serializedCredential
|
|
319
|
+
: await loadOrganizationModelProviderApiKey(db, settings, {
|
|
320
|
+
accountId,
|
|
321
|
+
workspaceId,
|
|
322
|
+
providerKind: kind,
|
|
323
|
+
});
|
|
324
|
+
if (credential)
|
|
325
|
+
result = withClaudeConnectionCredential(
|
|
326
|
+
result,
|
|
327
|
+
kind,
|
|
328
|
+
credential,
|
|
329
|
+
"organization",
|
|
330
|
+
binding ?? undefined,
|
|
331
|
+
);
|
|
312
332
|
const workspaceModels = await listWorkspaceProviderCustomModels(db, {
|
|
313
333
|
accountId,
|
|
314
334
|
workspaceId,
|
|
@@ -333,15 +353,33 @@ export async function settingsWithOrganizationProviderCredentials(
|
|
|
333
353
|
{ [kind]: { models: workspaceModels } },
|
|
334
354
|
"workspace",
|
|
335
355
|
);
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
356
|
+
const workspaceBinding =
|
|
357
|
+
kind === "claude_subscription"
|
|
358
|
+
? await loadClaudeSubscriptionUsageCredential(db, settings, {
|
|
359
|
+
accountId,
|
|
360
|
+
workspaceId,
|
|
361
|
+
scope: "workspace",
|
|
362
|
+
})
|
|
363
|
+
: null;
|
|
364
|
+
if (workspaceBinding && workspaceModelId)
|
|
365
|
+
await assertModelConnectionAllowsTurn(db, {
|
|
366
|
+
workspaceId,
|
|
367
|
+
subjectId: "worker:model-access",
|
|
368
|
+
modelId: workspaceModelId,
|
|
369
|
+
workspaceProviderConnectionId: workspaceBinding.connectionId,
|
|
370
|
+
});
|
|
371
|
+
const workspaceCredential =
|
|
372
|
+
kind === "claude_subscription"
|
|
373
|
+
? workspaceBinding?.serializedCredential
|
|
374
|
+
: await loadWorkspaceProviderApiKey(db, settings, workspaceId, kind, workspaceModelId);
|
|
343
375
|
if (workspaceCredential)
|
|
344
|
-
result = withClaudeConnectionCredential(
|
|
376
|
+
result = withClaudeConnectionCredential(
|
|
377
|
+
result,
|
|
378
|
+
kind,
|
|
379
|
+
workspaceCredential,
|
|
380
|
+
"workspace",
|
|
381
|
+
workspaceBinding ?? undefined,
|
|
382
|
+
);
|
|
345
383
|
}
|
|
346
384
|
return result;
|
|
347
385
|
}
|