@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
@@ -18,8 +18,10 @@ import {
18
18
  isMcpRequestTimeoutError,
19
19
  isMcpTransportConnectivityError,
20
20
  isModalTaskExecStartPreDispatchUnavailableError,
21
+ isModalCommandStartOutcomeUnknownError,
21
22
  isRoutingMutationOutcomeUnknownError,
22
23
  RoutingWorkspaceRootChangedError,
24
+ ResponsesStreamingTerminalError,
23
25
  SandboxMaterializationVerificationError,
24
26
  materializationVerificationDiagnostic,
25
27
  type MaterializationVerificationDiagnostic,
@@ -729,6 +731,7 @@ export function shouldRecoverCompactionProviderFailure(error: unknown): boolean
729
731
  export function classifyContextWindowOverflowError(
730
732
  error: unknown,
731
733
  ): { message: string; code?: string; detail?: string } | null {
734
+ if (isProviderSafetyRefusal(error)) return null;
732
735
  const fields = collectErrorStrings(error);
733
736
  const matched = fields.find(
734
737
  (value) =>
@@ -795,6 +798,9 @@ export function collectErrorStrings(value: unknown, seen = new WeakSet<object>()
795
798
  }
796
799
  seen.add(value);
797
800
  const out: string[] = [];
801
+ // This diagnostic is already provider-owned and byte-bounded. Do not widen
802
+ // generic detail traversal to arbitrary application payloads.
803
+ if (value instanceof ResponsesStreamingTerminalError) out.push(value.detail);
798
804
  const record = value as Record<string, unknown>;
799
805
  for (const key of ["message", "code", "type", "name", "param"]) {
800
806
  const field = record[key];
@@ -857,6 +863,9 @@ export function isExactStatuslessUpstreamConnectivityMessage(message: string): b
857
863
  }
858
864
 
859
865
  function providerSafetyRefusalDiagnostic(error: unknown): string | undefined {
866
+ if (error instanceof ResponsesStreamingTerminalError) {
867
+ return error.category === "safety" ? error.detail : undefined;
868
+ }
860
869
  return collectErrorStrings(error).find(
861
870
  (value) =>
862
871
  /^(?:content_policy_violation|content_filter|safety_violation|bio_policy|cyber_policy)$/.test(
@@ -870,6 +879,9 @@ function isProviderSafetyRefusal(error: unknown): boolean {
870
879
  }
871
880
 
872
881
  export function isTransientProviderError(error: unknown): boolean {
882
+ if (error instanceof ResponsesStreamingTerminalError) {
883
+ return error.category === "unavailable";
884
+ }
873
885
  // A semantic refusal can arrive inside a 5xx transport envelope.
874
886
  if (isProviderSafetyRefusal(error)) return false;
875
887
  const status =
@@ -1068,6 +1080,41 @@ function baseAgentRunFailurePayload(
1068
1080
  detail: safetyRefusalDiagnostic,
1069
1081
  };
1070
1082
  }
1083
+ if (error instanceof ResponsesStreamingTerminalError) {
1084
+ const quota =
1085
+ error.category === "unknown" || error.category === "rate_limit"
1086
+ ? classifyProviderQuotaExhaustionError({
1087
+ code: error.code,
1088
+ error: { code: error.code, type: error.type, message: error.detail },
1089
+ retryAfterSeconds: error.retryAfterSeconds,
1090
+ })
1091
+ : null;
1092
+ if (quota) {
1093
+ return {
1094
+ error: providerQuotaExhaustedMessage(quota.scope),
1095
+ code: PROVIDER_QUOTA_EXHAUSTED_CODE,
1096
+ retryable: false,
1097
+ quotaScope: quota.scope,
1098
+ detail: error.detail,
1099
+ };
1100
+ }
1101
+ return {
1102
+ error:
1103
+ error.category === "rate_limit"
1104
+ ? "Model provider rate limit hit. Try again in a minute or lower the reasoning effort."
1105
+ : error.category === "unavailable"
1106
+ ? "The model provider is temporarily unavailable. The same turn will retry after a short delay."
1107
+ : "The model provider rejected the response. Automatic retries stopped.",
1108
+ code:
1109
+ error.category === "rate_limit"
1110
+ ? "provider_rate_limited"
1111
+ : error.category === "unavailable"
1112
+ ? "provider_unavailable"
1113
+ : "provider_request_rejected",
1114
+ retryable: error.category === "rate_limit" || error.category === "unavailable",
1115
+ detail: error.detail,
1116
+ };
1117
+ }
1071
1118
  const message = error instanceof Error ? error.message : String(error);
1072
1119
  const status =
1073
1120
  typeof error === "object" && error !== null
@@ -1106,6 +1153,14 @@ function baseAgentRunFailurePayload(
1106
1153
  retryable: true,
1107
1154
  };
1108
1155
  }
1156
+ if (isModalCommandStartOutcomeUnknownError(error)) {
1157
+ return {
1158
+ error:
1159
+ "A managed sandbox command may have started, but its acknowledgement was lost. Automatic replay is disabled; inspect the sandbox state before retrying.",
1160
+ code: "sandbox_command_start_outcome_unknown",
1161
+ retryable: false,
1162
+ };
1163
+ }
1109
1164
  if (
1110
1165
  !isRoutingMutationOutcomeUnknownError(error) &&
1111
1166
  isModalTaskExecStartPreDispatchUnavailableError(error)
@@ -16,7 +16,10 @@ import {
16
16
  type CodexLeaseAccountStatus,
17
17
  } from "@opengeni/db";
18
18
  import { publishDurableSessionEvents } from "@opengeni/events";
19
- import { maxTurnsExceededRunState } from "@opengeni/runtime";
19
+ import {
20
+ maxTurnsExceededRunState,
21
+ isModalTaskExecStartPreDispatchUnavailableError,
22
+ } from "@opengeni/runtime";
20
23
  import { ApplicationFailure, CancelledFailure } from "@temporalio/activity";
21
24
  import {
22
25
  authoritativeCodexCapacityResetAt,
@@ -1536,12 +1539,16 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1536
1539
  if (
1537
1540
  !(await eventing.settle!({
1538
1541
  events: [
1542
+ ...(error.allowance
1543
+ ? [{ type: "usage.exhausted" as const, payload: error.allowance }]
1544
+ : []),
1539
1545
  {
1540
1546
  type: "turn.completed",
1541
1547
  payload: {
1542
1548
  output: "",
1543
1549
  segmentLimit: "budget_exhausted",
1544
1550
  detail: error.message,
1551
+ ...(error.allowance ?? {}),
1545
1552
  },
1546
1553
  },
1547
1554
  { type: "session.status.changed", payload: { status: "idle" } },
@@ -1549,6 +1556,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1549
1556
  turnStatus: "completed",
1550
1557
  sessionStatus: "idle",
1551
1558
  activeTurnId: null,
1559
+ ...(error.allowance ? { allowanceGoalPause: { rationale: error.allowance.message } } : {}),
1552
1560
  }))
1553
1561
  ) {
1554
1562
  return claimedResult({ status: "cancelled" });
@@ -1655,6 +1663,14 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1655
1663
  !!attempt.turnId &&
1656
1664
  !!attempt.triggerEventId &&
1657
1665
  attempt.executionGeneration > 0;
1666
+ const earlyCommandStartUnavailable =
1667
+ isModalTaskExecStartPreDispatchUnavailableError(error) &&
1668
+ !attempt.modelRequestStarted &&
1669
+ !eventing.turnStartedPublished &&
1670
+ !!attempt.turnId &&
1671
+ !!attempt.triggerEventId &&
1672
+ attempt.executionGeneration > 0;
1673
+ const earlyRecoverableSetup = earlyDefinitionMismatch || earlyCommandStartUnavailable;
1658
1674
  let failure = (
1659
1675
  earlyDefinitionMismatch
1660
1676
  ? { error: error.message, code: error.code, retryable: true }
@@ -1665,7 +1681,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1665
1681
  ) as ReturnType<typeof agentRunFailurePayload>;
1666
1682
  if (
1667
1683
  attempt.turnId &&
1668
- (earlyDefinitionMismatch ||
1684
+ (earlyRecoverableSetup ||
1669
1685
  (failure.retryable && eventing.publish && eventing.turnStartedPublished))
1670
1686
  ) {
1671
1687
  const nextProviderRecoveryCount = attempt.providerRecoveryCount + 1;
@@ -1676,7 +1692,7 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1676
1692
  });
1677
1693
  try {
1678
1694
  if (recoveryResult.status === "recovering") {
1679
- if (!earlyDefinitionMismatch) {
1695
+ if (!earlyRecoverableSetup) {
1680
1696
  await flushRuntimeBatcher();
1681
1697
  await historySink.reconcileConversationTruth({ requireDurable: true });
1682
1698
  }
@@ -1707,15 +1723,19 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
1707
1723
  return claimedResult(recoveryResult);
1708
1724
  }
1709
1725
  failure = providerRecoveryExhaustedFailure(failure, recoveryResult);
1710
- if (earlyDefinitionMismatch) {
1726
+ if (earlyRecoverableSetup) {
1711
1727
  // Setup has no eventing sink yet. Carry only the fixed, safe diagnostic
1712
1728
  // through Temporal into exact-attempt workflow failure settlement.
1713
1729
  control.activityStatus = "failed";
1714
1730
  control.turnMetricOutcome = "failed";
1715
1731
  control.activityError = error;
1716
1732
  throw ApplicationFailure.create({
1717
- message: `${error.message}. Automatic same-turn configuration recovery exhausted after ${recoveryResult.providerRecoveryCount} retries.`,
1718
- type: "TurnExecutionPolicyDefinitionMismatchError",
1733
+ message: earlyDefinitionMismatch
1734
+ ? `${error.message}. Automatic same-turn configuration recovery exhausted after ${recoveryResult.providerRecoveryCount} retries.`
1735
+ : failure.error,
1736
+ type: earlyDefinitionMismatch
1737
+ ? "TurnExecutionPolicyDefinitionMismatchError"
1738
+ : "SandboxCommandStartUnavailableError",
1719
1739
  nonRetryable: true,
1720
1740
  });
1721
1741
  }
@@ -0,0 +1,46 @@
1
+ /** One durable, same-logical-turn handoff, not a retry of model/tool effects. */
2
+ export function finalReplyNudge(turnId: string) {
3
+ return {
4
+ type: "message" as const,
5
+ role: "developer" as const,
6
+ content: [
7
+ {
8
+ type: "input_text" as const,
9
+ text: `[Runtime final-reply handoff for turn ${turnId}]\nYour final user-facing answer was empty. Reply to the user now with the requested deliverable or a concise outcome and its retained artifact link. Goal evidence is a short ledger proof, not the answer, and goal_complete does not deliver a chat reply. Use the completed work and tool results already in history; do not repeat completed tools or restart the task. If this is a late child result after goal completion, integrate material new findings and deliver any still-missing handoff. Do not change the completed goal or invent a result.`,
10
+ },
11
+ ],
12
+ };
13
+ }
14
+
15
+ export function hasFinalReplyNudge(history: readonly unknown[], turnId: string): boolean {
16
+ const text = finalReplyNudge(turnId).content[0]!.text;
17
+ return history.some((item) => {
18
+ const message = item as { role?: unknown; content?: unknown } | null;
19
+ return (
20
+ message?.role === "developer" &&
21
+ Array.isArray(message.content) &&
22
+ message.content.some(
23
+ (part: { type?: unknown; text?: unknown }) =>
24
+ part?.type === "input_text" && part.text === text,
25
+ )
26
+ );
27
+ });
28
+ }
29
+
30
+ export function needsFinalReply(input: {
31
+ output: unknown;
32
+ inputWaitYielded: boolean;
33
+ interrupted: boolean;
34
+ maintenance: boolean;
35
+ toolsExecuted: boolean;
36
+ completedGoal: boolean;
37
+ }): boolean {
38
+ return (
39
+ typeof input.output === "string" &&
40
+ input.output.trim().length === 0 &&
41
+ !input.inputWaitYielded &&
42
+ !input.interrupted &&
43
+ !input.maintenance &&
44
+ (input.toolsExecuted || input.completedGoal)
45
+ );
46
+ }
@@ -48,6 +48,7 @@ import { WorkspaceModelPolicyBlockedError } from "@opengeni/runtime";
48
48
  import {
49
49
  evaluateWorkspaceModelPolicy,
50
50
  resolveWorkspaceAgentHumanInputEnabled,
51
+ resolveWorkspaceDefaultAgentIdentity,
51
52
  type MediaGenerationResult,
52
53
  } from "@opengeni/contracts";
53
54
  import { codeSearchEnabledForTurn } from "@opengeni/contracts/code-search";
@@ -97,6 +98,12 @@ export type GovernanceModelOk = {
97
98
  /** Deployment and workspace allow the Jev-backed code_search tool. */
98
99
  codeSearchEnabled: boolean;
99
100
  workspaceAgentInstructions: string | null | undefined;
101
+ /**
102
+ * Modular composer identity tier: the explicit workspace default identity,
103
+ * else the frozen legacy persona without `{{core}}`. Never dropped by
104
+ * instruction policies (only read for sessions with an agent configuration).
105
+ */
106
+ workspaceAgentIdentity: string | null;
100
107
  workspaceGovernance: ReturnType<typeof renderWorkspaceGovernanceContext>;
101
108
  structuredWorkspacePolicyActive: boolean;
102
109
  workspaceMemory: string | null | undefined;
@@ -247,6 +254,10 @@ export async function prepareGovernanceAndModel(
247
254
  );
248
255
  const contextSelection = await resolveCompanyBrainContextSelection(db, governanceClaims);
249
256
  const workspaceAgentInstructions = contextSelection.legacyWorkspaceInstructions;
257
+ const workspaceAgentIdentity = resolveWorkspaceDefaultAgentIdentity(
258
+ workspace.settings,
259
+ workspaceAgentInstructions,
260
+ ).identity;
250
261
  const memoryPromptMode = contextSelection.receipt.memoryPromptMode;
251
262
  assertWorkspaceHumanInputAllowed(agentHumanInputEnabled, "resume", humanInputResume !== null);
252
263
  const companyProfileIncluded = contextSelection.receipt.companyProfileIncluded;
@@ -482,6 +493,7 @@ export async function prepareGovernanceAndModel(
482
493
  agentHumanInputEnabled,
483
494
  codeSearchEnabled,
484
495
  workspaceAgentInstructions,
496
+ workspaceAgentIdentity,
485
497
  workspaceGovernance,
486
498
  structuredWorkspacePolicyActive,
487
499
  workspaceMemory,
@@ -0,0 +1,94 @@
1
+ import type { StreamEvent } from "@openai/agents";
2
+
3
+ /** SDK producer/consumer rendezvous. Usage stays on the sole consumer writer. */
4
+ export function createModelCallAdmission(input: {
5
+ signal: AbortSignal;
6
+ admit: () => Promise<void>;
7
+ }) {
8
+ type Receipt = {
9
+ promise: Promise<void>;
10
+ resolve: () => void;
11
+ reject: (error: unknown) => void;
12
+ };
13
+ const receipts = new WeakMap<object, Receipt>();
14
+ const responseReceipts = new Map<string, Receipt>();
15
+ const pending = new Set<Receipt>();
16
+ const responseId = (event: unknown): string | null => {
17
+ const id = (event as { response?: { id?: unknown } } | null)?.response?.id;
18
+ return typeof id === "string" && id.length > 0 ? id : null;
19
+ };
20
+ let latest: Promise<void> = Promise.resolve();
21
+ let failure: unknown;
22
+ let failed = false;
23
+ let rejectHalted!: (error: unknown) => void;
24
+ const halted = new Promise<never>((_, reject) => {
25
+ rejectHalted = reject;
26
+ });
27
+ void halted.catch(() => undefined);
28
+ const fail = (error: unknown) => {
29
+ if (failed) return;
30
+ failed = true;
31
+ failure = error;
32
+ rejectHalted(error);
33
+ for (const receipt of pending) receipt.reject(error);
34
+ pending.clear();
35
+ responseReceipts.clear();
36
+ };
37
+ const abort = () => fail(input.signal.reason ?? new Error("Model stream cancelled"));
38
+ input.signal.addEventListener("abort", abort, { once: true });
39
+ if (input.signal.aborted) abort();
40
+ return {
41
+ beforeModelRequest: async () => {
42
+ await Promise.race([latest, halted]);
43
+ if (failed) throw failure;
44
+ // Even a settled predecessor does not grant admission to the next call.
45
+ await Promise.race([input.admit(), halted]);
46
+ if (failed) throw failure;
47
+ },
48
+ onModelResponse: (event: StreamEvent): Promise<void> => {
49
+ if (failed) return Promise.reject(failure);
50
+ const id = responseId(event);
51
+ const existing =
52
+ receipts.get(event) ??
53
+ (id ? responseReceipts.get(id) : undefined) ??
54
+ (!id && pending.size === 1 ? pending.values().next().value : undefined);
55
+ if (existing) return existing.promise;
56
+ let resolve!: () => void;
57
+ let reject!: (error: unknown) => void;
58
+ const promise = new Promise<void>((yes, no) => {
59
+ resolve = yes;
60
+ reject = no;
61
+ });
62
+ // The next model entry awaits this receipt; rejection can happen sooner.
63
+ void promise.catch(() => undefined);
64
+ const receipt = { promise, resolve, reject };
65
+ receipts.set(event, receipt);
66
+ if (id) responseReceipts.set(id, receipt);
67
+ pending.add(receipt);
68
+ latest = promise;
69
+ return promise;
70
+ },
71
+ settle: (event: { type: string; data?: unknown }) => {
72
+ if (event.type !== "raw_model_stream_event" || !event.data || typeof event.data !== "object")
73
+ return;
74
+ if ((event.data as { type?: unknown }).type !== "response_done") return;
75
+ const id = responseId(event.data);
76
+ // Runner retry normalization copies response_done. It cannot create a
77
+ // second response before this receipt permits the next model entry.
78
+ const receipt =
79
+ receipts.get(event.data) ??
80
+ (id ? responseReceipts.get(id) : undefined) ??
81
+ (pending.size === 1 ? pending.values().next().value : undefined);
82
+ if (!receipt) return;
83
+ pending.delete(receipt);
84
+ if (id) responseReceipts.delete(id);
85
+ receipt.resolve();
86
+ },
87
+ fail,
88
+ close: () => {
89
+ input.signal.removeEventListener("abort", abort);
90
+ if (pending.size > 0) fail(new Error("Model stream closed before response settlement"));
91
+ responseReceipts.clear();
92
+ },
93
+ };
94
+ }
@@ -28,6 +28,8 @@ import {
28
28
  type loadWorkspaceEnvironmentForRunWithCredentials,
29
29
  } from "../environment";
30
30
  import type { mergeResourceRefs } from "../common";
31
+ import type { withFirstPartyTools } from "../goals";
32
+ import { expandMcpAccountRoutes } from "../mcp-account-routes";
31
33
  import { startGitCredentialRenewalLoop } from "../git-credential-renewal";
32
34
  import {
33
35
  RUN_CREDENTIAL_EXPIRY_LEAD_MS,
@@ -63,6 +65,7 @@ import {
63
65
  sandboxSettingsForRoute,
64
66
  } from "./sandbox-route";
65
67
  import type { ClaimTurnOk } from "./claim";
68
+ import { turnCredentialRestriction } from "./credential-restriction";
66
69
  import type { GovernanceModelOk } from "./governance-model";
67
70
  import type { SandboxTurnRuntime } from "./sandbox-runtime";
68
71
  import type {
@@ -74,6 +77,7 @@ import type {
74
77
 
75
78
  export type PrepareRunCredentialsDeps = {
76
79
  localMcpServerIds?: readonly string[];
80
+ turnTools: ReturnType<typeof withFirstPartyTools>;
77
81
  input: RunAgentTurnInput;
78
82
  settings: Settings;
79
83
  db: ActivityServices["db"];
@@ -88,6 +92,7 @@ export type PrepareRunCredentialsDeps = {
88
92
  sandboxRuntime: SandboxTurnRuntime;
89
93
  turn: ClaimTurnOk["turn"];
90
94
  session: ClaimTurnOk["session"];
95
+ turnExecutionPolicy: ClaimTurnOk["turnExecutionPolicy"];
91
96
  fileAuthoritySubjectId: ClaimTurnOk["fileAuthoritySubjectId"];
92
97
  runSettings: GovernanceModelOk["runSettings"];
93
98
  workspaceVariableSet: Awaited<ReturnType<typeof loadWorkspaceEnvironmentForRunWithCredentials>>;
@@ -126,6 +131,7 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
126
131
  sandboxState,
127
132
  turn,
128
133
  session,
134
+ turnExecutionPolicy,
129
135
  fileAuthoritySubjectId,
130
136
  runSettings,
131
137
  workspaceVariableSet,
@@ -153,6 +159,14 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
153
159
  const platformGitResources = turnResources.filter(
154
160
  (resource) => resource.kind !== "repository" || repositoryHasExplicitGitConnection(resource),
155
161
  );
162
+ // Credential targets follow the same accepted-account narrowing as execution.
163
+ // A canonical connection without an accepted account does not execute, and
164
+ // account-qualified aliases never inherit an attachment's product headers.
165
+ const accountRoutes = expandMcpAccountRoutes({
166
+ settings: runSettings,
167
+ tools: deps.turnTools,
168
+ bindings: turn.mcpAccountBindings,
169
+ });
156
170
 
157
171
  const runCredentialResolver =
158
172
  effectiveRunCredentialBackend === "none" || effectiveRunCredentialBackend === "selfhosted"
@@ -160,10 +174,11 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
160
174
  : await waitForTurnOperation(
161
175
  bindRunCredentialResolver({
162
176
  db,
163
- settings: runSettings,
177
+ settings: accountRoutes.settings,
164
178
  initiatingHumanSubjectId: turn.initiatingHumanSubjectId ?? null,
165
179
  connectionCredentials: connectionCredentials ?? null,
166
180
  localMcpServerIds: deps.localMcpServerIds ?? [],
181
+ effectiveTools: accountRoutes.tools,
167
182
  accountId: input.accountId,
168
183
  workspaceId: input.workspaceId,
169
184
  session,
@@ -214,9 +229,9 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
214
229
  : null;
215
230
  const runMcpCredentials = new RunMcpCredentials(
216
231
  selectedSessionRemoteMcpTargets(
217
- runSettings,
232
+ accountRoutes.settings,
218
233
  session.mcpServers ?? [],
219
- turn.tools ?? [],
234
+ accountRoutes.tools,
220
235
  (deps.localMcpServerIds ?? []).map((id) => ({ id })),
221
236
  ),
222
237
  {
@@ -334,11 +349,13 @@ export async function prepareRunCredentials(deps: PrepareRunCredentialsDeps) {
334
349
  turn,
335
350
  })
336
351
  : undefined;
352
+ const credentialRestriction = turnCredentialRestriction(turnExecutionPolicy, session.metadata);
337
353
  const codemodeAuthority = {
338
354
  sessionId: input.sessionId,
339
355
  turnId: turn.id,
340
356
  attemptId: input.attemptId,
341
357
  executionGeneration: turn.executionGeneration,
358
+ ...(credentialRestriction ? { credentialRestriction } : {}),
342
359
  };
343
360
  const sandboxArtifactRuntime = sandboxArtifactRuntimeAdmission(
344
361
  settings,
@@ -3,8 +3,11 @@ import {
3
3
  readActiveSandbox,
4
4
  getWorkspaceCredentialProvider,
5
5
  loadClaudeSubscriptionUsageCredential,
6
+ resolveClaudeSubscriptionCredential,
7
+ ClaudeSubscriptionReconnectRequired,
6
8
  } from "@opengeni/db";
7
9
  import { routingEnabled } from "../../sandbox-routing";
10
+ import { resolveAgentToolFamilies } from "@opengeni/contracts";
8
11
  import { createKnowledgeSourceSyncActivities } from "../knowledge-source-sync";
9
12
  import {
10
13
  assertModelConnectionAllowsTurn,
@@ -26,6 +29,7 @@ import {
26
29
  import {
27
30
  REMOTE_COMPACTION_V2_BETA_FEATURE,
28
31
  REMOTE_COMPACTION_V2_IMPLEMENTATION,
32
+ awaitModelCallAdmission,
29
33
  materializeSandboxFileDownloads,
30
34
  sandboxFileDownloadFailureNote,
31
35
  type SandboxFileDownload,
@@ -378,12 +382,14 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
378
382
  getModelRunSettings: () => eventing.modelRunSettings,
379
383
  getExecutionGeneration: () => attempt.executionGeneration,
380
384
  });
381
- const checkpointBeforeProviderDispatch = () =>
382
- checkpointHistoryBeforeProviderDispatch(historySink, {
385
+ const checkpointBeforeProviderDispatch = async () => {
386
+ await awaitModelCallAdmission();
387
+ await checkpointHistoryBeforeProviderDispatch(historySink, {
383
388
  effectiveSandboxBackend: eventing.modelRunSettings.sandboxBackend,
384
389
  routingEnabled: routingEnabled(settings),
385
390
  readActiveSandbox: () => readActiveSandbox(db, input.workspaceId, input.sessionId),
386
391
  });
392
+ };
387
393
 
388
394
  try {
389
395
  const claimed = await claimTurnAttempt({
@@ -554,6 +560,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
554
560
  agentHumanInputEnabled,
555
561
  codeSearchEnabled,
556
562
  workspaceAgentInstructions,
563
+ workspaceAgentIdentity,
557
564
  workspaceGovernance,
558
565
  structuredWorkspacePolicyActive,
559
566
  workspaceMemory,
@@ -786,8 +793,12 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
786
793
  credentialId: providerTurn.effectiveXaiCredentialId,
787
794
  authoritySnapshot: turn.xaiProviderAccountAuthoritySnapshot,
788
795
  hostedSearch: {
789
- webSearch: runSettings.webSearchEnabled,
790
- xSearch: runSettings.webSearchEnabled,
796
+ webSearch: resolveAgentToolFamilies(session.agent, {
797
+ webSearch: runSettings.webSearchEnabled,
798
+ }).webSearch,
799
+ xSearch: resolveAgentToolFamilies(session.agent, {
800
+ webSearch: runSettings.webSearchEnabled,
801
+ }).webSearch,
791
802
  },
792
803
  streamIdleTimeoutMs: runSettings.supergrokResponseStreamIdleTimeoutMs,
793
804
  nextRequestId: () => `${dispatchId}:xai:${++xaiModelRequestSequence}`,
@@ -932,7 +943,30 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
932
943
  }),
933
944
  );
934
945
  const withClaudeUsage = <T>(fn: () => Promise<T>): Promise<T> =>
935
- withClaudeUsageObserver(claudeUsageObserver, fn);
946
+ withClaudeUsageObserver(claudeUsageObserver, fn, async (providerId, headers) => {
947
+ const binding = claudeUsageObserver.binding(providerId);
948
+ if (!binding) return headers;
949
+ const credential = await resolveClaudeSubscriptionCredential(
950
+ db,
951
+ settings,
952
+ {
953
+ accountId: input.accountId,
954
+ workspaceId: input.workspaceId,
955
+ scope: binding.scope,
956
+ },
957
+ {
958
+ expectedConnectionId: binding.expectedConnectionId,
959
+ expectedCredentialVersion: binding.expectedCredentialVersion,
960
+ },
961
+ );
962
+ if (!credential) return headers; // A replaced connection never lends its new token to this turn.
963
+ if ("reconnectRequired" in credential)
964
+ throw new ClaudeSubscriptionReconnectRequired();
965
+ claudeUsageObserver.renew(providerId, credential);
966
+ headers.set("authorization", `Bearer ${credential.token}`);
967
+ headers.delete("x-api-key");
968
+ return headers;
969
+ });
936
970
  const withCodex = <T>(fn: () => Promise<T>): Promise<T> =>
937
971
  codexContext ? codexRequestStorage.run(codexContext, fn) : fn();
938
972
  const withProviderRequestContext = <T>(fn: () => Promise<T>): Promise<T> =>
@@ -1004,6 +1038,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
1004
1038
  turnExecutionPolicy,
1005
1039
  resolvedModel,
1006
1040
  workspaceAgentInstructions,
1041
+ workspaceAgentIdentity,
1007
1042
  workspaceGovernance,
1008
1043
  structuredWorkspacePolicyActive,
1009
1044
  workspaceMemory,
@@ -1175,6 +1210,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
1175
1210
  );
1176
1211
 
1177
1212
  const runCredentials = await prepareRunCredentials({
1213
+ turnTools,
1178
1214
  localMcpServerIds: installedApiIntegrations.map((integration) => integration.serverId),
1179
1215
  input,
1180
1216
  settings,
@@ -1190,6 +1226,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
1190
1226
  sandboxRuntime,
1191
1227
  turn,
1192
1228
  session,
1229
+ turnExecutionPolicy,
1193
1230
  fileAuthoritySubjectId,
1194
1231
  runSettings,
1195
1232
  workspaceVariableSet,
@@ -1410,6 +1447,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
1410
1447
  supportsImageInput,
1411
1448
  agentHumanInputEnabled,
1412
1449
  workspaceAgentInstructions,
1450
+ workspaceAgentIdentity,
1413
1451
  workspaceGovernance,
1414
1452
  structuredWorkspacePolicyActive,
1415
1453
  workspaceMemory,
@@ -1539,6 +1577,7 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
1539
1577
  turnExecutionPolicy,
1540
1578
  resolvedModel,
1541
1579
  workspaceAgentInstructions,
1580
+ workspaceAgentIdentity,
1542
1581
  workspaceGovernance,
1543
1582
  structuredWorkspacePolicyActive,
1544
1583
  workspaceMemory,
@@ -102,6 +102,7 @@ export type SandboxRouteOk = {
102
102
  machinePrimary: boolean;
103
103
  groupBoxBackend: Settings["sandboxBackend"];
104
104
  groupBoxImage: ReturnType<typeof rigProviderImageSourceImage>;
105
+ groupBoxImagePolicy: "new_creates_only";
105
106
  sandboxCreationBackend: Settings["sandboxBackend"];
106
107
  effectiveRunCredentialBackend: Settings["sandboxBackend"];
107
108
  };
@@ -299,6 +300,7 @@ export async function resolveSandboxRoute(deps: SandboxRouteDeps): Promise<Sandb
299
300
  machinePrimary,
300
301
  groupBoxBackend,
301
302
  groupBoxImage,
303
+ groupBoxImagePolicy: "new_creates_only",
302
304
  sandboxCreationBackend,
303
305
  effectiveRunCredentialBackend,
304
306
  };
@@ -341,6 +343,7 @@ export async function establishTurnSandbox(deps: EstablishTurnSandboxDeps): Prom
341
343
  machinePrimary,
342
344
  groupBoxBackend,
343
345
  groupBoxImage,
346
+ groupBoxImagePolicy,
344
347
  rigVersion,
345
348
  turnResources,
346
349
  } = deps;
@@ -665,6 +668,7 @@ export async function establishTurnSandbox(deps: EstablishTurnSandboxDeps): Prom
665
668
  ...(groupBoxImage
666
669
  ? {
667
670
  image: groupBoxImage,
671
+ imagePolicy: groupBoxImagePolicy,
668
672
  }
669
673
  : {}),
670
674
  // The lazy acquire must enforce the same frozen rig authority
@@ -744,17 +748,17 @@ export async function establishTurnSandbox(deps: EstablishTurnSandboxDeps): Prom
744
748
  backend: groupBoxBackend,
745
749
  os: groupBoxOs,
746
750
  environment: sandboxEnvironment,
747
- // IMAGE IS SHARED STATE (B3): the container image
748
- // this run resolves. The lease stamps it + conflicts on a live shared box
749
- // running a DIFFERENT image (solo → durable rotation; N-holders →
750
- // SandboxImageConflictError surfaced as an actionable turn error). Select
751
- // the image for the actual group-box backend; a configured Modal image must
751
+ // Deployment/workspace pins select NEW creates only. The lease
752
+ // retains a live group's image through between-turn repins, even
753
+ // with other holders; only rotation/reaping elects a new image.
754
+ // Select the image for the actual group-box backend; a configured Modal image must
752
755
  // never override a Docker run. The selfhosted branch
753
756
  // (establishSelfhostedTurnSession) NEVER passes
754
757
  // an image — B3 lives only on this managed-box branch.
755
758
  ...(groupBoxImage
756
759
  ? {
757
760
  image: groupBoxImage,
761
+ imagePolicy: groupBoxImagePolicy,
758
762
  }
759
763
  : {}),
760
764
  // RIG IS SHARED STATE (M3): stamp the frozen rig version so the lease