@openclaw/codex 2026.5.10-beta.2 → 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-CTzy3Y6M.js → client-CpksBQ9l.js} +1 -1
- package/dist/{client-factory-BJL_efz4.js → client-factory-D2P0KD6r.js} +1 -1
- package/dist/{request-DC1Dz3iZ.js → command-formatters-Ttwc_kgX.js} +192 -57
- package/dist/{command-handlers-BLZCm3fD.js → command-handlers-iLF1EI-b.js} +306 -15
- package/dist/{compact-4FVe6NwI.js → compact-BJ1UYEyA.js} +3 -3
- package/dist/{rate-limit-cache-DbZvmrAD.js → computer-use-CVLaKaW3.js} +3 -26
- package/dist/{config-CT01BBDc.js → config-C7xdbzrp.js} +1 -1
- package/dist/{conversation-binding-FqeYliIk.js → conversation-binding-D4XZ-tvV.js} +5 -4
- package/dist/harness.js +8 -4
- package/dist/index.js +11 -6
- package/dist/media-understanding-provider.js +142 -41
- package/dist/{models-H-PTi3W0.js → models-Bg-Qf5s-.js} +2 -2
- package/dist/{plugin-activation-CweAZa7r.js → plugin-activation-C0soz2YD.js} +1 -1
- package/dist/{protocol-validators-CeCyJaWj.js → protocol-validators-CSY0BFBo.js} +4 -1
- package/dist/provider.js +2 -2
- package/dist/rate-limit-cache-dvhq-4pi.js +24 -0
- package/dist/request-BCAfJvSg.js +41 -0
- package/dist/{run-attempt-CefRS5_d.js → run-attempt-DZvighJE.js} +548 -1053
- package/dist/{session-binding-B44KIZM2.js → session-binding-UFKjHkKJ.js} +13 -4
- package/dist/{shared-client-p-TvEiNL.js → shared-client-K13b0L1X.js} +78 -13
- package/dist/side-question-Dh_g53P9.js +548 -0
- package/dist/test-api.js +2 -2
- package/dist/{thread-lifecycle-CGc-O_5d.js → thread-lifecycle-i42nWiSS.js} +18 -11
- package/dist/vision-tools-Bo0P6gwM.js +1008 -0
- package/package.json +7 -6
|
@@ -1,560 +1,28 @@
|
|
|
1
|
-
import { l as resolveCodexPluginsPolicy, o as readCodexPluginConfig, s as resolveCodexAppServerRuntimeOptions, u as withMcpElicitationsApprovalPolicy } from "./config-
|
|
2
|
-
import {
|
|
1
|
+
import { l as resolveCodexPluginsPolicy, o as readCodexPluginConfig, s as resolveCodexAppServerRuntimeOptions, u as withMcpElicitationsApprovalPolicy } from "./config-C7xdbzrp.js";
|
|
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
|
-
import { i as isCodexAppServerConnectionClosedError, r as isCodexAppServerApprovalRequest } from "./client-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { i as
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { n as
|
|
4
|
+
import { i as isCodexAppServerConnectionClosedError, r as isCodexAppServerApprovalRequest } from "./client-CpksBQ9l.js";
|
|
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 "@
|
|
22
|
-
import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/
|
|
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
|
|
24
|
+
import { buildSessionContext, migrateSessionEntries, parseSessionEntries } from "@earendil-works/pi-coding-agent";
|
|
25
|
+
import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
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();
|
|
@@ -1301,6 +307,9 @@ var CodexAppServerEventProjector = class {
|
|
|
1301
307
|
this.toolTranscriptMessages = [];
|
|
1302
308
|
this.toolTranscriptCallIds = /* @__PURE__ */ new Set();
|
|
1303
309
|
this.toolTranscriptResultIds = /* @__PURE__ */ new Set();
|
|
310
|
+
this.nativeGeneratedMediaUrls = /* @__PURE__ */ new Set();
|
|
311
|
+
this.diagnosticToolStartedAtByItem = /* @__PURE__ */ new Map();
|
|
312
|
+
this.afterToolCallObservedItemIds = /* @__PURE__ */ new Set();
|
|
1304
313
|
this.assistantStarted = false;
|
|
1305
314
|
this.reasoningStarted = false;
|
|
1306
315
|
this.reasoningEnded = false;
|
|
@@ -1418,7 +427,7 @@ var CodexAppServerEventProjector = class {
|
|
|
1418
427
|
messagingToolSentMediaUrls: toolTelemetry.messagingToolSentMediaUrls,
|
|
1419
428
|
messagingToolSentTargets: toolTelemetry.messagingToolSentTargets,
|
|
1420
429
|
heartbeatToolResponse: toolTelemetry.heartbeatToolResponse,
|
|
1421
|
-
toolMediaUrls: toolTelemetry
|
|
430
|
+
toolMediaUrls: this.buildToolMediaUrls(toolTelemetry),
|
|
1422
431
|
toolAudioAsVoice: toolTelemetry.toolAudioAsVoice,
|
|
1423
432
|
successfulCronAdds: toolTelemetry.successfulCronAdds,
|
|
1424
433
|
cloudCodeAssistFormatError: false,
|
|
@@ -1457,6 +466,9 @@ var CodexAppServerEventProjector = class {
|
|
|
1457
466
|
this.promptError = "codex app-server attempt timed out";
|
|
1458
467
|
this.promptErrorSource = "prompt";
|
|
1459
468
|
}
|
|
469
|
+
markAborted() {
|
|
470
|
+
this.aborted = true;
|
|
471
|
+
}
|
|
1460
472
|
isCompacting() {
|
|
1461
473
|
return this.activeCompactionItemIds.size > 0;
|
|
1462
474
|
}
|
|
@@ -1565,6 +577,7 @@ var CodexAppServerEventProjector = class {
|
|
|
1565
577
|
this.rememberAssistantItem(item.id);
|
|
1566
578
|
this.assistantTextByItem.set(item.id, item.text);
|
|
1567
579
|
}
|
|
580
|
+
this.recordNativeGeneratedMedia(item);
|
|
1568
581
|
if (item?.type === "plan" && typeof item.text === "string" && item.text) {
|
|
1569
582
|
this.planTextByItem.set(item.id, item.text);
|
|
1570
583
|
this.emitPlanUpdate({
|
|
@@ -1694,6 +707,7 @@ var CodexAppServerEventProjector = class {
|
|
|
1694
707
|
this.rememberAssistantItem(item.id);
|
|
1695
708
|
this.assistantTextByItem.set(item.id, item.text);
|
|
1696
709
|
}
|
|
710
|
+
this.recordNativeGeneratedMedia(item);
|
|
1697
711
|
if (item.type === "plan" && typeof item.text === "string" && item.text) {
|
|
1698
712
|
this.planTextByItem.set(item.id, item.text);
|
|
1699
713
|
this.emitPlanUpdate({
|
|
@@ -1704,6 +718,7 @@ var CodexAppServerEventProjector = class {
|
|
|
1704
718
|
this.recordToolMeta(item);
|
|
1705
719
|
this.recordNativeToolTranscriptCall(item);
|
|
1706
720
|
this.recordNativeToolTranscriptResult(item);
|
|
721
|
+
this.emitAfterToolCallObservation(item);
|
|
1707
722
|
this.emitToolResultSummary(item);
|
|
1708
723
|
this.emitToolResultOutput(item);
|
|
1709
724
|
}
|
|
@@ -1752,6 +767,16 @@ var CodexAppServerEventProjector = class {
|
|
|
1752
767
|
this.rememberAssistantItem(itemId);
|
|
1753
768
|
this.assistantTextByItem.set(itemId, text);
|
|
1754
769
|
}
|
|
770
|
+
recordNativeGeneratedMedia(item) {
|
|
771
|
+
if (item?.type !== "imageGeneration") return;
|
|
772
|
+
const savedPath = readItemString(item, "savedPath")?.trim();
|
|
773
|
+
if (savedPath) this.nativeGeneratedMediaUrls.add(savedPath);
|
|
774
|
+
}
|
|
775
|
+
buildToolMediaUrls(toolTelemetry) {
|
|
776
|
+
const mediaUrls = new Set(toolTelemetry.toolMediaUrls?.map((url) => url.trim()).filter(Boolean) ?? []);
|
|
777
|
+
if ((toolTelemetry.messagingToolSentMediaUrls?.length ?? 0) === 0) for (const mediaUrl of this.nativeGeneratedMediaUrls) mediaUrls.add(mediaUrl);
|
|
778
|
+
return mediaUrls.size > 0 ? [...mediaUrls] : toolTelemetry.toolMediaUrls;
|
|
779
|
+
}
|
|
1755
780
|
async maybeEndReasoning() {
|
|
1756
781
|
if (!this.reasoningStarted || this.reasoningEnded) return;
|
|
1757
782
|
this.reasoningEnded = true;
|
|
@@ -1799,6 +824,12 @@ var CodexAppServerEventProjector = class {
|
|
|
1799
824
|
const meta = itemMeta(item, this.toolProgressDetailMode());
|
|
1800
825
|
const args = params.phase === "start" ? itemToolArgs(item) : void 0;
|
|
1801
826
|
const status = params.phase === "result" ? itemStatus(item) : "running";
|
|
827
|
+
this.emitDiagnosticToolExecutionEvent({
|
|
828
|
+
phase: params.phase,
|
|
829
|
+
item,
|
|
830
|
+
name,
|
|
831
|
+
status
|
|
832
|
+
});
|
|
1802
833
|
this.emitAgentEvent({
|
|
1803
834
|
stream: "tool",
|
|
1804
835
|
data: {
|
|
@@ -1815,6 +846,74 @@ var CodexAppServerEventProjector = class {
|
|
|
1815
846
|
} : {}
|
|
1816
847
|
}
|
|
1817
848
|
});
|
|
849
|
+
if (params.phase === "result") this.emitAfterToolCallObservation(item);
|
|
850
|
+
}
|
|
851
|
+
emitDiagnosticToolExecutionEvent(params) {
|
|
852
|
+
const base = {
|
|
853
|
+
runId: this.params.runId,
|
|
854
|
+
sessionId: this.params.sessionId,
|
|
855
|
+
sessionKey: this.params.sessionKey,
|
|
856
|
+
toolName: params.name,
|
|
857
|
+
toolCallId: params.item.id
|
|
858
|
+
};
|
|
859
|
+
if (params.phase === "start") {
|
|
860
|
+
this.diagnosticToolStartedAtByItem.set(params.item.id, Date.now());
|
|
861
|
+
emitTrustedDiagnosticEvent({
|
|
862
|
+
type: "tool.execution.started",
|
|
863
|
+
...base
|
|
864
|
+
});
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
const startedAt = this.diagnosticToolStartedAtByItem.get(params.item.id);
|
|
868
|
+
this.diagnosticToolStartedAtByItem.delete(params.item.id);
|
|
869
|
+
const durationMs = (typeof params.item.durationMs === "number" ? params.item.durationMs : void 0) ?? (startedAt === void 0 ? 0 : Math.max(0, Date.now() - startedAt));
|
|
870
|
+
const terminalEvent = params.status === "blocked" ? {
|
|
871
|
+
type: "tool.execution.blocked",
|
|
872
|
+
reason: "codex_native_tool_blocked",
|
|
873
|
+
deniedReason: "codex_native_tool_blocked"
|
|
874
|
+
} : params.status === "failed" ? {
|
|
875
|
+
type: "tool.execution.error",
|
|
876
|
+
durationMs,
|
|
877
|
+
errorCategory: "codex_native_tool_error"
|
|
878
|
+
} : {
|
|
879
|
+
type: "tool.execution.completed",
|
|
880
|
+
durationMs
|
|
881
|
+
};
|
|
882
|
+
emitTrustedDiagnosticEvent({
|
|
883
|
+
...base,
|
|
884
|
+
...terminalEvent
|
|
885
|
+
});
|
|
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;
|
|
1818
917
|
}
|
|
1819
918
|
emitToolResultSummary(item) {
|
|
1820
919
|
if (!item || !this.params.onToolResult || !this.shouldEmitToolResult()) return;
|
|
@@ -1986,13 +1085,15 @@ var CodexAppServerEventProjector = class {
|
|
|
1986
1085
|
};
|
|
1987
1086
|
}
|
|
1988
1087
|
createToolCallMessage(params) {
|
|
1088
|
+
const args = normalizeToolTranscriptArguments(params.arguments);
|
|
1989
1089
|
return {
|
|
1990
1090
|
role: "assistant",
|
|
1991
1091
|
content: [{
|
|
1992
1092
|
type: "toolCall",
|
|
1993
1093
|
id: params.id,
|
|
1994
1094
|
name: params.name,
|
|
1995
|
-
arguments:
|
|
1095
|
+
arguments: args,
|
|
1096
|
+
input: args
|
|
1996
1097
|
}],
|
|
1997
1098
|
api: this.params.model.api ?? "openai-codex-responses",
|
|
1998
1099
|
provider: this.params.provider,
|
|
@@ -2011,6 +1112,9 @@ var CodexAppServerEventProjector = class {
|
|
|
2011
1112
|
isError: params.isError,
|
|
2012
1113
|
content: [{
|
|
2013
1114
|
type: "toolResult",
|
|
1115
|
+
id: params.id,
|
|
1116
|
+
name: params.name,
|
|
1117
|
+
toolName: params.name,
|
|
2014
1118
|
toolCallId: params.id,
|
|
2015
1119
|
toolUseId: params.id,
|
|
2016
1120
|
tool_use_id: params.id,
|
|
@@ -2193,6 +1297,14 @@ function shouldSynthesizeToolProgressForItem(item) {
|
|
|
2193
1297
|
default: return false;
|
|
2194
1298
|
}
|
|
2195
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
|
+
}
|
|
2196
1308
|
function shouldSuppressChannelProgressForItem(item) {
|
|
2197
1309
|
if (shouldSynthesizeToolProgressForItem(item)) return true;
|
|
2198
1310
|
return item.type === "dynamicToolCall";
|
|
@@ -2202,6 +1314,7 @@ function itemToolArgs(item) {
|
|
|
2202
1314
|
command: item.command,
|
|
2203
1315
|
...typeof item.cwd === "string" ? { cwd: item.cwd } : {}
|
|
2204
1316
|
});
|
|
1317
|
+
if (item.type === "fileChange") return sanitizeCodexAgentEventRecord({ changes: itemFileChanges(item) });
|
|
2205
1318
|
if (item.type === "webSearch" && typeof item.query === "string") return sanitizeCodexAgentEventRecord({ query: item.query });
|
|
2206
1319
|
if (item.type === "mcpToolCall") return sanitizeCodexToolArguments(item.arguments);
|
|
2207
1320
|
}
|
|
@@ -2213,10 +1326,7 @@ function itemToolResult(item) {
|
|
|
2213
1326
|
}) };
|
|
2214
1327
|
if (item.type === "fileChange") return { result: sanitizeCodexAgentEventRecord({
|
|
2215
1328
|
status: item.status,
|
|
2216
|
-
changes: item
|
|
2217
|
-
path: change.path,
|
|
2218
|
-
kind: change.kind
|
|
2219
|
-
}))
|
|
1329
|
+
changes: itemFileChanges(item)
|
|
2220
1330
|
}) };
|
|
2221
1331
|
if (item.type === "mcpToolCall") return { result: sanitizeCodexAgentEventRecord({
|
|
2222
1332
|
status: item.status,
|
|
@@ -2227,6 +1337,17 @@ function itemToolResult(item) {
|
|
|
2227
1337
|
if (item.type === "webSearch") return { result: sanitizeCodexAgentEventRecord({ status: "completed" }) };
|
|
2228
1338
|
return {};
|
|
2229
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
|
+
}
|
|
2230
1351
|
function itemMeta(item, detailMode = "explain") {
|
|
2231
1352
|
if (item.type === "commandExecution" && typeof item.command === "string") return inferToolMetaFromArgs("exec", {
|
|
2232
1353
|
command: item.command,
|
|
@@ -2733,18 +1854,14 @@ function readRequestId(record) {
|
|
|
2733
1854
|
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
2734
1855
|
}
|
|
2735
1856
|
//#endregion
|
|
2736
|
-
//#region extensions/codex/src/app-server/vision-tools.ts
|
|
2737
|
-
function filterToolsForVisionInputs(tools, params) {
|
|
2738
|
-
if (!params.modelHasVision || !params.hasInboundImages) return tools;
|
|
2739
|
-
return tools.filter((tool) => tool.name !== "image");
|
|
2740
|
-
}
|
|
2741
|
-
//#endregion
|
|
2742
1857
|
//#region extensions/codex/src/app-server/run-attempt.ts
|
|
2743
1858
|
const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 3e4;
|
|
2744
1859
|
const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
|
|
2745
1860
|
const CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 6e4;
|
|
2746
1861
|
const CODEX_APP_SERVER_STARTUP_CONNECTION_CLOSE_MAX_ATTEMPTS = 3;
|
|
2747
1862
|
const CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS = 100;
|
|
1863
|
+
const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
|
|
1864
|
+
const CODEX_USAGE_LIMIT_RATE_LIMIT_REFRESH_TIMEOUT_MS = 5e3;
|
|
2748
1865
|
const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 6e4;
|
|
2749
1866
|
const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 6e4;
|
|
2750
1867
|
const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 30 * 6e4;
|
|
@@ -2964,7 +2081,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
2964
2081
|
config: params.config,
|
|
2965
2082
|
agentId: params.agentId
|
|
2966
2083
|
});
|
|
2967
|
-
const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, sessionAgentId);
|
|
2084
|
+
const agentDir = params.agentDir ?? resolveAgentDir$1(params.config ?? {}, sessionAgentId);
|
|
2968
2085
|
const startupBinding = await readCodexAppServerBinding(params.sessionFile);
|
|
2969
2086
|
const startupAuthProfileCandidate = params.runtimePlan?.auth.forwardedAuthProfileId ?? params.authProfileId ?? startupBinding?.authProfileId;
|
|
2970
2087
|
const startupAuthProfileId = params.authProfileStore ? resolveCodexAppServerAuthProfileId({
|
|
@@ -3047,13 +2164,14 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3047
2164
|
historyMessages = await readMirroredSessionHistoryMessages(params.sessionFile) ?? historyMessages;
|
|
3048
2165
|
}
|
|
3049
2166
|
const baseDeveloperInstructions = buildDeveloperInstructions(params);
|
|
3050
|
-
const
|
|
2167
|
+
const workspaceBootstrapContext = await buildCodexWorkspaceBootstrapContext({
|
|
3051
2168
|
params,
|
|
3052
2169
|
resolvedWorkspace,
|
|
3053
2170
|
effectiveWorkspace,
|
|
3054
2171
|
sessionKey: sandboxSessionKey,
|
|
3055
2172
|
sessionAgentId
|
|
3056
2173
|
});
|
|
2174
|
+
const workspaceBootstrapInstructions = workspaceBootstrapContext.instructions;
|
|
3057
2175
|
let promptText = params.prompt;
|
|
3058
2176
|
let developerInstructions = joinPresentSections(baseDeveloperInstructions, workspaceBootstrapInstructions);
|
|
3059
2177
|
let prePromptMessageCount = historyMessages.length;
|
|
@@ -3095,12 +2213,21 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3095
2213
|
promptText = projection.promptText;
|
|
3096
2214
|
prePromptMessageCount = projection.prePromptMessageCount;
|
|
3097
2215
|
}
|
|
2216
|
+
promptText = prependCurrentTurnContext(promptText, params.currentTurnContext);
|
|
3098
2217
|
const promptBuild = await resolveAgentHarnessBeforePromptBuildResult({
|
|
3099
2218
|
prompt: promptText,
|
|
3100
2219
|
developerInstructions,
|
|
3101
2220
|
messages: historyMessages,
|
|
3102
2221
|
ctx: hookContext
|
|
3103
2222
|
});
|
|
2223
|
+
const systemPromptReport = buildCodexSystemPromptReport({
|
|
2224
|
+
attempt: params,
|
|
2225
|
+
sessionKey: sandboxSessionKey,
|
|
2226
|
+
workspaceDir: effectiveWorkspace,
|
|
2227
|
+
developerInstructions: promptBuild.developerInstructions,
|
|
2228
|
+
workspaceBootstrapContext,
|
|
2229
|
+
tools: toolBridge.specs
|
|
2230
|
+
});
|
|
3104
2231
|
const trajectoryRecorder = createCodexTrajectoryRecorder({
|
|
3105
2232
|
attempt: params,
|
|
3106
2233
|
cwd: effectiveWorkspace,
|
|
@@ -3419,6 +2546,36 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3419
2546
|
});
|
|
3420
2547
|
lifecycleTerminalEmitted = true;
|
|
3421
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
|
+
};
|
|
3422
2579
|
const handleNotification = async (notification) => {
|
|
3423
2580
|
userInputBridge?.handleNotification(notification);
|
|
3424
2581
|
if (!projector || !turnId) {
|
|
@@ -3426,11 +2583,16 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3426
2583
|
return;
|
|
3427
2584
|
}
|
|
3428
2585
|
const isCurrentTurnNotification = isTurnNotification(notification.params, thread.threadId, turnId);
|
|
3429
|
-
if (isCurrentTurnNotification)
|
|
2586
|
+
if (isCurrentTurnNotification) {
|
|
2587
|
+
touchTurnCompletionActivity(`notification:${notification.method}`, { details: describeNotificationActivity(notification) });
|
|
2588
|
+
reportCodexExecutionNotification(notification);
|
|
2589
|
+
}
|
|
3430
2590
|
if (isCurrentTurnNotification && notification.method === "error") if (isRetryableErrorNotification(notification.params)) disarmTurnCompletionIdleWatch();
|
|
3431
2591
|
else armTurnCompletionIdleWatch({ pinnedByTerminalError: true });
|
|
3432
2592
|
else if (turnCompletionIdleWatchArmed && !turnCompletionIdleWatchPinnedByTerminalError && notification.method !== "turn/completed" && isCurrentTurnNotification) disarmTurnCompletionIdleWatch();
|
|
3433
2593
|
const isTurnCompletion = notification.method === "turn/completed" && isCurrentTurnNotification;
|
|
2594
|
+
const isTurnAbortMarker = isCurrentTurnNotification && isCodexTurnAbortMarkerNotification(notification, { currentPromptText: promptBuild.prompt });
|
|
2595
|
+
const isTurnTerminal = isTurnCompletion || isTurnAbortMarker;
|
|
3434
2596
|
try {
|
|
3435
2597
|
await projector.handleNotification(notification);
|
|
3436
2598
|
} catch (error) {
|
|
@@ -3439,7 +2601,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3439
2601
|
error
|
|
3440
2602
|
});
|
|
3441
2603
|
} finally {
|
|
3442
|
-
if (
|
|
2604
|
+
if (isTurnTerminal) {
|
|
2605
|
+
if (isTurnAbortMarker) projector.markAborted();
|
|
3443
2606
|
if (!timedOut && !runAbortController.signal.aborted) await steeringQueue?.flushPending();
|
|
3444
2607
|
completed = true;
|
|
3445
2608
|
clearTurnCompletionIdleTimer();
|
|
@@ -3512,6 +2675,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3512
2675
|
tool: call.tool,
|
|
3513
2676
|
arguments: call.arguments
|
|
3514
2677
|
});
|
|
2678
|
+
emitExecutionPhaseOnce(`tool:${call.callId}`, {
|
|
2679
|
+
phase: "tool_execution_started",
|
|
2680
|
+
tool: call.tool,
|
|
2681
|
+
toolCallId: call.callId
|
|
2682
|
+
});
|
|
3515
2683
|
const toolMeta = inferCodexDynamicToolMeta(call, resolveCodexToolProgressDetailMode(params.toolProgressDetail));
|
|
3516
2684
|
const toolArgs = sanitizeCodexToolArguments(call.arguments);
|
|
3517
2685
|
emitCodexAppServerEvent(params, {
|
|
@@ -3586,10 +2754,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3586
2754
|
};
|
|
3587
2755
|
const turnStartFailureMessages = [...historyMessages, {
|
|
3588
2756
|
role: "user",
|
|
3589
|
-
content:
|
|
3590
|
-
|
|
3591
|
-
text: promptBuild.prompt
|
|
3592
|
-
}]
|
|
2757
|
+
content: promptBuild.prompt,
|
|
2758
|
+
timestamp: Date.now()
|
|
3593
2759
|
}];
|
|
3594
2760
|
let turn;
|
|
3595
2761
|
try {
|
|
@@ -3614,8 +2780,14 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3614
2780
|
signal: runAbortController.signal
|
|
3615
2781
|
}));
|
|
3616
2782
|
} catch (error) {
|
|
3617
|
-
const usageLimitError = formatCodexTurnStartUsageLimitError(
|
|
3618
|
-
|
|
2783
|
+
const usageLimitError = await formatCodexTurnStartUsageLimitError({
|
|
2784
|
+
client,
|
|
2785
|
+
error,
|
|
2786
|
+
pendingNotifications,
|
|
2787
|
+
timeoutMs: appServer.requestTimeoutMs,
|
|
2788
|
+
signal: runAbortController.signal
|
|
2789
|
+
});
|
|
2790
|
+
const turnStartErrorMessage = usageLimitError?.message ?? formatErrorMessage(error);
|
|
3619
2791
|
emitCodexAppServerEvent(params, {
|
|
3620
2792
|
stream: "codex_app_server.lifecycle",
|
|
3621
2793
|
data: {
|
|
@@ -3665,11 +2837,24 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3665
2837
|
}
|
|
3666
2838
|
});
|
|
3667
2839
|
params.abortSignal?.removeEventListener("abort", abortFromUpstream);
|
|
3668
|
-
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
|
+
}
|
|
3669
2853
|
throw error;
|
|
3670
2854
|
}
|
|
3671
2855
|
turnId = turn.turn.id;
|
|
3672
2856
|
const activeTurnId = turn.turn.id;
|
|
2857
|
+
emitExecutionPhaseOnce("turn_accepted", { phase: "turn_accepted" });
|
|
3673
2858
|
userInputBridge = createCodexUserInputBridge({
|
|
3674
2859
|
paramsForRun: params,
|
|
3675
2860
|
threadId: thread.threadId,
|
|
@@ -3682,7 +2867,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3682
2867
|
prompt: promptBuild.prompt,
|
|
3683
2868
|
imagesCount: params.images?.length ?? 0
|
|
3684
2869
|
});
|
|
3685
|
-
projector = new CodexAppServerEventProjector(params, thread.threadId, activeTurnId);
|
|
2870
|
+
projector = new CodexAppServerEventProjector(params, thread.threadId, activeTurnId, { nativePostToolUseRelayEnabled: nativeHookRelay?.allowedEvents.includes("post_tool_use") === true });
|
|
3686
2871
|
emitLifecycleStart();
|
|
3687
2872
|
const activeProjector = projector;
|
|
3688
2873
|
for (const notification of pendingNotifications.splice(0)) await enqueueNotification(notification);
|
|
@@ -3719,9 +2904,16 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3719
2904
|
runAbortController.abort("timeout");
|
|
3720
2905
|
}, Math.max(100, params.timeoutMs));
|
|
3721
2906
|
const abortListener = () => {
|
|
2907
|
+
const shouldRetireClient = timedOut;
|
|
3722
2908
|
interruptCodexTurnBestEffort(client, {
|
|
3723
2909
|
threadId: thread.threadId,
|
|
3724
|
-
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")
|
|
3725
2917
|
});
|
|
3726
2918
|
resolveCompletion?.();
|
|
3727
2919
|
};
|
|
@@ -3731,7 +2923,18 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3731
2923
|
await completion;
|
|
3732
2924
|
const result = activeProjector.buildResult(toolBridge.telemetry, { yieldDetected });
|
|
3733
2925
|
const finalAborted = result.aborted || runAbortController.signal.aborted;
|
|
3734
|
-
|
|
2926
|
+
let finalPromptError = turnCompletionIdleTimedOut ? turnCompletionIdleTimeoutMessage : timedOut ? "codex app-server attempt timed out" : result.promptError;
|
|
2927
|
+
const finalPromptErrorMessage = typeof finalPromptError === "string" ? finalPromptError : finalPromptError ? formatErrorMessage(finalPromptError) : void 0;
|
|
2928
|
+
if (shouldRefreshCodexRateLimitsForUsageLimitMessage(finalPromptErrorMessage)) finalPromptError = await refreshCodexUsageLimitErrorMessage({
|
|
2929
|
+
client,
|
|
2930
|
+
source: {
|
|
2931
|
+
message: finalPromptErrorMessage,
|
|
2932
|
+
codexErrorInfo: "usageLimitExceeded",
|
|
2933
|
+
rateLimits: readRecentCodexRateLimits()
|
|
2934
|
+
},
|
|
2935
|
+
timeoutMs: appServer.requestTimeoutMs,
|
|
2936
|
+
signal: runAbortController.signal
|
|
2937
|
+
});
|
|
3735
2938
|
const finalPromptErrorSource = timedOut ? "prompt" : result.promptErrorSource;
|
|
3736
2939
|
recordCodexTrajectoryCompletion(trajectoryRecorder, {
|
|
3737
2940
|
attempt: params,
|
|
@@ -3825,7 +3028,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3825
3028
|
timedOut,
|
|
3826
3029
|
aborted: finalAborted,
|
|
3827
3030
|
promptError: finalPromptError,
|
|
3828
|
-
promptErrorSource: finalPromptErrorSource
|
|
3031
|
+
promptErrorSource: finalPromptErrorSource,
|
|
3032
|
+
systemPromptReport
|
|
3829
3033
|
};
|
|
3830
3034
|
} finally {
|
|
3831
3035
|
emitLifecycleTerminal({
|
|
@@ -3862,6 +3066,60 @@ async function runCodexAppServerAttempt(params, options = {}) {
|
|
|
3862
3066
|
clearActiveEmbeddedRun(params.sessionId, handle, params.sessionKey);
|
|
3863
3067
|
}
|
|
3864
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
|
+
}
|
|
3865
3123
|
async function handleDynamicToolCallWithTimeout(params) {
|
|
3866
3124
|
if (params.signal.aborted) return failedDynamicToolResponse("OpenClaw dynamic tool call aborted before execution.");
|
|
3867
3125
|
const controller = new AbortController();
|
|
@@ -3989,8 +3247,31 @@ function buildCodexNativeHookRelayId(params) {
|
|
|
3989
3247
|
return `codex-${hash.digest("hex").slice(0, 40)}`;
|
|
3990
3248
|
}
|
|
3991
3249
|
function interruptCodexTurnBestEffort(client, params) {
|
|
3992
|
-
|
|
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) {
|
|
3993
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
|
|
3994
3275
|
});
|
|
3995
3276
|
}
|
|
3996
3277
|
function resolveOpenClawCodingToolsSessionKeys(params, sandboxSessionKey) {
|
|
@@ -4003,7 +3284,7 @@ async function buildDynamicTools(input) {
|
|
|
4003
3284
|
const { params } = input;
|
|
4004
3285
|
if (params.disableTools || !supportsModelTools(params.model)) return [];
|
|
4005
3286
|
const modelHasVision = params.model.input?.includes("image") ?? false;
|
|
4006
|
-
const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, input.sessionAgentId);
|
|
3287
|
+
const agentDir = params.agentDir ?? resolveAgentDir$1(params.config ?? {}, input.sessionAgentId);
|
|
4007
3288
|
const createOpenClawCodingTools = openClawCodingToolsFactoryForTests ?? (await import("openclaw/plugin-sdk/agent-harness")).createOpenClawCodingTools;
|
|
4008
3289
|
const sessionKeys = resolveOpenClawCodingToolsSessionKeys(params, input.sandboxSessionKey);
|
|
4009
3290
|
const filteredTools = filterCodexDynamicToolsForAllowlist(filterToolsForVisionInputs(filterCodexDynamicTools(createOpenClawCodingTools({
|
|
@@ -4142,14 +3423,76 @@ function resolveCodexTurnTerminalIdleTimeoutMs(value) {
|
|
|
4142
3423
|
function readDynamicToolCallParams(value) {
|
|
4143
3424
|
return readCodexDynamicToolCallParams(value);
|
|
4144
3425
|
}
|
|
4145
|
-
function formatCodexTurnStartUsageLimitError(
|
|
3426
|
+
async function formatCodexTurnStartUsageLimitError(params) {
|
|
3427
|
+
return refreshCodexUsageLimitError({
|
|
3428
|
+
client: params.client,
|
|
3429
|
+
source: readCodexTurnStartUsageLimitErrorSource(params.error, params.pendingNotifications),
|
|
3430
|
+
timeoutMs: params.timeoutMs,
|
|
3431
|
+
signal: params.signal
|
|
3432
|
+
});
|
|
3433
|
+
}
|
|
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) {
|
|
3443
|
+
const initialMessage = formatCodexUsageLimitErrorMessage(params.source);
|
|
3444
|
+
if (!shouldRefreshCodexRateLimitsForUsageLimitMessage(initialMessage)) return initialMessage ? {
|
|
3445
|
+
message: initialMessage,
|
|
3446
|
+
...params.source.rateLimitsTrustedForProfile ? { rateLimitsForProfile: params.source.rateLimits } : {}
|
|
3447
|
+
} : void 0;
|
|
3448
|
+
const rateLimits = await readCodexRateLimitsFromAppServerForUsageLimitError({
|
|
3449
|
+
client: params.client,
|
|
3450
|
+
timeoutMs: params.timeoutMs,
|
|
3451
|
+
signal: params.signal
|
|
3452
|
+
});
|
|
3453
|
+
if (!rateLimits) return initialMessage ? {
|
|
3454
|
+
message: initialMessage,
|
|
3455
|
+
...params.source.rateLimitsTrustedForProfile ? { rateLimitsForProfile: params.source.rateLimits } : {}
|
|
3456
|
+
} : void 0;
|
|
3457
|
+
const message = formatCodexUsageLimitErrorMessage({
|
|
3458
|
+
message: params.source.message,
|
|
3459
|
+
codexErrorInfo: params.source.codexErrorInfo,
|
|
3460
|
+
rateLimits
|
|
3461
|
+
}) ?? initialMessage;
|
|
3462
|
+
return message ? {
|
|
3463
|
+
message,
|
|
3464
|
+
rateLimitsForProfile: rateLimits
|
|
3465
|
+
} : void 0;
|
|
3466
|
+
}
|
|
3467
|
+
async function readCodexRateLimitsFromAppServerForUsageLimitError(params) {
|
|
3468
|
+
if (params.signal?.aborted) return;
|
|
3469
|
+
try {
|
|
3470
|
+
const rateLimits = await params.client.request(CODEX_CONTROL_METHODS.rateLimits, void 0, {
|
|
3471
|
+
timeoutMs: resolveCodexUsageLimitRateLimitRefreshTimeoutMs(params.timeoutMs),
|
|
3472
|
+
signal: params.signal
|
|
3473
|
+
});
|
|
3474
|
+
rememberCodexRateLimits(rateLimits);
|
|
3475
|
+
return rateLimits;
|
|
3476
|
+
} catch (error) {
|
|
3477
|
+
embeddedAgentLog.debug("codex app-server rate-limit refresh failed after usage-limit error", { error: formatErrorMessage(error) });
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
function resolveCodexUsageLimitRateLimitRefreshTimeoutMs(timeoutMs) {
|
|
3482
|
+
if (timeoutMs === void 0 || !Number.isFinite(timeoutMs) || timeoutMs <= 0) return CODEX_USAGE_LIMIT_RATE_LIMIT_REFRESH_TIMEOUT_MS;
|
|
3483
|
+
return Math.max(100, Math.min(timeoutMs, CODEX_USAGE_LIMIT_RATE_LIMIT_REFRESH_TIMEOUT_MS));
|
|
3484
|
+
}
|
|
3485
|
+
function readCodexTurnStartUsageLimitErrorSource(error, pendingNotifications) {
|
|
4146
3486
|
const notificationError = readLatestCodexErrorNotification(pendingNotifications);
|
|
3487
|
+
const notificationRateLimits = readLatestRateLimitNotificationPayload(pendingNotifications);
|
|
4147
3488
|
const errorPayload = readCodexErrorPayload(error);
|
|
4148
|
-
|
|
3489
|
+
const rateLimits = notificationRateLimits ?? errorPayload.rateLimits ?? readRecentCodexRateLimits();
|
|
3490
|
+
return {
|
|
4149
3491
|
message: notificationError?.message ?? errorPayload.message ?? formatErrorMessage(error),
|
|
4150
3492
|
codexErrorInfo: notificationError?.codexErrorInfo ?? errorPayload.codexErrorInfo,
|
|
4151
|
-
rateLimits
|
|
4152
|
-
|
|
3493
|
+
rateLimits,
|
|
3494
|
+
rateLimitsTrustedForProfile: notificationRateLimits !== void 0 || errorPayload.rateLimits !== void 0
|
|
3495
|
+
};
|
|
4153
3496
|
}
|
|
4154
3497
|
function readLatestRateLimitNotificationPayload(notifications) {
|
|
4155
3498
|
for (let index = notifications.length - 1; index >= 0; index -= 1) {
|
|
@@ -4178,10 +3521,12 @@ function readCodexErrorPayload(error) {
|
|
|
4178
3521
|
const data = error.data;
|
|
4179
3522
|
if (!isJsonObject(data)) return { message };
|
|
4180
3523
|
const nestedError = isJsonObject(data.error) ? data.error : data;
|
|
3524
|
+
const rateLimits = nestedError.rateLimits ?? data.rateLimits;
|
|
3525
|
+
if (rateLimits !== void 0) rememberCodexRateLimits(rateLimits);
|
|
4181
3526
|
return {
|
|
4182
3527
|
message: readString(nestedError, "message") ?? message,
|
|
4183
3528
|
codexErrorInfo: nestedError.codexErrorInfo,
|
|
4184
|
-
rateLimits
|
|
3529
|
+
rateLimits
|
|
4185
3530
|
};
|
|
4186
3531
|
}
|
|
4187
3532
|
function describeNotificationActivity(notification) {
|
|
@@ -4225,6 +3570,35 @@ function readNestedTurnId(record) {
|
|
|
4225
3570
|
const turn = record.turn;
|
|
4226
3571
|
return isJsonObject(turn) ? readString(turn, "id") : void 0;
|
|
4227
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
|
+
}
|
|
4228
3602
|
function readString(record, key) {
|
|
4229
3603
|
const value = record[key];
|
|
4230
3604
|
return typeof value === "string" ? value : void 0;
|
|
@@ -4238,9 +3612,9 @@ async function readMirroredSessionHistoryMessages(sessionFile) {
|
|
|
4238
3612
|
if (!messages) embeddedAgentLog.warn("failed to read mirrored session history for codex harness hooks", { sessionFile });
|
|
4239
3613
|
return messages;
|
|
4240
3614
|
}
|
|
4241
|
-
async function
|
|
3615
|
+
async function buildCodexWorkspaceBootstrapContext(params) {
|
|
4242
3616
|
try {
|
|
4243
|
-
const
|
|
3617
|
+
const bootstrapContext = await resolveBootstrapContextForRun({
|
|
4244
3618
|
workspaceDir: params.resolvedWorkspace,
|
|
4245
3619
|
config: params.params.config,
|
|
4246
3620
|
sessionKey: params.sessionKey,
|
|
@@ -4250,16 +3624,118 @@ async function buildCodexWorkspaceBootstrapInstructions(params) {
|
|
|
4250
3624
|
contextMode: params.params.bootstrapContextMode,
|
|
4251
3625
|
runKind: params.params.bootstrapContextRunKind
|
|
4252
3626
|
});
|
|
4253
|
-
|
|
3627
|
+
const contextFiles = bootstrapContext.contextFiles.map((file) => remapCodexContextFilePath({
|
|
4254
3628
|
file,
|
|
4255
3629
|
sourceWorkspaceDir: params.resolvedWorkspace,
|
|
4256
3630
|
targetWorkspaceDir: params.effectiveWorkspace
|
|
4257
|
-
}))
|
|
3631
|
+
}));
|
|
3632
|
+
return {
|
|
3633
|
+
...bootstrapContext,
|
|
3634
|
+
contextFiles,
|
|
3635
|
+
instructions: renderCodexWorkspaceBootstrapInstructions(contextFiles)
|
|
3636
|
+
};
|
|
4258
3637
|
} catch (error) {
|
|
4259
3638
|
embeddedAgentLog.warn("failed to load codex workspace bootstrap instructions", { error });
|
|
4260
|
-
return
|
|
3639
|
+
return {
|
|
3640
|
+
bootstrapFiles: [],
|
|
3641
|
+
contextFiles: []
|
|
3642
|
+
};
|
|
4261
3643
|
}
|
|
4262
3644
|
}
|
|
3645
|
+
function buildCodexSystemPromptReport(params) {
|
|
3646
|
+
const toolEntries = params.tools.map(buildCodexToolReportEntry);
|
|
3647
|
+
const schemaChars = toolEntries.reduce((sum, tool) => sum + tool.schemaChars, 0);
|
|
3648
|
+
const projectContextChars = params.workspaceBootstrapContext.instructions?.length ?? 0;
|
|
3649
|
+
const bootstrapMaxChars = readPositiveNumber(params.attempt.config?.agents?.defaults?.bootstrapMaxChars);
|
|
3650
|
+
const bootstrapTotalMaxChars = readPositiveNumber(params.attempt.config?.agents?.defaults?.bootstrapTotalMaxChars);
|
|
3651
|
+
return {
|
|
3652
|
+
source: "run",
|
|
3653
|
+
generatedAt: Date.now(),
|
|
3654
|
+
sessionId: params.attempt.sessionId,
|
|
3655
|
+
sessionKey: params.sessionKey,
|
|
3656
|
+
provider: params.attempt.provider,
|
|
3657
|
+
model: params.attempt.modelId,
|
|
3658
|
+
workspaceDir: params.workspaceDir,
|
|
3659
|
+
...bootstrapMaxChars ? { bootstrapMaxChars } : {},
|
|
3660
|
+
...bootstrapTotalMaxChars ? { bootstrapTotalMaxChars } : {},
|
|
3661
|
+
systemPrompt: {
|
|
3662
|
+
chars: params.developerInstructions.length,
|
|
3663
|
+
projectContextChars,
|
|
3664
|
+
nonProjectContextChars: Math.max(0, params.developerInstructions.length - projectContextChars)
|
|
3665
|
+
},
|
|
3666
|
+
injectedWorkspaceFiles: buildCodexBootstrapInjectionStats({
|
|
3667
|
+
bootstrapFiles: params.workspaceBootstrapContext.bootstrapFiles,
|
|
3668
|
+
injectedFiles: params.workspaceBootstrapContext.contextFiles
|
|
3669
|
+
}),
|
|
3670
|
+
skills: {
|
|
3671
|
+
promptChars: 0,
|
|
3672
|
+
entries: []
|
|
3673
|
+
},
|
|
3674
|
+
tools: {
|
|
3675
|
+
listChars: 0,
|
|
3676
|
+
schemaChars,
|
|
3677
|
+
entries: toolEntries
|
|
3678
|
+
}
|
|
3679
|
+
};
|
|
3680
|
+
}
|
|
3681
|
+
function buildCodexToolReportEntry(tool) {
|
|
3682
|
+
const summary = tool.description.trim();
|
|
3683
|
+
if (tool.deferLoading === true) return {
|
|
3684
|
+
name: tool.name,
|
|
3685
|
+
summaryChars: summary.length,
|
|
3686
|
+
schemaChars: 0,
|
|
3687
|
+
propertiesCount: null
|
|
3688
|
+
};
|
|
3689
|
+
return {
|
|
3690
|
+
name: tool.name,
|
|
3691
|
+
summaryChars: summary.length,
|
|
3692
|
+
...buildCodexToolSchemaStats(tool.inputSchema)
|
|
3693
|
+
};
|
|
3694
|
+
}
|
|
3695
|
+
function buildCodexToolSchemaStats(schema) {
|
|
3696
|
+
const schemaChars = (() => {
|
|
3697
|
+
try {
|
|
3698
|
+
return JSON.stringify(schema).length;
|
|
3699
|
+
} catch {
|
|
3700
|
+
return 0;
|
|
3701
|
+
}
|
|
3702
|
+
})();
|
|
3703
|
+
const properties = isJsonObject(schema) && isJsonObject(schema.properties) ? schema.properties : null;
|
|
3704
|
+
return {
|
|
3705
|
+
schemaChars,
|
|
3706
|
+
propertiesCount: properties ? Object.keys(properties).length : null
|
|
3707
|
+
};
|
|
3708
|
+
}
|
|
3709
|
+
function buildCodexBootstrapInjectionStats(params) {
|
|
3710
|
+
const injectedByPath = /* @__PURE__ */ new Map();
|
|
3711
|
+
const injectedByBaseName = /* @__PURE__ */ new Map();
|
|
3712
|
+
for (const file of params.injectedFiles) {
|
|
3713
|
+
const pathValue = readNonEmptyString(file.path);
|
|
3714
|
+
if (!pathValue) continue;
|
|
3715
|
+
if (!injectedByPath.has(pathValue)) injectedByPath.set(pathValue, file.content);
|
|
3716
|
+
const baseName = path.posix.basename(pathValue.replaceAll("\\", "/"));
|
|
3717
|
+
if (!injectedByBaseName.has(baseName)) injectedByBaseName.set(baseName, file.content);
|
|
3718
|
+
}
|
|
3719
|
+
return params.bootstrapFiles.map((file) => {
|
|
3720
|
+
const pathValue = readNonEmptyString(file.path) ?? file.name;
|
|
3721
|
+
const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length;
|
|
3722
|
+
const injectedChars = (injectedByPath.get(pathValue) ?? injectedByPath.get(file.name) ?? injectedByBaseName.get(file.name))?.length ?? 0;
|
|
3723
|
+
return {
|
|
3724
|
+
name: file.name,
|
|
3725
|
+
path: pathValue,
|
|
3726
|
+
missing: file.missing,
|
|
3727
|
+
rawChars,
|
|
3728
|
+
injectedChars,
|
|
3729
|
+
truncated: !file.missing && injectedChars < rawChars
|
|
3730
|
+
};
|
|
3731
|
+
});
|
|
3732
|
+
}
|
|
3733
|
+
function readPositiveNumber(value) {
|
|
3734
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
|
|
3735
|
+
}
|
|
3736
|
+
function readNonEmptyString(value) {
|
|
3737
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
3738
|
+
}
|
|
4263
3739
|
function renderCodexWorkspaceBootstrapInstructions(contextFiles) {
|
|
4264
3740
|
const files = contextFiles.filter((file) => {
|
|
4265
3741
|
const baseName = getCodexContextFileBasename(file.path);
|
|
@@ -4274,7 +3750,7 @@ function renderCodexWorkspaceBootstrapInstructions(contextFiles) {
|
|
|
4274
3750
|
"",
|
|
4275
3751
|
"The following project context files have been loaded:"
|
|
4276
3752
|
];
|
|
4277
|
-
if (hasSoulFile) lines.push("
|
|
3753
|
+
if (hasSoulFile) lines.push("SOUL.md: persona/tone. Follow it unless higher-priority instructions override.");
|
|
4278
3754
|
lines.push("");
|
|
4279
3755
|
for (const file of files) lines.push(`## ${file.path}`, "", file.content, "");
|
|
4280
3756
|
return lines.join("\n").trim();
|
|
@@ -4321,9 +3797,28 @@ async function mirrorTranscriptBestEffort(params) {
|
|
|
4321
3797
|
function isNonEmptyString(value) {
|
|
4322
3798
|
return typeof value === "string" && value.length > 0;
|
|
4323
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
|
+
}
|
|
4324
3815
|
function joinPresentSections(...sections) {
|
|
4325
3816
|
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
4326
3817
|
}
|
|
3818
|
+
function prependCurrentTurnContext(prompt, context) {
|
|
3819
|
+
const text = context?.text.trim();
|
|
3820
|
+
return text ? [text, prompt].filter(Boolean).join("\n\n") : prompt;
|
|
3821
|
+
}
|
|
4327
3822
|
function handleApprovalRequest(params) {
|
|
4328
3823
|
return handleCodexAppServerApprovalRequest({
|
|
4329
3824
|
method: params.method,
|