@openclaw/codex 2026.8.2 → 2026.9.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 (61) hide show
  1. package/dist/api.js +3 -3
  2. package/dist/{attempt-client-cleanup-CpVfwctT.js → attempt-client-cleanup-DYHcRQAa.js} +6 -43
  3. package/dist/attempt-context-DmQUNWwT.js +634 -0
  4. package/dist/{binding-connection-CF7BmrDk.js → binding-connection-E6uDsPlx.js} +2 -2
  5. package/dist/{bounded-turn-DRC3hcjm.js → bounded-turn-wzJrwKKe.js} +8 -9
  6. package/dist/{capabilities-D6m_TdVy.js → capabilities-dIjNVCYH.js} +1 -1
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters--UToCokS.js → command-formatters-CLHhEB1Q.js} +3 -3
  9. package/dist/{command-handlers-CLFx2Ypa.js → command-handlers-CTGb2csu.js} +54 -55
  10. package/dist/{compact-5xqW1oCh.js → compact-_t0HQrUS.js} +33 -25
  11. package/dist/{config-CcYL77O6.js → config-BhmfC1s6.js} +12 -8
  12. package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
  13. package/dist/doctor-contract-api.js +31 -4
  14. package/dist/{dynamic-tools-BmeXjwNZ.js → dynamic-tools-BU9tmlYm.js} +448 -457
  15. package/dist/{effective-mcp-catalog-XXQKgK1U.js → effective-mcp-catalog-DzYBW4h1.js} +2 -2
  16. package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-DHRFXKbS.js} +4 -4
  17. package/dist/{event-projector-usage-B2r5vhWH.js → event-projector-usage-C5mcrGjp.js} +9 -137
  18. package/dist/{harness-BgeB7LnD.js → harness-DXVZ5II5.js} +13 -13
  19. package/dist/harness.js +1 -1
  20. package/dist/index.js +323 -313
  21. package/dist/{isolated-completion-CxTus3vZ.js → isolated-completion-kg9kv4iH.js} +4 -4
  22. package/dist/{launch-args-sXOAg0SX.js → launch-args-RXQwn8zV.js} +7 -1
  23. package/dist/{managed-binary-ZkGodW19.js → managed-binary-4-Vjz5GH.js} +14 -9
  24. package/dist/media-understanding-provider.js +1 -1
  25. package/dist/{model-catalog-DULBcniB.js → model-catalog-CUM68Hkl.js} +11 -11
  26. package/dist/{models-DaWfETQk.js → models-sYnEc0gd.js} +3 -3
  27. package/dist/{native-subagent-monitor-vXYUZH37.js → native-subagent-monitor-CnH147jH.js} +3 -3
  28. package/dist/{node-exec-server.runtime-DirKVZ5h.js → node-exec-server.runtime-CstNHCmC.js} +2 -2
  29. package/dist/{plugin-approval-roundtrip-CCWQGlAI.js → plugin-approval-roundtrip-CNe-Xrjx.js} +29 -3
  30. package/dist/{plugin-marketplace-discovery-BXjIFEiw.js → plugin-marketplace-discovery-CfFlKqDl.js} +6 -6
  31. package/dist/{plugin-metadata-cache-DQQm4gWT.js → plugin-metadata-cache-BFrPhb2c.js} +5 -5
  32. package/dist/provider-capabilities-DW5p4k9w.js +101 -0
  33. package/dist/{rate-limits-BqHEHXQJ.js → rate-limits-CawQJfUS.js} +1 -1
  34. package/dist/{request-BWxweEdK.js → request-DYAI5FQE.js} +18 -6
  35. package/dist/{run-attempt-BVLE4Gbi.js → run-attempt-D1hJO4fX.js} +483 -1865
  36. package/dist/{runtime-artifact-D9pYp4zL.js → runtime-artifact-C3jVimFn.js} +19 -12
  37. package/dist/{session-binding-D0zinCP1.js → session-binding-BHuTCRZQ.js} +8 -10
  38. package/dist/{session-catalog-eMhVm0Ql.js → session-catalog-yyvTWFOw.js} +138 -116
  39. package/dist/{session-cli-B2lyjJA0.js → session-cli-CmFEXMAM.js} +1 -1
  40. package/dist/session-initialization-BeWzjGiR.js +74 -0
  41. package/dist/{session-permission-policy-ScnAyG90.js → session-permission-policy-DeWVTCMj.js} +2 -2
  42. package/dist/{session-retirement-CtUtUTr9.js → session-retirement-BujDjXVF.js} +16 -9
  43. package/dist/{session-upstream-marker-gqrYIRHk.js → session-upstream-marker-C351-5Qw.js} +18 -2
  44. package/dist/{settled-turn-context-Bk3IUwod.js → settled-turn-context-D3SNWtUn.js} +6 -28
  45. package/dist/{settled-turn-finalizer-BXLN9PfJ.js → settled-turn-finalizer-BVJx4yoc.js} +5 -5
  46. package/dist/{shared-client-B1tT5U6r.js → shared-client-DxcfF9KX.js} +2748 -101
  47. package/dist/{side-question-CZ-mCw-t.js → side-question-BTZAII4l.js} +97 -63
  48. package/dist/{thread-lifecycle-CGGKP720.js → thread-lifecycle-DAcEJ_Om.js} +2039 -1487
  49. package/dist/{thread-ownership-RkaoZCmQ.js → thread-ownership-zGokd8i1.js} +10 -9
  50. package/dist/thread-policy-g4KbJPzp.js +54 -0
  51. package/dist/{thread-resume-BFt1-ZDa.js → thread-resume-C7pyVKeW.js} +15 -7
  52. package/dist/{transcript-mirror-1ep4XjzF.js → transcript-mirror-DTUxFWa_.js} +47 -10
  53. package/dist/{transport-stdio-NT_Dpd9V.js → transport-stdio-CyPldczm.js} +3 -1
  54. package/dist/upstream-session-fork-CYlBIGVt.js +966 -0
  55. package/dist/{usage-DUhd58gI.js → usage-Bj-rrzkR.js} +3 -3
  56. package/dist/{web-search-provider.runtime-BEmlE7ov.js → web-search-provider.runtime-D2FWSK4w.js} +3 -3
  57. package/openclaw.plugin.json +0 -30
  58. package/package.json +5 -5
  59. package/dist/protocol-validators-Bg5rpNxk.js +0 -2542
  60. package/dist/session-catalog-home-id-Dt2GiG5o.js +0 -112
  61. package/dist/upstream-session-fork-Dc8BFExw.js +0 -312
@@ -1,18 +1,19 @@
1
- import { C as isCodexRemoteExecPlacementSandbox, T as readCodexPluginConfig } from "./config-CcYL77O6.js";
2
- import { Bt as CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE, Ht as isJsonObject, dt as resolveCodexToolAbortTerminalReason, et as createCodexElicitationResponse, ft as createFailedDynamicToolResponse, h as releaseLeasedSharedCodexAppServerClient, mt as withDynamicToolTranscriptDetails, pt as withDynamicToolExecutionState } from "./shared-client-B1tT5U6r.js";
3
- import { d as readCodexTurn } from "./protocol-validators-Bg5rpNxk.js";
4
- import { D as itemStatus, I as readItem, b as auditNativeToolTerminalStatus, g as isCodexNotificationForTurn, x as auditNativeToolUnfinishedStatus, y as auditNativeToolName } from "./attempt-client-cleanup-CpVfwctT.js";
5
- import { H as normalizeCodexDynamicToolName, L as filterCodexDynamicTools, R as filterCodexDynamicToolsForDisabledNativeSurface, V as isSystemAgentOnlyCodexDynamicToolAllowlist, d as emitCodexNativePreToolUseFailureDiagnostic, x as resolveCodexWebSearchPlan, z as isForcedPrivateQaCodexRuntime } from "./thread-lifecycle-CGGKP720.js";
6
- import { l as resolveCodexNativeExecutionPolicy, u as resolveCodexNodeExecToolOverrides } from "./request-BWxweEdK.js";
1
+ import { $t as isJsonObject, Qt as flattenCodexDynamicToolFunctions, Zt as CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE, _t as resolveCodexToolAbortTerminalReason, bt as withDynamicToolTranscriptDetails, it as readCodexTurn, ot as createCodexElicitationResponse, vt as createFailedDynamicToolResponse, yt as withDynamicToolExecutionState } from "./shared-client-DxcfF9KX.js";
2
+ import { E as readCodexPluginConfig, w as isCodexRemoteExecPlacementSandbox } from "./config-BhmfC1s6.js";
3
+ import { E as itemStatus, I as readItem, b as auditNativeToolUnfinishedStatus, h as isCodexNotificationForTurn, v as auditNativeToolName, y as auditNativeToolTerminalStatus } from "./attempt-client-cleanup-DYHcRQAa.js";
4
+ import { B as emitCodexNativePreToolUseFailureDiagnostic, S as resolveCodexWebSearchPlan, at as filterCodexDynamicTools, lt as isSystemAgentOnlyCodexDynamicToolAllowlist, ot as filterCodexDynamicToolsForDisabledNativeSurface, st as isForcedPrivateQaCodexRuntime, ut as normalizeCodexDynamicToolName } from "./thread-lifecycle-DAcEJ_Om.js";
5
+ import { l as resolveCodexNativeExecutionPolicy, u as resolveCodexNodeExecToolOverrides } from "./request-DYAI5FQE.js";
7
6
  import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-CnfoUPCE.js";
8
- import { r as formatCodexDisplayText } from "./command-formatters--UToCokS.js";
9
- import { a as sanitizeCodexApprovalVisibleText, c as waitForPluginApprovalDecision, i as requestPluginApproval, n as codexApprovalTimeoutText, o as stripDanglingCodexApprovalTerminalSequence, r as mapExecDecisionToOutcome, s as truncateCodexApprovalDisplayText, t as approvalRequestExplicitlyUnavailable } from "./plugin-approval-roundtrip-CCWQGlAI.js";
7
+ import { a as formatCodexDisplayText } from "./command-formatters-CLHhEB1Q.js";
8
+ import { a as requestPluginApprovalOutcome, c as truncateCodexApprovalDisplayText, i as requestPluginApproval, l as waitForPluginApprovalDecision, n as codexApprovalTimeoutText, o as sanitizeCodexApprovalVisibleText, r as mapExecDecisionToOutcome, s as stripDanglingCodexApprovalTerminalSequence, t as approvalRequestExplicitlyUnavailable } from "./plugin-approval-roundtrip-CNe-Xrjx.js";
9
+ import { a as CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME, c as createNodeExecAliasDynamicTool, i as CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME, l as isCodexDynamicToolExcluded, o as createGatewayExecProjection, r as CODEX_GATEWAY_EXEC_DYNAMIC_TOOL_NAME, s as createGatewayProcessProjection } from "./provider-capabilities-DW5p4k9w.js";
10
10
  import { asNonArrayRecord, asOptionalRecord, isRecord, normalizeOptionalString, normalizeTrimmedStringList, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
11
11
  import { validateJsonSchemaValue } from "openclaw/plugin-sdk/json-schema-runtime";
12
12
  import path, { posix } from "node:path";
13
13
  import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
14
14
  import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
15
15
  import { HEARTBEAT_RESPONSE_TOOL_NAME, buildAgentHookContextChannelFields, buildEmbeddedAttemptToolRunContext, consumeAdjustedParamsForToolCall, consumePreExecutionBlockedToolCall, createAgentToolResultMiddlewareRunner, createCodexAppServerToolResultExtensionRunner, embeddedAgentLog, extractMessagingToolSend, extractMessagingToolSendResult, extractToolResultMediaArtifact, filterProviderNormalizableTools, filterToolResultMediaUrls, finalizeToolTerminalPresentation, formatApprovalDisplayPath, formatToolExecutionErrorMessage, getBeforeToolCallFailureDisposition, getChannelAgentToolMeta, getPluginToolMeta, getPluginToolSideEffectOwnerKey, hasNativeHookRelayInvocation, invokeNativeHookRelay, isDeliveredMessageToolOnlySourceReplyResult, isDeliveredMessagingToolResult, isHostScopedAgentToolActive, isMessagingTool, isMessagingToolSendAction, isReplaySafeToolCall, isSubagentSessionKey, isToolResultError, isToolWrappedWithBeforeToolCallHook, normalizeAgentRuntimeTools, normalizeHeartbeatToolResponse, projectRuntimeToolInputSchema, resolveAttemptSpawnWorkspaceDir, resolveModelAuthMode, resolveNativeHookRelayDeferredToolApproval, resolveToolExecutionErrorKind, resolveToolResultFailureKind, runAgentHarnessAfterToolCallHook, sanitizeToolResult, setBeforeToolCallDiagnosticsEnabled, supportsModelTools, wrapToolWithBeforeToolCallHook } from "openclaw/plugin-sdk/agent-harness-runtime";
16
+ import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
16
17
  import { createHash, randomUUID } from "node:crypto";
17
18
  import { resolveAgentDir as resolveAgentDir$1 } from "openclaw/plugin-sdk/agent-runtime";
18
19
  import { formatErrorMessage as formatErrorMessage$1, redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
@@ -28,15 +29,17 @@ import { WebSocketServer } from "ws";
28
29
  import { fileURLToPath, pathToFileURL } from "node:url";
29
30
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
30
31
  import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
31
- import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
32
+ import { formatMcpCodexApprovalRemedy, requiresMcpCodexToolApproval, resolveCodexScheduledToolProjectionFactory, resolveProjectedMcpCodexToolApprovalMode, runWithCronCreatorAuthorityCapabilityResolver } from "openclaw/plugin-sdk/codex-mcp-projection";
32
33
  import { buildRemoteCommand, isToolAllowed, sanitizeEnvVars } from "openclaw/plugin-sdk/sandbox";
33
- import { pinExecToolTarget, resolveCodexScheduledToolProjectionFactory, runWithCronCreatorAuthorityCapabilityResolver } from "openclaw/plugin-sdk/codex-mcp-projection";
34
34
  import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
35
35
  import { getMediaDir } from "openclaw/plugin-sdk/media-runtime";
36
- import { getCoreTtsToolResultMediaUrls } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
37
- import { normalizeOpenAIToolSchemas } from "openclaw/plugin-sdk/provider-tools";
36
+ import { consumeTrustedToolNoStartError, getCoreTtsToolResultMediaUrls } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
37
+ import { normalizeOpenAIStrictCompatSchema } from "openclaw/plugin-sdk/provider-tools";
38
38
  //#region extensions/codex/src/app-server/event-projector-native-tool-lifecycle.ts
39
- /** Projects metadata-only lifecycle diagnostics for native tool items. */
39
+ function isMcpToolCallItemNotification(method, params) {
40
+ return (method === "item/started" || method === "item/completed") && isJsonObject(params.item) && params.item.type === "mcpToolCall";
41
+ }
42
+ /** Owns native item lifetimes for diagnostics and MCP approval correlation. */
40
43
  var CodexNativeToolLifecycleProjector = class {
41
44
  constructor(context, threadId, turnId, options = {}) {
42
45
  this.context = context;
@@ -50,13 +53,39 @@ var CodexNativeToolLifecycleProjector = class {
50
53
  this.approvalFailureDispositionByItem = /* @__PURE__ */ new Map();
51
54
  this.preToolUseFailureByItem = /* @__PURE__ */ new Map();
52
55
  this.finalized = false;
56
+ this.turnCompleted = false;
57
+ this.pendingMcpNotifications = 0;
58
+ }
59
+ getActiveMcpToolCall(serverName) {
60
+ if (this.finalized || this.turnCompleted || this.pendingMcpNotifications > 0 || this.options.runAbortSignal?.aborted) return;
61
+ let candidate;
62
+ for (const { mcpToolCall } of this.activeItems.values()) {
63
+ if (mcpToolCall?.server !== serverName) continue;
64
+ if (candidate) return;
65
+ candidate = mcpToolCall;
66
+ }
67
+ if (!candidate || candidate.status !== "inProgress" || typeof candidate.server !== "string" || !candidate.server.trim() || typeof candidate.tool !== "string" || !candidate.tool.trim() || candidate.arguments === void 0 || candidate.appContext != null || candidate.pluginId != null) return;
68
+ return {
69
+ id: candidate.id,
70
+ server: candidate.server,
71
+ tool: candidate.tool,
72
+ arguments: candidate.arguments
73
+ };
74
+ }
75
+ recordMcpToolCallReceipt(notification) {
76
+ const params = isJsonObject(notification.params) ? notification.params : void 0;
77
+ if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
78
+ if (isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications += 1;
79
+ else if (notification.method === "turn/completed" && readCodexTurn(params.turn)?.id === this.turnId) this.turnCompleted = true;
53
80
  }
54
81
  handleNotification(notification) {
55
82
  const params = isJsonObject(notification.params) ? notification.params : void 0;
56
83
  if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
84
+ if (this.pendingMcpNotifications > 0 && isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications -= 1;
57
85
  if (notification.method === "turn/completed") {
58
86
  const turn = readCodexTurn(params.turn);
59
87
  if (!turn || turn.id !== this.turnId) return;
88
+ this.turnCompleted = true;
60
89
  for (const item of turn.items ?? []) this.recordSnapshotItem(item);
61
90
  return;
62
91
  }
@@ -75,10 +104,10 @@ var CodexNativeToolLifecycleProjector = class {
75
104
  });
76
105
  }
77
106
  recordItem(params) {
78
- const toolName = auditNativeToolName(params.item);
107
+ const toolName = auditNativeToolName(params.item) ?? (params.item.type === "mcpToolCall" ? params.item.type : void 0);
79
108
  if (!toolName || this.completedItemIds.has(params.item.id)) return;
80
109
  if (params.phase === "start") {
81
- this.recordStarted(params.item.id, toolName, auditNativeToolUnfinishedStatus(params.item), params.sourceTimestampMs);
110
+ this.recordStarted(params.item.id, toolName, auditNativeToolUnfinishedStatus(params.item), params.sourceTimestampMs, params.item.type === "mcpToolCall" ? params.item : void 0);
82
111
  return;
83
112
  }
84
113
  if (params.item.type === "webSearch") {
@@ -204,12 +233,13 @@ var CodexNativeToolLifecycleProjector = class {
204
233
  item
205
234
  });
206
235
  }
207
- recordStarted(toolCallId, toolName, unfinishedStatus, sourceTimestampMs) {
236
+ recordStarted(toolCallId, toolName, unfinishedStatus, sourceTimestampMs, mcpToolCall) {
208
237
  if (this.activeItems.has(toolCallId)) return;
209
238
  this.startedAtByItem.set(toolCallId, sourceTimestampMs ?? Date.now());
210
239
  this.activeItems.set(toolCallId, {
211
240
  toolName,
212
- unfinishedStatus
241
+ unfinishedStatus,
242
+ mcpToolCall
213
243
  });
214
244
  emitTrustedDiagnosticEvent({
215
245
  type: "tool.execution.started",
@@ -544,53 +574,6 @@ function mapMessageMediaValues(args, mapValue) {
544
574
  /** Mutable dependency seam shared by dynamic-tool construction and its behavioral tests. */
545
575
  const dynamicToolBuildState = {};
546
576
  //#endregion
547
- //#region extensions/codex/src/app-server/shell-dynamic-tools.ts
548
- const CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME = "node_exec";
549
- const CODEX_GATEWAY_EXEC_DYNAMIC_TOOL_NAME = "gateway_exec";
550
- const CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME = "gateway_process";
551
- const PROCESS_FOLLOWUP_TEXT = "Use process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.";
552
- /** Returns true when plugin config explicitly removes any named dynamic tool. */
553
- function isCodexDynamicToolExcluded(config, names) {
554
- const normalizedNames = new Set(names.map((name) => normalizeCodexDynamicToolName(name)));
555
- return (config.codexDynamicToolsExclude ?? []).some((name) => normalizedNames.has(normalizeCodexDynamicToolName(name)));
556
- }
557
- function createNodeExecAliasDynamicTool(execTool, node) {
558
- const pinnedNode = node?.trim();
559
- const pinnedTool = pinExecToolTarget(execTool, {
560
- host: "node",
561
- ...pinnedNode ? { node: pinnedNode } : {}
562
- });
563
- const execute = async (toolCallId, args, signal, onUpdate) => {
564
- const result = await pinnedTool.execute(toolCallId, args, signal, onUpdate);
565
- return {
566
- ...result,
567
- content: result.content.map((item) => item.type === "text" ? Object.assign({}, item, { text: item.text.replace(PROCESS_FOLLOWUP_TEXT, "Remote-node background follow-up is unavailable. Wait for the command to complete.") }) : item)
568
- };
569
- };
570
- return {
571
- ...pinnedTool,
572
- name: CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME,
573
- description: pinnedNode ? "Run a shell command to completion on the OpenClaw configured remote node for this session. This tool always uses OpenClaw host=node internally and follows the existing node exec approval and allowlist policy. Remote-node background follow-up is unavailable. Use Codex's native shell for local app-server work." : "Run a shell command to completion on an OpenClaw remote node. Select the node by name or id when multiple nodes are available. This tool always uses OpenClaw host=node internally and follows the existing node exec approval and allowlist policy. Remote-node background follow-up is unavailable. Use Codex's native shell for local app-server work.",
574
- execute
575
- };
576
- }
577
- function createGatewayExecProjection(createProjection, execTool, params) {
578
- return createProjection(execTool, {
579
- kind: "exec",
580
- name: CODEX_GATEWAY_EXEC_DYNAMIC_TOOL_NAME,
581
- description: "Run a shell command through OpenClaw on the Gateway host for OpenClaw-managed Gateway environment access, including Secret Store agent-readable environment values and protected egress sentinels. Native Codex shell remains preferred for ordinary local work. This tool always uses OpenClaw host=gateway internally and follows Gateway exec approval and allowlist policy.",
582
- followupText: params.processAliasAvailable ? "Use gateway_process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up." : "Background session follow-up is unavailable because gateway_process is not exposed. Rerun without background=true and set yieldMs high enough to wait for completion.",
583
- ...params.ask ? { ask: params.ask } : {}
584
- });
585
- }
586
- function createGatewayProcessProjection(createProjection, processTool) {
587
- return createProjection(processTool, {
588
- kind: "process",
589
- name: CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME,
590
- description: "Manage background shell sessions in the existing per-session OpenClaw process scope: list, poll, log, write, send-keys, submit, paste, kill, clear, or remove. Use for gateway_exec follow-up; use native Codex shell session handling for ordinary local work."
591
- });
592
- }
593
- //#endregion
594
577
  //#region extensions/codex/src/app-server/vision-tools.ts
595
578
  /**
596
579
  * Codex's enabled native surface includes its stable view_image loader. Keep
@@ -616,6 +599,15 @@ const CODEX_NATIVE_SANDBOX_TOOL_REQUIREMENTS = [
616
599
  "apply_patch"
617
600
  ];
618
601
  const CODEX_MEMORY_FLUSH_DYNAMIC_TOOL_ALLOW = /* @__PURE__ */ new Set(["read", "write"]);
602
+ const CODEX_DISABLED_NATIVE_SHELL_DYNAMIC_TOOLS = /* @__PURE__ */ new Set([
603
+ "exec",
604
+ "process",
605
+ "sandbox_exec",
606
+ "sandbox_process",
607
+ CODEX_GATEWAY_EXEC_DYNAMIC_TOOL_NAME,
608
+ CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME,
609
+ CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME
610
+ ]);
619
611
  /** Keeps node filesystem and process ownership on its native exec-server. */
620
612
  function resolveCodexNodePlacementToolConstructionPlan(sandbox, nativeToolSurfaceEnabled) {
621
613
  if (!isCodexRemoteExecPlacementSandbox(sandbox) || sandbox?.backendId !== "node" || !("placementNodeId" in sandbox) || typeof sandbox.placementNodeId !== "string" || !sandbox.placementNodeId) return;
@@ -726,112 +718,114 @@ async function buildDynamicTools(input) {
726
718
  nativeProviderWebSearchSupport: input.nativeProviderWebSearchSupport
727
719
  });
728
720
  const webFetchHostnameAllowlistRef = {};
729
- const buildOpenClawCodingTools = () => {
730
- const toolConstructionPlan = resolveCodexNodePlacementToolConstructionPlan(input.sandbox, input.nativeToolSurfaceEnabled);
731
- const options = {
732
- agentId: input.sessionAgentId,
733
- policyAgentId: input.policyAgentId,
734
- ...toolRunContext,
735
- exec: {
736
- ...params.execOverrides,
737
- ...input.sessionPermissionPolicy ? { mode: input.sessionPermissionPolicy.execMode } : {},
738
- ...resolveCodexNodeExecToolOverrides(nativeExecutionPolicy),
739
- config: params.config,
740
- elevated: params.bashElevated
741
- },
742
- sessionPermissionPolicy: input.sessionPermissionPolicy ? {
743
- mode: input.sessionPermissionPolicy.mode,
744
- root: input.sessionPermissionPolicy.root
745
- } : void 0,
746
- sandbox: input.sandbox,
747
- ...toolConstructionPlan ? { toolConstructionPlan } : {},
748
- messageProvider: resolveCodexMessageToolProvider(params),
749
- toolPolicyMessageProvider: params.messageProvider ?? params.messageChannel,
750
- clientCaps: params.clientCaps,
751
- chatType: params.chatType,
752
- agentAccountId: params.agentAccountId,
753
- messageTo: params.messageTo,
754
- messageThreadId: params.messageThreadId,
755
- nativeChannelId: params.chatId,
756
- messageActionTurnCapability: params.messageActionTurnCapability,
757
- groupId: params.groupId,
758
- groupChannel: params.groupChannel,
759
- groupSpace: params.groupSpace,
760
- spawnedBy: params.spawnedBy,
761
- senderId: params.senderId,
762
- senderName: params.senderName,
763
- senderUsername: params.senderUsername,
764
- senderE164: params.senderE164,
765
- senderIsOwner: params.senderIsOwner,
766
- inputProvenance: params.inputProvenance,
767
- trustedInternalHandoff: params.trustedInternalHandoff,
768
- scheduledToolPolicy: params.scheduledToolPolicy,
769
- allowGatewaySubagentBinding: params.allowGatewaySubagentBinding || isForcedPrivateQaCodexRuntime(),
770
- sessionKey: input.sandboxSessionKey,
771
- runSessionKey: params.sessionKey && params.sessionKey !== input.sandboxSessionKey ? params.sessionKey : void 0,
772
- sessionId: params.sessionId,
773
- runId: params.runId,
774
- approvalReviewerDeviceId: params.approvalReviewerDeviceId,
775
- agentDir,
776
- preparedModelRuntime: params.preparedModelRuntime,
777
- cwd: input.effectiveCwd ?? input.effectiveWorkspace,
778
- workspaceDir: input.effectiveWorkspace,
779
- spawnWorkspaceDir: input.effectiveCwd && input.effectiveCwd !== input.effectiveWorkspace ? input.resolvedWorkspace : resolveAttemptSpawnWorkspaceDir({
780
- sandbox: input.sandbox,
781
- resolvedWorkspace: input.resolvedWorkspace
782
- }),
721
+ const toolConstructionPlan = resolveCodexNodePlacementToolConstructionPlan(input.sandbox, input.nativeToolSurfaceEnabled);
722
+ const options = {
723
+ agentId: input.sessionAgentId,
724
+ policyAgentId: input.policyAgentId,
725
+ ...toolRunContext,
726
+ exec: {
727
+ ...params.execOverrides,
728
+ ...input.sessionPermissionPolicy ? { mode: input.sessionPermissionPolicy.execMode } : {},
729
+ ...resolveCodexNodeExecToolOverrides(nativeExecutionPolicy),
783
730
  config: params.config,
784
- githubPublicationAvailable: params.githubPublicationAvailable,
785
- authProfileStore: params.toolAuthProfileStore ?? params.authProfileStore,
786
- abortSignal: input.runAbortController.signal,
787
- emitBeforeToolCallDiagnostics: false,
788
- modelProvider: params.model.provider,
789
- modelId: params.modelId,
790
- modelCompat: params.model.compat && typeof params.model.compat === "object" ? params.model.compat : void 0,
791
- modelApi: params.model.api,
792
- modelContextWindowTokens: params.model.contextWindow,
793
- delegationCapability: params.delegationCapability,
794
- modelAuthMode: resolveModelAuthMode(params.model.provider, params.config, params.toolAuthProfileStore ?? params.authProfileStore, { workspaceDir: input.effectiveWorkspace }),
795
- suppressManagedWebSearch: false,
796
- webFetchHostnameAllowlistRef,
797
- currentChannelId: params.currentChannelId,
798
- currentMessagingTarget: params.currentMessagingTarget,
799
- hookChannelId: resolveCodexAppServerHookChannelId(params, input.sandboxSessionKey),
800
- currentThreadTs: params.currentThreadTs,
801
- currentMessageId: params.currentMessageId,
802
- replyToMode: params.replyToMode,
803
- hasRepliedRef: params.hasRepliedRef,
804
- modelHasVision,
805
- computerContextEpoch: input.computerContextEpoch,
806
- registerRunCleanup: input.registerRunCleanup,
807
- requireExplicitMessageTarget: params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
808
- sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
809
- taskSuggestionDeliveryMode: params.taskSuggestionDeliveryMode,
810
- disableMessageTool: input.ignoreDisableMessageTool ? false : params.disableMessageTool,
811
- forceMessageTool: shouldForceMessageTool(messagePolicyParams),
812
- enableHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
813
- forceHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
814
- onYield: (message, acknowledgment) => {
815
- input.onYieldDetected(acknowledgment);
816
- input.onCodexAppServerEvent?.({
817
- stream: "codex_app_server.tool",
818
- data: {
819
- name: "sessions_yield",
820
- message
821
- }
822
- });
823
- },
824
- claimYieldCompletion: input.claimYieldCompletion,
825
- recordToolPrepStage: (name) => {
826
- toolBuildStages.mark(name);
827
- },
828
- onToolOutcome: params.onToolOutcome,
829
- isTurnTainted: params.isTurnTainted,
830
- allocateToolOutcomeOrdinal: params.allocateToolOutcomeOrdinal,
831
- cronCreatorToolAllowlistRef: input.cronCreatorToolAllowlistRef,
832
- cronCreatorToolAllowlistCaptureRef: input.cronCreatorToolAllowlistCaptureRef,
833
- cronCreatorAuthorityUnavailableReason: input.cronCreatorAuthorityUnavailableReason
834
- };
731
+ elevated: params.bashElevated
732
+ },
733
+ sessionPermissionPolicy: input.sessionPermissionPolicy ? {
734
+ mode: input.sessionPermissionPolicy.mode,
735
+ root: input.sessionPermissionPolicy.root
736
+ } : void 0,
737
+ sandbox: input.sandbox,
738
+ ...toolConstructionPlan ? { toolConstructionPlan } : {},
739
+ messageProvider: resolveCodexMessageToolProvider(params),
740
+ toolPolicyMessageProvider: params.messageProvider ?? params.messageChannel,
741
+ clientCaps: params.clientCaps,
742
+ chatType: params.chatType,
743
+ agentAccountId: params.agentAccountId,
744
+ messageTo: params.messageTo,
745
+ messageThreadId: params.messageThreadId,
746
+ nativeChannelId: params.chatId,
747
+ messageActionTurnCapability: params.messageActionTurnCapability,
748
+ groupId: params.groupId,
749
+ groupChannel: params.groupChannel,
750
+ groupSpace: params.groupSpace,
751
+ spawnedBy: params.spawnedBy,
752
+ senderId: params.senderId,
753
+ senderName: params.senderName,
754
+ senderUsername: params.senderUsername,
755
+ senderE164: params.senderE164,
756
+ senderIsOwner: params.senderIsOwner,
757
+ inputProvenance: params.inputProvenance,
758
+ trustedInternalHandoff: params.trustedInternalHandoff,
759
+ scheduledToolPolicy: params.scheduledToolPolicy,
760
+ allowGatewaySubagentBinding: params.allowGatewaySubagentBinding || isForcedPrivateQaCodexRuntime(),
761
+ sessionKey: input.sandboxSessionKey,
762
+ runSessionKey: params.sessionKey && params.sessionKey !== input.sandboxSessionKey ? params.sessionKey : void 0,
763
+ sessionId: params.sessionId,
764
+ runId: params.runId,
765
+ approvalReviewerDeviceId: params.approvalReviewerDeviceId,
766
+ agentDir,
767
+ preparedModelRuntime: params.preparedModelRuntime,
768
+ cwd: input.effectiveCwd ?? input.effectiveWorkspace,
769
+ workspaceDir: input.effectiveWorkspace,
770
+ spawnWorkspaceDir: input.effectiveCwd && input.effectiveCwd !== input.effectiveWorkspace ? input.resolvedWorkspace : resolveAttemptSpawnWorkspaceDir({
771
+ sandbox: input.sandbox,
772
+ resolvedWorkspace: input.resolvedWorkspace
773
+ }),
774
+ config: params.config,
775
+ skillsSnapshot: params.skillsSnapshot,
776
+ ...params.skillLibraryAuthoring ? { skillWorkshop: { libraryAuthoring: params.skillLibraryAuthoring } } : {},
777
+ githubPublicationAvailable: params.githubPublicationAvailable,
778
+ authProfileStore: params.toolAuthProfileStore ?? params.authProfileStore,
779
+ abortSignal: input.runAbortController.signal,
780
+ emitBeforeToolCallDiagnostics: false,
781
+ modelProvider: params.model.provider,
782
+ modelId: params.modelId,
783
+ modelCompat: params.model.compat && typeof params.model.compat === "object" ? params.model.compat : void 0,
784
+ modelApi: params.model.api,
785
+ modelContextWindowTokens: params.model.contextWindow,
786
+ delegationCapability: params.delegationCapability,
787
+ modelAuthMode: resolveModelAuthMode(params.model.provider, params.config, params.toolAuthProfileStore ?? params.authProfileStore, { workspaceDir: input.effectiveWorkspace }),
788
+ suppressManagedWebSearch: false,
789
+ webFetchHostnameAllowlistRef,
790
+ currentChannelId: params.currentChannelId,
791
+ currentMessagingTarget: params.currentMessagingTarget,
792
+ hookChannelId: resolveCodexAppServerHookChannelId(params, input.sandboxSessionKey),
793
+ currentThreadTs: params.currentThreadTs,
794
+ currentMessageId: params.currentMessageId,
795
+ replyToMode: params.replyToMode,
796
+ hasRepliedRef: params.hasRepliedRef,
797
+ modelHasVision,
798
+ computerContextEpoch: input.computerContextEpoch,
799
+ registerRunCleanup: input.registerRunCleanup,
800
+ requireExplicitMessageTarget: params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
801
+ sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
802
+ taskSuggestionDeliveryMode: params.taskSuggestionDeliveryMode,
803
+ disableMessageTool: input.ignoreDisableMessageTool ? false : params.disableMessageTool,
804
+ forceMessageTool: shouldForceMessageTool(messagePolicyParams),
805
+ enableHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
806
+ forceHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
807
+ onYield: (message, acknowledgment) => {
808
+ input.onYieldDetected(acknowledgment);
809
+ input.onCodexAppServerEvent?.({
810
+ stream: "codex_app_server.tool",
811
+ data: {
812
+ name: "sessions_yield",
813
+ message
814
+ }
815
+ });
816
+ },
817
+ claimYieldCompletion: input.claimYieldCompletion,
818
+ recordToolPrepStage: (name) => {
819
+ toolBuildStages.mark(name);
820
+ },
821
+ onToolOutcome: params.onToolOutcome,
822
+ isTurnTainted: params.isTurnTainted,
823
+ allocateToolOutcomeOrdinal: params.allocateToolOutcomeOrdinal,
824
+ cronCreatorToolAllowlistRef: input.cronCreatorToolAllowlistRef,
825
+ cronCreatorToolAllowlistCaptureRef: input.cronCreatorToolAllowlistCaptureRef,
826
+ cronCreatorAuthorityUnavailableReason: input.cronCreatorAuthorityUnavailableReason
827
+ };
828
+ const buildOpenClawCodingTools = () => {
835
829
  const bindingOptions = { cwd: input.effectiveCwd ?? input.effectiveWorkspace };
836
830
  if (injectedOpenClawCodingToolsFactory) return params.hostCapabilities.bindToolSurface(injectedOpenClawCodingToolsFactory(options), bindingOptions);
837
831
  const createToolSurface = params.hostCapabilities.createToolSurface;
@@ -851,7 +845,7 @@ async function buildDynamicTools(input) {
851
845
  const readableAllTools = [...readableAllToolProjection.tools];
852
846
  const normallyProfiledTools = input.nativeToolSurfaceEnabled === false ? filterCodexDynamicToolsForDisabledNativeSurface(readableAllTools, input.pluginConfig, { preserveShell: shouldKeepOpenClawShellDynamicTools(input, nativeExecutionPolicy) }) : filterCodexDynamicTools(readableAllTools, input.pluginConfig);
853
847
  const profileFilteredTools = (input.isHostScopedToolActive?.("openclaw") ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) ? preserveRingZeroSystemAgentTool(readableAllTools, normallyProfiledTools) : normallyProfiledTools;
854
- const codexFilteredTools = addNodeShellDynamicToolsIfNeeded(addGatewayShellDynamicToolsIfAvailable(addSandboxShellDynamicToolsIfAvailable(isCodexMemoryFlushRun(params) ? filterCodexMemoryFlushDynamicTools(readableAllTools) : profileFilteredTools, readableAllTools, input, nativeExecutionPolicy), readableAllTools, input, nativeExecutionPolicy), readableAllTools, input, nativeExecutionPolicy);
848
+ const codexFilteredTools = await addNodeShellDynamicToolsIfNeeded(addGatewayShellDynamicToolsIfAvailable(addSandboxShellDynamicToolsIfAvailable(isCodexMemoryFlushRun(params) ? filterCodexMemoryFlushDynamicTools(readableAllTools) : profileFilteredTools, readableAllTools, input, nativeExecutionPolicy), readableAllTools, input, nativeExecutionPolicy), readableAllTools, input, nativeExecutionPolicy);
855
849
  toolBuildStages.mark("codex-filtering");
856
850
  const visionFilteredTools = filterCodexVisionTools(codexFilteredTools, {
857
851
  modelHasVision,
@@ -905,7 +899,7 @@ async function buildDynamicTools(input) {
905
899
  const webSearchAllowed = normalizedTools.some((tool) => tool.name === "web_search");
906
900
  webFetchHostnameAllowlistRef.value = webSearchAllowed ? webSearchPlan.webFetchHostnameAllowlist : void 0;
907
901
  input.onWebSearchPolicyResolved?.(webSearchAllowed);
908
- const exposedTools = webSearchPlan.suppressManagedWebSearch ? normalizedTools.filter((tool) => tool.name !== "web_search") : normalizedTools;
902
+ const exposedTools = placeDisabledNativeShellToolsInDirectNamespace(webSearchPlan.suppressManagedWebSearch ? normalizedTools.filter((tool) => tool.name !== "web_search") : normalizedTools, input.nativeToolSurfaceEnabled);
909
903
  if (preNormalizationDiagnostics.length > 0) embeddedAgentLog.warn(`codex app-server quarantined ${preNormalizationDiagnostics.length} unsupported runtime tool schema${preNormalizationDiagnostics.length === 1 ? "" : "s"} before dynamic tool registration`, {
910
904
  runId: params.runId,
911
905
  sessionId: params.sessionId,
@@ -1078,19 +1072,25 @@ function isSandboxShellDynamicToolExcluded(config) {
1078
1072
  "sandbox_process"
1079
1073
  ]);
1080
1074
  }
1081
- function addNodeShellDynamicToolsIfNeeded(filteredTools, allTools, input, nodePolicy) {
1075
+ async function addNodeShellDynamicToolsIfNeeded(filteredTools, allTools, input, nodePolicy) {
1082
1076
  if (isCodexMemoryFlushRun(input.params)) return filteredTools;
1083
1077
  const nodeExecIsDefault = nodePolicy.effectiveExecHost === "node";
1084
1078
  const nodeExecAvailableFromAuto = nodePolicy.requestedExecHost === "auto" && nodePolicy.effectiveExecHost === "gateway";
1085
1079
  if (!nodeExecIsDefault && !nodeExecAvailableFromAuto) return filteredTools;
1086
1080
  const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
1087
- if (!execTool) return filteredTools;
1088
- if (!isCodexDynamicToolExcluded(input.pluginConfig, ["exec", "node_exec"]) && !filteredTools.some((tool) => normalizeCodexDynamicToolName(tool.name) === "node_exec")) return [...filteredTools, createNodeExecAliasDynamicTool(execTool, nodePolicy.node)];
1089
- return filteredTools;
1081
+ if (!execTool || isCodexDynamicToolExcluded(input.pluginConfig, ["exec", "node_exec"]) || filteredTools.some((tool) => normalizeCodexDynamicToolName(tool.name) === "node_exec")) return filteredTools;
1082
+ const nodeExec = await createNodeExecAliasDynamicTool(execTool, nodePolicy.node, input.runAbortController.signal, input.nodeExecAvailability);
1083
+ return nodeExec ? [...filteredTools, nodeExec] : filteredTools;
1090
1084
  }
1091
1085
  function shouldKeepOpenClawShellDynamicTools(input, nodePolicy) {
1092
1086
  return !isCodexMemoryFlushRun(input.params) && input.nativeToolSurfaceEnabled === false && input.sandbox?.enabled !== true && nodePolicy.effectiveExecHost !== "node";
1093
1087
  }
1088
+ /** Keeps replacement shell tools direct even when model metadata mandates Codex Code Mode. */
1089
+ function placeDisabledNativeShellToolsInDirectNamespace(tools, nativeToolSurfaceEnabled) {
1090
+ if (nativeToolSurfaceEnabled !== false) return tools;
1091
+ for (const tool of tools) if (CODEX_DISABLED_NATIVE_SHELL_DYNAMIC_TOOLS.has(normalizeCodexDynamicToolName(tool.name))) tool.catalogMode = "direct-only";
1092
+ return tools;
1093
+ }
1094
1094
  /** Applies a normalized tool allowlist while preserving shell aliases for exec/process. */
1095
1095
  function filterCodexDynamicToolsForAllowlist(tools, toolsAllow) {
1096
1096
  if (!toolsAllow) return tools;
@@ -3257,52 +3257,6 @@ async function handleMessage(session, data) {
3257
3257
  await session.handleRequest(parseRequest(buffer.toString("utf8")));
3258
3258
  }
3259
3259
  //#endregion
3260
- //#region extensions/codex/src/app-server/provider-capabilities.ts
3261
- function resolveOverriddenProviderWebSearchSupport(modelProviderOverride) {
3262
- const provider = modelProviderOverride?.trim().toLowerCase();
3263
- if (!provider) return;
3264
- return provider === "openai" ? "supported" : "unsupported";
3265
- }
3266
- async function readConfiguredProviderWebSearchSupport(params) {
3267
- return (await params.client.request("modelProvider/capabilities/read", {}, {
3268
- timeoutMs: params.timeoutMs,
3269
- signal: params.signal
3270
- })).webSearch ? "supported" : "unsupported";
3271
- }
3272
- async function resolveCodexProviderWebSearchSupportForClient(params) {
3273
- const overrideSupport = resolveOverriddenProviderWebSearchSupport(params.modelProviderOverride);
3274
- if (overrideSupport) return overrideSupport;
3275
- try {
3276
- return await readConfiguredProviderWebSearchSupport(params);
3277
- } catch {
3278
- return "unknown";
3279
- }
3280
- }
3281
- async function resolveCodexProviderWebSearchSupport(params) {
3282
- const overrideSupport = resolveOverriddenProviderWebSearchSupport(params.modelProviderOverride);
3283
- if (overrideSupport) return overrideSupport;
3284
- let client;
3285
- try {
3286
- client = await params.clientFactory({
3287
- startOptions: params.appServer.start,
3288
- ...params.preparedAuth ? { preparedAuth: params.preparedAuth } : { authProfileId: params.authProfileId },
3289
- agentDir: params.agentDir,
3290
- config: params.config,
3291
- timeoutMs: params.appServer.requestTimeoutMs
3292
- });
3293
- return await resolveCodexProviderWebSearchSupportForClient({
3294
- client,
3295
- timeoutMs: params.appServer.requestTimeoutMs,
3296
- modelProviderOverride: params.modelProviderOverride,
3297
- signal: params.signal
3298
- });
3299
- } catch {
3300
- return "unknown";
3301
- } finally {
3302
- if (client) releaseLeasedSharedCodexAppServerClient(client);
3303
- }
3304
- }
3305
- //#endregion
3306
3260
  //#region extensions/codex/src/app-server/approval-bridge.ts
3307
3261
  /**
3308
3262
  * Bridges Codex app-server approval requests into OpenClaw policy hooks and
@@ -4211,16 +4165,63 @@ async function routeCodexAppServerElicitationRequest(params) {
4211
4165
  signal: params.signal
4212
4166
  }));
4213
4167
  }
4214
- const approvalPrompt = readComputerUseApprovalElicitation(requestParams, params.computerUseMcpServerName) ?? readBridgeableApprovalElicitation(requestParams);
4168
+ const computerUsePrompt = readComputerUseApprovalElicitation(requestParams, params.computerUseMcpServerName);
4169
+ const approvalPrompt = computerUsePrompt ?? readBridgeableApprovalElicitation(requestParams);
4215
4170
  if (!approvalPrompt) return handled(createCodexElicitationResponse("decline"));
4171
+ let persistence;
4172
+ if (!computerUsePrompt) {
4173
+ const serverName = readNonBlankStringField(requestParams, "serverName");
4174
+ const server = serverName ? params.paramsForRun.config?.mcp?.servers?.[serverName] : void 0;
4175
+ const mode = serverName ? resolveProjectedMcpCodexToolApprovalMode(serverName, server ?? {}, params.projectedMcpServers?.[serverName]) : void 0;
4176
+ if (!requiresMcpCodexToolApproval({
4177
+ mode,
4178
+ fullPermission: params.autoApproveMcpTools
4179
+ })) {
4180
+ params.paramsForRun.hostCapabilities.assertActive();
4181
+ return handled(buildElicitationResponse(approvalPrompt, "approved-once"));
4182
+ }
4183
+ if (mode === "prompt") approvalPrompt.allowedDecisions = ["allow-once", "deny"];
4184
+ else if (serverName && serverName !== CODEX_APPS_SERVER_NAME && Object.hasOwn(params.paramsForRun.config?.mcp?.servers ?? {}, serverName) && requestTurnId === params.turnId && readPersistHints(approvalPrompt.meta, "explicit").includes("always")) {
4185
+ const resolveItem = () => {
4186
+ const item = params.getActiveMcpToolCall?.(serverName);
4187
+ return item?.server === serverName && matchesMcpApprovalDisplay(item, approvalPrompt.meta) ? item : void 0;
4188
+ };
4189
+ const item = resolveItem();
4190
+ if (item) persistence = {
4191
+ mcpTool: {
4192
+ server: serverName,
4193
+ tool: item.tool
4194
+ },
4195
+ toolCallId: item.id,
4196
+ isMcpToolApprovalActive: () => {
4197
+ const current = resolveItem();
4198
+ return current?.id === item.id && current.tool === item.tool;
4199
+ }
4200
+ };
4201
+ }
4202
+ }
4216
4203
  return handled(buildElicitationResponse(approvalPrompt, await requestPluginApprovalOutcome({
4217
- paramsForRun: params.paramsForRun,
4204
+ hostCapabilities: params.paramsForRun.hostCapabilities,
4218
4205
  title: approvalPrompt.title,
4219
4206
  description: approvalPrompt.description,
4220
4207
  allowedDecisions: approvalPrompt.allowedDecisions,
4208
+ toolName: "codex_mcp_tool_approval",
4209
+ ...persistence,
4221
4210
  signal: params.signal
4222
4211
  })));
4223
4212
  }
4213
+ function matchesMcpApprovalDisplay(item, meta) {
4214
+ if (!Object.hasOwn(meta, MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY)) return true;
4215
+ const display = meta[MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY];
4216
+ if (!Array.isArray(display)) return false;
4217
+ const args = item.arguments;
4218
+ return display.every((param) => {
4219
+ if (!isJsonObject(param) || typeof param.name !== "string" || !isJsonObject(args)) return false;
4220
+ if (!Object.hasOwn(args, param.name)) return false;
4221
+ const value = args[param.name];
4222
+ return typeof param.value !== "string" || param.value === (typeof value === "string" ? value : JSON.stringify(value));
4223
+ });
4224
+ }
4224
4225
  function handled(response) {
4225
4226
  return {
4226
4227
  kind: "handled",
@@ -4336,14 +4337,14 @@ async function buildPluginPolicyElicitationResponse(params) {
4336
4337
  const response = buildElicitationResponse(approvalPrompt, "approved-once");
4337
4338
  if (response.action === "accept") {
4338
4339
  if (mode === "allow") return response;
4339
- const outcome = await requestPluginApprovalOutcome({
4340
- paramsForRun: params.paramsForRun,
4340
+ return buildElicitationResponse(approvalPrompt, await requestPluginApprovalOutcome({
4341
+ hostCapabilities: params.paramsForRun.hostCapabilities,
4341
4342
  title: approvalPrompt.title,
4342
4343
  description: approvalPrompt.description,
4343
4344
  allowedDecisions: allowedPluginPolicyApprovalDecisions(mode, approvalPrompt),
4345
+ toolName: "codex_mcp_tool_approval",
4344
4346
  signal: params.signal
4345
- });
4346
- return buildElicitationResponse(approvalPrompt, mode === "ask" && outcome === "approved-session" ? "approved-once" : outcome);
4347
+ }));
4347
4348
  }
4348
4349
  logPluginElicitationDecline("unmappable_schema", params.requestParams);
4349
4350
  return createCodexElicitationResponse("decline");
@@ -4378,15 +4379,16 @@ function readPluginApprovalElicitation(entry, requestParams) {
4378
4379
  allowedDecisions: buildApprovalAllowedDecisions(requestedSchema, meta)
4379
4380
  };
4380
4381
  }
4381
- function buildApprovalAllowedDecisions(requestedSchema, meta) {
4382
- return canMapPersistentApproval(requestedSchema, meta) ? [
4382
+ function buildApprovalAllowedDecisions(requestedSchema, meta, allowSession = false) {
4383
+ return canMapPersistentApproval(requestedSchema, meta, allowSession) ? [
4383
4384
  "allow-once",
4384
4385
  "allow-always",
4385
4386
  "deny"
4386
4387
  ] : ["allow-once", "deny"];
4387
4388
  }
4388
- function canMapPersistentApproval(requestedSchema, meta) {
4389
+ function canMapPersistentApproval(requestedSchema, meta, allowSession) {
4389
4390
  const persistHints = readPersistHints(meta, "explicit");
4391
+ if (allowSession) return choosePersistHint(persistHints) !== void 0;
4390
4392
  if (persistHints.length > 0) return persistHints.includes("always");
4391
4393
  const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
4392
4394
  return Object.entries(properties).some(([name, value]) => {
@@ -4411,16 +4413,20 @@ function readBridgeableApprovalElicitation(requestParams) {
4411
4413
  const requestedSchema = requestParams.requestedSchema;
4412
4414
  if (readNonBlankStringField(requestedSchema, "type") !== "object" || !isJsonObject(requestedSchema.properties)) return;
4413
4415
  const title = sanitizeDisplayText(readNonBlankStringField(requestParams, "message") ?? "") || "Codex MCP tool approval";
4416
+ const serverName = readNonBlankStringField(requestParams, "serverName");
4414
4417
  return {
4415
4418
  title,
4416
4419
  description: buildApprovalDescription({
4417
4420
  title,
4418
4421
  meta: requestParams["_meta"],
4419
4422
  requestedSchema,
4420
- serverName: sanitizeOptionalDisplayText(readNonBlankStringField(requestParams, "serverName"))
4423
+ serverName: sanitizeOptionalDisplayText(serverName),
4424
+ remedy: serverName ? formatMcpCodexApprovalRemedy(serverName) : void 0
4421
4425
  }),
4422
4426
  requestedSchema,
4423
- meta: requestParams["_meta"]
4427
+ meta: requestParams["_meta"],
4428
+ persistHintsMode: "explicit",
4429
+ allowedDecisions: buildApprovalAllowedDecisions(requestedSchema, requestParams["_meta"], true)
4424
4430
  };
4425
4431
  }
4426
4432
  function readComputerUseApprovalElicitation(requestParams, expectedServerName) {
@@ -4450,6 +4456,7 @@ function buildApprovalDescription(params) {
4450
4456
  connectorName && `App: ${connectorName}`,
4451
4457
  toolTitle && `Tool: ${toolTitle}`,
4452
4458
  params.serverName && `MCP server: ${params.serverName}`,
4459
+ params.remedy,
4453
4460
  toolDescription
4454
4461
  ].filter((line) => Boolean(line));
4455
4462
  const paramLines = readDisplayParamLines(params.meta);
@@ -4530,36 +4537,10 @@ function sanitizeDisplayText(value) {
4530
4537
  const escaped = sanitized ? formatCodexDisplayText(sanitized) : "";
4531
4538
  return clipped && escaped ? `${escaped}...` : escaped;
4532
4539
  }
4533
- async function requestPluginApprovalOutcome(params) {
4534
- try {
4535
- const requestResult = await requestPluginApproval({
4536
- hostCapabilities: params.paramsForRun.hostCapabilities,
4537
- signal: params.signal,
4538
- title: params.title,
4539
- description: params.description,
4540
- severity: "warning",
4541
- toolName: "codex_mcp_tool_approval",
4542
- allowedDecisions: params.allowedDecisions
4543
- });
4544
- const approvalId = requestResult?.id;
4545
- if (!approvalId) return "unavailable";
4546
- const approvalResult = approvalRequestExplicitlyUnavailable(requestResult) ? void 0 : await waitForPluginApprovalDecision({
4547
- hostCapabilities: params.paramsForRun.hostCapabilities,
4548
- approvalId,
4549
- signal: params.signal
4550
- });
4551
- if (params.signal?.aborted) return "cancelled";
4552
- if (approvalResult?.terminalReason === "timeout") return "timed-out";
4553
- return mapExecDecisionToOutcome(approvalResult?.decision);
4554
- } catch {
4555
- return params.signal?.aborted ? "cancelled" : "denied";
4556
- }
4557
- }
4558
4540
  function buildElicitationResponse(approvalPrompt, outcome) {
4559
4541
  const { requestedSchema, meta } = approvalPrompt;
4560
4542
  if (outcome === "cancelled") return createCodexElicitationResponse("cancel");
4561
- if (outcome === "timed-out") return createCodexElicitationResponse("decline", null, { message: codexApprovalTimeoutText("other") });
4562
- if (outcome === "denied" || outcome === "unavailable") return createCodexElicitationResponse("decline");
4543
+ if (outcome === "timed-out" || outcome === "denied" || outcome === "unavailable") return createCodexElicitationResponse("decline");
4563
4544
  const content = buildAcceptedContent(approvalPrompt, outcome);
4564
4545
  if (!content && !hasNoSchemaProperties(requestedSchema)) {
4565
4546
  embeddedAgentLog.warn("codex MCP approval elicitation approved without a mappable response", {
@@ -4586,7 +4567,7 @@ function buildAcceptedContent(approvalPrompt, outcome) {
4586
4567
  schema,
4587
4568
  required: required.has(name)
4588
4569
  };
4589
- const next = readApprovalFieldValue(property, outcome) ?? readPersistFieldValue(property, meta, outcome, approvalPrompt.persistHintsMode ?? "legacy") ?? readFallbackFieldValue(property, outcome);
4570
+ const next = readApprovalFieldValue(property, outcome, choosePersistHint(readPersistHints(meta, approvalPrompt.persistHintsMode))) ?? readPersistFieldValue(property, meta, outcome, approvalPrompt.persistHintsMode ?? "legacy") ?? readFallbackFieldValue(property, outcome);
4590
4571
  if (next === void 0) {
4591
4572
  if (isApprovalField(property)) sawApprovalField = true;
4592
4573
  if (property.required) return;
@@ -4597,15 +4578,14 @@ function buildAcceptedContent(approvalPrompt, outcome) {
4597
4578
  }
4598
4579
  return sawApprovalField ? content : void 0;
4599
4580
  }
4600
- function readApprovalFieldValue(property, outcome) {
4581
+ function readApprovalFieldValue(property, outcome, persist) {
4601
4582
  if (!isApprovalField(property)) return;
4602
4583
  if (readNonBlankStringField(property.schema, "type") === "boolean") return true;
4603
4584
  const options = readEnumOptions(property.schema);
4604
4585
  if (options.length === 0) return;
4605
- const sessionChoice = options.find((option) => isSessionApprovalOption(option));
4606
4586
  const acceptChoice = options.find((option) => isPositiveApprovalOption(option));
4607
- if (outcome === "approved-session") return sessionChoice?.value ?? acceptChoice?.value;
4608
- return acceptChoice?.value ?? sessionChoice?.value;
4587
+ if (outcome === "approved-session") return options.find((option) => isPersistentApprovalOption(option, persist))?.value ?? acceptChoice?.value;
4588
+ return acceptChoice?.value;
4609
4589
  }
4610
4590
  function readPersistFieldValue(property, meta, outcome, persistHintsMode) {
4611
4591
  if (!isPersistField(property) || outcome !== "approved-session") return;
@@ -4684,9 +4664,9 @@ function isPositiveApprovalOption(option) {
4684
4664
  const haystack = `${option.value} ${option.label}`.toLowerCase();
4685
4665
  return /\b(allow|approve|accept|yes|continue|proceed|true)\b/.test(haystack);
4686
4666
  }
4687
- function isSessionApprovalOption(option) {
4667
+ function isPersistentApprovalOption(option, persist) {
4688
4668
  const haystack = `${option.value} ${option.label}`.toLowerCase();
4689
- return /\b(session|always|persistent)\b/.test(haystack) && /\b(allow|approve|accept)\b/.test(haystack);
4669
+ return (persist === "always" ? /\b(always|persistent)\b|\bdon't ask me again\b/.test(haystack) : persist === "session" && /\bsession\b/.test(haystack)) && /\b(allow|approve|accept)\b/.test(haystack);
4690
4670
  }
4691
4671
  function readNonBlankStringField(record, key) {
4692
4672
  const value = record?.[key];
@@ -4699,6 +4679,191 @@ function readFirstString$1(record, keys) {
4699
4679
  }
4700
4680
  }
4701
4681
  //#endregion
4682
+ //#region extensions/codex/src/app-server/dynamic-tool-catalog.ts
4683
+ /** Namespace attached to OpenClaw-owned dynamic tools exposed to Codex. */
4684
+ const CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE = "openclaw";
4685
+ const CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS = 128;
4686
+ const CODEX_DYNAMIC_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/u;
4687
+ const ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES = /* @__PURE__ */ new Set([
4688
+ "agents_list",
4689
+ "progress_card",
4690
+ "sessions_spawn",
4691
+ "sessions_yield"
4692
+ ]);
4693
+ function createCodexDynamicToolSpecs(params) {
4694
+ const directToolNames = /* @__PURE__ */ new Set([...ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES, ...params.directToolNames ?? []]);
4695
+ const specs = [];
4696
+ const namespaceTools = [];
4697
+ const directOnlyNamespaceTools = [];
4698
+ const entries = params.loading === "direct" ? params.entries : params.entries.toSorted((left, right) => left.name.localeCompare(right.name));
4699
+ for (const entry of entries) {
4700
+ const functionSpec = createCodexDynamicToolFunctionSpec({ entry });
4701
+ if (entry.name === "openclaw" && directToolNames.has(entry.name)) {
4702
+ specs.push(functionSpec);
4703
+ continue;
4704
+ }
4705
+ if (entry.tool.catalogMode === "direct-only") {
4706
+ directOnlyNamespaceTools.push(functionSpec);
4707
+ continue;
4708
+ }
4709
+ if (params.loading === "direct" || directToolNames.has(entry.name)) {
4710
+ specs.push(functionSpec);
4711
+ continue;
4712
+ }
4713
+ namespaceTools.push({
4714
+ ...functionSpec,
4715
+ deferLoading: true
4716
+ });
4717
+ }
4718
+ if (namespaceTools.length > 0) specs.push({
4719
+ type: "namespace",
4720
+ name: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
4721
+ description: "",
4722
+ tools: namespaceTools
4723
+ });
4724
+ if (directOnlyNamespaceTools.length > 0) specs.push({
4725
+ type: "namespace",
4726
+ name: CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE,
4727
+ description: "",
4728
+ tools: directOnlyNamespaceTools
4729
+ });
4730
+ return specs;
4731
+ }
4732
+ function createCodexDynamicToolFunctionSpec(params) {
4733
+ return {
4734
+ type: "function",
4735
+ name: params.entry.name,
4736
+ description: params.entry.description,
4737
+ inputSchema: params.entry.inputSchema
4738
+ };
4739
+ }
4740
+ function projectCodexDynamicTools(tools) {
4741
+ const projectedTools = [];
4742
+ const quarantinedTools = [];
4743
+ let length;
4744
+ try {
4745
+ length = tools.length;
4746
+ } catch {
4747
+ return {
4748
+ tools: [],
4749
+ quarantinedTools: [{
4750
+ tool: "tool[0]",
4751
+ violations: ["tool[0] is unreadable"]
4752
+ }]
4753
+ };
4754
+ }
4755
+ for (let toolIndex = 0; toolIndex < length; toolIndex += 1) {
4756
+ let tool;
4757
+ try {
4758
+ tool = tools[toolIndex];
4759
+ } catch {
4760
+ quarantinedTools.push({
4761
+ tool: `tool[${toolIndex}]`,
4762
+ violations: [`tool[${toolIndex}] is unreadable`]
4763
+ });
4764
+ continue;
4765
+ }
4766
+ const descriptor = readCodexDynamicToolDescriptor(tool, toolIndex);
4767
+ if (!descriptor.ok) {
4768
+ quarantinedTools.push(descriptor.diagnostic);
4769
+ continue;
4770
+ }
4771
+ const normalizedParameters = normalizeOpenAIStrictCompatSchema(descriptor.parameters);
4772
+ const projection = projectRuntimeToolInputSchema(normalizedParameters ?? descriptor.parameters, `${descriptor.name}.inputSchema`);
4773
+ if (projection.violations.length > 0) {
4774
+ quarantinedTools.push({
4775
+ tool: descriptor.name,
4776
+ violations: projection.violations
4777
+ });
4778
+ continue;
4779
+ }
4780
+ if (!isRecord(projection.schema)) {
4781
+ quarantinedTools.push({
4782
+ tool: descriptor.name,
4783
+ violations: [`${descriptor.name}.inputSchema must be a JSON object schema`]
4784
+ });
4785
+ continue;
4786
+ }
4787
+ projectedTools.push({
4788
+ tool,
4789
+ name: descriptor.name,
4790
+ description: descriptor.description,
4791
+ inputSchema: projection.schema
4792
+ });
4793
+ }
4794
+ return {
4795
+ tools: projectedTools,
4796
+ quarantinedTools
4797
+ };
4798
+ }
4799
+ function readCodexDynamicToolDescriptor(tool, toolIndex) {
4800
+ const fallbackName = `tool[${toolIndex}]`;
4801
+ let name;
4802
+ try {
4803
+ const rawName = tool.name;
4804
+ if (typeof rawName !== "string" || !rawName) return {
4805
+ ok: false,
4806
+ diagnostic: {
4807
+ tool: fallbackName,
4808
+ violations: [`${fallbackName}.name must be a non-empty string`]
4809
+ }
4810
+ };
4811
+ const trimmedName = rawName.trim();
4812
+ let nameViolation;
4813
+ if (!trimmedName) nameViolation = `${rawName}.name must not be empty`;
4814
+ else if (trimmedName !== rawName) nameViolation = `${rawName}.name must not have leading or trailing whitespace`;
4815
+ else if (!CODEX_DYNAMIC_TOOL_NAME_PATTERN.test(rawName)) nameViolation = `${rawName}.name must match ^[a-zA-Z0-9_-]+$`;
4816
+ else if (rawName.length > CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS) nameViolation = `${rawName}.name must be at most ${CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS} characters`;
4817
+ else if (rawName === "mcp" || rawName.startsWith("mcp__")) nameViolation = `${rawName}.name is reserved by Codex app-server`;
4818
+ if (nameViolation) return {
4819
+ ok: false,
4820
+ diagnostic: {
4821
+ tool: rawName,
4822
+ violations: [nameViolation]
4823
+ }
4824
+ };
4825
+ name = rawName;
4826
+ } catch {
4827
+ return {
4828
+ ok: false,
4829
+ diagnostic: {
4830
+ tool: fallbackName,
4831
+ violations: [`${fallbackName}.name is unreadable`]
4832
+ }
4833
+ };
4834
+ }
4835
+ let description;
4836
+ try {
4837
+ description = typeof tool.description === "string" ? tool.description : "";
4838
+ } catch {
4839
+ return {
4840
+ ok: false,
4841
+ diagnostic: {
4842
+ tool: name,
4843
+ violations: [`${name}.description is unreadable`]
4844
+ }
4845
+ };
4846
+ }
4847
+ let parameters;
4848
+ try {
4849
+ parameters = tool.parameters;
4850
+ } catch {
4851
+ return {
4852
+ ok: false,
4853
+ diagnostic: {
4854
+ tool: name,
4855
+ violations: [`${name}.inputSchema is unreadable`]
4856
+ }
4857
+ };
4858
+ }
4859
+ return {
4860
+ ok: true,
4861
+ name,
4862
+ description,
4863
+ parameters
4864
+ };
4865
+ }
4866
+ //#endregion
4702
4867
  //#region extensions/codex/src/app-server/dynamic-tools.ts
4703
4868
  /**
4704
4869
  * Bridges OpenClaw runtime tools into Codex app-server dynamic tool specs and
@@ -4862,15 +5027,6 @@ function normalizeAcceptedSessionSpawn(result) {
4862
5027
  childSessionKey
4863
5028
  } : null;
4864
5029
  }
4865
- /** Namespace attached to OpenClaw-owned dynamic tools exposed to Codex. */
4866
- const CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE = "openclaw";
4867
- const CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS = 128;
4868
- const CODEX_DYNAMIC_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/u;
4869
- const ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES = /* @__PURE__ */ new Set([
4870
- "agents_list",
4871
- "sessions_spawn",
4872
- "sessions_yield"
4873
- ]);
4874
5030
  const EXPLICIT_MESSAGE_PROVIDER_KEYS = ["channel", "provider"];
4875
5031
  const EXPLICIT_MESSAGE_TARGET_KEYS = [
4876
5032
  "target",
@@ -4910,11 +5066,13 @@ function createCodexDynamicToolBridge(params) {
4910
5066
  const toolResultMaxChars = typeof contextWindowTokens === "number" && Number.isFinite(contextWindowTokens) && contextWindowTokens > 0 ? Math.max(1, resolveLiveToolResultMaxChars({ contextWindowTokens })) : DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS;
4911
5067
  const availableProjection = projectCodexExecutableDynamicToolSurface(params.tools, params.hookContext);
4912
5068
  const registeredProjection = params.registeredTools ? projectCodexDynamicTools(params.registeredTools) : availableProjection;
4913
- const availableTools = availableProjection.tools;
5069
+ const inheritedSpecs = params.registeredSpecs ? structuredClone([...params.registeredSpecs]) : void 0;
5070
+ const inheritedNames = inheritedSpecs ? new Set(flattenCodexDynamicToolFunctions(inheritedSpecs).map((tool) => tool.name)) : void 0;
5071
+ const availableTools = availableProjection.tools.filter((entry) => !inheritedNames || inheritedNames.has(entry.name));
4914
5072
  const quarantinedAvailableToolNames = new Set(availableProjection.quarantinedTools.map((tool) => tool.tool));
4915
5073
  const registeredSpecTools = (params.registeredTools ? registeredProjection.tools : availableTools).filter((entry) => !quarantinedAvailableToolNames.has(entry.name));
4916
5074
  const toolMap = new Map(availableTools.map((entry) => [entry.name, entry]));
4917
- const registeredToolNames = new Set(registeredSpecTools.map((entry) => entry.name));
5075
+ const registeredToolNames = inheritedNames ?? new Set(registeredSpecTools.map((entry) => entry.name));
4918
5076
  const quarantinedTools = dedupeQuarantinedDynamicTools([...availableProjection.quarantinedTools, ...registeredProjection.quarantinedTools]);
4919
5077
  warnQuarantinedDynamicTools({
4920
5078
  tools: quarantinedTools,
@@ -4947,16 +5105,23 @@ function createCodexDynamicToolBridge(params) {
4947
5105
  };
4948
5106
  const legacyExtensionRunner = createCodexAppServerToolResultExtensionRunner(toolResultHookContext);
4949
5107
  const executionSnapshotStates = /* @__PURE__ */ new Map();
4950
- const directToolNames = /* @__PURE__ */ new Set([...ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES, ...params.directToolNames ?? []]);
5108
+ const directToolNames = params.directToolNames;
4951
5109
  let readRemoteWorkspaceFile;
4952
5110
  return {
4953
5111
  availableTools: availableTools.map((entry) => entry.tool),
4954
- availableSpecs: createCodexDynamicToolSpecs({
5112
+ availableSpecs: inheritedSpecs ? inheritedSpecs.flatMap((spec) => {
5113
+ if (spec.type === "function") return toolMap.has(spec.name) ? [spec] : [];
5114
+ const tools = spec.tools.filter((tool) => toolMap.has(tool.name));
5115
+ return tools.length ? [{
5116
+ ...spec,
5117
+ tools
5118
+ }] : [];
5119
+ }) : createCodexDynamicToolSpecs({
4955
5120
  entries: availableTools,
4956
5121
  loading: params.loading ?? "searchable",
4957
5122
  directToolNames
4958
5123
  }),
4959
- specs: createCodexDynamicToolSpecs({
5124
+ specs: inheritedSpecs ?? createCodexDynamicToolSpecs({
4960
5125
  entries: registeredSpecTools,
4961
5126
  loading: params.loading ?? "searchable",
4962
5127
  directToolNames
@@ -5171,6 +5336,8 @@ function createCodexDynamicToolBridge(params) {
5171
5336
  sideEffectEvidence: !replaySafe
5172
5337
  });
5173
5338
  } catch (error) {
5339
+ const trustedNoStart = consumeTrustedToolNoStartError(error);
5340
+ executionPrevented ||= trustedNoStart;
5174
5341
  captureExecutionBoundary();
5175
5342
  if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId) invalidateComputerFrame(params.computerContextEpoch);
5176
5343
  const executionDisposition = getBeforeToolCallFailureDisposition(error) ?? (signal.aborted ? resolveCodexToolAbortTerminalReason(signal) : resolveToolExecutionErrorKind(error));
@@ -5281,182 +5448,6 @@ function wrapProjectedCodexDynamicTools(tools, hookContext) {
5281
5448
  quarantinedTools
5282
5449
  };
5283
5450
  }
5284
- function createCodexDynamicToolSpecs(params) {
5285
- const specs = [];
5286
- const namespaceTools = [];
5287
- const directOnlyNamespaceTools = [];
5288
- const entries = params.loading === "direct" ? params.entries : params.entries.toSorted((left, right) => left.name.localeCompare(right.name));
5289
- for (const entry of entries) {
5290
- const functionSpec = createCodexDynamicToolFunctionSpec({ entry });
5291
- if (entry.name === "openclaw" && params.directToolNames.has(entry.name)) {
5292
- specs.push(functionSpec);
5293
- continue;
5294
- }
5295
- if (entry.tool.catalogMode === "direct-only") {
5296
- directOnlyNamespaceTools.push(functionSpec);
5297
- continue;
5298
- }
5299
- if (params.loading === "direct" || params.directToolNames.has(entry.name)) {
5300
- specs.push(functionSpec);
5301
- continue;
5302
- }
5303
- namespaceTools.push({
5304
- ...functionSpec,
5305
- deferLoading: true
5306
- });
5307
- }
5308
- if (namespaceTools.length > 0) specs.push({
5309
- type: "namespace",
5310
- name: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
5311
- description: "",
5312
- tools: namespaceTools
5313
- });
5314
- if (directOnlyNamespaceTools.length > 0) specs.push({
5315
- type: "namespace",
5316
- name: CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE,
5317
- description: "",
5318
- tools: directOnlyNamespaceTools
5319
- });
5320
- return specs;
5321
- }
5322
- function createCodexDynamicToolFunctionSpec(params) {
5323
- return {
5324
- type: "function",
5325
- name: params.entry.name,
5326
- description: params.entry.description,
5327
- inputSchema: params.entry.inputSchema
5328
- };
5329
- }
5330
- function projectCodexDynamicTools(tools) {
5331
- const projectedTools = [];
5332
- const quarantinedTools = [];
5333
- let length;
5334
- try {
5335
- length = tools.length;
5336
- } catch {
5337
- return {
5338
- tools: [],
5339
- quarantinedTools: [{
5340
- tool: "tool[0]",
5341
- violations: ["tool[0] is unreadable"]
5342
- }]
5343
- };
5344
- }
5345
- for (let toolIndex = 0; toolIndex < length; toolIndex += 1) {
5346
- let tool;
5347
- try {
5348
- tool = tools[toolIndex];
5349
- } catch {
5350
- quarantinedTools.push({
5351
- tool: `tool[${toolIndex}]`,
5352
- violations: [`tool[${toolIndex}] is unreadable`]
5353
- });
5354
- continue;
5355
- }
5356
- const descriptor = readCodexDynamicToolDescriptor(tool, toolIndex);
5357
- if (!descriptor.ok) {
5358
- quarantinedTools.push(descriptor.diagnostic);
5359
- continue;
5360
- }
5361
- const normalizedParameters = normalizeOpenAIToolSchemas({
5362
- provider: "openai",
5363
- modelApi: "openai-chatgpt-responses",
5364
- tools: [{ parameters: descriptor.parameters }]
5365
- })[0]?.parameters;
5366
- const projection = projectRuntimeToolInputSchema(normalizedParameters ?? descriptor.parameters, `${descriptor.name}.inputSchema`);
5367
- if (projection.violations.length > 0) {
5368
- quarantinedTools.push({
5369
- tool: descriptor.name,
5370
- violations: projection.violations
5371
- });
5372
- continue;
5373
- }
5374
- if (!isRecord(projection.schema)) {
5375
- quarantinedTools.push({
5376
- tool: descriptor.name,
5377
- violations: [`${descriptor.name}.inputSchema must be a JSON object schema`]
5378
- });
5379
- continue;
5380
- }
5381
- projectedTools.push({
5382
- tool,
5383
- name: descriptor.name,
5384
- description: descriptor.description,
5385
- inputSchema: projection.schema
5386
- });
5387
- }
5388
- return {
5389
- tools: projectedTools,
5390
- quarantinedTools
5391
- };
5392
- }
5393
- function readCodexDynamicToolDescriptor(tool, toolIndex) {
5394
- const fallbackName = `tool[${toolIndex}]`;
5395
- let name;
5396
- try {
5397
- const rawName = tool.name;
5398
- if (typeof rawName !== "string" || !rawName) return {
5399
- ok: false,
5400
- diagnostic: {
5401
- tool: fallbackName,
5402
- violations: [`${fallbackName}.name must be a non-empty string`]
5403
- }
5404
- };
5405
- const trimmedName = rawName.trim();
5406
- let nameViolation;
5407
- if (!trimmedName) nameViolation = `${rawName}.name must not be empty`;
5408
- else if (trimmedName !== rawName) nameViolation = `${rawName}.name must not have leading or trailing whitespace`;
5409
- else if (!CODEX_DYNAMIC_TOOL_NAME_PATTERN.test(rawName)) nameViolation = `${rawName}.name must match ^[a-zA-Z0-9_-]+$`;
5410
- else if (rawName.length > CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS) nameViolation = `${rawName}.name must be at most ${CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS} characters`;
5411
- else if (rawName === "mcp" || rawName.startsWith("mcp__")) nameViolation = `${rawName}.name is reserved by Codex app-server`;
5412
- if (nameViolation) return {
5413
- ok: false,
5414
- diagnostic: {
5415
- tool: rawName,
5416
- violations: [nameViolation]
5417
- }
5418
- };
5419
- name = rawName;
5420
- } catch {
5421
- return {
5422
- ok: false,
5423
- diagnostic: {
5424
- tool: fallbackName,
5425
- violations: [`${fallbackName}.name is unreadable`]
5426
- }
5427
- };
5428
- }
5429
- let description;
5430
- try {
5431
- description = typeof tool.description === "string" ? tool.description : "";
5432
- } catch {
5433
- return {
5434
- ok: false,
5435
- diagnostic: {
5436
- tool: name,
5437
- violations: [`${name}.description is unreadable`]
5438
- }
5439
- };
5440
- }
5441
- let parameters;
5442
- try {
5443
- parameters = tool.parameters;
5444
- } catch {
5445
- return {
5446
- ok: false,
5447
- diagnostic: {
5448
- tool: name,
5449
- violations: [`${name}.inputSchema is unreadable`]
5450
- }
5451
- };
5452
- }
5453
- return {
5454
- ok: true,
5455
- name,
5456
- description,
5457
- parameters
5458
- };
5459
- }
5460
5451
  function warnQuarantinedDynamicTools(params) {
5461
5452
  if (params.tools.length === 0) return;
5462
5453
  const unique = /* @__PURE__ */ new Map();
@@ -5767,4 +5758,4 @@ function isCronAddAction(args) {
5767
5758
  return typeof action === "string" && action.trim().toLowerCase() === "add";
5768
5759
  }
5769
5760
  //#endregion
5770
- export { shouldRequireCodexSandboxExecServerEnvironment as C, CodexNativeToolLifecycleProjector as D, readBoundedCodexRemoteWorkspaceFile as E, shouldEnableCodexAppServerNativeToolSurface as S, filterCodexVisionTools as T, resolveCodexAppServerHookChannelId as _, emitDynamicToolStartedDiagnostic as a, resolveCodexNodePlacementToolConstructionPlan as b, resolveCodexProviderWebSearchSupport as c, releaseCodexSandboxExecServerEnvironment as d, buildDynamicTools as f, resolveCodexAppServerExecutionCwd as g, formatCodexDynamicToolBuildStageSummary as h, emitDynamicToolErrorDiagnostic as i, resolveCodexProviderWebSearchSupportForClient as l, disableCodexPluginThreadConfig as m, projectCodexExecutableDynamicTools as n, emitDynamicToolTerminalDiagnostic as o, createCodexDynamicToolBuildStageTracker as p, routeCodexAppServerElicitationRequest as r, handleCodexAppServerApprovalRequest as s, createCodexDynamicToolBridge as t, ensureCodexSandboxExecServerEnvironment as u, resolveCodexExternalSandboxPolicyForOpenClawSandbox as v, shouldWarnCodexDynamicToolBuildStageSummary as w, resolveCodexSandboxEnvironmentSelection as x, resolveCodexMessageToolProvider as y };
5761
+ export { filterCodexVisionTools as C, shouldWarnCodexDynamicToolBuildStageSummary as S, CodexNativeToolLifecycleProjector as T, resolveCodexMessageToolProvider as _, emitDynamicToolStartedDiagnostic as a, shouldEnableCodexAppServerNativeToolSurface as b, ensureCodexSandboxExecServerEnvironment as c, createCodexDynamicToolBuildStageTracker as d, disableCodexPluginThreadConfig as f, resolveCodexExternalSandboxPolicyForOpenClawSandbox as g, resolveCodexAppServerHookChannelId as h, emitDynamicToolErrorDiagnostic as i, releaseCodexSandboxExecServerEnvironment as l, resolveCodexAppServerExecutionCwd as m, projectCodexExecutableDynamicTools as n, emitDynamicToolTerminalDiagnostic as o, formatCodexDynamicToolBuildStageSummary as p, routeCodexAppServerElicitationRequest as r, handleCodexAppServerApprovalRequest as s, createCodexDynamicToolBridge as t, buildDynamicTools as u, resolveCodexNodePlacementToolConstructionPlan as v, readBoundedCodexRemoteWorkspaceFile as w, shouldRequireCodexSandboxExecServerEnvironment as x, resolveCodexSandboxEnvironmentSelection as y };