@opengeni/worker-bundle 2.2.0 → 2.2.1-canary.36919050519001

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 (74) 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/final-reply.d.ts +18 -0
  7. package/dist/activities/agent-turn/governance-model.d.ts +6 -0
  8. package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
  9. package/dist/activities/agent-turn/run-credentials.d.ts +2 -0
  10. package/dist/activities/agent-turn/sandbox-establish.d.ts +1 -0
  11. package/dist/activities/agent-turn/session-title.d.ts +3 -1
  12. package/dist/activities/agent-turn/stream-attempt.d.ts +1 -0
  13. package/dist/activities/agent-turn/turn-context.d.ts +1 -0
  14. package/dist/activities/automations.d.ts +3 -1
  15. package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
  16. package/dist/activities/goals.d.ts +8 -0
  17. package/dist/activities/run-credentials.d.ts +4 -2
  18. package/dist/activities/types.d.ts +1 -1
  19. package/dist/{activities-control-MHNA3CYV.js → activities-control-55ECL47G.js} +403 -116
  20. package/dist/activities-control-55ECL47G.js.map +1 -0
  21. package/dist/activities-control.d.ts +30 -0
  22. package/dist/{activities-turn-FQ5OO2WW.js → activities-turn-T7Y3BWYO.js} +678 -246
  23. package/dist/activities-turn-T7Y3BWYO.js.map +1 -0
  24. package/dist/activities.d.ts +30 -0
  25. package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
  26. package/dist/{chunk-RZAAGRFQ.js → chunk-OWNLATJL.js} +189 -80
  27. package/dist/chunk-OWNLATJL.js.map +1 -0
  28. package/dist/index.d.ts +15 -0
  29. package/dist/index.js +45 -23
  30. package/dist/index.js.map +1 -1
  31. package/dist/sandbox-resume.d.ts +4 -1
  32. package/dist/workflow-bundle.js +72 -10
  33. package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
  34. package/dist/workflows.d.ts +1 -0
  35. package/package.json +21 -20
  36. package/src/activities/agent-run-admission.ts +17 -1
  37. package/src/activities/agent-turn/admission.ts +64 -1
  38. package/src/activities/agent-turn/agent-build.ts +24 -3
  39. package/src/activities/agent-turn/claim.ts +56 -14
  40. package/src/activities/agent-turn/claude-usage-observer.ts +33 -1
  41. package/src/activities/agent-turn/compaction-prep.ts +67 -16
  42. package/src/activities/agent-turn/errors.ts +55 -0
  43. package/src/activities/agent-turn/failure-settlement.ts +26 -6
  44. package/src/activities/agent-turn/final-reply.ts +46 -0
  45. package/src/activities/agent-turn/governance-model.ts +12 -0
  46. package/src/activities/agent-turn/model-call-admission.ts +94 -0
  47. package/src/activities/agent-turn/run-credentials.ts +15 -3
  48. package/src/activities/agent-turn/run.ts +43 -5
  49. package/src/activities/agent-turn/sandbox-establish.ts +9 -5
  50. package/src/activities/agent-turn/session-title.ts +8 -2
  51. package/src/activities/agent-turn/stream-attempt.ts +144 -38
  52. package/src/activities/agent-turn/tool-environment.ts +51 -39
  53. package/src/activities/agent-turn/turn-context.ts +1 -0
  54. package/src/activities/automations.ts +55 -3
  55. package/src/activities/browser-deadline-checkpoint.ts +279 -0
  56. package/src/activities/capabilities.ts +52 -14
  57. package/src/activities/goals.ts +67 -95
  58. package/src/activities/knowledge-indexing.ts +29 -0
  59. package/src/activities/open-suffix-resume.ts +2 -1
  60. package/src/activities/run-credentials.ts +22 -16
  61. package/src/activities/scheduled-tasks.ts +83 -13
  62. package/src/activities/types.ts +1 -0
  63. package/src/activities/workspace-credential-provider.ts +12 -0
  64. package/src/activities-control.ts +2 -0
  65. package/src/activities.ts +3 -0
  66. package/src/browser-deadline-checkpoint-contract.ts +5 -0
  67. package/src/index.ts +53 -25
  68. package/src/sandbox-resume.ts +23 -8
  69. package/src/sandbox-routing.ts +4 -2
  70. package/src/workflows/browser-deadline-checkpoint.ts +50 -0
  71. package/src/workflows.ts +4 -0
  72. package/dist/activities-control-MHNA3CYV.js.map +0 -1
  73. package/dist/activities-turn-FQ5OO2WW.js.map +0 -1
  74. 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>;
@@ -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.36919050519001",
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.36919050519001",
58
+ "@opengeni/capabilities": "^0.3.5-canary.36919050519001",
59
+ "@opengeni/codemode": "^0.6.6-canary.36919050519001",
60
+ "@opengeni/codex": "^0.2.30-canary.36919050519001",
61
+ "@opengeni/config": "^3.2.0-canary.36919050519001",
62
+ "@opengeni/contracts": "^5.5.0-canary.36919050519001",
63
+ "@opengeni/core": "^5.0.1-canary.36919050519001",
64
+ "@opengeni/db": "^6.3.0-canary.36919050519001",
65
+ "@opengeni/documents": "^0.8.38-canary.36919050519001",
66
+ "@opengeni/events": "^0.4.36-canary.36919050519001",
67
+ "@opengeni/github": "^0.8.1-canary.36919050519001",
68
+ "@opengeni/interaction": "^0.4.44-canary.36919050519001",
69
+ "@opengeni/jev": "^0.2.1-canary.36919050519001",
70
+ "@opengeni/network": "^0.3.1-canary.36919050519001",
71
+ "@opengeni/observability": "^0.8.36-canary.36919050519001",
72
+ "@opengeni/runtime": "^4.4.0-canary.36919050519001",
73
+ "@opengeni/sdk": "^7.5.0-canary.36919050519001",
74
+ "@opengeni/storage": "^0.2.137-canary.36919050519001",
75
+ "@opengeni/tool-gateway": "^0.1.17-canary.36919050519001",
76
+ "@opengeni/xai-subscription": "^0.1.5-canary.36919050519001",
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
  : {}),
@@ -62,6 +62,8 @@ import {
62
62
  turnExecutionPolicyBillingIdentity,
63
63
  legacyTurnExecutionPolicyInput,
64
64
  ensureRunAllowed,
65
+ AllowanceExhaustedError,
66
+ type AllowanceRefusal,
65
67
  } from "./admission";
66
68
  import { providerRecoveryCountFromMetadata, isWorkerShutdownCancellation } from "./errors";
67
69
  import { throwIfTurnOperationCancelled, waitForTurnOperation } from "./sandbox-provision";
@@ -368,20 +370,27 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
368
370
  // bypass OpenGeni credit/token gates)
369
371
  // AND the optional host `entitlements` port (when bound, its admitRun replaces
370
372
  // 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
- );
373
+ let allowanceRefusal: AllowanceRefusal | null = null;
374
+ try {
375
+ await waitForTurnOperation(
376
+ ensureRunAllowed(
377
+ capabilitySettings,
378
+ db,
379
+ input.accountId,
380
+ input.workspaceId,
381
+ billingState.isExternallyBilledTurn,
382
+ entitlements,
383
+ billingState.chargesOpenGeniCredits,
384
+ billingState.countsTowardTokenCap,
385
+ turn.initiatingHumanSubjectId,
386
+ ),
387
+ cancellationSignal,
388
+ undefined,
389
+ );
390
+ } catch (error) {
391
+ if (!(error instanceof AllowanceExhaustedError)) throw error;
392
+ allowanceRefusal = error.refusal;
393
+ }
385
394
  // Setup (variableSet load, MCP connects, sandbox restore) does not
386
395
  // stream and so never observes cancellation on its own; these explicit
387
396
  // checks let a graceful shutdown checkpoint the turn before the worker is
@@ -514,6 +523,9 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
514
523
  ...(inputSettlement.suppressGoalContinuation !== undefined
515
524
  ? { suppressGoalContinuation: inputSettlement.suppressGoalContinuation }
516
525
  : {}),
526
+ ...(inputSettlement.allowanceGoalPause
527
+ ? { allowanceGoalPause: inputSettlement.allowanceGoalPause }
528
+ : {}),
517
529
  events: inputs,
518
530
  ...(runState ? { runState } : {}),
519
531
  ...(compactionRequestFailure ? { compactionRequestFailure } : {}),
@@ -575,6 +587,36 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
575
587
  durationSeconds: (performance.now() - activityStarted) / 1_000,
576
588
  });
577
589
  const turnStartSettlementStartedAt = performance.now();
590
+ if (allowanceRefusal) {
591
+ // Claim has frozen the exact human, but no provider or sandbox has started.
592
+ // Use the same terminal valve as a post-response stop, retaining a usable
593
+ // session and a visible typed refusal instead of manufacturing a failure.
594
+ if (
595
+ !(await eventing.settle({
596
+ events: [
597
+ { type: "usage.exhausted", payload: allowanceRefusal },
598
+ {
599
+ type: "turn.completed",
600
+ payload: {
601
+ output: "",
602
+ segmentLimit: "budget_exhausted",
603
+ ...allowanceRefusal,
604
+ },
605
+ },
606
+ { type: "session.status.changed", payload: { status: "idle" } },
607
+ ],
608
+ turnStatus: "completed",
609
+ sessionStatus: "idle",
610
+ activeTurnId: null,
611
+ allowanceGoalPause: { rationale: allowanceRefusal.message },
612
+ }))
613
+ ) {
614
+ return { exit: claimedResult({ status: "cancelled" }) };
615
+ }
616
+ control.turnMetricOutcome = "completed";
617
+ control.activityStatus = "idle";
618
+ return { exit: claimedResult({ status: "idle" }) };
619
+ }
578
620
  if (
579
621
  !(await eventing.settle({
580
622
  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) {