@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
@@ -0,0 +1,9 @@
1
+ import type { BrowserDeadlineCheckpointTarget } from "@opengeni/db";
2
+ export declare function browserDeadlineCheckpointWorkflow(target: BrowserDeadlineCheckpointTarget): Promise<{
3
+ status: "skipped";
4
+ } | {
5
+ status: "suspended";
6
+ }>;
7
+ /** Runs from the existing lease-sweep dispatch. Inventory never performs provider
8
+ * I/O; each exact browser has an independent durable capture/cleanup child. */
9
+ export declare function browserDeadlineCheckpointSweepWorkflow(): Promise<void>;
@@ -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 {};
@@ -6,6 +6,7 @@ export { automationRunWorkflow, type AutomationRunWorkflowInput } from "./workfl
6
6
  export { knowledgeSourceSyncWake, knowledgeSourceSyncWorkflow, knowledgeSourceSyncWorkflowId, type KnowledgeSourceSyncWorkflowInput, } from "./workflows/knowledge-source-sync.js";
7
7
  export { sandboxDrainWorkflow, sandboxReaperMaintenanceWorkflow, sandboxReaperWorkflow, sandboxReaperWorkflowV2, } from "./workflows/sandbox-reaper.js";
8
8
  export { fileUploadReaperWorkflow } from "./workflows/file-upload-reaper.js";
9
+ export { browserDeadlineCheckpointWorkflow, browserDeadlineCheckpointSweepWorkflow, } from "./workflows/browser-deadline-checkpoint.js";
9
10
  export { siteAuthMaintenanceWorkflow } from "./workflows/site-auth-maintenance.js";
10
11
  export { videoGenerationWorkflow, videoGenerationWorkflowId, type VideoGenerationWorkflowInput, } from "./workflows/video-generation.js";
11
12
  export { sessionWorkflowWakeDispatcherWorkflow } from "./workflows/workflow-wake.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/worker-bundle",
3
- "version": "2.2.0",
3
+ "version": "2.2.1-canary.36927100590001",
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,25 +54,26 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@llamaindex/liteparse": "2.14.2",
57
- "@opengeni/agent-proto": "^0.6.0",
58
- "@opengeni/capabilities": "^0.3.4",
59
- "@opengeni/codemode": "^0.6.5",
60
- "@opengeni/codex": "^0.2.29",
61
- "@opengeni/config": "^3.1.1",
62
- "@opengeni/contracts": "^5.4.0",
63
- "@opengeni/core": "^5.0.0",
64
- "@opengeni/db": "^6.2.0",
65
- "@opengeni/documents": "^0.8.37",
66
- "@opengeni/events": "^0.4.35",
67
- "@opengeni/github": "^0.8.0",
68
- "@opengeni/jev": "^0.2.0",
69
- "@opengeni/network": "^0.3.1",
70
- "@opengeni/observability": "^0.8.35",
71
- "@opengeni/runtime": "^4.3.0",
72
- "@opengeni/sdk": "^7.4.0",
73
- "@opengeni/storage": "^0.2.136",
74
- "@opengeni/tool-gateway": "^0.1.16",
75
- "@opengeni/xai-subscription": "^0.1.4",
57
+ "@opengeni/agent-proto": "^0.6.1-canary.36927100590001",
58
+ "@opengeni/capabilities": "^0.3.5-canary.36927100590001",
59
+ "@opengeni/codemode": "^0.6.6-canary.36927100590001",
60
+ "@opengeni/codex": "^0.2.30-canary.36927100590001",
61
+ "@opengeni/config": "^3.2.0-canary.36927100590001",
62
+ "@opengeni/contracts": "^5.5.0-canary.36927100590001",
63
+ "@opengeni/core": "^5.0.1-canary.36927100590001",
64
+ "@opengeni/db": "^6.3.0-canary.36927100590001",
65
+ "@opengeni/documents": "^0.8.38-canary.36927100590001",
66
+ "@opengeni/events": "^0.4.36-canary.36927100590001",
67
+ "@opengeni/github": "^0.8.1-canary.36927100590001",
68
+ "@opengeni/interaction": "^0.4.44-canary.36927100590001",
69
+ "@opengeni/jev": "^0.2.1-canary.36927100590001",
70
+ "@opengeni/network": "^0.3.1-canary.36927100590001",
71
+ "@opengeni/observability": "^0.8.36-canary.36927100590001",
72
+ "@opengeni/runtime": "^4.4.0-canary.36927100590001",
73
+ "@opengeni/sdk": "^7.5.0-canary.36927100590001",
74
+ "@opengeni/storage": "^0.2.137-canary.36927100590001",
75
+ "@opengeni/tool-gateway": "^0.1.17-canary.36927100590001",
76
+ "@opengeni/xai-subscription": "^0.1.5-canary.36927100590001",
76
77
  "@temporalio/activity": "^1.17.0",
77
78
  "@temporalio/client": "^1.17.0",
78
79
  "@temporalio/worker": "^1.17.0",
@@ -1,10 +1,16 @@
1
1
  import { configuredStaticUsageLimits, type Settings } from "@opengeni/config";
2
2
  import { modelFundingForAdmission } from "@opengeni/core";
3
- import { getBillingBalance, isCodexBilledTurn, sumUsageQuantity } from "@opengeni/db";
3
+ import {
4
+ checkWorkspaceAllowance,
5
+ getBillingBalance,
6
+ isCodexBilledTurn,
7
+ sumUsageQuantity,
8
+ } from "@opengeni/db";
4
9
  import type { ControlActivityServices } from "./types";
5
10
 
6
11
  export type AgentRunAdmissionDenial =
7
12
  | "insufficient_credits"
13
+ | "allowance_exhausted"
8
14
  | "monthly_model_cost_limit"
9
15
  | "monthly_agent_run_limit";
10
16
 
@@ -16,6 +22,8 @@ export async function agentRunAdmissionDenial(
16
22
  workspaceId: string;
17
23
  model: string;
18
24
  requestedAgentRuns: number;
25
+ /** The accepted work's causal human, not the scheduler/service caller. */
26
+ initiatingHumanSubjectId?: string | null;
19
27
  },
20
28
  ): Promise<AgentRunAdmissionDenial | null> {
21
29
  const codexBilled = await isCodexBilledTurn({
@@ -46,6 +54,14 @@ export async function agentRunAdmissionDenial(
46
54
  if (balance.balanceMicros <= 0) return "insufficient_credits";
47
55
  }
48
56
  }
57
+ if (!externallyBilled) {
58
+ const refusal = await checkWorkspaceAllowance(services.db, {
59
+ accountId: input.accountId,
60
+ workspaceId: input.workspaceId,
61
+ subjectId: input.initiatingHumanSubjectId ?? null,
62
+ });
63
+ if (refusal) return refusal.code;
64
+ }
49
65
  if (
50
66
  services.settings.usageLimitsMode !== "static" &&
51
67
  services.settings.usageLimitsMode !== "managed"
@@ -1,4 +1,4 @@
1
- import { getBillingBalance, sumUsageQuantity } from "@opengeni/db";
1
+ import { checkWorkspaceAllowance, getBillingBalance, sumUsageQuantity } from "@opengeni/db";
2
2
  import {
3
3
  configuredStaticUsageLimits,
4
4
  resolveTurnExecutionPolicyV1,
@@ -8,6 +8,7 @@ import { selectCodexCredentialId } from "@opengeni/codex";
8
8
  import { directPersonalConnectionSubjectId } from "@opengeni/core";
9
9
  import type { TurnActivityServices as ActivityServices } from "../types";
10
10
  import {
11
+ type AllowanceExhaustedRefusal,
11
12
  type SessionEvent,
12
13
  type SessionTurn,
13
14
  type ToolAuthNeededPayload,
@@ -243,12 +244,65 @@ export class BudgetExhaustedError extends Error {
243
244
  constructor(
244
245
  message: string,
245
246
  readonly serializedRunState: string | null,
247
+ readonly allowance: AllowanceRefusal | null = null,
246
248
  ) {
247
249
  super(message);
248
250
  this.name = "BudgetExhaustedError";
249
251
  }
250
252
  }
251
253
 
254
+ export type AllowanceRefusal = AllowanceExhaustedRefusal;
255
+
256
+ /** Account allowance exhaustion is recoverable state, never a provider failure. */
257
+ export class AllowanceExhaustedError extends Error {
258
+ readonly code = "allowance_exhausted";
259
+
260
+ constructor(readonly refusal: AllowanceRefusal) {
261
+ super(refusal.message);
262
+ this.name = "AllowanceExhaustedError";
263
+ }
264
+ }
265
+
266
+ /** Revalidate between paid calls without replaying provider work or reserving credits. */
267
+ export async function ensureRunAllowedBetweenModelCalls(input: {
268
+ settings: Settings;
269
+ db: ActivityServices["db"];
270
+ accountId: string;
271
+ workspaceId: string;
272
+ isExternallyBilledTurn: boolean;
273
+ entitlements?: ActivityServices["entitlements"];
274
+ chargesOpenGeniCredits: boolean;
275
+ countsTowardTokenCap: boolean;
276
+ initiatingHumanSubjectId: string | null;
277
+ serializedRunState?: () => string | null;
278
+ }): Promise<void> {
279
+ try {
280
+ await ensureRunAllowed(
281
+ input.settings,
282
+ input.db,
283
+ input.accountId,
284
+ input.workspaceId,
285
+ input.isExternallyBilledTurn,
286
+ input.entitlements,
287
+ input.chargesOpenGeniCredits,
288
+ input.countsTowardTokenCap,
289
+ input.initiatingHumanSubjectId,
290
+ );
291
+ } catch (limitError) {
292
+ let serializedRunState: string | null = null;
293
+ try {
294
+ serializedRunState = input.serializedRunState?.() ?? null;
295
+ } catch {
296
+ // Durable history remains authoritative when the SDK state cannot serialize.
297
+ }
298
+ throw new BudgetExhaustedError(
299
+ limitError instanceof Error ? limitError.message : String(limitError),
300
+ serializedRunState,
301
+ limitError instanceof AllowanceExhaustedError ? limitError.refusal : null,
302
+ );
303
+ }
304
+ }
305
+
252
306
  // Exported for unit testing the external-billing bypass (codex-billing.test.ts); not
253
307
  // part of the activity surface. Takes the accepted policy's billing attribution and
254
308
  // the optional §7.5 P3 host `entitlements` port (when bound, its `admitRun` REPLACES
@@ -262,6 +316,7 @@ export async function ensureRunAllowed(
262
316
  entitlements?: ActivityServices["entitlements"],
263
317
  chargesOpenGeniCredits = !isExternallyBilledTurn,
264
318
  countsTowardTokenCap = !isExternallyBilledTurn,
319
+ initiatingHumanSubjectId: string | null = null,
265
320
  ): Promise<void> {
266
321
  // Upstream settlement and workspace-facing cost are independent. External
267
322
  // metering skips the token cap; free/subscription/workspace cost skips the
@@ -302,6 +357,14 @@ export async function ensureRunAllowed(
302
357
  throw new Error("insufficient OpenGeni credits");
303
358
  }
304
359
  }
360
+ if (chargesOpenGeniCredits) {
361
+ const refusal = await checkWorkspaceAllowance(db, {
362
+ accountId,
363
+ workspaceId,
364
+ subjectId: initiatingHumanSubjectId,
365
+ });
366
+ if (refusal) throw new AllowanceExhaustedError(refusal);
367
+ }
305
368
  if (settings.usageLimitsMode === "static" || settings.usageLimitsMode === "managed") {
306
369
  const limits = configuredStaticUsageLimits(settings);
307
370
  if (limits.maxMonthlyAgentRunsPerWorkspace) {
@@ -10,6 +10,7 @@ import {
10
10
  getSessionTurnForAttempt,
11
11
  ensureSessionReasoningConfiguration,
12
12
  ensureSessionSkillCatalog,
13
+ sessionHasToolRouterHistory,
13
14
  } from "@opengeni/db";
14
15
  import { recoveryAwareSessionInstructions } from "./recovery-warning";
15
16
  import {
@@ -52,7 +53,7 @@ import {
52
53
  } from "../image-generation-references";
53
54
  import { SandboxChannelAService } from "@opengeni/runtime/sandbox";
54
55
  import { sandboxRunAs } from "@opengeni/runtime";
55
- import { VideoGenerationRejectedResult } from "@opengeni/contracts";
56
+ import { VideoGenerationRejectedResult, resolveAgentToolFamilies } from "@opengeni/contracts";
56
57
 
57
58
  import {
58
59
  structuredToolTransportForTurn,
@@ -111,6 +112,7 @@ export type BuildTurnAgentDeps = {
111
112
  supportsImageInput: GovernanceModelOk["supportsImageInput"];
112
113
  agentHumanInputEnabled: GovernanceModelOk["agentHumanInputEnabled"];
113
114
  workspaceAgentInstructions: GovernanceModelOk["workspaceAgentInstructions"];
115
+ workspaceAgentIdentity: GovernanceModelOk["workspaceAgentIdentity"];
114
116
  workspaceGovernance: GovernanceModelOk["workspaceGovernance"];
115
117
  structuredWorkspacePolicyActive: GovernanceModelOk["structuredWorkspacePolicyActive"];
116
118
  workspaceMemory: GovernanceModelOk["workspaceMemory"];
@@ -173,6 +175,7 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
173
175
  supportsImageInput,
174
176
  agentHumanInputEnabled,
175
177
  workspaceAgentInstructions,
178
+ workspaceAgentIdentity,
176
179
  workspaceGovernance,
177
180
  structuredWorkspacePolicyActive,
178
181
  workspaceMemory,
@@ -284,7 +287,7 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
284
287
  // Never expose a paid image operation unless its permanent artifact can
285
288
  // be committed. Failing after provider execution would leave an
286
289
  // unrecoverable outcome-unknown operation with no user-visible image.
287
- if (!objectStorage) return {};
290
+ if (!objectStorage || !resolveAgentToolFamilies(session.agent).media) return {};
288
291
  if (nativeImageProviderBinding) {
289
292
  media.nativeImageGenerationRetention = {
290
293
  ...nativeImageProviderBinding,
@@ -405,7 +408,7 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
405
408
  videoGenerationPolicy.defaultModelId !== null &&
406
409
  videoGenerationPolicy.enabledModelIds.length > 0;
407
410
  let videoGenerationCredential: VideoGenerationCredentialLease | null = null;
408
- if (objectStorage && videoGenerationEnabled) {
411
+ if (objectStorage && videoGenerationEnabled && resolveAgentToolFamilies(session.agent).media) {
409
412
  if (videoGenerationPolicy.fundingSource === "opengeni_credits") {
410
413
  videoGenerationCredential = managedVideoGenerationCredentialLease(eventing.modelRunSettings);
411
414
  } else if (videoGenerationPolicy.fundingSource === "workspace_gateway") {
@@ -626,6 +629,13 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
626
629
  effort: turn.reasoningEffort,
627
630
  })
628
631
  : turn.reasoningEffort;
632
+ const toolRouterInHistory = session.agent
633
+ ? await sessionHasToolRouterHistory(db, {
634
+ accountId: input.accountId,
635
+ workspaceId: input.workspaceId,
636
+ sessionId: input.sessionId,
637
+ })
638
+ : false;
629
639
  const agent = (() => {
630
640
  const agentConstructionStartedAt = performance.now();
631
641
  let agentConstructionOutcome: "completed" | "failed" = "completed";
@@ -652,6 +662,7 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
652
662
  }
653
663
  : {}),
654
664
  ...(preparedTools.inputWaitYield ? { inputWaitYield: preparedTools.inputWaitYield } : {}),
665
+ ...(session.agent ? { agentConfig: session.agent, toolRouterInHistory } : {}),
655
666
  reasoningEffort: requestReasoningEffort,
656
667
  latencyMode: turnExecutionPolicy.latencyMode,
657
668
  ...(serviceTier ? { serviceTier } : {}),
@@ -757,6 +768,16 @@ export async function buildTurnAgent(deps: BuildTurnAgentDeps) {
757
768
  onRetainableSessionImageOutput: media.retainSessionImageAtToolBoundary,
758
769
  skillCatalog: deps.skillCatalog,
759
770
  skillCatalogInHistory: true,
771
+ // A session with an agent configuration composes the modular prompt
772
+ // (identity, base behavior, runtime mechanics, capability modules);
773
+ // its workspace identity survives instruction policies. Null keeps
774
+ // the legacy composition below byte-for-byte.
775
+ ...(session.agent
776
+ ? {
777
+ agentConfig: session.agent,
778
+ ...(workspaceAgentIdentity ? { workspaceAgentIdentity } : {}),
779
+ }
780
+ : {}),
760
781
  ...(!structuredWorkspacePolicyActive && workspaceAgentInstructions
761
782
  ? { instructionsTemplate: workspaceAgentInstructions }
762
783
  : {}),
@@ -56,12 +56,15 @@ 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,
62
63
  turnExecutionPolicyBillingIdentity,
63
64
  legacyTurnExecutionPolicyInput,
64
65
  ensureRunAllowed,
66
+ AllowanceExhaustedError,
67
+ type AllowanceRefusal,
65
68
  } from "./admission";
66
69
  import { providerRecoveryCountFromMetadata, isWorkerShutdownCancellation } from "./errors";
67
70
  import { throwIfTurnOperationCancelled, waitForTurnOperation } from "./sandbox-provision";
@@ -288,10 +291,21 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
288
291
  const codexAppsCredentialId = capabilitySettings.codexConnectedAppsEnabled
289
292
  ? await resolveCodexAppsCredentialIdForRun(db, input.workspaceId)
290
293
  : null;
291
- const policyForAbsent =
294
+ const candidatePolicy =
292
295
  claimedPolicy.kind === "valid"
293
296
  ? claimedPolicy.policy
294
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;
295
309
  const installedPolicy = await installOrReadTurnExecutionPolicyForAttempt(db, {
296
310
  accountId: input.accountId,
297
311
  workspaceId: input.workspaceId,
@@ -368,20 +382,27 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
368
382
  // bypass OpenGeni credit/token gates)
369
383
  // AND the optional host `entitlements` port (when bound, its admitRun replaces
370
384
  // the local credit read). Unset port → today's local-ledger path.
371
- await waitForTurnOperation(
372
- ensureRunAllowed(
373
- capabilitySettings,
374
- db,
375
- input.accountId,
376
- input.workspaceId,
377
- billingState.isExternallyBilledTurn,
378
- entitlements,
379
- billingState.chargesOpenGeniCredits,
380
- billingState.countsTowardTokenCap,
381
- ),
382
- cancellationSignal,
383
- undefined,
384
- );
385
+ let allowanceRefusal: AllowanceRefusal | null = null;
386
+ try {
387
+ await waitForTurnOperation(
388
+ ensureRunAllowed(
389
+ capabilitySettings,
390
+ db,
391
+ input.accountId,
392
+ input.workspaceId,
393
+ billingState.isExternallyBilledTurn,
394
+ entitlements,
395
+ billingState.chargesOpenGeniCredits,
396
+ billingState.countsTowardTokenCap,
397
+ turn.initiatingHumanSubjectId,
398
+ ),
399
+ cancellationSignal,
400
+ undefined,
401
+ );
402
+ } catch (error) {
403
+ if (!(error instanceof AllowanceExhaustedError)) throw error;
404
+ allowanceRefusal = error.refusal;
405
+ }
385
406
  // Setup (variableSet load, MCP connects, sandbox restore) does not
386
407
  // stream and so never observes cancellation on its own; these explicit
387
408
  // checks let a graceful shutdown checkpoint the turn before the worker is
@@ -514,6 +535,9 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
514
535
  ...(inputSettlement.suppressGoalContinuation !== undefined
515
536
  ? { suppressGoalContinuation: inputSettlement.suppressGoalContinuation }
516
537
  : {}),
538
+ ...(inputSettlement.allowanceGoalPause
539
+ ? { allowanceGoalPause: inputSettlement.allowanceGoalPause }
540
+ : {}),
517
541
  events: inputs,
518
542
  ...(runState ? { runState } : {}),
519
543
  ...(compactionRequestFailure ? { compactionRequestFailure } : {}),
@@ -575,6 +599,36 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
575
599
  durationSeconds: (performance.now() - activityStarted) / 1_000,
576
600
  });
577
601
  const turnStartSettlementStartedAt = performance.now();
602
+ if (allowanceRefusal) {
603
+ // Claim has frozen the exact human, but no provider or sandbox has started.
604
+ // Use the same terminal valve as a post-response stop, retaining a usable
605
+ // session and a visible typed refusal instead of manufacturing a failure.
606
+ if (
607
+ !(await eventing.settle({
608
+ events: [
609
+ { type: "usage.exhausted", payload: allowanceRefusal },
610
+ {
611
+ type: "turn.completed",
612
+ payload: {
613
+ output: "",
614
+ segmentLimit: "budget_exhausted",
615
+ ...allowanceRefusal,
616
+ },
617
+ },
618
+ { type: "session.status.changed", payload: { status: "idle" } },
619
+ ],
620
+ turnStatus: "completed",
621
+ sessionStatus: "idle",
622
+ activeTurnId: null,
623
+ allowanceGoalPause: { rationale: allowanceRefusal.message },
624
+ }))
625
+ ) {
626
+ return { exit: claimedResult({ status: "cancelled" }) };
627
+ }
628
+ control.turnMetricOutcome = "completed";
629
+ control.activityStatus = "idle";
630
+ return { exit: claimedResult({ status: "idle" }) };
631
+ }
578
632
  if (
579
633
  !(await eventing.settle({
580
634
  events: [
@@ -35,6 +35,17 @@ export async function createClaudeUsageObserver(
35
35
  .map(async (provider) => {
36
36
  const scope: Scope =
37
37
  provider.kind === "claude-subscription-workspace" ? "workspace" : "organization";
38
+ const binding = provider.anthropic?.credentialBinding;
39
+ if (binding && provider.apiKey)
40
+ return [
41
+ provider.id,
42
+ {
43
+ scope,
44
+ token: provider.apiKey,
45
+ expectedConnectionId: binding.connectionId,
46
+ expectedCredentialVersion: binding.credentialVersion,
47
+ },
48
+ ] as const;
38
49
  const credential = await readCredential(scope).catch(() => null);
39
50
  if (!credential || credential.token !== provider.apiKey) return null;
40
51
  return [
@@ -49,7 +60,7 @@ export async function createClaudeUsageObserver(
49
60
  }),
50
61
  );
51
62
  const captured = new Map(bindings.filter((binding) => binding !== null));
52
- return (providerId: string, response: Response) => {
63
+ const observe = (providerId: string, response: Response) => {
53
64
  const binding = captured.get(providerId);
54
65
  if (!binding) return;
55
66
  const { scope, ...identity } = binding;
@@ -76,4 +87,25 @@ export async function createClaudeUsageObserver(
76
87
  : {}),
77
88
  });
78
89
  };
90
+ return Object.assign(observe, {
91
+ binding(providerId: string) {
92
+ return captured.get(providerId);
93
+ },
94
+ renew(
95
+ providerId: string,
96
+ credential: {
97
+ token: string;
98
+ connectionId: string;
99
+ credentialVersion: number;
100
+ },
101
+ ) {
102
+ const binding = captured.get(providerId);
103
+ if (
104
+ binding &&
105
+ binding.expectedConnectionId === credential.connectionId &&
106
+ binding.expectedCredentialVersion === credential.credentialVersion
107
+ )
108
+ captured.set(providerId, { ...binding, token: credential.token });
109
+ },
110
+ });
79
111
  }
@@ -4,7 +4,9 @@ import {
4
4
  appendSessionInstructions,
5
5
  appendWorkspaceGovernance,
6
6
  appendWorkspaceMemory,
7
+ agentPromptResourcesFor,
7
8
  composeAgentInstructions,
9
+ inspectPersistentAgentInstructions,
8
10
  requestRemoteCompactionV2,
9
11
  preparedCompactionRequest,
10
12
  queuePreparedCompaction,
@@ -86,6 +88,7 @@ export type CompactionPrepDeps = {
86
88
  turnExecutionPolicy: ClaimTurnOk["turnExecutionPolicy"];
87
89
  resolvedModel: GovernanceModelOk["resolvedModel"];
88
90
  workspaceAgentInstructions: GovernanceModelOk["workspaceAgentInstructions"];
91
+ workspaceAgentIdentity: GovernanceModelOk["workspaceAgentIdentity"];
89
92
  workspaceGovernance: GovernanceModelOk["workspaceGovernance"];
90
93
  structuredWorkspacePolicyActive: GovernanceModelOk["structuredWorkspacePolicyActive"];
91
94
  workspaceMemory: GovernanceModelOk["workspaceMemory"];
@@ -131,6 +134,59 @@ export type PostAgentCompactionOutcome =
131
134
  | { exit: RunAgentTurnResult }
132
135
  | { ok: PostAgentCompactionOk };
133
136
 
137
+ /**
138
+ * System instructions for the standalone (chat-provider) compaction request.
139
+ * A session with an agent configuration uses the same modular composer and
140
+ * module selection as its turns; a legacy session keeps the historical
141
+ * persona + CORE + governance + session + memory string byte-for-byte.
142
+ */
143
+ export function standaloneCompactionInstructions(input: {
144
+ settings: Settings;
145
+ session: Pick<ClaimTurnOk["session"], "agent" | "instructions" | "resources">;
146
+ workspaceAgentInstructions: string | null | undefined;
147
+ workspaceAgentIdentity: string | null;
148
+ workspaceGovernance: string | null | undefined;
149
+ structuredWorkspacePolicyActive: boolean;
150
+ workspaceMemory: string | null | undefined;
151
+ rig: { name: string; version: number } | undefined;
152
+ }): string {
153
+ if (input.session.agent) {
154
+ return inspectPersistentAgentInstructions(input.settings, {
155
+ agentConfig: input.session.agent,
156
+ ...(input.workspaceAgentIdentity
157
+ ? { workspaceAgentIdentity: input.workspaceAgentIdentity }
158
+ : {}),
159
+ ...(input.workspaceGovernance ? { workspaceGovernance: input.workspaceGovernance } : {}),
160
+ ...(input.workspaceMemory ? { workspaceMemory: input.workspaceMemory } : {}),
161
+ ...(input.session.instructions ? { sessionInstructions: input.session.instructions } : {}),
162
+ ...(input.rig ? { rig: input.rig } : {}),
163
+ // Session-level facts only: per-turn attachments do not apply to a
164
+ // standalone compaction request.
165
+ agentPromptResources: agentPromptResourcesFor(
166
+ input.settings,
167
+ input.session.resources.filter((resource) => resource.kind !== "file"),
168
+ input.rig ? { rig: input.rig } : {},
169
+ ),
170
+ }).composed;
171
+ }
172
+ return appendWorkspaceMemory(
173
+ appendSessionInstructions(
174
+ appendWorkspaceGovernance(
175
+ composeAgentInstructions(
176
+ input.structuredWorkspacePolicyActive
177
+ ? input.settings.agentInstructionsTemplate
178
+ : (input.workspaceAgentInstructions ?? input.settings.agentInstructionsTemplate),
179
+ undefined,
180
+ input.rig,
181
+ ),
182
+ input.workspaceGovernance ?? undefined,
183
+ ),
184
+ input.session.instructions ?? undefined,
185
+ ),
186
+ input.workspaceMemory ?? undefined,
187
+ );
188
+ }
189
+
134
190
  export async function prepareCompaction(deps: CompactionPrepDeps): Promise<CompactionPrepOutcome> {
135
191
  const {
136
192
  input,
@@ -155,6 +211,7 @@ export async function prepareCompaction(deps: CompactionPrepDeps): Promise<Compa
155
211
  turnExecutionPolicy,
156
212
  resolvedModel,
157
213
  workspaceAgentInstructions,
214
+ workspaceAgentIdentity,
158
215
  workspaceGovernance,
159
216
  structuredWorkspacePolicyActive,
160
217
  workspaceMemory,
@@ -334,22 +391,16 @@ export async function prepareCompaction(deps: CompactionPrepDeps): Promise<Compa
334
391
  !remoteV2CompactionNeedsAgentPrefix &&
335
392
  !portableResponsesNeedsAgentPrefix
336
393
  ) {
337
- const compactionInstructions = appendWorkspaceMemory(
338
- appendSessionInstructions(
339
- appendWorkspaceGovernance(
340
- composeAgentInstructions(
341
- structuredWorkspacePolicyActive
342
- ? eventing.modelRunSettings.agentInstructionsTemplate
343
- : (workspaceAgentInstructions ?? eventing.modelRunSettings.agentInstructionsTemplate),
344
- undefined,
345
- rigVersion && rigName ? { name: rigName, version: rigVersion.version } : undefined,
346
- ),
347
- workspaceGovernance ?? undefined,
348
- ),
349
- session.instructions ?? undefined,
350
- ),
351
- workspaceMemory ?? undefined,
352
- );
394
+ const compactionInstructions = standaloneCompactionInstructions({
395
+ settings: eventing.modelRunSettings,
396
+ session,
397
+ workspaceAgentInstructions,
398
+ workspaceAgentIdentity,
399
+ workspaceGovernance,
400
+ structuredWorkspacePolicyActive,
401
+ workspaceMemory,
402
+ rig: rigVersion && rigName ? { name: rigName, version: rigVersion.version } : undefined,
403
+ });
353
404
  const requested = await isSessionCompactionRequested(db, input.workspaceId, input.sessionId);
354
405
  let outcome: Awaited<ReturnType<typeof maybeCompactContext>> | null = null;
355
406
  if (requested) {
@@ -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
+ }