@opengeni/worker-bundle 1.0.0-canary.0 → 1.0.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.
Files changed (244) hide show
  1. package/dist/activities/agent-run-admission.d.ts +3 -1
  2. package/dist/activities/agent-turn/admission.d.ts +24 -3
  3. package/dist/activities/agent-turn/agent-build.d.ts +4 -0
  4. package/dist/activities/agent-turn/claude-usage-observer.d.ts +58 -0
  5. package/dist/activities/agent-turn/code-search.d.ts +60 -0
  6. package/dist/activities/agent-turn/codex-capacity.d.ts +13 -0
  7. package/dist/activities/agent-turn/codex-plan-entitlement.d.ts +105 -0
  8. package/dist/activities/agent-turn/codex.d.ts +3 -2
  9. package/dist/activities/agent-turn/compaction-prep.d.ts +21 -5
  10. package/dist/activities/agent-turn/credential-leases.d.ts +19 -33
  11. package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
  12. package/dist/activities/agent-turn/errors.d.ts +82 -11
  13. package/dist/activities/agent-turn/failure-settlement.d.ts +6 -3
  14. package/dist/activities/agent-turn/final-reply.d.ts +18 -0
  15. package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
  16. package/dist/activities/agent-turn/finalization.d.ts +4 -2
  17. package/dist/activities/agent-turn/github-repository-bindings.d.ts +2 -0
  18. package/dist/activities/agent-turn/governance-model.d.ts +9 -2
  19. package/dist/activities/agent-turn/history-sink.d.ts +2 -0
  20. package/dist/activities/agent-turn/history.d.ts +2 -0
  21. package/dist/activities/agent-turn/input-wait-reply.d.ts +41 -0
  22. package/dist/activities/agent-turn/media-artifacts.d.ts +5 -3
  23. package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
  24. package/dist/activities/agent-turn/model-usage.d.ts +6 -0
  25. package/dist/activities/agent-turn/model-wire-dispatch.d.ts +15 -0
  26. package/dist/activities/agent-turn/optional-repositories.d.ts +59 -0
  27. package/dist/activities/agent-turn/provider-dispatch-barrier.d.ts +12 -1
  28. package/dist/activities/agent-turn/quiescence.d.ts +7 -7
  29. package/dist/activities/agent-turn/recovery-warning.d.ts +9 -0
  30. package/dist/activities/agent-turn/refresh-credentials.d.ts +6 -0
  31. package/dist/activities/agent-turn/run-credentials.d.ts +8 -3
  32. package/dist/activities/agent-turn/sandbox-establish.d.ts +2 -1
  33. package/dist/activities/agent-turn/sandbox-provision.d.ts +3 -1
  34. package/dist/activities/agent-turn/sandbox-route.d.ts +3 -20
  35. package/dist/activities/agent-turn/sandbox-runtime.d.ts +2 -2
  36. package/dist/activities/agent-turn/session-title.d.ts +34 -2
  37. package/dist/activities/agent-turn/skill-checkout.d.ts +25 -6
  38. package/dist/activities/agent-turn/skill-read.d.ts +70 -2
  39. package/dist/activities/agent-turn/skill-remove.d.ts +14 -0
  40. package/dist/activities/agent-turn/skill-search.d.ts +3 -1
  41. package/dist/activities/agent-turn/skill-tools.d.ts +15 -2
  42. package/dist/activities/agent-turn/skill-transfer.d.ts +18 -6
  43. package/dist/activities/agent-turn/stream-attempt.d.ts +7 -3
  44. package/dist/activities/agent-turn/subscription-lease.d.ts +38 -0
  45. package/dist/activities/agent-turn/tool-environment.d.ts +56 -6
  46. package/dist/activities/agent-turn/tool-policy.d.ts +43 -0
  47. package/dist/activities/agent-turn/turn-context.d.ts +29 -0
  48. package/dist/activities/agent-turn/turn-sandbox-access.d.ts +3 -0
  49. package/dist/activities/agent-turn/xai-capacity.d.ts +2 -1
  50. package/dist/activities/automations.d.ts +3 -1
  51. package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
  52. package/dist/activities/codemode-dispatcher.d.ts +9 -2
  53. package/dist/activities/codex-capacity.d.ts +18 -2
  54. package/dist/activities/codex-rotation.d.ts +7 -1
  55. package/dist/activities/context-compaction.d.ts +16 -2
  56. package/dist/activities/gmail-files.d.ts +32 -0
  57. package/dist/activities/goals.d.ts +8 -0
  58. package/dist/activities/knowledge-indexing.d.ts +19 -0
  59. package/dist/activities/mcp-account-routes.d.ts +3 -0
  60. package/dist/activities/mcp-credentials.d.ts +12 -13
  61. package/dist/activities/mcp-operation-authority.d.ts +0 -5
  62. package/dist/activities/model-call-fact-reconciler.d.ts +34 -0
  63. package/dist/activities/open-suffix-resume.d.ts +13 -0
  64. package/dist/activities/parent-wake.d.ts +6 -12
  65. package/dist/activities/retained-screenshots.d.ts +7 -2
  66. package/dist/activities/rig-verification.d.ts +2 -3
  67. package/dist/activities/run-credential-renewal.d.ts +1 -0
  68. package/dist/activities/run-credentials.d.ts +15 -3
  69. package/dist/activities/sandbox-images.d.ts +1 -1
  70. package/dist/activities/sandbox-lease.d.ts +47 -9
  71. package/dist/activities/scheduled-tasks.d.ts +4 -35
  72. package/dist/activities/session-state.d.ts +70 -3
  73. package/dist/activities/types.d.ts +55 -6
  74. package/dist/activities/workspace-capture.d.ts +2 -0
  75. package/dist/activities/workspace-credential-provider.d.ts +46 -0
  76. package/dist/{activities-control-HMFGSKC5.js → activities-control-BNPTRTOE.js} +1896 -418
  77. package/dist/activities-control-BNPTRTOE.js.map +1 -0
  78. package/dist/activities-control.d.ts +182 -4
  79. package/dist/{activities-turn-AP67JADS.js → activities-turn-5LGZOSVU.js} +4476 -1327
  80. package/dist/activities-turn-5LGZOSVU.js.map +1 -0
  81. package/dist/activities.d.ts +182 -4
  82. package/dist/artifact-materializer-entry.js +5 -2
  83. package/dist/artifact-materializer-entry.js.map +1 -1
  84. package/dist/artifact-outbox-entry.js +5 -2
  85. package/dist/artifact-outbox-entry.js.map +1 -1
  86. package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
  87. package/dist/{chunk-A4XQWVZK.js → chunk-3ZTRWDMN.js} +307 -4
  88. package/dist/chunk-3ZTRWDMN.js.map +1 -0
  89. package/dist/{chunk-RAMNYSAW.js → chunk-4ER3YXOT.js} +22 -4
  90. package/dist/chunk-4ER3YXOT.js.map +1 -0
  91. package/dist/{chunk-CYITU5UH.js → chunk-LJX7Z2KP.js} +1445 -343
  92. package/dist/chunk-LJX7Z2KP.js.map +1 -0
  93. package/dist/editable-artifact-materializer-service.d.ts +2 -2
  94. package/dist/editable-artifact-outbox-service.d.ts +2 -2
  95. package/dist/host-export-pump.d.ts +3 -1
  96. package/dist/host-export.d.ts +1 -1
  97. package/dist/host-export.js +1 -1
  98. package/dist/http.d.ts +1 -1
  99. package/dist/index.d.ts +95 -3
  100. package/dist/index.js +137 -43
  101. package/dist/index.js.map +1 -1
  102. package/dist/observability-metrics.d.ts +118 -8
  103. package/dist/retained-process-diagnostics.d.ts +4 -0
  104. package/dist/retained-process-retry.d.ts +7 -0
  105. package/dist/sandbox-resume.d.ts +55 -13
  106. package/dist/sandbox-routing.d.ts +5 -3
  107. package/dist/sandbox-snapshot-diagnostics.d.ts +20 -0
  108. package/dist/session-event-fanout.d.ts +6 -0
  109. package/dist/warm-capture-holder.d.ts +3 -0
  110. package/dist/worker-service-lifecycle.d.ts +12 -0
  111. package/dist/workflow-bundle.js +273 -41
  112. package/dist/workflows/activities.d.ts +1 -1
  113. package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
  114. package/dist/workflows/scheduled-tasks.d.ts +4 -0
  115. package/dist/workflows/session.d.ts +36 -0
  116. package/dist/workflows.d.ts +1 -0
  117. package/package.json +21 -19
  118. package/src/activities/agent-run-admission.ts +17 -1
  119. package/src/activities/agent-turn/admission.ts +66 -3
  120. package/src/activities/agent-turn/agent-build.ts +84 -10
  121. package/src/activities/agent-turn/claim.ts +186 -43
  122. package/src/activities/agent-turn/claude-usage-observer.ts +221 -0
  123. package/src/activities/agent-turn/code-search.ts +275 -0
  124. package/src/activities/agent-turn/codex-capacity.ts +172 -8
  125. package/src/activities/agent-turn/codex-plan-entitlement.ts +254 -0
  126. package/src/activities/agent-turn/codex.ts +13 -5
  127. package/src/activities/agent-turn/compaction-prep.ts +170 -102
  128. package/src/activities/agent-turn/credential-leases.ts +122 -216
  129. package/src/activities/agent-turn/credential-restriction.ts +14 -0
  130. package/src/activities/agent-turn/errors.ts +647 -48
  131. package/src/activities/agent-turn/failure-settlement.ts +682 -130
  132. package/src/activities/agent-turn/final-reply.ts +46 -0
  133. package/src/activities/agent-turn/finalization-monitor.ts +120 -0
  134. package/src/activities/agent-turn/finalization.ts +146 -43
  135. package/src/activities/agent-turn/github-repository-bindings.ts +3 -1
  136. package/src/activities/agent-turn/governance-model.ts +69 -28
  137. package/src/activities/agent-turn/history-sink.ts +16 -2
  138. package/src/activities/agent-turn/history.ts +38 -11
  139. package/src/activities/agent-turn/input-wait-reply.ts +103 -0
  140. package/src/activities/agent-turn/knowledge-source-tools.ts +2 -0
  141. package/src/activities/agent-turn/media-artifacts.ts +2 -1
  142. package/src/activities/agent-turn/model-call-admission.ts +94 -0
  143. package/src/activities/agent-turn/model-usage.ts +64 -3
  144. package/src/activities/agent-turn/model-wire-dispatch.ts +27 -0
  145. package/src/activities/agent-turn/optional-repositories.ts +224 -0
  146. package/src/activities/agent-turn/provider-dispatch-barrier.ts +22 -0
  147. package/src/activities/agent-turn/quiescence.ts +10 -7
  148. package/src/activities/agent-turn/recovery-warning.ts +16 -0
  149. package/src/activities/agent-turn/refresh-credentials.ts +44 -0
  150. package/src/activities/agent-turn/run-credentials.ts +116 -55
  151. package/src/activities/agent-turn/run.ts +320 -93
  152. package/src/activities/agent-turn/sandbox-establish.ts +84 -8
  153. package/src/activities/agent-turn/sandbox-provision.ts +3 -1
  154. package/src/activities/agent-turn/sandbox-route.ts +4 -21
  155. package/src/activities/agent-turn/sandbox-runtime.ts +343 -18
  156. package/src/activities/agent-turn/session-title.ts +81 -5
  157. package/src/activities/agent-turn/skill-checkout.ts +111 -24
  158. package/src/activities/agent-turn/skill-install.ts +1 -1
  159. package/src/activities/agent-turn/skill-read.ts +374 -28
  160. package/src/activities/agent-turn/skill-remove.ts +46 -0
  161. package/src/activities/agent-turn/skill-save.ts +1 -1
  162. package/src/activities/agent-turn/skill-search.ts +10 -1
  163. package/src/activities/agent-turn/skill-selection.ts +4 -0
  164. package/src/activities/agent-turn/skill-tools.ts +112 -18
  165. package/src/activities/agent-turn/skill-transfer.ts +68 -23
  166. package/src/activities/agent-turn/stream-attempt.ts +313 -77
  167. package/src/activities/agent-turn/subscription-lease.ts +115 -0
  168. package/src/activities/agent-turn/tool-environment.ts +316 -167
  169. package/src/activities/agent-turn/tool-policy.ts +80 -1
  170. package/src/activities/agent-turn/turn-context.ts +35 -0
  171. package/src/activities/agent-turn/turn-sandbox-access.ts +7 -1
  172. package/src/activities/agent-turn/xai-capacity.ts +94 -51
  173. package/src/activities/automations.ts +76 -4
  174. package/src/activities/browser-deadline-checkpoint.ts +279 -0
  175. package/src/activities/capabilities.ts +108 -11
  176. package/src/activities/codemode-dispatcher.ts +36 -8
  177. package/src/activities/codex-capacity.ts +60 -25
  178. package/src/activities/codex-rotation.ts +39 -17
  179. package/src/activities/context-compaction.ts +75 -18
  180. package/src/activities/gmail-files.ts +88 -0
  181. package/src/activities/goals.ts +116 -121
  182. package/src/activities/google-drive-publication.ts +1 -3
  183. package/src/activities/image-generation-operation.ts +1 -1
  184. package/src/activities/knowledge-indexing.ts +269 -15
  185. package/src/activities/knowledge-source-sync.ts +10 -0
  186. package/src/activities/mcp-account-routes.ts +17 -0
  187. package/src/activities/mcp-credentials.ts +83 -194
  188. package/src/activities/mcp-operation-authority.ts +16 -41
  189. package/src/activities/model-call-fact-reconciler.ts +112 -0
  190. package/src/activities/open-suffix-resume.ts +15 -2
  191. package/src/activities/parent-wake.ts +46 -21
  192. package/src/activities/retained-screenshots.ts +82 -11
  193. package/src/activities/rig-verification.ts +38 -37
  194. package/src/activities/run-credential-renewal.ts +13 -1
  195. package/src/activities/run-credentials.ts +59 -17
  196. package/src/activities/run-input.ts +25 -16
  197. package/src/activities/sandbox-images.ts +0 -1
  198. package/src/activities/sandbox-lease.ts +798 -82
  199. package/src/activities/scheduled-tasks.ts +550 -178
  200. package/src/activities/session-state.ts +245 -6
  201. package/src/activities/site-auth-maintenance.ts +3 -0
  202. package/src/activities/types.ts +72 -5
  203. package/src/activities/workflow-wake.ts +2 -17
  204. package/src/activities/workspace-capture.ts +13 -2
  205. package/src/activities/workspace-credential-provider.ts +283 -0
  206. package/src/activities-control.ts +4 -0
  207. package/src/activities-turn.ts +5 -0
  208. package/src/activities.ts +13 -0
  209. package/src/activity-services.ts +16 -4
  210. package/src/browser-deadline-checkpoint-contract.ts +5 -0
  211. package/src/editable-artifact-hint-broker.ts +3 -0
  212. package/src/editable-artifact-materializer-service.ts +7 -3
  213. package/src/editable-artifact-outbox-service.ts +4 -4
  214. package/src/github-rest-mcp.ts +1 -2
  215. package/src/host-export-pump.ts +26 -4
  216. package/src/host-export.ts +3 -0
  217. package/src/http.ts +4 -2
  218. package/src/index.ts +99 -37
  219. package/src/observability-metrics.ts +510 -5
  220. package/src/personal-github-git-credentials.ts +2 -1
  221. package/src/pr-review-credentials.ts +1 -3
  222. package/src/retained-process-diagnostics.ts +50 -0
  223. package/src/retained-process-retry.ts +36 -0
  224. package/src/sandbox-resume.ts +388 -101
  225. package/src/sandbox-routing.ts +150 -25
  226. package/src/sandbox-snapshot-diagnostics.ts +112 -0
  227. package/src/session-event-fanout.ts +23 -0
  228. package/src/warm-capture-holder.ts +15 -0
  229. package/src/worker-service-lifecycle.ts +53 -0
  230. package/src/workflows/activities.ts +16 -1
  231. package/src/workflows/browser-deadline-checkpoint.ts +50 -0
  232. package/src/workflows/file-upload-reaper.ts +2 -0
  233. package/src/workflows/scheduled-tasks.ts +26 -13
  234. package/src/workflows/session.ts +242 -22
  235. package/src/workflows.ts +4 -0
  236. package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
  237. package/dist/activities/packs.d.ts +0 -55
  238. package/dist/activities-control-HMFGSKC5.js.map +0 -1
  239. package/dist/activities-turn-AP67JADS.js.map +0 -1
  240. package/dist/chunk-A4XQWVZK.js.map +0 -1
  241. package/dist/chunk-CYITU5UH.js.map +0 -1
  242. package/dist/chunk-RAMNYSAW.js.map +0 -1
  243. package/src/activities/agent-turn/knowledge-sources.ts +0 -86
  244. package/src/activities/packs.ts +0 -166
@@ -4,18 +4,22 @@ import {
4
4
  WORKSPACE_OPENROUTER_MODEL_ID_PREFIX,
5
5
  } from "@opengeni/config";
6
6
  import {
7
+ applySessionAgentConfigWriteThrough,
7
8
  assertWorkspaceModelPolicyAllows,
8
9
  canonicalConfiguredModel,
10
+ resolveSessionAgentConfigForCreate,
9
11
  createAndStartSessionWithOutcome,
10
12
  recordWorkspaceUsage,
11
13
  resolveCatalogSettings,
12
14
  resolveWorkspaceCatalogSettings,
15
+ isDeveloperSetupDelegatedPermissionAllowed,
13
16
  } from "@opengeni/core";
14
17
  import {
15
18
  AutomationAuthorityRevokedError,
16
19
  assertAutomationRunAuthorityInTransaction,
17
20
  claimAutomationRun,
18
21
  settleAutomationRun,
22
+ requireWorkspace,
19
23
  } from "@opengeni/db";
20
24
  import { SessionSkills } from "@opengeni/contracts";
21
25
  import { agentRunAdmissionDenial } from "./agent-run-admission";
@@ -30,6 +34,8 @@ export type AutomationActivityOptions = {
30
34
  settle?: typeof settleAutomationRun;
31
35
  assertAuthority?: typeof assertAutomationRunAuthorityInTransaction;
32
36
  createSession?: typeof createAndStartSessionWithOutcome;
37
+ /** Workspace read for agent-configuration defaults (tests inject it). */
38
+ readWorkspace?: typeof requireWorkspace;
33
39
  admit?: typeof agentRunAdmissionDenial;
34
40
  assertModelPolicy?: typeof assertWorkspaceModelPolicyAllows;
35
41
  recordUsage?: typeof recordWorkspaceUsage;
@@ -43,6 +49,7 @@ export function createAutomationActivities(
43
49
  const settle = options.settle ?? settleAutomationRun;
44
50
  const assertAuthority = options.assertAuthority ?? assertAutomationRunAuthorityInTransaction;
45
51
  const createSession = options.createSession ?? createAndStartSessionWithOutcome;
52
+ const readWorkspace = options.readWorkspace ?? requireWorkspace;
46
53
  const admit = options.admit ?? agentRunAdmissionDenial;
47
54
  const assertModelPolicy = options.assertModelPolicy ?? assertWorkspaceModelPolicyAllows;
48
55
  const recordUsage = options.recordUsage ?? recordWorkspaceUsage;
@@ -100,6 +107,20 @@ export function createAutomationActivities(
100
107
  }
101
108
 
102
109
  const template = accepted.sessionTemplate;
110
+ if (
111
+ template.credentialRestriction === "developer_setup" &&
112
+ template.firstPartyMcpPermissions.some(
113
+ (permission) => !isDeveloperSetupDelegatedPermissionAllowed(permission),
114
+ )
115
+ ) {
116
+ await settle(service.db, {
117
+ workspaceId: input.workspaceId,
118
+ runId: run.id,
119
+ status: "failed",
120
+ errorCode: "credential_restriction_violation",
121
+ });
122
+ return { action: "failed", reason: "credential_restriction_violation" };
123
+ }
103
124
  const requestedModel = template.model ?? deploymentCatalogSettings.openaiModel;
104
125
  const catalogSettings =
105
126
  requestedModel.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX) ||
@@ -158,7 +179,49 @@ export function createAutomationActivities(
158
179
  reason: "interactive_compute_not_allowed",
159
180
  };
160
181
  }
161
- const turnExecutionPolicy = resolveTurnExecutionPolicyV1(catalogSettings, {
182
+ // Agent configuration for the generated session (null = legacy). The
183
+ // template's explicit tools are its baseline; capabilities only narrow.
184
+ let agentWriteThrough: {
185
+ config: import("@opengeni/contracts").ResolvedAgentConfig | null;
186
+ instructions: string | undefined;
187
+ firstPartyMcpTools: typeof template.firstPartyMcpTools;
188
+ tools: typeof template.tools;
189
+ };
190
+ try {
191
+ const workspaceSettings = (await readWorkspace(service.db, input.workspaceId)).settings;
192
+ const resolution = resolveSessionAgentConfigForCreate({
193
+ settings: catalogSettings,
194
+ creator: "automation",
195
+ request: template.agent,
196
+ instructions: template.instructions ?? undefined,
197
+ workspaceSettings,
198
+ parent: null,
199
+ goal: false,
200
+ });
201
+ const written = applySessionAgentConfigWriteThrough({
202
+ config: resolution.config,
203
+ firstPartyMcpTools: template.firstPartyMcpTools,
204
+ tools: template.tools,
205
+ toolPolicy: { mode: "explicit", inheritedFromSessionId: null },
206
+ productServerIds: template.tools.map((tool) => tool.id),
207
+ });
208
+ agentWriteThrough = {
209
+ config: resolution.config,
210
+ instructions: resolution.instructions,
211
+ firstPartyMcpTools: written.firstPartyMcpTools,
212
+ tools: written.tools,
213
+ };
214
+ } catch (error) {
215
+ if (!isUnprocessableEntity(error)) throw error;
216
+ await settle(service.db, {
217
+ workspaceId: input.workspaceId,
218
+ runId: run.id,
219
+ status: "failed",
220
+ errorCode: "dispatch_failed",
221
+ });
222
+ return { action: "failed", reason: "dispatch_failed" };
223
+ }
224
+ const resolvedTurnExecutionPolicy = resolveTurnExecutionPolicyV1(catalogSettings, {
162
225
  modelId: model,
163
226
  requestedModelId: template.model,
164
227
  modelSource: template.model ? "explicit" : "deployment",
@@ -167,6 +230,12 @@ export function createAutomationActivities(
167
230
  latencyMode: "standard",
168
231
  latencyModeSource: "deployment",
169
232
  });
233
+ const turnExecutionPolicy = template.credentialRestriction
234
+ ? {
235
+ ...resolvedTurnExecutionPolicy,
236
+ credentialRestriction: template.credentialRestriction,
237
+ }
238
+ : resolvedTurnExecutionPolicy;
170
239
  const created = await createSession({
171
240
  db: service.db,
172
241
  bus: service.bus,
@@ -189,10 +258,12 @@ export function createAutomationActivities(
189
258
  accountId: input.accountId,
190
259
  workspaceId: input.workspaceId,
191
260
  initialMessage: accepted.initialMessage,
261
+ surface: "automation",
262
+ metrics: service.observability,
192
263
  resources: template.resources,
193
264
  skills: SessionSkills.parse(template.skills),
194
265
  bundledSkillIds: template.bundledSkillIds,
195
- tools: template.tools,
266
+ tools: agentWriteThrough.tools,
196
267
  toolPolicy: { mode: "explicit", inheritedFromSessionId: null },
197
268
  model,
198
269
  reasoningEffort,
@@ -211,10 +282,11 @@ export function createAutomationActivities(
211
282
  label: accepted.serviceLabel,
212
283
  },
213
284
  createdByContext: accepted.provenance,
214
- instructions: template.instructions,
285
+ instructions: agentWriteThrough.instructions ?? template.instructions,
215
286
  policyRole: template.policyRole,
216
287
  firstPartyMcpPermissions: template.firstPartyMcpPermissions,
217
- firstPartyMcpTools: template.firstPartyMcpTools,
288
+ firstPartyMcpTools: agentWriteThrough.firstPartyMcpTools,
289
+ ...(agentWriteThrough.config ? { agentConfig: agentWriteThrough.config } : {}),
218
290
  retainWorkspaceCustomModel:
219
291
  model.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX) ||
220
292
  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
+ }
@@ -1,3 +1,9 @@
1
+ import {
2
+ CLAUDE_CONNECTION_KINDS,
3
+ claudeProviderId,
4
+ withClaudeConnectionCatalog,
5
+ withClaudeConnectionCredential,
6
+ } from "@opengeni/config";
1
7
  import {
2
8
  environmentsEncryptionKeyBytes,
3
9
  type Settings,
@@ -22,6 +28,7 @@ import {
22
28
  getWorkspaceOpenRouterCustomModelForExecution,
23
29
  listSessionMcpServerMetadata,
24
30
  listSessionMcpServersForRun,
31
+ getSessionAttemptMcpApprovalPolicies,
25
32
  listWorkspaceGatewayCustomModels,
26
33
  listWorkspaceOpenRouterCustomModels,
27
34
  workspaceCodexSubscriptionActive,
@@ -30,6 +37,9 @@ import {
30
37
  listOrganizationModelProviderCustomModelsForWorkspace,
31
38
  getOrganizationModelProviderCustomModelForExecution,
32
39
  loadOrganizationModelProviderApiKey,
40
+ listWorkspaceProviderCustomModels,
41
+ getWorkspaceProviderCustomModelForExecution,
42
+ loadWorkspaceProviderApiKey,
33
43
  type Database,
34
44
  type SessionMcpServerForRun,
35
45
  } from "@opengeni/db";
@@ -47,10 +57,40 @@ export async function settingsWithSessionMcpServersForRun(
47
57
  },
48
58
  ): Promise<Settings> {
49
59
  const encryptionKey = environmentsEncryptionKeyBytes(settings);
60
+ let policies: Awaited<ReturnType<typeof getSessionAttemptMcpApprovalPolicies>>;
61
+ let resolvedServers: SessionMcpServerForRun[] | undefined;
62
+ if (encryptionKey && typeof (db as Database & { rollback?: unknown }).rollback !== "function") {
63
+ // Both readers independently fence this exact active attempt. The server
64
+ // read must remain fresh for credential renewal; it does not consume the
65
+ // policy read's result. Root-pool RLS transactions may overlap, whereas
66
+ // nested scopes on a transaction handle must retain serial savepoints.
67
+ const [policyResult, serverResult] = await Promise.allSettled([
68
+ (async () =>
69
+ await getSessionAttemptMcpApprovalPolicies(db, workspaceId, sessionId, attemptId))(),
70
+ (async () =>
71
+ await listSessionMcpServersForRun(db, workspaceId, sessionId, attemptId, encryptionKey))(),
72
+ ]);
73
+ // Observe both reads before returning or propagating an error. Preserve the
74
+ // previous policy-first diagnostic priority, including synchronous ports.
75
+ if (policyResult.status === "rejected") throw policyResult.reason;
76
+ if (serverResult.status === "rejected") throw serverResult.reason;
77
+ policies = policyResult.value;
78
+ resolvedServers = serverResult.value;
79
+ } else {
80
+ policies = await getSessionAttemptMcpApprovalPolicies(db, workspaceId, sessionId, attemptId);
81
+ }
82
+ const policySettings = {
83
+ ...settings,
84
+ mcpServers: settings.mcpServers.map((server) =>
85
+ Object.hasOwn(policies, server.id)
86
+ ? { ...server, requireApproval: policies[server.id] }
87
+ : server,
88
+ ),
89
+ };
50
90
  if (!encryptionKey) {
51
91
  const metadata = await listSessionMcpServerMetadata(db, workspaceId, sessionId);
52
92
  if (metadata.length === 0) {
53
- return settings;
93
+ return policySettings;
54
94
  }
55
95
  if (metadata.some((server) => server.headerNames.length > 0)) {
56
96
  throw new Error(
@@ -58,18 +98,20 @@ export async function settingsWithSessionMcpServersForRun(
58
98
  );
59
99
  }
60
100
  }
61
- const servers = await listSessionMcpServersForRun(
62
- db,
63
- workspaceId,
64
- sessionId,
65
- attemptId,
66
- encryptionKey ?? null,
67
- );
101
+ const servers =
102
+ resolvedServers ??
103
+ (await listSessionMcpServersForRun(
104
+ db,
105
+ workspaceId,
106
+ sessionId,
107
+ attemptId,
108
+ encryptionKey ?? null,
109
+ ));
68
110
  // Keep credential provenance coupled to the exact decrypted rows that are
69
111
  // overlaid into settings. A session projection read earlier in the turn can
70
112
  // be stale after a concurrent mcpCredentialUpdates renewal.
71
113
  options?.onResolvedServers?.(servers);
72
- return settingsWithSessionMcpServers(settings, servers);
114
+ return settingsWithSessionMcpServers(policySettings, servers);
73
115
  }
74
116
 
75
117
  export function settingsWithSessionMcpServers(
@@ -233,7 +275,10 @@ export async function settingsWithOrganizationProviderCredentials(
233
275
  settings: Settings,
234
276
  retainedProductModelId?: string | null,
235
277
  ): Promise<Settings> {
236
- const buildModels = async (providerKind: "vercel_gateway" | "openrouter", prefix: string) => {
278
+ const buildModels = async (
279
+ providerKind: "vercel_gateway" | "openrouter" | "anthropic" | "claude_subscription",
280
+ prefix: string,
281
+ ) => {
237
282
  const active = await listOrganizationModelProviderCustomModelsForWorkspace(db, {
238
283
  accountId,
239
284
  workspaceId,
@@ -269,7 +314,59 @@ export async function settingsWithOrganizationProviderCredentials(
269
314
  workspaceId,
270
315
  providerKind: "openrouter",
271
316
  });
272
- return openRouterKey
317
+ let result = openRouterKey
273
318
  ? withOrganizationOpenRouterCredential(gatewaySettings, openRouterKey, openRouterModels)
274
319
  : withOrganizationOpenRouterCatalogProvider(gatewaySettings, openRouterModels);
320
+ for (const kind of CLAUDE_CONNECTION_KINDS) {
321
+ if (kind === "claude_subscription" && !settings.claudeSubscriptionEnabled) continue;
322
+ const models = await buildModels(kind, claudeProviderId(kind) + "/");
323
+ result = withClaudeConnectionCatalog(result, { [kind]: { models } });
324
+ const credential =
325
+ kind === "claude_subscription"
326
+ ? null
327
+ : await loadOrganizationModelProviderApiKey(db, settings, {
328
+ accountId,
329
+ workspaceId,
330
+ providerKind: kind,
331
+ });
332
+ if (credential)
333
+ result = withClaudeConnectionCredential(result, kind, credential, "organization", undefined);
334
+ const workspaceModels = await listWorkspaceProviderCustomModels(db, {
335
+ accountId,
336
+ workspaceId,
337
+ providerKind: kind,
338
+ });
339
+ const workspacePrefix = claudeProviderId(kind, "workspace") + "/";
340
+ const workspaceModelId = retainedProductModelId?.startsWith(workspacePrefix)
341
+ ? retainedProductModelId
342
+ : null;
343
+ if (workspaceModelId) {
344
+ const retained = await getWorkspaceProviderCustomModelForExecution(db, {
345
+ accountId,
346
+ workspaceId,
347
+ providerKind: kind,
348
+ upstreamModelId: workspaceModelId.slice(workspacePrefix.length),
349
+ });
350
+ if (retained && !workspaceModels.some((model) => model.id === retained.id))
351
+ workspaceModels.push(retained);
352
+ }
353
+ result = withClaudeConnectionCatalog(
354
+ result,
355
+ { [kind]: { models: workspaceModels } },
356
+ "workspace",
357
+ );
358
+ const workspaceCredential =
359
+ kind === "claude_subscription"
360
+ ? null
361
+ : await loadWorkspaceProviderApiKey(db, settings, workspaceId, kind, workspaceModelId);
362
+ if (workspaceCredential)
363
+ result = withClaudeConnectionCredential(
364
+ result,
365
+ kind,
366
+ workspaceCredential,
367
+ "workspace",
368
+ undefined,
369
+ );
370
+ }
371
+ return result;
275
372
  }