@opengeni/runtime 4.6.0 → 4.7.0-canary.37090756480001

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 (72) hide show
  1. package/dist/agent-instructions/modules/media.d.ts +3 -0
  2. package/dist/anthropic-messages.d.ts +13 -3
  3. package/dist/anthropic-request-error.d.ts +10 -0
  4. package/dist/assets/codemode-client.json +1 -1
  5. package/dist/{chunk-A22BKN4P.js → chunk-LJNCGUMB.js} +3 -3
  6. package/dist/chunk-LJNCGUMB.js.map +1 -0
  7. package/dist/{chunk-GTWKWJ67.js → chunk-P2OVGKDG.js} +505 -100
  8. package/dist/chunk-P2OVGKDG.js.map +1 -0
  9. package/dist/{chunk-D5DPVA2C.js → chunk-R25L27T3.js} +2047 -1275
  10. package/dist/chunk-R25L27T3.js.map +1 -0
  11. package/dist/claude-subscription-usage.d.ts +14 -4
  12. package/dist/context-compaction.d.ts +7 -7
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +13 -3
  15. package/dist/interaction-tools.d.ts +1 -1
  16. package/dist/mcp-network.js +1 -1
  17. package/dist/runtime-skills.d.ts +1 -0
  18. package/dist/sandbox/channel-a.d.ts +3 -0
  19. package/dist/sandbox/index.d.ts +3 -1
  20. package/dist/sandbox/index.js +7 -1
  21. package/dist/sandbox/provider-command-session.d.ts +21 -0
  22. package/dist/sandbox/providers/modal-command-argv.d.ts +4 -0
  23. package/dist/sandbox/providers/modal-command-control.d.ts +17 -5
  24. package/dist/sandbox/providers/modal-command-observation-errors.d.ts +3 -0
  25. package/dist/sandbox/providers/modal-command-raw-page.d.ts +100 -0
  26. package/dist/sandbox/providers/modal-command-router-wire.d.ts +17 -0
  27. package/dist/sandbox/routing/routing-session.d.ts +10 -0
  28. package/dist/sandbox/turn-tool-cancellation.d.ts +4 -0
  29. package/dist/workspace-tool-gateway.js +3 -3
  30. package/package.json +12 -12
  31. package/src/agent-instructions/PROMPT_CHANGELOG.md +23 -1
  32. package/src/agent-instructions/compose.ts +2 -0
  33. package/src/agent-instructions/modules/media.ts +20 -0
  34. package/src/agent-instructions/runtime-mechanics.ts +8 -0
  35. package/src/anthropic-messages.ts +223 -31
  36. package/src/anthropic-request-error.ts +45 -0
  37. package/src/bundled_default_skills/opengeni-client/SKILL.md +9 -7
  38. package/src/bundled_default_skills/opengeni-client/references/agent-recipes.md +1 -1
  39. package/src/bundled_default_skills/opengeni-client/references/api-workflows.md +61 -0
  40. package/src/bundled_default_skills/opengeni-client/references/compatibility-and-troubleshooting.md +86 -0
  41. package/src/bundled_default_skills/opengeni-client/references/data-tools-and-credentials.md +82 -0
  42. package/src/bundled_default_skills/opengeni-client/references/discovery-and-autonomy.md +32 -25
  43. package/src/bundled_default_skills/opengeni-client/references/external-users-and-connect.md +17 -0
  44. package/src/bundled_default_skills/opengeni-client/references/product-shapes-and-ui.md +156 -0
  45. package/src/bundled_schedule_skills/opengeni-schedules/SKILL.md +56 -0
  46. package/src/claude-subscription-usage.ts +55 -9
  47. package/src/context-compaction.ts +16 -9
  48. package/src/index.ts +85 -18
  49. package/src/interaction-tools.ts +113 -3
  50. package/src/lazy-tool-transport.ts +26 -10
  51. package/src/mcp-network.ts +4 -2
  52. package/src/model-provider-client.ts +11 -3
  53. package/src/model-provider-routing.ts +4 -0
  54. package/src/runtime-skills.ts +8 -0
  55. package/src/sandbox/channel-a.ts +3 -0
  56. package/src/sandbox/index.ts +11 -1
  57. package/src/sandbox/provider-command-session.ts +58 -0
  58. package/src/sandbox/provider-errors.ts +43 -9
  59. package/src/sandbox/providers/modal-command-argv.ts +20 -0
  60. package/src/sandbox/providers/modal-command-control.ts +422 -116
  61. package/src/sandbox/providers/modal-command-observation-errors.ts +65 -0
  62. package/src/sandbox/providers/modal-command-raw-page.ts +147 -0
  63. package/src/sandbox/providers/modal-command-router-wire.ts +104 -12
  64. package/src/sandbox/providers/modal-command-session.ts +26 -4
  65. package/src/sandbox/providers/modal-legacy-command-control.ts +2 -9
  66. package/src/sandbox/providers/modal-materialization-verification.ts +70 -6
  67. package/src/sandbox/routing/routing-session.ts +140 -41
  68. package/src/sandbox/run-credentials.ts +62 -19
  69. package/src/sandbox/turn-tool-cancellation.ts +159 -42
  70. package/dist/chunk-A22BKN4P.js.map +0 -1
  71. package/dist/chunk-D5DPVA2C.js.map +0 -1
  72. package/dist/chunk-GTWKWJ67.js.map +0 -1
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * The checkpoint model sees the current active history plus one fixed
5
5
  * checkpoint prompt, then the active history is rebuilt from the newest real
6
- * user messages within one cumulative 20k-token budget plus one summary.
7
- * Assistant messages, tool calls/results, reasoning, and images are removed
8
- * from the active model-facing history; the database audit rows remain.
6
+ * user/system input messages within one cumulative 20k-token budget plus one summary.
7
+ * Assistant messages, tool calls/results, and reasoning are removed from the
8
+ * active model-facing history; retained input images and database audit rows remain.
9
9
  */
10
10
 
11
11
  import {
@@ -118,11 +118,16 @@ function itemRole(item: unknown): string | undefined {
118
118
  return typeof role === "string" ? role : undefined;
119
119
  }
120
120
 
121
- /** A user-authored `message` item is the only legal turn boundary. */
121
+ /** A real user-role `message` item. */
122
122
  export function isUserMessage(item: unknown): boolean {
123
123
  return itemType(item) === "message" && itemRole(item) === "user";
124
124
  }
125
125
 
126
+ /** Accepted machine-input batches are canonical system messages, not user intent. */
127
+ function isPortableInputMessage(item: unknown): boolean {
128
+ return isUserMessage(item) || (itemType(item) === "message" && itemRole(item) === "system");
129
+ }
130
+
126
131
  /** True for our synthetic compaction summary item. */
127
132
  export function isCompactionSummary(item: unknown): boolean {
128
133
  return (
@@ -1617,7 +1622,7 @@ function oldestLogicalUnitCuts(items: readonly CompactionItem[]): number[] {
1617
1622
 
1618
1623
  /**
1619
1624
  * Build the active history after compaction:
1620
- * the newest real user messages that fit one cumulative, model-bounded budget
1625
+ * the newest user/system input messages that fit one cumulative, model-bounded budget
1621
1626
  * (prior summaries excluded, retained images preserved) plus one marked summary item.
1622
1627
  */
1623
1628
  export function buildCompactionReplacementHistory(
@@ -1633,7 +1638,7 @@ export function buildCompactionReplacementHistory(
1633
1638
  );
1634
1639
  for (let index = items.length - 1; index >= 0 && remaining > 0; index -= 1) {
1635
1640
  const item = items[index]!;
1636
- if (!isUserMessage(item) || isCompactionSummary(item) || isAttachmentCatalog(item)) {
1641
+ if (!isPortableInputMessage(item) || isCompactionSummary(item) || isAttachmentCatalog(item)) {
1637
1642
  continue;
1638
1643
  }
1639
1644
  const textTokens = estimateTextTokens(messageText(item));
@@ -1667,18 +1672,20 @@ export function isRemoteCompactionItem(item: unknown): item is CompactionItem {
1667
1672
  );
1668
1673
  }
1669
1674
 
1670
- /** Messages retained beside a remote v2 compaction blob (user + developer). */
1675
+ /** Messages retained beside a remote v2 compaction blob (user + system + developer). */
1671
1676
  export function isRetainedRemoteV2Message(item: unknown): boolean {
1672
1677
  if (isCompactionSummary(item) || isAttachmentCatalog(item) || itemType(item) === "compaction") {
1673
1678
  return false;
1674
1679
  }
1675
1680
  const role = itemRole(item);
1676
- return itemType(item) === "message" && (role === "user" || role === "developer");
1681
+ return (
1682
+ itemType(item) === "message" && (role === "user" || role === "system" || role === "developer")
1683
+ );
1677
1684
  }
1678
1685
 
1679
1686
  /**
1680
1687
  * Build the active history after Codex remote compaction v2:
1681
- * newest retained user/developer messages within the CLI 64k budget plus the
1688
+ * newest retained user/system/developer messages within the CLI 64k budget plus the
1682
1689
  * opaque `{ type: "compaction", encrypted_content }` item.
1683
1690
  *
1684
1691
  * Both modes preserve retained image parts. Charge their projected image
package/src/index.ts CHANGED
@@ -6,11 +6,13 @@ import {
6
6
  } from "./prepared-compaction-request";
7
7
  export { preparedCompactionRequest, queuePreparedCompaction } from "./prepared-compaction-request";
8
8
  import { AnthropicMessagesModel } from "./anthropic-messages";
9
+ export { AnthropicProviderRejection } from "./anthropic-messages";
9
10
  import { instrumentedModelFetch } from "./model-provider-client";
10
11
  import type { ModelProviderApi, ResolvedModelProvider, Settings } from "@opengeni/config";
11
12
  import { isRunMcpCredentialError, RunMcpCredentials } from "./mcp-run-credentials";
12
13
  import { normalizeCredentialProviderMcpUrl } from "@opengeni/contracts";
13
14
  export { RunMcpCredentials, RunMcpCredentialError } from "./mcp-run-credentials";
15
+ export { AnthropicRequestError } from "./anthropic-request-error";
14
16
  import { executeCommandReadWithRefresh } from "./command-read-refresh";
15
17
  import {
16
18
  captureMcpOperationDispatch,
@@ -266,6 +268,7 @@ import {
266
268
  import { AsyncLocalStorage } from "node:async_hooks";
267
269
  import { createHash, randomUUID } from "node:crypto";
268
270
  import { dirname, isAbsolute, join, posix as posixPath } from "node:path";
271
+ import { gzipSync } from "node:zlib";
269
272
 
270
273
  import { z } from "zod";
271
274
 
@@ -312,6 +315,7 @@ import {
312
315
  createSandboxClient,
313
316
  isModalTaskExecStartPreDispatchUnavailableError,
314
317
  isModalCommandStartOutcomeUnknownError,
318
+ isProviderCommandObservationUnavailableError,
315
319
  isRoutingMutationOutcomeUnknownError,
316
320
  renderRoutingMutationOutcomeUnknownToolResult,
317
321
  repairSerializedRunStateExposedPorts,
@@ -3974,6 +3978,9 @@ function buildAgentCapabilitiesFromComposition(
3974
3978
  // Preserve that behavior except for client-side, pre-dispatch Modal
3975
3979
  // readiness proof, which reaches bounded same-turn recovery.
3976
3980
  execCommandErrorFunction: (_context, error) => {
3981
+ if (isProviderCommandObservationUnavailableError(error)) {
3982
+ return "Managed sandbox command observation unavailable. Outcome unknown. Do not replay the command or resend stdin; observe the existing invocation.";
3983
+ }
3977
3984
  if (isModalTaskExecStartPreDispatchUnavailableError(error)) throw error;
3978
3985
  if (isRoutingMutationOutcomeUnknownError(error)) {
3979
3986
  // The outer physical fence must retain the exact process before
@@ -11766,11 +11773,11 @@ const RIG_SETUP_PROVIDER_IMAGE_MARKER_ROOT = "/var/opengeni";
11766
11773
  // Modal's command transport caps aggregate argv at 64 KiB. Cancellation and
11767
11774
  // run-as wrappers duplicate/expand this command, so stage moderate scripts too.
11768
11775
  const RIG_SETUP_INLINE_COMMAND_MAX_BYTES = 4 * 1024;
11769
- // The cancellation fence embeds a lifecycle command twice, then the current
11770
- // runAs wrapper repeats it across several execution branches. Keep each base64
11771
- // chunk below Modal's 64-KiB aggregate argument ceiling after both wrappers.
11772
- const RIG_SETUP_PAYLOAD_CHUNK_CHARS = 7 * 1024;
11776
+ // Both cancellation and the SDK run-as wrapper repeat the payload three times.
11777
+ // Leave room for their fixed shell programs under Modal's 64-KiB argv ceiling.
11778
+ const RIG_SETUP_PAYLOAD_CHUNK_CHARS = 2 * 1024;
11773
11779
  const RIG_SETUP_PAYLOAD_ROOT = "/tmp/opengeni/rig-setup-payloads";
11780
+ const RIG_SETUP_GZIP_SENTINEL = "__OPENGENI_SETUP_GZIP__";
11774
11781
 
11775
11782
  export type RigSetupScriptCommandOptions = {
11776
11783
  timeoutMs?: number;
@@ -11907,22 +11914,55 @@ async function stageRigSetupScript(
11907
11914
  session: SandboxSessionLike,
11908
11915
  script: string,
11909
11916
  context: SandboxLifecycleHookContext,
11917
+ options: { payloadRoot?: string; label?: string } = {},
11910
11918
  ): Promise<string> {
11911
- const payloadPath = `${RIG_SETUP_PAYLOAD_ROOT}/${randomUUID()}.sh`;
11919
+ const payloadRoot = options.payloadRoot ?? RIG_SETUP_PAYLOAD_ROOT;
11920
+ const payloadPath = `${payloadRoot}/${randomUUID()}.sh`;
11912
11921
  const encodedPath = `${payloadPath}.b64`;
11913
- const encoded = Buffer.from(script, "utf8").toString("base64");
11914
- const commands = [
11915
- `set -eu\numask 077\nmkdir -p ${shellQuote(RIG_SETUP_PAYLOAD_ROOT)}\n: > ${shellQuote(encodedPath)}`,
11916
- ];
11917
- for (let offset = 0; offset < encoded.length; offset += RIG_SETUP_PAYLOAD_CHUNK_CHARS) {
11922
+ const bytes = Buffer.from(script, "utf8");
11923
+ const compressed = gzipSync(bytes);
11924
+ const compressionUseful = compressed.length < bytes.length;
11925
+ try {
11926
+ // Repeated shell programs compress well. Probe in the existing bootstrap
11927
+ // call, retaining the same bounded transfer on machines without gzip.
11928
+ const bootstrap = await runSandboxLifecycleCommand(
11929
+ session,
11930
+ {
11931
+ cmd: [
11932
+ "set -eu",
11933
+ "umask 077",
11934
+ `mkdir -p ${shellQuote(payloadRoot)}`,
11935
+ `: > ${shellQuote(encodedPath)}`,
11936
+ ...(compressionUseful
11937
+ ? [
11938
+ `if command -v gzip >/dev/null 2>&1; then printf '%s\\n' ${shellQuote(RIG_SETUP_GZIP_SENTINEL)}; fi`,
11939
+ ]
11940
+ : []),
11941
+ ].join("\n"),
11942
+ workdir: "/workspace",
11943
+ ...(context.runAs ? { runAs: context.runAs } : {}),
11944
+ yieldTimeMs: SANDBOX_LIFECYCLE_COMMAND_TIMEOUT_MS,
11945
+ maxOutputTokens: 4_000,
11946
+ },
11947
+ context.commandRunner,
11948
+ );
11949
+ assertSandboxCommandSucceeded(
11950
+ bootstrap,
11951
+ options.label ?? "Sandbox Environment setup payload staging",
11952
+ );
11953
+ const useCompression =
11954
+ compressionUseful &&
11955
+ sandboxCommandOutput(bootstrap).split(/\r?\n/u).includes(RIG_SETUP_GZIP_SENTINEL);
11956
+ const encoded = (useCompression ? compressed : bytes).toString("base64");
11957
+ const commands: string[] = [];
11958
+ for (let offset = 0; offset < encoded.length; offset += RIG_SETUP_PAYLOAD_CHUNK_CHARS) {
11959
+ commands.push(
11960
+ `printf '%s' ${shellQuote(encoded.slice(offset, offset + RIG_SETUP_PAYLOAD_CHUNK_CHARS))} >> ${shellQuote(encodedPath)}`,
11961
+ );
11962
+ }
11918
11963
  commands.push(
11919
- `printf '%s' ${shellQuote(encoded.slice(offset, offset + RIG_SETUP_PAYLOAD_CHUNK_CHARS))} >> ${shellQuote(encodedPath)}`,
11964
+ `set -eu\nbase64 -d < ${shellQuote(encodedPath)}${useCompression ? " | gzip -dc" : ""} > ${shellQuote(payloadPath)}\nchmod 0700 ${shellQuote(payloadPath)}\nrm -f ${shellQuote(encodedPath)}`,
11920
11965
  );
11921
- }
11922
- commands.push(
11923
- `set -eu\nbase64 -d ${shellQuote(encodedPath)} > ${shellQuote(payloadPath)}\nchmod 0700 ${shellQuote(payloadPath)}\nrm -f ${shellQuote(encodedPath)}`,
11924
- );
11925
- try {
11926
11966
  for (const command of commands) {
11927
11967
  const result = await runSandboxLifecycleCommand(
11928
11968
  session,
@@ -11935,7 +11975,10 @@ async function stageRigSetupScript(
11935
11975
  },
11936
11976
  context.commandRunner,
11937
11977
  );
11938
- assertSandboxCommandSucceeded(result, "Sandbox Environment setup payload staging");
11978
+ assertSandboxCommandSucceeded(
11979
+ result,
11980
+ options.label ?? "Sandbox Environment setup payload staging",
11981
+ );
11939
11982
  }
11940
11983
  return payloadPath;
11941
11984
  } catch (error) {
@@ -12168,6 +12211,7 @@ export async function runRepositoryCloneHook(
12168
12211
  editor: null,
12169
12212
  staged: [],
12170
12213
  };
12214
+ let stagedCloneScript: string | null = null;
12171
12215
  try {
12172
12216
  // Direct provider tokens retain the established off-manifest per-exec seed.
12173
12217
  // Smart-Git broker bearers take a stricter path: stage opaque bytes through
@@ -12195,9 +12239,19 @@ export async function runRepositoryCloneHook(
12195
12239
  stagedBrokerSeeds.staged,
12196
12240
  options,
12197
12241
  );
12198
- const command = sandboxGitProvisioningCommand(
12242
+ let command = sandboxGitProvisioningCommand(
12199
12243
  seedPrefix ? `${seedPrefix}\n${cloneCommand}` : cloneCommand,
12200
12244
  );
12245
+ // SDK setup also wraps run-as commands and cancellation can expand them.
12246
+ // Reuse the bounded script transport instead of sending the whole clone
12247
+ // program through those nested shell arguments.
12248
+ if (Buffer.byteLength(command, "utf8") > RIG_SETUP_INLINE_COMMAND_MAX_BYTES) {
12249
+ stagedCloneScript = await stageRigSetupScript(session, command, context, {
12250
+ payloadRoot: "/tmp/opengeni/repository-setup-payloads",
12251
+ label: "Repository setup payload staging",
12252
+ });
12253
+ command = `exec /bin/sh ${shellQuote(stagedCloneScript)}`;
12254
+ }
12201
12255
  const result = await runSandboxLifecycleCommand(
12202
12256
  session,
12203
12257
  {
@@ -12237,6 +12291,19 @@ export async function runRepositoryCloneHook(
12237
12291
  });
12238
12292
  throw error;
12239
12293
  } finally {
12294
+ if (stagedCloneScript) {
12295
+ await runSandboxLifecycleCommand(
12296
+ session,
12297
+ {
12298
+ cmd: `rm -f ${shellQuote(stagedCloneScript)} ${shellQuote(`${stagedCloneScript}.b64`)}`,
12299
+ workdir: "/workspace",
12300
+ ...(context.runAs ? { runAs: context.runAs } : {}),
12301
+ yieldTimeMs: SANDBOX_LIFECYCLE_COMMAND_TIMEOUT_MS,
12302
+ maxOutputTokens: 1_000,
12303
+ },
12304
+ context.commandRunner,
12305
+ ).catch(() => undefined);
12306
+ }
12240
12307
  if (stagedBrokerSeeds.editor) {
12241
12308
  await cleanupStagedGitCredentialSeeds(stagedBrokerSeeds.editor, stagedBrokerSeeds.staged);
12242
12309
  }
@@ -9,6 +9,10 @@ import {
9
9
  BrowserActionReceipt,
10
10
  BrowserClipboard,
11
11
  BrowserDiagnosticBatch,
12
+ BrowserDownload,
13
+ BrowserDownloadListResponse,
14
+ BrowserDownloadSaveRequest,
15
+ BrowserDownloadSaveResponse,
12
16
  BrowserDomReadResponse,
13
17
  BrowserDomReadLocator,
14
18
  BrowserDomReadSelector,
@@ -279,6 +283,8 @@ const TOOL_PERMISSION = {
279
283
  browser_act: "sessions:control",
280
284
  browser_clipboard: "sessions:read",
281
285
  browser_debug: "sessions:read",
286
+ browser_downloads: "sessions:read",
287
+ browser_download_save: ["sessions:control", "files:upload"],
282
288
  browser_auth: "sessions:control",
283
289
  interaction_request_human: "sessions:control",
284
290
  browser_identity: "sessions:control",
@@ -290,7 +296,7 @@ const TOOL_PERMISSION = {
290
296
  computer_clipboard: "sessions:read",
291
297
  computer_act: "sessions:control",
292
298
  computer_lifecycle: "sessions:control",
293
- } as const satisfies Record<InteractionAttemptToolName, Permission>;
299
+ } as const satisfies Record<InteractionAttemptToolName, Permission | readonly Permission[]>;
294
300
 
295
301
  const DiscoveryInput = z
296
302
  .object({
@@ -618,6 +624,26 @@ const ComputerLifecycleInput = z
618
624
 
619
625
  const TERMINAL_LIFECYCLES = new Set(["ended", "failed"]);
620
626
 
627
+ const BrowserDownloadsInput = z
628
+ .object({
629
+ browserSessionId: z.string().uuid(),
630
+ operation: z.enum(["list", "get"]).default("list"),
631
+ downloadId: z.string().uuid().optional(),
632
+ })
633
+ .strict()
634
+ .superRefine((value, context) => {
635
+ if ((value.operation === "get") !== (value.downloadId !== undefined)) {
636
+ context.addIssue({
637
+ code: "custom",
638
+ path: ["downloadId"],
639
+ message: "downloadId is required only for operation=get",
640
+ });
641
+ }
642
+ });
643
+ const BrowserDownloadSaveInput = BrowserDownloadSaveRequest.omit({ operationId: true })
644
+ .extend({ browserSessionId: z.string().uuid(), downloadId: z.string().uuid() })
645
+ .strict();
646
+
621
647
  export type CreateInteractionAttemptToolsInput = {
622
648
  transport: InteractionTransport;
623
649
  workspaceId: string;
@@ -1039,6 +1065,82 @@ export function createInteractionAttemptToolDefinitions(
1039
1065
  ),
1040
1066
  });
1041
1067
 
1068
+ add({
1069
+ name: "browser_downloads",
1070
+ codemodePath: ["interaction", "browser", "downloads"],
1071
+ title: "Inspect browser downloads",
1072
+ description:
1073
+ "List browser-produced files or get one exact download by id. Metadata identifies completed bytes and their SHA-256; diagnostics alone do not expose a download id or file bytes. Use browser_download_save to materialize a completed download in its source session's workspace. Attached browsers and Lightpanda do not expose managed downloads.",
1074
+ input: BrowserDownloadsInput,
1075
+ output: z.union([BrowserDownloadListResponse, BrowserDownload]),
1076
+ readOnly: true,
1077
+ idempotent: true,
1078
+ execute: async (value) => {
1079
+ if (value.operation === "get") {
1080
+ const download = await input.transport.getBrowserDownload(
1081
+ input.workspaceId,
1082
+ value.browserSessionId,
1083
+ value.downloadId!,
1084
+ );
1085
+ if (
1086
+ download.browserSessionId !== value.browserSessionId ||
1087
+ download.id !== value.downloadId
1088
+ ) {
1089
+ throw new Error("Browser download belongs to another resource");
1090
+ }
1091
+ return download;
1092
+ }
1093
+ const response = await input.transport.listBrowserDownloads(
1094
+ input.workspaceId,
1095
+ value.browserSessionId,
1096
+ );
1097
+ if (
1098
+ response.browserSessionId !== value.browserSessionId ||
1099
+ response.downloads.some(
1100
+ (download) =>
1101
+ download.browserSessionId !== value.browserSessionId ||
1102
+ download.controllerGeneration !== response.controllerGeneration,
1103
+ )
1104
+ ) {
1105
+ throw new Error("Browser downloads belong to another session binding");
1106
+ }
1107
+ return response;
1108
+ },
1109
+ });
1110
+
1111
+ add({
1112
+ name: "browser_download_save",
1113
+ codemodePath: ["interaction", "browser", "downloadSave"],
1114
+ title: "Save browser download to workspace",
1115
+ description:
1116
+ "Save one exact completed managed browser download to a portable relative path in the browser's source session workspace. Requires sessions:control and files:upload. Returns the materialized destinationPath, fileId and integrity metadata; read the saved bytes with ordinary workspace file tools. Existing files are protected unless overwrite=true. The attempt operation id fences retries; uncertain outcomes must reconcile that same operation. Attached browsers and Lightpanda cannot publish managed downloads.",
1117
+ input: BrowserDownloadSaveInput,
1118
+ output: BrowserDownloadSaveResponse,
1119
+ readOnly: false,
1120
+ idempotent: true,
1121
+ execute: async (value, context) => {
1122
+ const response = await input.transport.saveBrowserDownload(
1123
+ input.workspaceId,
1124
+ value.browserSessionId,
1125
+ value.downloadId,
1126
+ {
1127
+ operationId: context.operationId,
1128
+ destinationPath: value.destinationPath,
1129
+ overwrite: value.overwrite,
1130
+ },
1131
+ );
1132
+ if (
1133
+ response.download.browserSessionId !== value.browserSessionId ||
1134
+ response.download.id !== value.downloadId ||
1135
+ response.operationId !== context.operationId ||
1136
+ response.destinationPath !== value.destinationPath
1137
+ ) {
1138
+ throw new Error("Browser download save returned another operation binding");
1139
+ }
1140
+ return response;
1141
+ },
1142
+ });
1143
+
1042
1144
  add({
1043
1145
  name: "browser_auth",
1044
1146
  codemodePath: ["interaction", "browser", "auth"],
@@ -1992,8 +2094,16 @@ function jsonSchema(schema: z.ZodType): AttemptToolJsonSchema {
1992
2094
  return z.toJSONSchema(schema, { target: "draft-2020-12" }) as AttemptToolJsonSchema;
1993
2095
  }
1994
2096
 
1995
- function hasToolPermission(permissions: readonly Permission[], required: Permission): boolean {
1996
- return permissions.includes(required) || permissions.includes("workspace:admin");
2097
+ function hasToolPermission(
2098
+ permissions: readonly Permission[],
2099
+ required: Permission | readonly Permission[],
2100
+ ): boolean {
2101
+ return (
2102
+ permissions.includes("workspace:admin") ||
2103
+ (typeof required === "string" ? [required] : required).every((permission) =>
2104
+ permissions.includes(permission),
2105
+ )
2106
+ );
1997
2107
  }
1998
2108
 
1999
2109
  function firstPartyApiBaseUrl(settings: Settings, workspaceId: string): string {
@@ -477,10 +477,10 @@ export class LazyToolRuntime {
477
477
  typeof args.limit === "number" && Number.isFinite(args.limit)
478
478
  ? Math.max(1, Math.min(40, Math.floor(args.limit)))
479
479
  : 20;
480
- const tools = [...new Set(this.searchableTools(this.currentTools))]
480
+ const authorizedTools = [...new Set(this.searchableTools(this.currentTools))]
481
481
  .filter(isFunctionTool)
482
- .filter((tool) => tool.name.startsWith(prefix))
483
482
  .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
483
+ const tools = authorizedTools.filter((tool) => tool.name.startsWith(prefix));
484
484
  const cursorIndex =
485
485
  cursor === undefined ? -1 : tools.findIndex((tool) => tool.name === cursor);
486
486
  if (cursor !== undefined && cursorIndex === -1) {
@@ -513,17 +513,33 @@ export class LazyToolRuntime {
513
513
  }
514
514
  descriptors.push(descriptor);
515
515
  }
516
- return JSON.stringify({
516
+ const result = {
517
517
  tools: descriptors,
518
518
  total: tools.length,
519
519
  nextCursor: index < tools.length ? descriptors.at(-1)!.name : null,
520
- ...(prefix && tools.length === 0
521
- ? {
522
- message:
523
- "No tool names match this literal prefix. Retry tool_list without namePrefix to browse the authorized catalog; tool names can include a server namespace.",
524
- }
525
- : {}),
526
- });
520
+ };
521
+ if (!prefix || tools.length > 0) return JSON.stringify(result);
522
+
523
+ // Preserve literal-prefix pagination. Recovery hints are descriptors
524
+ // from the same authorized deferred pool, never schemas or new grants.
525
+ const recovery = {
526
+ ...result,
527
+ message:
528
+ "No tool names match this literal prefix. This does not establish capability absence. Load any suggested exact names with tool_search, or retry tool_list without namePrefix to browse the authorized catalog.",
529
+ suggestions: [] as { name: string; description: string }[],
530
+ };
531
+ for (const candidate of authorizedTools) {
532
+ if (!candidate.name.includes(prefix)) continue;
533
+ const suggestion = {
534
+ name: candidate.name,
535
+ description: Array.from(candidate.description).slice(0, 160).join(""),
536
+ };
537
+ const next = { ...recovery, suggestions: [...recovery.suggestions, suggestion] };
538
+ if (Buffer.byteLength(JSON.stringify(next)) > 16 * 1024) continue;
539
+ recovery.suggestions.push(suggestion);
540
+ if (recovery.suggestions.length >= Math.min(limit, 8)) break;
541
+ }
542
+ return JSON.stringify(recovery);
527
543
  },
528
544
  }) as unknown as Tool;
529
545
  }
@@ -17,7 +17,9 @@ export const MCP_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
17
17
  // a lower HTTP limit rejects valid calls before the tool can execute and turns
18
18
  // a deterministic transport refusal into apparent outcome uncertainty.
19
19
  export const MCP_MAX_INBOUND_REQUEST_BYTES = CODEMODE_ARGUMENTS_MAX_BYTES + 64 * 1024;
20
- export const MCP_MAX_TOOL_DEFINITION_BYTES = 128 * 1024;
20
+ // Rich nested schemas fit within one bounded discovery/disclosure envelope.
21
+ // Whole-server and aggregate limits still bound the complete catalog.
22
+ export const MCP_MAX_TOOL_DEFINITION_BYTES = 512 * 1024;
21
23
  export const MCP_MAX_TOOL_LIST_BYTES = 4 * 1024 * 1024;
22
24
  export const MCP_MAX_AGGREGATE_TOOL_LIST_ENTRIES = MCP_MAX_CATALOG_TOOL_ENTRIES;
23
25
  // One provider may use the available catalog allowance. The shared budget
@@ -25,7 +27,7 @@ export const MCP_MAX_AGGREGATE_TOOL_LIST_ENTRIES = MCP_MAX_CATALOG_TOOL_ENTRIES;
25
27
  // silently excluded otherwise bounded catalogs from best-effort discovery.
26
28
  export const MCP_MAX_TOOL_LIST_ENTRIES = MCP_MAX_AGGREGATE_TOOL_LIST_ENTRIES;
27
29
  export const MCP_MAX_TOOL_RESULT_BYTES = 1024 * 1024;
28
- export const MCP_MAX_TOOL_SEARCH_DISCLOSURE_BYTES = 256 * 1024;
30
+ export const MCP_MAX_TOOL_SEARCH_DISCLOSURE_BYTES = MCP_MAX_TOOL_DEFINITION_BYTES + 16 * 1024;
29
31
  export const MCP_MAX_SELECTED_SERVERS = 64;
30
32
  export const MCP_MAX_CONCURRENT_SERVER_OPERATIONS = 8;
31
33
  // @openai/agents has a separate lifecycle fence around MCPServer.connect().
@@ -30,7 +30,7 @@ import { recordModelTransportStarted } from "./model-preparation-diagnostics";
30
30
  import { captureProviderRequestBody } from "./model-request-capture";
31
31
  import { withoutQuotaExhaustedRetries } from "./provider-quota";
32
32
  import {
33
- observeClaudeUsageResponse,
33
+ captureClaudeRequestToken,
34
34
  prepareClaudeSubscriptionRequest,
35
35
  } from "./claude-subscription-usage";
36
36
 
@@ -266,6 +266,12 @@ function withoutAuthenticationHeaders(inner: typeof fetch): typeof fetch {
266
266
  }
267
267
 
268
268
  export function buildProviderClient(provider: ResolvedModelProvider, settings: Settings): OpenAI {
269
+ if (
270
+ (provider.kind === "direct-openai-workspace" || provider.kind === "direct-azure-workspace") &&
271
+ !provider.apiKey?.trim()
272
+ ) {
273
+ throw new Error("OpenAI or Azure OpenAI workspace key is unavailable");
274
+ }
269
275
  const workspaceGateway = provider.kind === "vercel-gateway-workspace";
270
276
  const scopedCredentialProvider =
271
277
  provider.credentialSource?.kind === "workspace_connection" ||
@@ -403,7 +409,9 @@ export function instrumentedModelFetch(provider: string, inner: typeof fetch): t
403
409
  if (!isModelCallFetch(input)) {
404
410
  return await inner(input, init);
405
411
  }
406
- init = await prepareClaudeSubscriptionRequest(provider, input, init);
412
+ const prepared = await prepareClaudeSubscriptionRequest(provider, input, init);
413
+ init = prepared.init;
414
+ const claudeRequestToken = captureClaudeRequestToken(input, init);
407
415
  // The attempt-local observer durably checkpoints provider dispatch before
408
416
  // this process can place request bytes on the network.
409
417
  await recordModelTransportStarted();
@@ -411,7 +419,7 @@ export function instrumentedModelFetch(provider: string, inner: typeof fetch): t
411
419
  const started = performance.now();
412
420
  try {
413
421
  const response = await inner(input, capture.init);
414
- observeClaudeUsageResponse(provider, response);
422
+ prepared.observe(response, claudeRequestToken);
415
423
  recordModelCallMetric(provider, response.ok ? "completed" : "failed", started);
416
424
  return response;
417
425
  } catch (error) {
@@ -1,3 +1,4 @@
1
+ import { isDirectModelId } from "@opengeni/contracts";
1
2
  import type { ConfiguredModel, ResolvedModelProvider, Settings } from "@opengeni/config";
2
3
  import { configuredProviders, resolveModelProvider } from "@opengeni/config";
3
4
  import {
@@ -334,6 +335,9 @@ export class MultiProviderModelProvider implements ModelProvider {
334
335
  throw new XaiSubscriptionUnavailableError(modelName);
335
336
  }
336
337
  }
338
+ if (modelName && isDirectModelId(modelName)) {
339
+ throw new Error("The selected OpenAI or Azure OpenAI connection is unavailable");
340
+ }
337
341
  // Preserve the legacy unlisted-model fallback, but bind it through the same
338
342
  // typed request-policy model as every configured Responses call. This keeps
339
343
  // Azure wire normalization at the object stage instead of reintroducing a
@@ -50,6 +50,7 @@ export type NativeToolSkillSet = Readonly<{
50
50
  defaults?: boolean;
51
51
  editableArtifacts: boolean;
52
52
  sites?: boolean;
53
+ schedules?: boolean;
53
54
  videoGeneration: boolean;
54
55
  }>;
55
56
 
@@ -121,6 +122,7 @@ export function loadNativeToolSkillArtifacts(
121
122
  ): readonly RuntimeSkillArtifact[] {
122
123
  const directories: string[] = nativeTools.defaults === false ? [] : ["bundled_default_skills"];
123
124
  if (nativeTools.projects) directories.push("bundled_project_skills");
125
+ if (nativeTools.schedules) directories.push("bundled_schedule_skills");
124
126
  if (nativeTools.editableArtifacts) directories.push("bundled_artifact_skills");
125
127
  if (nativeTools.sites) directories.push("bundled_site_skills");
126
128
  if (nativeTools.videoGeneration) directories.push("bundled_video_skills");
@@ -391,6 +393,12 @@ function nativeToolSkillSources(nativeTools: NativeToolSkillSet): Array<{
391
393
  reason: "bundled Site authoring skill",
392
394
  });
393
395
  }
396
+ if (nativeTools.schedules) {
397
+ sources.push({
398
+ names: skillDirNames(packagedSkillDirectory("bundled_schedule_skills")),
399
+ reason: "native scheduled-task tool surface",
400
+ });
401
+ }
394
402
  if (nativeTools.videoGeneration) {
395
403
  sources.push({
396
404
  names: skillDirNames(packagedSkillDirectory("bundled_video_skills")),
@@ -178,6 +178,7 @@ export type ChannelASession = ProviderCommandSession & {
178
178
  chars?: string;
179
179
  yieldTimeMs?: number;
180
180
  maxOutputTokens?: number;
181
+ signal?: AbortSignal;
181
182
  }): Promise<string>;
182
183
  writeStdinForProcessMutation?(args: {
183
184
  sessionId: number;
@@ -193,6 +194,8 @@ export type ChannelASession = ProviderCommandSession & {
193
194
  }): Promise<string>;
194
195
  cancelExecCommand?(opId: string): Promise<boolean>;
195
196
  hasRetainedProcess?(providerSessionId: number): boolean;
197
+ /** Cleanup only: forget the local route after exact durable terminal proof. */
198
+ reconcileRetainedProcess?(providerSessionId: number): Promise<boolean>;
196
199
  retainedProcessHasTypedHandleLoss?(providerSessionId: number): boolean;
197
200
  execCommandForProcessControl?(providerSessionId: number, args: ChannelAExecArgs): Promise<string>;
198
201
  createEditor?(runAs?: string): ChannelAEditor;
@@ -2,7 +2,11 @@ export type {
2
2
  ProviderCommandPersistence,
3
3
  ProviderCommandSession,
4
4
  } from "./provider-command-session";
5
- export { ProviderCommandStartOutcomeUnknownError } from "./provider-command-session";
5
+ export {
6
+ ProviderCommandStartOutcomeUnknownError,
7
+ ProviderCommandObservationUnavailableError,
8
+ isProviderCommandObservationUnavailableError,
9
+ } from "./provider-command-session";
6
10
  // @opengeni/runtime/sandbox — the agent-loop-free sandbox leaf.
7
11
  //
8
12
  // This module is the load-bearing pre-req for the API-direct control plane
@@ -26,6 +30,12 @@ export { ProviderCommandStartOutcomeUnknownError } from "./provider-command-sess
26
30
  import type { Settings } from "@opengeni/config";
27
31
  import { collectSandboxEnvironment, parseExposedPorts } from "@opengeni/config";
28
32
  export type { WorkspaceArchiveSpool, VerifiedHostWorkspaceArchive } from "./archive-spool";
33
+ export { reduceModalRawOutputPage } from "./providers/modal-command-raw-page";
34
+ export type {
35
+ ModalRawOutputPage,
36
+ ModalRawOutputStreams,
37
+ ModalRawOutputExit,
38
+ } from "./providers/modal-command-raw-page";
29
39
  import type { WorkspaceArchiveSpool } from "./archive-spool";
30
40
  import { restoreHostWorkspaceArchive } from "./host-archive-spool";
31
41
  export {