@openclaw/codex 2026.5.10-beta.3 → 2026.5.10-beta.4
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/{client-factory-BHbKz0nI.js → client-factory-D2P0KD6r.js} +1 -1
- package/dist/{request-Bj4IRDU9.js → command-formatters-Ttwc_kgX.js} +87 -51
- package/dist/{command-handlers-BPaLMsgL.js → command-handlers-iLF1EI-b.js} +303 -12
- package/dist/{compact-CMIpm-H_.js → compact-BJ1UYEyA.js} +2 -2
- package/dist/{rate-limit-cache-BFTJlMhx.js → computer-use-CVLaKaW3.js} +2 -25
- package/dist/{conversation-binding-zeuTIuN2.js → conversation-binding-D4XZ-tvV.js} +4 -3
- package/dist/harness.js +8 -4
- package/dist/index.js +5 -4
- package/dist/media-understanding-provider.js +3 -3
- package/dist/{models-lvthKXfT.js → models-Bg-Qf5s-.js} +2 -2
- package/dist/{protocol-validators-CeCyJaWj.js → protocol-validators-CSY0BFBo.js} +4 -1
- package/dist/provider.js +1 -1
- package/dist/rate-limit-cache-dvhq-4pi.js +24 -0
- package/dist/request-BCAfJvSg.js +41 -0
- package/dist/{run-attempt-COMElKgN.js → run-attempt-DZvighJE.js} +297 -1039
- package/dist/{session-binding-BAaJ-7s-.js → session-binding-UFKjHkKJ.js} +12 -3
- package/dist/{shared-client-BRX9tReb.js → shared-client-K13b0L1X.js} +76 -11
- package/dist/side-question-Dh_g53P9.js +548 -0
- package/dist/test-api.js +1 -1
- package/dist/{thread-lifecycle-DRyCVHhL.js → thread-lifecycle-i42nWiSS.js} +5 -5
- package/dist/vision-tools-Bo0P6gwM.js +1008 -0
- package/package.json +5 -5
- /package/dist/{plugin-activation-Dn14PX1W.js → plugin-activation-C0soz2YD.js} +0 -0
|
@@ -1,560 +1,28 @@
|
|
|
1
1
|
import { l as resolveCodexPluginsPolicy, o as readCodexPluginConfig, s as resolveCodexAppServerRuntimeOptions, u as withMcpElicitationsApprovalPolicy } from "./config-C7xdbzrp.js";
|
|
2
|
-
import {
|
|
2
|
+
import { a as readCodexDynamicToolCallParams, c as readCodexTurn, i as assertCodexTurnStartResponse } from "./protocol-validators-CSY0BFBo.js";
|
|
3
3
|
import { t as isJsonObject } from "./protocol-C9UWI98H.js";
|
|
4
4
|
import { i as isCodexAppServerConnectionClosedError, r as isCodexAppServerApprovalRequest } from "./client-CpksBQ9l.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { i as
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { n as
|
|
5
|
+
import { n as CODEX_CONTROL_METHODS } from "./request-BCAfJvSg.js";
|
|
6
|
+
import { d as resolveCodexUsageLimitResetAtMs, f as shouldRefreshCodexRateLimitsForUsageLimitMessage, r as formatCodexDisplayText, u as formatCodexUsageLimitErrorMessage } from "./command-formatters-Ttwc_kgX.js";
|
|
7
|
+
import { c as resolveCodexAppServerAuthProfileId, d as resolveCodexAppServerHomeDir, l as resolveCodexAppServerAuthProfileIdForAgent, o as refreshCodexAppServerAuthTokens, s as resolveCodexAppServerAuthAccountCacheKey, t as clearSharedCodexAppServerClientIfCurrent, u as resolveCodexAppServerEnvApiKeyCacheKey } from "./shared-client-K13b0L1X.js";
|
|
8
|
+
import { i as readCodexAppServerBinding } from "./session-binding-UFKjHkKJ.js";
|
|
9
|
+
import { i as defaultCodexAppInventoryCache, r as buildCodexAppInventoryCacheKey } from "./plugin-activation-C0soz2YD.js";
|
|
10
|
+
import { d as buildCodexPluginThreadConfig, f as buildCodexPluginThreadConfigInputFingerprint, g as normalizeCodexDynamicToolName, h as filterCodexDynamicTools, m as createCodexDynamicToolBridge, o as buildTurnStartParams, p as shouldBuildCodexPluginThreadConfig, r as buildDeveloperInstructions, s as codexDynamicToolsFingerprint, t as areCodexDynamicToolFingerprintsCompatible, u as startOrResumeThread } from "./thread-lifecycle-i42nWiSS.js";
|
|
11
|
+
import { n as defaultCodexAppServerClientFactory } from "./client-factory-D2P0KD6r.js";
|
|
12
|
+
import { n as handleCodexAppServerElicitationRequest, r as handleCodexAppServerApprovalRequest, t as filterToolsForVisionInputs } from "./vision-tools-Bo0P6gwM.js";
|
|
13
|
+
import { t as ensureCodexComputerUse } from "./computer-use-CVLaKaW3.js";
|
|
14
|
+
import { n as rememberCodexRateLimits, t as readRecentCodexRateLimits } from "./rate-limit-cache-dvhq-4pi.js";
|
|
12
15
|
import { createHash } from "node:crypto";
|
|
13
16
|
import nodeFs from "node:fs";
|
|
14
|
-
import { TOOL_PROGRESS_OUTPUT_MAX_CHARS, acquireSessionWriteLock, appendSessionTranscriptMessage, assembleHarnessContextEngine, bootstrapHarnessContextEngine, buildEmbeddedAttemptToolRunContext, buildHarnessContextEngineRuntimeContext, buildHarnessContextEngineRuntimeContextFromUsage,
|
|
17
|
+
import { TOOL_PROGRESS_OUTPUT_MAX_CHARS, acquireSessionWriteLock, appendSessionTranscriptMessage, assembleHarnessContextEngine, bootstrapHarnessContextEngine, buildEmbeddedAttemptToolRunContext, buildHarnessContextEngineRuntimeContext, buildHarnessContextEngineRuntimeContextFromUsage, classifyAgentHarnessTerminalOutcome, clearActiveEmbeddedRun, embeddedAgentLog, emitAgentEvent, emitSessionTranscriptUpdate, finalizeHarnessContextEngineTurn, formatErrorMessage, formatToolAggregate, formatToolProgressOutput, inferToolMetaFromArgs, isActiveHarnessContextEngine, isSubagentSessionKey, normalizeAgentRuntimeTools, normalizeUsage, registerNativeHookRelay, resolveAgentHarnessBeforePromptBuildResult, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, resolveSessionWriteLockAcquireTimeoutMs, resolveUserPath, runAgentCleanupStep, runAgentHarnessAfterCompactionHook, runAgentHarnessAfterToolCallHook, runAgentHarnessAgentEndHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessBeforeMessageWriteHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, runHarnessContextEngineMaintenance, setActiveEmbeddedRun, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
15
18
|
import fs from "node:fs/promises";
|
|
16
19
|
import path from "node:path";
|
|
17
|
-
import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
|
|
20
|
+
import { markAuthProfileBlockedUntil, resolveAgentDir as resolveAgentDir$1 } from "openclaw/plugin-sdk/agent-runtime";
|
|
18
21
|
import { appendRegularFile, pathExists } from "openclaw/plugin-sdk/security-runtime";
|
|
19
22
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
20
23
|
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
21
|
-
import { buildSessionContext, migrateSessionEntries, parseSessionEntries } from "@
|
|
24
|
+
import { buildSessionContext, migrateSessionEntries, parseSessionEntries } from "@earendil-works/pi-coding-agent";
|
|
22
25
|
import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
23
|
-
//#region extensions/codex/src/app-server/plugin-approval-roundtrip.ts
|
|
24
|
-
const DEFAULT_CODEX_APPROVAL_TIMEOUT_MS = 12e4;
|
|
25
|
-
const MAX_PLUGIN_APPROVAL_TITLE_LENGTH = 80;
|
|
26
|
-
const MAX_PLUGIN_APPROVAL_DESCRIPTION_LENGTH = 256;
|
|
27
|
-
async function requestPluginApproval(params) {
|
|
28
|
-
const timeoutMs = DEFAULT_CODEX_APPROVAL_TIMEOUT_MS;
|
|
29
|
-
return callGatewayTool("plugin.approval.request", { timeoutMs: timeoutMs + 1e4 }, {
|
|
30
|
-
pluginId: "openclaw-codex-app-server",
|
|
31
|
-
title: truncateForGateway(params.title, MAX_PLUGIN_APPROVAL_TITLE_LENGTH),
|
|
32
|
-
description: truncateForGateway(params.description, MAX_PLUGIN_APPROVAL_DESCRIPTION_LENGTH),
|
|
33
|
-
severity: params.severity,
|
|
34
|
-
toolName: params.toolName,
|
|
35
|
-
toolCallId: params.toolCallId,
|
|
36
|
-
agentId: params.paramsForRun.agentId,
|
|
37
|
-
sessionKey: params.paramsForRun.sessionKey,
|
|
38
|
-
turnSourceChannel: params.paramsForRun.messageChannel ?? params.paramsForRun.messageProvider,
|
|
39
|
-
turnSourceTo: params.paramsForRun.currentChannelId,
|
|
40
|
-
turnSourceAccountId: params.paramsForRun.agentAccountId,
|
|
41
|
-
turnSourceThreadId: params.paramsForRun.currentThreadTs,
|
|
42
|
-
timeoutMs,
|
|
43
|
-
twoPhase: true
|
|
44
|
-
}, { expectFinal: false });
|
|
45
|
-
}
|
|
46
|
-
function approvalRequestExplicitlyUnavailable(result) {
|
|
47
|
-
if (result === null || result === void 0 || typeof result !== "object") return false;
|
|
48
|
-
let descriptor;
|
|
49
|
-
try {
|
|
50
|
-
descriptor = Object.getOwnPropertyDescriptor(result, "decision");
|
|
51
|
-
} catch {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
return descriptor !== void 0 && "value" in descriptor && descriptor.value === null;
|
|
55
|
-
}
|
|
56
|
-
async function waitForPluginApprovalDecision(params) {
|
|
57
|
-
const waitPromise = callGatewayTool("plugin.approval.waitDecision", { timeoutMs: DEFAULT_CODEX_APPROVAL_TIMEOUT_MS + 1e4 }, { id: params.approvalId });
|
|
58
|
-
if (!params.signal) return (await waitPromise)?.decision;
|
|
59
|
-
let onAbort;
|
|
60
|
-
const abortPromise = new Promise((_, reject) => {
|
|
61
|
-
if (params.signal.aborted) {
|
|
62
|
-
reject(params.signal.reason);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
onAbort = () => reject(params.signal.reason);
|
|
66
|
-
params.signal.addEventListener("abort", onAbort, { once: true });
|
|
67
|
-
});
|
|
68
|
-
try {
|
|
69
|
-
return (await Promise.race([waitPromise, abortPromise]))?.decision;
|
|
70
|
-
} finally {
|
|
71
|
-
if (onAbort) params.signal.removeEventListener("abort", onAbort);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function mapExecDecisionToOutcome(decision) {
|
|
75
|
-
if (decision === "allow-once") return "approved-once";
|
|
76
|
-
if (decision === "allow-always") return "approved-session";
|
|
77
|
-
if (decision === null || decision === void 0) return "unavailable";
|
|
78
|
-
return "denied";
|
|
79
|
-
}
|
|
80
|
-
function truncateForGateway(value, maxLength) {
|
|
81
|
-
return value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
82
|
-
}
|
|
83
|
-
//#endregion
|
|
84
|
-
//#region extensions/codex/src/app-server/approval-bridge.ts
|
|
85
|
-
const PERMISSION_DESCRIPTION_MAX_LENGTH = 700;
|
|
86
|
-
const PERMISSION_SAMPLE_LIMIT = 2;
|
|
87
|
-
const PERMISSION_VALUE_MAX_LENGTH = 48;
|
|
88
|
-
const COMMAND_PREVIEW_WITH_DETAILS_MAX_LENGTH = 80;
|
|
89
|
-
const APPROVAL_PREVIEW_SCAN_MAX_LENGTH = 4096;
|
|
90
|
-
const APPROVAL_PREVIEW_OMITTED = "[preview truncated or unsafe content omitted]";
|
|
91
|
-
const ANSI_OSC_SEQUENCE_RE$1 = new RegExp(String.raw`(?:\u001b]|\u009d)[^\u001b\u009c\u0007]*(?:\u0007|\u001b\\|\u009c)`, "g");
|
|
92
|
-
const ANSI_CONTROL_SEQUENCE_RE$1 = new RegExp(String.raw`(?:\u001b\[[0-?]*[ -/]*[@-~]|\u009b[0-?]*[ -/]*[@-~]|\u001b[@-Z\\-_])`, "g");
|
|
93
|
-
const CONTROL_CHARACTER_RE$1 = new RegExp(String.raw`[\u0000-\u001f\u007f-\u009f]+`, "g");
|
|
94
|
-
const INVISIBLE_FORMATTING_CONTROL_RE$1 = new RegExp(String.raw`[\u00ad\u034f\u061c\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff\ufe00-\ufe0f\u{e0100}-\u{e01ef}]`, "gu");
|
|
95
|
-
const DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE$1 = new RegExp(String.raw`(?:\u001b\][^\u001b\u009c\u0007]*|\u009d[^\u001b\u009c\u0007]*|\u001b\[[0-?]*[ -/]*|\u009b[0-?]*[ -/]*|\u001b)$`);
|
|
96
|
-
async function handleCodexAppServerApprovalRequest(params) {
|
|
97
|
-
const requestParams = isJsonObject(params.requestParams) ? params.requestParams : void 0;
|
|
98
|
-
if (!matchesCurrentTurn(requestParams, params.threadId, params.turnId)) return;
|
|
99
|
-
if (!isSupportedAppServerApprovalMethod(params.method)) return unsupportedApprovalResponse();
|
|
100
|
-
const context = buildApprovalContext({
|
|
101
|
-
method: params.method,
|
|
102
|
-
requestParams,
|
|
103
|
-
paramsForRun: params.paramsForRun
|
|
104
|
-
});
|
|
105
|
-
try {
|
|
106
|
-
const requestResult = await requestPluginApproval({
|
|
107
|
-
paramsForRun: params.paramsForRun,
|
|
108
|
-
title: context.title,
|
|
109
|
-
description: context.description,
|
|
110
|
-
severity: context.severity,
|
|
111
|
-
toolName: context.toolName,
|
|
112
|
-
toolCallId: context.itemId
|
|
113
|
-
});
|
|
114
|
-
const approvalId = requestResult?.id;
|
|
115
|
-
if (!approvalId) {
|
|
116
|
-
emitApprovalEvent(params.paramsForRun, {
|
|
117
|
-
phase: "resolved",
|
|
118
|
-
kind: context.kind,
|
|
119
|
-
status: "unavailable",
|
|
120
|
-
title: context.title,
|
|
121
|
-
...context.eventDetails,
|
|
122
|
-
...approvalEventScope(params.method, "denied"),
|
|
123
|
-
message: "Codex app-server approval route unavailable."
|
|
124
|
-
});
|
|
125
|
-
return buildApprovalResponse(params.method, context.requestParams, "denied");
|
|
126
|
-
}
|
|
127
|
-
emitApprovalEvent(params.paramsForRun, {
|
|
128
|
-
phase: "requested",
|
|
129
|
-
kind: context.kind,
|
|
130
|
-
status: "pending",
|
|
131
|
-
title: context.title,
|
|
132
|
-
approvalId,
|
|
133
|
-
approvalSlug: approvalId,
|
|
134
|
-
...context.eventDetails,
|
|
135
|
-
message: "Codex app-server approval requested."
|
|
136
|
-
});
|
|
137
|
-
const outcome = mapExecDecisionToOutcome(approvalRequestExplicitlyUnavailable(requestResult) ? null : await waitForPluginApprovalDecision({
|
|
138
|
-
approvalId,
|
|
139
|
-
signal: params.signal
|
|
140
|
-
}));
|
|
141
|
-
emitApprovalEvent(params.paramsForRun, {
|
|
142
|
-
phase: "resolved",
|
|
143
|
-
kind: context.kind,
|
|
144
|
-
status: outcome === "denied" ? "denied" : outcome === "unavailable" ? "unavailable" : outcome === "cancelled" ? "failed" : "approved",
|
|
145
|
-
title: context.title,
|
|
146
|
-
approvalId,
|
|
147
|
-
approvalSlug: approvalId,
|
|
148
|
-
...context.eventDetails,
|
|
149
|
-
...approvalEventScope(params.method, outcome),
|
|
150
|
-
message: approvalResolutionMessage(outcome)
|
|
151
|
-
});
|
|
152
|
-
return buildApprovalResponse(params.method, context.requestParams, outcome);
|
|
153
|
-
} catch (error) {
|
|
154
|
-
const cancelled = params.signal?.aborted === true;
|
|
155
|
-
emitApprovalEvent(params.paramsForRun, {
|
|
156
|
-
phase: "resolved",
|
|
157
|
-
kind: context.kind,
|
|
158
|
-
status: cancelled ? "failed" : "unavailable",
|
|
159
|
-
title: context.title,
|
|
160
|
-
...context.eventDetails,
|
|
161
|
-
...approvalEventScope(params.method, cancelled ? "cancelled" : "denied"),
|
|
162
|
-
message: cancelled ? "Codex app-server approval cancelled because the run stopped." : `Codex app-server approval route failed: ${formatCodexDisplayText(formatErrorMessage$1(error))}`
|
|
163
|
-
});
|
|
164
|
-
return buildApprovalResponse(params.method, context.requestParams, cancelled ? "cancelled" : "denied");
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
function buildApprovalResponse(method, requestParams, outcome) {
|
|
168
|
-
if (method === "item/commandExecution/requestApproval") return { decision: commandApprovalDecision(requestParams, outcome) };
|
|
169
|
-
if (method === "item/fileChange/requestApproval") return { decision: fileChangeApprovalDecision(outcome) };
|
|
170
|
-
if (method === "item/permissions/requestApproval") {
|
|
171
|
-
if (outcome === "approved-session" || outcome === "approved-once") return {
|
|
172
|
-
permissions: requestedPermissions(requestParams),
|
|
173
|
-
scope: outcome === "approved-session" ? "session" : "turn"
|
|
174
|
-
};
|
|
175
|
-
return {
|
|
176
|
-
permissions: {},
|
|
177
|
-
scope: "turn"
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
return unsupportedApprovalResponse();
|
|
181
|
-
}
|
|
182
|
-
function matchesCurrentTurn(requestParams, threadId, turnId) {
|
|
183
|
-
if (!requestParams) return false;
|
|
184
|
-
const requestThreadId = readString$4(requestParams, "threadId") ?? readString$4(requestParams, "conversationId");
|
|
185
|
-
const requestTurnId = readString$4(requestParams, "turnId");
|
|
186
|
-
return requestThreadId === threadId && requestTurnId === turnId;
|
|
187
|
-
}
|
|
188
|
-
function buildApprovalContext(params) {
|
|
189
|
-
const itemId = readString$4(params.requestParams, "itemId") ?? readString$4(params.requestParams, "callId") ?? readString$4(params.requestParams, "approvalId");
|
|
190
|
-
const commandDetailLines = params.method === "item/commandExecution/requestApproval" ? describeCommandApprovalDetails(params.requestParams) : [];
|
|
191
|
-
const commandPreview = sanitizeApprovalPreview(readDisplayCommandPreview(params.requestParams), commandDetailLines.length > 0 ? COMMAND_PREVIEW_WITH_DETAILS_MAX_LENGTH : 180);
|
|
192
|
-
const reasonPreview = sanitizeApprovalPreview(readStringPreview(params.requestParams, "reason"), 180);
|
|
193
|
-
const command = commandPreview.text;
|
|
194
|
-
const reason = reasonPreview.text;
|
|
195
|
-
const kind = approvalKindForMethod(params.method);
|
|
196
|
-
const permissionLines = params.method === "item/permissions/requestApproval" ? describeRequestedPermissions(params.requestParams) : [];
|
|
197
|
-
const title = kind === "exec" ? "Codex app-server command approval" : params.method === "item/permissions/requestApproval" ? "Codex app-server permission approval" : kind === "plugin" ? "Codex app-server file approval" : "Codex app-server approval";
|
|
198
|
-
const subject = permissionLines[0] ?? (command ? `Command: ${formatApprovalPreviewSubject(command, commandPreview.omitted)}` : commandPreview.omitted ? `Command: ${APPROVAL_PREVIEW_OMITTED}` : reason ? `Reason: ${formatApprovalPreviewSubject(reason, reasonPreview.omitted)}` : reasonPreview.omitted ? `Reason: ${APPROVAL_PREVIEW_OMITTED}` : `Request method: ${params.method}`);
|
|
199
|
-
return {
|
|
200
|
-
kind,
|
|
201
|
-
title,
|
|
202
|
-
description: permissionLines.length > 0 ? joinDescriptionLinesWithinLimit(permissionLines, PERMISSION_DESCRIPTION_MAX_LENGTH) : [
|
|
203
|
-
subject,
|
|
204
|
-
...commandDetailLines,
|
|
205
|
-
params.paramsForRun.sessionKey && `Session: ${params.paramsForRun.sessionKey}`
|
|
206
|
-
].filter(Boolean).join("\n"),
|
|
207
|
-
severity: kind === "exec" ? "warning" : "info",
|
|
208
|
-
toolName: kind === "exec" ? "codex_command_approval" : params.method === "item/permissions/requestApproval" ? "codex_permission_approval" : "codex_file_approval",
|
|
209
|
-
itemId,
|
|
210
|
-
requestParams: params.requestParams,
|
|
211
|
-
eventDetails: {
|
|
212
|
-
...itemId ? { itemId } : {},
|
|
213
|
-
...command ? { command } : {},
|
|
214
|
-
...commandPreview.omitted ? { commandPreviewOmitted: true } : {},
|
|
215
|
-
...reason ? { reason } : {},
|
|
216
|
-
...reasonPreview.omitted ? { reasonPreviewOmitted: true } : {}
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
function commandApprovalDecision(requestParams, outcome) {
|
|
221
|
-
if (outcome === "cancelled") return commandRejectionDecision(requestParams, "cancel");
|
|
222
|
-
if (outcome === "denied" || outcome === "unavailable") return commandRejectionDecision(requestParams, "decline");
|
|
223
|
-
if (outcome === "approved-session") {
|
|
224
|
-
if (hasAvailableDecision(requestParams, "acceptForSession")) return "acceptForSession";
|
|
225
|
-
const amendmentDecision = findAvailableCommandAmendmentDecision(requestParams);
|
|
226
|
-
if (amendmentDecision) return amendmentDecision;
|
|
227
|
-
}
|
|
228
|
-
return hasAvailableDecision(requestParams, "accept") ? "accept" : commandRejectionDecision(requestParams, "decline");
|
|
229
|
-
}
|
|
230
|
-
function fileChangeApprovalDecision(outcome) {
|
|
231
|
-
if (outcome === "cancelled") return "cancel";
|
|
232
|
-
if (outcome === "denied" || outcome === "unavailable") return "decline";
|
|
233
|
-
return outcome === "approved-session" ? "acceptForSession" : "accept";
|
|
234
|
-
}
|
|
235
|
-
function requestedPermissions(requestParams) {
|
|
236
|
-
const permissions = isJsonObject(requestParams?.permissions) ? requestParams.permissions : {};
|
|
237
|
-
const granted = {};
|
|
238
|
-
if (isJsonObject(permissions.network)) granted.network = permissions.network;
|
|
239
|
-
if (isJsonObject(permissions.fileSystem)) granted.fileSystem = permissions.fileSystem;
|
|
240
|
-
return granted;
|
|
241
|
-
}
|
|
242
|
-
function unsupportedApprovalResponse() {
|
|
243
|
-
return {
|
|
244
|
-
decision: "decline",
|
|
245
|
-
reason: "OpenClaw codex app-server bridge does not grant native approvals yet."
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
function describeRequestedPermissions(requestParams) {
|
|
249
|
-
return describePermissionProfile(requestedPermissions(requestParams), "Permissions");
|
|
250
|
-
}
|
|
251
|
-
function describeCommandApprovalDetails(requestParams) {
|
|
252
|
-
const lines = [];
|
|
253
|
-
const additionalPermissions = isJsonObject(requestParams?.additionalPermissions) ? requestParams.additionalPermissions : void 0;
|
|
254
|
-
if (additionalPermissions) lines.push(...describePermissionProfile(additionalPermissions, "Additional permissions"));
|
|
255
|
-
const execpolicySummary = summarizeStringArray(requestParams?.proposedExecpolicyAmendment, "Proposed exec policy", sanitizePermissionScalar);
|
|
256
|
-
if (execpolicySummary) lines.push(execpolicySummary);
|
|
257
|
-
const networkAmendmentSummary = summarizeNetworkPolicyAmendments(requestParams?.proposedNetworkPolicyAmendments);
|
|
258
|
-
if (networkAmendmentSummary) lines.push(networkAmendmentSummary);
|
|
259
|
-
return lines;
|
|
260
|
-
}
|
|
261
|
-
function describePermissionProfile(permissions, label) {
|
|
262
|
-
const lines = [];
|
|
263
|
-
const kinds = [];
|
|
264
|
-
const risks = /* @__PURE__ */ new Set();
|
|
265
|
-
if (isJsonObject(permissions.network)) kinds.push("network");
|
|
266
|
-
if (isJsonObject(permissions.fileSystem)) kinds.push("fileSystem");
|
|
267
|
-
if (kinds.length > 0) lines.push(`${label}: ${kinds.join(", ")}`);
|
|
268
|
-
let networkSummary;
|
|
269
|
-
if (isJsonObject(permissions.network)) {
|
|
270
|
-
const summaries = [summarizeNetworkEnabledPermission(permissions.network, risks), summarizePermissionRecord(permissions.network, risks, [{
|
|
271
|
-
key: "allowHosts",
|
|
272
|
-
label: "allowHosts",
|
|
273
|
-
sanitize: sanitizePermissionHostValue,
|
|
274
|
-
risksFor: permissionHostRisks
|
|
275
|
-
}])].filter((summary) => Boolean(summary));
|
|
276
|
-
networkSummary = summaries.length > 0 ? summaries.join("; ") : void 0;
|
|
277
|
-
}
|
|
278
|
-
let fileSystemSummary;
|
|
279
|
-
if (isJsonObject(permissions.fileSystem)) {
|
|
280
|
-
const summaries = [summarizePermissionRecord(permissions.fileSystem, risks, [
|
|
281
|
-
{
|
|
282
|
-
key: "read",
|
|
283
|
-
label: "read",
|
|
284
|
-
sanitize: sanitizePermissionPathValue,
|
|
285
|
-
risksFor: permissionPathRisks
|
|
286
|
-
},
|
|
287
|
-
{
|
|
288
|
-
key: "write",
|
|
289
|
-
label: "write",
|
|
290
|
-
sanitize: sanitizePermissionPathValue,
|
|
291
|
-
risksFor: permissionPathRisks
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
key: "roots",
|
|
295
|
-
label: "roots",
|
|
296
|
-
sanitize: sanitizePermissionPathValue,
|
|
297
|
-
risksFor: permissionPathRisks
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
key: "readPaths",
|
|
301
|
-
label: "readPaths",
|
|
302
|
-
sanitize: sanitizePermissionPathValue,
|
|
303
|
-
risksFor: permissionPathRisks
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
key: "writePaths",
|
|
307
|
-
label: "writePaths",
|
|
308
|
-
sanitize: sanitizePermissionPathValue,
|
|
309
|
-
risksFor: permissionPathRisks
|
|
310
|
-
}
|
|
311
|
-
]), summarizeFileSystemEntries(permissions.fileSystem, risks)].filter((summary) => Boolean(summary));
|
|
312
|
-
fileSystemSummary = summaries.length > 0 ? summaries.join("; ") : void 0;
|
|
313
|
-
}
|
|
314
|
-
if (risks.size > 0) lines.push(`High-risk targets: ${[...risks].join(", ")}`);
|
|
315
|
-
if (networkSummary) lines.push(`Network ${networkSummary}`);
|
|
316
|
-
if (fileSystemSummary) lines.push(`File system ${fileSystemSummary}`);
|
|
317
|
-
return lines;
|
|
318
|
-
}
|
|
319
|
-
function summarizeNetworkEnabledPermission(permission, risks) {
|
|
320
|
-
const enabled = permission.enabled;
|
|
321
|
-
if (typeof enabled !== "boolean") return;
|
|
322
|
-
if (enabled) risks.add("network access");
|
|
323
|
-
return `enabled: ${enabled}`;
|
|
324
|
-
}
|
|
325
|
-
function summarizeFileSystemEntries(permission, risks) {
|
|
326
|
-
const entries = permission.entries;
|
|
327
|
-
if (!Array.isArray(entries)) return;
|
|
328
|
-
const samples = [];
|
|
329
|
-
let count = 0;
|
|
330
|
-
for (const entry of entries) {
|
|
331
|
-
const item = isJsonObject(entry) ? entry : void 0;
|
|
332
|
-
const path = typeof item?.path === "string" ? item.path.trim() : "";
|
|
333
|
-
const access = typeof item?.access === "string" ? item.access.trim() : "";
|
|
334
|
-
if (!path || !access) continue;
|
|
335
|
-
count += 1;
|
|
336
|
-
if (access !== "none") for (const risk of permissionPathRisks(path)) risks.add(risk);
|
|
337
|
-
if (samples.length < PERMISSION_SAMPLE_LIMIT) samples.push(`${sanitizePermissionScalar(access)} ${sanitizePermissionPathValue(path)}`);
|
|
338
|
-
}
|
|
339
|
-
if (count === 0) return;
|
|
340
|
-
const remaining = count - samples.length;
|
|
341
|
-
const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
|
|
342
|
-
return `entries: ${samples.join(", ")}${remainderSuffix}`;
|
|
343
|
-
}
|
|
344
|
-
function summarizePermissionRecord(permission, risks, descriptors) {
|
|
345
|
-
const details = [];
|
|
346
|
-
for (const descriptor of descriptors) {
|
|
347
|
-
const summary = summarizePermissionArray(permission, descriptor, risks);
|
|
348
|
-
if (summary) details.push(summary);
|
|
349
|
-
}
|
|
350
|
-
return details.length > 0 ? details.join("; ") : void 0;
|
|
351
|
-
}
|
|
352
|
-
function summarizePermissionArray(record, descriptor, risks) {
|
|
353
|
-
const values = readStringArray(record, descriptor.key);
|
|
354
|
-
if (values.length === 0) return;
|
|
355
|
-
for (const value of values) for (const risk of descriptor.risksFor(value)) risks.add(risk);
|
|
356
|
-
const sampleValues = values.slice(0, PERMISSION_SAMPLE_LIMIT).map(descriptor.sanitize).filter(Boolean);
|
|
357
|
-
if (sampleValues.length === 0) return `${descriptor.label}: ${values.length}`;
|
|
358
|
-
const remaining = values.length - sampleValues.length;
|
|
359
|
-
const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
|
|
360
|
-
return `${descriptor.label}: ${sampleValues.join(", ")}${remainderSuffix}`;
|
|
361
|
-
}
|
|
362
|
-
function summarizeStringArray(value, label, sanitize) {
|
|
363
|
-
if (!Array.isArray(value)) return;
|
|
364
|
-
const values = value.filter((entry) => typeof entry === "string").map((entry) => sanitize(entry)).filter(Boolean);
|
|
365
|
-
if (values.length === 0) return;
|
|
366
|
-
const samples = values.slice(0, PERMISSION_SAMPLE_LIMIT);
|
|
367
|
-
const remaining = values.length - samples.length;
|
|
368
|
-
const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
|
|
369
|
-
return `${label}: ${samples.join(", ")}${remainderSuffix}`;
|
|
370
|
-
}
|
|
371
|
-
function summarizeNetworkPolicyAmendments(value) {
|
|
372
|
-
if (!Array.isArray(value)) return;
|
|
373
|
-
const samples = [];
|
|
374
|
-
let count = 0;
|
|
375
|
-
for (const entry of value) {
|
|
376
|
-
const amendment = isJsonObject(entry) ? entry : void 0;
|
|
377
|
-
const host = typeof amendment?.host === "string" ? amendment.host : "";
|
|
378
|
-
const action = typeof amendment?.action === "string" ? amendment.action : "";
|
|
379
|
-
if (!host || !action) continue;
|
|
380
|
-
count += 1;
|
|
381
|
-
if (samples.length < PERMISSION_SAMPLE_LIMIT) samples.push(`${sanitizePermissionScalar(action)} ${sanitizePermissionHostValue(host)}`);
|
|
382
|
-
}
|
|
383
|
-
if (count === 0) return;
|
|
384
|
-
const remaining = count - samples.length;
|
|
385
|
-
const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
|
|
386
|
-
return `Proposed network policy: ${samples.join(", ")}${remainderSuffix}`;
|
|
387
|
-
}
|
|
388
|
-
function readStringArray(record, key) {
|
|
389
|
-
const value = record[key];
|
|
390
|
-
return Array.isArray(value) ? value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean) : [];
|
|
391
|
-
}
|
|
392
|
-
function sanitizePermissionHostValue(value) {
|
|
393
|
-
const withoutScheme = sanitizePermissionScalar(value).toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//, "");
|
|
394
|
-
const authority = withoutScheme.split(/[/?#]/, 1)[0] ?? withoutScheme;
|
|
395
|
-
return truncate(authority.includes("@") ? authority.slice(authority.lastIndexOf("@") + 1) : authority, PERMISSION_VALUE_MAX_LENGTH);
|
|
396
|
-
}
|
|
397
|
-
function sanitizePermissionPathValue(value) {
|
|
398
|
-
return truncate(formatApprovalDisplayPath(sanitizePermissionScalar(value)), PERMISSION_VALUE_MAX_LENGTH);
|
|
399
|
-
}
|
|
400
|
-
function sanitizePermissionScalar(value) {
|
|
401
|
-
return sanitizeVisibleScalar(value);
|
|
402
|
-
}
|
|
403
|
-
function permissionHostRisks(value) {
|
|
404
|
-
const normalized = value.trim().toLowerCase();
|
|
405
|
-
const risks = [];
|
|
406
|
-
if (normalized.includes("*")) {
|
|
407
|
-
risks.push("wildcard hosts");
|
|
408
|
-
if (isPrivateNetworkHostPattern(normalized)) risks.push("private-network wildcards");
|
|
409
|
-
}
|
|
410
|
-
return risks;
|
|
411
|
-
}
|
|
412
|
-
function permissionPathRisks(value) {
|
|
413
|
-
const normalized = sanitizePermissionScalar(value);
|
|
414
|
-
const risks = [];
|
|
415
|
-
if (normalized === "/" || normalized === "\\" || /^[A-Za-z]:[\\/]*$/.test(normalized)) risks.push("filesystem root");
|
|
416
|
-
return risks;
|
|
417
|
-
}
|
|
418
|
-
function isPrivateNetworkHostPattern(value) {
|
|
419
|
-
const wildcardStripped = value.toLowerCase().replace(/^\*\./, "");
|
|
420
|
-
if (wildcardStripped === "localhost" || wildcardStripped === "local" || wildcardStripped === "internal" || wildcardStripped === "lan" || wildcardStripped === "home" || wildcardStripped === "corp" || wildcardStripped === "private" || wildcardStripped.endsWith(".local") || wildcardStripped.endsWith(".internal") || wildcardStripped.endsWith(".lan") || wildcardStripped.endsWith(".home") || wildcardStripped.endsWith(".corp") || wildcardStripped.endsWith(".private")) return true;
|
|
421
|
-
if (wildcardStripped.startsWith("10.") || wildcardStripped.startsWith("127.") || wildcardStripped.startsWith("192.168.") || wildcardStripped.startsWith("169.254.")) return true;
|
|
422
|
-
return /^172\.(1[6-9]|2\d|3[0-1])\./.test(wildcardStripped);
|
|
423
|
-
}
|
|
424
|
-
function hasAvailableDecision(requestParams, decision) {
|
|
425
|
-
const available = requestParams?.availableDecisions;
|
|
426
|
-
if (!Array.isArray(available)) return true;
|
|
427
|
-
return available.includes(decision);
|
|
428
|
-
}
|
|
429
|
-
function findAvailableCommandAmendmentDecision(requestParams) {
|
|
430
|
-
const available = requestParams?.availableDecisions;
|
|
431
|
-
if (!Array.isArray(available)) return;
|
|
432
|
-
return available.find((entry) => isJsonObject(entry) && (isJsonObject(entry.acceptWithExecpolicyAmendment) || isJsonObject(entry.applyNetworkPolicyAmendment)));
|
|
433
|
-
}
|
|
434
|
-
function commandRejectionDecision(requestParams, preferred) {
|
|
435
|
-
const available = requestParams?.availableDecisions;
|
|
436
|
-
if (!Array.isArray(available)) return preferred;
|
|
437
|
-
if (available.includes(preferred)) return preferred;
|
|
438
|
-
const alternate = preferred === "decline" ? "cancel" : "decline";
|
|
439
|
-
if (available.includes(alternate)) return alternate;
|
|
440
|
-
return preferred;
|
|
441
|
-
}
|
|
442
|
-
function approvalResolutionMessage(outcome) {
|
|
443
|
-
if (outcome === "approved-session") return "Codex app-server approval granted for the session.";
|
|
444
|
-
if (outcome === "approved-once") return "Codex app-server approval granted for this turn.";
|
|
445
|
-
if (outcome === "cancelled") return "Codex app-server approval cancelled.";
|
|
446
|
-
if (outcome === "unavailable") return "Codex app-server approval unavailable.";
|
|
447
|
-
return "Codex app-server approval denied.";
|
|
448
|
-
}
|
|
449
|
-
function approvalScopeForOutcome(outcome) {
|
|
450
|
-
return outcome === "approved-session" ? "session" : "turn";
|
|
451
|
-
}
|
|
452
|
-
function approvalEventScope(method, outcome) {
|
|
453
|
-
return method === "item/permissions/requestApproval" ? { scope: approvalScopeForOutcome(outcome) } : {};
|
|
454
|
-
}
|
|
455
|
-
function approvalKindForMethod(method) {
|
|
456
|
-
if (method.includes("commandExecution") || method.includes("execCommand")) return "exec";
|
|
457
|
-
if (method.includes("fileChange") || method.includes("Patch") || method.includes("permissions")) return "plugin";
|
|
458
|
-
return "unknown";
|
|
459
|
-
}
|
|
460
|
-
function isSupportedAppServerApprovalMethod(method) {
|
|
461
|
-
return method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval" || method === "item/permissions/requestApproval";
|
|
462
|
-
}
|
|
463
|
-
function emitApprovalEvent(params, data) {
|
|
464
|
-
params.onAgentEvent?.({
|
|
465
|
-
stream: "approval",
|
|
466
|
-
data
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
function readDisplayCommandPreview(record) {
|
|
470
|
-
const actionCommand = readCommandActionsPreview(record);
|
|
471
|
-
if (actionCommand) return actionCommand;
|
|
472
|
-
return readCommandPreview(record);
|
|
473
|
-
}
|
|
474
|
-
function readCommandActionsPreview(record) {
|
|
475
|
-
const actions = record?.commandActions;
|
|
476
|
-
if (!Array.isArray(actions)) return;
|
|
477
|
-
let source;
|
|
478
|
-
for (const action of actions) {
|
|
479
|
-
const command = isJsonObject(action) ? readString$4(action, "command") : void 0;
|
|
480
|
-
if (!command) continue;
|
|
481
|
-
source = appendPreviewPart(source, command, " && ");
|
|
482
|
-
if (source.clipped) break;
|
|
483
|
-
}
|
|
484
|
-
return source;
|
|
485
|
-
}
|
|
486
|
-
function readCommandPreview(record) {
|
|
487
|
-
const command = record?.command;
|
|
488
|
-
if (typeof command === "string") return previewSource(command);
|
|
489
|
-
if (!Array.isArray(command)) return;
|
|
490
|
-
let source;
|
|
491
|
-
for (const part of command) {
|
|
492
|
-
if (typeof part !== "string") return;
|
|
493
|
-
source = appendPreviewPart(source, part, " ");
|
|
494
|
-
if (source.clipped) break;
|
|
495
|
-
}
|
|
496
|
-
return source;
|
|
497
|
-
}
|
|
498
|
-
function readStringPreview(record, key) {
|
|
499
|
-
const value = readString$4(record, key);
|
|
500
|
-
return value === void 0 ? void 0 : previewSource(value);
|
|
501
|
-
}
|
|
502
|
-
function readString$4(record, key) {
|
|
503
|
-
const value = record?.[key];
|
|
504
|
-
return typeof value === "string" ? value : void 0;
|
|
505
|
-
}
|
|
506
|
-
function truncate(value, maxLength) {
|
|
507
|
-
return value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
508
|
-
}
|
|
509
|
-
function previewSource(value) {
|
|
510
|
-
return {
|
|
511
|
-
value: value.slice(0, APPROVAL_PREVIEW_SCAN_MAX_LENGTH),
|
|
512
|
-
clipped: value.length > APPROVAL_PREVIEW_SCAN_MAX_LENGTH
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
function appendPreviewPart(source, part, separator) {
|
|
516
|
-
const value = `${source?.value ? `${source.value}${separator}` : ""}${part}`;
|
|
517
|
-
const clipped = source?.clipped === true || value.length > APPROVAL_PREVIEW_SCAN_MAX_LENGTH;
|
|
518
|
-
return {
|
|
519
|
-
value: value.slice(0, APPROVAL_PREVIEW_SCAN_MAX_LENGTH),
|
|
520
|
-
clipped
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
function sanitizeApprovalPreview(source, maxLength) {
|
|
524
|
-
if (!source || !source.value) return { omitted: false };
|
|
525
|
-
const sanitized = sanitizeVisibleScalar(source.value.replace(DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE$1, ""));
|
|
526
|
-
if (!sanitized) return { omitted: true };
|
|
527
|
-
return {
|
|
528
|
-
text: formatCodexDisplayText(truncate(sanitized, maxLength)),
|
|
529
|
-
omitted: source.clipped
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
function sanitizeVisibleScalar(value) {
|
|
533
|
-
return value.replace(ANSI_OSC_SEQUENCE_RE$1, "").replace(ANSI_CONTROL_SEQUENCE_RE$1, "").replace(INVISIBLE_FORMATTING_CONTROL_RE$1, " ").replace(CONTROL_CHARACTER_RE$1, " ").replace(/\s+/g, " ").trim();
|
|
534
|
-
}
|
|
535
|
-
function formatApprovalPreviewSubject(text, omitted) {
|
|
536
|
-
return omitted ? `${text} ${APPROVAL_PREVIEW_OMITTED}` : text;
|
|
537
|
-
}
|
|
538
|
-
function joinDescriptionLinesWithinLimit(lines, maxLength) {
|
|
539
|
-
let description = "";
|
|
540
|
-
for (const line of lines) {
|
|
541
|
-
const prefix = description ? "\n" : "";
|
|
542
|
-
const next = `${description}${prefix}${line}`;
|
|
543
|
-
if (next.length <= maxLength) {
|
|
544
|
-
description = next;
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
const remaining = maxLength - description.length - prefix.length;
|
|
548
|
-
if (remaining < 3) break;
|
|
549
|
-
description += `${prefix}${truncate(line, remaining)}`;
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
return description;
|
|
553
|
-
}
|
|
554
|
-
function formatErrorMessage$1(error) {
|
|
555
|
-
return error instanceof Error ? error.message : String(error);
|
|
556
|
-
}
|
|
557
|
-
//#endregion
|
|
558
26
|
//#region extensions/codex/src/app-server/context-engine-projection.ts
|
|
559
27
|
const CONTEXT_HEADER = "OpenClaw assembled context for this turn:";
|
|
560
28
|
const CONTEXT_OPEN = "<conversation_context>";
|
|
@@ -632,469 +100,6 @@ function truncateText(text, maxChars) {
|
|
|
632
100
|
return text.length > maxChars ? `${text.slice(0, maxChars)}\n[truncated ${text.length - maxChars} chars]` : text;
|
|
633
101
|
}
|
|
634
102
|
//#endregion
|
|
635
|
-
//#region extensions/codex/src/app-server/elicitation-bridge.ts
|
|
636
|
-
const MCP_TOOL_APPROVAL_KIND = "mcp_tool_call";
|
|
637
|
-
const MCP_TOOL_APPROVAL_KIND_KEY = "codex_approval_kind";
|
|
638
|
-
const MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY = "connector_name";
|
|
639
|
-
const MCP_TOOL_APPROVAL_TOOL_TITLE_KEY = "tool_title";
|
|
640
|
-
const MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY = "tool_description";
|
|
641
|
-
const MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY = "tool_params_display";
|
|
642
|
-
const PLUGIN_APP_ID_META_KEYS = [
|
|
643
|
-
"app_id",
|
|
644
|
-
"appId",
|
|
645
|
-
"codex_app_id",
|
|
646
|
-
"codexAppId"
|
|
647
|
-
];
|
|
648
|
-
const PLUGIN_NAME_META_KEYS = [
|
|
649
|
-
"plugin_name",
|
|
650
|
-
"pluginName",
|
|
651
|
-
"codex_plugin_name",
|
|
652
|
-
"codexPluginName"
|
|
653
|
-
];
|
|
654
|
-
const PLUGIN_CONFIG_KEY_META_KEYS = [
|
|
655
|
-
"config_key",
|
|
656
|
-
"configKey",
|
|
657
|
-
"codex_config_key"
|
|
658
|
-
];
|
|
659
|
-
const PLUGIN_MARKETPLACE_NAME_META_KEYS = [
|
|
660
|
-
"marketplace_name",
|
|
661
|
-
"marketplaceName",
|
|
662
|
-
"codex_marketplace_name",
|
|
663
|
-
"codexMarketplaceName"
|
|
664
|
-
];
|
|
665
|
-
const MAX_DISPLAY_PARAM_ENTRIES = 8;
|
|
666
|
-
const MAX_DISPLAY_PARAM_VALUE_LENGTH = 120;
|
|
667
|
-
const MAX_DISPLAY_VALUE_ARRAY_ITEMS = 8;
|
|
668
|
-
const MAX_DISPLAY_VALUE_OBJECT_KEYS = 8;
|
|
669
|
-
const MAX_DISPLAY_VALUE_DEPTH = 3;
|
|
670
|
-
const DISPLAY_TEXT_SCAN_MAX_LENGTH = 4096;
|
|
671
|
-
const ANSI_OSC_SEQUENCE_RE = new RegExp(String.raw`(?:\u001b]|\u009d)[^\u001b\u009c\u0007]*(?:\u0007|\u001b\\|\u009c)`, "g");
|
|
672
|
-
const ANSI_CONTROL_SEQUENCE_RE = new RegExp(String.raw`(?:\u001b\[[0-?]*[ -/]*[@-~]|\u009b[0-?]*[ -/]*[@-~]|\u001b[@-Z\\-_])`, "g");
|
|
673
|
-
const CONTROL_CHARACTER_RE = new RegExp(String.raw`[\u0000-\u001f\u007f-\u009f]+`, "g");
|
|
674
|
-
const INVISIBLE_FORMATTING_CONTROL_RE = new RegExp(String.raw`[\u00ad\u034f\u061c\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff\ufe00-\ufe0f\u{e0100}-\u{e01ef}]`, "gu");
|
|
675
|
-
const DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE = new RegExp(String.raw`(?:\u001b\][^\u001b\u009c\u0007]*|\u009d[^\u001b\u009c\u0007]*|\u001b\[[0-?]*[ -/]*|\u009b[0-?]*[ -/]*|\u001b)$`);
|
|
676
|
-
async function handleCodexAppServerElicitationRequest(params) {
|
|
677
|
-
const requestParams = isJsonObject(params.requestParams) ? params.requestParams : void 0;
|
|
678
|
-
if (!requestParams) return;
|
|
679
|
-
if (!matchesCurrentThread(requestParams, params.threadId)) return;
|
|
680
|
-
if (turnIdMismatches(requestParams, params.turnId)) return;
|
|
681
|
-
const pluginResolution = resolvePluginElicitation({
|
|
682
|
-
requestParams,
|
|
683
|
-
pluginAppPolicyContext: params.pluginAppPolicyContext
|
|
684
|
-
});
|
|
685
|
-
if (pluginResolution.kind !== "not_plugin") {
|
|
686
|
-
if (pluginResolution.kind === "decline") {
|
|
687
|
-
logPluginElicitationDecline(pluginResolution.reason, requestParams);
|
|
688
|
-
return declineElicitationResponse();
|
|
689
|
-
}
|
|
690
|
-
if (!hasExactTurnId(requestParams, params.turnId)) {
|
|
691
|
-
logPluginElicitationDecline("missing_active_turn", requestParams);
|
|
692
|
-
return declineElicitationResponse();
|
|
693
|
-
}
|
|
694
|
-
return buildPluginPolicyElicitationResponse(pluginResolution.entry, requestParams);
|
|
695
|
-
}
|
|
696
|
-
const approvalPrompt = readBridgeableApprovalElicitation(requestParams);
|
|
697
|
-
if (!approvalPrompt) return;
|
|
698
|
-
const outcome = await requestPluginApprovalOutcome({
|
|
699
|
-
paramsForRun: params.paramsForRun,
|
|
700
|
-
title: approvalPrompt.title,
|
|
701
|
-
description: approvalPrompt.description,
|
|
702
|
-
signal: params.signal
|
|
703
|
-
});
|
|
704
|
-
return buildElicitationResponse(approvalPrompt.requestedSchema, approvalPrompt.meta, outcome);
|
|
705
|
-
}
|
|
706
|
-
function matchesCurrentThread(requestParams, threadId) {
|
|
707
|
-
if (!requestParams) return false;
|
|
708
|
-
return readString$3(requestParams, "threadId") === threadId;
|
|
709
|
-
}
|
|
710
|
-
function turnIdMismatches(requestParams, turnId) {
|
|
711
|
-
const rawTurnId = requestParams?.turnId;
|
|
712
|
-
return rawTurnId !== null && rawTurnId !== void 0 && rawTurnId !== turnId;
|
|
713
|
-
}
|
|
714
|
-
function hasExactTurnId(requestParams, turnId) {
|
|
715
|
-
return requestParams?.turnId === turnId;
|
|
716
|
-
}
|
|
717
|
-
function resolvePluginElicitation(params) {
|
|
718
|
-
const requestParams = params.requestParams;
|
|
719
|
-
if (!requestParams) return { kind: "not_plugin" };
|
|
720
|
-
const meta = isJsonObject(requestParams._meta) ? requestParams._meta : {};
|
|
721
|
-
const context = params.pluginAppPolicyContext;
|
|
722
|
-
const entries = context ? Object.values(context.apps) : [];
|
|
723
|
-
const appId = readFirstString(meta, PLUGIN_APP_ID_META_KEYS) ?? readFirstString(requestParams, PLUGIN_APP_ID_META_KEYS);
|
|
724
|
-
if (appId) {
|
|
725
|
-
if (!context) return {
|
|
726
|
-
kind: "decline",
|
|
727
|
-
reason: "missing_policy_context"
|
|
728
|
-
};
|
|
729
|
-
const entry = context.apps[appId];
|
|
730
|
-
return uniquePluginMatch(entry ? [entry] : [], "app_id");
|
|
731
|
-
}
|
|
732
|
-
const serverName = readString$3(requestParams, "serverName");
|
|
733
|
-
if (serverName && context) {
|
|
734
|
-
const matches = entries.filter((entry) => entry.mcpServerNames.includes(serverName));
|
|
735
|
-
if (matches.length > 0) return uniquePluginMatch(matches, "server_name");
|
|
736
|
-
}
|
|
737
|
-
const metadataResolution = resolvePluginStableMetadataMatch({
|
|
738
|
-
meta,
|
|
739
|
-
requestParams,
|
|
740
|
-
entries,
|
|
741
|
-
context
|
|
742
|
-
});
|
|
743
|
-
if (metadataResolution.kind !== "not_plugin") return metadataResolution;
|
|
744
|
-
if (context && hasDisplayNameOnlyPluginMatch(meta, entries)) return {
|
|
745
|
-
kind: "decline",
|
|
746
|
-
reason: "display_name_only"
|
|
747
|
-
};
|
|
748
|
-
return { kind: "not_plugin" };
|
|
749
|
-
}
|
|
750
|
-
function resolvePluginStableMetadataMatch(params) {
|
|
751
|
-
const pluginName = readFirstString(params.meta, PLUGIN_NAME_META_KEYS) ?? readFirstString(params.requestParams, PLUGIN_NAME_META_KEYS);
|
|
752
|
-
const configKey = readFirstString(params.meta, PLUGIN_CONFIG_KEY_META_KEYS) ?? readFirstString(params.requestParams, PLUGIN_CONFIG_KEY_META_KEYS);
|
|
753
|
-
const marketplaceName = readFirstString(params.meta, PLUGIN_MARKETPLACE_NAME_META_KEYS) ?? readFirstString(params.requestParams, PLUGIN_MARKETPLACE_NAME_META_KEYS);
|
|
754
|
-
if (!pluginName && !configKey) return { kind: "not_plugin" };
|
|
755
|
-
if (!params.context) return {
|
|
756
|
-
kind: "decline",
|
|
757
|
-
reason: "missing_policy_context"
|
|
758
|
-
};
|
|
759
|
-
return uniquePluginMatch(params.entries.filter((entry) => {
|
|
760
|
-
if (marketplaceName && entry.marketplaceName !== marketplaceName) return false;
|
|
761
|
-
if (pluginName && entry.pluginName !== pluginName) return false;
|
|
762
|
-
if (configKey && entry.configKey !== configKey) return false;
|
|
763
|
-
return true;
|
|
764
|
-
}), "metadata");
|
|
765
|
-
}
|
|
766
|
-
function uniquePluginMatch(matches, source) {
|
|
767
|
-
if (matches.length === 1 && matches[0]) return {
|
|
768
|
-
kind: "matched",
|
|
769
|
-
entry: matches[0]
|
|
770
|
-
};
|
|
771
|
-
return {
|
|
772
|
-
kind: "decline",
|
|
773
|
-
reason: matches.length === 0 ? `${source}_not_enabled` : `${source}_ambiguous`
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
function hasDisplayNameOnlyPluginMatch(meta, entries) {
|
|
777
|
-
const connectorName = readString$3(meta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY);
|
|
778
|
-
if (!connectorName) return false;
|
|
779
|
-
const normalized = normalizePluginIdentityText(connectorName);
|
|
780
|
-
return entries.some((entry) => normalizePluginIdentityText(entry.pluginName) === normalized || normalizePluginIdentityText(entry.configKey) === normalized);
|
|
781
|
-
}
|
|
782
|
-
function normalizePluginIdentityText(value) {
|
|
783
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
784
|
-
}
|
|
785
|
-
function buildPluginPolicyElicitationResponse(entry, requestParams) {
|
|
786
|
-
if (!entry.allowDestructiveActions) {
|
|
787
|
-
logPluginElicitationDecline("destructive_actions_disabled", requestParams);
|
|
788
|
-
return declineElicitationResponse();
|
|
789
|
-
}
|
|
790
|
-
if (readString$3(requestParams, "mode") !== "form" || !isJsonObject(requestParams.requestedSchema)) {
|
|
791
|
-
logPluginElicitationDecline("unsupported_schema", requestParams);
|
|
792
|
-
return declineElicitationResponse();
|
|
793
|
-
}
|
|
794
|
-
const meta = isJsonObject(requestParams._meta) ? requestParams._meta : {};
|
|
795
|
-
const response = buildElicitationResponse(requestParams.requestedSchema, meta, "approved-once");
|
|
796
|
-
if (isJsonObject(response) && response.action === "accept") return response;
|
|
797
|
-
logPluginElicitationDecline("unmappable_schema", requestParams);
|
|
798
|
-
return declineElicitationResponse();
|
|
799
|
-
}
|
|
800
|
-
function declineElicitationResponse() {
|
|
801
|
-
return {
|
|
802
|
-
action: "decline",
|
|
803
|
-
content: null,
|
|
804
|
-
_meta: null
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
function logPluginElicitationDecline(reason, requestParams) {
|
|
808
|
-
embeddedAgentLog.debug("codex plugin elicitation declined", {
|
|
809
|
-
reason,
|
|
810
|
-
serverName: readString$3(requestParams, "serverName"),
|
|
811
|
-
mode: readString$3(requestParams, "mode")
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
function readBridgeableApprovalElicitation(requestParams) {
|
|
815
|
-
if (!requestParams || readString$3(requestParams, "mode") !== "form" || !isJsonObject(requestParams._meta) || requestParams._meta[MCP_TOOL_APPROVAL_KIND_KEY] !== MCP_TOOL_APPROVAL_KIND || !isJsonObject(requestParams.requestedSchema)) return;
|
|
816
|
-
const requestedSchema = requestParams.requestedSchema;
|
|
817
|
-
if (readString$3(requestedSchema, "type") !== "object" || !isJsonObject(requestedSchema.properties)) return;
|
|
818
|
-
const title = sanitizeDisplayText(readString$3(requestParams, "message") ?? "") || "Codex MCP tool approval";
|
|
819
|
-
return {
|
|
820
|
-
title,
|
|
821
|
-
description: buildApprovalDescription({
|
|
822
|
-
title,
|
|
823
|
-
meta: requestParams._meta,
|
|
824
|
-
requestedSchema,
|
|
825
|
-
serverName: sanitizeOptionalDisplayText(readString$3(requestParams, "serverName"))
|
|
826
|
-
}),
|
|
827
|
-
requestedSchema,
|
|
828
|
-
meta: requestParams._meta
|
|
829
|
-
};
|
|
830
|
-
}
|
|
831
|
-
function buildApprovalDescription(params) {
|
|
832
|
-
const connectorName = sanitizeOptionalDisplayText(readString$3(params.meta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY));
|
|
833
|
-
const toolTitle = sanitizeOptionalDisplayText(readString$3(params.meta, MCP_TOOL_APPROVAL_TOOL_TITLE_KEY));
|
|
834
|
-
const toolDescription = sanitizeOptionalDisplayText(readString$3(params.meta, MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY));
|
|
835
|
-
const summaryLines = [
|
|
836
|
-
connectorName && `App: ${connectorName}`,
|
|
837
|
-
toolTitle && `Tool: ${toolTitle}`,
|
|
838
|
-
params.serverName && `MCP server: ${params.serverName}`,
|
|
839
|
-
toolDescription
|
|
840
|
-
].filter((line) => Boolean(line));
|
|
841
|
-
const paramLines = readDisplayParamLines(params.meta);
|
|
842
|
-
const propertyLines = readPropertyDescriptionLines(params.requestedSchema);
|
|
843
|
-
return [
|
|
844
|
-
params.title,
|
|
845
|
-
summaryLines.join("\n"),
|
|
846
|
-
paramLines.length > 0 ? ["Parameters:", ...paramLines].join("\n") : "",
|
|
847
|
-
propertyLines.length > 0 ? ["Fields:", ...propertyLines].join("\n") : ""
|
|
848
|
-
].filter(Boolean).join("\n\n");
|
|
849
|
-
}
|
|
850
|
-
function readPropertyDescriptionLines(requestedSchema) {
|
|
851
|
-
const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
|
|
852
|
-
return Object.entries(properties).map(([name, value]) => {
|
|
853
|
-
const schema = isJsonObject(value) ? value : void 0;
|
|
854
|
-
if (!schema) return;
|
|
855
|
-
const propTitle = sanitizeDisplayText(readString$3(schema, "title") ?? "") || sanitizeDisplayText(name) || "field";
|
|
856
|
-
const description = sanitizeOptionalDisplayText(readString$3(schema, "description"));
|
|
857
|
-
return description ? `- ${propTitle}: ${description}` : `- ${propTitle}`;
|
|
858
|
-
}).filter((line) => Boolean(line));
|
|
859
|
-
}
|
|
860
|
-
function readDisplayParamLines(meta) {
|
|
861
|
-
const displayParams = meta[MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY];
|
|
862
|
-
if (!Array.isArray(displayParams)) return [];
|
|
863
|
-
const lines = displayParams.slice(0, MAX_DISPLAY_PARAM_ENTRIES).map((entry) => {
|
|
864
|
-
const param = isJsonObject(entry) ? entry : void 0;
|
|
865
|
-
if (!param) return;
|
|
866
|
-
const name = sanitizeOptionalDisplayText(readString$3(param, "display_name")) ?? sanitizeOptionalDisplayText(readString$3(param, "name"));
|
|
867
|
-
if (!name) return;
|
|
868
|
-
return `- ${name}: ${formatDisplayParamValue(param.value)}`;
|
|
869
|
-
}).filter((line) => Boolean(line));
|
|
870
|
-
const remaining = displayParams.length - MAX_DISPLAY_PARAM_ENTRIES;
|
|
871
|
-
return remaining > 0 ? [...lines, `- Additional parameters: ${remaining} more`] : lines;
|
|
872
|
-
}
|
|
873
|
-
function formatDisplayParamValue(value) {
|
|
874
|
-
return truncateDisplayText(sanitizeDisplayText(typeof value === "string" ? value : formatDisplayJsonValue(value ?? null)), MAX_DISPLAY_PARAM_VALUE_LENGTH);
|
|
875
|
-
}
|
|
876
|
-
function formatDisplayJsonValue(value, depth = MAX_DISPLAY_VALUE_DEPTH) {
|
|
877
|
-
if (value === null) return "null";
|
|
878
|
-
if (typeof value === "string") return JSON.stringify(truncateDisplayText(sanitizeDisplayText(value), 80));
|
|
879
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
880
|
-
if (Array.isArray(value)) {
|
|
881
|
-
if (depth <= 0) return "[truncated]";
|
|
882
|
-
const parts = [];
|
|
883
|
-
const limit = Math.min(value.length, MAX_DISPLAY_VALUE_ARRAY_ITEMS);
|
|
884
|
-
for (let i = 0; i < limit; i += 1) parts.push(formatDisplayJsonValue(value[i] ?? null, depth - 1));
|
|
885
|
-
if (value.length > MAX_DISPLAY_VALUE_ARRAY_ITEMS) parts.push("...");
|
|
886
|
-
return `[${parts.join(",")}]`;
|
|
887
|
-
}
|
|
888
|
-
if (typeof value === "object") {
|
|
889
|
-
if (depth <= 0) return "{truncated}";
|
|
890
|
-
const parts = [];
|
|
891
|
-
let count = 0;
|
|
892
|
-
let truncated = false;
|
|
893
|
-
for (const key in value) {
|
|
894
|
-
if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
|
|
895
|
-
if (count >= MAX_DISPLAY_VALUE_OBJECT_KEYS) {
|
|
896
|
-
truncated = true;
|
|
897
|
-
break;
|
|
898
|
-
}
|
|
899
|
-
const safeKey = truncateDisplayText(sanitizeDisplayText(key), 80);
|
|
900
|
-
parts.push(`${JSON.stringify(safeKey)}:${formatDisplayJsonValue(value[key] ?? null, depth - 1)}`);
|
|
901
|
-
count += 1;
|
|
902
|
-
}
|
|
903
|
-
if (truncated) parts.push("...");
|
|
904
|
-
return `{${parts.join(",")}}`;
|
|
905
|
-
}
|
|
906
|
-
return "null";
|
|
907
|
-
}
|
|
908
|
-
function sanitizeOptionalDisplayText(value) {
|
|
909
|
-
return (value === void 0 ? "" : sanitizeDisplayText(value)) || void 0;
|
|
910
|
-
}
|
|
911
|
-
function sanitizeDisplayText(value) {
|
|
912
|
-
const scanned = value.slice(0, DISPLAY_TEXT_SCAN_MAX_LENGTH);
|
|
913
|
-
const clipped = value.length > DISPLAY_TEXT_SCAN_MAX_LENGTH;
|
|
914
|
-
const sanitized = scanned.replace(ANSI_OSC_SEQUENCE_RE, "").replace(ANSI_CONTROL_SEQUENCE_RE, "").replace(DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE, "").replace(INVISIBLE_FORMATTING_CONTROL_RE, " ").replace(CONTROL_CHARACTER_RE, " ").replace(/\s+/g, " ").trim();
|
|
915
|
-
const escaped = sanitized ? formatCodexDisplayText(sanitized) : "";
|
|
916
|
-
return clipped && escaped ? `${escaped}...` : escaped;
|
|
917
|
-
}
|
|
918
|
-
function truncateDisplayText(value, maxLength) {
|
|
919
|
-
return value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
920
|
-
}
|
|
921
|
-
async function requestPluginApprovalOutcome(params) {
|
|
922
|
-
try {
|
|
923
|
-
const requestResult = await requestPluginApproval({
|
|
924
|
-
paramsForRun: params.paramsForRun,
|
|
925
|
-
title: params.title,
|
|
926
|
-
description: params.description,
|
|
927
|
-
severity: "warning",
|
|
928
|
-
toolName: "codex_mcp_tool_approval"
|
|
929
|
-
});
|
|
930
|
-
const approvalId = requestResult?.id;
|
|
931
|
-
if (!approvalId) return "unavailable";
|
|
932
|
-
return mapExecDecisionToOutcome(approvalRequestExplicitlyUnavailable(requestResult) ? null : await waitForPluginApprovalDecision({
|
|
933
|
-
approvalId,
|
|
934
|
-
signal: params.signal
|
|
935
|
-
}));
|
|
936
|
-
} catch {
|
|
937
|
-
return params.signal?.aborted ? "cancelled" : "denied";
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
function buildElicitationResponse(requestedSchema, meta, outcome) {
|
|
941
|
-
if (outcome === "cancelled") return {
|
|
942
|
-
action: "cancel",
|
|
943
|
-
content: null,
|
|
944
|
-
_meta: null
|
|
945
|
-
};
|
|
946
|
-
if (outcome === "denied" || outcome === "unavailable") return {
|
|
947
|
-
action: "decline",
|
|
948
|
-
content: null,
|
|
949
|
-
_meta: null
|
|
950
|
-
};
|
|
951
|
-
const content = buildAcceptedContent(requestedSchema, meta, outcome);
|
|
952
|
-
if (!content) {
|
|
953
|
-
if (hasNoSchemaProperties(requestedSchema)) return {
|
|
954
|
-
action: "accept",
|
|
955
|
-
content: null,
|
|
956
|
-
_meta: buildAcceptedMeta(meta, outcome)
|
|
957
|
-
};
|
|
958
|
-
embeddedAgentLog.warn("codex MCP approval elicitation approved without a mappable response", {
|
|
959
|
-
approvalKind: meta[MCP_TOOL_APPROVAL_KIND_KEY],
|
|
960
|
-
fields: Object.keys(requestedSchema.properties ?? {}),
|
|
961
|
-
outcome
|
|
962
|
-
});
|
|
963
|
-
return {
|
|
964
|
-
action: "decline",
|
|
965
|
-
content: null,
|
|
966
|
-
_meta: null
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
return {
|
|
970
|
-
action: "accept",
|
|
971
|
-
content,
|
|
972
|
-
_meta: buildAcceptedMeta(meta, outcome)
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
function buildAcceptedContent(requestedSchema, meta, outcome) {
|
|
976
|
-
const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : void 0;
|
|
977
|
-
if (!properties) return;
|
|
978
|
-
const required = Array.isArray(requestedSchema.required) ? new Set(requestedSchema.required.filter((entry) => typeof entry === "string")) : /* @__PURE__ */ new Set();
|
|
979
|
-
const content = {};
|
|
980
|
-
let sawApprovalField = false;
|
|
981
|
-
for (const [name, value] of Object.entries(properties)) {
|
|
982
|
-
const schema = isJsonObject(value) ? value : void 0;
|
|
983
|
-
if (!schema) continue;
|
|
984
|
-
const property = {
|
|
985
|
-
name,
|
|
986
|
-
schema,
|
|
987
|
-
required: required.has(name)
|
|
988
|
-
};
|
|
989
|
-
const next = readApprovalFieldValue(property, outcome) ?? readPersistFieldValue(property, meta, outcome) ?? readFallbackFieldValue(property, outcome);
|
|
990
|
-
if (next === void 0) {
|
|
991
|
-
if (isApprovalField(property)) sawApprovalField = true;
|
|
992
|
-
if (property.required) return;
|
|
993
|
-
continue;
|
|
994
|
-
}
|
|
995
|
-
if (isApprovalField(property)) sawApprovalField = true;
|
|
996
|
-
content[name] = next;
|
|
997
|
-
}
|
|
998
|
-
return sawApprovalField ? content : void 0;
|
|
999
|
-
}
|
|
1000
|
-
function readApprovalFieldValue(property, outcome) {
|
|
1001
|
-
if (!isApprovalField(property)) return;
|
|
1002
|
-
if (readString$3(property.schema, "type") === "boolean") return true;
|
|
1003
|
-
const options = readEnumOptions(property.schema);
|
|
1004
|
-
if (options.length === 0) return;
|
|
1005
|
-
const sessionChoice = options.find((option) => isSessionApprovalOption(option));
|
|
1006
|
-
const acceptChoice = options.find((option) => isPositiveApprovalOption(option));
|
|
1007
|
-
if (outcome === "approved-session") return sessionChoice?.value ?? acceptChoice?.value;
|
|
1008
|
-
return acceptChoice?.value ?? sessionChoice?.value;
|
|
1009
|
-
}
|
|
1010
|
-
function readPersistFieldValue(property, meta, outcome) {
|
|
1011
|
-
if (!isPersistField(property) || outcome !== "approved-session") return;
|
|
1012
|
-
const persistHints = readPersistHints(meta);
|
|
1013
|
-
const options = readEnumOptions(property.schema);
|
|
1014
|
-
if (options.length === 0) return;
|
|
1015
|
-
const preferred = choosePersistHint(persistHints);
|
|
1016
|
-
if (preferred) return options.find((option) => option.value === preferred || option.label === preferred)?.value;
|
|
1017
|
-
}
|
|
1018
|
-
function readDefaultValue(schema) {
|
|
1019
|
-
return schema.default;
|
|
1020
|
-
}
|
|
1021
|
-
function readFallbackFieldValue(property, outcome) {
|
|
1022
|
-
if (outcome === "approved-once" && isPersistField(property)) return;
|
|
1023
|
-
return readDefaultValue(property.schema);
|
|
1024
|
-
}
|
|
1025
|
-
function isApprovalField(property) {
|
|
1026
|
-
const haystack = propertyText(property).toLowerCase();
|
|
1027
|
-
return /\b(approve|approval|allow|accept|decision)\b/.test(haystack);
|
|
1028
|
-
}
|
|
1029
|
-
function isPersistField(property) {
|
|
1030
|
-
const haystack = propertyText(property).toLowerCase();
|
|
1031
|
-
return /\b(persist|session|always|scope)\b/.test(haystack);
|
|
1032
|
-
}
|
|
1033
|
-
function propertyText(property) {
|
|
1034
|
-
return [
|
|
1035
|
-
property.name,
|
|
1036
|
-
readString$3(property.schema, "title"),
|
|
1037
|
-
readString$3(property.schema, "description")
|
|
1038
|
-
].filter(Boolean).join(" ");
|
|
1039
|
-
}
|
|
1040
|
-
function readPersistHints(meta) {
|
|
1041
|
-
const raw = meta.persist;
|
|
1042
|
-
if (typeof raw === "string") return [raw];
|
|
1043
|
-
if (Array.isArray(raw)) return raw.filter((entry) => typeof entry === "string");
|
|
1044
|
-
return ["session", "always"];
|
|
1045
|
-
}
|
|
1046
|
-
function buildAcceptedMeta(meta, outcome) {
|
|
1047
|
-
if (outcome !== "approved-session") return null;
|
|
1048
|
-
const persist = choosePersistHint(readPersistHints(meta));
|
|
1049
|
-
return persist ? { persist } : null;
|
|
1050
|
-
}
|
|
1051
|
-
function choosePersistHint(persistHints) {
|
|
1052
|
-
if (persistHints.includes("always")) return "always";
|
|
1053
|
-
if (persistHints.includes("session")) return "session";
|
|
1054
|
-
}
|
|
1055
|
-
function hasNoSchemaProperties(requestedSchema) {
|
|
1056
|
-
const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
|
|
1057
|
-
return Object.keys(properties).length === 0;
|
|
1058
|
-
}
|
|
1059
|
-
function readEnumOptions(schema) {
|
|
1060
|
-
if (Array.isArray(schema.enum)) {
|
|
1061
|
-
const values = schema.enum.filter((entry) => typeof entry === "string");
|
|
1062
|
-
const labels = Array.isArray(schema.enumNames) ? schema.enumNames.filter((entry) => typeof entry === "string") : [];
|
|
1063
|
-
return values.map((value, index) => ({
|
|
1064
|
-
value,
|
|
1065
|
-
label: labels[index] ?? value
|
|
1066
|
-
}));
|
|
1067
|
-
}
|
|
1068
|
-
if (Array.isArray(schema.oneOf)) return schema.oneOf.map((entry) => {
|
|
1069
|
-
const option = isJsonObject(entry) ? entry : void 0;
|
|
1070
|
-
const value = readString$3(option, "const");
|
|
1071
|
-
if (!value) return;
|
|
1072
|
-
return {
|
|
1073
|
-
value,
|
|
1074
|
-
label: readString$3(option, "title") ?? value
|
|
1075
|
-
};
|
|
1076
|
-
}).filter((entry) => Boolean(entry));
|
|
1077
|
-
return [];
|
|
1078
|
-
}
|
|
1079
|
-
function isPositiveApprovalOption(option) {
|
|
1080
|
-
const haystack = `${option.value} ${option.label}`.toLowerCase();
|
|
1081
|
-
return /\b(allow|approve|accept|yes|continue|proceed|true)\b/.test(haystack);
|
|
1082
|
-
}
|
|
1083
|
-
function isSessionApprovalOption(option) {
|
|
1084
|
-
const haystack = `${option.value} ${option.label}`.toLowerCase();
|
|
1085
|
-
return /\b(session|always|persistent)\b/.test(haystack) && /\b(allow|approve|accept)\b/.test(haystack);
|
|
1086
|
-
}
|
|
1087
|
-
function readString$3(record, key) {
|
|
1088
|
-
const value = record?.[key];
|
|
1089
|
-
return typeof value === "string" && value.trim() ? value : void 0;
|
|
1090
|
-
}
|
|
1091
|
-
function readFirstString(record, keys) {
|
|
1092
|
-
for (const key of keys) {
|
|
1093
|
-
const value = readString$3(record, key);
|
|
1094
|
-
if (value) return value;
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
//#endregion
|
|
1098
103
|
//#region extensions/codex/src/app-server/session-history.ts
|
|
1099
104
|
function isMissingFileError(error) {
|
|
1100
105
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
@@ -1282,10 +287,11 @@ const CODEX_PROMPT_TOTAL_INPUT_KEYS = [
|
|
|
1282
287
|
const MAX_TOOL_OUTPUT_DELTA_MESSAGES_PER_ITEM = 20;
|
|
1283
288
|
const TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS = 12e3;
|
|
1284
289
|
var CodexAppServerEventProjector = class {
|
|
1285
|
-
constructor(params, threadId, turnId) {
|
|
290
|
+
constructor(params, threadId, turnId, options = {}) {
|
|
1286
291
|
this.params = params;
|
|
1287
292
|
this.threadId = threadId;
|
|
1288
293
|
this.turnId = turnId;
|
|
294
|
+
this.options = options;
|
|
1289
295
|
this.assistantTextByItem = /* @__PURE__ */ new Map();
|
|
1290
296
|
this.assistantItemOrder = [];
|
|
1291
297
|
this.reasoningTextByItem = /* @__PURE__ */ new Map();
|
|
@@ -1303,6 +309,7 @@ var CodexAppServerEventProjector = class {
|
|
|
1303
309
|
this.toolTranscriptResultIds = /* @__PURE__ */ new Set();
|
|
1304
310
|
this.nativeGeneratedMediaUrls = /* @__PURE__ */ new Set();
|
|
1305
311
|
this.diagnosticToolStartedAtByItem = /* @__PURE__ */ new Map();
|
|
312
|
+
this.afterToolCallObservedItemIds = /* @__PURE__ */ new Set();
|
|
1306
313
|
this.assistantStarted = false;
|
|
1307
314
|
this.reasoningStarted = false;
|
|
1308
315
|
this.reasoningEnded = false;
|
|
@@ -1459,6 +466,9 @@ var CodexAppServerEventProjector = class {
|
|
|
1459
466
|
this.promptError = "codex app-server attempt timed out";
|
|
1460
467
|
this.promptErrorSource = "prompt";
|
|
1461
468
|
}
|
|
469
|
+
markAborted() {
|
|
470
|
+
this.aborted = true;
|
|
471
|
+
}
|
|
1462
472
|
isCompacting() {
|
|
1463
473
|
return this.activeCompactionItemIds.size > 0;
|
|
1464
474
|
}
|
|
@@ -1708,6 +718,7 @@ var CodexAppServerEventProjector = class {
|
|
|
1708
718
|
this.recordToolMeta(item);
|
|
1709
719
|
this.recordNativeToolTranscriptCall(item);
|
|
1710
720
|
this.recordNativeToolTranscriptResult(item);
|
|
721
|
+
this.emitAfterToolCallObservation(item);
|
|
1711
722
|
this.emitToolResultSummary(item);
|
|
1712
723
|
this.emitToolResultOutput(item);
|
|
1713
724
|
}
|
|
@@ -1835,6 +846,7 @@ var CodexAppServerEventProjector = class {
|
|
|
1835
846
|
} : {}
|
|
1836
847
|
}
|
|
1837
848
|
});
|
|
849
|
+
if (params.phase === "result") this.emitAfterToolCallObservation(item);
|
|
1838
850
|
}
|
|
1839
851
|
emitDiagnosticToolExecutionEvent(params) {
|
|
1840
852
|
const base = {
|
|
@@ -1872,6 +884,37 @@ var CodexAppServerEventProjector = class {
|
|
|
1872
884
|
...terminalEvent
|
|
1873
885
|
});
|
|
1874
886
|
}
|
|
887
|
+
emitAfterToolCallObservation(item) {
|
|
888
|
+
if (!this.shouldEmitAfterToolCallObservation(item)) return;
|
|
889
|
+
const name = itemName(item);
|
|
890
|
+
if (!name) return;
|
|
891
|
+
const status = itemStatus(item);
|
|
892
|
+
if (status === "running") return;
|
|
893
|
+
this.afterToolCallObservedItemIds.add(item.id);
|
|
894
|
+
const result = itemToolResult(item).result;
|
|
895
|
+
const error = itemToolError(item, status);
|
|
896
|
+
const startedAt = typeof item.durationMs === "number" ? Date.now() - Math.max(0, item.durationMs) : void 0;
|
|
897
|
+
const hookParams = {
|
|
898
|
+
toolName: name,
|
|
899
|
+
toolCallId: item.id,
|
|
900
|
+
runId: this.params.runId,
|
|
901
|
+
agentId: this.params.agentId,
|
|
902
|
+
sessionId: this.params.sessionId,
|
|
903
|
+
sessionKey: this.params.sessionKey,
|
|
904
|
+
startArgs: itemToolArgs(item) ?? {},
|
|
905
|
+
...result !== void 0 ? { result } : {},
|
|
906
|
+
...error ? { error } : {},
|
|
907
|
+
...startedAt !== void 0 ? { startedAt } : {}
|
|
908
|
+
};
|
|
909
|
+
setImmediate(() => {
|
|
910
|
+
runAgentHarnessAfterToolCallHook(hookParams);
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
shouldEmitAfterToolCallObservation(item) {
|
|
914
|
+
if (!shouldSynthesizeToolProgressForItem(item) || this.afterToolCallObservedItemIds.has(item.id)) return false;
|
|
915
|
+
if (this.options.nativePostToolUseRelayEnabled && isNativePostToolUseRelayItem(item)) return false;
|
|
916
|
+
return true;
|
|
917
|
+
}
|
|
1875
918
|
emitToolResultSummary(item) {
|
|
1876
919
|
if (!item || !this.params.onToolResult || !this.shouldEmitToolResult()) return;
|
|
1877
920
|
const itemId = item.id;
|
|
@@ -2254,6 +1297,14 @@ function shouldSynthesizeToolProgressForItem(item) {
|
|
|
2254
1297
|
default: return false;
|
|
2255
1298
|
}
|
|
2256
1299
|
}
|
|
1300
|
+
function isNativePostToolUseRelayItem(item) {
|
|
1301
|
+
switch (item.type) {
|
|
1302
|
+
case "commandExecution":
|
|
1303
|
+
case "fileChange":
|
|
1304
|
+
case "mcpToolCall": return true;
|
|
1305
|
+
default: return false;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
2257
1308
|
function shouldSuppressChannelProgressForItem(item) {
|
|
2258
1309
|
if (shouldSynthesizeToolProgressForItem(item)) return true;
|
|
2259
1310
|
return item.type === "dynamicToolCall";
|
|
@@ -2263,6 +1314,7 @@ function itemToolArgs(item) {
|
|
|
2263
1314
|
command: item.command,
|
|
2264
1315
|
...typeof item.cwd === "string" ? { cwd: item.cwd } : {}
|
|
2265
1316
|
});
|
|
1317
|
+
if (item.type === "fileChange") return sanitizeCodexAgentEventRecord({ changes: itemFileChanges(item) });
|
|
2266
1318
|
if (item.type === "webSearch" && typeof item.query === "string") return sanitizeCodexAgentEventRecord({ query: item.query });
|
|
2267
1319
|
if (item.type === "mcpToolCall") return sanitizeCodexToolArguments(item.arguments);
|
|
2268
1320
|
}
|
|
@@ -2274,10 +1326,7 @@ function itemToolResult(item) {
|
|
|
2274
1326
|
}) };
|
|
2275
1327
|
if (item.type === "fileChange") return { result: sanitizeCodexAgentEventRecord({
|
|
2276
1328
|
status: item.status,
|
|
2277
|
-
changes: item
|
|
2278
|
-
path: change.path,
|
|
2279
|
-
kind: change.kind
|
|
2280
|
-
}))
|
|
1329
|
+
changes: itemFileChanges(item)
|
|
2281
1330
|
}) };
|
|
2282
1331
|
if (item.type === "mcpToolCall") return { result: sanitizeCodexAgentEventRecord({
|
|
2283
1332
|
status: item.status,
|
|
@@ -2288,6 +1337,17 @@ function itemToolResult(item) {
|
|
|
2288
1337
|
if (item.type === "webSearch") return { result: sanitizeCodexAgentEventRecord({ status: "completed" }) };
|
|
2289
1338
|
return {};
|
|
2290
1339
|
}
|
|
1340
|
+
function itemFileChanges(item) {
|
|
1341
|
+
return Array.isArray(item.changes) ? item.changes.map((change) => ({
|
|
1342
|
+
path: change.path,
|
|
1343
|
+
kind: change.kind
|
|
1344
|
+
})) : [];
|
|
1345
|
+
}
|
|
1346
|
+
function itemToolError(item, status) {
|
|
1347
|
+
if (status === "blocked") return "codex native tool blocked";
|
|
1348
|
+
if (status !== "failed") return;
|
|
1349
|
+
return itemOutputText(item) ?? "codex native tool failed";
|
|
1350
|
+
}
|
|
2291
1351
|
function itemMeta(item, detailMode = "explain") {
|
|
2292
1352
|
if (item.type === "commandExecution" && typeof item.command === "string") return inferToolMetaFromArgs("exec", {
|
|
2293
1353
|
command: item.command,
|
|
@@ -2794,18 +1854,13 @@ function readRequestId(record) {
|
|
|
2794
1854
|
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
2795
1855
|
}
|
|
2796
1856
|
//#endregion
|
|
2797
|
-
//#region extensions/codex/src/app-server/vision-tools.ts
|
|
2798
|
-
function filterToolsForVisionInputs(tools, params) {
|
|
2799
|
-
if (!params.modelHasVision || !params.hasInboundImages) return tools;
|
|
2800
|
-
return tools.filter((tool) => tool.name !== "image");
|
|
2801
|
-
}
|
|
2802
|
-
//#endregion
|
|
2803
1857
|
//#region extensions/codex/src/app-server/run-attempt.ts
|
|
2804
1858
|
const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 3e4;
|
|
2805
1859
|
const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
|
|
2806
1860
|
const CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 6e4;
|
|
2807
1861
|
const CODEX_APP_SERVER_STARTUP_CONNECTION_CLOSE_MAX_ATTEMPTS = 3;
|
|
2808
1862
|
const CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS = 100;
|
|
1863
|
+
const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
|
|
2809
1864
|
const CODEX_USAGE_LIMIT_RATE_LIMIT_REFRESH_TIMEOUT_MS = 5e3;
|
|
2810
1865
|
const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 6e4;
|
|
2811
1866
|
const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 6e4;
|
|
@@ -3026,7 +2081,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3026
2081
|
config: params.config,
|
|
3027
2082
|
agentId: params.agentId
|
|
3028
2083
|
});
|
|
3029
|
-
const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, sessionAgentId);
|
|
2084
|
+
const agentDir = params.agentDir ?? resolveAgentDir$1(params.config ?? {}, sessionAgentId);
|
|
3030
2085
|
const startupBinding = await readCodexAppServerBinding(params.sessionFile);
|
|
3031
2086
|
const startupAuthProfileCandidate = params.runtimePlan?.auth.forwardedAuthProfileId ?? params.authProfileId ?? startupBinding?.authProfileId;
|
|
3032
2087
|
const startupAuthProfileId = params.authProfileStore ? resolveCodexAppServerAuthProfileId({
|
|
@@ -3491,6 +2546,36 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3491
2546
|
});
|
|
3492
2547
|
lifecycleTerminalEmitted = true;
|
|
3493
2548
|
};
|
|
2549
|
+
const executionPhaseKeys = /* @__PURE__ */ new Set();
|
|
2550
|
+
const emitExecutionPhaseOnce = (key, info) => {
|
|
2551
|
+
if (executionPhaseKeys.has(key)) return;
|
|
2552
|
+
executionPhaseKeys.add(key);
|
|
2553
|
+
params.onExecutionPhase?.({
|
|
2554
|
+
provider: params.provider,
|
|
2555
|
+
model: params.modelId,
|
|
2556
|
+
backend: "codex-app-server",
|
|
2557
|
+
...info
|
|
2558
|
+
});
|
|
2559
|
+
};
|
|
2560
|
+
const reportCodexExecutionNotification = (notification) => {
|
|
2561
|
+
if (notification.method === "turn/started") {
|
|
2562
|
+
emitExecutionPhaseOnce("turn_accepted", { phase: "turn_accepted" });
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
if (notification.method === "item/agentMessage/delta") {
|
|
2566
|
+
emitExecutionPhaseOnce("assistant_output_started", { phase: "assistant_output_started" });
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
if (notification.method !== "item/started") return;
|
|
2570
|
+
const item = readCodexNotificationItem(notification.params);
|
|
2571
|
+
const tool = item ? codexExecutionToolName(item) : void 0;
|
|
2572
|
+
if (!item || !tool) return;
|
|
2573
|
+
emitExecutionPhaseOnce(`tool:${item.id}`, {
|
|
2574
|
+
phase: "tool_execution_started",
|
|
2575
|
+
tool,
|
|
2576
|
+
itemId: item.id
|
|
2577
|
+
});
|
|
2578
|
+
};
|
|
3494
2579
|
const handleNotification = async (notification) => {
|
|
3495
2580
|
userInputBridge?.handleNotification(notification);
|
|
3496
2581
|
if (!projector || !turnId) {
|
|
@@ -3498,11 +2583,16 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3498
2583
|
return;
|
|
3499
2584
|
}
|
|
3500
2585
|
const isCurrentTurnNotification = isTurnNotification(notification.params, thread.threadId, turnId);
|
|
3501
|
-
if (isCurrentTurnNotification)
|
|
2586
|
+
if (isCurrentTurnNotification) {
|
|
2587
|
+
touchTurnCompletionActivity(`notification:${notification.method}`, { details: describeNotificationActivity(notification) });
|
|
2588
|
+
reportCodexExecutionNotification(notification);
|
|
2589
|
+
}
|
|
3502
2590
|
if (isCurrentTurnNotification && notification.method === "error") if (isRetryableErrorNotification(notification.params)) disarmTurnCompletionIdleWatch();
|
|
3503
2591
|
else armTurnCompletionIdleWatch({ pinnedByTerminalError: true });
|
|
3504
2592
|
else if (turnCompletionIdleWatchArmed && !turnCompletionIdleWatchPinnedByTerminalError && notification.method !== "turn/completed" && isCurrentTurnNotification) disarmTurnCompletionIdleWatch();
|
|
3505
2593
|
const isTurnCompletion = notification.method === "turn/completed" && isCurrentTurnNotification;
|
|
2594
|
+
const isTurnAbortMarker = isCurrentTurnNotification && isCodexTurnAbortMarkerNotification(notification, { currentPromptText: promptBuild.prompt });
|
|
2595
|
+
const isTurnTerminal = isTurnCompletion || isTurnAbortMarker;
|
|
3506
2596
|
try {
|
|
3507
2597
|
await projector.handleNotification(notification);
|
|
3508
2598
|
} catch (error) {
|
|
@@ -3511,7 +2601,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3511
2601
|
error
|
|
3512
2602
|
});
|
|
3513
2603
|
} finally {
|
|
3514
|
-
if (
|
|
2604
|
+
if (isTurnTerminal) {
|
|
2605
|
+
if (isTurnAbortMarker) projector.markAborted();
|
|
3515
2606
|
if (!timedOut && !runAbortController.signal.aborted) await steeringQueue?.flushPending();
|
|
3516
2607
|
completed = true;
|
|
3517
2608
|
clearTurnCompletionIdleTimer();
|
|
@@ -3584,6 +2675,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3584
2675
|
tool: call.tool,
|
|
3585
2676
|
arguments: call.arguments
|
|
3586
2677
|
});
|
|
2678
|
+
emitExecutionPhaseOnce(`tool:${call.callId}`, {
|
|
2679
|
+
phase: "tool_execution_started",
|
|
2680
|
+
tool: call.tool,
|
|
2681
|
+
toolCallId: call.callId
|
|
2682
|
+
});
|
|
3587
2683
|
const toolMeta = inferCodexDynamicToolMeta(call, resolveCodexToolProgressDetailMode(params.toolProgressDetail));
|
|
3588
2684
|
const toolArgs = sanitizeCodexToolArguments(call.arguments);
|
|
3589
2685
|
emitCodexAppServerEvent(params, {
|
|
@@ -3658,10 +2754,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3658
2754
|
};
|
|
3659
2755
|
const turnStartFailureMessages = [...historyMessages, {
|
|
3660
2756
|
role: "user",
|
|
3661
|
-
content:
|
|
3662
|
-
|
|
3663
|
-
text: promptBuild.prompt
|
|
3664
|
-
}]
|
|
2757
|
+
content: promptBuild.prompt,
|
|
2758
|
+
timestamp: Date.now()
|
|
3665
2759
|
}];
|
|
3666
2760
|
let turn;
|
|
3667
2761
|
try {
|
|
@@ -3693,7 +2787,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3693
2787
|
timeoutMs: appServer.requestTimeoutMs,
|
|
3694
2788
|
signal: runAbortController.signal
|
|
3695
2789
|
});
|
|
3696
|
-
const turnStartErrorMessage = usageLimitError ?? formatErrorMessage(error);
|
|
2790
|
+
const turnStartErrorMessage = usageLimitError?.message ?? formatErrorMessage(error);
|
|
3697
2791
|
emitCodexAppServerEvent(params, {
|
|
3698
2792
|
stream: "codex_app_server.lifecycle",
|
|
3699
2793
|
data: {
|
|
@@ -3743,11 +2837,24 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3743
2837
|
}
|
|
3744
2838
|
});
|
|
3745
2839
|
params.abortSignal?.removeEventListener("abort", abortFromUpstream);
|
|
3746
|
-
if (usageLimitError)
|
|
2840
|
+
if (usageLimitError) {
|
|
2841
|
+
await markCodexAuthProfileBlockedFromRateLimits({
|
|
2842
|
+
params,
|
|
2843
|
+
authProfileId: startupAuthProfileId,
|
|
2844
|
+
rateLimits: usageLimitError.rateLimitsForProfile
|
|
2845
|
+
});
|
|
2846
|
+
return buildCodexTurnStartFailureResult({
|
|
2847
|
+
params,
|
|
2848
|
+
message: usageLimitError.message,
|
|
2849
|
+
messagesSnapshot: turnStartFailureMessages,
|
|
2850
|
+
systemPromptReport
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
3747
2853
|
throw error;
|
|
3748
2854
|
}
|
|
3749
2855
|
turnId = turn.turn.id;
|
|
3750
2856
|
const activeTurnId = turn.turn.id;
|
|
2857
|
+
emitExecutionPhaseOnce("turn_accepted", { phase: "turn_accepted" });
|
|
3751
2858
|
userInputBridge = createCodexUserInputBridge({
|
|
3752
2859
|
paramsForRun: params,
|
|
3753
2860
|
threadId: thread.threadId,
|
|
@@ -3760,7 +2867,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3760
2867
|
prompt: promptBuild.prompt,
|
|
3761
2868
|
imagesCount: params.images?.length ?? 0
|
|
3762
2869
|
});
|
|
3763
|
-
projector = new CodexAppServerEventProjector(params, thread.threadId, activeTurnId);
|
|
2870
|
+
projector = new CodexAppServerEventProjector(params, thread.threadId, activeTurnId, { nativePostToolUseRelayEnabled: nativeHookRelay?.allowedEvents.includes("post_tool_use") === true });
|
|
3764
2871
|
emitLifecycleStart();
|
|
3765
2872
|
const activeProjector = projector;
|
|
3766
2873
|
for (const notification of pendingNotifications.splice(0)) await enqueueNotification(notification);
|
|
@@ -3797,9 +2904,16 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3797
2904
|
runAbortController.abort("timeout");
|
|
3798
2905
|
}, Math.max(100, params.timeoutMs));
|
|
3799
2906
|
const abortListener = () => {
|
|
2907
|
+
const shouldRetireClient = timedOut;
|
|
3800
2908
|
interruptCodexTurnBestEffort(client, {
|
|
3801
2909
|
threadId: thread.threadId,
|
|
3802
|
-
turnId: activeTurnId
|
|
2910
|
+
turnId: activeTurnId,
|
|
2911
|
+
timeoutMs: shouldRetireClient ? CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS : void 0
|
|
2912
|
+
});
|
|
2913
|
+
if (shouldRetireClient) retireCodexAppServerClientAfterTimedOutTurn(client, {
|
|
2914
|
+
threadId: thread.threadId,
|
|
2915
|
+
turnId: activeTurnId,
|
|
2916
|
+
reason: String(runAbortController.signal.reason ?? "timeout")
|
|
3803
2917
|
});
|
|
3804
2918
|
resolveCompletion?.();
|
|
3805
2919
|
};
|
|
@@ -3952,6 +3066,60 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3952
3066
|
clearActiveEmbeddedRun(params.sessionId, handle, params.sessionKey);
|
|
3953
3067
|
}
|
|
3954
3068
|
}
|
|
3069
|
+
async function markCodexAuthProfileBlockedFromRateLimits(params) {
|
|
3070
|
+
const authProfileId = params.authProfileId?.trim();
|
|
3071
|
+
if (!authProfileId || !params.params.authProfileStore) return;
|
|
3072
|
+
const blockedUntil = resolveCodexUsageLimitResetAtMs(params.rateLimits);
|
|
3073
|
+
if (!blockedUntil) return;
|
|
3074
|
+
try {
|
|
3075
|
+
await markAuthProfileBlockedUntil({
|
|
3076
|
+
store: params.params.authProfileStore,
|
|
3077
|
+
profileId: authProfileId,
|
|
3078
|
+
blockedUntil,
|
|
3079
|
+
source: "codex_rate_limits",
|
|
3080
|
+
agentDir: params.params.agentDir,
|
|
3081
|
+
runId: params.params.runId,
|
|
3082
|
+
modelId: params.params.modelId
|
|
3083
|
+
});
|
|
3084
|
+
} catch (error) {
|
|
3085
|
+
embeddedAgentLog.debug("failed to mark Codex auth profile blocked from app-server limits", {
|
|
3086
|
+
authProfileId,
|
|
3087
|
+
error: formatErrorMessage(error)
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
function buildCodexTurnStartFailureResult(params) {
|
|
3092
|
+
return {
|
|
3093
|
+
aborted: false,
|
|
3094
|
+
externalAbort: false,
|
|
3095
|
+
timedOut: false,
|
|
3096
|
+
idleTimedOut: false,
|
|
3097
|
+
timedOutDuringCompaction: false,
|
|
3098
|
+
timedOutDuringToolExecution: false,
|
|
3099
|
+
promptError: params.message,
|
|
3100
|
+
promptErrorSource: "prompt",
|
|
3101
|
+
sessionIdUsed: params.params.sessionId,
|
|
3102
|
+
messagesSnapshot: params.messagesSnapshot,
|
|
3103
|
+
assistantTexts: [],
|
|
3104
|
+
toolMetas: [],
|
|
3105
|
+
lastAssistant: void 0,
|
|
3106
|
+
didSendViaMessagingTool: false,
|
|
3107
|
+
messagingToolSentTexts: [],
|
|
3108
|
+
messagingToolSentMediaUrls: [],
|
|
3109
|
+
messagingToolSentTargets: [],
|
|
3110
|
+
cloudCodeAssistFormatError: false,
|
|
3111
|
+
replayMetadata: {
|
|
3112
|
+
hadPotentialSideEffects: false,
|
|
3113
|
+
replaySafe: true
|
|
3114
|
+
},
|
|
3115
|
+
itemLifecycle: {
|
|
3116
|
+
startedCount: 0,
|
|
3117
|
+
completedCount: 0,
|
|
3118
|
+
activeCount: 0
|
|
3119
|
+
},
|
|
3120
|
+
systemPromptReport: params.systemPromptReport
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3955
3123
|
async function handleDynamicToolCallWithTimeout(params) {
|
|
3956
3124
|
if (params.signal.aborted) return failedDynamicToolResponse("OpenClaw dynamic tool call aborted before execution.");
|
|
3957
3125
|
const controller = new AbortController();
|
|
@@ -4079,8 +3247,31 @@ function buildCodexNativeHookRelayId(params) {
|
|
|
4079
3247
|
return `codex-${hash.digest("hex").slice(0, 40)}`;
|
|
4080
3248
|
}
|
|
4081
3249
|
function interruptCodexTurnBestEffort(client, params) {
|
|
4082
|
-
|
|
3250
|
+
const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
|
|
3251
|
+
const requestParams = {
|
|
3252
|
+
threadId: params.threadId,
|
|
3253
|
+
turnId: params.turnId
|
|
3254
|
+
};
|
|
3255
|
+
try {
|
|
3256
|
+
const interrupt = requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams);
|
|
3257
|
+
Promise.resolve(interrupt).catch((error) => {
|
|
3258
|
+
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
3259
|
+
});
|
|
3260
|
+
} catch (error) {
|
|
4083
3261
|
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
|
|
3265
|
+
const clearedSharedClient = clearSharedCodexAppServerClientIfCurrent(client);
|
|
3266
|
+
if (!clearedSharedClient) {
|
|
3267
|
+
const close = client.close;
|
|
3268
|
+
if (typeof close === "function") close.call(client);
|
|
3269
|
+
}
|
|
3270
|
+
embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
|
|
3271
|
+
threadId: params.threadId,
|
|
3272
|
+
turnId: params.turnId,
|
|
3273
|
+
reason: params.reason,
|
|
3274
|
+
clearedSharedClient
|
|
4084
3275
|
});
|
|
4085
3276
|
}
|
|
4086
3277
|
function resolveOpenClawCodingToolsSessionKeys(params, sandboxSessionKey) {
|
|
@@ -4093,7 +3284,7 @@ async function buildDynamicTools(input) {
|
|
|
4093
3284
|
const { params } = input;
|
|
4094
3285
|
if (params.disableTools || !supportsModelTools(params.model)) return [];
|
|
4095
3286
|
const modelHasVision = params.model.input?.includes("image") ?? false;
|
|
4096
|
-
const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, input.sessionAgentId);
|
|
3287
|
+
const agentDir = params.agentDir ?? resolveAgentDir$1(params.config ?? {}, input.sessionAgentId);
|
|
4097
3288
|
const createOpenClawCodingTools = openClawCodingToolsFactoryForTests ?? (await import("openclaw/plugin-sdk/agent-harness")).createOpenClawCodingTools;
|
|
4098
3289
|
const sessionKeys = resolveOpenClawCodingToolsSessionKeys(params, input.sandboxSessionKey);
|
|
4099
3290
|
const filteredTools = filterCodexDynamicToolsForAllowlist(filterToolsForVisionInputs(filterCodexDynamicTools(createOpenClawCodingTools({
|
|
@@ -4233,7 +3424,7 @@ function readDynamicToolCallParams(value) {
|
|
|
4233
3424
|
return readCodexDynamicToolCallParams(value);
|
|
4234
3425
|
}
|
|
4235
3426
|
async function formatCodexTurnStartUsageLimitError(params) {
|
|
4236
|
-
return
|
|
3427
|
+
return refreshCodexUsageLimitError({
|
|
4237
3428
|
client: params.client,
|
|
4238
3429
|
source: readCodexTurnStartUsageLimitErrorSource(params.error, params.pendingNotifications),
|
|
4239
3430
|
timeoutMs: params.timeoutMs,
|
|
@@ -4241,19 +3432,37 @@ async function formatCodexTurnStartUsageLimitError(params) {
|
|
|
4241
3432
|
});
|
|
4242
3433
|
}
|
|
4243
3434
|
async function refreshCodexUsageLimitErrorMessage(params) {
|
|
3435
|
+
return (await refreshCodexUsageLimitError({
|
|
3436
|
+
client: params.client,
|
|
3437
|
+
source: params.source,
|
|
3438
|
+
timeoutMs: params.timeoutMs,
|
|
3439
|
+
signal: params.signal
|
|
3440
|
+
}))?.message;
|
|
3441
|
+
}
|
|
3442
|
+
async function refreshCodexUsageLimitError(params) {
|
|
4244
3443
|
const initialMessage = formatCodexUsageLimitErrorMessage(params.source);
|
|
4245
|
-
if (!shouldRefreshCodexRateLimitsForUsageLimitMessage(initialMessage)) return initialMessage
|
|
3444
|
+
if (!shouldRefreshCodexRateLimitsForUsageLimitMessage(initialMessage)) return initialMessage ? {
|
|
3445
|
+
message: initialMessage,
|
|
3446
|
+
...params.source.rateLimitsTrustedForProfile ? { rateLimitsForProfile: params.source.rateLimits } : {}
|
|
3447
|
+
} : void 0;
|
|
4246
3448
|
const rateLimits = await readCodexRateLimitsFromAppServerForUsageLimitError({
|
|
4247
3449
|
client: params.client,
|
|
4248
3450
|
timeoutMs: params.timeoutMs,
|
|
4249
3451
|
signal: params.signal
|
|
4250
3452
|
});
|
|
4251
|
-
if (!rateLimits) return initialMessage
|
|
4252
|
-
|
|
3453
|
+
if (!rateLimits) return initialMessage ? {
|
|
3454
|
+
message: initialMessage,
|
|
3455
|
+
...params.source.rateLimitsTrustedForProfile ? { rateLimitsForProfile: params.source.rateLimits } : {}
|
|
3456
|
+
} : void 0;
|
|
3457
|
+
const message = formatCodexUsageLimitErrorMessage({
|
|
4253
3458
|
message: params.source.message,
|
|
4254
3459
|
codexErrorInfo: params.source.codexErrorInfo,
|
|
4255
3460
|
rateLimits
|
|
4256
3461
|
}) ?? initialMessage;
|
|
3462
|
+
return message ? {
|
|
3463
|
+
message,
|
|
3464
|
+
rateLimitsForProfile: rateLimits
|
|
3465
|
+
} : void 0;
|
|
4257
3466
|
}
|
|
4258
3467
|
async function readCodexRateLimitsFromAppServerForUsageLimitError(params) {
|
|
4259
3468
|
if (params.signal?.aborted) return;
|
|
@@ -4275,11 +3484,14 @@ function resolveCodexUsageLimitRateLimitRefreshTimeoutMs(timeoutMs) {
|
|
|
4275
3484
|
}
|
|
4276
3485
|
function readCodexTurnStartUsageLimitErrorSource(error, pendingNotifications) {
|
|
4277
3486
|
const notificationError = readLatestCodexErrorNotification(pendingNotifications);
|
|
3487
|
+
const notificationRateLimits = readLatestRateLimitNotificationPayload(pendingNotifications);
|
|
4278
3488
|
const errorPayload = readCodexErrorPayload(error);
|
|
3489
|
+
const rateLimits = notificationRateLimits ?? errorPayload.rateLimits ?? readRecentCodexRateLimits();
|
|
4279
3490
|
return {
|
|
4280
3491
|
message: notificationError?.message ?? errorPayload.message ?? formatErrorMessage(error),
|
|
4281
3492
|
codexErrorInfo: notificationError?.codexErrorInfo ?? errorPayload.codexErrorInfo,
|
|
4282
|
-
rateLimits
|
|
3493
|
+
rateLimits,
|
|
3494
|
+
rateLimitsTrustedForProfile: notificationRateLimits !== void 0 || errorPayload.rateLimits !== void 0
|
|
4283
3495
|
};
|
|
4284
3496
|
}
|
|
4285
3497
|
function readLatestRateLimitNotificationPayload(notifications) {
|
|
@@ -4309,10 +3521,12 @@ function readCodexErrorPayload(error) {
|
|
|
4309
3521
|
const data = error.data;
|
|
4310
3522
|
if (!isJsonObject(data)) return { message };
|
|
4311
3523
|
const nestedError = isJsonObject(data.error) ? data.error : data;
|
|
3524
|
+
const rateLimits = nestedError.rateLimits ?? data.rateLimits;
|
|
3525
|
+
if (rateLimits !== void 0) rememberCodexRateLimits(rateLimits);
|
|
4312
3526
|
return {
|
|
4313
3527
|
message: readString(nestedError, "message") ?? message,
|
|
4314
3528
|
codexErrorInfo: nestedError.codexErrorInfo,
|
|
4315
|
-
rateLimits
|
|
3529
|
+
rateLimits
|
|
4316
3530
|
};
|
|
4317
3531
|
}
|
|
4318
3532
|
function describeNotificationActivity(notification) {
|
|
@@ -4356,6 +3570,35 @@ function readNestedTurnId(record) {
|
|
|
4356
3570
|
const turn = record.turn;
|
|
4357
3571
|
return isJsonObject(turn) ? readString(turn, "id") : void 0;
|
|
4358
3572
|
}
|
|
3573
|
+
const CODEX_TURN_ABORT_MARKER_START = "<turn_aborted>";
|
|
3574
|
+
const CODEX_TURN_ABORT_MARKER_END = "</turn_aborted>";
|
|
3575
|
+
const CODEX_INTERRUPTED_USER_GUIDANCE = "The user interrupted the previous turn on purpose. Any running unified exec processes may still be running in the background. If any tools/commands were aborted, they may have partially executed.";
|
|
3576
|
+
const CODEX_INTERRUPTED_DEVELOPER_GUIDANCE = "The previous turn was interrupted on purpose. Any running unified exec processes may still be running in the background. If any tools/commands were aborted, they may have partially executed.";
|
|
3577
|
+
function isCodexTurnAbortMarkerNotification(notification, options = {}) {
|
|
3578
|
+
if (notification.method !== "rawResponseItem/completed" || !isJsonObject(notification.params)) return false;
|
|
3579
|
+
const item = notification.params.item;
|
|
3580
|
+
const role = isJsonObject(item) ? readString(item, "role") : void 0;
|
|
3581
|
+
if (!isJsonObject(item) || role !== "user" && role !== "developer") return false;
|
|
3582
|
+
const text = extractRawResponseItemText(item).trim();
|
|
3583
|
+
if (role === "user" && text === options.currentPromptText?.trim()) return false;
|
|
3584
|
+
const markerBody = readCodexTurnAbortMarkerBody(text);
|
|
3585
|
+
return markerBody === CODEX_INTERRUPTED_USER_GUIDANCE || markerBody === CODEX_INTERRUPTED_DEVELOPER_GUIDANCE;
|
|
3586
|
+
}
|
|
3587
|
+
function readCodexTurnAbortMarkerBody(text) {
|
|
3588
|
+
if (!text.startsWith(CODEX_TURN_ABORT_MARKER_START) || !text.endsWith(CODEX_TURN_ABORT_MARKER_END)) return;
|
|
3589
|
+
return text.slice(14, -15).trim();
|
|
3590
|
+
}
|
|
3591
|
+
function extractRawResponseItemText(item) {
|
|
3592
|
+
const content = item.content;
|
|
3593
|
+
if (!Array.isArray(content)) return "";
|
|
3594
|
+
return content.flatMap((entry) => {
|
|
3595
|
+
if (!isJsonObject(entry)) return [];
|
|
3596
|
+
const type = readString(entry, "type");
|
|
3597
|
+
if (type !== "input_text" && type !== "text") return [];
|
|
3598
|
+
const text = readString(entry, "text");
|
|
3599
|
+
return text ? [text] : [];
|
|
3600
|
+
}).join("");
|
|
3601
|
+
}
|
|
4359
3602
|
function readString(record, key) {
|
|
4360
3603
|
const value = record[key];
|
|
4361
3604
|
return typeof value === "string" ? value : void 0;
|
|
@@ -4554,6 +3797,21 @@ async function mirrorTranscriptBestEffort(params) {
|
|
|
4554
3797
|
function isNonEmptyString(value) {
|
|
4555
3798
|
return typeof value === "string" && value.length > 0;
|
|
4556
3799
|
}
|
|
3800
|
+
function readCodexNotificationItem(params) {
|
|
3801
|
+
if (!isJsonObject(params) || !isJsonObject(params.item)) return;
|
|
3802
|
+
const item = params.item;
|
|
3803
|
+
return typeof item.id === "string" && typeof item.type === "string" ? item : void 0;
|
|
3804
|
+
}
|
|
3805
|
+
function codexExecutionToolName(item) {
|
|
3806
|
+
if (item.type === "dynamicToolCall" && typeof item.tool === "string") return item.tool;
|
|
3807
|
+
if (item.type === "mcpToolCall" && typeof item.tool === "string") {
|
|
3808
|
+
const server = typeof item.server === "string" && item.server ? item.server : void 0;
|
|
3809
|
+
return server ? `${server}.${item.tool}` : item.tool;
|
|
3810
|
+
}
|
|
3811
|
+
if (item.type === "commandExecution") return "bash";
|
|
3812
|
+
if (item.type === "fileChange") return "apply_patch";
|
|
3813
|
+
if (item.type === "webSearch") return "web_search";
|
|
3814
|
+
}
|
|
4557
3815
|
function joinPresentSections(...sections) {
|
|
4558
3816
|
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
4559
3817
|
}
|