@opengeni/worker-bundle 1.0.1 → 2.0.0

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 (122) hide show
  1. package/dist/activities/agent-turn/codex.d.ts +1 -1
  2. package/dist/activities/agent-turn/compaction-prep.d.ts +1 -5
  3. package/dist/activities/agent-turn/errors.d.ts +31 -5
  4. package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
  5. package/dist/activities/agent-turn/finalization.d.ts +1 -1
  6. package/dist/activities/agent-turn/governance-model.d.ts +0 -2
  7. package/dist/activities/agent-turn/quiescence.d.ts +1 -0
  8. package/dist/activities/agent-turn/recovery-warning.d.ts +8 -0
  9. package/dist/activities/agent-turn/run-credentials.d.ts +0 -1
  10. package/dist/activities/agent-turn/sandbox-establish.d.ts +1 -1
  11. package/dist/activities/agent-turn/sandbox-route.d.ts +0 -20
  12. package/dist/activities/agent-turn/sandbox-runtime.d.ts +1 -1
  13. package/dist/activities/agent-turn/skill-remove.d.ts +14 -0
  14. package/dist/activities/agent-turn/skill-search.d.ts +1 -1
  15. package/dist/activities/agent-turn/stream-attempt.d.ts +1 -0
  16. package/dist/activities/agent-turn/tool-environment.d.ts +0 -4
  17. package/dist/activities/agent-turn/turn-context.d.ts +2 -0
  18. package/dist/activities/agent-turn/turn-sandbox-access.d.ts +3 -0
  19. package/dist/activities/codex-capacity.d.ts +6 -1
  20. package/dist/activities/codex-rotation.d.ts +1 -1
  21. package/dist/activities/context-compaction.d.ts +7 -1
  22. package/dist/activities/mcp-account-routes.d.ts +22 -0
  23. package/dist/activities/mcp-credentials.d.ts +12 -13
  24. package/dist/activities/mcp-operation-authority.d.ts +0 -5
  25. package/dist/activities/rig-verification.d.ts +2 -3
  26. package/dist/activities/sandbox-images.d.ts +1 -1
  27. package/dist/activities/sandbox-lease.d.ts +4 -3
  28. package/dist/activities/session-state.d.ts +57 -2
  29. package/dist/activities/types.d.ts +12 -3
  30. package/dist/{activities-control-HMFGSKC5.js → activities-control-BOLIY3H4.js} +366 -187
  31. package/dist/activities-control-BOLIY3H4.js.map +1 -0
  32. package/dist/activities-control.d.ts +110 -2
  33. package/dist/{activities-turn-AP67JADS.js → activities-turn-2B3WMHBB.js} +767 -471
  34. package/dist/activities-turn-2B3WMHBB.js.map +1 -0
  35. package/dist/activities.d.ts +110 -2
  36. package/dist/artifact-materializer-entry.js +5 -2
  37. package/dist/artifact-materializer-entry.js.map +1 -1
  38. package/dist/artifact-outbox-entry.js +2 -2
  39. package/dist/artifact-outbox-entry.js.map +1 -1
  40. package/dist/{chunk-A4XQWVZK.js → chunk-I7HKKMUJ.js} +77 -1
  41. package/dist/chunk-I7HKKMUJ.js.map +1 -0
  42. package/dist/{chunk-CYITU5UH.js → chunk-TVEE2UUC.js} +275 -146
  43. package/dist/chunk-TVEE2UUC.js.map +1 -0
  44. package/dist/editable-artifact-materializer-service.d.ts +2 -2
  45. package/dist/editable-artifact-outbox-service.d.ts +2 -2
  46. package/dist/index.d.ts +55 -1
  47. package/dist/index.js +4 -3
  48. package/dist/index.js.map +1 -1
  49. package/dist/observability-metrics.d.ts +11 -3
  50. package/dist/retained-process-diagnostics.d.ts +4 -0
  51. package/dist/sandbox-resume.d.ts +2 -12
  52. package/dist/sandbox-routing.d.ts +5 -3
  53. package/dist/sandbox-snapshot-diagnostics.d.ts +20 -0
  54. package/dist/warm-capture-holder.d.ts +3 -0
  55. package/dist/workflow-bundle.js +51 -11
  56. package/package.json +15 -15
  57. package/src/activities/agent-turn/agent-build.ts +42 -5
  58. package/src/activities/agent-turn/claim.ts +12 -0
  59. package/src/activities/agent-turn/codex-capacity.ts +26 -4
  60. package/src/activities/agent-turn/codex.ts +6 -2
  61. package/src/activities/agent-turn/compaction-prep.ts +53 -72
  62. package/src/activities/agent-turn/errors.ts +98 -10
  63. package/src/activities/agent-turn/failure-settlement.ts +49 -28
  64. package/src/activities/agent-turn/finalization-monitor.ts +121 -0
  65. package/src/activities/agent-turn/finalization.ts +79 -21
  66. package/src/activities/agent-turn/governance-model.ts +15 -19
  67. package/src/activities/agent-turn/history.ts +24 -10
  68. package/src/activities/agent-turn/quiescence.ts +3 -0
  69. package/src/activities/agent-turn/recovery-warning.ts +18 -0
  70. package/src/activities/agent-turn/run-credentials.ts +3 -7
  71. package/src/activities/agent-turn/run.ts +68 -52
  72. package/src/activities/agent-turn/sandbox-establish.ts +10 -2
  73. package/src/activities/agent-turn/sandbox-route.ts +0 -20
  74. package/src/activities/agent-turn/sandbox-runtime.ts +40 -7
  75. package/src/activities/agent-turn/skill-remove.ts +46 -0
  76. package/src/activities/agent-turn/skill-save.ts +1 -1
  77. package/src/activities/agent-turn/skill-search.ts +1 -1
  78. package/src/activities/agent-turn/skill-selection.ts +2 -0
  79. package/src/activities/agent-turn/skill-tools.ts +10 -5
  80. package/src/activities/agent-turn/stream-attempt.ts +38 -15
  81. package/src/activities/agent-turn/tool-environment.ts +44 -29
  82. package/src/activities/agent-turn/turn-context.ts +3 -0
  83. package/src/activities/agent-turn/turn-sandbox-access.ts +7 -1
  84. package/src/activities/capabilities.ts +17 -2
  85. package/src/activities/codex-capacity.ts +38 -9
  86. package/src/activities/codex-rotation.ts +18 -5
  87. package/src/activities/context-compaction.ts +26 -4
  88. package/src/activities/goals.ts +1 -0
  89. package/src/activities/google-drive-publication.ts +1 -3
  90. package/src/activities/mcp-account-routes.ts +139 -0
  91. package/src/activities/mcp-credentials.ts +83 -194
  92. package/src/activities/mcp-operation-authority.ts +16 -41
  93. package/src/activities/parent-wake.ts +8 -0
  94. package/src/activities/rig-verification.ts +38 -37
  95. package/src/activities/run-input.ts +25 -16
  96. package/src/activities/sandbox-images.ts +0 -1
  97. package/src/activities/sandbox-lease.ts +177 -44
  98. package/src/activities/scheduled-tasks.ts +118 -93
  99. package/src/activities/session-state.ts +83 -3
  100. package/src/activities/types.ts +18 -2
  101. package/src/activities/workflow-wake.ts +2 -17
  102. package/src/editable-artifact-materializer-service.ts +7 -3
  103. package/src/editable-artifact-outbox-service.ts +4 -4
  104. package/src/github-rest-mcp.ts +0 -1
  105. package/src/index.ts +2 -1
  106. package/src/observability-metrics.ts +105 -2
  107. package/src/personal-github-git-credentials.ts +0 -1
  108. package/src/pr-review-credentials.ts +0 -2
  109. package/src/retained-process-diagnostics.ts +50 -0
  110. package/src/sandbox-resume.ts +58 -73
  111. package/src/sandbox-routing.ts +44 -19
  112. package/src/sandbox-snapshot-diagnostics.ts +112 -0
  113. package/src/warm-capture-holder.ts +15 -0
  114. package/src/workflows/session.ts +64 -2
  115. package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
  116. package/dist/activities/packs.d.ts +0 -55
  117. package/dist/activities-control-HMFGSKC5.js.map +0 -1
  118. package/dist/activities-turn-AP67JADS.js.map +0 -1
  119. package/dist/chunk-A4XQWVZK.js.map +0 -1
  120. package/dist/chunk-CYITU5UH.js.map +0 -1
  121. package/src/activities/agent-turn/knowledge-sources.ts +0 -86
  122. package/src/activities/packs.ts +0 -166
@@ -41,7 +41,7 @@ export function createSkillSaveAttemptToolDefinition(input: {
41
41
  codemodePath: ["opengeni", "skill_save"],
42
42
  title: "Save Skill text",
43
43
  description:
44
- "Create or edit a workspace Skill without a sandbox. Supply only changed UTF-8 text files and explicit deletions; omitted files are preserved. Use the revision and scope version returned by Skill discovery for stale-write protection. For creation choose a new UUID, set expectedRevisionId to null and expectedScopeVersion to 1. The effective Skills setting governs publication: Automatic publishes, Review first saves a pending revision in Knowledge > Needs review while the task continues, and Off prevents agent authoring. Report the actual receipt without asking an approval question. Private chats save personal Skills; shared chats save workspace Skills.",
44
+ "Create or edit a Skill for reusable procedures or context-specific and personal behavioral preferences, not retrieval-only facts. Put its applicability in the SKILL.md description so the prompt index can guide skill_read; the full body is loaded when relevant. Short always-on workspace rules use instruction_policy_save. Supply only changed UTF-8 text files and explicit deletions; omitted files are preserved. No sandbox is needed. Use the revision and scope version returned by Skill discovery for stale-write protection. For creation choose a new UUID, set expectedRevisionId to null and expectedScopeVersion to 1. The effective Skills setting governs publication: Automatic publishes, Review first saves a pending revision in Knowledge > Needs review while the task continues, and Off prevents agent authoring. Report the actual receipt without asking an approval question. Private chats save personal Skills; shared chats save workspace Skills. Preserve the intended scope; another destination is not a workaround for unavailable scope or learning restrictions.",
45
45
  inputSchema: {
46
46
  type: "object",
47
47
  properties: {
@@ -8,7 +8,7 @@ export type WorkspaceSkillSearchEntry = Readonly<{
8
8
  revisionId?: string;
9
9
  scopeVersion?: number;
10
10
  installationVersion?: number;
11
- source?: "workspace" | "builtin" | "session" | "pack";
11
+ source?: "workspace" | "builtin" | "session";
12
12
  }>;
13
13
 
14
14
  export function createSkillSearchAttemptToolDefinition(input: {
@@ -14,6 +14,8 @@ export function configuredBundledSkillNames(context: BundledSkillConfiguration):
14
14
  const tools = new Set(context.firstPartyTools);
15
15
  const artifacts = () => tools.has("editable_artifact_list") && tools.has("editable_artifact_get");
16
16
  const definitions = [
17
+ { name: "opengeni-help", include: () => true },
18
+ { name: "opengeni-client", include: () => true },
17
19
  { name: "opengeni-visualize", include: () => true },
18
20
  { name: "document-parsing", include: () => true },
19
21
  { name: "opengeni-skills", include: () => true },
@@ -18,6 +18,7 @@ import {
18
18
  readSkill,
19
19
  resolveSkillImport,
20
20
  saveSkill,
21
+ removeSkill,
21
22
  } from "@opengeni/core";
22
23
  import {
23
24
  buildPortableSkillArtifact,
@@ -31,6 +32,7 @@ import { createSkillReadAttemptToolDefinition, type SkillReadContent } from "./s
31
32
  import { createSkillSearchAttemptToolDefinition } from "./skill-search";
32
33
  import { createSkillSaveAttemptToolDefinition, type SkillSaveRequest } from "./skill-save";
33
34
  import { createSkillInstallAttemptToolDefinition } from "./skill-install";
35
+ import { createSkillRemoveAttemptToolDefinition } from "./skill-remove";
34
36
  import {
35
37
  createSkillCheckoutAttemptToolDefinition,
36
38
  createSkillPublishAttemptToolDefinition,
@@ -122,6 +124,13 @@ export function createWorkspaceSkillTools(input: {
122
124
  );
123
125
  };
124
126
  return [
127
+ createSkillRemoveAttemptToolDefinition({
128
+ authorize,
129
+ remove: async (request) =>
130
+ withReviewState(
131
+ await removeSkill(input.db, { ...context, ...request, actor: input.actor }),
132
+ ),
133
+ }),
125
134
  createSkillReadAttemptToolDefinition({ authorize, load }),
126
135
  createSkillSearchAttemptToolDefinition({
127
136
  authorize,
@@ -140,11 +149,7 @@ export function createWorkspaceSkillTools(input: {
140
149
  id: entry.id,
141
150
  name: entry.artifact.name,
142
151
  description: entry.artifact.description || entry.artifact.name,
143
- source: entry.id.startsWith("builtin:")
144
- ? ("builtin" as const)
145
- : entry.id.startsWith("session:")
146
- ? ("session" as const)
147
- : ("pack" as const),
152
+ source: entry.id.startsWith("builtin:") ? ("builtin" as const) : ("session" as const),
148
153
  })),
149
154
  ],
150
155
  publicSearch: createPublicSkillSearchClient(input.settings),
@@ -22,6 +22,7 @@ import {
22
22
  interruptionKindForCallItem,
23
23
  releaseMcpResultCustomDataFromSdkEvent,
24
24
  findCompactionNeededError,
25
+ compactionProviderRejection,
25
26
  withRunCredentialsSession,
26
27
  runOwnedSandboxSetup,
27
28
  type SandboxFileDownload,
@@ -69,6 +70,7 @@ import {
69
70
  isCompletedGeneratedImageSdkEvent,
70
71
  } from "../generated-images";
71
72
  import { ToolResultSpill } from "./tool-result-spill";
73
+ import { ownedTurnSandboxForAgent } from "./turn-sandbox-access";
72
74
  import { createTurnCredentialLeases } from "./credential-leases";
73
75
  import { createTurnMediaArtifacts } from "./media-artifacts";
74
76
  import { createTurnHistorySink } from "./history-sink";
@@ -96,6 +98,7 @@ import {
96
98
  compactionFailureReason,
97
99
  safeErrorDiagnostic,
98
100
  compactionFailureReasonFromError,
101
+ compactionFailureTurnEventPayload,
99
102
  isCompactionSummaryFailure,
100
103
  PostCompactionContinuationEmptyError,
101
104
  shouldRecoverCompactionProviderFailure,
@@ -221,7 +224,7 @@ export type TurnStreamAttemptDeps = {
221
224
  activeSandboxBackend: Settings["sandboxBackend"] | undefined;
222
225
  groupBoxBackend: Settings["sandboxBackend"];
223
226
  turnExecutionPolicy: TurnExecutionPolicyV1;
224
- turn: { executionGeneration: number; model: string };
227
+ turn: { executionGeneration: number; model: string; source?: string };
225
228
  trigger: NonNullable<Awaited<ReturnType<typeof getSessionEvent>>>;
226
229
  humanInputResume: Awaited<ReturnType<typeof getHumanInputResumeForEvent>>;
227
230
  attachPendingUpdatesAfterOpenSuffix: () => Promise<boolean>;
@@ -650,8 +653,7 @@ export async function runTurnStreamAttempt(
650
653
  });
651
654
  }
652
655
  };
653
- const ownedEstablished =
654
- sandboxState.resolvedSandbox?.established ?? sandboxState.lazyOwnedSandbox;
656
+ const ownedEstablished = ownedTurnSandboxForAgent(sandboxState);
655
657
  const runStreamOnce = async (): ReturnType<OpenGeniRuntime["runStream"]> => {
656
658
  const eagerResolvedSandbox = sandboxState.resolvedSandbox;
657
659
  // Eager owned sessions must settle the exact platform-setup provider
@@ -759,8 +761,8 @@ export async function runTurnStreamAttempt(
759
761
  eventing.firstModelRequestPreparationStartedAt = performance.now();
760
762
  eventing.firstModelRequestCheckpointAt = eventing.firstModelRequestPreparationStartedAt;
761
763
  }
762
- const providerDispatchStartedAt = performance.now();
763
- let providerDispatchOutcome: "completed" | "failed" = "completed";
764
+ const streamInitializationStartedAt = performance.now();
765
+ let streamInitializationOutcome: "completed" | "failed" = "completed";
764
766
  try {
765
767
  // This histogram describes worker preparation until the first entry
766
768
  // into the runtime. Lazy SDK request preparation and the durable
@@ -894,15 +896,15 @@ export async function runTurnStreamAttempt(
894
896
  : {}),
895
897
  });
896
898
  } catch (error) {
897
- providerDispatchOutcome = "failed";
899
+ streamInitializationOutcome = "failed";
898
900
  throw error;
899
901
  } finally {
900
902
  recordTurnStartupPhase(observability, {
901
- phase: "provider_dispatch",
903
+ phase: "runtime_stream_initialization",
902
904
  provider: turnExecutionPolicy.providerId,
903
905
  backend: activeSandboxBackend ?? groupBoxBackend,
904
- outcome: providerDispatchOutcome,
905
- durationSeconds: (performance.now() - providerDispatchStartedAt) / 1_000,
906
+ outcome: streamInitializationOutcome,
907
+ durationSeconds: (performance.now() - streamInitializationStartedAt) / 1_000,
906
908
  });
907
909
  }
908
910
  };
@@ -1747,6 +1749,7 @@ export async function runTurnStreamAttempt(
1747
1749
  return claimedResult({ status: "cancelled" });
1748
1750
  }
1749
1751
  if (
1752
+ turn.source !== "compaction" &&
1750
1753
  generateSessionTitleInParallel &&
1751
1754
  runtime.generateSessionTitle &&
1752
1755
  !runtimeCancellationSignal.aborted
@@ -1822,6 +1825,7 @@ export async function runTurnStreamAttempt(
1822
1825
  let compacted = false;
1823
1826
  let compactionHandled = false;
1824
1827
  let compactionFailureMessage: string | null = null;
1828
+ let compactionFailureError: unknown = null;
1825
1829
  let compactionRequestCleared = false;
1826
1830
  try {
1827
1831
  const outcome = await forceContextCompaction(
@@ -1856,6 +1860,7 @@ export async function runTurnStreamAttempt(
1856
1860
  {
1857
1861
  clearRequestedCompaction: recoveryKind === "operator",
1858
1862
  publishLiveEvents: publishCompactionLiveEvents,
1863
+ providerRejection: compactionProviderRejection(compactError),
1859
1864
  },
1860
1865
  );
1861
1866
  compactionRequestCleared = landmark.requestConsumed;
@@ -1863,6 +1868,7 @@ export async function runTurnStreamAttempt(
1863
1868
  await finishParallelSessionTitle();
1864
1869
  const deferredSteer = await settleDeferredSteerAfterCompaction();
1865
1870
  if (deferredSteer) return deferredSteer;
1871
+ compactionFailureError = compactError;
1866
1872
  compactionFailureMessage = String(compactionFailureReasonFromError(compactError));
1867
1873
  observability.warn("context compaction recovery compaction failed", {
1868
1874
  sessionId: input.sessionId,
@@ -1880,13 +1886,9 @@ export async function runTurnStreamAttempt(
1880
1886
  events: [
1881
1887
  {
1882
1888
  type: "turn.failed",
1883
- payload: {
1889
+ payload: compactionFailureTurnEventPayload(compactionFailureError, {
1884
1890
  error: errorMessage,
1885
- code: "context_compaction_failed",
1886
- retryable: false,
1887
- recovery: "user_message",
1888
- compacted: false,
1889
- },
1891
+ }),
1890
1892
  },
1891
1893
  {
1892
1894
  type: "session.status.changed",
@@ -1919,6 +1921,27 @@ export async function runTurnStreamAttempt(
1919
1921
  await finishParallelSessionTitle();
1920
1922
  const deferredSteer = await settleDeferredSteerAfterCompaction();
1921
1923
  if (deferredSteer) return deferredSteer;
1924
+ if (turn.source === "compaction") {
1925
+ const settled = await eventing.settle!({
1926
+ events: [
1927
+ {
1928
+ type: "turn.completed",
1929
+ payload: {
1930
+ maintenance: "context_compaction",
1931
+ result: compacted ? "compacted" : "already_applied",
1932
+ },
1933
+ },
1934
+ { type: "session.status.changed", payload: { status: "idle" } },
1935
+ ],
1936
+ turnStatus: "completed",
1937
+ sessionStatus: "idle",
1938
+ activeTurnId: null,
1939
+ });
1940
+ if (!settled) return claimedResult({ status: "cancelled" });
1941
+ control.turnMetricOutcome = "completed";
1942
+ control.activityStatus = "idle";
1943
+ return claimedResult({ status: "idle" });
1944
+ }
1922
1945
  // Codex parity: compaction remains inside the same logical turn and
1923
1946
  // the same activity. Rebuild the model-visible history from the
1924
1947
  // durable replacement and continue the sampling loop; do not create
@@ -38,6 +38,11 @@ import {
38
38
  resolveGoogleDrivePublicationTarget,
39
39
  } from "../google-drive-publication";
40
40
  import { connectionTokenResolverForTurn } from "../mcp-credentials";
41
+ import {
42
+ accountRouteAuthNeededPayload,
43
+ expandApiIntegrationAccountRoutes,
44
+ expandMcpAccountRoutes,
45
+ } from "../mcp-account-routes";
41
46
  import { createMcpOperationPersistence } from "@opengeni/db/mcp-operations";
42
47
  import { createMcpOperationReadStore } from "../mcp-operation-store";
43
48
  import { createMcpOperationObserverResolver } from "../mcp-operation-observer";
@@ -51,7 +56,7 @@ import { buildCodexTokenResolver } from "../codex-auth";
51
56
  import { CODEX_CLIENT_VERSION } from "@opengeni/codex";
52
57
  import { mergeResourceRefs } from "../common";
53
58
  import {
54
- defaultSessionMcpServerIds,
59
+ workspaceSessionToolPolicyDefaultServerIds,
55
60
  loadRigDefaultVariableSetEnvironment,
56
61
  mergeRigDefaultVariableSetEnvironment,
57
62
  buildApiIntegrationMcpServers,
@@ -64,7 +69,7 @@ import {
64
69
  import { loadWorkspaceEnvironmentForRunWithCredentials } from "../environment";
65
70
  import { withFirstPartyTools } from "../goals";
66
71
  import type { TurnActivityServices as ActivityServices, RunAgentTurnInput } from "../types";
67
- import { recordTurnStartupPhase } from "../../observability-metrics";
72
+ import { recordToolPreparationPhase, recordTurnStartupPhase } from "../../observability-metrics";
68
73
  import { ToolResultSpill } from "./tool-result-spill";
69
74
  import { createTurnMediaArtifacts } from "./media-artifacts";
70
75
  import { SandboxChannelAService } from "@opengeni/runtime/sandbox";
@@ -103,7 +108,6 @@ import { createListModelsAttemptToolDefinition } from "./list-models";
103
108
  import { createWorkspaceSkillTools } from "./skill-tools";
104
109
  import { loadConfiguredBundledSkills } from "./skill-selection";
105
110
  import { guardSkillFilesystem } from "./skill-transfer";
106
- import type { RuntimeSkillActivation } from "@opengeni/runtime";
107
111
 
108
112
  export type PrepareTurnToolPolicyDeps = {
109
113
  input: RunAgentTurnInput;
@@ -125,7 +129,6 @@ export type PrepareTurnToolRuntimeDeps = {
125
129
  input: import("../types").RunKnowledgeSourceSyncBatchInput,
126
130
  ) => Promise<import("../types").RunKnowledgeSourceSyncBatchResult>)
127
131
  | undefined;
128
- selectedSkillActivations: readonly RuntimeSkillActivation[];
129
132
  input: RunAgentTurnInput;
130
133
  catalogSourceSettings: Settings;
131
134
  db: ActivityServices["db"];
@@ -134,7 +137,6 @@ export type PrepareTurnToolRuntimeDeps = {
134
137
  objectStorage: ActivityServices["objectStorage"];
135
138
  observability: ActivityServices["observability"];
136
139
  cancellationSignal: AbortSignal | undefined;
137
- connectionCredentials: ActivityServices["connectionCredentials"];
138
140
  eventing: EventingState;
139
141
  attempt: AttemptIdentityState;
140
142
  sandboxState: SandboxRuntimeState;
@@ -152,7 +154,6 @@ export type PrepareTurnToolRuntimeDeps = {
152
154
  lazyToolTransport: GovernanceModelOk["lazyToolTransport"];
153
155
  turnTools: ReturnType<typeof withFirstPartyTools>;
154
156
  connectionScope: { accountId: string; workspaceId: string };
155
- hostCredentialRootSessionId: string | null;
156
157
  sandboxArtifactRuntime: ReturnType<typeof sandboxArtifactRuntimeAdmission>;
157
158
  activeSandboxBackend: Settings["sandboxBackend"] | undefined;
158
159
  groupBoxBackend: Settings["sandboxBackend"];
@@ -212,7 +213,15 @@ export async function prepareTurnToolPolicy(deps: PrepareTurnToolPolicyDeps) {
212
213
  ? scheduledEffectiveMcpServerIds.filter((id) => currentMcpServerIds.has(id))
213
214
  : [...currentMcpServerIds],
214
215
  defaultMcpServerIds:
215
- scheduledEffectiveMcpServerIds ?? defaultSessionMcpServerIds(capabilitySettings.mcpServers),
216
+ scheduledEffectiveMcpServerIds ??
217
+ (session.toolPolicy.mode === "workspace_default"
218
+ ? await workspaceSessionToolPolicyDefaultServerIds(
219
+ db,
220
+ input.workspaceId,
221
+ capabilitySettings,
222
+ fileAuthoritySubjectId ?? undefined,
223
+ )
224
+ : []),
216
225
  });
217
226
  const mcpAvailabilityNote = unavailableMcpOperationalContext({
218
227
  droppedIds: resolvedToolPolicy.effectivePolicy.droppedIds,
@@ -349,7 +358,6 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
349
358
  objectStorage,
350
359
  observability,
351
360
  cancellationSignal,
352
- connectionCredentials,
353
361
  eventing,
354
362
  attempt,
355
363
  sandboxState,
@@ -361,10 +369,9 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
361
369
  codexAppsCredentialId,
362
370
  turnExecutionPolicy,
363
371
  trigger,
364
- runSettings,
372
+ runSettings: canonicalRunSettings,
365
373
  lazyToolTransport,
366
- turnTools,
367
- hostCredentialRootSessionId,
374
+ turnTools: canonicalTurnTools,
368
375
  sandboxArtifactRuntime,
369
376
  activeSandboxBackend,
370
377
  groupBoxBackend,
@@ -377,26 +384,29 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
377
384
  throwIfTurnCancelled,
378
385
  } = deps;
379
386
 
387
+ const accountRoutes = expandMcpAccountRoutes({
388
+ settings: canonicalRunSettings,
389
+ tools: canonicalTurnTools,
390
+ bindings: turn.mcpAccountBindings,
391
+ });
392
+ const runSettings = accountRoutes.settings;
393
+ const turnTools = accountRoutes.tools;
380
394
  const toolContextPreparationStartedAt = performance.now();
381
395
  throwIfWorkerShuttingDown();
382
396
  throwIfTurnCancelled();
383
- const mcpCredentialRootSessionId =
384
- connectionCredentials?.mcpCredentials && hostCredentialRootSessionId
385
- ? hostCredentialRootSessionId
386
- : input.sessionId;
387
397
  // Connection credentials and the optional Apps credential are resolved
388
398
  // independently. Inference auth is never an Apps fallback.
389
399
  const rawResolveCredential = connectionTokenResolverForTurn({
390
400
  db,
391
401
  settings: runSettings,
392
- connectionCredentials: connectionCredentials ?? null,
402
+ canonicalMcpServerIds: canonicalRunSettings.mcpServers
403
+ .filter((server) => server.connectionRef)
404
+ .map((server) => server.id),
393
405
  accountId: input.accountId,
394
406
  workspaceId: input.workspaceId,
395
407
  sessionId: input.sessionId,
396
- rootSessionId: mcpCredentialRootSessionId,
397
408
  attemptId: input.attemptId,
398
409
  turn,
399
- observability,
400
410
  });
401
411
  const personalConnectionDelegations = turn.personalConnectionDelegations;
402
412
  const delegatedMembershipChecks = new Map<string, Promise<boolean>>();
@@ -463,16 +473,24 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
463
473
  return;
464
474
  }
465
475
  await eventing.publish!(
466
- [{ type: "tool.auth_needed", payload: rollingSafeToolAuthNeededPayload(payload) }],
476
+ [
477
+ {
478
+ type: "tool.auth_needed",
479
+ payload: rollingSafeToolAuthNeededPayload(
480
+ accountRouteAuthNeededPayload(payload, turn.mcpAccountBindings),
481
+ ),
482
+ },
483
+ ],
467
484
  true,
468
485
  );
469
486
  };
470
- const selectedApiIntegrationServerIds = new Set(turnTools.map((tool) => tool.id));
471
487
  const apiIntegrationMcpServers = buildApiIntegrationMcpServers({
472
488
  settings: runSettings,
473
- integrations: installedApiIntegrations.filter((integration) =>
474
- selectedApiIntegrationServerIds.has(integration.serverId),
475
- ),
489
+ integrations: expandApiIntegrationAccountRoutes({
490
+ integrations: installedApiIntegrations,
491
+ bindings: turn.mcpAccountBindings,
492
+ tools: turnTools,
493
+ }),
476
494
  authority: {
477
495
  accountId: input.accountId,
478
496
  workspaceId: input.workspaceId,
@@ -608,7 +626,6 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
608
626
  });
609
627
  const selectedSkills = [
610
628
  ...bundledSkills,
611
- ...deps.selectedSkillActivations.map((entry) => ({ id: entry.id, artifact: entry.artifact })),
612
629
  ...session.skills.map((skill) => ({
613
630
  id: `session:${session.id}:${skill.name}`,
614
631
  artifact: skill,
@@ -950,6 +967,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
950
967
  try {
951
968
  eventing.preparedTools = await waitForTurnOperation(
952
969
  runtime.prepareTools(githubRestMcp.settings, githubRestMcp.tools, {
970
+ mcpAccountLabels: accountRoutes.accountLabels,
953
971
  accountId: input.accountId,
954
972
  workspaceId: input.workspaceId,
955
973
  sessionId: input.sessionId,
@@ -1002,13 +1020,10 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
1002
1020
  localMcpServers,
1003
1021
  ...(deferNonEagerToolPreparation ? { deferNonEagerUntilToolDemand: true } : {}),
1004
1022
  onPreparationPhase: (measurement) => {
1005
- recordTurnStartupPhase(observability, {
1006
- phase: `tool_${measurement.phase}`,
1023
+ recordToolPreparationPhase(observability, {
1024
+ ...measurement,
1007
1025
  provider: turnExecutionPolicy.providerId,
1008
1026
  backend: activeSandboxBackend ?? groupBoxBackend,
1009
- outcome: measurement.outcome,
1010
- durationSeconds: measurement.durationSeconds,
1011
- count: githubRestMcp.tools.length,
1012
1027
  });
1013
1028
  },
1014
1029
  onAttemptToolCatalog: async (catalog) => {
@@ -1,3 +1,4 @@
1
+ import type { TurnHeartbeatDetails } from "../../op-journal";
1
2
  import type { Settings } from "@opengeni/config";
2
3
  import type { CodexUsageHeaderSnapshot } from "@opengeni/codex";
3
4
  import type { AppendEventInput, ApplySessionTurnSettlementInput } from "@opengeni/db";
@@ -123,6 +124,7 @@ export type RenewalState = {
123
124
  };
124
125
 
125
126
  export type EventingState = {
127
+ heartbeatDetails: TurnHeartbeatDetails | null;
126
128
  heartbeatTimer: ReturnType<typeof startActivityHeartbeat> | undefined;
127
129
  batcher: ReturnType<typeof createRuntimeBatcher> | null;
128
130
  preparedTools: Awaited<ReturnType<OpenGeniRuntime["prepareTools"]>> | null;
@@ -254,6 +256,7 @@ export function createTurnContext(input: {
254
256
  publishedRunCredentialNotices: new Set(),
255
257
  },
256
258
  eventing: {
259
+ heartbeatDetails: null,
257
260
  heartbeatTimer: undefined,
258
261
  batcher: null,
259
262
  preparedTools: null,
@@ -13,6 +13,12 @@ export type ResolvedTurnSandboxAccess = Readonly<{
13
13
  leaseEpoch: number;
14
14
  }>;
15
15
 
16
+ /** The lazy wrapper remains the agent's owner even after provisioning fills
17
+ * resolvedSandbox with the raw provider handle for capture and release. */
18
+ export function ownedTurnSandboxForAgent(sandboxState: TurnSandboxAccessState) {
19
+ return sandboxState.lazyOwnedSandbox ?? sandboxState.resolvedSandbox?.established;
20
+ }
21
+
16
22
  /** Join the turn's canonical sandbox boundary and return its active routed session. */
17
23
  export async function resolveTurnSandboxAccess(
18
24
  sandboxState: TurnSandboxAccessState,
@@ -24,7 +30,7 @@ export async function resolveTurnSandboxAccess(
24
30
  sandbox = await sandboxState.turnSandboxProvisioner.get();
25
31
  }
26
32
 
27
- const session = (sandboxState.lazyOwnedSandbox?.session ??
33
+ const session = (ownedTurnSandboxForAgent(sandboxState)?.session ??
28
34
  sandbox?.established.session ??
29
35
  sdkOwnedSandboxSession) as ChannelASession | null;
30
36
  if (!session) throw new Error(unavailableMessage);
@@ -22,6 +22,7 @@ import {
22
22
  getWorkspaceOpenRouterCustomModelForExecution,
23
23
  listSessionMcpServerMetadata,
24
24
  listSessionMcpServersForRun,
25
+ getSessionAttemptMcpApprovalPolicies,
25
26
  listWorkspaceGatewayCustomModels,
26
27
  listWorkspaceOpenRouterCustomModels,
27
28
  workspaceCodexSubscriptionActive,
@@ -47,10 +48,24 @@ export async function settingsWithSessionMcpServersForRun(
47
48
  },
48
49
  ): Promise<Settings> {
49
50
  const encryptionKey = environmentsEncryptionKeyBytes(settings);
51
+ const policies = await getSessionAttemptMcpApprovalPolicies(
52
+ db,
53
+ workspaceId,
54
+ sessionId,
55
+ attemptId,
56
+ );
57
+ const policySettings = {
58
+ ...settings,
59
+ mcpServers: settings.mcpServers.map((server) =>
60
+ Object.hasOwn(policies, server.id)
61
+ ? { ...server, requireApproval: policies[server.id] }
62
+ : server,
63
+ ),
64
+ };
50
65
  if (!encryptionKey) {
51
66
  const metadata = await listSessionMcpServerMetadata(db, workspaceId, sessionId);
52
67
  if (metadata.length === 0) {
53
- return settings;
68
+ return policySettings;
54
69
  }
55
70
  if (metadata.some((server) => server.headerNames.length > 0)) {
56
71
  throw new Error(
@@ -69,7 +84,7 @@ export async function settingsWithSessionMcpServersForRun(
69
84
  // overlaid into settings. A session projection read earlier in the turn can
70
85
  // be stale after a concurrent mcpCredentialUpdates renewal.
71
86
  options?.onResolvedServers?.(servers);
72
- return settingsWithSessionMcpServers(settings, servers);
87
+ return settingsWithSessionMcpServers(policySettings, servers);
73
88
  }
74
89
 
75
90
  export function settingsWithSessionMcpServers(
@@ -1,4 +1,5 @@
1
1
  import {
2
+ connectionModelAllowed,
2
3
  armCodexCapacityWait,
3
4
  fetchCodexUsageForAccount,
4
5
  getCodexCapacityWaitForSession,
@@ -90,8 +91,15 @@ export async function signalPendingCodexCapacityWakeTargets(
90
91
 
91
92
  export function codexCapacityDecision<TPolicyScope = never, TUnavailableDiagnostic = never>(
92
93
  context: CodexCapacitySelectionContext<TPolicyScope, TUnavailableDiagnostic>,
94
+ now = new Date(),
93
95
  ): ReturnType<Parameters<typeof reconcileCodexCapacityWaitDb>[2]> {
94
- const now = new Date();
96
+ context = {
97
+ ...context,
98
+ accounts: context.accounts.filter(
99
+ (account) =>
100
+ !context.modelId || connectionModelAllowed(account.allowedModelIds, context.modelId),
101
+ ),
102
+ };
95
103
  const selected = selectCodexCredentialLeaseForTurn({
96
104
  context,
97
105
  sessionId: context.sessionId,
@@ -114,8 +122,12 @@ export function codexCapacityDecision<TPolicyScope = never, TUnavailableDiagnost
114
122
  credentialId: selected.credentialId,
115
123
  diagnostic: {
116
124
  connectedCount: context.accounts.length,
117
- eligibleCount: context.accounts.filter((account) => isCodexCredentialEligible(account, now))
118
- .length,
125
+ eligibleCount: context.accounts.filter(
126
+ (account) =>
127
+ (account.id === selected.credentialId ||
128
+ !context.failedCredentialIds?.includes(account.id)) &&
129
+ isCodexCredentialEligible(account, now),
130
+ ).length,
119
131
  },
120
132
  };
121
133
  }
@@ -134,8 +146,7 @@ export function codexCapacityDecision<TPolicyScope = never, TUnavailableDiagnost
134
146
  account.status === "active" &&
135
147
  account.allocatorEnabled &&
136
148
  account.exhaustedKind === "quota" &&
137
- account.exhaustedUntil !== null &&
138
- account.exhaustedUntil > now,
149
+ account.exhaustedUntil !== null,
139
150
  );
140
151
  const policyAccount = capacityAccounts[0] ?? null;
141
152
  const mutationOnlyStatusBlock =
@@ -162,7 +173,7 @@ export function codexCapacityDecision<TPolicyScope = never, TUnavailableDiagnost
162
173
  kind: "unavailable",
163
174
  earliestResetAt: authoritativeReset,
164
175
  resetKind:
165
- selected.decision.kind === "none" ||
176
+ (selected.decision.kind === "none" && !hasReconcilableQuotaCooldown && !authoritativeReset) ||
166
177
  selected.decision.kind === "allocatorDisabled" ||
167
178
  mutationOnlyStatusBlock
168
179
  ? "mutation_only"
@@ -191,6 +202,16 @@ export async function armAndReconcileCodexCapacityWait(
191
202
  options: { onArmed?: () => void } = {},
192
203
  ) {
193
204
  const armed = await armCodexCapacityWait(services.db, input);
205
+ if (armed.action === "stopped") {
206
+ options.onArmed?.();
207
+ await publishDurableSessionEvents(
208
+ services.bus,
209
+ input.workspaceId,
210
+ input.sessionId,
211
+ armed.events,
212
+ );
213
+ return armed;
214
+ }
194
215
  if (armed.action !== "waiting") return armed;
195
216
 
196
217
  options.onArmed?.();
@@ -219,8 +240,9 @@ export async function armAndReconcileCodexCapacityWait(
219
240
  async function refreshCapacityMetadata(
220
241
  services: ControlActivityServices,
221
242
  workspaceId: string,
243
+ turnId: string,
222
244
  ): Promise<void> {
223
- const accounts = await listCodexAccountStatuses(services.db, workspaceId).catch(() => []);
245
+ const accounts = await listCodexAccountStatuses(services.db, workspaceId, turnId).catch(() => []);
224
246
  const now = new Date();
225
247
  const stale = accounts.filter(
226
248
  (account) =>
@@ -231,7 +253,14 @@ async function refreshCapacityMetadata(
231
253
  await refreshCodexUsageAndRepairCapacityWaiters(
232
254
  stale.map(
233
255
  (account) => () =>
234
- fetchCodexUsageForAccount(services.db, services.settings, workspaceId, account.id),
256
+ fetchCodexUsageForAccount(
257
+ services.db,
258
+ services.settings,
259
+ workspaceId,
260
+ account.id,
261
+ undefined,
262
+ turnId,
263
+ ),
235
264
  ),
236
265
  () => signalPendingCodexCapacityWakeTargets(services, workspaceId),
237
266
  );
@@ -338,7 +367,7 @@ export function createCodexCapacityActivities(services: () => Promise<ControlAct
338
367
  if (boundedRefreshAttempted) {
339
368
  // This is a bounded secret-safe control-plane quota refresh. It creates no
340
369
  // turn, model call, user message, schedule, or entitlement action.
341
- await refreshCapacityMetadata(resolved, input.workspaceId);
370
+ await refreshCapacityMetadata(resolved, input.workspaceId, current.blockedTurnId);
342
371
  }
343
372
  const result = await reconcileCodexCapacityWaitDb(
344
373
  resolved.db,
@@ -12,6 +12,7 @@ import type {
12
12
  CodexLeaseAccountStatus,
13
13
  CodexPinSource,
14
14
  } from "@opengeni/db";
15
+ import { connectionModelAllowed } from "@opengeni/db";
15
16
 
16
17
  export type CodexRotationAccount = CodexAccountStatus | CodexLeaseAccountStatus;
17
18
 
@@ -21,14 +22,12 @@ export function codexAccountNeedsLiveCapacityRefresh(
21
22
  CodexAccountStatus,
22
23
  "primaryUsedPercent" | "secondaryUsedPercent" | "exhaustedUntil" | "exhaustedKind"
23
24
  >,
24
- now: Date,
25
+ _now: Date,
25
26
  ): boolean {
26
27
  return (
27
28
  (account.primaryUsedPercent ?? 0) >= CODEX_USAGE_EXHAUSTED_PCT ||
28
29
  (account.secondaryUsedPercent ?? 0) >= CODEX_USAGE_EXHAUSTED_PCT ||
29
- (account.exhaustedKind === "quota" &&
30
- account.exhaustedUntil !== null &&
31
- account.exhaustedUntil > now)
30
+ (account.exhaustedKind === "quota" && account.exhaustedUntil !== null)
32
31
  );
33
32
  }
34
33
 
@@ -524,8 +523,16 @@ export function selectCodexCredentialLeaseForTurn<
524
523
  sessionLastCredentialId: string | null;
525
524
  now: Date;
526
525
  }): CodexTurnLeaseSelection {
527
- const { accounts, activeCredentialId, rotationEnabled, rotationStrategy, existingCredentialId } =
526
+ const { activeCredentialId, rotationEnabled, rotationStrategy, existingCredentialId } =
528
527
  args.context;
528
+ if (args.context.failoverExhausted) {
529
+ return { credentialId: null, decision: { kind: "none" }, advanceActivePointer: false };
530
+ }
531
+ const accounts = args.context.modelId
532
+ ? args.context.accounts.filter((account) =>
533
+ connectionModelAllowed(account.allowedModelIds, args.context.modelId!),
534
+ )
535
+ : args.context.accounts;
529
536
  const failedCredentialIds = new Set(args.context.failedCredentialIds ?? []);
530
537
  // Normalized: rotation-enabled always behaves as sharded (see
531
538
  // effectiveRotationStrategy); the stored value is only ever legacy residue.
@@ -564,6 +571,9 @@ export function selectCodexCredentialLeaseForTurn<
564
571
  if (!pinned) {
565
572
  return { credentialId: null, decision: { kind: "none" }, advanceActivePointer: false };
566
573
  }
574
+ if (failedCredentialIds.has(pinned.id)) {
575
+ return { credentialId: null, decision: { kind: "none" }, advanceActivePointer: false };
576
+ }
567
577
  if (!pinned.allocatorEnabled) {
568
578
  return {
569
579
  credentialId: null,
@@ -633,6 +643,9 @@ export function selectCodexCredentialLeaseForTurn<
633
643
  if (!active) {
634
644
  return { credentialId: null, decision: { kind: "none" }, advanceActivePointer: false };
635
645
  }
646
+ if (failedCredentialIds.has(active.id)) {
647
+ return { credentialId: null, decision: { kind: "none" }, advanceActivePointer: false };
648
+ }
636
649
  if (!active.allocatorEnabled) {
637
650
  return {
638
651
  credentialId: null,