@opengeni/runtime 2.5.2 → 2.5.3-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 (49) hide show
  1. package/dist/{agent-Q2VNH5FV.js → agent-RJQLKFIW.js} +2 -2
  2. package/dist/{chunk-LGLJ5PVD.js → chunk-46BFUGFL.js} +2622 -1368
  3. package/dist/chunk-46BFUGFL.js.map +1 -0
  4. package/dist/{chunk-CD22I76N.js → chunk-FHGLYLBL.js} +2 -2
  5. package/dist/{chunk-25B3EQRW.js → chunk-UNICCRW5.js} +4 -4
  6. package/dist/{chunk-GJC7H2FC.js → chunk-VUEWS4GI.js} +494 -95
  7. package/dist/chunk-VUEWS4GI.js.map +1 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.js +22 -4
  10. package/dist/{localSources-Z5IJIIRV.js → localSources-PVFCAPN2.js} +3 -3
  11. package/dist/mcp-operation-dispatch.d.ts +45 -0
  12. package/dist/mcp-operation-observation.d.ts +137 -0
  13. package/dist/operational-instructions.d.ts +1 -1
  14. package/dist/sandbox/archive-spool.d.ts +14 -0
  15. package/dist/sandbox/host-archive-spool.d.ts +19 -0
  16. package/dist/sandbox/index.d.ts +5 -1
  17. package/dist/sandbox/index.js +7 -3
  18. package/dist/sandbox/routing/backend-resolver.d.ts +1 -0
  19. package/dist/sandbox/selfhosted/file-transfer.d.ts +20 -0
  20. package/dist/sandbox/selfhosted/session.d.ts +7 -0
  21. package/dist/sandbox/workspace-archive.d.ts +7 -0
  22. package/dist/workspace-skills.d.ts +4 -0
  23. package/dist/workspace-tool-gateway.js +4 -4
  24. package/package.json +11 -11
  25. package/src/bundled_default_skills/document-parsing/SKILL.md +36 -0
  26. package/src/bundled_site_skills/opengeni-sites/SKILL.md +1 -1
  27. package/src/index.ts +231 -45
  28. package/src/lazy-tool-transport.ts +2 -0
  29. package/src/mcp-operation-dispatch.ts +144 -0
  30. package/src/mcp-operation-observation.ts +148 -0
  31. package/src/operational-instructions.ts +1 -1
  32. package/src/runtime-skills.ts +7 -2
  33. package/src/sandbox/archive-spool.ts +16 -0
  34. package/src/sandbox/host-archive-spool.ts +1040 -0
  35. package/src/sandbox/index.ts +292 -203
  36. package/src/sandbox/routing/backend-resolver.ts +2 -0
  37. package/src/sandbox/selfhosted/control-rpc.ts +22 -0
  38. package/src/sandbox/selfhosted/fault-rendering.ts +9 -0
  39. package/src/sandbox/selfhosted/file-transfer.ts +160 -0
  40. package/src/sandbox/selfhosted/op-testing.ts +3 -0
  41. package/src/sandbox/selfhosted/session.ts +194 -5
  42. package/src/sandbox/workspace-archive.ts +54 -1
  43. package/src/workspace-skills.ts +171 -31
  44. package/dist/chunk-GJC7H2FC.js.map +0 -1
  45. package/dist/chunk-LGLJ5PVD.js.map +0 -1
  46. /package/dist/{agent-Q2VNH5FV.js.map → agent-RJQLKFIW.js.map} +0 -0
  47. /package/dist/{chunk-CD22I76N.js.map → chunk-FHGLYLBL.js.map} +0 -0
  48. /package/dist/{chunk-25B3EQRW.js.map → chunk-UNICCRW5.js.map} +0 -0
  49. /package/dist/{localSources-Z5IJIIRV.js.map → localSources-PVFCAPN2.js.map} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { type NativeSnapshotDescriptor, type NativeSnapshotRef, type WorkspaceArchiveDescriptor, type WorkspaceTreeFingerprint } from "@opengeni/contracts";
2
+ import type { VerifiedHostWorkspaceArchive } from "./archive-spool.js";
2
3
  export { WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION, decodeNativeSnapshotRef, encodeNativeSnapshotRef, parseWorkspaceArchiveDescriptor, type NativeSnapshotDescriptor, type NativeSnapshotProvider, type NativeSnapshotRef, type TarWorkspaceArchiveDescriptor, type WorkspaceArchiveDescriptor, type WorkspaceTreeFingerprint, } from "@opengeni/contracts";
3
4
  export type VerifiedWorkspaceArchive = {
4
5
  bytes: Uint8Array;
@@ -7,6 +8,12 @@ export type VerifiedWorkspaceArchive = {
7
8
  kind: "tar" | "provider_snapshot";
8
9
  nativeSnapshot?: NativeSnapshotRef;
9
10
  };
11
+ export type VerifiedWorkspaceArchivePayload = VerifiedWorkspaceArchive | VerifiedHostWorkspaceArchive;
12
+ /** Host-backed portable archives can go directly to object storage without an
13
+ * intermediate whole-document string or buffer. Existing inline/provider APIs
14
+ * retain their compatibility path when disk-backed publication is unavailable. */
15
+ export declare function captureWorkspaceArchiveForStorage(session: unknown, capturedAtMs: number, options: WorkspaceArchiveCaptureOptions, objectStorageAvailable: boolean): Promise<VerifiedWorkspaceArchivePayload>;
16
+ export declare function disposeWorkspaceArchive(archive: VerifiedWorkspaceArchivePayload | undefined): Promise<void>;
10
17
  export type WorkspaceArchiveIntegrityCode = "archive_metadata_missing" | "archive_metadata_invalid" | "archive_base64_invalid" | "archive_hash_mismatch" | "archive_hydration_failed" | "workspace_fingerprint_unavailable" | "workspace_changed_during_capture" | "workspace_fingerprint_mismatch" | "native_snapshot_reference_invalid" | "native_snapshot_fallback_unverified";
11
18
  export declare class WorkspaceArchiveIntegrityError extends Error {
12
19
  readonly code: WorkspaceArchiveIntegrityCode;
@@ -1,3 +1,4 @@
1
+ import { type Tool } from "@openai/agents";
1
2
  import { Capability, type SandboxSessionLike } from "@openai/agents/sandbox";
2
3
  export type WorkspaceSkillSearchPath = Readonly<{
3
4
  path: string;
@@ -24,6 +25,9 @@ export declare class WorkspaceSkillsCapability extends Capability {
24
25
  readonly type = "workspace-skills";
25
26
  private discovery?;
26
27
  constructor(searchPaths: readonly WorkspaceSkillSearchPath[], reservedNames?: ReadonlySet<string>, shadowedNames?: ReadonlySet<string>);
28
+ bind(session: SandboxSessionLike): this;
29
+ private discover;
30
+ tools(): Tool<unknown>[];
27
31
  instructions(): Promise<string | null>;
28
32
  }
29
33
  export declare function workspaceSkills(searchPaths: readonly WorkspaceSkillSearchPath[], reservedNames?: Iterable<string>, shadowedNames?: Iterable<string>): WorkspaceSkillsCapability;
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  prepareAgentTools
3
- } from "./chunk-GJC7H2FC.js";
4
- import "./chunk-LGLJ5PVD.js";
5
- import "./chunk-CD22I76N.js";
3
+ } from "./chunk-VUEWS4GI.js";
4
+ import "./chunk-46BFUGFL.js";
5
+ import "./chunk-FHGLYLBL.js";
6
6
  import "./chunk-GB763TSV.js";
7
7
  import "./chunk-UZ66CGC2.js";
8
8
  import "./chunk-JHM4OS7C.js";
9
- import "./chunk-25B3EQRW.js";
9
+ import "./chunk-UNICCRW5.js";
10
10
  import "./chunk-PZ5AY32C.js";
11
11
  export {
12
12
  prepareAgentTools as prepareWorkspaceToolGatewayTools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/runtime",
3
- "version": "2.5.2",
3
+ "version": "2.5.3-canary.1",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -62,16 +62,16 @@
62
62
  "@openai/agents": "0.14.3",
63
63
  "@openai/agents-core": "0.14.3",
64
64
  "@openai/agents-extensions": "0.14.3",
65
- "@opengeni/agent-proto": "^0.5.1",
66
- "@opengeni/capabilities": "^0.3.3",
67
- "@opengeni/codemode": "^0.5.5",
68
- "@opengeni/codex": "^0.2.22",
69
- "@opengeni/config": "^1.1.1",
70
- "@opengeni/contracts": "^3.0.1",
71
- "@opengeni/network": "^0.3.1",
72
- "@opengeni/sdk": "^5.0.3",
73
- "@opengeni/tool-gateway": "^0.1.6",
74
- "@opengeni/xai-subscription": "^0.1.4",
65
+ "@opengeni/agent-proto": "^0.5.1-canary.27",
66
+ "@opengeni/capabilities": "^0.3.3-canary.9",
67
+ "@opengeni/codemode": "^0.5.6-canary.1",
68
+ "@opengeni/codex": "^0.2.22-canary.9",
69
+ "@opengeni/config": "^1.1.2-canary.1",
70
+ "@opengeni/contracts": "^3.0.2-canary.1",
71
+ "@opengeni/network": "^0.3.1-canary.9",
72
+ "@opengeni/sdk": "^5.0.5-canary.1",
73
+ "@opengeni/tool-gateway": "^0.1.7-canary.1",
74
+ "@opengeni/xai-subscription": "^0.1.4-canary.9",
75
75
  "debug": "4.4.3",
76
76
  "modal": "0.9.0",
77
77
  "openai": "6.47.0",
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: document-parsing
3
+ description: Extract readable Markdown from local Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV, and text-based PDF files using the preinstalled AnyDoc runtime.
4
+ license: MIT
5
+ metadata:
6
+ runtime: anydoc@0.1.8
7
+ upstream: https://github.com/firecrawl/anydoc
8
+ ---
9
+
10
+ # Parse local documents
11
+
12
+ Use the preinstalled `anydoc` CLI when a task needs the contents of a local
13
+ office document, ebook, CSV, or text-based PDF. This is read-only extraction;
14
+ it does not edit the source or replace OpenGeni's durable artifact tools.
15
+
16
+ ```bash
17
+ anydoc report.docx
18
+ anydoc slides.pptx -o /tmp/slides.md
19
+ anydoc - --format csv < data-without-extension
20
+ ```
21
+
22
+ Rules:
23
+
24
+ 1. Supported inputs: `.doc`, `.docx`, `.docm`, `.odt`, `.rtf`, `.epub`,
25
+ `.pdf`, `.ppt`, `.pps`, `.pot`, `.pptx`, `.pptm`, `.ppsx`, `.ppsm`,
26
+ `.odp`, `.xls`, `.xlsx`, `.xlsm`, `.xlsb`, `.ods`, and `.csv`.
27
+ 2. Let AnyDoc detect the format from file content. Specify `--format` only for
28
+ signature-less input such as CSV on stdin or a file without a useful name.
29
+ 3. For large output, use `-o` and inspect only the relevant sections instead
30
+ of streaming the whole document into model context.
31
+ 4. Preserve the source file. AnyDoc output is a derived reading aid, not a new
32
+ canonical document or editable artifact.
33
+ 5. Scanned/image-only PDFs need OCR and are unsupported. Encrypted, malformed,
34
+ or resource-limit failures must be reported rather than guessed around.
35
+ 6. Never install or download AnyDoc at runtime. If `anydoc` is unavailable,
36
+ report that the selected compute image lacks the parsing runtime.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: opengeni-sites
3
- description: Build, inspect, edit, validate, and publish OpenGeni Sites as ordinary Bun + React source projects compiled to one self-contained HTML artifact. Use for interactive pages, dashboards, visualizations, workflows, and focused apps that may call workspace tools through the typed Site bridge.
3
+ description: Build, edit, and publish OpenGeni Sites. Proactively recommend Sites for visual or interactive deliverables such as dashboards, explorable reports, landing pages, demos, trackers, portals, directories, calculators, forms, and custom agent interfaces—even when the user doesn't name Sites. Prefer Sites when the delivery format is open. When working on an existing application, follow its established architecture and workflow; consider Sites only when relevant to the requested outcome, not as an automatic conversion. Respect explicit format and platform choices.
4
4
  ---
5
5
 
6
6
  # OpenGeni Sites
package/src/index.ts CHANGED
@@ -1,5 +1,29 @@
1
1
  import type { ModelProviderApi, ResolvedModelProvider, Settings } from "@opengeni/config";
2
2
  import { executeCommandReadWithRefresh } from "./command-read-refresh";
3
+ import {
4
+ captureMcpOperationDispatch,
5
+ hasActiveRecoverableMcpOperation,
6
+ runRecoverableMcpOperation,
7
+ type McpOperationPersistence,
8
+ } from "./mcp-operation-dispatch";
9
+ export {
10
+ captureMcpOperationDispatch,
11
+ hasActiveRecoverableMcpOperation,
12
+ McpOperationOutcomeUnknownError,
13
+ runRecoverableMcpOperation,
14
+ type CapturedMcpOperation,
15
+ type McpOperationPersistence,
16
+ type RecoverableMcpOperation,
17
+ } from "./mcp-operation-dispatch";
18
+ import { assertMcpOperationObservationAuthority } from "./mcp-operation-observation";
19
+ export {
20
+ assertMcpOperationObservationAuthority,
21
+ observeMcpOperation,
22
+ runMcpOperationObservationWithAuthority,
23
+ type McpOperationObservationAuthority,
24
+ type McpObservationBinding,
25
+ type McpObservationReceipt,
26
+ } from "./mcp-operation-observation";
3
27
  import { formatSkillCatalog, type SkillCatalogDescriptor } from "./skill-catalog";
4
28
  export { formatSkillCatalog, type SkillCatalogDescriptor } from "./skill-catalog";
5
29
  import {
@@ -11,6 +35,7 @@ import {
11
35
  AGENT_INSTRUCTIONS_CORE_PLACEHOLDER,
12
36
  collectSandboxEnvironment,
13
37
  configuredProviders,
38
+ McpOperationRecoverySchema,
14
39
  firstPartyMcpInternalBaseUrl,
15
40
  firstPartyMcpInternalWorkspaceUrl,
16
41
  resolveFirstPartyDelegationSecret,
@@ -683,6 +708,8 @@ export type ResolveConnectionCredentialResult =
683
708
  connectionId: string;
684
709
  authoritySource?: "host";
685
710
  authorizeProviderRequest?: () => Promise<boolean>;
711
+ /** Metadata-only immutable authority binding supplied by the trusted worker resolver. */
712
+ operationAuthorityDigest?: string;
686
713
  expiresAt?: Date | null;
687
714
  }
688
715
  | {
@@ -1723,6 +1750,11 @@ type ModelToolInvocation = {
1723
1750
  };
1724
1751
 
1725
1752
  const modelToolInvocation = new AsyncLocalStorage<ModelToolInvocation>();
1753
+ // Correlation must not activate the separate approval-only invocation guard.
1754
+ const modelMcpCallIdentity = new AsyncLocalStorage<{
1755
+ modelName: string;
1756
+ callId: string;
1757
+ } | null>();
1726
1758
  const agentInputWaitYields = new WeakMap<object, InputWaitYield>();
1727
1759
 
1728
1760
  export type BuildAgentOptions = {
@@ -2657,6 +2689,7 @@ export function buildOpenGeniAgent(
2657
2689
  agent as unknown as ApprovalCapableAgent,
2658
2690
  options.approvedToolCallId,
2659
2691
  );
2692
+ installModelMcpCallIdentity(agent as unknown as ApprovalCapableAgent);
2660
2693
  return agent;
2661
2694
  }
2662
2695
 
@@ -2785,6 +2818,7 @@ export function buildOpenGeniAgent(
2785
2818
  agent as unknown as ApprovalCapableAgent,
2786
2819
  options.approvedToolCallId,
2787
2820
  );
2821
+ installModelMcpCallIdentity(agent as unknown as ApprovalCapableAgent);
2788
2822
  return agent;
2789
2823
  }
2790
2824
 
@@ -2876,6 +2910,42 @@ type ApprovalCapableAgent = {
2876
2910
  clone?: (config: unknown) => ApprovalCapableAgent;
2877
2911
  };
2878
2912
 
2913
+ /** Preserve SDK correlation through MCP projection without granting approval.
2914
+ * SandboxAgent clones rebuild instance methods, so reinstall on every clone. */
2915
+ function installModelMcpCallIdentity(agent: ApprovalCapableAgent): void {
2916
+ const listMcpTools = agent.getMcpTools.bind(agent);
2917
+ agent.getMcpTools = async (resolutionContext: unknown) =>
2918
+ (await listMcpTools(resolutionContext)).map((tool) => {
2919
+ if (tool.type !== "function") return tool;
2920
+ const invoke = tool.invoke.bind(tool);
2921
+ return {
2922
+ ...tool,
2923
+ invoke: async (runContext, input, details) => {
2924
+ const callId = details?.toolCall?.callId;
2925
+ return await modelMcpCallIdentity.run(
2926
+ typeof callId === "string" && callId.length > 0
2927
+ ? { modelName: tool.name, callId }
2928
+ : null,
2929
+ async () => await invoke(runContext, input, details),
2930
+ );
2931
+ },
2932
+ };
2933
+ });
2934
+ const clone = agent.clone?.bind(agent);
2935
+ if (clone) {
2936
+ agent.clone = (config: unknown) => {
2937
+ const cloned = clone(config);
2938
+ installModelMcpCallIdentity(cloned);
2939
+ return cloned;
2940
+ };
2941
+ }
2942
+ }
2943
+
2944
+ function modelMcpSourceCallId(modelName: string): string | undefined {
2945
+ const identity = modelMcpCallIdentity.getStore();
2946
+ return identity?.modelName === modelName ? identity.callId : undefined;
2947
+ }
2948
+
2879
2949
  /**
2880
2950
  * Install the approval wrap on a single agent instance: replace `getMcpTools`
2881
2951
  * with one that stamps `needsApproval: () => true` on every MCP tool whose
@@ -3612,6 +3682,8 @@ export type ToolPreparationPhaseMeasurement = {
3612
3682
  };
3613
3683
 
3614
3684
  export type PrepareToolsOptions = {
3685
+ /** Opt-in exact-attempt persistence; absence preserves ordinary MCP execution. */
3686
+ mcpOperationPersistence?: McpOperationPersistence;
3615
3687
  /** Live exact-owner control refresh; API remains read/observation authority. */
3616
3688
  refreshOwnedCommand?: (commandId: string) => Promise<boolean>;
3617
3689
  accountId?: string;
@@ -3986,24 +4058,30 @@ export async function prepareAgentTools(
3986
4058
  }
3987
4059
  const optional = tool.optional === true;
3988
4060
  return {
3989
- server: new PrefixedMcpServer(
3990
- local.server,
3991
- config.id,
3992
- config.allowedTools,
3993
- optional || Boolean(config.connectionRef),
3994
- aggregateToolBudget,
3995
- `${config.id}:${index}`,
3996
- false,
3997
- buildConnectorAttachmentAuthority(
3998
- config,
3999
- options,
4000
- resolvedMcpToolConnectionIds,
4001
- config.url,
4002
- local.resolvedConnectionId,
4061
+ server: configureMcpOperationRecovery(
4062
+ new PrefixedMcpServer(
4063
+ local.server,
4064
+ config.id,
4065
+ config.allowedTools,
4066
+ optional || Boolean(config.connectionRef),
4067
+ aggregateToolBudget,
4068
+ `${config.id}:${index}`,
4069
+ false,
4070
+ buildConnectorAttachmentAuthority(
4071
+ config,
4072
+ options,
4073
+ resolvedMcpToolConnectionIds,
4074
+ config.url,
4075
+ local.resolvedConnectionId,
4076
+ ),
4077
+ tool.eager !== true,
4078
+ local.preflightCall,
4079
+ local.approvalAuthority,
4003
4080
  ),
4004
- tool.eager !== true,
4005
- local.preflightCall,
4006
- local.approvalAuthority,
4081
+ config,
4082
+ options,
4083
+ config.url,
4084
+ false,
4007
4085
  ),
4008
4086
  bestEffort: optional || Boolean(config.connectionRef),
4009
4087
  optional,
@@ -4121,24 +4199,30 @@ export async function prepareAgentTools(
4121
4199
  }
4122
4200
  : {}),
4123
4201
  });
4124
- const server = new PrefixedMcpServer(
4125
- innerServer,
4126
- config.id,
4127
- config.allowedTools,
4128
- bestEffort,
4129
- aggregateToolBudget,
4130
- `${config.id}:${index}`,
4131
- firstParty && !bestEffort,
4132
- buildConnectorAttachmentAuthority(config, options, resolvedMcpToolConnectionIds, url),
4133
- tool.eager !== true,
4134
- undefined,
4135
- undefined,
4136
- firstParty && config.id === "opengeni" && !config.connectionRef && !bridge
4137
- ? inputWaitYield
4138
- : undefined,
4139
- firstParty && config.id === "opengeni" && !config.connectionRef && !bridge
4140
- ? options.refreshOwnedCommand
4141
- : undefined,
4202
+ const server = configureMcpOperationRecovery(
4203
+ new PrefixedMcpServer(
4204
+ innerServer,
4205
+ config.id,
4206
+ config.allowedTools,
4207
+ bestEffort,
4208
+ aggregateToolBudget,
4209
+ `${config.id}:${index}`,
4210
+ firstParty && !bestEffort,
4211
+ buildConnectorAttachmentAuthority(config, options, resolvedMcpToolConnectionIds, url),
4212
+ tool.eager !== true,
4213
+ undefined,
4214
+ undefined,
4215
+ firstParty && config.id === "opengeni" && !config.connectionRef && !bridge
4216
+ ? inputWaitYield
4217
+ : undefined,
4218
+ firstParty && config.id === "opengeni" && !config.connectionRef && !bridge
4219
+ ? options.refreshOwnedCommand
4220
+ : undefined,
4221
+ ),
4222
+ config,
4223
+ options,
4224
+ url,
4225
+ Boolean(config.connectionRef) && !bridge && !isCodexAppsMcpServer(config),
4142
4226
  );
4143
4227
  return {
4144
4228
  server,
@@ -4788,18 +4872,42 @@ async function prepareToolGatewayDefinitionsFromServers(
4788
4872
  }),
4789
4873
  }
4790
4874
  : {}),
4791
- execute: async (args, context) =>
4792
- await server.executeCatalogTool(
4793
- toolName,
4794
- args,
4795
- {
4796
- ...(context.transportMeta ?? {}),
4797
- opengeniOperationId: context.operationId,
4798
- },
4875
+ execute: async (args, context) => {
4876
+ const execute = async () =>
4877
+ await server.executeCatalogTool(
4878
+ toolName,
4879
+ args,
4880
+ {
4881
+ ...(context.transportMeta ?? {}),
4882
+ opengeniOperationId: context.operationId,
4883
+ },
4884
+ {
4885
+ ...(context.signal ? { signal: context.signal } : {}),
4886
+ },
4887
+ );
4888
+ const recovery = configuredMcpOperationRecovery(server, toolName);
4889
+ if (!recovery) return await execute();
4890
+ if (!recovery.supported || !["model", "codemode"].includes(context.caller.kind)) {
4891
+ throw new Error(
4892
+ "MCP operation recovery requires an exact attempt and remote brokered execution",
4893
+ );
4894
+ }
4895
+ return await runRecoverableMcpOperation(
4799
4896
  {
4800
- ...(context.signal ? { signal: context.signal } : {}),
4897
+ operationId: context.operationId,
4898
+ ...(context.sourceCallId === undefined
4899
+ ? {}
4900
+ : { sourceCallId: context.sourceCallId }),
4901
+ serverId: server.registryId,
4902
+ originalTool: toolName,
4903
+ observerTool: recovery.policy[toolName]!.observerTool,
4904
+ destinationDigest: recovery.destinationDigest,
4905
+ argumentDigest: digestCanonicalJson(args),
4801
4906
  },
4802
- ),
4907
+ recovery.persistence,
4908
+ execute,
4909
+ );
4910
+ },
4803
4911
  };
4804
4912
  });
4805
4913
  },
@@ -4818,6 +4926,38 @@ function attemptToolCodemodePath(serverId: string, toolName: string): readonly s
4818
4926
  return [serverId, toolName];
4819
4927
  }
4820
4928
 
4929
+ type PreparedMcpOperationRecovery = {
4930
+ policy: NonNullable<Settings["mcpServers"][number]["operationRecovery"]>;
4931
+ persistence: McpOperationPersistence;
4932
+ destinationDigest: string;
4933
+ supported: boolean;
4934
+ };
4935
+
4936
+ const mcpOperationRecoveryByServer = new WeakMap<PrefixedMcpServer, PreparedMcpOperationRecovery>();
4937
+
4938
+ function configureMcpOperationRecovery(
4939
+ server: PrefixedMcpServer,
4940
+ config: Settings["mcpServers"][number],
4941
+ options: PrepareToolsOptions,
4942
+ destinationUrl: string,
4943
+ brokered: boolean,
4944
+ ): PrefixedMcpServer {
4945
+ if (config.operationRecovery && options.mcpOperationPersistence) {
4946
+ mcpOperationRecoveryByServer.set(server, {
4947
+ policy: McpOperationRecoverySchema.parse(config.operationRecovery),
4948
+ persistence: options.mcpOperationPersistence,
4949
+ destinationDigest: digestCanonicalJson(new URL(destinationUrl).toString()),
4950
+ supported: brokered && attemptToolScope(options) !== null,
4951
+ });
4952
+ }
4953
+ return server;
4954
+ }
4955
+
4956
+ function configuredMcpOperationRecovery(server: PrefixedMcpServer, toolName: string) {
4957
+ const recovery = mcpOperationRecoveryByServer.get(server);
4958
+ return recovery && Object.hasOwn(recovery.policy, toolName) ? recovery : undefined;
4959
+ }
4960
+
4821
4961
  function attemptToolSource(serverId: string): ToolGatewayDefinition["source"] {
4822
4962
  if (serverId === "opengeni" || serverId === "files" || serverId === "docs") {
4823
4963
  return serverId;
@@ -4889,6 +5029,35 @@ function connectionBrokerFetch(
4889
5029
  suppressSetupAuthNeeded,
4890
5030
  );
4891
5031
  }
5032
+ if (request.method === "tools/call") {
5033
+ assertMcpOperationObservationAuthority({
5034
+ serverId: config.id,
5035
+ ...(request.toolName === undefined ? {} : { toolName: request.toolName }),
5036
+ destinationDigest: digestCanonicalJson(destinationUrl),
5037
+ ...(first.operationAuthorityDigest === undefined
5038
+ ? {}
5039
+ : { authorityDigest: first.operationAuthorityDigest }),
5040
+ });
5041
+ if (
5042
+ hasActiveRecoverableMcpOperation() &&
5043
+ (request.batch ||
5044
+ request.id === undefined ||
5045
+ request.id === null ||
5046
+ !request.operationId ||
5047
+ !request.toolName)
5048
+ ) {
5049
+ throw new Error("Recoverable MCP request is missing its exact dispatch identity");
5050
+ }
5051
+ // The helper is a no-op without its private context. Always enter it for
5052
+ // tools/call so a late transport continuation cannot bypass a closed context.
5053
+ await captureMcpOperationDispatch({
5054
+ operationId: request.operationId ?? "",
5055
+ serverId: config.id,
5056
+ toolName: request.toolName ?? "",
5057
+ destinationDigest: digestCanonicalJson(destinationUrl),
5058
+ authorityDigest: first.operationAuthorityDigest ?? "",
5059
+ });
5060
+ }
4892
5061
  const response = await baseFetch(
4893
5062
  fetchInputForAttempt(input),
4894
5063
  withConnectionHeaders(input, init, first.headers),
@@ -6387,9 +6556,11 @@ class AttemptDefinitionMcpServer implements MCPServer {
6387
6556
  ): Promise<any> {
6388
6557
  if (this.closed) throw new Error("local model tool server is closed");
6389
6558
  const environment = await this.requiredAttemptToolEnvironment();
6559
+ const sourceCallId = modelMcpSourceCallId(toolName);
6390
6560
  return await this.resultCustomDataBridge.captureResult(args, async (cleanArgs) =>
6391
6561
  environment.callModel({
6392
6562
  modelName: toolName,
6563
+ ...(sourceCallId === undefined ? {} : { sourceCallId }),
6393
6564
  arguments: cleanArgs ?? {},
6394
6565
  subjectId: this.subjectId,
6395
6566
  ...(meta === undefined ? {} : { transportMeta: meta }),
@@ -6711,8 +6882,10 @@ export class PrefixedMcpServer implements MCPServer {
6711
6882
  }
6712
6883
  return await this.resultCustomDataBridge.captureResult(args, async (cleanArgs) => {
6713
6884
  if (this.attemptToolEnvironment) {
6885
+ const sourceCallId = modelMcpSourceCallId(toolName);
6714
6886
  return await this.attemptToolEnvironment.callModel({
6715
6887
  modelName: toolName,
6888
+ ...(sourceCallId === undefined ? {} : { sourceCallId }),
6716
6889
  arguments: cleanArgs ?? {},
6717
6890
  subjectId: this.attemptToolSubjectId,
6718
6891
  ...(meta === undefined ? {} : { transportMeta: meta }),
@@ -6741,6 +6914,9 @@ export class PrefixedMcpServer implements MCPServer {
6741
6914
  if (!this.isAllowed(unprefixed)) {
6742
6915
  throw new Error(`MCP tool ${unprefixed} is not allowed for server ${this.registryId}`);
6743
6916
  }
6917
+ if (configuredMcpOperationRecovery(this, unprefixed) && !hasActiveRecoverableMcpOperation()) {
6918
+ throw new Error("MCP operation recovery requires its trusted attempt execution context");
6919
+ }
6744
6920
  // Nested prefix wrappers are a projection seam around one physical
6745
6921
  // tools/call. The innermost wrapper owns the single metric observation.
6746
6922
  const recordsPhysicalCall = !(this.inner instanceof PrefixedMcpServer);
@@ -6814,6 +6990,16 @@ export class PrefixedMcpServer implements MCPServer {
6814
6990
  // state. The broker refreshed credentials for future requests but did not
6815
6991
  // replay this call. Preserve that ambiguity as an explicit model-visible
6816
6992
  // error for required and best-effort servers alike.
6993
+ if (hasActiveRecoverableMcpOperation()) {
6994
+ recordOutcome(
6995
+ isToolOutcomeUncertainMcpError(error)
6996
+ ? "outcome_uncertain"
6997
+ : isAuthNeededMcpError(error)
6998
+ ? "auth_needed"
6999
+ : mcpThrownToolCallOutcome(error, options?.signal),
7000
+ );
7001
+ throw error;
7002
+ }
6817
7003
  if (isToolOutcomeUncertainMcpError(error)) {
6818
7004
  recordOutcome("outcome_uncertain");
6819
7005
  return boundedMcpToolResult({
@@ -33,6 +33,8 @@ const ALWAYS_VISIBLE_BASE_TOOL_NAMES: ReadonlySet<string> = new Set([
33
33
  "apply_patch",
34
34
  // The server-backed reader needs neither sandbox setup nor tool search.
35
35
  "skill_read",
36
+ // Repository descriptors route to the live sandbox reader.
37
+ "repository_skill_read",
36
38
  "request_human_input",
37
39
  "list_models",
38
40
  ]);