@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.
Files changed (88) 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 +1 -0
  4. package/dist/activities/agent-turn/claude-usage-observer.d.ts +13 -1
  5. package/dist/activities/agent-turn/compaction-prep.d.ts +20 -0
  6. package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
  7. package/dist/activities/agent-turn/final-reply.d.ts +18 -0
  8. package/dist/activities/agent-turn/governance-model.d.ts +6 -0
  9. package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
  10. package/dist/activities/agent-turn/run-credentials.d.ts +4 -0
  11. package/dist/activities/agent-turn/sandbox-establish.d.ts +1 -0
  12. package/dist/activities/agent-turn/session-title.d.ts +3 -1
  13. package/dist/activities/agent-turn/stream-attempt.d.ts +1 -0
  14. package/dist/activities/agent-turn/turn-context.d.ts +1 -0
  15. package/dist/activities/automations.d.ts +3 -1
  16. package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
  17. package/dist/activities/goals.d.ts +8 -0
  18. package/dist/activities/run-credentials.d.ts +4 -2
  19. package/dist/activities/scheduled-tasks.d.ts +4 -35
  20. package/dist/activities/types.d.ts +4 -1
  21. package/dist/activities/workspace-capture.d.ts +2 -0
  22. package/dist/{activities-control-MHNA3CYV.js → activities-control-4F2A734B.js} +502 -133
  23. package/dist/activities-control-4F2A734B.js.map +1 -0
  24. package/dist/activities-control.d.ts +30 -0
  25. package/dist/{activities-turn-FQ5OO2WW.js → activities-turn-TESOIOEX.js} +696 -251
  26. package/dist/activities-turn-TESOIOEX.js.map +1 -0
  27. package/dist/activities.d.ts +30 -0
  28. package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
  29. package/dist/{chunk-RZAAGRFQ.js → chunk-4PJTUBWQ.js} +220 -88
  30. package/dist/chunk-4PJTUBWQ.js.map +1 -0
  31. package/dist/{chunk-IJZU752L.js → chunk-YG27NEMX.js} +34 -1
  32. package/dist/chunk-YG27NEMX.js.map +1 -0
  33. package/dist/index.d.ts +15 -0
  34. package/dist/index.js +46 -24
  35. package/dist/index.js.map +1 -1
  36. package/dist/observability-metrics.d.ts +16 -1
  37. package/dist/sandbox-resume.d.ts +4 -1
  38. package/dist/workflow-bundle.js +89 -21
  39. package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
  40. package/dist/workflows/scheduled-tasks.d.ts +4 -0
  41. package/dist/workflows.d.ts +1 -0
  42. package/package.json +21 -20
  43. package/src/activities/agent-run-admission.ts +17 -1
  44. package/src/activities/agent-turn/admission.ts +64 -1
  45. package/src/activities/agent-turn/agent-build.ts +24 -3
  46. package/src/activities/agent-turn/claim.ts +69 -15
  47. package/src/activities/agent-turn/claude-usage-observer.ts +33 -1
  48. package/src/activities/agent-turn/compaction-prep.ts +67 -16
  49. package/src/activities/agent-turn/credential-restriction.ts +14 -0
  50. package/src/activities/agent-turn/errors.ts +55 -0
  51. package/src/activities/agent-turn/failure-settlement.ts +26 -6
  52. package/src/activities/agent-turn/final-reply.ts +46 -0
  53. package/src/activities/agent-turn/governance-model.ts +12 -0
  54. package/src/activities/agent-turn/model-call-admission.ts +94 -0
  55. package/src/activities/agent-turn/run-credentials.ts +20 -3
  56. package/src/activities/agent-turn/run.ts +44 -5
  57. package/src/activities/agent-turn/sandbox-establish.ts +9 -5
  58. package/src/activities/agent-turn/session-title.ts +8 -2
  59. package/src/activities/agent-turn/stream-attempt.ts +144 -38
  60. package/src/activities/agent-turn/tool-environment.ts +55 -39
  61. package/src/activities/agent-turn/turn-context.ts +1 -0
  62. package/src/activities/automations.ts +78 -4
  63. package/src/activities/browser-deadline-checkpoint.ts +279 -0
  64. package/src/activities/capabilities.ts +52 -14
  65. package/src/activities/goals.ts +95 -98
  66. package/src/activities/knowledge-indexing.ts +29 -0
  67. package/src/activities/open-suffix-resume.ts +2 -1
  68. package/src/activities/run-credentials.ts +22 -16
  69. package/src/activities/sandbox-lease.ts +22 -0
  70. package/src/activities/scheduled-tasks.ts +172 -38
  71. package/src/activities/site-auth-maintenance.ts +1 -0
  72. package/src/activities/types.ts +4 -0
  73. package/src/activities/workspace-capture.ts +13 -2
  74. package/src/activities/workspace-credential-provider.ts +12 -0
  75. package/src/activities-control.ts +2 -0
  76. package/src/activities.ts +3 -0
  77. package/src/browser-deadline-checkpoint-contract.ts +5 -0
  78. package/src/index.ts +53 -25
  79. package/src/observability-metrics.ts +55 -1
  80. package/src/sandbox-resume.ts +23 -8
  81. package/src/sandbox-routing.ts +4 -2
  82. package/src/workflows/browser-deadline-checkpoint.ts +50 -0
  83. package/src/workflows/scheduled-tasks.ts +26 -13
  84. package/src/workflows.ts +4 -0
  85. package/dist/activities-control-MHNA3CYV.js.map +0 -1
  86. package/dist/activities-turn-FQ5OO2WW.js.map +0 -1
  87. package/dist/chunk-IJZU752L.js.map +0 -1
  88. package/dist/chunk-RZAAGRFQ.js.map +0 -1
@@ -10,11 +10,14 @@ import {
10
10
  SessionAgentAccess,
11
11
  SessionMemoryScope,
12
12
  normalizeAutomaticSessionTitle,
13
+ metadataWithTurnExecutionPolicyV1,
13
14
  scheduledOccurrencePayloadUtf8Bytes,
14
15
  stableJson,
15
16
  TurnExecutionPolicyV1,
16
17
  type ScheduledTask,
17
18
  type ScheduledTaskRun,
19
+ AgentConfigError,
20
+ type ResolvedAgentConfig,
18
21
  } from "@opengeni/contracts";
19
22
  import {
20
23
  openGeniSlackBotMetadata,
@@ -33,6 +36,12 @@ import {
33
36
  settingsWithEnabledCapabilityMcpServers,
34
37
  settingsWithSessionMcpServerMetadata,
35
38
  swapActiveSandbox,
39
+ agentConfigMayResolve,
40
+ applySessionAgentConfigWriteThrough,
41
+ resolveSessionAgentConfigForCreate,
42
+ recordSessionCreated,
43
+ recordUserMessageAccepted,
44
+ type ProductUsageMetricsSink,
36
45
  } from "@opengeni/core";
37
46
  import {
38
47
  appendSessionEvents,
@@ -62,6 +71,7 @@ import {
62
71
  getNestedAgentDepthDeploymentPolicy,
63
72
  getSessionByCreateIdempotencyKey,
64
73
  getSessionCreationExecutionPolicy,
74
+ metadataWithAgentConfigCreateIdentity,
65
75
  getVariableSet,
66
76
  isCodexBilledModel,
67
77
  initializeSessionStartAtomically,
@@ -120,6 +130,30 @@ type ScheduledTemporalActivityIdentity = {
120
130
  activityId: string;
121
131
  };
122
132
 
133
+ export function scheduledTaskRunExecutionPolicy(
134
+ policy: TurnExecutionPolicyV1,
135
+ input: DispatchScheduledTaskRunInput,
136
+ creatorRestriction?: "developer_setup",
137
+ ): TurnExecutionPolicyV1 {
138
+ return creatorRestriction === "developer_setup" ||
139
+ (input.triggerType !== "scheduled" && input.credentialRestriction === "developer_setup")
140
+ ? { ...policy, credentialRestriction: "developer_setup" }
141
+ : policy;
142
+ }
143
+
144
+ /** Only a creator ceiling is standing; a manual caller ceiling is per-run. */
145
+ export function scheduledSessionExecutionPolicyMetadata(
146
+ policy: unknown,
147
+ creatorRestriction?: "developer_setup",
148
+ ): Record<string, unknown> {
149
+ if (policy === undefined || policy === null) return {};
150
+ const acceptedPolicy = TurnExecutionPolicyV1.parse(policy);
151
+ return creatorRestriction === "developer_setup" &&
152
+ acceptedPolicy.credentialRestriction === "developer_setup"
153
+ ? metadataWithTurnExecutionPolicyV1({}, acceptedPolicy)
154
+ : {};
155
+ }
156
+
123
157
  export function scheduledTaskRunProducerKey(
124
158
  input: DispatchScheduledTaskRunInput,
125
159
  suppliedActivityIdentity?: ScheduledTemporalActivityIdentity,
@@ -189,6 +223,15 @@ export function scheduledTaskGeneratedSessionCreateIdempotencyKey(producerKey: s
189
223
  * alone, with no Intl call and so no host ICU or tzdata build to make the two
190
224
  * texts differ.
191
225
  */
226
+ /** A scheduler-generated run session: a service-created root session. */
227
+ function recordScheduledSessionCreated(observability: ProductUsageMetricsSink | undefined): void {
228
+ recordSessionCreated(observability, {
229
+ surface: "scheduled",
230
+ createdByKind: "service",
231
+ parentSessionId: null,
232
+ });
233
+ }
234
+
192
235
  export function scheduledTaskSessionTitle(taskName: string): string {
193
236
  return normalizeAutomaticSessionTitle(taskName) ?? "Scheduled run";
194
237
  }
@@ -328,6 +371,7 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
328
371
  try {
329
372
  return await recoverBoundScheduledTaskDispatch({
330
373
  db,
374
+ observability: baseService.observability,
331
375
  bus,
332
376
  settings,
333
377
  wakeSessionWorkflow,
@@ -501,7 +545,7 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
501
545
  settings.openaiReasoningEffort;
502
546
  let sandboxBackend = task.agentConfig.sandboxBackend ?? settings.sandboxBackend;
503
547
  let sandboxOs: "linux" | "macos" | "windows" = "linux";
504
- const taskTools = withFirstPartyTools(settings, task.agentConfig.tools);
548
+ let taskTools = withFirstPartyTools(settings, task.agentConfig.tools);
505
549
  // A task created by a live agent attempt froze its creator's effective
506
550
  // first-party selection, permission set, and session access policy;
507
551
  // its generated sessions inherit that boundary under today's
@@ -510,13 +554,55 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
510
554
  const creatorPolicy = await getScheduledTaskCreatorPolicy(db, task.workspaceId, task.id);
511
555
  // A person-chosen Slack channel adds only the two bot posting tools;
512
556
  // they post nowhere else, whatever the creator's own selection was.
513
- const firstPartyMcpTools = withScheduledSlackBotPostingTools(
557
+ let firstPartyMcpTools = withScheduledSlackBotPostingTools(
514
558
  creatorPolicy?.firstPartyMcpTools
515
559
  ? allowedFirstPartyMcpToolsForSession(settings, creatorPolicy.firstPartyMcpTools)
516
560
  : resolveFirstPartyMcpToolPolicy(settings).default,
517
561
  task.agentConfig,
518
562
  resolveFirstPartyMcpToolPolicy(settings).allowed,
519
563
  );
564
+ // Agent configuration for generated sessions (null = legacy). The
565
+ // scheduled baseline above (creator policy or deployment default) is
566
+ // what "all" reproduces; capabilities only narrow it. Frozen into the
567
+ // accepted execution so retries and recovery never resolve again.
568
+ let resolvedAgentConfig: ResolvedAgentConfig | null = null;
569
+ let resolvedAgentInstructions: string | undefined;
570
+ try {
571
+ const agentResolution = resolveSessionAgentConfigForCreate({
572
+ settings,
573
+ creator: "scheduled",
574
+ request: task.agentConfig.agent,
575
+ instructions: undefined,
576
+ workspaceSettings: agentConfigMayResolve(settings, task.agentConfig.agent)
577
+ ? (await requireWorkspace(db, task.workspaceId)).settings
578
+ : {},
579
+ parent: null,
580
+ goal: task.agentConfig.goal !== undefined,
581
+ });
582
+ resolvedAgentConfig = agentResolution.config;
583
+ resolvedAgentInstructions = agentResolution.instructions;
584
+ const written = applySessionAgentConfigWriteThrough({
585
+ config: resolvedAgentConfig,
586
+ firstPartyMcpTools,
587
+ tools: taskTools,
588
+ toolPolicy: { mode: "explicit", inheritedFromSessionId: null },
589
+ productServerIds: task.agentConfig.tools.map((tool) => tool.id),
590
+ });
591
+ firstPartyMcpTools = written.firstPartyMcpTools;
592
+ taskTools = written.tools;
593
+ } catch (error) {
594
+ const status =
595
+ error instanceof Error ? (error as Error & { status?: unknown }).status : undefined;
596
+ if (!(error instanceof Error) || status !== 422) throw error;
597
+ const notEnabled =
598
+ error.cause instanceof AgentConfigError &&
599
+ error.cause.code === "agent_config_not_enabled";
600
+ return await refuseAdmission(
601
+ "scheduled_authority_unavailable",
602
+ notEnabled,
603
+ `scheduled agent configuration is not admissible: ${error.message}`,
604
+ );
605
+ }
520
606
  const firstPartyMcpPermissions = creatorPolicy?.firstPartyMcpPermissions
521
607
  ? [...creatorPolicy.firstPartyMcpPermissions]
522
608
  : [...DEFAULT_FIRST_PARTY_MCP_PERMISSIONS];
@@ -917,6 +1003,7 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
917
1003
  workspaceId: task.workspaceId,
918
1004
  model: targetSessionExecution?.model ?? model,
919
1005
  requestedAgentRuns: input.agentRunUsageIdempotencyKey ? 0 : 1,
1006
+ initiatingHumanSubjectId: causalHumanSubjectId,
920
1007
  },
921
1008
  );
922
1009
  if (admissionDenial) {
@@ -945,23 +1032,28 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
945
1032
  }
946
1033
  }
947
1034
  : undefined;
948
- const turnExecutionPolicy: TurnExecutionPolicyV1 = resolveTurnExecutionPolicyV1(settings, {
949
- modelId: acceptedModel,
950
- requestedModelId: generatedTarget && task.agentConfig.model ? task.agentConfig.model : null,
951
- modelSource: generatedTarget
952
- ? task.agentConfig.model
953
- ? "explicit"
954
- : "deployment"
955
- : "session",
956
- reasoningEffort: acceptedReasoningEffort,
957
- reasoningSource: generatedTarget
958
- ? task.agentConfig.reasoningEffort
959
- ? "explicit"
960
- : "deployment"
961
- : "session",
962
- latencyMode: acceptedLatencyMode,
963
- latencyModeSource: generatedTarget ? "deployment" : "session",
964
- });
1035
+ const turnExecutionPolicy = scheduledTaskRunExecutionPolicy(
1036
+ resolveTurnExecutionPolicyV1(settings, {
1037
+ modelId: acceptedModel,
1038
+ requestedModelId:
1039
+ generatedTarget && task.agentConfig.model ? task.agentConfig.model : null,
1040
+ modelSource: generatedTarget
1041
+ ? task.agentConfig.model
1042
+ ? "explicit"
1043
+ : "deployment"
1044
+ : "session",
1045
+ reasoningEffort: acceptedReasoningEffort,
1046
+ reasoningSource: generatedTarget
1047
+ ? task.agentConfig.reasoningEffort
1048
+ ? "explicit"
1049
+ : "deployment"
1050
+ : "session",
1051
+ latencyMode: acceptedLatencyMode,
1052
+ latencyModeSource: generatedTarget ? "deployment" : "session",
1053
+ }),
1054
+ input,
1055
+ creatorPolicy?.credentialRestriction,
1056
+ );
965
1057
  const deferredEvents: Array<{
966
1058
  sessionId: string;
967
1059
  events: Awaited<ReturnType<typeof appendSessionEvents>>;
@@ -994,6 +1086,12 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
994
1086
  resolvedTools: taskTools,
995
1087
  resolvedFirstPartyMcpTools: firstPartyMcpTools,
996
1088
  resolvedFirstPartyMcpPermissions: firstPartyMcpPermissions,
1089
+ ...(resolvedAgentConfig
1090
+ ? {
1091
+ resolvedAgentConfig,
1092
+ ...(resolvedAgentInstructions !== undefined ? { resolvedAgentInstructions } : {}),
1093
+ }
1094
+ : {}),
997
1095
  resolvedVariableSet: acceptedVariableSet
998
1096
  ? { id: acceptedVariableSet.id, generation: acceptedVariableSet.generation }
999
1097
  : null,
@@ -1201,6 +1299,10 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
1201
1299
  tools: taskTools,
1202
1300
  firstPartyMcpTools,
1203
1301
  firstPartyMcpPermissions,
1302
+ ...(resolvedAgentConfig ? { agentConfig: resolvedAgentConfig } : {}),
1303
+ ...(resolvedAgentInstructions !== undefined
1304
+ ? { instructions: resolvedAgentInstructions }
1305
+ : {}),
1204
1306
  ...creatorSessionPolicy,
1205
1307
  ...(task.agentConfig.knowledgeSource?.destination.kind === "personal"
1206
1308
  ? {
@@ -1210,6 +1312,10 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
1210
1312
  : {}),
1211
1313
  metadata: {
1212
1314
  ...taskMetadata,
1315
+ ...scheduledSessionExecutionPolicyMetadata(
1316
+ turnExecutionPolicy,
1317
+ creatorPolicy?.credentialRestriction,
1318
+ ),
1213
1319
  model,
1214
1320
  reasoningEffort,
1215
1321
  scheduledTaskId: task.id,
@@ -1305,6 +1411,7 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
1305
1411
  }
1306
1412
  session = created.session;
1307
1413
  sessionCreated = created.created;
1414
+ if (sessionCreated) recordScheduledSessionCreated(baseService.observability);
1308
1415
  if (!sessionCreated) {
1309
1416
  await bindScheduledTaskRunSessionInTransaction(dispatchDb, {
1310
1417
  accountId: task.accountId,
@@ -1589,6 +1696,9 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
1589
1696
  run: { ...run, status: "skipped" as const, error },
1590
1697
  };
1591
1698
  }
1699
+ if (scheduledUpdate.added) {
1700
+ recordUserMessageAccepted(baseService.observability, { surface: "scheduled" });
1701
+ }
1592
1702
  if (scheduledUpdate.added && scheduledUpdate.events.length > 0) {
1593
1703
  if (deferPublications) {
1594
1704
  deferredEvents.push({
@@ -1732,6 +1842,9 @@ export function createScheduledTaskActivities(services: () => Promise<ControlAct
1732
1842
  run: { ...run, status: "skipped" as const, error },
1733
1843
  };
1734
1844
  }
1845
+ if (bundled.added) {
1846
+ recordUserMessageAccepted(baseService.observability, { surface: "scheduled" });
1847
+ }
1735
1848
  if (bundled.added && bundled.events.length > 0) {
1736
1849
  if (deferPublications) {
1737
1850
  deferredEvents.push({
@@ -2143,6 +2256,7 @@ async function seedScheduledGeneratedSessionRoute(input: {
2143
2256
 
2144
2257
  async function recoverBoundScheduledTaskDispatch(input: {
2145
2258
  db: Database;
2259
+ observability?: ProductUsageMetricsSink | undefined;
2146
2260
  bus: ControlActivityServices["bus"];
2147
2261
  settings: ControlActivityServices["settings"];
2148
2262
  wakeSessionWorkflow: WakeSessionWorkflowSignal | null;
@@ -2157,6 +2271,11 @@ async function recoverBoundScheduledTaskDispatch(input: {
2157
2271
  const generatedSession =
2158
2272
  task.runMode === "new_session_per_run" ||
2159
2273
  (task.runMode === "reusable_session" && task.reusableSessionId === null);
2274
+ // The immutable creator policy determines standing session authority, not
2275
+ // a manual caller's per-run ceiling. Read it for creation and recovery checks.
2276
+ const recoveredCreatorPolicy = generatedSession
2277
+ ? await getScheduledTaskCreatorPolicy(input.db, task.workspaceId, task.id)
2278
+ : null;
2160
2279
  const frozenSlack = input.acceptedExecution.resolvedSlackBotConnection;
2161
2280
  if (frozenSlack) {
2162
2281
  const currentSlack = await requireOpenGeniSlackBotConnection(
@@ -2225,14 +2344,6 @@ async function recoverBoundScheduledTaskDispatch(input: {
2225
2344
  }
2226
2345
  const taskMetadata = { ...task.agentConfig.metadata };
2227
2346
  delete taskMetadata[OPENGENI_SLACK_BOT_SESSION_METADATA_KEY];
2228
- // Tools and permissions were frozen into the accepted execution. The
2229
- // creator session policy is immutable on the task row (a tombstoned task
2230
- // still answers), so re-reading it here is deterministic for the same run.
2231
- const recoveredCreatorPolicy = await getScheduledTaskCreatorPolicy(
2232
- input.db,
2233
- task.workspaceId,
2234
- task.id,
2235
- );
2236
2347
  const created = await createSessionWithIdempotencyKeyResult(input.db, {
2237
2348
  accountId: task.accountId,
2238
2349
  workspaceId: task.workspaceId,
@@ -2242,6 +2353,12 @@ async function recoverBoundScheduledTaskDispatch(input: {
2242
2353
  tools: input.acceptedExecution.resolvedTools,
2243
2354
  firstPartyMcpTools: input.acceptedExecution.resolvedFirstPartyMcpTools,
2244
2355
  firstPartyMcpPermissions: input.acceptedExecution.resolvedFirstPartyMcpPermissions,
2356
+ ...(input.acceptedExecution.resolvedAgentConfig
2357
+ ? { agentConfig: input.acceptedExecution.resolvedAgentConfig }
2358
+ : {}),
2359
+ ...(input.acceptedExecution.resolvedAgentInstructions !== undefined
2360
+ ? { instructions: input.acceptedExecution.resolvedAgentInstructions }
2361
+ : {}),
2245
2362
  ...scheduledCreatorSessionPolicyInput(recoveredCreatorPolicy?.sessionPolicy ?? null),
2246
2363
  ...(task.agentConfig.knowledgeSource?.destination.kind === "personal"
2247
2364
  ? {
@@ -2251,6 +2368,10 @@ async function recoverBoundScheduledTaskDispatch(input: {
2251
2368
  : {}),
2252
2369
  metadata: {
2253
2370
  ...taskMetadata,
2371
+ ...scheduledSessionExecutionPolicyMetadata(
2372
+ input.acceptedExecution.turnExecutionPolicy,
2373
+ recoveredCreatorPolicy?.credentialRestriction,
2374
+ ),
2254
2375
  model: input.acceptedExecution.resolvedModel,
2255
2376
  reasoningEffort: input.acceptedExecution.resolvedReasoningEffort,
2256
2377
  scheduledTaskId: task.id,
@@ -2309,6 +2430,7 @@ async function recoverBoundScheduledTaskDispatch(input: {
2309
2430
  });
2310
2431
  if (created.denied) throw new SessionSpawnDeniedDbError(created.denial);
2311
2432
  session = created.session;
2433
+ if (created.created) recordScheduledSessionCreated(input.observability);
2312
2434
  if (!created.created) {
2313
2435
  await bindScheduledTaskRunSessionInTransaction(input.db, {
2314
2436
  accountId: task.accountId,
@@ -2379,20 +2501,27 @@ async function recoverBoundScheduledTaskDispatch(input: {
2379
2501
  );
2380
2502
  const expectedTaskMetadata = { ...task.agentConfig.metadata };
2381
2503
  delete expectedTaskMetadata[OPENGENI_SLACK_BOT_SESSION_METADATA_KEY];
2382
- const expectedMetadata = {
2383
- ...expectedTaskMetadata,
2384
- model: input.acceptedExecution.resolvedModel,
2385
- reasoningEffort: input.acceptedExecution.resolvedReasoningEffort,
2386
- scheduledTaskId: task.id,
2387
- scheduledTaskRunMode: task.runMode,
2388
- ...(task.agentConfig.goal ? { scheduledTaskGoal: task.agentConfig.goal } : {}),
2389
- scheduledTaskRunId: canonicalGeneratedRunId,
2390
- ...(frozenSlack ? { [OPENGENI_SLACK_BOT_SESSION_METADATA_KEY]: frozenSlack.id } : {}),
2391
- };
2504
+ const expectedMetadata = metadataWithAgentConfigCreateIdentity(
2505
+ {
2506
+ ...expectedTaskMetadata,
2507
+ ...scheduledSessionExecutionPolicyMetadata(
2508
+ input.acceptedExecution.turnExecutionPolicy,
2509
+ recoveredCreatorPolicy?.credentialRestriction,
2510
+ ),
2511
+ model: input.acceptedExecution.resolvedModel,
2512
+ reasoningEffort: input.acceptedExecution.resolvedReasoningEffort,
2513
+ scheduledTaskId: task.id,
2514
+ scheduledTaskRunMode: task.runMode,
2515
+ ...(task.agentConfig.goal ? { scheduledTaskGoal: task.agentConfig.goal } : {}),
2516
+ scheduledTaskRunId: canonicalGeneratedRunId,
2517
+ ...(frozenSlack ? { [OPENGENI_SLACK_BOT_SESSION_METADATA_KEY]: frozenSlack.id } : {}),
2518
+ },
2519
+ input.acceptedExecution.resolvedAgentConfig,
2520
+ );
2392
2521
  if (
2393
2522
  session.createIdempotencyKey !== expectedCreateKey ||
2394
2523
  session.initialMessage !== task.agentConfig.prompt ||
2395
- session.instructions !== null ||
2524
+ session.instructions !== (input.acceptedExecution.resolvedAgentInstructions ?? null) ||
2396
2525
  session.policyRole !== null ||
2397
2526
  stableJson(session.skills) !== "[]" ||
2398
2527
  stableJson(session.toolPolicy) !==
@@ -2434,6 +2563,8 @@ async function recoverBoundScheduledTaskDispatch(input: {
2434
2563
  stableJson(input.acceptedExecution.resolvedFirstPartyMcpTools) ||
2435
2564
  stableJson(session.firstPartyMcpPermissions) !==
2436
2565
  stableJson(input.acceptedExecution.resolvedFirstPartyMcpPermissions) ||
2566
+ stableJson(session.agent) !==
2567
+ stableJson(input.acceptedExecution.resolvedAgentConfig ?? null) ||
2437
2568
  session.maxNestedAgentDepthOverride !== (task.agentConfig.maxNestedAgentDepth ?? null) ||
2438
2569
  (session.variableSetId ?? null) !==
2439
2570
  (input.acceptedExecution.resolvedVariableSet?.id ?? null) ||
@@ -2599,6 +2730,9 @@ async function recoverBoundScheduledTaskDispatch(input: {
2599
2730
  ) {
2600
2731
  return { action: "blocked", reason: "scheduled_run_terminal" };
2601
2732
  }
2733
+ if (scheduledUpdate.added) {
2734
+ recordUserMessageAccepted(input.observability, { surface: "scheduled" });
2735
+ }
2602
2736
  if (scheduledUpdate.added && scheduledUpdate.events.length > 0) {
2603
2737
  await publishDurableSessionEvents(
2604
2738
  input.bus,
@@ -176,6 +176,7 @@ async function dispatchMaintenanceSession(
176
176
  initialMessage: maintenancePrompt(maintenance),
177
177
  // OpenGeni's own periodic check: no human or product surface entered it.
178
178
  surface: "system",
179
+ metrics: service.observability,
179
180
  resources: [],
180
181
  tools: [{ kind: "mcp", id: "opengeni" }],
181
182
  toolPolicy: { mode: "explicit", inheritedFromSessionId: null },
@@ -436,6 +436,7 @@ export type DispatchScheduledTaskRunInput = {
436
436
  triggerType: Extract<ScheduledTaskTriggerType, "scheduled">;
437
437
  agentRunUsageIdempotencyKey?: never;
438
438
  initiator?: never;
439
+ credentialRestriction?: never;
439
440
  }
440
441
  | {
441
442
  triggerType: Extract<
@@ -445,6 +446,8 @@ export type DispatchScheduledTaskRunInput = {
445
446
  agentRunUsageIdempotencyKey: string;
446
447
  /** Exact identity used by the API-side charge for this same trigger. */
447
448
  initiator: TurnInitiator;
449
+ /** Trusted API-side caller ceiling for this occurrence, not the schedule. */
450
+ credentialRestriction?: "developer_setup";
448
451
  }
449
452
  );
450
453
 
@@ -458,6 +461,7 @@ export type DispatchScheduledTaskRunResult =
458
461
  refusal?: import("@opengeni/contracts").ScheduledTaskAdmissionRefusal;
459
462
  reason:
460
463
  | "insufficient_credits"
464
+ | "allowance_exhausted"
461
465
  | "monthly_model_cost_limit"
462
466
  | "monthly_agent_run_limit"
463
467
  | "malformed_manual_trigger"
@@ -947,13 +947,24 @@ async function runCapture(
947
947
  }
948
948
 
949
949
  // ── 10. observe completion ────────────────────────────────────────────────
950
- const durationMs = Date.now() - startedAt;
950
+ recordWorkspaceCaptureCompleted(observability, Date.now() - startedAt);
951
+ }
952
+
953
+ /** Count one committed logical capture and observe its end-to-end duration. */
954
+ export function recordWorkspaceCaptureCompleted(
955
+ observability: Pick<Observability, "incrementCounter" | "observeHistogram">,
956
+ durationMs: number,
957
+ ): void {
951
958
  observability.incrementCounter({
952
959
  name: "opengeni_workspace_capture_total",
953
960
  labels: { result: "ok" },
954
961
  });
962
+ // Distinct from the physical `opengeni_workspace_capture_duration_seconds`
963
+ // {backend,outcome} histogram the runtime hook records in this same
964
+ // registry: one name cannot carry two label sets.
955
965
  observability.observeHistogram({
956
- name: "opengeni_workspace_capture_duration_seconds",
966
+ name: "opengeni_workspace_capture_revision_duration_seconds",
967
+ help: "Turn-end logical workspace revision capture duration for committed revisions.",
957
968
  value: durationMs / 1000,
958
969
  });
959
970
  }
@@ -78,6 +78,8 @@ export function credentialProviderRequestBody(
78
78
  mcpServers: [],
79
79
  },
80
80
  ): CredentialProviderRequest {
81
+ const via = input.initiatorContext.via;
82
+ const latestHop = Array.isArray(via) ? via.at(-1) : null;
81
83
  return {
82
84
  type: "credentials.request",
83
85
  lane: selection.lane,
@@ -92,6 +94,16 @@ export function credentialProviderRequestBody(
92
94
  turnId: input.turnId,
93
95
  attemptId: input.attemptId,
94
96
  initiator: { kind: input.initiator.kind, subjectId: input.initiator.subjectId },
97
+ initiatorContext: {
98
+ kind:
99
+ latestHop?.kind === "agent"
100
+ ? "agent"
101
+ : input.initiator.kind === "subject"
102
+ ? "human"
103
+ : "service",
104
+ initiator: input.initiator,
105
+ context: input.initiatorContext,
106
+ },
95
107
  initiatingHumanSubjectId,
96
108
  initiatingHuman,
97
109
  sandboxBackend: input.effectiveSandboxBackend,
@@ -2,6 +2,7 @@ import { createKnowledgeIndexingActivities } from "./activities/knowledge-indexi
2
2
  import { createSharedActivityServices } from "./activity-services";
3
3
  import { createCodexCapacityActivities } from "./activities/codex-capacity";
4
4
  import { createBrowserStateArtifactMaintenanceActivities } from "./activities/browser-state-artifact-reaper";
5
+ import { createBrowserDeadlineCheckpointActivities } from "./activities/browser-deadline-checkpoint";
5
6
  import { createAutomationActivities } from "./activities/automations";
6
7
  import { createDocumentActivities } from "./activities/documents";
7
8
  import { createFileUploadReaperActivities } from "./activities/file-upload-reaper";
@@ -39,6 +40,7 @@ export function createControlActivitiesFromServices(
39
40
  ...createCodexCapacityActivities(services),
40
41
  ...createRigVerificationActivities(services),
41
42
  ...createBrowserStateArtifactMaintenanceActivities(services),
43
+ ...createBrowserDeadlineCheckpointActivities(services),
42
44
  ...createFileUploadReaperActivities(services),
43
45
  ...createRetainedScreenshotMaintenanceActivities(services),
44
46
  ...createWorkflowWakeActivities(services),
package/src/activities.ts CHANGED
@@ -117,6 +117,9 @@ export const maybeContinueGoal = defaultControlActivities.maybeContinueGoal;
117
117
  export const getCodexCapacityWait = defaultControlActivities.getCodexCapacityWait;
118
118
  export const reconcileCodexCapacityWait = defaultControlActivities.reconcileCodexCapacityWait;
119
119
  export const prepareSandboxLeaseSweep = defaultControlActivities.prepareSandboxLeaseSweep;
120
+ export const listDueBrowserCheckpoints = defaultControlActivities.listDueBrowserCheckpoints;
121
+ export const checkpointBrowserBeforeDeadline =
122
+ defaultControlActivities.checkpointBrowserBeforeDeadline;
120
123
  export const drainSandboxLease = defaultControlActivities.drainSandboxLease;
121
124
  export const maintainSandboxLeaseSweep = defaultControlActivities.maintainSandboxLeaseSweep;
122
125
  export const reapSandboxLeases = defaultControlActivities.reapSandboxLeases;
@@ -0,0 +1,5 @@
1
+ export const BROWSER_DEADLINE_CHECKPOINT_SWEEP_ID = "opengeni-browser-deadline-checkpoints-v1";
2
+ const QUEUE_SUFFIX = "-browser-checkpoint-v1";
3
+ export function browserDeadlineCheckpointTaskQueue(base: string): string {
4
+ return base.endsWith(QUEUE_SUFFIX) ? base : `${base}${QUEUE_SUFFIX}`;
5
+ }
package/src/index.ts CHANGED
@@ -99,6 +99,10 @@ import {
99
99
  SANDBOX_REAPER_V2_WORKFLOW_ID,
100
100
  sandboxLifecycleTaskQueue,
101
101
  } from "./sandbox-reaper-contract";
102
+ import {
103
+ BROWSER_DEADLINE_CHECKPOINT_SWEEP_ID,
104
+ browserDeadlineCheckpointTaskQueue,
105
+ } from "./browser-deadline-checkpoint-contract";
102
106
 
103
107
  export {
104
108
  createHostExportPump,
@@ -323,28 +327,39 @@ export async function createOpenGeniWorker(options: WorkerOptions): Promise<{
323
327
  return { worker, connection };
324
328
  }
325
329
 
330
+ const ownedWorkers = [worker];
326
331
  try {
327
- const sandboxLifecycleWorker = await Worker.create({
328
- ...sharedWorkerOptions,
329
- ...workflowDefinition,
330
- taskQueue: sandboxLifecycleTaskQueue(settings.temporalTaskQueue),
331
- reuseV8Context: true,
332
- workflowThreadPoolSize: 1,
333
- maxCachedWorkflows: CONTROL_WORKER_MAX_CACHED_WORKFLOWS,
334
- maxConcurrentWorkflowTaskExecutions: CONTROL_WORKER_MAX_CONCURRENT_WORKFLOW_TASKS,
335
- maxConcurrentActivityTaskExecutions: CONTROL_WORKER_MAX_CONCURRENT_ACTIVITIES,
336
- });
332
+ // The new activity type has its own queue. Retain the V1 poller so old
333
+ // drain histories finish; old binaries never receive checkpoint work.
334
+ for (const taskQueue of [
335
+ sandboxLifecycleTaskQueue(settings.temporalTaskQueue),
336
+ browserDeadlineCheckpointTaskQueue(settings.temporalTaskQueue),
337
+ ]) {
338
+ const lifecycleWorker = await Worker.create({
339
+ ...sharedWorkerOptions,
340
+ ...workflowDefinition,
341
+ taskQueue,
342
+ reuseV8Context: true,
343
+ workflowThreadPoolSize: 1,
344
+ maxCachedWorkflows: CONTROL_WORKER_MAX_CACHED_WORKFLOWS,
345
+ maxConcurrentWorkflowTaskExecutions: CONTROL_WORKER_MAX_CONCURRENT_WORKFLOW_TASKS,
346
+ maxConcurrentActivityTaskExecutions: CONTROL_WORKER_MAX_CONCURRENT_ACTIVITIES,
347
+ });
348
+ ownedWorkers.push(lifecycleWorker);
349
+ }
337
350
  return {
338
- worker: combineWorkerRunTargets([worker, sandboxLifecycleWorker]),
351
+ worker: combineWorkerRunTargets(ownedWorkers),
339
352
  connection,
340
353
  };
341
354
  } catch (error) {
342
- try {
343
- worker.shutdown();
344
- } catch {
345
- // Preserve the lifecycle worker construction failure. The base poller
346
- // still received its shutdown request; a secondary synchronous
347
- // shutdown error must not replace the startup root cause.
355
+ for (const ownedWorker of ownedWorkers) {
356
+ try {
357
+ ownedWorker.shutdown();
358
+ } catch {
359
+ // Preserve the lifecycle worker construction failure. The base poller
360
+ // still received its shutdown request; a secondary synchronous
361
+ // shutdown error must not replace the startup root cause.
362
+ }
348
363
  }
349
364
  throw error;
350
365
  }
@@ -478,20 +493,33 @@ export async function createWorkerWorkflowSignaler(
478
493
  return normalizeTurnTaskQueueStats(response.stats);
479
494
  },
480
495
  startSandboxReaperWorkflow: async () => {
481
- try {
482
- await temporal.workflow.start("sandboxReaperWorkflowV2", {
496
+ // Same backend tick, no additional schedule. A bounded independent
497
+ // inventory cannot prevent the existing per-box drain from starting.
498
+ const starts = await Promise.allSettled([
499
+ temporal.workflow.start("sandboxReaperWorkflowV2", {
483
500
  taskQueue: sandboxLifecycleTaskQueue(settings.temporalTaskQueue),
484
501
  workflowId: SANDBOX_REAPER_V2_WORKFLOW_ID,
485
502
  workflowIdReusePolicy: "ALLOW_DUPLICATE",
486
503
  args: [],
487
- });
488
- return "started";
489
- } catch (error) {
490
- if (error instanceof WorkflowExecutionAlreadyStartedError) {
491
- return "already_running";
504
+ }),
505
+ temporal.workflow.start("browserDeadlineCheckpointSweepWorkflow", {
506
+ taskQueue: browserDeadlineCheckpointTaskQueue(settings.temporalTaskQueue),
507
+ workflowId: BROWSER_DEADLINE_CHECKPOINT_SWEEP_ID,
508
+ workflowIdReusePolicy: "ALLOW_DUPLICATE",
509
+ args: [],
510
+ }),
511
+ ]);
512
+ for (const start of starts) {
513
+ if (
514
+ start.status === "rejected" &&
515
+ !(start.reason instanceof WorkflowExecutionAlreadyStartedError)
516
+ ) {
517
+ // The other start already settled. Retry the router without losing
518
+ // ordinary draining or silently omitting the checkpoint sweep.
519
+ throw start.reason;
492
520
  }
493
- throw error;
494
521
  }
522
+ return starts[0].status === "fulfilled" ? "started" : "already_running";
495
523
  },
496
524
  startVideoGenerationWorkflow: async ({ accountId, workspaceId, operationId }) => {
497
525
  try {