@openclaw/codex 2026.6.8-beta.2 → 2026.6.9-beta.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.
- package/dist/{notification-correlation-BGoG0N90.js → app-server-policy-KpyUye2D.js} +15 -74
- package/dist/attempt-notifications-Meja3TX6.js +249 -0
- package/dist/{client-factory-C71Xdb6z.js → client-factory-CPuoOOx6.js} +3 -2
- package/dist/{client-DovmVtbr.js → client-m7XiCQpO.js} +24 -2
- package/dist/{command-handlers-DHecBkuc.js → command-handlers-CqS0Kv3Z.js} +17 -13
- package/dist/{compact-GEKusemL.js → compact-CMVlldii.js} +5 -5
- package/dist/{computer-use-D1qxvTE_.js → computer-use-qLm7Gf35.js} +3 -3
- package/dist/{config-DJXpBkGf.js → config-BksX0T33.js} +160 -20
- package/dist/{conversation-binding-CbnMilVW.js → conversation-binding-DBof5w9J.js} +166 -58
- package/dist/doctor-contract-api.js +37 -3
- package/dist/harness.js +6 -6
- package/dist/index.js +108 -27
- package/dist/media-understanding-provider-CiwPkR58.js +463 -0
- package/dist/media-understanding-provider.js +1 -355
- package/dist/{models-DRKnOpzF.js → models-BsyV1Psm.js} +2 -2
- package/dist/{plugin-app-cache-key-kIqpGTQj.js → plugin-app-cache-key-CN9zBiCG.js} +17 -6
- package/dist/{protocol-oeJQu4rs.js → protocol-dh-ETiNd.js} +4 -1
- package/dist/{protocol-validators-B48C6S9O.js → protocol-validators-B19q5BIX.js} +210 -16
- package/dist/{native-hook-relay-C3i3FYtc.js → provider-capabilities-BOvTfMhF.js} +803 -78
- package/dist/{provider-C5P6352f.js → provider-qJygHQx4.js} +4 -4
- package/dist/provider.js +1 -1
- package/dist/{request-BxZ40QKY.js → request-DTIEyUrA.js} +2 -2
- package/dist/{run-attempt-D_q-VrpR.js → run-attempt-DRhB6bTc.js} +421 -774
- package/dist/{sandbox-guard-C-Yv9uwY.js → sandbox-guard-3tnjhjFb.js} +37 -10
- package/dist/{session-binding-ElbcSq2o.js → session-binding-BRUi8y6E.js} +27 -8
- package/dist/{shared-client-jFMumCOe.js → shared-client-Bg5d7VV9.js} +4 -4
- package/dist/{side-question-DqKLfWMB.js → side-question-BdYL4kj1.js} +106 -42
- package/dist/{thread-lifecycle-cban34w3.js → thread-lifecycle-CbJqqnEw.js} +912 -616
- package/dist/web-search-contract-api.js +10 -0
- package/dist/web-search-provider.runtime-DrpQ8Cyd.js +74 -0
- package/dist/web-search-provider.shared-BrZmlqyR.js +32 -0
- package/npm-shrinkwrap.json +2 -2
- package/openclaw.plugin.json +129 -4
- package/package.json +13 -5
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { n as isJsonObject } from "./protocol-dh-ETiNd.js";
|
|
2
|
+
import { C as isForcedPrivateQaCodexRuntime, S as filterCodexDynamicTools, _ as resolveCodexWebSearchPlan, w as normalizeCodexDynamicToolName, x as sanitizeInlineImageDataUrl, y as invalidInlineImageText } from "./thread-lifecycle-CbJqqnEw.js";
|
|
3
|
+
import { c as isTrustedCodexModelBackedOpenAIProvider, u as readCodexPluginConfig } from "./config-BksX0T33.js";
|
|
4
|
+
import { a as formatCodexDisplayText } from "./app-server-policy-KpyUye2D.js";
|
|
5
|
+
import { o as releaseLeasedSharedCodexAppServerClient } from "./shared-client-Bg5d7VV9.js";
|
|
6
|
+
import { i as resolveCodexNativeExecutionPolicy } from "./sandbox-guard-3tnjhjFb.js";
|
|
5
7
|
import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
8
|
+
import { asOptionalRecord, isRecord, normalizeTrimmedStringList } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
9
|
import { createHash } from "node:crypto";
|
|
10
|
+
import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
|
|
7
11
|
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
8
|
-
import {
|
|
9
|
-
import { HEARTBEAT_RESPONSE_TOOL_NAME, buildAgentHookContextChannelFields, callGatewayTool, createAgentToolResultMiddlewareRunner, createCodexAppServerToolResultExtensionRunner, embeddedAgentLog, extractToolResultMediaArtifact, filterToolResultMediaUrls, formatApprovalDisplayPath, hasNativeHookRelayInvocation, invokeNativeHookRelay, isMessagingTool, isMessagingToolSendAction, isToolResultError, isToolWrappedWithBeforeToolCallHook, normalizeHeartbeatToolResponse, projectRuntimeToolInputSchema, registerNativeHookRelay, resolveNativeHookRelayDeferredToolApproval, runAgentHarnessAfterToolCallHook, runBeforeToolCallHook, sanitizeToolResult, setBeforeToolCallDiagnosticsEnabled, wrapToolWithBeforeToolCallHook } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
12
|
+
import { HEARTBEAT_RESPONSE_TOOL_NAME, buildAgentHookContextChannelFields, buildEmbeddedAttemptToolRunContext, callGatewayTool, consumeAdjustedParamsForToolCall, consumePreExecutionBlockedToolCall, createAgentToolResultMiddlewareRunner, createCodexAppServerToolResultExtensionRunner, embeddedAgentLog, extractMessagingToolSend, extractMessagingToolSendResult, extractToolResultMediaArtifact, filterProviderNormalizableTools, filterToolResultMediaUrls, finalizeToolTerminalPresentation, formatApprovalDisplayPath, getChannelAgentToolMeta, getPluginToolMeta, hasNativeHookRelayInvocation, invokeNativeHookRelay, isMessagingTool, isMessagingToolSendAction, isReplaySafeToolCall, isSubagentSessionKey, isToolResultError, isToolWrappedWithBeforeToolCallHook, normalizeAgentRuntimeTools, normalizeHeartbeatToolResponse, projectRuntimeToolInputSchema, registerNativeHookRelay, resolveAttemptSpawnWorkspaceDir, resolveModelAuthMode, resolveNativeHookRelayDeferredToolApproval, runAgentHarnessAfterToolCallHook, runBeforeToolCallHook, sanitizeToolResult, setBeforeToolCallDiagnosticsEnabled, supportsModelTools, wrapToolWithBeforeToolCallHook } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
10
13
|
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
14
|
+
import { isToolAllowed } from "openclaw/plugin-sdk/sandbox";
|
|
11
15
|
import { buildExecAutoReviewInputForShellCommand, reviewExecRequestWithConfiguredModel } from "openclaw/plugin-sdk/agent-harness-exec-review-runtime";
|
|
12
16
|
/** Default idle timeout while waiting for app-server turn completion. */
|
|
13
17
|
const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 6e4;
|
|
@@ -112,7 +116,8 @@ async function requestPluginApproval(params) {
|
|
|
112
116
|
turnSourceAccountId: params.paramsForRun.agentAccountId,
|
|
113
117
|
turnSourceThreadId: params.paramsForRun.currentThreadTs,
|
|
114
118
|
timeoutMs,
|
|
115
|
-
twoPhase: true
|
|
119
|
+
twoPhase: true,
|
|
120
|
+
...params.allowedDecisions ? { allowedDecisions: params.allowedDecisions } : {}
|
|
116
121
|
}, { expectFinal: false });
|
|
117
122
|
}
|
|
118
123
|
/** Detects the gateway's explicit null-decision marker for unavailable approvals. */
|
|
@@ -1068,6 +1073,535 @@ function filterToolsForVisionInputs(tools, params) {
|
|
|
1068
1073
|
return tools.filter((tool) => tool.name !== "image");
|
|
1069
1074
|
}
|
|
1070
1075
|
//#endregion
|
|
1076
|
+
//#region extensions/codex/src/app-server/dynamic-tool-build.ts
|
|
1077
|
+
/**
|
|
1078
|
+
* Builds the Codex app-server dynamic tool list for one turn, including
|
|
1079
|
+
* OpenClaw-owned tools, Codex native-tool fallback rules, sandbox shell shims,
|
|
1080
|
+
* and provider allowlist normalization.
|
|
1081
|
+
*/
|
|
1082
|
+
const CODEX_NATIVE_SANDBOX_TOOL_REQUIREMENTS = [
|
|
1083
|
+
"exec",
|
|
1084
|
+
"process",
|
|
1085
|
+
"read",
|
|
1086
|
+
"write",
|
|
1087
|
+
"edit",
|
|
1088
|
+
"apply_patch"
|
|
1089
|
+
];
|
|
1090
|
+
const CODEX_MEMORY_FLUSH_DYNAMIC_TOOL_ALLOW = new Set(["read", "write"]);
|
|
1091
|
+
const CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME = "node_exec";
|
|
1092
|
+
const CODEX_NODE_PROCESS_DYNAMIC_TOOL_NAME = "node_process";
|
|
1093
|
+
const CODEX_NODE_EXEC_HIDDEN_PARAMETER_NAMES = new Set([
|
|
1094
|
+
"host",
|
|
1095
|
+
"security",
|
|
1096
|
+
"ask",
|
|
1097
|
+
"node"
|
|
1098
|
+
]);
|
|
1099
|
+
/** Splits sandbox and run session keys so tool calls can bind to both scopes when needed. */
|
|
1100
|
+
function resolveOpenClawCodingToolsSessionKeys(params, sandboxSessionKey) {
|
|
1101
|
+
return {
|
|
1102
|
+
sessionKey: sandboxSessionKey,
|
|
1103
|
+
runSessionKey: params.sessionKey && params.sessionKey !== sandboxSessionKey ? params.sessionKey : void 0
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
/** Returns the canonical channel used for Codex message routing and receipts. */
|
|
1107
|
+
function resolveCodexMessageToolProvider(params) {
|
|
1108
|
+
return params.messageChannel ?? params.messageProvider;
|
|
1109
|
+
}
|
|
1110
|
+
/** Resolves the channel id that hook events should target for this Codex app-server turn. */
|
|
1111
|
+
function resolveCodexAppServerHookChannelId(params, sandboxSessionKey) {
|
|
1112
|
+
return buildAgentHookContextChannelFields({
|
|
1113
|
+
sessionKey: sandboxSessionKey,
|
|
1114
|
+
messageChannel: params.messageChannel,
|
|
1115
|
+
messageProvider: params.messageProvider,
|
|
1116
|
+
currentChannelId: params.currentChannelId,
|
|
1117
|
+
messageTo: params.messageTo
|
|
1118
|
+
}).channelId;
|
|
1119
|
+
}
|
|
1120
|
+
const CODEX_DYNAMIC_TOOL_BUILD_WARN_TOTAL_MS = 1e3;
|
|
1121
|
+
const CODEX_DYNAMIC_TOOL_BUILD_WARN_STAGE_MS = 500;
|
|
1122
|
+
/** Creates cheap optional timing instrumentation for the dynamic-tool hot path. */
|
|
1123
|
+
function createCodexDynamicToolBuildStageTracker(options = {}) {
|
|
1124
|
+
if (!options.enabled) return {
|
|
1125
|
+
mark() {},
|
|
1126
|
+
snapshot() {
|
|
1127
|
+
return {
|
|
1128
|
+
totalMs: 0,
|
|
1129
|
+
stages: []
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
const startedAt = Date.now();
|
|
1134
|
+
let previousAt = startedAt;
|
|
1135
|
+
const stages = [];
|
|
1136
|
+
const toMs = (value) => Math.max(0, Math.round(value));
|
|
1137
|
+
return {
|
|
1138
|
+
mark(name) {
|
|
1139
|
+
const currentAt = Date.now();
|
|
1140
|
+
stages.push({
|
|
1141
|
+
name,
|
|
1142
|
+
durationMs: toMs(currentAt - previousAt),
|
|
1143
|
+
elapsedMs: toMs(currentAt - startedAt)
|
|
1144
|
+
});
|
|
1145
|
+
previousAt = currentAt;
|
|
1146
|
+
},
|
|
1147
|
+
snapshot() {
|
|
1148
|
+
return {
|
|
1149
|
+
totalMs: toMs(Date.now() - startedAt),
|
|
1150
|
+
stages: stages.slice()
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
/** Returns true when dynamic-tool construction is slow enough to warrant a warning log. */
|
|
1156
|
+
function shouldWarnCodexDynamicToolBuildStageSummary(summary) {
|
|
1157
|
+
return summary.totalMs >= CODEX_DYNAMIC_TOOL_BUILD_WARN_TOTAL_MS || summary.stages.some((stage) => stage.durationMs >= CODEX_DYNAMIC_TOOL_BUILD_WARN_STAGE_MS);
|
|
1158
|
+
}
|
|
1159
|
+
/** Formats per-stage timings into the compact form used by Codex app-server logs. */
|
|
1160
|
+
function formatCodexDynamicToolBuildStageSummary(summary) {
|
|
1161
|
+
return summary.stages.length > 0 ? summary.stages.map((stage) => `${stage.name}:${stage.durationMs}ms@${stage.elapsedMs}ms`).join(",") : "none";
|
|
1162
|
+
}
|
|
1163
|
+
/** Builds, filters, and normalizes Codex-compatible runtime tools for a single turn. */
|
|
1164
|
+
async function buildDynamicTools(input) {
|
|
1165
|
+
const { params } = input;
|
|
1166
|
+
const messagePolicyParams = input.ignoreDisableMessageTool ? {
|
|
1167
|
+
...params,
|
|
1168
|
+
disableMessageTool: false
|
|
1169
|
+
} : params;
|
|
1170
|
+
if (params.disableTools) {
|
|
1171
|
+
input.onWebSearchPolicyResolved?.(false);
|
|
1172
|
+
return [];
|
|
1173
|
+
}
|
|
1174
|
+
if (!supportsModelTools(params.model)) {
|
|
1175
|
+
input.onPersistentWebSearchPolicyResolved?.(false);
|
|
1176
|
+
input.onWebSearchPolicyResolved?.(false);
|
|
1177
|
+
return [];
|
|
1178
|
+
}
|
|
1179
|
+
const toolBuildStages = createCodexDynamicToolBuildStageTracker({ enabled: input.profilerEnabled });
|
|
1180
|
+
const modelHasVision = params.model.input?.includes("image") ?? false;
|
|
1181
|
+
const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, input.sessionAgentId);
|
|
1182
|
+
const agentHarness = await import("openclaw/plugin-sdk/agent-harness");
|
|
1183
|
+
const createOpenClawCodingTools = agentHarness.createOpenClawCodingTools;
|
|
1184
|
+
toolBuildStages.mark("load-agent-harness-tools");
|
|
1185
|
+
const sessionKeys = resolveOpenClawCodingToolsSessionKeys(params, input.sandboxSessionKey);
|
|
1186
|
+
const nativeExecutionPolicy = resolveCodexNativeExecutionPolicyForDynamicTools(input);
|
|
1187
|
+
const allTools = createOpenClawCodingTools({
|
|
1188
|
+
agentId: input.sessionAgentId,
|
|
1189
|
+
...buildEmbeddedAttemptToolRunContext(params),
|
|
1190
|
+
exec: {
|
|
1191
|
+
...params.execOverrides,
|
|
1192
|
+
...resolveNodeExecToolOverrides(nativeExecutionPolicy),
|
|
1193
|
+
config: params.config,
|
|
1194
|
+
elevated: params.bashElevated
|
|
1195
|
+
},
|
|
1196
|
+
sandbox: input.sandbox,
|
|
1197
|
+
messageProvider: resolveCodexMessageToolProvider(params),
|
|
1198
|
+
toolPolicyMessageProvider: params.messageProvider ?? params.messageChannel,
|
|
1199
|
+
agentAccountId: params.agentAccountId,
|
|
1200
|
+
messageTo: params.messageTo,
|
|
1201
|
+
messageThreadId: params.messageThreadId,
|
|
1202
|
+
groupId: params.groupId,
|
|
1203
|
+
groupChannel: params.groupChannel,
|
|
1204
|
+
groupSpace: params.groupSpace,
|
|
1205
|
+
spawnedBy: params.spawnedBy,
|
|
1206
|
+
senderId: params.senderId,
|
|
1207
|
+
senderName: params.senderName,
|
|
1208
|
+
senderUsername: params.senderUsername,
|
|
1209
|
+
senderE164: params.senderE164,
|
|
1210
|
+
allowGatewaySubagentBinding: params.allowGatewaySubagentBinding || isForcedPrivateQaCodexRuntime(),
|
|
1211
|
+
...sessionKeys,
|
|
1212
|
+
sessionId: params.sessionId,
|
|
1213
|
+
runId: params.runId,
|
|
1214
|
+
agentDir,
|
|
1215
|
+
cwd: input.effectiveCwd ?? input.effectiveWorkspace,
|
|
1216
|
+
workspaceDir: input.effectiveWorkspace,
|
|
1217
|
+
spawnWorkspaceDir: input.effectiveCwd && input.effectiveCwd !== input.effectiveWorkspace ? input.resolvedWorkspace : resolveAttemptSpawnWorkspaceDir({
|
|
1218
|
+
sandbox: input.sandbox,
|
|
1219
|
+
resolvedWorkspace: input.resolvedWorkspace
|
|
1220
|
+
}),
|
|
1221
|
+
config: params.config,
|
|
1222
|
+
authProfileStore: params.toolAuthProfileStore ?? params.authProfileStore,
|
|
1223
|
+
abortSignal: input.runAbortController.signal,
|
|
1224
|
+
emitBeforeToolCallDiagnostics: false,
|
|
1225
|
+
modelProvider: params.model.provider,
|
|
1226
|
+
modelId: params.modelId,
|
|
1227
|
+
modelCompat: params.model.compat && typeof params.model.compat === "object" ? params.model.compat : void 0,
|
|
1228
|
+
modelApi: params.model.api,
|
|
1229
|
+
modelContextWindowTokens: params.model.contextWindow,
|
|
1230
|
+
modelAuthMode: resolveModelAuthMode(params.model.provider, params.config, params.toolAuthProfileStore ?? params.authProfileStore, { workspaceDir: input.effectiveWorkspace }),
|
|
1231
|
+
suppressManagedWebSearch: false,
|
|
1232
|
+
currentChannelId: params.currentChannelId,
|
|
1233
|
+
currentMessagingTarget: params.currentMessagingTarget,
|
|
1234
|
+
hookChannelId: resolveCodexAppServerHookChannelId(params, input.sandboxSessionKey),
|
|
1235
|
+
currentThreadTs: params.currentThreadTs,
|
|
1236
|
+
currentMessageId: params.currentMessageId,
|
|
1237
|
+
replyToMode: params.replyToMode,
|
|
1238
|
+
hasRepliedRef: params.hasRepliedRef,
|
|
1239
|
+
modelHasVision,
|
|
1240
|
+
requireExplicitMessageTarget: params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
|
|
1241
|
+
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
|
1242
|
+
disableMessageTool: input.ignoreDisableMessageTool ? false : params.disableMessageTool,
|
|
1243
|
+
forceMessageTool: shouldForceMessageTool(messagePolicyParams),
|
|
1244
|
+
enableHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
|
|
1245
|
+
forceHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
|
|
1246
|
+
onYield: (message) => {
|
|
1247
|
+
input.onYieldDetected();
|
|
1248
|
+
input.onCodexAppServerEvent?.({
|
|
1249
|
+
stream: "codex_app_server.tool",
|
|
1250
|
+
data: {
|
|
1251
|
+
name: "sessions_yield",
|
|
1252
|
+
message
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
},
|
|
1256
|
+
recordToolPrepStage: (name) => {
|
|
1257
|
+
toolBuildStages.mark(name);
|
|
1258
|
+
},
|
|
1259
|
+
onToolOutcome: params.onToolOutcome,
|
|
1260
|
+
allocateToolOutcomeOrdinal: params.allocateToolOutcomeOrdinal
|
|
1261
|
+
});
|
|
1262
|
+
toolBuildStages.mark("create-openclaw-coding-tools");
|
|
1263
|
+
const preNormalizationDiagnostics = [];
|
|
1264
|
+
const readableAllToolProjection = filterProviderNormalizableTools(allTools);
|
|
1265
|
+
preNormalizationDiagnostics.push(...readableAllToolProjection.diagnostics);
|
|
1266
|
+
const webSearchPlan = resolveCodexWebSearchPlan({
|
|
1267
|
+
config: params.config,
|
|
1268
|
+
disableTools: params.disableTools,
|
|
1269
|
+
nativeToolSurfaceEnabled: input.nativeToolSurfaceEnabled,
|
|
1270
|
+
nativeProviderWebSearchSupport: input.nativeProviderWebSearchSupport
|
|
1271
|
+
});
|
|
1272
|
+
const readableAllTools = [...readableAllToolProjection.tools];
|
|
1273
|
+
const codexFilteredTools = addNodeShellDynamicToolsIfNeeded(addSandboxShellDynamicToolsIfAvailable(isCodexMemoryFlushRun(params) ? filterCodexMemoryFlushDynamicTools(readableAllTools) : filterCodexDynamicTools(readableAllTools, input.pluginConfig), readableAllTools, input), readableAllTools, input, nativeExecutionPolicy);
|
|
1274
|
+
toolBuildStages.mark("codex-filtering");
|
|
1275
|
+
const visionFilteredTools = filterToolsForVisionInputs(codexFilteredTools, {
|
|
1276
|
+
modelHasVision,
|
|
1277
|
+
hasInboundImages: (params.images?.length ?? 0) > 0
|
|
1278
|
+
});
|
|
1279
|
+
toolBuildStages.mark("vision-filtering");
|
|
1280
|
+
const webSearchPresent = visionFilteredTools.some((tool) => tool.name === "web_search");
|
|
1281
|
+
const webSearchPolicy = agentHarness.resolveWebSearchToolPolicy({
|
|
1282
|
+
config: params.config,
|
|
1283
|
+
modelProvider: params.model.provider,
|
|
1284
|
+
modelId: params.modelId,
|
|
1285
|
+
agentId: input.sessionAgentId,
|
|
1286
|
+
sessionKey: input.sandboxSessionKey,
|
|
1287
|
+
sandboxToolPolicy: input.sandbox?.tools,
|
|
1288
|
+
messageProvider: resolveCodexMessageToolProvider(params),
|
|
1289
|
+
agentAccountId: params.agentAccountId,
|
|
1290
|
+
groupId: params.groupId,
|
|
1291
|
+
groupChannel: params.groupChannel,
|
|
1292
|
+
groupSpace: params.groupSpace,
|
|
1293
|
+
spawnedBy: params.spawnedBy,
|
|
1294
|
+
senderId: params.senderId,
|
|
1295
|
+
senderName: params.senderName,
|
|
1296
|
+
senderUsername: params.senderUsername,
|
|
1297
|
+
senderE164: params.senderE164
|
|
1298
|
+
});
|
|
1299
|
+
const transientWebSearchRestriction = !webSearchPolicy.allowed && webSearchPolicy.persistentAllowed || isCodexMemoryFlushRun(params);
|
|
1300
|
+
const persistentCodexWebSearchSurface = params.config?.tools?.web?.search?.enabled !== false && !(input.pluginConfig.codexDynamicToolsExclude ?? []).some((name) => normalizeCodexDynamicToolName(name) === "web_search");
|
|
1301
|
+
input.onPersistentWebSearchPolicyResolved?.(webSearchPresent || persistentCodexWebSearchSurface && transientWebSearchRestriction && webSearchPolicy.persistentAllowed);
|
|
1302
|
+
const filteredTools = filterCodexDynamicToolsForAllowlist(visionFilteredTools, includeForcedCodexDynamicToolAllow(params.toolsAllow, messagePolicyParams));
|
|
1303
|
+
toolBuildStages.mark("allowlist-filter");
|
|
1304
|
+
const normalizedTools = normalizeAgentRuntimeTools({
|
|
1305
|
+
runtimePlan: input.ignoreRuntimePlan ? void 0 : params.runtimePlan,
|
|
1306
|
+
tools: filteredTools,
|
|
1307
|
+
provider: params.provider,
|
|
1308
|
+
config: params.config,
|
|
1309
|
+
workspaceDir: input.effectiveWorkspace,
|
|
1310
|
+
env: process.env,
|
|
1311
|
+
modelId: params.modelId,
|
|
1312
|
+
modelApi: params.model.api,
|
|
1313
|
+
model: params.model,
|
|
1314
|
+
onPreNormalizationSchemaDiagnostics: (diagnostics) => preNormalizationDiagnostics.push(...diagnostics)
|
|
1315
|
+
});
|
|
1316
|
+
toolBuildStages.mark("runtime-normalization");
|
|
1317
|
+
input.onWebSearchPolicyResolved?.(normalizedTools.some((tool) => tool.name === "web_search"));
|
|
1318
|
+
const exposedTools = webSearchPlan.suppressManagedWebSearch ? normalizedTools.filter((tool) => tool.name !== "web_search") : normalizedTools;
|
|
1319
|
+
if (preNormalizationDiagnostics.length > 0) embeddedAgentLog.warn(`codex app-server quarantined ${preNormalizationDiagnostics.length} unsupported runtime tool schema${preNormalizationDiagnostics.length === 1 ? "" : "s"} before dynamic tool registration`, {
|
|
1320
|
+
runId: params.runId,
|
|
1321
|
+
sessionId: params.sessionId,
|
|
1322
|
+
diagnostics: preNormalizationDiagnostics.map((diagnostic) => ({
|
|
1323
|
+
index: diagnostic.toolIndex,
|
|
1324
|
+
tool: diagnostic.toolName,
|
|
1325
|
+
violations: diagnostic.violations.slice(0, 12),
|
|
1326
|
+
violationCount: diagnostic.violations.length
|
|
1327
|
+
}))
|
|
1328
|
+
});
|
|
1329
|
+
const summary = toolBuildStages.snapshot();
|
|
1330
|
+
if (shouldWarnCodexDynamicToolBuildStageSummary(summary)) {
|
|
1331
|
+
const phase = input.forceHeartbeatTool ? "registered-tools" : "runtime-tools";
|
|
1332
|
+
embeddedAgentLog.warn(`codex app-server dynamic tool build timings runId=${params.runId} sessionId=${params.sessionId} phase=${phase} totalMs=${summary.totalMs} stages=${formatCodexDynamicToolBuildStageSummary(summary)}`, {
|
|
1333
|
+
runId: params.runId,
|
|
1334
|
+
sessionId: params.sessionId,
|
|
1335
|
+
phase,
|
|
1336
|
+
totalMs: summary.totalMs,
|
|
1337
|
+
stages: summary.stages,
|
|
1338
|
+
allToolCount: readableAllTools.length,
|
|
1339
|
+
codexFilteredToolCount: codexFilteredTools.length,
|
|
1340
|
+
visionFilteredToolCount: visionFilteredTools.length,
|
|
1341
|
+
filteredToolCount: filteredTools.length,
|
|
1342
|
+
normalizedToolCount: exposedTools.length,
|
|
1343
|
+
forceHeartbeatTool: input.forceHeartbeatTool === true,
|
|
1344
|
+
ignoreRuntimePlan: input.ignoreRuntimePlan === true,
|
|
1345
|
+
nativeToolSurfaceEnabled: input.nativeToolSurfaceEnabled === true
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
return exposedTools;
|
|
1349
|
+
}
|
|
1350
|
+
/** Preserves delivery-critical tools when a narrow allowlist would otherwise hide them. */
|
|
1351
|
+
function includeForcedCodexDynamicToolAllow(toolsAllow, params) {
|
|
1352
|
+
if (toolsAllow === void 0 || hasWildcardCodexToolsAllow(toolsAllow)) return toolsAllow;
|
|
1353
|
+
const forcedToolNames = shouldForceMessageTool(params) ? ["message"] : [];
|
|
1354
|
+
if (forcedToolNames.length === 0) return toolsAllow;
|
|
1355
|
+
if (toolsAllow.length === 0) return forcedToolNames;
|
|
1356
|
+
const normalized = new Set(toolsAllow.map((name) => normalizeCodexDynamicToolName(name)));
|
|
1357
|
+
const missingToolNames = forcedToolNames.filter((toolName) => !normalized.has(normalizeCodexDynamicToolName(toolName)));
|
|
1358
|
+
return missingToolNames.length === 0 ? toolsAllow : [...toolsAllow, ...missingToolNames];
|
|
1359
|
+
}
|
|
1360
|
+
/** Decides whether Codex native code mode can own shell/file tools for this turn. */
|
|
1361
|
+
function shouldEnableCodexAppServerNativeToolSurface(params, sandbox, options = {}) {
|
|
1362
|
+
if (isCodexMemoryFlushRun(params)) return false;
|
|
1363
|
+
const toolsAllow = includeForcedCodexDynamicToolAllow(params.toolsAllow, params);
|
|
1364
|
+
if (toolsAllow === void 0) return canCodexAppServerNativeToolSurfaceHonorSandbox(sandbox, options);
|
|
1365
|
+
return hasWildcardCodexToolsAllow(toolsAllow) && canCodexAppServerNativeToolSurfaceHonorSandbox(sandbox, options);
|
|
1366
|
+
}
|
|
1367
|
+
/** Returns true when OpenClaw policy requires the Node-owned exec/process tools instead. */
|
|
1368
|
+
function isCodexNativeExecutionBlockedByNodeExecHost(params, options = {}) {
|
|
1369
|
+
return !resolveCodexNativeExecutionPolicy({
|
|
1370
|
+
config: params.config,
|
|
1371
|
+
sessionKey: resolveCodexRuntimePolicySessionKey(params, options.runtimeSessionKey),
|
|
1372
|
+
sessionId: params.sessionId,
|
|
1373
|
+
agentId: options.agentId,
|
|
1374
|
+
execOverrides: params.execOverrides,
|
|
1375
|
+
sandboxAvailable: options.sandbox?.enabled,
|
|
1376
|
+
readRuntimeSessionEntry: true
|
|
1377
|
+
}).nativeToolSurfaceAllowed;
|
|
1378
|
+
}
|
|
1379
|
+
function resolveCodexRuntimePolicySessionKey(params, runtimeSessionKey) {
|
|
1380
|
+
return runtimeSessionKey?.trim() || params.sandboxSessionKey?.trim() || params.sessionKey?.trim() || params.sessionId;
|
|
1381
|
+
}
|
|
1382
|
+
function canCodexAppServerNativeToolSurfaceHonorSandbox(sandbox, options = {}) {
|
|
1383
|
+
if (!sandbox?.enabled) return true;
|
|
1384
|
+
if (options.sandboxExecServerEnabled === true && sandbox.backend && canSandboxToolPolicyExposeCodexNativeToolSurface(sandbox)) return true;
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
function canSandboxToolPolicyExposeCodexNativeToolSurface(sandbox) {
|
|
1388
|
+
return CODEX_NATIVE_SANDBOX_TOOL_REQUIREMENTS.every((toolName) => isToolAllowed(sandbox.tools, toolName));
|
|
1389
|
+
}
|
|
1390
|
+
function isCodexMemoryFlushRun(params) {
|
|
1391
|
+
return params?.trigger === "memory" && Boolean(params.memoryFlushWritePath?.trim());
|
|
1392
|
+
}
|
|
1393
|
+
function filterCodexMemoryFlushDynamicTools(tools) {
|
|
1394
|
+
return tools.filter((tool) => CODEX_MEMORY_FLUSH_DYNAMIC_TOOL_ALLOW.has(normalizeCodexDynamicToolName(tool.name)));
|
|
1395
|
+
}
|
|
1396
|
+
/** Requires a Codex sandbox environment only when native tools must run inside OpenClaw sandboxing. */
|
|
1397
|
+
function shouldRequireCodexSandboxExecServerEnvironment(params) {
|
|
1398
|
+
return Boolean(params.sandbox?.enabled && params.nativeToolSurfaceEnabled && params.sandboxExecServerEnabled);
|
|
1399
|
+
}
|
|
1400
|
+
/** Selects the sandbox exec-server environment passed through the Codex app-server protocol. */
|
|
1401
|
+
function resolveCodexSandboxEnvironmentSelection(environment, nativeToolSurfaceEnabled) {
|
|
1402
|
+
return environment && nativeToolSurfaceEnabled ? [environment] : void 0;
|
|
1403
|
+
}
|
|
1404
|
+
/** Chooses the cwd visible to Codex native execution after sandbox exec-server setup. */
|
|
1405
|
+
function resolveCodexAppServerExecutionCwd(params) {
|
|
1406
|
+
return mapCodexAppServerRemoteWorkspacePath({
|
|
1407
|
+
value: params.environment && params.nativeToolSurfaceEnabled ? params.environment.cwd : params.effectiveCwd,
|
|
1408
|
+
localWorkspaceRoot: params.localWorkspaceRoot,
|
|
1409
|
+
remoteWorkspaceRoot: params.remoteWorkspaceRoot
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
/** Projects a local OpenClaw workspace cwd into the remote Codex app-server workspace root. */
|
|
1413
|
+
function mapCodexAppServerRemoteWorkspacePath(params) {
|
|
1414
|
+
if (!params.remoteWorkspaceRoot) return params.value;
|
|
1415
|
+
const localRoot = normalizeRemoteWorkspaceMatchPath(params.localWorkspaceRoot);
|
|
1416
|
+
const remoteRoot = normalizeRemoteWorkspaceMatchPath(params.remoteWorkspaceRoot);
|
|
1417
|
+
const normalizedValue = normalizeRemoteWorkspaceMatchPath(params.value);
|
|
1418
|
+
if (!localRoot || !remoteRoot) throw new Error("Codex remoteWorkspaceRoot requires non-empty workspace roots.");
|
|
1419
|
+
if (normalizedValue === localRoot) return remoteRoot;
|
|
1420
|
+
const prefix = `${localRoot}/`;
|
|
1421
|
+
if (!normalizedValue.startsWith(prefix)) throw new Error(`Codex remoteWorkspaceRoot is configured but cwd ${params.value} is outside OpenClaw workspace root ${params.localWorkspaceRoot}; refusing to send a gateway-local cwd to the remote Codex app-server.`);
|
|
1422
|
+
return joinRemoteWorkspacePath(remoteRoot, normalizedValue.slice(prefix.length));
|
|
1423
|
+
}
|
|
1424
|
+
function normalizeRemoteWorkspaceMatchPath(value) {
|
|
1425
|
+
return trimTrailingPathSeparator(value.replace(/\\/gu, "/"));
|
|
1426
|
+
}
|
|
1427
|
+
function trimTrailingPathSeparator(value) {
|
|
1428
|
+
return value.length > 1 ? value.replace(/[\\/]+$/u, "") : value;
|
|
1429
|
+
}
|
|
1430
|
+
function joinRemoteWorkspacePath(remoteRoot, suffix) {
|
|
1431
|
+
return remoteRoot === "/" ? `/${suffix}` : `${remoteRoot}/${suffix}`;
|
|
1432
|
+
}
|
|
1433
|
+
/** Converts OpenClaw sandbox networking into Codex's external-sandbox policy shape. */
|
|
1434
|
+
function resolveCodexExternalSandboxPolicyForOpenClawSandbox(sandbox) {
|
|
1435
|
+
return {
|
|
1436
|
+
type: "externalSandbox",
|
|
1437
|
+
networkAccess: codexNetworkAccessForOpenClawSandbox(sandbox) ? "enabled" : "restricted"
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
function codexNetworkAccessForOpenClawSandbox(sandbox) {
|
|
1441
|
+
if (sandbox?.backendId !== "docker") return true;
|
|
1442
|
+
const network = sandbox?.docker?.network?.trim().toLowerCase();
|
|
1443
|
+
return Boolean(network && network !== "none");
|
|
1444
|
+
}
|
|
1445
|
+
/** Returns a Codex config copy with app-server Codex plugin loading disabled for thread tools. */
|
|
1446
|
+
function disableCodexPluginThreadConfig(pluginConfig) {
|
|
1447
|
+
const config = readCodexPluginConfig(pluginConfig);
|
|
1448
|
+
return {
|
|
1449
|
+
...config,
|
|
1450
|
+
codexPlugins: {
|
|
1451
|
+
...config.codexPlugins,
|
|
1452
|
+
enabled: false
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
/** Adds sandbox_exec/process aliases when native Code Mode cannot directly honor the sandbox. */
|
|
1457
|
+
function addSandboxShellDynamicToolsIfAvailable(filteredTools, allTools, input) {
|
|
1458
|
+
if (!shouldExposeSandboxExecDynamicTool(input) || isSandboxShellDynamicToolExcluded(input.pluginConfig)) return filteredTools;
|
|
1459
|
+
const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
|
|
1460
|
+
const processTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "process");
|
|
1461
|
+
if (!execTool || !processTool) return filteredTools;
|
|
1462
|
+
const sandboxExecTool = {
|
|
1463
|
+
...execTool,
|
|
1464
|
+
name: "sandbox_exec",
|
|
1465
|
+
description: "Run a shell command through OpenClaw's configured sandbox backend for this session. Use when OpenClaw sandboxing is active or when a command must execute in the sandbox backend, such as an SSH-backed sandbox or Docker container-path bind layout. Use Codex's native shell only when no OpenClaw sandbox is active and native Code Mode is available.",
|
|
1466
|
+
execute: async (toolCallId, args, signal, onUpdate) => {
|
|
1467
|
+
const result = await execTool.execute(toolCallId, args, signal, onUpdate);
|
|
1468
|
+
return {
|
|
1469
|
+
...result,
|
|
1470
|
+
content: result.content.map((item) => item.type === "text" ? Object.assign({}, item, { text: item.text.replace("Use process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.", "Use sandbox_process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.") }) : item)
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
const sandboxProcessTool = {
|
|
1475
|
+
...processTool,
|
|
1476
|
+
name: "sandbox_process",
|
|
1477
|
+
description: "Manage sandbox_exec sessions that were started through OpenClaw's configured sandbox backend for this session: list, poll, log, write, send-keys, submit, paste, kill, clear, or remove. Use only for sandbox_exec follow-up; use Codex's native shell session handling only when no OpenClaw sandbox is active and native Code Mode is available."
|
|
1478
|
+
};
|
|
1479
|
+
return [
|
|
1480
|
+
...filteredTools,
|
|
1481
|
+
sandboxExecTool,
|
|
1482
|
+
sandboxProcessTool
|
|
1483
|
+
];
|
|
1484
|
+
}
|
|
1485
|
+
function shouldExposeSandboxExecDynamicTool(input) {
|
|
1486
|
+
if (isCodexMemoryFlushRun(input.params)) return false;
|
|
1487
|
+
if (isCodexNativeExecutionBlockedByNodeExecHost(input.params, {
|
|
1488
|
+
agentId: input.sessionAgentId,
|
|
1489
|
+
runtimeSessionKey: input.sandboxSessionKey,
|
|
1490
|
+
sandbox: input.sandbox
|
|
1491
|
+
})) return false;
|
|
1492
|
+
const backendId = input.sandbox?.enabled ? input.sandbox.backendId.trim().toLowerCase() : "";
|
|
1493
|
+
return Boolean(backendId && input.nativeToolSurfaceEnabled === false);
|
|
1494
|
+
}
|
|
1495
|
+
function isCodexDynamicToolExcluded(config, names) {
|
|
1496
|
+
const normalizedNames = new Set(names.map((name) => normalizeCodexDynamicToolName(name)));
|
|
1497
|
+
return (config.codexDynamicToolsExclude ?? []).some((name) => {
|
|
1498
|
+
const normalized = normalizeCodexDynamicToolName(name);
|
|
1499
|
+
return normalizedNames.has(normalized);
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
function isSandboxShellDynamicToolExcluded(config) {
|
|
1503
|
+
return isCodexDynamicToolExcluded(config, [
|
|
1504
|
+
"exec",
|
|
1505
|
+
"sandbox_exec",
|
|
1506
|
+
"process",
|
|
1507
|
+
"sandbox_process"
|
|
1508
|
+
]);
|
|
1509
|
+
}
|
|
1510
|
+
function addNodeShellDynamicToolsIfNeeded(filteredTools, allTools, input, nodePolicy) {
|
|
1511
|
+
if (isCodexMemoryFlushRun(input.params)) return filteredTools;
|
|
1512
|
+
if (nodePolicy.effectiveExecHost !== "node") return filteredTools;
|
|
1513
|
+
const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
|
|
1514
|
+
const processTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "process");
|
|
1515
|
+
if (!execTool || !processTool) return filteredTools;
|
|
1516
|
+
const toolsToAppend = [];
|
|
1517
|
+
if (!isCodexDynamicToolExcluded(input.pluginConfig, ["exec", CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME]) && !filteredTools.some((tool) => normalizeCodexDynamicToolName(tool.name) === CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME)) toolsToAppend.push(createNodeExecDynamicTool(execTool, nodePolicy.node));
|
|
1518
|
+
if (!isCodexDynamicToolExcluded(input.pluginConfig, ["process", CODEX_NODE_PROCESS_DYNAMIC_TOOL_NAME]) && !filteredTools.some((tool) => normalizeCodexDynamicToolName(tool.name) === CODEX_NODE_PROCESS_DYNAMIC_TOOL_NAME)) toolsToAppend.push(createNodeProcessDynamicTool(processTool));
|
|
1519
|
+
return toolsToAppend.length > 0 ? [...filteredTools, ...toolsToAppend] : filteredTools;
|
|
1520
|
+
}
|
|
1521
|
+
function createNodeExecDynamicTool(execTool, configuredNode) {
|
|
1522
|
+
return {
|
|
1523
|
+
...execTool,
|
|
1524
|
+
name: CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME,
|
|
1525
|
+
description: "Run a shell command 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. Use node_process for follow-up on backgrounded node_exec sessions. Use Codex's native shell for local app-server work.",
|
|
1526
|
+
parameters: hideNodeExecDynamicToolParameters(execTool.parameters),
|
|
1527
|
+
execute: async (toolCallId, args, signal, onUpdate) => {
|
|
1528
|
+
const result = await execTool.execute(toolCallId, pinNodeExecDynamicToolArgs(args, configuredNode), signal, onUpdate);
|
|
1529
|
+
return {
|
|
1530
|
+
...result,
|
|
1531
|
+
content: result.content.map((item) => item.type === "text" ? Object.assign({}, item, { text: item.text.replace("Use process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.", "Use node_process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.") }) : item)
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
function createNodeProcessDynamicTool(processTool) {
|
|
1537
|
+
return {
|
|
1538
|
+
...processTool,
|
|
1539
|
+
name: CODEX_NODE_PROCESS_DYNAMIC_TOOL_NAME,
|
|
1540
|
+
description: "Manage node_exec sessions that were started on the OpenClaw configured remote node for this session: list, poll, log, write, send-keys, submit, paste, kill, clear, or remove. Use only for node_exec follow-up; use Codex's native shell session handling for local app-server work."
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
function pinNodeExecDynamicToolArgs(args, configuredNode) {
|
|
1544
|
+
const { host: _host, security: _security, ask: _ask, node: _node, ...rest } = args && typeof args === "object" && !Array.isArray(args) ? args : {};
|
|
1545
|
+
const node = configuredNode?.trim();
|
|
1546
|
+
return {
|
|
1547
|
+
...rest,
|
|
1548
|
+
host: "node",
|
|
1549
|
+
...node ? { node } : {}
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
function hideNodeExecDynamicToolParameters(parameters) {
|
|
1553
|
+
if (!parameters || typeof parameters !== "object" || Array.isArray(parameters)) return parameters;
|
|
1554
|
+
const schema = parameters;
|
|
1555
|
+
const rawProperties = schema.properties;
|
|
1556
|
+
if (!rawProperties || typeof rawProperties !== "object" || Array.isArray(rawProperties)) return parameters;
|
|
1557
|
+
const nextProperties = Object.fromEntries(Object.entries(rawProperties).filter(([name]) => !CODEX_NODE_EXEC_HIDDEN_PARAMETER_NAMES.has(normalizeCodexDynamicToolName(name))));
|
|
1558
|
+
const rawRequired = schema.required;
|
|
1559
|
+
const nextRequired = Array.isArray(rawRequired) ? rawRequired.filter((name) => typeof name !== "string" || !CODEX_NODE_EXEC_HIDDEN_PARAMETER_NAMES.has(normalizeCodexDynamicToolName(name))) : rawRequired;
|
|
1560
|
+
return {
|
|
1561
|
+
...schema,
|
|
1562
|
+
properties: nextProperties,
|
|
1563
|
+
...Array.isArray(rawRequired) ? { required: nextRequired } : {}
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
function resolveCodexNativeExecutionPolicyForDynamicTools(input) {
|
|
1567
|
+
return resolveCodexNativeExecutionPolicy({
|
|
1568
|
+
config: input.params.config,
|
|
1569
|
+
sessionKey: resolveCodexRuntimePolicySessionKey(input.params, input.sandboxSessionKey),
|
|
1570
|
+
sessionId: input.params.sessionId,
|
|
1571
|
+
agentId: input.sessionAgentId,
|
|
1572
|
+
execOverrides: input.params.execOverrides,
|
|
1573
|
+
sandboxAvailable: input.sandbox?.enabled,
|
|
1574
|
+
readRuntimeSessionEntry: true
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
function resolveNodeExecToolOverrides(policy) {
|
|
1578
|
+
if (policy.effectiveExecHost !== "node") return;
|
|
1579
|
+
const node = policy.node?.trim();
|
|
1580
|
+
return {
|
|
1581
|
+
host: "node",
|
|
1582
|
+
...node ? { node } : {}
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
/** Applies a normalized tool allowlist while preserving shell aliases for exec/process. */
|
|
1586
|
+
function filterCodexDynamicToolsForAllowlist(tools, toolsAllow) {
|
|
1587
|
+
if (!toolsAllow) return tools;
|
|
1588
|
+
if (toolsAllow.length === 0) return [];
|
|
1589
|
+
if (hasWildcardCodexToolsAllow(toolsAllow)) return tools;
|
|
1590
|
+
const allowSet = new Set(toolsAllow.map((name) => normalizeCodexDynamicToolName(name)).filter(Boolean));
|
|
1591
|
+
return tools.filter((tool) => {
|
|
1592
|
+
const normalized = normalizeCodexDynamicToolName(tool.name);
|
|
1593
|
+
return allowSet.has(normalized) || normalized === "sandbox_exec" && allowSet.has("exec") || normalized === "sandbox_process" && (allowSet.has("exec") || allowSet.has("process")) || normalized === CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME && allowSet.has("exec") || normalized === CODEX_NODE_PROCESS_DYNAMIC_TOOL_NAME && (allowSet.has("exec") || allowSet.has("process"));
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
/** Detects the wildcard allowlist marker after Codex tool-name normalization. */
|
|
1597
|
+
function hasWildcardCodexToolsAllow(toolsAllow) {
|
|
1598
|
+
return toolsAllow.some((name) => normalizeCodexDynamicToolName(name) === "*");
|
|
1599
|
+
}
|
|
1600
|
+
/** Forces message delivery through the message tool when the source channel requires it. */
|
|
1601
|
+
function shouldForceMessageTool(params) {
|
|
1602
|
+
return params.disableMessageTool !== true && params.sourceReplyDeliveryMode === "message_tool_only";
|
|
1603
|
+
}
|
|
1604
|
+
//#endregion
|
|
1071
1605
|
//#region extensions/codex/src/app-server/dynamic-tool-diagnostics.ts
|
|
1072
1606
|
/**
|
|
1073
1607
|
* Trusted diagnostics emitted around Codex dynamic tool execution lifecycle.
|
|
@@ -1128,6 +1662,15 @@ function emitDynamicToolTerminalDiagnostic(params) {
|
|
|
1128
1662
|
}
|
|
1129
1663
|
//#endregion
|
|
1130
1664
|
//#region extensions/codex/src/app-server/dynamic-tools.ts
|
|
1665
|
+
function applyCurrentMessageProvider(toolName, args, currentProvider) {
|
|
1666
|
+
const hasProvider = typeof args.provider === "string" && args.provider.trim().length > 0 ? true : typeof args.channel === "string" && args.channel.trim().length > 0;
|
|
1667
|
+
const provider = currentProvider?.trim();
|
|
1668
|
+
if (toolName !== "message" || hasProvider || !provider) return args;
|
|
1669
|
+
return {
|
|
1670
|
+
...args,
|
|
1671
|
+
provider
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1131
1674
|
/** Namespace attached to OpenClaw-owned dynamic tools exposed to Codex. */
|
|
1132
1675
|
const CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE = "openclaw";
|
|
1133
1676
|
const ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES = new Set(["sessions_yield"]);
|
|
@@ -1168,24 +1711,38 @@ function createCodexDynamicToolBridge(params) {
|
|
|
1168
1711
|
runtime: "codex",
|
|
1169
1712
|
...toolResultHookContext
|
|
1170
1713
|
});
|
|
1714
|
+
const isReplaySafeToolInstance = (tool) => {
|
|
1715
|
+
const pluginMeta = getPluginToolMeta(tool);
|
|
1716
|
+
if (pluginMeta) return pluginMeta.replaySafe === true;
|
|
1717
|
+
return getChannelAgentToolMeta(tool) === void 0;
|
|
1718
|
+
};
|
|
1171
1719
|
const legacyExtensionRunner = createCodexAppServerToolResultExtensionRunner(toolResultHookContext);
|
|
1172
1720
|
const directToolNames = new Set([...ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES, ...params.directToolNames ?? []]);
|
|
1173
1721
|
return {
|
|
1174
|
-
availableSpecs:
|
|
1175
|
-
|
|
1722
|
+
availableSpecs: createCodexDynamicToolSpecs({
|
|
1723
|
+
entries: availableTools,
|
|
1176
1724
|
loading: params.loading ?? "searchable",
|
|
1177
1725
|
directToolNames
|
|
1178
|
-
})
|
|
1179
|
-
specs:
|
|
1180
|
-
|
|
1726
|
+
}),
|
|
1727
|
+
specs: createCodexDynamicToolSpecs({
|
|
1728
|
+
entries: registeredSpecTools,
|
|
1181
1729
|
loading: params.loading ?? "searchable",
|
|
1182
1730
|
directToolNames
|
|
1183
|
-
})
|
|
1731
|
+
}),
|
|
1184
1732
|
telemetry,
|
|
1185
1733
|
handleToolCall: async (call, options) => {
|
|
1186
1734
|
const toolEntry = toolMap.get(call.tool);
|
|
1187
1735
|
if (!toolEntry) {
|
|
1188
1736
|
const message = registeredToolNames.has(call.tool) ? `OpenClaw tool is not available for this turn: ${call.tool}` : `Unknown OpenClaw tool: ${call.tool}`;
|
|
1737
|
+
finalizeToolTerminalPresentation({
|
|
1738
|
+
toolCallId: call.callId,
|
|
1739
|
+
runId: toolResultHookContext.runId,
|
|
1740
|
+
result: failedToolResult(message),
|
|
1741
|
+
isError: true,
|
|
1742
|
+
observer: params.hookContext?.onToolOutcome,
|
|
1743
|
+
toolName: call.tool,
|
|
1744
|
+
toolCallOrdinal: options?.toolCallOrdinal
|
|
1745
|
+
});
|
|
1189
1746
|
notifyAgentToolResult(options?.onAgentToolResult, call.tool, failedToolResult(message), true);
|
|
1190
1747
|
if (registeredToolNames.has(call.tool)) return {
|
|
1191
1748
|
contentItems: [{
|
|
@@ -1207,17 +1764,33 @@ function createCodexDynamicToolBridge(params) {
|
|
|
1207
1764
|
const startedAt = Date.now();
|
|
1208
1765
|
const signal = composeAbortSignals(params.signal, options?.signal);
|
|
1209
1766
|
let didStartExecution = false;
|
|
1767
|
+
let executionPrevented = false;
|
|
1768
|
+
let executedArgs = structuredClone(args);
|
|
1210
1769
|
try {
|
|
1211
1770
|
const preparedArgs = tool.prepareArguments ? tool.prepareArguments(args) : args;
|
|
1771
|
+
const telemetryArgs = isRecord(preparedArgs) ? preparedArgs : args;
|
|
1772
|
+
executedArgs = structuredClone(telemetryArgs);
|
|
1773
|
+
const messagingContext = {
|
|
1774
|
+
config: params.hookContext?.config,
|
|
1775
|
+
currentChannelId: params.hookContext?.currentChannelId,
|
|
1776
|
+
currentMessagingTarget: params.hookContext?.currentMessagingTarget,
|
|
1777
|
+
currentThreadId: params.hookContext?.currentThreadId,
|
|
1778
|
+
replyToMode: params.hookContext?.replyToMode,
|
|
1779
|
+
hasRepliedRef: params.hookContext?.hasRepliedRef ? { value: params.hookContext.hasRepliedRef.value } : void 0
|
|
1780
|
+
};
|
|
1212
1781
|
didStartExecution = true;
|
|
1213
1782
|
const rawResult = await tool.execute(call.callId, preparedArgs, signal);
|
|
1783
|
+
const adjustedExecutedArgs = consumeAdjustedParamsForToolCall(call.callId, toolResultHookContext.runId);
|
|
1784
|
+
if (isRecord(adjustedExecutedArgs)) executedArgs = structuredClone(adjustedExecutedArgs);
|
|
1785
|
+
executionPrevented = consumePreExecutionBlockedToolCall(call.callId, toolResultHookContext.runId);
|
|
1786
|
+
const telemetryRawResult = sanitizeToolResult(rawResult);
|
|
1214
1787
|
const rawIsError = isCodexToolResultError(rawResult);
|
|
1215
1788
|
const middlewareResult = await middlewareRunner.applyToolResultMiddleware({
|
|
1216
1789
|
threadId: call.threadId,
|
|
1217
1790
|
turnId: call.turnId,
|
|
1218
1791
|
toolCallId: call.callId,
|
|
1219
1792
|
toolName,
|
|
1220
|
-
args,
|
|
1793
|
+
args: structuredClone(executedArgs),
|
|
1221
1794
|
isError: rawIsError,
|
|
1222
1795
|
result: rawResult
|
|
1223
1796
|
});
|
|
@@ -1226,19 +1799,11 @@ function createCodexDynamicToolBridge(params) {
|
|
|
1226
1799
|
turnId: call.turnId,
|
|
1227
1800
|
toolCallId: call.callId,
|
|
1228
1801
|
toolName,
|
|
1229
|
-
args,
|
|
1802
|
+
args: structuredClone(executedArgs),
|
|
1230
1803
|
result: middlewareResult
|
|
1231
1804
|
});
|
|
1232
1805
|
const resultIsError = rawIsError || isCodexToolResultError(result);
|
|
1233
1806
|
notifyAgentToolResult(options?.onAgentToolResult, toolName, result, resultIsError);
|
|
1234
|
-
collectToolTelemetry({
|
|
1235
|
-
toolName,
|
|
1236
|
-
args,
|
|
1237
|
-
result,
|
|
1238
|
-
mediaTrustResult: rawResult,
|
|
1239
|
-
telemetry,
|
|
1240
|
-
isError: resultIsError
|
|
1241
|
-
});
|
|
1242
1807
|
runAgentHarnessAfterToolCallHook({
|
|
1243
1808
|
toolName,
|
|
1244
1809
|
toolCallId: call.callId,
|
|
@@ -1247,24 +1812,59 @@ function createCodexDynamicToolBridge(params) {
|
|
|
1247
1812
|
sessionId: toolResultHookContext.sessionId,
|
|
1248
1813
|
sessionKey: toolResultHookContext.sessionKey,
|
|
1249
1814
|
channelId: toolResultHookContext.channelId,
|
|
1250
|
-
startArgs:
|
|
1815
|
+
startArgs: executedArgs,
|
|
1251
1816
|
result,
|
|
1252
1817
|
startedAt
|
|
1253
1818
|
});
|
|
1819
|
+
finalizeToolTerminalPresentation({
|
|
1820
|
+
toolCallId: call.callId,
|
|
1821
|
+
runId: toolResultHookContext.runId,
|
|
1822
|
+
result,
|
|
1823
|
+
isError: resultIsError,
|
|
1824
|
+
observer: params.hookContext?.onToolOutcome,
|
|
1825
|
+
toolName,
|
|
1826
|
+
toolCallOrdinal: options?.toolCallOrdinal
|
|
1827
|
+
});
|
|
1828
|
+
const messagingTelemetryArgs = applyCurrentMessageProvider(toolName, executedArgs, params.hookContext?.currentChannelProvider);
|
|
1829
|
+
const messagingTarget = isMessagingTool(toolName) && isMessagingToolSendAction(toolName, executedArgs) ? extractMessagingToolSend(toolName, messagingTelemetryArgs, messagingContext) : void 0;
|
|
1830
|
+
const confirmedMessagingTarget = !rawIsError && messagingTarget ? extractMessagingToolSendResult(messagingTarget, telemetryRawResult) : messagingTarget;
|
|
1831
|
+
collectToolTelemetry({
|
|
1832
|
+
toolName,
|
|
1833
|
+
args: executedArgs,
|
|
1834
|
+
result,
|
|
1835
|
+
mediaTrustResult: telemetryRawResult,
|
|
1836
|
+
telemetry,
|
|
1837
|
+
isError: resultIsError,
|
|
1838
|
+
messagingTarget: confirmedMessagingTarget
|
|
1839
|
+
});
|
|
1254
1840
|
const terminalType = inferToolResultDiagnosticTerminalType(result, resultIsError);
|
|
1255
1841
|
const response = withDiagnosticTerminalType({
|
|
1256
1842
|
contentItems: convertToolContents(result.content, toolResultMaxChars),
|
|
1257
1843
|
success: !resultIsError
|
|
1258
1844
|
}, terminalType);
|
|
1259
1845
|
withDynamicToolTermination(response, rawResult.terminate === true || result.terminate === true || isToolResultYield(rawResult) || isToolResultYield(result));
|
|
1260
|
-
|
|
1261
|
-
|
|
1846
|
+
const asyncStarted = isAsyncStartedToolResult(rawResult) || isAsyncStartedToolResult(result);
|
|
1847
|
+
withDynamicToolAsyncStarted(response, asyncStarted);
|
|
1848
|
+
return withSideEffectEvidence(response, !(executionPrevented || !asyncStarted && isReplaySafeToolInstance(toolEntry.tool) && isReplaySafeToolCall(toolName, executedArgs)));
|
|
1262
1849
|
} catch (error) {
|
|
1263
1850
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1264
|
-
|
|
1851
|
+
const adjustedExecutedArgs = consumeAdjustedParamsForToolCall(call.callId, toolResultHookContext.runId);
|
|
1852
|
+
if (isRecord(adjustedExecutedArgs)) executedArgs = structuredClone(adjustedExecutedArgs);
|
|
1853
|
+
executionPrevented = executionPrevented || consumePreExecutionBlockedToolCall(call.callId, toolResultHookContext.runId);
|
|
1854
|
+
const failedResult = failedToolResult(errorMessage);
|
|
1855
|
+
finalizeToolTerminalPresentation({
|
|
1856
|
+
toolCallId: call.callId,
|
|
1857
|
+
runId: toolResultHookContext.runId,
|
|
1858
|
+
result: failedResult,
|
|
1859
|
+
isError: true,
|
|
1860
|
+
observer: params.hookContext?.onToolOutcome,
|
|
1861
|
+
toolName,
|
|
1862
|
+
toolCallOrdinal: options?.toolCallOrdinal
|
|
1863
|
+
});
|
|
1864
|
+
notifyAgentToolResult(options?.onAgentToolResult, toolName, failedResult, true);
|
|
1265
1865
|
collectToolTelemetry({
|
|
1266
1866
|
toolName,
|
|
1267
|
-
args,
|
|
1867
|
+
args: executedArgs,
|
|
1268
1868
|
result: void 0,
|
|
1269
1869
|
telemetry,
|
|
1270
1870
|
isError: true
|
|
@@ -1277,17 +1877,18 @@ function createCodexDynamicToolBridge(params) {
|
|
|
1277
1877
|
sessionId: toolResultHookContext.sessionId,
|
|
1278
1878
|
sessionKey: toolResultHookContext.sessionKey,
|
|
1279
1879
|
channelId: toolResultHookContext.channelId,
|
|
1280
|
-
startArgs:
|
|
1880
|
+
startArgs: executedArgs,
|
|
1281
1881
|
error: errorMessage,
|
|
1282
1882
|
startedAt
|
|
1283
1883
|
});
|
|
1884
|
+
const replaySafe = !didStartExecution || executionPrevented || isReplaySafeToolInstance(toolEntry.tool) && isReplaySafeToolCall(toolName, executedArgs);
|
|
1284
1885
|
return withSideEffectEvidence(withDiagnosticTerminalType({
|
|
1285
1886
|
contentItems: [{
|
|
1286
1887
|
type: "inputText",
|
|
1287
1888
|
text: errorMessage
|
|
1288
1889
|
}],
|
|
1289
1890
|
success: false
|
|
1290
|
-
}, "error"), didStartExecution);
|
|
1891
|
+
}, "error"), didStartExecution && !replaySafe);
|
|
1291
1892
|
}
|
|
1292
1893
|
}
|
|
1293
1894
|
};
|
|
@@ -1339,18 +1940,35 @@ function wrapProjectedCodexDynamicTools(tools, hookContext) {
|
|
|
1339
1940
|
quarantinedTools
|
|
1340
1941
|
};
|
|
1341
1942
|
}
|
|
1342
|
-
function
|
|
1343
|
-
const
|
|
1943
|
+
function createCodexDynamicToolSpecs(params) {
|
|
1944
|
+
const specs = [];
|
|
1945
|
+
const namespaceTools = [];
|
|
1946
|
+
for (const entry of params.entries) {
|
|
1947
|
+
const functionSpec = createCodexDynamicToolFunctionSpec({ entry });
|
|
1948
|
+
if (params.loading === "direct" || params.directToolNames.has(entry.name)) {
|
|
1949
|
+
specs.push(functionSpec);
|
|
1950
|
+
continue;
|
|
1951
|
+
}
|
|
1952
|
+
namespaceTools.push({
|
|
1953
|
+
...functionSpec,
|
|
1954
|
+
deferLoading: true
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
if (namespaceTools.length > 0) specs.push({
|
|
1958
|
+
type: "namespace",
|
|
1959
|
+
name: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
|
|
1960
|
+
description: "",
|
|
1961
|
+
tools: namespaceTools
|
|
1962
|
+
});
|
|
1963
|
+
return specs;
|
|
1964
|
+
}
|
|
1965
|
+
function createCodexDynamicToolFunctionSpec(params) {
|
|
1966
|
+
return {
|
|
1967
|
+
type: "function",
|
|
1344
1968
|
name: params.entry.name,
|
|
1345
1969
|
description: params.entry.description,
|
|
1346
1970
|
inputSchema: params.entry.inputSchema
|
|
1347
1971
|
};
|
|
1348
|
-
if (params.loading === "direct" || params.directToolNames.has(params.entry.name)) return base;
|
|
1349
|
-
return {
|
|
1350
|
-
...base,
|
|
1351
|
-
namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
|
|
1352
|
-
deferLoading: true
|
|
1353
|
-
};
|
|
1354
1972
|
}
|
|
1355
1973
|
function projectCodexDynamicTools(tools) {
|
|
1356
1974
|
const projectedTools = [];
|
|
@@ -1553,19 +2171,21 @@ function collectToolTelemetry(params) {
|
|
|
1553
2171
|
const mediaUrls = collectMediaUrls(params.args);
|
|
1554
2172
|
params.telemetry.messagingToolSentMediaUrls.push(...mediaUrls);
|
|
1555
2173
|
params.telemetry.messagingToolSentTargets.push({
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
2174
|
+
...params.messagingTarget ?? {
|
|
2175
|
+
tool: params.toolName,
|
|
2176
|
+
provider: readFirstString$1(params.args, ["provider", "channel"]) ?? params.toolName,
|
|
2177
|
+
accountId: readFirstString$1(params.args, ["accountId", "account_id"]),
|
|
2178
|
+
to: readFirstString$1(params.args, [
|
|
2179
|
+
"to",
|
|
2180
|
+
"target",
|
|
2181
|
+
"recipient"
|
|
2182
|
+
]),
|
|
2183
|
+
threadId: readFirstString$1(params.args, [
|
|
2184
|
+
"threadId",
|
|
2185
|
+
"thread_id",
|
|
2186
|
+
"messageThreadId"
|
|
2187
|
+
])
|
|
2188
|
+
},
|
|
1569
2189
|
...text ? { text } : {},
|
|
1570
2190
|
...mediaUrls.length > 0 ? { mediaUrls } : {}
|
|
1571
2191
|
});
|
|
@@ -1845,17 +2465,22 @@ async function handleCodexAppServerElicitationRequest(params) {
|
|
|
1845
2465
|
logPluginElicitationDecline("missing_active_turn", requestParams);
|
|
1846
2466
|
return declineElicitationResponse();
|
|
1847
2467
|
}
|
|
1848
|
-
return buildPluginPolicyElicitationResponse(
|
|
2468
|
+
return await buildPluginPolicyElicitationResponse({
|
|
2469
|
+
entry: pluginResolution.entry,
|
|
2470
|
+
requestParams,
|
|
2471
|
+
paramsForRun: params.paramsForRun,
|
|
2472
|
+
signal: params.signal
|
|
2473
|
+
});
|
|
1849
2474
|
}
|
|
1850
2475
|
const approvalPrompt = readComputerUseApprovalElicitation(requestParams, params.computerUseMcpServerName) ?? readBridgeableApprovalElicitation(requestParams);
|
|
1851
2476
|
if (!approvalPrompt) return;
|
|
1852
|
-
|
|
2477
|
+
return buildElicitationResponse(approvalPrompt, await requestPluginApprovalOutcome({
|
|
1853
2478
|
paramsForRun: params.paramsForRun,
|
|
1854
2479
|
title: approvalPrompt.title,
|
|
1855
2480
|
description: approvalPrompt.description,
|
|
2481
|
+
allowedDecisions: approvalPrompt.allowedDecisions,
|
|
1856
2482
|
signal: params.signal
|
|
1857
|
-
});
|
|
1858
|
-
return buildElicitationResponse(approvalPrompt.requestedSchema, approvalPrompt.meta, outcome);
|
|
2483
|
+
}));
|
|
1859
2484
|
}
|
|
1860
2485
|
function matchesCurrentThread(requestParams, threadId) {
|
|
1861
2486
|
if (!requestParams) return false;
|
|
@@ -1953,21 +2578,77 @@ function hasDisplayNameOnlyPluginMatch(meta, entries) {
|
|
|
1953
2578
|
function normalizePluginIdentityText(value) {
|
|
1954
2579
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
1955
2580
|
}
|
|
1956
|
-
function buildPluginPolicyElicitationResponse(
|
|
1957
|
-
|
|
1958
|
-
|
|
2581
|
+
async function buildPluginPolicyElicitationResponse(params) {
|
|
2582
|
+
const mode = resolvePluginDestructiveApprovalMode(params.entry);
|
|
2583
|
+
if (mode === "deny") {
|
|
2584
|
+
logPluginElicitationDecline("destructive_actions_disabled", params.requestParams);
|
|
1959
2585
|
return declineElicitationResponse();
|
|
1960
2586
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
2587
|
+
const approvalPrompt = readPluginApprovalElicitation(params.entry, params.requestParams);
|
|
2588
|
+
if (!approvalPrompt) {
|
|
2589
|
+
logPluginElicitationDecline("unsupported_schema", params.requestParams);
|
|
1963
2590
|
return declineElicitationResponse();
|
|
1964
2591
|
}
|
|
1965
|
-
const
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2592
|
+
const response = buildElicitationResponse(approvalPrompt, "approved-once");
|
|
2593
|
+
if (isJsonObject(response) && response.action === "accept") {
|
|
2594
|
+
if (mode === "allow") return response;
|
|
2595
|
+
return buildElicitationResponse(approvalPrompt, await requestPluginApprovalOutcome({
|
|
2596
|
+
paramsForRun: params.paramsForRun,
|
|
2597
|
+
title: approvalPrompt.title,
|
|
2598
|
+
description: approvalPrompt.description,
|
|
2599
|
+
allowedDecisions: approvalPrompt.allowedDecisions,
|
|
2600
|
+
signal: params.signal
|
|
2601
|
+
}));
|
|
2602
|
+
}
|
|
2603
|
+
logPluginElicitationDecline("unmappable_schema", params.requestParams);
|
|
1969
2604
|
return declineElicitationResponse();
|
|
1970
2605
|
}
|
|
2606
|
+
function resolvePluginDestructiveApprovalMode(entry) {
|
|
2607
|
+
return entry.destructiveApprovalMode ?? (entry.allowDestructiveActions ? "allow" : "deny");
|
|
2608
|
+
}
|
|
2609
|
+
function readPluginApprovalElicitation(entry, requestParams) {
|
|
2610
|
+
if (readString(requestParams, "mode") !== "form" || !isJsonObject(requestParams.requestedSchema)) return;
|
|
2611
|
+
const requestedSchema = requestParams.requestedSchema;
|
|
2612
|
+
if (readString(requestedSchema, "type") !== "object" || !isJsonObject(requestedSchema.properties)) return;
|
|
2613
|
+
const meta = isJsonObject(requestParams["_meta"]) ? requestParams["_meta"] : {};
|
|
2614
|
+
const title = sanitizeDisplayText(readString(requestParams, "message") ?? "") || "Codex plugin approval";
|
|
2615
|
+
const descriptionMeta = { ...meta };
|
|
2616
|
+
if (!readString(descriptionMeta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY)) descriptionMeta[MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY] = entry.pluginName;
|
|
2617
|
+
return {
|
|
2618
|
+
title,
|
|
2619
|
+
description: buildApprovalDescription({
|
|
2620
|
+
title,
|
|
2621
|
+
meta: descriptionMeta,
|
|
2622
|
+
requestedSchema,
|
|
2623
|
+
serverName: sanitizeOptionalDisplayText(readString(requestParams, "serverName"))
|
|
2624
|
+
}),
|
|
2625
|
+
requestedSchema,
|
|
2626
|
+
meta,
|
|
2627
|
+
persistHintsMode: "explicit",
|
|
2628
|
+
allowedDecisions: buildApprovalAllowedDecisions(requestedSchema, meta)
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
function buildApprovalAllowedDecisions(requestedSchema, meta) {
|
|
2632
|
+
return canMapPersistentApproval(requestedSchema, meta) ? [
|
|
2633
|
+
"allow-once",
|
|
2634
|
+
"allow-always",
|
|
2635
|
+
"deny"
|
|
2636
|
+
] : ["allow-once", "deny"];
|
|
2637
|
+
}
|
|
2638
|
+
function canMapPersistentApproval(requestedSchema, meta) {
|
|
2639
|
+
const persistHints = readPersistHints(meta, "explicit");
|
|
2640
|
+
if (persistHints.length > 0) return persistHints.includes("always");
|
|
2641
|
+
const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
|
|
2642
|
+
return Object.entries(properties).some(([name, value]) => {
|
|
2643
|
+
const schema = isJsonObject(value) ? value : void 0;
|
|
2644
|
+
if (!schema) return false;
|
|
2645
|
+
return isPersistField({
|
|
2646
|
+
name,
|
|
2647
|
+
schema,
|
|
2648
|
+
required: false
|
|
2649
|
+
}) && chooseAlwaysPersistOptionValue(readEnumOptions(schema)) !== void 0;
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
1971
2652
|
function declineElicitationResponse() {
|
|
1972
2653
|
return {
|
|
1973
2654
|
action: "decline",
|
|
@@ -2115,7 +2796,8 @@ async function requestPluginApprovalOutcome(params) {
|
|
|
2115
2796
|
title: params.title,
|
|
2116
2797
|
description: params.description,
|
|
2117
2798
|
severity: "warning",
|
|
2118
|
-
toolName: "codex_mcp_tool_approval"
|
|
2799
|
+
toolName: "codex_mcp_tool_approval",
|
|
2800
|
+
allowedDecisions: params.allowedDecisions
|
|
2119
2801
|
});
|
|
2120
2802
|
const approvalId = requestResult?.id;
|
|
2121
2803
|
if (!approvalId) return "unavailable";
|
|
@@ -2127,7 +2809,8 @@ async function requestPluginApprovalOutcome(params) {
|
|
|
2127
2809
|
return params.signal?.aborted ? "cancelled" : "denied";
|
|
2128
2810
|
}
|
|
2129
2811
|
}
|
|
2130
|
-
function buildElicitationResponse(
|
|
2812
|
+
function buildElicitationResponse(approvalPrompt, outcome) {
|
|
2813
|
+
const { requestedSchema, meta } = approvalPrompt;
|
|
2131
2814
|
if (outcome === "cancelled") return {
|
|
2132
2815
|
action: "cancel",
|
|
2133
2816
|
content: null,
|
|
@@ -2138,12 +2821,12 @@ function buildElicitationResponse(requestedSchema, meta, outcome) {
|
|
|
2138
2821
|
content: null,
|
|
2139
2822
|
_meta: null
|
|
2140
2823
|
};
|
|
2141
|
-
const content = buildAcceptedContent(
|
|
2824
|
+
const content = buildAcceptedContent(approvalPrompt, outcome);
|
|
2142
2825
|
if (!content) {
|
|
2143
2826
|
if (hasNoSchemaProperties(requestedSchema)) return {
|
|
2144
2827
|
action: "accept",
|
|
2145
2828
|
content: null,
|
|
2146
|
-
_meta: buildAcceptedMeta(meta, outcome)
|
|
2829
|
+
_meta: buildAcceptedMeta(meta, outcome, approvalPrompt.persistHintsMode ?? "legacy")
|
|
2147
2830
|
};
|
|
2148
2831
|
embeddedAgentLog.warn("codex MCP approval elicitation approved without a mappable response", {
|
|
2149
2832
|
approvalKind: meta[MCP_TOOL_APPROVAL_KIND_KEY],
|
|
@@ -2159,10 +2842,11 @@ function buildElicitationResponse(requestedSchema, meta, outcome) {
|
|
|
2159
2842
|
return {
|
|
2160
2843
|
action: "accept",
|
|
2161
2844
|
content,
|
|
2162
|
-
_meta: buildAcceptedMeta(meta, outcome)
|
|
2845
|
+
_meta: buildAcceptedMeta(meta, outcome, approvalPrompt.persistHintsMode ?? "legacy")
|
|
2163
2846
|
};
|
|
2164
2847
|
}
|
|
2165
|
-
function buildAcceptedContent(
|
|
2848
|
+
function buildAcceptedContent(approvalPrompt, outcome) {
|
|
2849
|
+
const { requestedSchema, meta } = approvalPrompt;
|
|
2166
2850
|
const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : void 0;
|
|
2167
2851
|
if (!properties) return;
|
|
2168
2852
|
const required = Array.isArray(requestedSchema.required) ? new Set(requestedSchema.required.filter((entry) => typeof entry === "string")) : /* @__PURE__ */ new Set();
|
|
@@ -2176,7 +2860,7 @@ function buildAcceptedContent(requestedSchema, meta, outcome) {
|
|
|
2176
2860
|
schema,
|
|
2177
2861
|
required: required.has(name)
|
|
2178
2862
|
};
|
|
2179
|
-
const next = readApprovalFieldValue(property, outcome) ?? readPersistFieldValue(property, meta, outcome) ?? readFallbackFieldValue(property, outcome);
|
|
2863
|
+
const next = readApprovalFieldValue(property, outcome) ?? readPersistFieldValue(property, meta, outcome, approvalPrompt.persistHintsMode ?? "legacy") ?? readFallbackFieldValue(property, outcome);
|
|
2180
2864
|
if (next === void 0) {
|
|
2181
2865
|
if (isApprovalField(property)) sawApprovalField = true;
|
|
2182
2866
|
if (property.required) return;
|
|
@@ -2197,13 +2881,14 @@ function readApprovalFieldValue(property, outcome) {
|
|
|
2197
2881
|
if (outcome === "approved-session") return sessionChoice?.value ?? acceptChoice?.value;
|
|
2198
2882
|
return acceptChoice?.value ?? sessionChoice?.value;
|
|
2199
2883
|
}
|
|
2200
|
-
function readPersistFieldValue(property, meta, outcome) {
|
|
2884
|
+
function readPersistFieldValue(property, meta, outcome, persistHintsMode) {
|
|
2201
2885
|
if (!isPersistField(property) || outcome !== "approved-session") return;
|
|
2202
|
-
const persistHints = readPersistHints(meta);
|
|
2886
|
+
const persistHints = readPersistHints(meta, persistHintsMode);
|
|
2203
2887
|
const options = readEnumOptions(property.schema);
|
|
2204
2888
|
if (options.length === 0) return;
|
|
2205
2889
|
const preferred = choosePersistHint(persistHints);
|
|
2206
2890
|
if (preferred) return options.find((option) => option.value === preferred || option.label === preferred)?.value;
|
|
2891
|
+
if (persistHintsMode === "explicit") return chooseAlwaysPersistOptionValue(options);
|
|
2207
2892
|
}
|
|
2208
2893
|
function readDefaultValue(schema) {
|
|
2209
2894
|
return schema.default;
|
|
@@ -2227,21 +2912,27 @@ function propertyText(property) {
|
|
|
2227
2912
|
readString(property.schema, "description")
|
|
2228
2913
|
].filter(Boolean).join(" ");
|
|
2229
2914
|
}
|
|
2230
|
-
function readPersistHints(meta) {
|
|
2915
|
+
function readPersistHints(meta, mode = "legacy") {
|
|
2231
2916
|
const raw = meta.persist;
|
|
2232
2917
|
if (typeof raw === "string") return [raw];
|
|
2233
2918
|
if (Array.isArray(raw)) return raw.filter((entry) => typeof entry === "string");
|
|
2234
|
-
return ["session", "always"];
|
|
2919
|
+
return mode === "legacy" ? ["session", "always"] : [];
|
|
2235
2920
|
}
|
|
2236
|
-
function buildAcceptedMeta(meta, outcome) {
|
|
2921
|
+
function buildAcceptedMeta(meta, outcome, persistHintsMode) {
|
|
2237
2922
|
if (outcome !== "approved-session") return null;
|
|
2238
|
-
const persist = choosePersistHint(readPersistHints(meta));
|
|
2923
|
+
const persist = choosePersistHint(readPersistHints(meta, persistHintsMode));
|
|
2239
2924
|
return persist ? { persist } : null;
|
|
2240
2925
|
}
|
|
2241
2926
|
function choosePersistHint(persistHints) {
|
|
2242
2927
|
if (persistHints.includes("always")) return "always";
|
|
2243
2928
|
if (persistHints.includes("session")) return "session";
|
|
2244
2929
|
}
|
|
2930
|
+
function chooseAlwaysPersistOptionValue(options) {
|
|
2931
|
+
return options.find((option) => optionMatchesPersist(option, "always"))?.value;
|
|
2932
|
+
}
|
|
2933
|
+
function optionMatchesPersist(option, persist) {
|
|
2934
|
+
return option.value.toLowerCase() === persist || option.label.toLowerCase() === persist;
|
|
2935
|
+
}
|
|
2245
2936
|
function hasNoSchemaProperties(requestedSchema) {
|
|
2246
2937
|
const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
|
|
2247
2938
|
return Object.keys(properties).length === 0;
|
|
@@ -2472,4 +3163,38 @@ function sortJsonValue(value) {
|
|
|
2472
3163
|
return sorted;
|
|
2473
3164
|
}
|
|
2474
3165
|
//#endregion
|
|
2475
|
-
|
|
3166
|
+
//#region extensions/codex/src/app-server/provider-capabilities.ts
|
|
3167
|
+
async function readConfiguredProviderWebSearchSupport(params) {
|
|
3168
|
+
return (await params.client.request("modelProvider/capabilities/read", {}, {
|
|
3169
|
+
timeoutMs: params.timeoutMs,
|
|
3170
|
+
signal: params.signal
|
|
3171
|
+
})).webSearch ? "supported" : "unsupported";
|
|
3172
|
+
}
|
|
3173
|
+
async function resolveCodexProviderWebSearchSupportForClient(params) {
|
|
3174
|
+
const modelProviderOverride = params.modelProviderOverride?.trim().toLowerCase();
|
|
3175
|
+
if (modelProviderOverride === "openai") return "supported";
|
|
3176
|
+
if (modelProviderOverride) return "unsupported";
|
|
3177
|
+
try {
|
|
3178
|
+
return await readConfiguredProviderWebSearchSupport(params);
|
|
3179
|
+
} catch {
|
|
3180
|
+
return "unknown";
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
async function resolveCodexProviderWebSearchSupport(params) {
|
|
3184
|
+
let client;
|
|
3185
|
+
try {
|
|
3186
|
+
client = await params.clientFactory(params.appServer.start, params.authProfileId, params.agentDir, params.config, { timeoutMs: params.appServer.requestTimeoutMs });
|
|
3187
|
+
return await resolveCodexProviderWebSearchSupportForClient({
|
|
3188
|
+
client,
|
|
3189
|
+
timeoutMs: params.appServer.requestTimeoutMs,
|
|
3190
|
+
modelProviderOverride: params.modelProviderOverride,
|
|
3191
|
+
signal: params.signal
|
|
3192
|
+
});
|
|
3193
|
+
} catch {
|
|
3194
|
+
return "unknown";
|
|
3195
|
+
} finally {
|
|
3196
|
+
if (client) releaseLeasedSharedCodexAppServerClient(client);
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
//#endregion
|
|
3200
|
+
export { CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as A, resolveCodexMessageToolProvider as C, shouldWarnCodexDynamicToolBuildStageSummary as D, shouldRequireCodexSandboxExecServerEnvironment as E, resolveCodexTurnTerminalIdleTimeoutMs as F, withCodexStartupTimeout as I, resolveCodexStartupTimeoutMs as M, resolveCodexTurnAssistantCompletionIdleTimeoutMs as N, filterToolsForVisionInputs as O, resolveCodexTurnCompletionIdleTimeoutMs as P, resolveCodexExternalSandboxPolicyForOpenClawSandbox as S, shouldEnableCodexAppServerNativeToolSurface as T, createCodexDynamicToolBuildStageTracker as _, buildCodexNativeHookRelayConfig as a, resolveCodexAppServerExecutionCwd as b, resolveCodexNativeHookRelayEvents as c, handleCodexAppServerElicitationRequest as d, createCodexDynamicToolBridge as f, buildDynamicTools as g, emitDynamicToolTerminalDiagnostic as h, CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS as i, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as j, handleCodexAppServerApprovalRequest as k, resolveCodexNativeHookRelayTtlMs as l, emitDynamicToolStartedDiagnostic as m, resolveCodexProviderWebSearchSupportForClient as n, buildCodexNativeHookRelayDisabledConfig as o, emitDynamicToolErrorDiagnostic as p, CODEX_NATIVE_HOOK_RELAY_EVENTS as r, createCodexNativeHookRelay as s, resolveCodexProviderWebSearchSupport as t, scheduleCodexNativeHookRelayUnregister as u, disableCodexPluginThreadConfig as v, resolveCodexSandboxEnvironmentSelection as w, resolveCodexAppServerHookChannelId as x, formatCodexDynamicToolBuildStageSummary as y };
|