@opengeni/worker-bundle 2.2.1 → 2.3.0-canary.36935312914001

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 (37) hide show
  1. package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
  2. package/dist/activities/agent-turn/run-credentials.d.ts +2 -0
  3. package/dist/activities/scheduled-tasks.d.ts +4 -35
  4. package/dist/activities/types.d.ts +3 -0
  5. package/dist/activities/workspace-capture.d.ts +2 -0
  6. package/dist/{activities-control-55ECL47G.js → activities-control-RDA4E2DM.js} +102 -20
  7. package/dist/activities-control-RDA4E2DM.js.map +1 -0
  8. package/dist/{activities-turn-T7Y3BWYO.js → activities-turn-A7LDKRLD.js} +20 -7
  9. package/dist/activities-turn-A7LDKRLD.js.map +1 -0
  10. package/dist/{chunk-OWNLATJL.js → chunk-HXPW4HPN.js} +60 -24
  11. package/dist/chunk-HXPW4HPN.js.map +1 -0
  12. package/dist/{chunk-IJZU752L.js → chunk-YG27NEMX.js} +34 -1
  13. package/dist/chunk-YG27NEMX.js.map +1 -0
  14. package/dist/index.js +2 -2
  15. package/dist/observability-metrics.d.ts +16 -1
  16. package/dist/workflow-bundle.js +18 -12
  17. package/dist/workflows/scheduled-tasks.d.ts +4 -0
  18. package/package.json +21 -21
  19. package/src/activities/agent-turn/claim.ts +13 -1
  20. package/src/activities/agent-turn/credential-restriction.ts +14 -0
  21. package/src/activities/agent-turn/errors.ts +39 -13
  22. package/src/activities/agent-turn/run-credentials.ts +5 -0
  23. package/src/activities/agent-turn/run.ts +1 -0
  24. package/src/activities/agent-turn/tool-environment.ts +4 -0
  25. package/src/activities/automations.ts +23 -1
  26. package/src/activities/goals.ts +33 -8
  27. package/src/activities/sandbox-lease.ts +22 -0
  28. package/src/activities/scheduled-tasks.ts +89 -25
  29. package/src/activities/site-auth-maintenance.ts +1 -0
  30. package/src/activities/types.ts +3 -0
  31. package/src/activities/workspace-capture.ts +13 -2
  32. package/src/observability-metrics.ts +55 -1
  33. package/src/workflows/scheduled-tasks.ts +26 -13
  34. package/dist/activities-control-55ECL47G.js.map +0 -1
  35. package/dist/activities-turn-T7Y3BWYO.js.map +0 -1
  36. package/dist/chunk-IJZU752L.js.map +0 -1
  37. package/dist/chunk-OWNLATJL.js.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import type { TurnInitiator } from "@opengeni/contracts";
2
+ import type { DispatchScheduledTaskRunInput } from "../activities/types.js";
2
3
  type ScheduledTaskFireWorkflowBase = {
3
4
  accountId: string;
4
5
  workspaceId: string;
@@ -8,10 +9,13 @@ export type ScheduledTaskFireWorkflowInput = ScheduledTaskFireWorkflowBase & ({
8
9
  triggerType: "scheduled";
9
10
  agentRunUsageIdempotencyKey?: never;
10
11
  initiator?: never;
12
+ credentialRestriction?: never;
11
13
  } | {
12
14
  triggerType: "manual" | "initial" | "provider_event" | "retry" | "repair";
13
15
  agentRunUsageIdempotencyKey: string;
14
16
  initiator: TurnInitiator;
17
+ credentialRestriction?: "developer_setup";
15
18
  });
19
+ export declare function scheduledTaskDispatchInput(input: ScheduledTaskFireWorkflowInput, producerKey: string): DispatchScheduledTaskRunInput;
16
20
  export declare function scheduledTaskFireWorkflow(input: ScheduledTaskFireWorkflowInput): Promise<void>;
17
21
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/worker-bundle",
3
- "version": "2.2.1",
3
+ "version": "2.3.0-canary.36935312914001",
4
4
  "description": "OpenGeni worker entry and reusable embedded lifecycle, shipped with a release-coherent pre-bundled Temporal workflow artifact.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -54,26 +54,26 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@llamaindex/liteparse": "2.14.2",
57
- "@opengeni/agent-proto": "^0.6.1",
58
- "@opengeni/capabilities": "^0.3.5",
59
- "@opengeni/codemode": "^0.6.6",
60
- "@opengeni/codex": "^0.2.30",
61
- "@opengeni/config": "^3.2.0",
62
- "@opengeni/contracts": "^5.5.0",
63
- "@opengeni/core": "^5.0.1",
64
- "@opengeni/db": "^6.3.0",
65
- "@opengeni/documents": "^0.8.38",
66
- "@opengeni/events": "^0.4.36",
67
- "@opengeni/github": "^0.8.1",
68
- "@opengeni/interaction": "^0.4.44",
69
- "@opengeni/jev": "^0.2.1",
70
- "@opengeni/network": "^0.3.1",
71
- "@opengeni/observability": "^0.8.36",
72
- "@opengeni/runtime": "^4.4.0",
73
- "@opengeni/sdk": "^7.5.0",
74
- "@opengeni/storage": "^0.2.137",
75
- "@opengeni/tool-gateway": "^0.1.17",
76
- "@opengeni/xai-subscription": "^0.1.5",
57
+ "@opengeni/agent-proto": "^0.6.1-canary.36935312914001",
58
+ "@opengeni/capabilities": "^0.3.5-canary.36935312914001",
59
+ "@opengeni/codemode": "^0.6.7-canary.36935312914001",
60
+ "@opengeni/codex": "^0.2.31-canary.36935312914001",
61
+ "@opengeni/config": "^3.2.1-canary.36935312914001",
62
+ "@opengeni/contracts": "^5.6.0-canary.36935312914001",
63
+ "@opengeni/core": "^5.1.0-canary.36935312914001",
64
+ "@opengeni/db": "^6.4.0-canary.36935312914001",
65
+ "@opengeni/documents": "^0.8.39-canary.36935312914001",
66
+ "@opengeni/events": "^0.4.37-canary.36935312914001",
67
+ "@opengeni/github": "^0.8.2-canary.36935312914001",
68
+ "@opengeni/interaction": "^0.4.45-canary.36935312914001",
69
+ "@opengeni/jev": "^0.2.1-canary.36935312914001",
70
+ "@opengeni/network": "^0.3.1-canary.36935312914001",
71
+ "@opengeni/observability": "^0.8.37-canary.36935312914001",
72
+ "@opengeni/runtime": "^4.5.0-canary.36935312914001",
73
+ "@opengeni/sdk": "^7.6.0-canary.36935312914001",
74
+ "@opengeni/storage": "^0.2.138-canary.36935312914001",
75
+ "@opengeni/tool-gateway": "^0.1.18-canary.36935312914001",
76
+ "@opengeni/xai-subscription": "^0.1.5-canary.36935312914001",
77
77
  "@temporalio/activity": "^1.17.0",
78
78
  "@temporalio/client": "^1.17.0",
79
79
  "@temporalio/worker": "^1.17.0",
@@ -56,6 +56,7 @@ import {
56
56
  import { createTurnCredentialLeases } from "./credential-leases";
57
57
  import { createTurnMediaArtifacts } from "./media-artifacts";
58
58
  import { readTurnExecutionPolicyV1 } from "@opengeni/contracts";
59
+ import { turnCredentialRestriction } from "./credential-restriction";
59
60
 
60
61
  import {
61
62
  credentialSubjectIdForTurnInitiator,
@@ -290,10 +291,21 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
290
291
  const codexAppsCredentialId = capabilitySettings.codexConnectedAppsEnabled
291
292
  ? await resolveCodexAppsCredentialIdForRun(db, input.workspaceId)
292
293
  : null;
293
- const policyForAbsent =
294
+ const candidatePolicy =
294
295
  claimedPolicy.kind === "valid"
295
296
  ? claimedPolicy.policy
296
297
  : resolveTurnExecutionPolicyV1(capabilitySettings, legacyTurnExecutionPolicyInput(turn));
298
+ // This context is frozen by the accepted-turn writer from the exact source
299
+ // turn. Its reserved restriction field cannot come from public service JSON.
300
+ const credentialRestriction =
301
+ claimedPolicy.kind === "absent"
302
+ ? turn.initiatorContext?.credentialRestriction === "developer_setup"
303
+ ? "developer_setup"
304
+ : turnCredentialRestriction(candidatePolicy, session.metadata)
305
+ : undefined;
306
+ const policyForAbsent = credentialRestriction
307
+ ? { ...candidatePolicy, credentialRestriction }
308
+ : candidatePolicy;
297
309
  const installedPolicy = await installOrReadTurnExecutionPolicyForAttempt(db, {
298
310
  accountId: input.accountId,
299
311
  workspaceId: input.workspaceId,
@@ -0,0 +1,14 @@
1
+ import { readTurnExecutionPolicyV1, type TurnExecutionPolicyV1 } from "@opengeni/contracts";
2
+
3
+ /** Only accepted policies establish the ceiling; ordinary metadata is not authority. */
4
+ export function turnCredentialRestriction(
5
+ acceptedPolicy: TurnExecutionPolicyV1,
6
+ initialSessionMetadata: unknown,
7
+ ): "developer_setup" | undefined {
8
+ const initialPolicy = readTurnExecutionPolicyV1(initialSessionMetadata);
9
+ return acceptedPolicy.credentialRestriction === "developer_setup" ||
10
+ (initialPolicy.kind === "valid" &&
11
+ initialPolicy.policy.credentialRestriction === "developer_setup")
12
+ ? "developer_setup"
13
+ : undefined;
14
+ }
@@ -3,6 +3,8 @@ import {
3
3
  ActiveSessionHistoryLimitExceededError,
4
4
  ApprovalRunStateLimitExceededError,
5
5
  nestedPostgresSqlState,
6
+ databaseFailureCode,
7
+ isRetryablePersistenceSqlState,
6
8
  safeDatabaseErrorFacts,
7
9
  isRetryableDatabaseTransportFailure,
8
10
  isSessionEventPersistenceError,
@@ -352,6 +354,13 @@ function retryableDatabaseFailureCode(
352
354
  error: unknown,
353
355
  ): PostClaimDatabaseRecoveryDetail["code"] | null {
354
356
  const persistenceFailure = isSessionEventPersistenceError(error) ? error : null;
357
+ if (!persistenceFailure) {
358
+ const driver = findPostgresDriverError(error);
359
+ const driverSqlState = typeof driver?.code === "string" ? driver.code : null;
360
+ if (isRetryablePersistenceSqlState(driverSqlState)) {
361
+ return databaseFailureCode(driverSqlState);
362
+ }
363
+ }
355
364
  const sqlState = persistenceFailure?.details.sqlState ?? null;
356
365
  if (sqlState === null && isRetryableDatabaseTransportFailure(error)) {
357
366
  return "db_failure";
@@ -1010,6 +1019,16 @@ function findPostgresDriverError(error: unknown): Record<string, unknown> | null
1010
1019
  return null;
1011
1020
  }
1012
1021
 
1022
+ function isRawDatabaseQueryError(error: unknown): boolean {
1023
+ return (
1024
+ error instanceof Error &&
1025
+ "query" in error &&
1026
+ typeof error.query === "string" &&
1027
+ "params" in error &&
1028
+ Array.isArray(error.params)
1029
+ );
1030
+ }
1031
+
1013
1032
  export function agentRunFailurePayload(
1014
1033
  error: unknown,
1015
1034
  options: { isCodexTurn?: boolean } = {},
@@ -1060,7 +1079,11 @@ function baseAgentRunFailurePayload(
1060
1079
  return { error: error.message, code: "retained_attachment_transport_limit", retryable: false };
1061
1080
  }
1062
1081
  if (error instanceof MandatoryHistoryPersistenceError) {
1063
- const underlying = isSessionEventPersistenceError(error.cause)
1082
+ const databaseFailure =
1083
+ isSessionEventPersistenceError(error.cause) ||
1084
+ findPostgresDriverError(error.cause) !== null ||
1085
+ isRawDatabaseQueryError(error.cause);
1086
+ const underlying = databaseFailure
1064
1087
  ? agentRunFailurePayload(error.cause, options)
1065
1088
  : {
1066
1089
  error: error.cause instanceof Error ? error.cause.message : String(error.cause),
@@ -1070,6 +1093,21 @@ function baseAgentRunFailurePayload(
1070
1093
  historyPersistenceStage: error.stage,
1071
1094
  };
1072
1095
  }
1096
+ // Raw ORM wrappers can contain the full SQL and its parameters. Classify a
1097
+ // real driver before provider message heuristics; the original cause stays
1098
+ // available to internal diagnostics. This payload grants no replay authority.
1099
+ const postgresDriverError = findPostgresDriverError(error);
1100
+ const rawOrmFailure = isRawDatabaseQueryError(error);
1101
+ if ((postgresDriverError || rawOrmFailure) && !isSessionEventPersistenceError(error)) {
1102
+ const database = safeDatabaseErrorFacts(postgresDriverError ?? error);
1103
+ const sqlState = postgresDriverError ? nestedPostgresSqlState(postgresDriverError) : null;
1104
+ return {
1105
+ error: "OpenGeni encountered a database error.",
1106
+ code: databaseFailureCode(sqlState),
1107
+ sqlState,
1108
+ ...(Object.keys(database).length > 0 ? { database } : {}),
1109
+ };
1110
+ }
1073
1111
  const safetyRefusalDiagnostic = providerSafetyRefusalDiagnostic(error);
1074
1112
  if (safetyRefusalDiagnostic !== undefined) {
1075
1113
  return {
@@ -1362,18 +1400,6 @@ function baseAgentRunFailurePayload(
1362
1400
  }
1363
1401
  return { error: message, code: "provider_unavailable", retryable: true };
1364
1402
  }
1365
- const postgresDriverError = findPostgresDriverError(error);
1366
- if (postgresDriverError) {
1367
- const database = safeDatabaseErrorFacts(postgresDriverError);
1368
- const sqlState = nestedPostgresSqlState(postgresDriverError);
1369
- if (sqlState !== null || Object.keys(database).length > 0) {
1370
- return {
1371
- error: message,
1372
- sqlState,
1373
- ...(Object.keys(database).length > 0 ? { database } : {}),
1374
- };
1375
- }
1376
- }
1377
1403
  return { error: message };
1378
1404
  }
1379
1405
 
@@ -65,6 +65,7 @@ import {
65
65
  sandboxSettingsForRoute,
66
66
  } from "./sandbox-route";
67
67
  import type { ClaimTurnOk } from "./claim";
68
+ import { turnCredentialRestriction } from "./credential-restriction";
68
69
  import type { GovernanceModelOk } from "./governance-model";
69
70
  import type { SandboxTurnRuntime } from "./sandbox-runtime";
70
71
  import type {
@@ -91,6 +92,7 @@ export type PrepareRunCredentialsDeps = {
91
92
  sandboxRuntime: SandboxTurnRuntime;
92
93
  turn: ClaimTurnOk["turn"];
93
94
  session: ClaimTurnOk["session"];
95
+ turnExecutionPolicy: ClaimTurnOk["turnExecutionPolicy"];
94
96
  fileAuthoritySubjectId: ClaimTurnOk["fileAuthoritySubjectId"];
95
97
  runSettings: GovernanceModelOk["runSettings"];
96
98
  workspaceVariableSet: Awaited<ReturnType<typeof loadWorkspaceEnvironmentForRunWithCredentials>>;
@@ -129,6 +131,7 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
129
131
  sandboxState,
130
132
  turn,
131
133
  session,
134
+ turnExecutionPolicy,
132
135
  fileAuthoritySubjectId,
133
136
  runSettings,
134
137
  workspaceVariableSet,
@@ -346,11 +349,13 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
346
349
  turn,
347
350
  })
348
351
  : undefined;
352
+ const credentialRestriction = turnCredentialRestriction(turnExecutionPolicy, session.metadata);
349
353
  const codemodeAuthority = {
350
354
  sessionId: input.sessionId,
351
355
  turnId: turn.id,
352
356
  attemptId: input.attemptId,
353
357
  executionGeneration: turn.executionGeneration,
358
+ ...(credentialRestriction ? { credentialRestriction } : {}),
354
359
  };
355
360
  const sandboxArtifactRuntime = sandboxArtifactRuntimeAdmission(
356
361
  settings,
@@ -1226,6 +1226,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
1226
1226
  sandboxRuntime,
1227
1227
  turn,
1228
1228
  session,
1229
+ turnExecutionPolicy,
1229
1230
  fileAuthoritySubjectId,
1230
1231
  runSettings,
1231
1232
  workspaceVariableSet,
@@ -127,6 +127,7 @@ import { codeSearchToolDefinitions, codeSearchWorkspaceFromChannel } from "./cod
127
127
  import { createWorkspaceSkillTools } from "./skill-tools";
128
128
  import { loadConfiguredBundledSkills } from "./skill-selection";
129
129
  import { guardSkillFilesystem } from "./skill-transfer";
130
+ import { turnCredentialRestriction } from "./credential-restriction";
130
131
 
131
132
  export type PrepareTurnToolPolicyDeps = {
132
133
  input: RunAgentTurnInput;
@@ -422,6 +423,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
422
423
  });
423
424
  const runSettings = accountRoutes.settings;
424
425
  const turnTools = accountRoutes.tools;
426
+ const credentialRestriction = turnCredentialRestriction(turnExecutionPolicy, session.metadata);
425
427
  const toolContextPreparationStartedAt = performance.now();
426
428
  throwIfWorkerShuttingDown();
427
429
  throwIfTurnCancelled();
@@ -1009,6 +1011,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
1009
1011
  : []),
1010
1012
  ...createFirstPartyInteractionAttemptToolDefinitions({
1011
1013
  settings: runSettings,
1014
+ ...(credentialRestriction ? { credentialRestriction } : {}),
1012
1015
  scope: {
1013
1016
  accountId: input.accountId,
1014
1017
  workspaceId: input.workspaceId,
@@ -1104,6 +1107,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
1104
1107
  try {
1105
1108
  eventing.preparedTools = await waitForTurnOperation(
1106
1109
  runtime.prepareTools(githubRestMcp.settings, githubRestMcp.tools, {
1110
+ ...(credentialRestriction ? { credentialRestriction } : {}),
1107
1111
  mcpAccountLabels: accountRoutes.accountLabels,
1108
1112
  accountId: input.accountId,
1109
1113
  workspaceId: input.workspaceId,
@@ -13,6 +13,7 @@ import {
13
13
  recordWorkspaceUsage,
14
14
  resolveCatalogSettings,
15
15
  resolveWorkspaceCatalogSettings,
16
+ isDeveloperSetupDelegatedPermissionAllowed,
16
17
  } from "@opengeni/core";
17
18
  import {
18
19
  AutomationAuthorityRevokedError,
@@ -107,6 +108,20 @@ export function createAutomationActivities(
107
108
  }
108
109
 
109
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
+ }
110
125
  const requestedModel = template.model ?? deploymentCatalogSettings.openaiModel;
111
126
  const catalogSettings =
112
127
  requestedModel.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX) ||
@@ -209,7 +224,7 @@ export function createAutomationActivities(
209
224
  });
210
225
  return { action: "failed", reason: "dispatch_failed" };
211
226
  }
212
- const turnExecutionPolicy = resolveTurnExecutionPolicyV1(catalogSettings, {
227
+ const resolvedTurnExecutionPolicy = resolveTurnExecutionPolicyV1(catalogSettings, {
213
228
  modelId: model,
214
229
  requestedModelId: template.model,
215
230
  modelSource: template.model ? "explicit" : "deployment",
@@ -218,6 +233,12 @@ export function createAutomationActivities(
218
233
  latencyMode: "standard",
219
234
  latencyModeSource: "deployment",
220
235
  });
236
+ const turnExecutionPolicy = template.credentialRestriction
237
+ ? {
238
+ ...resolvedTurnExecutionPolicy,
239
+ credentialRestriction: template.credentialRestriction,
240
+ }
241
+ : resolvedTurnExecutionPolicy;
221
242
  const created = await createSession({
222
243
  db: service.db,
223
244
  bus: service.bus,
@@ -241,6 +262,7 @@ export function createAutomationActivities(
241
262
  workspaceId: input.workspaceId,
242
263
  initialMessage: accepted.initialMessage,
243
264
  surface: "automation",
265
+ metrics: service.observability,
244
266
  resources: template.resources,
245
267
  skills: SessionSkills.parse(template.skills),
246
268
  bundledSkillIds: template.bundledSkillIds,
@@ -14,6 +14,7 @@ import {
14
14
  import {
15
15
  evaluateWorkspaceModelPolicy,
16
16
  mergeToolRefs,
17
+ readTurnExecutionPolicyV1,
17
18
  type SessionGoal,
18
19
  type ToolRef,
19
20
  } from "@opengeni/contracts";
@@ -22,6 +23,7 @@ import {
22
23
  enqueueSessionWorkflowWakeIfRunnable,
23
24
  getWorkspaceModelPolicy,
24
25
  getSessionGoal,
26
+ getSessionTurn,
25
27
  materializeGoalContinuation,
26
28
  requireSession,
27
29
  } from "@opengeni/db";
@@ -36,6 +38,7 @@ import {
36
38
  resolveWorkspaceCatalogSettings,
37
39
  } from "@opengeni/core";
38
40
  import { agentRunAdmissionDenial } from "./agent-run-admission";
41
+ import { turnCredentialRestriction } from "./agent-turn/credential-restriction";
39
42
 
40
43
  export function createGoalActivities(services: () => Promise<ControlActivityServices>) {
41
44
  async function enqueueGoalRetryWake(input: MaybeContinueGoalInput): Promise<void> {
@@ -124,6 +127,16 @@ export function createGoalActivities(services: () => Promise<ControlActivityServ
124
127
  latencyMode: continuationLatencyMode,
125
128
  latencyModeSource: "continuation",
126
129
  });
130
+ const continuationPolicy: NonNullable<
131
+ Parameters<typeof materializeGoalContinuation>[1]["policy"]
132
+ > = {
133
+ model: continuationModel,
134
+ reasoningEffort: continuationReasoningEffort,
135
+ latencyMode: continuationLatencyMode,
136
+ ...(turnExecutionPolicy ? { turnExecutionPolicy } : {}),
137
+ tools: withFirstPartyTools(settings, session.tools),
138
+ sandboxBackend: session.sandboxBackend,
139
+ };
127
140
  const decision = await materializeGoalContinuation(db, {
128
141
  accountId: input.accountId,
129
142
  workspaceId: input.workspaceId,
@@ -140,6 +153,25 @@ export function createGoalActivities(services: () => Promise<ControlActivityServ
140
153
  // pause can clear on its own; a policy pause needs a model/policy change)
141
154
  // and rides the same visible-pause channel.
142
155
  admission: async (tx, causalTurn) => {
156
+ // The materializer selects this exact causal row under its session/goal
157
+ // locks and freezes policy only after admission returns. Never infer
158
+ // credential authority from the generated continuation's payload.
159
+ const sourceTurn = causalTurn
160
+ ? await getSessionTurn(tx, input.workspaceId, causalTurn.id)
161
+ : null;
162
+ if (causalTurn && (!sourceTurn || sourceTurn.sessionId !== input.sessionId)) {
163
+ throw new Error("Goal continuation source turn is unavailable");
164
+ }
165
+ const sourcePolicy = readTurnExecutionPolicyV1(sourceTurn?.metadata);
166
+ if (turnExecutionPolicy) {
167
+ const credentialRestriction = turnCredentialRestriction(
168
+ sourcePolicy.kind === "valid" ? sourcePolicy.policy : turnExecutionPolicy,
169
+ session.metadata,
170
+ );
171
+ continuationPolicy.turnExecutionPolicy = credentialRestriction
172
+ ? { ...turnExecutionPolicy, credentialRestriction }
173
+ : turnExecutionPolicy;
174
+ }
143
175
  const budgetBlocked = modelPolicyBlocked
144
176
  ? null
145
177
  : await goalRunBudgetBlocked(
@@ -158,14 +190,7 @@ export function createGoalActivities(services: () => Promise<ControlActivityServ
158
190
  : (budgetBlocked?.pausedReason ?? "limits"),
159
191
  };
160
192
  },
161
- policy: {
162
- model: continuationModel,
163
- reasoningEffort: continuationReasoningEffort,
164
- latencyMode: continuationLatencyMode,
165
- ...(turnExecutionPolicy ? { turnExecutionPolicy } : {}),
166
- tools: withFirstPartyTools(settings, session.tools),
167
- sandboxBackend: session.sandboxBackend,
168
- },
193
+ policy: continuationPolicy,
169
194
  // Long-wait guidance is only given when `wait_for_input` is actually in this
170
195
  // session's effective first-party selection (the same source the worker
171
196
  // signs into the delegated token and the API uses to register tools), so
@@ -41,6 +41,8 @@ import {
41
41
  deferRetainedProcessReconciliation,
42
42
  forceDrainOverLimitViewerOnlyBoxes,
43
43
  listCreditBalancesByAccount,
44
+ countActiveUsers,
45
+ readCreditGrantTotals,
44
46
  readVerifiedSignupTrialSwitch,
45
47
  listLegacyModalCheckpointSlots,
46
48
  listLiveModalSandboxLeaseAttributions,
@@ -150,7 +152,9 @@ import { assertSandboxDrainInputTiming, sandboxDrainTiming } from "../sandbox-re
150
152
  import type { ControlActivityServices as ActivityServices } from "./types";
151
153
  import { reconcilePendingParentSystemUpdates } from "./parent-wake";
152
154
  import {
155
+ recordActiveUserGauges,
153
156
  recordCreditBalanceGauges,
157
+ recordCreditGrantGauges,
154
158
  recordCreditMicros,
155
159
  recordExpiredDrainingSandboxLeaseGauges,
156
160
  recordOpenSandboxKubernetesInventoryGauges,
@@ -2525,6 +2529,24 @@ async function refreshQueueLeaseAndCreditGauges(
2525
2529
  });
2526
2530
  }
2527
2531
  })(),
2532
+ (async () => {
2533
+ try {
2534
+ recordActiveUserGauges(observability, await countActiveUsers(db));
2535
+ } catch (error) {
2536
+ observability.warn("sandbox reaper: active-user gauge refresh failed", {
2537
+ error: error instanceof Error ? error.message : String(error),
2538
+ });
2539
+ }
2540
+ })(),
2541
+ (async () => {
2542
+ try {
2543
+ recordCreditGrantGauges(observability, await readCreditGrantTotals(db));
2544
+ } catch (error) {
2545
+ observability.warn("sandbox reaper: credit-grant gauge refresh failed", {
2546
+ error: error instanceof Error ? error.message : String(error),
2547
+ });
2548
+ }
2549
+ })(),
2528
2550
  (async () => {
2529
2551
  recordVerifiedSignupTrialDeploymentFlagGauge(
2530
2552
  observability,