@opengeni/worker-bundle 0.28.3 → 0.28.4-canary.1

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 (54) hide show
  1. package/dist/activities/agent-turn/codex-selection-diagnostics.d.ts +1 -0
  2. package/dist/activities/agent-turn/mcp-operation-read-tool.d.ts +16 -0
  3. package/dist/activities/codex-fleet-shadow.d.ts +1 -1
  4. package/dist/activities/mcp-operation-authority.d.ts +16 -0
  5. package/dist/activities/mcp-operation-observer.d.ts +14 -0
  6. package/dist/activities/mcp-operation-reader.d.ts +45 -0
  7. package/dist/activities/mcp-operation-store.d.ts +6 -0
  8. package/dist/activities/sandbox-lease.d.ts +12 -4
  9. package/dist/{activities-control-UXGPMDL3.js → activities-control-MKGEOBXT.js} +90 -71
  10. package/dist/activities-control-MKGEOBXT.js.map +1 -0
  11. package/dist/{activities-turn-NOYDE7PX.js → activities-turn-5MZZ6HQA.js} +15691 -815
  12. package/dist/activities-turn-5MZZ6HQA.js.map +1 -0
  13. package/dist/artifact-materializer-entry.js +2 -0
  14. package/dist/artifact-materializer-entry.js.map +1 -1
  15. package/dist/artifact-outbox-entry.js +2 -0
  16. package/dist/artifact-outbox-entry.js.map +1 -1
  17. package/dist/{chunk-BARTLVBK.js → chunk-A4XQWVZK.js} +25 -25
  18. package/dist/chunk-A4XQWVZK.js.map +1 -0
  19. package/dist/{chunk-NNXLW6V7.js → chunk-OA5CSY5N.js} +147 -112
  20. package/dist/chunk-OA5CSY5N.js.map +1 -0
  21. package/dist/chunk-PZ5AY32C.js +10 -0
  22. package/dist/chunk-PZ5AY32C.js.map +1 -0
  23. package/dist/host-export.js +1 -0
  24. package/dist/index.js +3 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/observability-metrics.d.ts +12 -4
  27. package/dist/sandbox-archive-storage.d.ts +15 -3
  28. package/dist/sandbox-resume.d.ts +1 -1
  29. package/dist/sandbox-routing.d.ts +6 -1
  30. package/package.json +18 -17
  31. package/src/activities/agent-turn/claim.ts +3 -3
  32. package/src/activities/agent-turn/codex-capacity.ts +31 -53
  33. package/src/activities/agent-turn/codex-selection-diagnostics.ts +1 -0
  34. package/src/activities/agent-turn/finalization.ts +10 -5
  35. package/src/activities/agent-turn/mcp-operation-read-tool.ts +61 -0
  36. package/src/activities/agent-turn/sandbox-establish.ts +2 -0
  37. package/src/activities/agent-turn/skill-search.ts +2 -16
  38. package/src/activities/agent-turn/stream-attempt.ts +1 -1
  39. package/src/activities/agent-turn/tool-environment.ts +65 -0
  40. package/src/activities/codex-fleet-shadow.ts +3 -0
  41. package/src/activities/mcp-credentials.ts +94 -40
  42. package/src/activities/mcp-operation-authority.ts +76 -0
  43. package/src/activities/mcp-operation-observer.ts +86 -0
  44. package/src/activities/mcp-operation-reader.ts +108 -0
  45. package/src/activities/mcp-operation-store.ts +98 -0
  46. package/src/activities/sandbox-lease.ts +111 -73
  47. package/src/observability-metrics.ts +28 -24
  48. package/src/sandbox-archive-storage.ts +82 -18
  49. package/src/sandbox-resume.ts +79 -63
  50. package/src/sandbox-routing.ts +5 -1
  51. package/dist/activities-control-UXGPMDL3.js.map +0 -1
  52. package/dist/activities-turn-NOYDE7PX.js.map +0 -1
  53. package/dist/chunk-BARTLVBK.js.map +0 -1
  54. package/dist/chunk-NNXLW6V7.js.map +0 -1
@@ -1,4 +1,5 @@
1
- import type { ObjectStorage } from "@opengeni/storage";
1
+ import { type ObjectStorage } from "@opengeni/storage";
2
+ import type { VerifiedHostWorkspaceArchive } from "@opengeni/runtime/sandbox";
2
3
  import { type WorkspaceArchiveDescriptor, type WorkspaceArchiveObjectRef } from "@opengeni/contracts";
3
4
  export declare class WorkspaceArchiveObjectStorageRequiredError extends Error {
4
5
  readonly code: "workspace_archive_object_storage_required";
@@ -13,15 +14,26 @@ export declare function putTarWorkspaceArchiveObject(input: {
13
14
  archive: {
14
15
  bytes: Uint8Array;
15
16
  descriptor: WorkspaceArchiveDescriptor;
16
- };
17
+ } | VerifiedHostWorkspaceArchive;
17
18
  }): Promise<WorkspaceArchiveObjectRef>;
19
+ /** The caller owns a fresh candidate returned by this module. A thrown or lost
20
+ * database response is UNKNOWN: it may have committed, so never delete on catch.
21
+ * Only the transaction's exact candidate disposition can authorize cleanup. */
22
+ export declare function persistWorkspaceArchiveCandidate<T extends {
23
+ wrote: boolean;
24
+ candidateDisposition?: "adopted" | "already_referenced" | "unused";
25
+ }>(input: {
26
+ objectStorage?: ObjectStorage | null;
27
+ ref?: WorkspaceArchiveObjectRef;
28
+ persist: () => Promise<T>;
29
+ }): Promise<T>;
18
30
  export declare function putVersion1TarArchiveOrInline(input: {
19
31
  backend: string;
20
32
  objectStorage?: ObjectStorage | null | undefined;
21
33
  accountId: string;
22
34
  workspaceId: string;
23
35
  sandboxGroupId: string;
24
- archive: {
36
+ archive: VerifiedHostWorkspaceArchive | {
25
37
  bytes: Uint8Array;
26
38
  descriptor: WorkspaceArchiveDescriptor;
27
39
  base64: string;
@@ -1,7 +1,7 @@
1
1
  import { type Settings } from "@opengeni/config";
2
2
  import { readLease, reinstateTurnLeaseHolder, SandboxLeaseSupersededError, type Database, type LeaseHolderKind } from "@opengeni/db";
3
3
  import { establishSandboxSessionFromEnvelope, type EstablishedSandboxSession, type RuntimeMetricsHooks } from "@opengeni/runtime";
4
- import type { ObjectStorage } from "@opengeni/storage";
4
+ import { type ObjectStorage } from "@opengeni/storage";
5
5
  import type { Observability } from "@opengeni/observability";
6
6
  export type ResumeHolderKind = LeaseHolderKind;
7
7
  /**
@@ -2,7 +2,7 @@ import { type Settings } from "@opengeni/config";
2
2
  import { type Database, type EnrollmentRecord } from "@opengeni/db";
3
3
  import type { OpStreamOutputFrame } from "@opengeni/runtime/sandbox";
4
4
  import type { EventBus } from "@opengeni/events";
5
- import { type EstablishedSandboxSession, type RoutingSandboxFirstOperationObserver, type RoutingSandboxOperationObserver, type SelfhostedOpObserver, type SelfhostedRelayConfig, type OpStreamJournal } from "@opengeni/runtime";
5
+ import { type EstablishedSandboxSession, type RoutingSandboxFirstOperationObserver, type RoutingSandboxOperationObserver, type SelfhostedOpObserver, type SelfhostedConnectionBinding, type SelfhostedRelayConfig, type OpStreamJournal } from "@opengeni/runtime";
6
6
  export type RoutingWiringServices = {
7
7
  db: Database;
8
8
  settings: Settings;
@@ -179,6 +179,8 @@ export type SelfhostedTurnSessionArgs = {
179
179
  operationResourcePolicy: EnrollmentRecord["operationPolicy"];
180
180
  operationResourcePolicySupported: boolean;
181
181
  operationCpuQuotaSupported: boolean;
182
+ /** Feature advertisement only; refreshed by exact live admission for each operation. */
183
+ transactionalFsWriteSupported?: boolean;
182
184
  /** The active pointer's epoch — the control-op fence echoed to the agent. */
183
185
  epoch: number;
184
186
  /** The run's declared sandbox environment (the SAME object fed to buildAgent +
@@ -197,6 +199,9 @@ export type SelfhostedTurnSessionArgs = {
197
199
  sessionId: string;
198
200
  };
199
201
  };
202
+ /** Project one enrollment read into one exact runtime route. Capability and
203
+ * process identity must be taken from the same snapshot. */
204
+ export declare function connectionBindingFor(services: RoutingWiringServices, enrollment: EnrollmentRecord | null): SelfhostedConnectionBinding | null;
200
205
  export declare function establishSelfhostedTurnSession(services: RoutingWiringServices, args: SelfhostedTurnSessionArgs): Promise<EstablishedSandboxSession>;
201
206
  /** Whether the routing proxy should wrap the turn box: the hot-swap feature is
202
207
  * gated by the selfhosted flag (the active pointer + swap tools are only
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/worker-bundle",
3
- "version": "0.28.3",
3
+ "version": "0.28.4-canary.1",
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,22 +54,23 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@llamaindex/liteparse": "2.14.2",
57
- "@opengeni/agent-proto": "^0.5.1",
58
- "@opengeni/capabilities": "^0.3.3",
59
- "@opengeni/codemode": "^0.5.5",
60
- "@opengeni/codex": "^0.2.22",
61
- "@opengeni/config": "^1.1.1",
62
- "@opengeni/contracts": "^3.0.1",
63
- "@opengeni/core": "^2.9.3",
64
- "@opengeni/db": "^4.3.2",
65
- "@opengeni/documents": "^0.8.26",
66
- "@opengeni/events": "^0.4.24",
67
- "@opengeni/github": "^0.7.9",
68
- "@opengeni/network": "^0.3.1",
69
- "@opengeni/observability": "^0.8.25",
70
- "@opengeni/runtime": "^2.5.2",
71
- "@opengeni/storage": "^0.2.126",
72
- "@opengeni/xai-subscription": "^0.1.4",
57
+ "@opengeni/agent-proto": "^0.5.1-canary.27",
58
+ "@opengeni/capabilities": "^0.3.3-canary.9",
59
+ "@opengeni/codemode": "^0.5.6-canary.1",
60
+ "@opengeni/codex": "^0.2.22-canary.9",
61
+ "@opengeni/config": "^1.1.2-canary.1",
62
+ "@opengeni/contracts": "^3.0.2-canary.1",
63
+ "@opengeni/core": "^2.9.4-canary.1",
64
+ "@opengeni/db": "^4.3.3-canary.1",
65
+ "@opengeni/documents": "^0.8.27-canary.1",
66
+ "@opengeni/events": "^0.4.25-canary.1",
67
+ "@opengeni/github": "^0.7.10-canary.1",
68
+ "@opengeni/network": "^0.3.1-canary.9",
69
+ "@opengeni/observability": "^0.8.26-canary.1",
70
+ "@opengeni/runtime": "^2.5.3-canary.1",
71
+ "@opengeni/storage": "^0.2.127-canary.1",
72
+ "@opengeni/tool-gateway": "^0.1.7-canary.1",
73
+ "@opengeni/xai-subscription": "^0.1.4-canary.9",
73
74
  "@temporalio/activity": "^1.17.0",
74
75
  "@temporalio/client": "^1.17.0",
75
76
  "@temporalio/worker": "^1.17.0",
@@ -347,7 +347,7 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
347
347
  });
348
348
  return attached.action === "claimed" && attached.turn.id === turn.id;
349
349
  };
350
- turnLifecycleMetricsFor(observability).start(attempt.turnId);
350
+ turnLifecycleMetricsFor(observability).start({ attemptId: input.attemptId });
351
351
  // §7.5 P3 — pass the accepted billing attribution (externally funded turns
352
352
  // bypass OpenGeni credit/token gates)
353
353
  // AND the optional host `entitlements` port (when bound, its admitRun replaces
@@ -451,7 +451,7 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
451
451
  }
452
452
  recordCanonicalStartupMilestones(appended.canonicalStartupMilestones);
453
453
  if (inputs.length > 0) {
454
- turnLifecycleMetricsFor(observability).progress(attempt.turnId!);
454
+ turnLifecycleMetricsFor(observability).progress({ attemptId: input.attemptId });
455
455
  }
456
456
  activityContext?.heartbeat({
457
457
  ...heartbeatDetails,
@@ -515,7 +515,7 @@ export async function claimTurnAttempt(deps: ClaimTurnDeps): Promise<ClaimTurnOu
515
515
  return false;
516
516
  }
517
517
  recordCanonicalStartupMilestones(result.canonicalStartupMilestones);
518
- turnLifecycleMetricsFor(observability).progress(attempt.turnId!);
518
+ turnLifecycleMetricsFor(observability).progress({ attemptId: input.attemptId });
519
519
  await publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, result.events);
520
520
  if (inputSettlement.turnStatus === "requires_action") {
521
521
  // The settlement transaction committed the parent's child_requires_action
@@ -5,7 +5,7 @@ import {
5
5
  CodexCredentialLeaseAttemptFencedError,
6
6
  CodexCredentialFailoverExhaustedError,
7
7
  CODEX_CREDENTIAL_LEASE_TTL_MS,
8
- recordSessionActiveCodexCredential,
8
+ recordSessionCodexSelectionForTurnAttempt,
9
9
  setSessionCodexPinInTransaction,
10
10
  settleCodexCredentialFailover,
11
11
  withSessionCodexCapacityMutation,
@@ -288,19 +288,40 @@ export async function selectCodexTurnCapacity(
288
288
  leases.codex.confirmedUntilMs !== null;
289
289
  if (leases.codex.held) leases.codex.startHeartbeat();
290
290
 
291
+ const eligibleCount = leased.accounts.filter((account) =>
292
+ isCodexCredentialEligible(account, new Date()),
293
+ ).length;
294
+ const selectionReceipt = providerTurn.effectiveCodexCredentialId
295
+ ? await recordSessionCodexSelectionForTurnAttempt(db, {
296
+ workspaceId: input.workspaceId,
297
+ sessionId: input.sessionId,
298
+ turnId,
299
+ attemptId: input.attemptId,
300
+ executionGeneration: attempt.executionGeneration,
301
+ credentialId: providerTurn.effectiveCodexCredentialId,
302
+ strategy: leased.rotationStrategy,
303
+ reusedLease: leased.reused,
304
+ pinnedCredentialId: sessionPin,
305
+ pinSource: sessionPinSource,
306
+ eligibleCount,
307
+ connectedCount: leased.accounts.length,
308
+ })
309
+ : null;
310
+ if (selectionReceipt)
311
+ await publishDurableSessionEvents(
312
+ bus,
313
+ input.workspaceId,
314
+ input.sessionId,
315
+ selectionReceipt.events,
316
+ );
317
+ const selectionDiagnostics = selectionReceipt?.diagnostics ?? null;
291
318
  const actualOutcome = providerTurn.effectiveCodexCredentialId
292
319
  ? "selected"
293
320
  : rotationDecision.kind === "allCapped" || rotationDecision.kind === "allocatorDisabled"
294
321
  ? "waiting"
295
322
  : "none";
296
- const actualReason = providerTurn.effectiveCodexCredentialId
297
- ? leased.reused
298
- ? "lease_reused"
299
- : sessionPin === providerTurn.effectiveCodexCredentialId
300
- ? "pin"
301
- : rotationDecision.kind === "active" && rotationDecision.moved
302
- ? "rotation"
303
- : "active"
323
+ const actualReason = selectionDiagnostics
324
+ ? selectionDiagnostics.reason
304
325
  : rotationDecision.kind === "allCapped"
305
326
  ? "all_capped"
306
327
  : rotationDecision.kind === "allocatorDisabled"
@@ -362,9 +383,6 @@ export async function selectCodexTurnCapacity(
362
383
  });
363
384
  }
364
385
 
365
- const eligibleCount = leased.accounts.filter((account) =>
366
- isCodexCredentialEligible(account, new Date()),
367
- ).length;
368
386
  const poolDepth = eligibleCount === 0 ? "zero" : eligibleCount === 1 ? "one" : "many";
369
387
  observability.incrementCounter({
370
388
  name: "opengeni_codex_pool_observations_total",
@@ -690,34 +708,7 @@ export async function selectCodexTurnCapacity(
690
708
  return { exit: claimedResult({ status: "cancelled" }) };
691
709
  }
692
710
  if (providerTurn.effectiveCodexCredentialId) {
693
- const priorAccountId = lockedSessionCodexState.lastCredentialId;
694
- if (priorAccountId !== providerTurn.effectiveCodexCredentialId) {
695
- await recordSessionActiveCodexCredential(
696
- db,
697
- input.workspaceId,
698
- input.sessionId,
699
- providerTurn.effectiveCodexCredentialId,
700
- );
701
- const rotated = rotationDecision.kind === "active" && rotationDecision.moved;
702
- await eventing.publish([
703
- {
704
- type: "codex.account.switched",
705
- payload: {
706
- fromAccountId: priorAccountId,
707
- toAccountId: providerTurn.effectiveCodexCredentialId,
708
- reason: rotated ? "rotation" : "manual",
709
- },
710
- },
711
- ]);
712
- }
713
-
714
- const selectionReason = leased.reused
715
- ? "lease_reused"
716
- : sessionPin === providerTurn.effectiveCodexCredentialId
717
- ? "pin"
718
- : rotationDecision.kind === "active" && rotationDecision.moved
719
- ? "rotation"
720
- : "active";
711
+ const selectionReason = selectionDiagnostics!.reason;
721
712
  observability.incrementCounter({
722
713
  name: "opengeni_codex_credential_selections_total",
723
714
  help: "Codex credential selections by strategy and reason.",
@@ -727,19 +718,6 @@ export async function selectCodexTurnCapacity(
727
718
  reason: selectionReason,
728
719
  },
729
720
  });
730
- await eventing.publish([
731
- {
732
- type: "codex.credential.selected",
733
- payload: {
734
- credentialId: providerTurn.effectiveCodexCredentialId,
735
- strategy: leased.rotationStrategy,
736
- reason: selectionReason,
737
- eligibleCount,
738
- connectedCount: leased.accounts.length,
739
- reused: leased.reused,
740
- },
741
- },
742
- ]);
743
721
  }
744
722
  } catch (error) {
745
723
  credentialSelectionOutcome = "failed";
@@ -0,0 +1 @@
1
+ export { codexSelectionDiagnostics } from "@opengeni/db";
@@ -745,12 +745,17 @@ export async function finalizeTurnAttempt(deps: TurnFinalizationDeps): Promise<v
745
745
  status: finalizationError ? "cleanup_failed" : control.activityStatus,
746
746
  durationSeconds,
747
747
  });
748
- if (attempt.turnId && control.activityStatus !== "unknown") {
749
- turnLifecycleMetricsFor(observability).finish(
750
- attempt.turnId,
751
- control.turnMetricOutcome,
748
+ if (attempt.turnId) {
749
+ // This registry describes physical activity ownership, not durable turn
750
+ // settlement. Always clear the exact attempt when its activity unwinds,
751
+ // even when failure settlement could not classify an outcome. Otherwise
752
+ // a recovered successor can make progress while the predecessor leaves a
753
+ // process-local false stuck-turn alert behind.
754
+ turnLifecycleMetricsFor(observability).finish({
755
+ attemptId: input.attemptId,
756
+ outcome: control.turnMetricOutcome,
752
757
  durationSeconds,
753
- );
758
+ });
754
759
  }
755
760
  activitySpan.end({
756
761
  attributes: {
@@ -0,0 +1,61 @@
1
+ import type { AttemptToolDefinition } from "@opengeni/codemode";
2
+ import { AttemptToolResult } from "@opengeni/contracts";
3
+ import { z } from "zod";
4
+
5
+ const selectorSchema = z.union([
6
+ z.object({ operationId: z.string().uuid() }).strict(),
7
+ z.object({ sourceTurnId: z.string().uuid(), sourceCallId: z.string().min(1) }).strict(),
8
+ ]);
9
+
10
+ export type McpOperationReadSelector = z.infer<typeof selectorSchema>;
11
+
12
+ /** Attempt-local adapter. The supplied read closure owns canonical session
13
+ * access, current provider authority, immutable binding checks and settlement.
14
+ * No caller-controlled provider parameters cross this tool boundary. */
15
+ export function createOperationReadAttemptToolDefinition(input: {
16
+ read: (selector: McpOperationReadSelector) => Promise<Record<string, unknown>>;
17
+ }): AttemptToolDefinition {
18
+ return {
19
+ identity: { serverId: "opengeni", toolName: "operation_read" },
20
+ modelName: "operation_read",
21
+ codemodePath: ["opengeni", "operation_read"],
22
+ title: "Read an existing MCP operation outcome",
23
+ description:
24
+ "Read a retained MCP operation and, when supported and currently authorized, observe its provider receipt. " +
25
+ "Use the operationId from the original call, or its exact sourceTurnId and sourceCallId if that response was lost. " +
26
+ "This never repeats the original mutation. Unknown means the outcome is not proven, not that the operation failed. " +
27
+ "Provider support and renewed authorization may be required after reconnecting.",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ operationId: { type: "string", format: "uuid" },
32
+ sourceTurnId: { type: "string", format: "uuid" },
33
+ sourceCallId: { type: "string", minLength: 1 },
34
+ },
35
+ additionalProperties: false,
36
+ oneOf: [
37
+ {
38
+ required: ["operationId"],
39
+ not: { anyOf: [{ required: ["sourceTurnId"] }, { required: ["sourceCallId"] }] },
40
+ },
41
+ { required: ["sourceTurnId", "sourceCallId"], not: { required: ["operationId"] } },
42
+ ],
43
+ },
44
+ annotations: {
45
+ readOnlyHint: true,
46
+ destructiveHint: false,
47
+ idempotentHint: true,
48
+ openWorldHint: true,
49
+ },
50
+ source: "opengeni",
51
+ approval: "none",
52
+ execute: async (args) => {
53
+ const selector = selectorSchema.parse(args);
54
+ const receipt = await input.read(selector);
55
+ return AttemptToolResult.parse({
56
+ content: [{ type: "text", text: JSON.stringify(receipt) }],
57
+ structuredContent: receipt,
58
+ });
59
+ },
60
+ };
61
+ }
@@ -475,6 +475,8 @@ export async function establishTurnSandbox(deps: EstablishTurnSandboxDeps): Prom
475
475
  liveMachineEnrollment?.agentCapabilities.operationResourcePolicy === true,
476
476
  operationCpuQuotaSupported:
477
477
  liveMachineEnrollment?.agentCapabilities.operationCpuQuota === true,
478
+ transactionalFsWriteSupported:
479
+ liveMachineEnrollment?.agentCapabilities.transactionalFsWrite === true,
478
480
  ...(activeSandboxRecord!.scope === "user" && fileAuthoritySubjectId
479
481
  ? {
480
482
  personalMachineAttempt: {
@@ -1,6 +1,5 @@
1
1
  import type { AttemptToolDefinition } from "@opengeni/codemode";
2
2
  import { PublicSkillSearchError, type PublicSkillSearchClient } from "@opengeni/core";
3
- import { listSkillLibraryEntries } from "@opengeni/runtime/skill-library";
4
3
 
5
4
  export type WorkspaceSkillSearchEntry = Readonly<{
6
5
  id: string;
@@ -23,7 +22,7 @@ export function createSkillSearchAttemptToolDefinition(input: {
23
22
  codemodePath: ["opengeni", "skill_search"],
24
23
  title: "Search Skills",
25
24
  description:
26
- "Find installed workspace Skills or available curated and public Skills. Returns identifiers and install sources, not file contents. Search never installs or starts a sandbox. Scope defaults to all; use installed to avoid external search.",
25
+ "Find installed workspace Skills or available public Skills. Returns identifiers and install sources, not file contents. Search never installs or starts a sandbox. Scope defaults to all; use installed to avoid external search.",
27
26
  inputSchema: {
28
27
  type: "object",
29
28
  properties: {
@@ -60,19 +59,6 @@ export function createSkillSearchAttemptToolDefinition(input: {
60
59
  source: entry.source ?? "workspace",
61
60
  installed: !entry.source || entry.source === "workspace",
62
61
  }));
63
- const libraryHits =
64
- scope === "installed"
65
- ? []
66
- : listSkillLibraryEntries()
67
- .filter(matches)
68
- .slice(0, limit)
69
- .map((entry) => ({
70
- id: `library:${entry.id}`,
71
- name: entry.name,
72
- description: entry.description,
73
- source: "library" as const,
74
- libraryId: entry.id,
75
- }));
76
62
  let publicResult: Awaited<ReturnType<PublicSkillSearchClient["search"]>> | null = null;
77
63
  let publicError: {
78
64
  source: "skills_sh";
@@ -93,7 +79,7 @@ export function createSkillSearchAttemptToolDefinition(input: {
93
79
  }
94
80
  const output = {
95
81
  workspace: workspaceHits,
96
- library: libraryHits,
82
+ library: [], // Retained response field for older clients.
97
83
  public: publicResult?.items.map((entry) => ({ ...entry })) ?? [],
98
84
  // A provider outage is not an empty search. Keep local results useful
99
85
  // and expose partial failure explicitly, without leaking network details.
@@ -1062,7 +1062,7 @@ export async function runTurnStreamAttempt(
1062
1062
  currentToolBatchCallIds = new Set<string>();
1063
1063
  currentToolBatchCompletedCallIds = new Set<string>();
1064
1064
  await historySink.reconcileConversationTruth();
1065
- turnLifecycleMetricsFor(observability).progress(attempt.turnId!);
1065
+ turnLifecycleMetricsFor(observability).progress({ attemptId: input.attemptId });
1066
1066
  modelCheckpointMemoryCollector.schedule(observability);
1067
1067
  try {
1068
1068
  await ensureRunAllowed(
@@ -37,6 +37,11 @@ import {
37
37
  resolveGoogleDrivePublicationTarget,
38
38
  } from "../google-drive-publication";
39
39
  import { connectionTokenResolverForTurn } from "../mcp-credentials";
40
+ import { createMcpOperationPersistence } from "@opengeni/db/mcp-operations";
41
+ import { createMcpOperationReadStore } from "../mcp-operation-store";
42
+ import { createMcpOperationObserverResolver } from "../mcp-operation-observer";
43
+ import { readMcpOperation } from "../mcp-operation-reader";
44
+ import { createOperationReadAttemptToolDefinition } from "./mcp-operation-read-tool";
40
45
  import { buildGitHubRestMcpForTurn } from "../../github-rest-mcp";
41
46
  import { materializeConnectorAttachmentsInChannel } from "../connector-attachments";
42
47
  import { allowedFirstPartyMcpToolsForSession, type Settings } from "@opengeni/config";
@@ -668,7 +673,66 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
668
673
  });
669
674
  },
670
675
  });
676
+ const operationRecoveryEnabled = githubRestMcp.settings.mcpServers.some(
677
+ (server) =>
678
+ server.operationRecovery &&
679
+ Object.keys(server.operationRecovery).length > 0 &&
680
+ githubRestMcp.tools.some((tool) => tool.kind === "mcp" && tool.id === server.id),
681
+ );
682
+ const operationAttempt = {
683
+ accountId: input.accountId,
684
+ workspaceId: input.workspaceId,
685
+ sessionId: input.sessionId,
686
+ turnId: turn.id,
687
+ attemptId: input.attemptId,
688
+ executionGeneration: attempt.executionGeneration,
689
+ };
690
+ const operationPersistence = operationRecoveryEnabled
691
+ ? createMcpOperationPersistence(db, operationAttempt)
692
+ : undefined;
693
+ const operationReadStore = operationRecoveryEnabled
694
+ ? createMcpOperationReadStore(db, operationAttempt)
695
+ : undefined;
696
+ const operationObserver = createMcpOperationObserverResolver({
697
+ settings: githubRestMcp.settings,
698
+ workspaceId: input.workspaceId,
699
+ ...(credentialSubjectId ? { credentialSubjectId } : {}),
700
+ resolveCredential,
701
+ assertAttempt: async () => {
702
+ throwIfWorkerShuttingDown();
703
+ throwIfTurnCancelled();
704
+ const current = await getSessionTurnForAttempt(
705
+ db,
706
+ input.workspaceId,
707
+ input.sessionId,
708
+ input.attemptId,
709
+ );
710
+ if (
711
+ !current ||
712
+ current.id !== turn.id ||
713
+ current.executionGeneration !== attempt.executionGeneration
714
+ ) {
715
+ throw new Error("MCP operation reader no longer owns the executing attempt");
716
+ }
717
+ },
718
+ getEnvironment: async () => {
719
+ const prepared = eventing.preparedTools;
720
+ if (!prepared) return null;
721
+ return ((await prepared.ready) ?? prepared).attemptToolEnvironment;
722
+ },
723
+ });
671
724
  const attemptToolDefinitions = [
725
+ ...(operationReadStore
726
+ ? [
727
+ createOperationReadAttemptToolDefinition({
728
+ read: async (selector) =>
729
+ await readMcpOperation(selector, {
730
+ ...operationReadStore,
731
+ resolveObserver: operationObserver,
732
+ }),
733
+ }),
734
+ ]
735
+ : []),
672
736
  ...skillTools,
673
737
  createListModelsAttemptToolDefinition({
674
738
  currentModelId: turnExecutionPolicy.productModelId,
@@ -876,6 +940,7 @@ export async function prepareTurnToolRuntime(deps: PrepareTurnToolRuntimeDeps) {
876
940
  ...(credentialSubjectId ? { credentialSubjectId } : {}),
877
941
  ...(codexAppsAuth ? { codexAppsAuth } : {}),
878
942
  resolveCredential,
943
+ ...(operationPersistence ? { mcpOperationPersistence: operationPersistence } : {}),
879
944
  ...(linkedAuthority
880
945
  ? {
881
946
  authorizeAttemptExecution: async () => {
@@ -18,6 +18,9 @@ export type CodexFleetActualDecisionV1 = {
18
18
  candidateKey: string | null;
19
19
  reason:
20
20
  | "lease_reused"
21
+ | "affinity_reused"
22
+ | "assigned"
23
+ | "switched"
21
24
  | "pin"
22
25
  | "rotation"
23
26
  | "active"