@openclaw/codex 2026.6.11 → 2026.7.1-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-server-policy-BPTiVNsW.js → app-server-policy-C968Kgin.js} +5 -5
- package/dist/{attempt-notifications-BYnndyFl.js → attempt-notifications-BGsEIIDI.js} +29 -40
- package/dist/{command-handlers-DEPTJYk0.js → command-handlers-Dv-pzAmg.js} +278 -237
- package/dist/command-rpc-eLM_mtC7.js +784 -0
- package/dist/compact-4mVBriKT.js +603 -0
- package/dist/{computer-use-ClrJwoFp.js → computer-use-Bmaz333N.js} +3 -3
- package/dist/{config-CszD0vP3.js → config-CYEDnLJ2.js} +44 -18
- package/dist/conversation-binding-data-BHRpYbX3.js +121 -0
- package/dist/doctor-contract-api.js +590 -22
- package/dist/harness.js +29 -10
- package/dist/index.js +1134 -35
- package/dist/{media-understanding-provider-ZUQHN7gT.js → media-understanding-provider-BHOu9-Gg.js} +13 -8
- package/dist/media-understanding-provider.js +1 -1
- package/dist/{models-CAmfBWRl.js → models-DDmO1zwd.js} +2 -2
- package/dist/notification-correlation-Bo7KB3ks.js +35 -0
- package/dist/{plugin-app-cache-key-C9ILt7bl.js → plugin-app-cache-key-BrhVdeEf.js} +13 -5
- package/dist/{protocol-dh-ETiNd.js → protocol-2POPqAY4.js} +2 -1
- package/dist/{protocol-validators-B19q5BIX.js → protocol-validators-dZQ-UTOa.js} +205 -56
- package/dist/provider-capabilities-DzI8fgEu.js +6172 -0
- package/dist/provider-catalog.js +4 -2
- package/dist/{provider-B-OHpbD3.js → provider-zjPfx5Fs.js} +94 -23
- package/dist/provider.js +2 -2
- package/dist/{request-Dcd452Nk.js → request-BcJyl8KL.js} +2 -2
- package/dist/{run-attempt-CXxtFC9d.js → run-attempt-DNSNk_G8.js} +1250 -2992
- package/dist/{sandbox-guard-3tnjhjFb.js → sandbox-guard-DA2TQfZW.js} +5 -3
- package/dist/session-binding-C1ZXdP-x.js +627 -0
- package/dist/session-binding-meta-B7aEMU7g.js +6 -0
- package/dist/shared-client-4ICy3U6d.js +1853 -0
- package/dist/{side-question-C-DhgJOd.js → side-question-CMOpEhvY.js} +134 -142
- package/dist/{thread-lifecycle-DtT3-ehU.js → thread-lifecycle-qWE88Dn2.js} +803 -481
- package/dist/{web-search-provider.runtime-CkwxrXQj.js → web-search-provider.runtime-BSlriav6.js} +3 -3
- package/npm-shrinkwrap.json +34 -34
- package/openclaw.plugin.json +23 -3
- package/package.json +9 -6
- package/dist/client-CLrtWgrD.js +0 -729
- package/dist/client-factory-Bm6HsGob.js +0 -18
- package/dist/compact-aQJ0ZKhe.js +0 -278
- package/dist/conversation-binding-t0XMLz-j.js +0 -1557
- package/dist/provider-capabilities-D7hnTO76.js +0 -3201
- package/dist/rate-limit-cache-C7qmZ0Jh.js +0 -26
- package/dist/session-binding-HOuPt-E0.js +0 -338
- package/dist/shared-client-Dtx5i7Ez.js +0 -1013
package/dist/index.js
CHANGED
|
@@ -1,28 +1,59 @@
|
|
|
1
1
|
import { createCodexAppServerAgentHarness } from "./harness.js";
|
|
2
|
-
import {
|
|
3
|
-
import { d as resolveCodexAppServerRuntimeOptions, t as CODEX_PLUGINS_MARKETPLACE_NAME, u as readCodexPluginConfig } from "./config-
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
6
|
-
import {
|
|
7
|
-
import { a as
|
|
8
|
-
import {
|
|
9
|
-
import { a as
|
|
10
|
-
import { t as
|
|
2
|
+
import { r as isJsonObject, t as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS } from "./protocol-2POPqAY4.js";
|
|
3
|
+
import { d as resolveCodexAppServerRuntimeOptions, g as resolveOpenClawExecPolicyForCodexAppServer, i as codexSandboxPolicyForTurn, n as canUseCodexModelBackedApprovalsReviewerForModel, t as CODEX_PLUGINS_MARKETPLACE_NAME, u as readCodexPluginConfig } from "./config-CYEDnLJ2.js";
|
|
4
|
+
import { r as assertCodexThreadStartResponse } from "./protocol-validators-dZQ-UTOa.js";
|
|
5
|
+
import { G as pluginReadParams, W as ensureCodexPluginActivation, f as resolveCodexAppServerRequestModelSelection, q as defaultCodexAppInventoryCache, t as CODEX_NATIVE_PERSONALITY_NONE } from "./thread-lifecycle-qWE88Dn2.js";
|
|
6
|
+
import { t as buildCodexProvider } from "./provider-zjPfx5Fs.js";
|
|
7
|
+
import { _ as resolveCodexAppServerAuthAccountCacheKey, a as getLeasedSharedCodexAppServerClient, b as resolveCodexAppServerFallbackApiKeyCacheKey, o as releaseLeasedSharedCodexAppServerClient, r as clearSharedCodexAppServerClientIfCurrentAndWait, y as resolveCodexAppServerAuthProfileIdForAgent } from "./shared-client-4ICy3U6d.js";
|
|
8
|
+
import { n as CODEX_APP_SERVER_BINDING_NAMESPACE, t as CODEX_APP_SERVER_BINDING_MAX_ENTRIES } from "./session-binding-meta-B7aEMU7g.js";
|
|
9
|
+
import { a as normalizeCodexAppServerBindingModelProvider, i as isCodexAppServerNativeAuthProfile, s as sessionBindingIdentity } from "./session-binding-C1ZXdP-x.js";
|
|
10
|
+
import { t as buildCodexMediaUnderstandingProvider } from "./media-understanding-provider-BHOu9-Gg.js";
|
|
11
|
+
import { i as describeControlFailure, n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS } from "./plugin-app-cache-key-BrhVdeEf.js";
|
|
12
|
+
import { a as formatCodexDisplayText, t as resolveCodexAppServerForModelProvider } from "./app-server-policy-C968Kgin.js";
|
|
13
|
+
import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-DA2TQfZW.js";
|
|
14
|
+
import { S as canMutateCodexHost, a as createCodexCliSessionNodeHostCommands, b as trackCodexConversationActiveTurn, c as listCodexCliSessionsOnNode, l as resolveCodexCliSessionForBindingOnNode, o as createCodexCliSessionNodeInvokePolicies, t as codexControlRequest, u as resumeCodexCliSessionOnNode, x as CODEX_NATIVE_EXECUTION_AUTH_ERROR } from "./command-rpc-eLM_mtC7.js";
|
|
15
|
+
import { a as readCodexConversationBindingDataRecord, i as readCodexConversationBindingData } from "./conversation-binding-data-BHRpYbX3.js";
|
|
16
|
+
import { n as readCodexNotificationThreadId, r as readCodexNotificationTurnId } from "./notification-correlation-Bo7KB3ks.js";
|
|
17
|
+
import { t as requestCodexAppServerJson } from "./request-BcJyl8KL.js";
|
|
11
18
|
import { t as createCodexWebSearchProviderBase } from "./web-search-provider.shared-BrZmlqyR.js";
|
|
12
19
|
import { mutateConfigFile } from "openclaw/plugin-sdk/config-mutation";
|
|
13
20
|
import { resolveLivePluginConfigObject, resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
14
21
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
15
22
|
import fs from "node:fs/promises";
|
|
16
23
|
import path from "node:path";
|
|
17
|
-
import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
18
|
-
import { asBoolean, isRecord, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
24
|
+
import { parseStrictNonNegativeInteger, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
19
25
|
import os from "node:os";
|
|
20
|
-
import { loadAuthProfileStoreWithoutExternalProfiles, resolveAgentConfig, resolveAgentWorkspaceDir, resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
|
26
|
+
import { loadAuthProfileStoreWithoutExternalProfiles, resolveAgentConfig, resolveAgentWorkspaceDir, resolveDefaultAgentId, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
|
|
27
|
+
import { loadExecApprovals } from "openclaw/plugin-sdk/exec-approvals-runtime";
|
|
28
|
+
import { asBoolean, asOptionalRecord, isRecord, normalizeOptionalString, normalizeSingleOrTrimmedStringList, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
29
|
+
import { formatErrorMessage, resolveSandboxContext } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
30
|
+
import { jsonResult, readStringParam } from "openclaw/plugin-sdk/core";
|
|
21
31
|
import { applyAuthProfileConfig, buildApiKeyCredential, buildOauthProviderAuthResult, buildOpenAICodexCredentialExtra, readCodexCliCredentialsCached, resolveOpenAICodexAuthIdentity, resolveOpenAICodexImportProfileName, updateAuthProfileStoreWithLock } from "openclaw/plugin-sdk/provider-auth";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
22
33
|
import { pathExists } from "openclaw/plugin-sdk/security-runtime";
|
|
34
|
+
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
35
|
+
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
23
36
|
import { MIGRATION_REASON_TARGET_EXISTS, applyMigrationManualItem, createMigrationItem, createMigrationManualItem, hasMigrationConfigPatchConflict, markMigrationItemConflict, markMigrationItemError, markMigrationItemSkipped, readMigrationConfigPath, summarizeMigrationItems, writeMigrationConfigPath } from "openclaw/plugin-sdk/migration";
|
|
24
37
|
import { archiveMigrationItem, copyMigrationFileItem, withCachedMigrationConfigRuntime, writeMigrationReport } from "openclaw/plugin-sdk/migration-runtime";
|
|
38
|
+
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
|
25
39
|
import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
|
|
40
|
+
import { Type } from "typebox";
|
|
41
|
+
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
42
|
+
//#region extensions/codex/src/app-server/session-binding-store.ts
|
|
43
|
+
/** Defers schema compilation and auth loading until the first binding operation. */
|
|
44
|
+
function createLazyCodexAppServerBindingStore(state) {
|
|
45
|
+
let resolved;
|
|
46
|
+
const store = () => resolved ??= import("./session-binding-C1ZXdP-x.js").then((n) => n.c).then(({ createCodexAppServerBindingStore }) => createCodexAppServerBindingStore(state));
|
|
47
|
+
return {
|
|
48
|
+
read: async (identity) => (await store()).read(identity),
|
|
49
|
+
mutate: async (identity, mutation) => (await store()).mutate(identity, mutation),
|
|
50
|
+
prepareSessionGenerationReclaim: async (identity) => (await store()).prepareSessionGenerationReclaim(identity),
|
|
51
|
+
adoptSessionGeneration: async (identity, previousSessionId) => (await store()).adoptSessionGeneration(identity, previousSessionId),
|
|
52
|
+
retireSessionGeneration: async (identity) => (await store()).retireSessionGeneration(identity),
|
|
53
|
+
withLease: async (identity, run) => (await store()).withLease(identity, run)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
26
57
|
//#region extensions/codex/src/commands.ts
|
|
27
58
|
/** Creates the reserved `/codex` command definition exposed by the plugin. */
|
|
28
59
|
function createCodexCommand(options) {
|
|
@@ -43,19 +74,815 @@ function createCodexCommand(options) {
|
|
|
43
74
|
};
|
|
44
75
|
}
|
|
45
76
|
/** Dispatches a `/codex` command to the subcommand handler and formats failures for chat. */
|
|
46
|
-
async function handleCodexCommand(ctx, options
|
|
47
|
-
const { loadSubcommandHandler, ...subcommandOptions } = options;
|
|
77
|
+
async function handleCodexCommand(ctx, options) {
|
|
78
|
+
const { loadSubcommandHandler, resolvePluginConfig, ...subcommandOptions } = options;
|
|
48
79
|
try {
|
|
49
|
-
return await (loadSubcommandHandler ? await loadSubcommandHandler() : await loadDefaultCodexSubcommandHandler())(ctx,
|
|
80
|
+
return await (loadSubcommandHandler ? await loadSubcommandHandler() : await loadDefaultCodexSubcommandHandler())(ctx, {
|
|
81
|
+
...subcommandOptions,
|
|
82
|
+
pluginConfig: resolvePluginConfig?.() ?? subcommandOptions.pluginConfig
|
|
83
|
+
});
|
|
50
84
|
} catch (error) {
|
|
51
85
|
return { text: `Codex command failed: ${formatCodexDisplayText(describeControlFailure(error))}` };
|
|
52
86
|
}
|
|
53
87
|
}
|
|
54
88
|
async function loadDefaultCodexSubcommandHandler() {
|
|
55
|
-
const { handleCodexSubcommand } = await import("./command-handlers-
|
|
89
|
+
const { handleCodexSubcommand } = await import("./command-handlers-Dv-pzAmg.js");
|
|
56
90
|
return handleCodexSubcommand;
|
|
57
91
|
}
|
|
58
92
|
//#endregion
|
|
93
|
+
//#region extensions/codex/src/conversation-turn-collector.ts
|
|
94
|
+
const MAX_PENDING_NOTIFICATIONS_PER_TURN = 100;
|
|
95
|
+
function createCodexConversationTurnCollector(threadId) {
|
|
96
|
+
let turnId;
|
|
97
|
+
let completed = false;
|
|
98
|
+
let failedError;
|
|
99
|
+
let timeout;
|
|
100
|
+
const assistantTextByItem = /* @__PURE__ */ new Map();
|
|
101
|
+
const assistantOrder = [];
|
|
102
|
+
const pendingNotificationsByTurnId = /* @__PURE__ */ new Map();
|
|
103
|
+
let resolveCompletion;
|
|
104
|
+
let rejectCompletion;
|
|
105
|
+
const rememberItem = (itemId) => {
|
|
106
|
+
if (!assistantOrder.includes(itemId)) assistantOrder.push(itemId);
|
|
107
|
+
};
|
|
108
|
+
const collectReplyText = () => {
|
|
109
|
+
return assistantOrder.map((itemId) => assistantTextByItem.get(itemId)?.trim()).filter((text) => Boolean(text)).at(-1) ?? "";
|
|
110
|
+
};
|
|
111
|
+
const clearWaitState = () => {
|
|
112
|
+
if (timeout) {
|
|
113
|
+
clearTimeout(timeout);
|
|
114
|
+
timeout = void 0;
|
|
115
|
+
}
|
|
116
|
+
resolveCompletion = void 0;
|
|
117
|
+
rejectCompletion = void 0;
|
|
118
|
+
};
|
|
119
|
+
const finish = () => {
|
|
120
|
+
if (completed) return;
|
|
121
|
+
completed = true;
|
|
122
|
+
if (failedError) rejectCompletion?.(new Error(failedError));
|
|
123
|
+
else resolveCompletion?.({ replyText: collectReplyText() });
|
|
124
|
+
clearWaitState();
|
|
125
|
+
};
|
|
126
|
+
const handleNotification = (notification) => {
|
|
127
|
+
const params = isJsonObject(notification.params) ? notification.params : void 0;
|
|
128
|
+
if (!params || readCodexNotificationThreadId(params) !== threadId) return;
|
|
129
|
+
if (!turnId) {
|
|
130
|
+
const pendingTurnId = readNotificationTurnId(params);
|
|
131
|
+
if (pendingTurnId) {
|
|
132
|
+
const pending = pendingNotificationsByTurnId.get(pendingTurnId) ?? [];
|
|
133
|
+
if (pending.length < MAX_PENDING_NOTIFICATIONS_PER_TURN) {
|
|
134
|
+
pending.push(notification);
|
|
135
|
+
pendingNotificationsByTurnId.set(pendingTurnId, pending);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!isNotificationForTurn(params, threadId, turnId)) return;
|
|
141
|
+
if (notification.method === "item/agentMessage/delta") {
|
|
142
|
+
const itemId = readString(params, "itemId") ?? "assistant";
|
|
143
|
+
const delta = readTextString(params, "delta");
|
|
144
|
+
if (!delta) return;
|
|
145
|
+
rememberItem(itemId);
|
|
146
|
+
assistantTextByItem.set(itemId, `${assistantTextByItem.get(itemId) ?? ""}${delta}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (notification.method === "item/completed") {
|
|
150
|
+
const item = isJsonObject(params.item) ? params.item : void 0;
|
|
151
|
+
if (item?.type === "agentMessage") {
|
|
152
|
+
const itemId = readString(item, "id") ?? readString(params, "itemId") ?? "assistant";
|
|
153
|
+
const text = readTextString(item, "text");
|
|
154
|
+
if (text) {
|
|
155
|
+
rememberItem(itemId);
|
|
156
|
+
assistantTextByItem.set(itemId, text);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (notification.method === "turn/completed") {
|
|
162
|
+
const turn = isJsonObject(params.turn) ? params.turn : void 0;
|
|
163
|
+
if (readString(turn, "status") === "failed") failedError = readString(asOptionalRecord(turn?.error), "message") ?? "codex app-server turn failed";
|
|
164
|
+
const items = Array.isArray(turn?.items) ? turn.items : [];
|
|
165
|
+
for (const item of items) {
|
|
166
|
+
if (!isJsonObject(item) || item.type !== "agentMessage") continue;
|
|
167
|
+
const itemId = readString(item, "id") ?? `assistant-${assistantOrder.length + 1}`;
|
|
168
|
+
const text = readTextString(item, "text");
|
|
169
|
+
if (text) {
|
|
170
|
+
rememberItem(itemId);
|
|
171
|
+
assistantTextByItem.set(itemId, text);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
finish();
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
setTurnId(nextTurnId) {
|
|
179
|
+
turnId = nextTurnId;
|
|
180
|
+
const pending = pendingNotificationsByTurnId.get(nextTurnId) ?? [];
|
|
181
|
+
pendingNotificationsByTurnId.clear();
|
|
182
|
+
for (const notification of pending) handleNotification(notification);
|
|
183
|
+
},
|
|
184
|
+
handleNotification,
|
|
185
|
+
wait(params) {
|
|
186
|
+
if (completed) return failedError ? Promise.reject(new Error(failedError)) : Promise.resolve({ replyText: collectReplyText() });
|
|
187
|
+
return new Promise((resolve, reject) => {
|
|
188
|
+
resolveCompletion = resolve;
|
|
189
|
+
rejectCompletion = reject;
|
|
190
|
+
timeout = setTimeout(() => {
|
|
191
|
+
completed = true;
|
|
192
|
+
reject(/* @__PURE__ */ new Error("codex app-server bound turn timed out"));
|
|
193
|
+
clearWaitState();
|
|
194
|
+
}, resolveTimerTimeoutMs(params.timeoutMs, 100, 100));
|
|
195
|
+
timeout.unref?.();
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function isNotificationForTurn(params, threadId, turnId) {
|
|
201
|
+
if (readCodexNotificationThreadId(params) !== threadId) return false;
|
|
202
|
+
if (!turnId) return true;
|
|
203
|
+
const directTurnId = readString(params, "turnId");
|
|
204
|
+
if (directTurnId) return directTurnId === turnId;
|
|
205
|
+
return readString(isJsonObject(params.turn) ? params.turn : void 0, "id") === turnId;
|
|
206
|
+
}
|
|
207
|
+
function readNotificationTurnId(params) {
|
|
208
|
+
return readCodexNotificationTurnId(params);
|
|
209
|
+
}
|
|
210
|
+
function readString(record, key) {
|
|
211
|
+
const value = record?.[key];
|
|
212
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
213
|
+
}
|
|
214
|
+
function readTextString(record, key) {
|
|
215
|
+
const value = record?.[key];
|
|
216
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region extensions/codex/src/conversation-turn-input.ts
|
|
220
|
+
const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
221
|
+
".avif",
|
|
222
|
+
".gif",
|
|
223
|
+
".jpeg",
|
|
224
|
+
".jpg",
|
|
225
|
+
".png",
|
|
226
|
+
".webp"
|
|
227
|
+
]);
|
|
228
|
+
function buildCodexConversationTurnInput(params) {
|
|
229
|
+
return [{
|
|
230
|
+
type: "text",
|
|
231
|
+
text: params.prompt,
|
|
232
|
+
text_elements: []
|
|
233
|
+
}, ...extractInboundMedia(params.event).map(toCodexImageInput).filter((item) => item !== void 0)];
|
|
234
|
+
}
|
|
235
|
+
function extractInboundMedia(event) {
|
|
236
|
+
const metadata = event.metadata ?? {};
|
|
237
|
+
const paths = normalizeSingleOrTrimmedStringList(metadata.mediaPaths).concat(normalizeSingleOrTrimmedStringList(metadata.mediaPath));
|
|
238
|
+
const urls = normalizeSingleOrTrimmedStringList(metadata.mediaUrls).concat(normalizeSingleOrTrimmedStringList(metadata.mediaUrl));
|
|
239
|
+
const mimeTypes = normalizeSingleOrTrimmedStringList(metadata.mediaTypes).concat(normalizeSingleOrTrimmedStringList(metadata.mediaType));
|
|
240
|
+
const count = Math.max(paths.length, urls.length, mimeTypes.length);
|
|
241
|
+
const media = [];
|
|
242
|
+
for (let index = 0; index < count; index += 1) media.push({
|
|
243
|
+
path: paths[index],
|
|
244
|
+
url: urls[index],
|
|
245
|
+
mimeType: mimeTypes[index] ?? mimeTypes[0]
|
|
246
|
+
});
|
|
247
|
+
return media;
|
|
248
|
+
}
|
|
249
|
+
function toCodexImageInput(media) {
|
|
250
|
+
if (!isImageMedia(media)) return;
|
|
251
|
+
const localPath = media.path ?? readLocalMediaPath(media.url);
|
|
252
|
+
if (localPath) {
|
|
253
|
+
const normalized = normalizeFileUrl(localPath);
|
|
254
|
+
return normalized ? {
|
|
255
|
+
type: "localImage",
|
|
256
|
+
path: normalized
|
|
257
|
+
} : void 0;
|
|
258
|
+
}
|
|
259
|
+
return media.url ? {
|
|
260
|
+
type: "image",
|
|
261
|
+
url: media.url
|
|
262
|
+
} : void 0;
|
|
263
|
+
}
|
|
264
|
+
function isImageMedia(media) {
|
|
265
|
+
if (media.mimeType?.toLowerCase().startsWith("image/")) return true;
|
|
266
|
+
const candidate = media.path ?? media.url;
|
|
267
|
+
if (!candidate) return false;
|
|
268
|
+
return IMAGE_EXTENSIONS.has(path.extname(candidate.split(/[?#]/, 1)[0] ?? "").toLowerCase());
|
|
269
|
+
}
|
|
270
|
+
function normalizeFileUrl(value) {
|
|
271
|
+
if (!value.startsWith("file://")) return value;
|
|
272
|
+
try {
|
|
273
|
+
return fileURLToPath(value);
|
|
274
|
+
} catch {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function readLocalMediaPath(value) {
|
|
279
|
+
if (!value) return;
|
|
280
|
+
if (value.startsWith("file://")) return value;
|
|
281
|
+
if (value.startsWith("//")) return;
|
|
282
|
+
if (path.isAbsolute(value) || path.win32.isAbsolute(value)) return value;
|
|
283
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(value) ? void 0 : value;
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region extensions/codex/src/conversation-binding.ts
|
|
287
|
+
const DEFAULT_BOUND_TURN_TIMEOUT_MS = 20 * 6e4;
|
|
288
|
+
const DEFAULT_AGENT_ID = "main";
|
|
289
|
+
const VALID_AGENT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
|
290
|
+
const INVALID_AGENT_ID_CHARS_PATTERN = /[^a-z0-9_-]+/g;
|
|
291
|
+
const LEADING_DASH_PATTERN = /^-+/;
|
|
292
|
+
const TRAILING_DASH_PATTERN = /-+$/;
|
|
293
|
+
const NATIVE_CONVERSATION_INTERACTIVE_APPROVALS_UNAVAILABLE = "OpenClaw native Codex conversation binding cannot route interactive approvals yet; use the Codex harness or explicit /acp spawn codex for that workflow.";
|
|
294
|
+
async function resolveConversationAppServerRuntime(params) {
|
|
295
|
+
const execPolicy = resolveConversationExecPolicy({
|
|
296
|
+
config: params.config,
|
|
297
|
+
agentId: params.agentId,
|
|
298
|
+
sessionKey: params.sessionKey
|
|
299
|
+
});
|
|
300
|
+
const sandboxForPolicy = execPolicy.touched && execPolicy.security === "full" && execPolicy.ask !== "off" ? await resolveSandboxContext({
|
|
301
|
+
config: params.config,
|
|
302
|
+
sessionKey: params.sessionKey,
|
|
303
|
+
workspaceDir: params.workspaceDir
|
|
304
|
+
}) : void 0;
|
|
305
|
+
return {
|
|
306
|
+
execPolicy,
|
|
307
|
+
runtime: resolveCodexAppServerRuntimeOptions({
|
|
308
|
+
pluginConfig: params.pluginConfig,
|
|
309
|
+
execPolicy,
|
|
310
|
+
modelProvider: params.modelProvider,
|
|
311
|
+
model: params.model,
|
|
312
|
+
config: params.config,
|
|
313
|
+
agentDir: params.agentDir,
|
|
314
|
+
openClawSandboxActive: Boolean(sandboxForPolicy?.enabled)
|
|
315
|
+
})
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const CODEX_CONVERSATION_GLOBAL_STATE = Symbol.for("openclaw.codex.conversationBinding");
|
|
319
|
+
const CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS = "This Codex thread is bound to an OpenClaw conversation. Answer normally; OpenClaw will deliver your final response back to the conversation.";
|
|
320
|
+
function getGlobalState() {
|
|
321
|
+
const globalState = globalThis;
|
|
322
|
+
globalState[CODEX_CONVERSATION_GLOBAL_STATE] ??= { queue: new KeyedAsyncQueue() };
|
|
323
|
+
return globalState[CODEX_CONVERSATION_GLOBAL_STATE];
|
|
324
|
+
}
|
|
325
|
+
async function handleCodexConversationInboundClaim(event, ctx, options) {
|
|
326
|
+
const data = readCodexConversationBindingData(ctx.pluginBinding);
|
|
327
|
+
if (!data) return;
|
|
328
|
+
if (event.commandAuthorized !== true) return { handled: true };
|
|
329
|
+
const prompt = event.bodyForAgent?.trim() || event.content?.trim() || "";
|
|
330
|
+
if (!prompt) return { handled: true };
|
|
331
|
+
if (!canMutateCodexHost(event)) return {
|
|
332
|
+
handled: true,
|
|
333
|
+
reply: { text: CODEX_NATIVE_EXECUTION_AUTH_ERROR }
|
|
334
|
+
};
|
|
335
|
+
const nativeExecutionBlock = data.kind === "codex-cli-node-session" ? resolveCodexNativeSandboxBlock({
|
|
336
|
+
config: options.config,
|
|
337
|
+
sessionKey: event.sessionKey ?? ctx.sessionKey,
|
|
338
|
+
surface: "Codex CLI node conversation binding"
|
|
339
|
+
}) : resolveCodexNativeExecutionBlock({
|
|
340
|
+
config: options.config,
|
|
341
|
+
sessionKey: event.sessionKey ?? ctx.sessionKey,
|
|
342
|
+
agentId: data.agentId,
|
|
343
|
+
surface: "Codex app-server conversation binding"
|
|
344
|
+
});
|
|
345
|
+
if (nativeExecutionBlock) return {
|
|
346
|
+
handled: true,
|
|
347
|
+
reply: { text: nativeExecutionBlock }
|
|
348
|
+
};
|
|
349
|
+
if (data.kind === "codex-cli-node-session") {
|
|
350
|
+
const resume = options.resumeCodexCliSessionOnNode;
|
|
351
|
+
if (!resume) return {
|
|
352
|
+
handled: true,
|
|
353
|
+
reply: { text: "Codex CLI node binding is unavailable because Gateway node runtime is not attached." }
|
|
354
|
+
};
|
|
355
|
+
try {
|
|
356
|
+
return {
|
|
357
|
+
handled: true,
|
|
358
|
+
reply: (await enqueueBoundTurn(`${data.nodeId}:${data.sessionId}`, async () => {
|
|
359
|
+
return { reply: { text: (await resume({
|
|
360
|
+
nodeId: data.nodeId,
|
|
361
|
+
sessionId: data.sessionId,
|
|
362
|
+
prompt,
|
|
363
|
+
cwd: data.cwd,
|
|
364
|
+
timeoutMs: options.timeoutMs
|
|
365
|
+
})).text.trim() || "Codex completed without a text reply." } };
|
|
366
|
+
})).reply
|
|
367
|
+
};
|
|
368
|
+
} catch (error) {
|
|
369
|
+
return {
|
|
370
|
+
handled: true,
|
|
371
|
+
reply: { text: `Codex CLI node turn failed: ${formatCodexDisplayText(formatErrorMessage(error))}` }
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
return {
|
|
377
|
+
handled: true,
|
|
378
|
+
reply: (await enqueueBoundTurn(data.bindingId, () => runBoundTurnWithMissingThreadRecovery({
|
|
379
|
+
bindingStore: options.bindingStore,
|
|
380
|
+
data,
|
|
381
|
+
prompt,
|
|
382
|
+
event,
|
|
383
|
+
config: options.config,
|
|
384
|
+
sessionKey: event.sessionKey ?? ctx.sessionKey,
|
|
385
|
+
pluginConfig: options.pluginConfig,
|
|
386
|
+
timeoutMs: options.timeoutMs
|
|
387
|
+
}))).reply
|
|
388
|
+
};
|
|
389
|
+
} catch (error) {
|
|
390
|
+
return {
|
|
391
|
+
handled: true,
|
|
392
|
+
reply: { text: `Codex app-server turn failed: ${formatCodexDisplayText(formatErrorMessage(error))}` }
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
async function handleCodexConversationBindingResolved(event, options) {
|
|
397
|
+
if (event.status !== "denied") return;
|
|
398
|
+
const data = readCodexConversationBindingDataRecord(event.request.data ?? {});
|
|
399
|
+
if (!data || data.kind !== "codex-app-server-session") return;
|
|
400
|
+
const identity = conversationBindingIdentity(data);
|
|
401
|
+
const binding = await options.bindingStore.read(identity);
|
|
402
|
+
if (!data.start?.id || binding?.conversationStartId === data.start.id) await options.bindingStore.mutate(identity, { kind: "clear" });
|
|
403
|
+
}
|
|
404
|
+
async function resolveThreadBindingRuntime(params) {
|
|
405
|
+
const agentLookup = buildAgentLookup({
|
|
406
|
+
agentDir: params.agentDir,
|
|
407
|
+
config: params.config
|
|
408
|
+
});
|
|
409
|
+
const modelProvider = resolveThreadRequestModelProvider({
|
|
410
|
+
authProfileId: params.authProfileId,
|
|
411
|
+
modelProvider: params.modelProvider,
|
|
412
|
+
...agentLookup
|
|
413
|
+
});
|
|
414
|
+
const modelSelection = resolveOptionalThreadRequestModelSelection({
|
|
415
|
+
model: params.model,
|
|
416
|
+
modelProvider,
|
|
417
|
+
authProfileId: params.authProfileId,
|
|
418
|
+
...agentLookup
|
|
419
|
+
});
|
|
420
|
+
const reviewerModelProvider = resolveModelBackedReviewerPolicyProvider({
|
|
421
|
+
authProfileId: params.authProfileId,
|
|
422
|
+
modelProvider: params.modelProvider,
|
|
423
|
+
...agentLookup
|
|
424
|
+
});
|
|
425
|
+
const { execPolicy, runtime } = await resolveConversationAppServerRuntime({
|
|
426
|
+
pluginConfig: params.pluginConfig,
|
|
427
|
+
config: params.config,
|
|
428
|
+
agentId: params.agentId,
|
|
429
|
+
sessionKey: params.sessionKey,
|
|
430
|
+
workspaceDir: params.workspaceDir,
|
|
431
|
+
modelProvider: reviewerModelProvider,
|
|
432
|
+
model: params.model,
|
|
433
|
+
agentDir: params.agentDir
|
|
434
|
+
});
|
|
435
|
+
const modelScopedRuntime = resolveCodexAppServerForModelProvider({
|
|
436
|
+
appServer: runtime,
|
|
437
|
+
provider: reviewerModelProvider,
|
|
438
|
+
model: params.model,
|
|
439
|
+
config: params.config,
|
|
440
|
+
env: process.env,
|
|
441
|
+
agentDir: params.agentDir
|
|
442
|
+
});
|
|
443
|
+
assertNativeConversationApprovalPolicySupported({
|
|
444
|
+
execPolicy,
|
|
445
|
+
approvalPolicy: execPolicy?.touched ? modelScopedRuntime.approvalPolicy : params.approvalPolicy ?? modelScopedRuntime.approvalPolicy,
|
|
446
|
+
approvalsReviewer: modelScopedRuntime.approvalsReviewer,
|
|
447
|
+
modelBackedApprovalsReviewerUnavailable: !canUseCodexModelBackedApprovalsReviewerForModel({
|
|
448
|
+
modelProvider: reviewerModelProvider,
|
|
449
|
+
model: params.model,
|
|
450
|
+
config: params.config,
|
|
451
|
+
env: process.env,
|
|
452
|
+
agentDir: params.agentDir
|
|
453
|
+
})
|
|
454
|
+
});
|
|
455
|
+
const client = await getLeasedSharedCodexAppServerClient({
|
|
456
|
+
startOptions: runtime.start,
|
|
457
|
+
timeoutMs: runtime.requestTimeoutMs,
|
|
458
|
+
authProfileId: params.authProfileId,
|
|
459
|
+
...agentLookup
|
|
460
|
+
});
|
|
461
|
+
return {
|
|
462
|
+
execPolicy,
|
|
463
|
+
runtime: modelScopedRuntime,
|
|
464
|
+
agentLookup,
|
|
465
|
+
model: modelSelection?.model,
|
|
466
|
+
modelProvider: modelSelection?.modelProvider ?? modelProvider,
|
|
467
|
+
client
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function buildThreadRequestRuntimeOptions(params, resolved) {
|
|
471
|
+
const serviceTier = params.serviceTier ?? resolved.runtime.serviceTier;
|
|
472
|
+
const sandbox = resolved.execPolicy?.touched ? resolved.runtime.sandbox : params.sandbox ?? resolved.runtime.sandbox;
|
|
473
|
+
return {
|
|
474
|
+
approvalPolicy: resolved.execPolicy?.touched ? resolved.runtime.approvalPolicy : params.approvalPolicy ?? resolved.runtime.approvalPolicy,
|
|
475
|
+
approvalsReviewer: resolved.runtime.approvalsReviewer,
|
|
476
|
+
...codexConversationSandboxOrPermissions(resolved.runtime, sandbox),
|
|
477
|
+
...serviceTier ? { serviceTier } : {}
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function codexConversationSandboxOrPermissions(runtime, sandbox) {
|
|
481
|
+
const networkProxy = runtime.networkProxy;
|
|
482
|
+
if (networkProxy) return { config: networkProxy.configPatch };
|
|
483
|
+
return { sandbox };
|
|
484
|
+
}
|
|
485
|
+
async function requestNewConversationBindingThread(params, resolved) {
|
|
486
|
+
return await resolved.client.request("thread/start", {
|
|
487
|
+
cwd: params.workspaceDir,
|
|
488
|
+
...resolved.model ? { model: resolved.model } : {},
|
|
489
|
+
...resolved.modelProvider ? { modelProvider: resolved.modelProvider } : {},
|
|
490
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
491
|
+
...buildThreadRequestRuntimeOptions(params, resolved),
|
|
492
|
+
developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
|
|
493
|
+
experimentalRawEvents: true
|
|
494
|
+
}, { timeoutMs: resolved.runtime.requestTimeoutMs });
|
|
495
|
+
}
|
|
496
|
+
async function writeThreadBindingFromResponse(params, resolved, response) {
|
|
497
|
+
const runtimeApprovalPolicy = typeof resolved.runtime.approvalPolicy === "string" ? resolved.runtime.approvalPolicy : void 0;
|
|
498
|
+
if (!await params.bindingStore.mutate(params.identity, {
|
|
499
|
+
kind: "set",
|
|
500
|
+
binding: {
|
|
501
|
+
threadId: response.thread.id,
|
|
502
|
+
cwd: response.thread.cwd ?? params.workspaceDir,
|
|
503
|
+
authProfileId: params.authProfileId,
|
|
504
|
+
model: response.model ?? resolved.model ?? params.model,
|
|
505
|
+
modelProvider: normalizeCodexAppServerBindingModelProvider({
|
|
506
|
+
authProfileId: params.authProfileId,
|
|
507
|
+
modelProvider: response.modelProvider ?? resolved.modelProvider ?? params.modelProvider,
|
|
508
|
+
...resolved.agentLookup
|
|
509
|
+
}),
|
|
510
|
+
approvalPolicy: resolved.execPolicy?.touched ? runtimeApprovalPolicy : params.approvalPolicy ?? runtimeApprovalPolicy,
|
|
511
|
+
sandbox: resolved.execPolicy?.touched ? resolved.runtime.sandbox : params.sandbox ?? resolved.runtime.sandbox,
|
|
512
|
+
serviceTier: params.serviceTier ?? resolved.runtime.serviceTier ?? void 0,
|
|
513
|
+
networkProxyProfileName: resolved.runtime.networkProxy?.profileName,
|
|
514
|
+
networkProxyConfigFingerprint: resolved.runtime.networkProxy?.configFingerprint
|
|
515
|
+
}
|
|
516
|
+
})) throw new Error("Codex conversation binding changed while storing its thread.");
|
|
517
|
+
}
|
|
518
|
+
async function attachExistingThread(params) {
|
|
519
|
+
const resolved = await resolveThreadBindingRuntime(params);
|
|
520
|
+
try {
|
|
521
|
+
await writeThreadBindingFromResponse(params, resolved, resolved.runtime.networkProxy ? await requestNewConversationBindingThread(params, resolved) : await resolved.client.request(CODEX_CONTROL_METHODS.resumeThread, {
|
|
522
|
+
threadId: params.threadId,
|
|
523
|
+
...resolved.model ? { model: resolved.model } : {},
|
|
524
|
+
...resolved.modelProvider ? { modelProvider: resolved.modelProvider } : {},
|
|
525
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
526
|
+
...buildThreadRequestRuntimeOptions(params, resolved)
|
|
527
|
+
}, { timeoutMs: resolved.runtime.requestTimeoutMs }));
|
|
528
|
+
} finally {
|
|
529
|
+
releaseLeasedSharedCodexAppServerClient(resolved.client);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function createThread(params) {
|
|
533
|
+
const resolved = await resolveThreadBindingRuntime(params);
|
|
534
|
+
try {
|
|
535
|
+
await writeThreadBindingFromResponse(params, resolved, await requestNewConversationBindingThread(params, resolved));
|
|
536
|
+
} finally {
|
|
537
|
+
releaseLeasedSharedCodexAppServerClient(resolved.client);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async function runBoundTurn(params) {
|
|
541
|
+
const agentLookup = buildAgentLookup({
|
|
542
|
+
agentDir: params.data.agentDir,
|
|
543
|
+
config: params.config
|
|
544
|
+
});
|
|
545
|
+
const identity = conversationBindingIdentity(params.data);
|
|
546
|
+
const binding = await params.bindingStore.read(identity);
|
|
547
|
+
if (!binding?.threadId) throw new Error("bound Codex conversation has no thread binding");
|
|
548
|
+
let threadId = binding.threadId;
|
|
549
|
+
const workspaceDir = binding.cwd || params.data.workspaceDir;
|
|
550
|
+
const reviewerModelProvider = resolveModelBackedReviewerPolicyProvider({
|
|
551
|
+
authProfileId: binding.authProfileId,
|
|
552
|
+
modelProvider: binding.modelProvider,
|
|
553
|
+
...agentLookup
|
|
554
|
+
});
|
|
555
|
+
const { execPolicy, runtime } = await resolveConversationAppServerRuntime({
|
|
556
|
+
pluginConfig: params.pluginConfig,
|
|
557
|
+
config: params.config,
|
|
558
|
+
agentId: params.data.agentId,
|
|
559
|
+
sessionKey: params.sessionKey,
|
|
560
|
+
workspaceDir,
|
|
561
|
+
modelProvider: reviewerModelProvider,
|
|
562
|
+
model: binding.model,
|
|
563
|
+
agentDir: params.data.agentDir
|
|
564
|
+
});
|
|
565
|
+
const modelScopedRuntime = resolveCodexAppServerForModelProvider({
|
|
566
|
+
appServer: runtime,
|
|
567
|
+
provider: reviewerModelProvider,
|
|
568
|
+
model: binding.model,
|
|
569
|
+
config: params.config,
|
|
570
|
+
env: process.env,
|
|
571
|
+
agentDir: params.data.agentDir
|
|
572
|
+
});
|
|
573
|
+
const modelBackedApprovalsReviewerUnavailable = !canUseCodexModelBackedApprovalsReviewerForModel({
|
|
574
|
+
modelProvider: reviewerModelProvider,
|
|
575
|
+
model: binding.model,
|
|
576
|
+
config: params.config,
|
|
577
|
+
env: process.env,
|
|
578
|
+
agentDir: params.data.agentDir
|
|
579
|
+
});
|
|
580
|
+
const useModelScopedPolicy = execPolicy?.touched === true || modelBackedApprovalsReviewerUnavailable;
|
|
581
|
+
const approvalPolicy = useModelScopedPolicy ? modelScopedRuntime.approvalPolicy : binding.approvalPolicy ?? modelScopedRuntime.approvalPolicy;
|
|
582
|
+
const sandbox = useModelScopedPolicy ? modelScopedRuntime.sandbox : binding.sandbox ?? modelScopedRuntime.sandbox;
|
|
583
|
+
const permissionProfile = modelScopedRuntime.networkProxy?.profileName;
|
|
584
|
+
const networkProxyConfigFingerprint = modelScopedRuntime.networkProxy?.configFingerprint;
|
|
585
|
+
const networkProxyBindingChanged = binding.networkProxyProfileName !== permissionProfile || binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint;
|
|
586
|
+
const serviceTier = binding.serviceTier ?? runtime.serviceTier;
|
|
587
|
+
let useStickyNetworkProfile = permissionProfile !== void 0 && binding.networkProxyProfileName === permissionProfile && binding.networkProxyConfigFingerprint === networkProxyConfigFingerprint;
|
|
588
|
+
assertNativeConversationApprovalPolicySupported({
|
|
589
|
+
execPolicy,
|
|
590
|
+
approvalPolicy,
|
|
591
|
+
approvalsReviewer: modelScopedRuntime.approvalsReviewer,
|
|
592
|
+
modelBackedApprovalsReviewerUnavailable
|
|
593
|
+
});
|
|
594
|
+
const modelSelection = binding.model ? resolveCodexAppServerRequestModelSelection({
|
|
595
|
+
model: binding.model,
|
|
596
|
+
modelProvider: binding.modelProvider,
|
|
597
|
+
authProfileId: binding.authProfileId,
|
|
598
|
+
...agentLookup
|
|
599
|
+
}) : void 0;
|
|
600
|
+
const client = await getLeasedSharedCodexAppServerClient({
|
|
601
|
+
startOptions: runtime.start,
|
|
602
|
+
timeoutMs: runtime.requestTimeoutMs,
|
|
603
|
+
authProfileId: binding.authProfileId,
|
|
604
|
+
...agentLookup
|
|
605
|
+
});
|
|
606
|
+
let notificationCleanup = () => void 0;
|
|
607
|
+
let requestCleanup = () => void 0;
|
|
608
|
+
try {
|
|
609
|
+
if (networkProxyBindingChanged) {
|
|
610
|
+
const response = assertCodexThreadStartResponse(await client.request("thread/start", {
|
|
611
|
+
cwd: workspaceDir,
|
|
612
|
+
...modelSelection?.model ? { model: modelSelection.model } : {},
|
|
613
|
+
...modelSelection?.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
614
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
615
|
+
approvalPolicy,
|
|
616
|
+
approvalsReviewer: modelScopedRuntime.approvalsReviewer,
|
|
617
|
+
...modelScopedRuntime.networkProxy ? { config: modelScopedRuntime.networkProxy.configPatch } : { sandbox },
|
|
618
|
+
...serviceTier ? { serviceTier } : {},
|
|
619
|
+
developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
|
|
620
|
+
experimentalRawEvents: true
|
|
621
|
+
}, { timeoutMs: runtime.requestTimeoutMs }));
|
|
622
|
+
threadId = response.thread.id;
|
|
623
|
+
if (!await params.bindingStore.mutate(identity, {
|
|
624
|
+
kind: "set",
|
|
625
|
+
binding: {
|
|
626
|
+
threadId,
|
|
627
|
+
cwd: response.thread.cwd ?? workspaceDir,
|
|
628
|
+
authProfileId: binding.authProfileId,
|
|
629
|
+
model: response.model ?? modelSelection?.model ?? binding.model,
|
|
630
|
+
modelProvider: normalizeCodexAppServerBindingModelProvider({
|
|
631
|
+
authProfileId: binding.authProfileId,
|
|
632
|
+
modelProvider: response.modelProvider ?? modelSelection?.modelProvider ?? binding.modelProvider,
|
|
633
|
+
...agentLookup
|
|
634
|
+
}),
|
|
635
|
+
approvalPolicy: typeof approvalPolicy === "string" ? approvalPolicy : void 0,
|
|
636
|
+
sandbox,
|
|
637
|
+
serviceTier: serviceTier ?? void 0,
|
|
638
|
+
networkProxyProfileName: modelScopedRuntime.networkProxy?.profileName,
|
|
639
|
+
networkProxyConfigFingerprint: modelScopedRuntime.networkProxy?.configFingerprint,
|
|
640
|
+
conversationStartId: binding.conversationStartId,
|
|
641
|
+
conversationSourceTransferComplete: binding.conversationSourceTransferComplete,
|
|
642
|
+
historyCoveredThrough: binding.historyCoveredThrough
|
|
643
|
+
}
|
|
644
|
+
})) throw new Error("Codex conversation binding changed while rotating its thread.");
|
|
645
|
+
useStickyNetworkProfile = modelScopedRuntime.networkProxy !== void 0;
|
|
646
|
+
}
|
|
647
|
+
const collector = createCodexConversationTurnCollector(threadId);
|
|
648
|
+
notificationCleanup = client.addNotificationHandler((notification) => collector.handleNotification(notification));
|
|
649
|
+
requestCleanup = client.addRequestHandler(async (request) => {
|
|
650
|
+
if (request.method === "item/tool/call") return {
|
|
651
|
+
contentItems: [{
|
|
652
|
+
type: "inputText",
|
|
653
|
+
text: "OpenClaw native Codex conversation binding does not expose dynamic OpenClaw tools yet."
|
|
654
|
+
}],
|
|
655
|
+
success: false
|
|
656
|
+
};
|
|
657
|
+
if (request.method === "item/commandExecution/requestApproval" || request.method === "item/fileChange/requestApproval") return {
|
|
658
|
+
decision: "decline",
|
|
659
|
+
reason: "OpenClaw native Codex conversation binding cannot route interactive approvals yet; use the Codex harness or explicit /acp spawn codex for that workflow."
|
|
660
|
+
};
|
|
661
|
+
if (request.method === "item/permissions/requestApproval") return {
|
|
662
|
+
permissions: {},
|
|
663
|
+
scope: "turn"
|
|
664
|
+
};
|
|
665
|
+
if (request.method.includes("requestApproval")) return {
|
|
666
|
+
decision: "decline",
|
|
667
|
+
reason: "OpenClaw native Codex conversation binding cannot route interactive approvals yet; use the Codex harness or explicit /acp spawn codex for that workflow."
|
|
668
|
+
};
|
|
669
|
+
});
|
|
670
|
+
const turnId = (await client.request("turn/start", {
|
|
671
|
+
threadId,
|
|
672
|
+
input: buildCodexConversationTurnInput({
|
|
673
|
+
prompt: params.prompt,
|
|
674
|
+
event: params.event
|
|
675
|
+
}),
|
|
676
|
+
cwd: workspaceDir,
|
|
677
|
+
approvalPolicy,
|
|
678
|
+
approvalsReviewer: modelScopedRuntime.approvalsReviewer,
|
|
679
|
+
...useStickyNetworkProfile ? {} : { sandboxPolicy: codexSandboxPolicyForTurn(sandbox, workspaceDir) },
|
|
680
|
+
...modelSelection?.model ? { model: modelSelection.model } : {},
|
|
681
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
682
|
+
...serviceTier ? { serviceTier } : {}
|
|
683
|
+
}, { timeoutMs: runtime.requestTimeoutMs })).turn.id;
|
|
684
|
+
const activeCleanup = trackCodexConversationActiveTurn({
|
|
685
|
+
identity,
|
|
686
|
+
threadId,
|
|
687
|
+
turnId
|
|
688
|
+
});
|
|
689
|
+
collector.setTurnId(turnId);
|
|
690
|
+
return { reply: { text: (await collector.wait({ timeoutMs: params.timeoutMs ?? DEFAULT_BOUND_TURN_TIMEOUT_MS }).finally(activeCleanup)).replyText.trim() || "Codex completed without a text reply." } };
|
|
691
|
+
} finally {
|
|
692
|
+
notificationCleanup();
|
|
693
|
+
requestCleanup();
|
|
694
|
+
releaseLeasedSharedCodexAppServerClient(client);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function assertNativeConversationApprovalPolicySupported(params) {
|
|
698
|
+
if (params.approvalPolicy !== "never" && (params.execPolicy?.touched === true || params.modelBackedApprovalsReviewerUnavailable && params.approvalsReviewer === "user")) throw new Error(NATIVE_CONVERSATION_INTERACTIVE_APPROVALS_UNAVAILABLE);
|
|
699
|
+
}
|
|
700
|
+
async function runBoundTurnWithMissingThreadRecovery(params) {
|
|
701
|
+
await prepareConversationBinding(params);
|
|
702
|
+
try {
|
|
703
|
+
return await runBoundTurn(params);
|
|
704
|
+
} catch (error) {
|
|
705
|
+
if (!isCodexThreadNotFoundError(error)) throw error;
|
|
706
|
+
await prepareConversationBinding(params, { forceNew: true });
|
|
707
|
+
return await runBoundTurn(params);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
async function prepareConversationBinding(params, options = {}) {
|
|
711
|
+
const identity = conversationBindingIdentity(params.data);
|
|
712
|
+
await params.bindingStore.withLease(identity, async () => {
|
|
713
|
+
const current = await params.bindingStore.read(identity);
|
|
714
|
+
const requested = params.data.start && current?.conversationStartId !== params.data.start.id ? params.data.start : void 0;
|
|
715
|
+
if (current && !requested && !options.forceNew) return;
|
|
716
|
+
const sourceIdentity = params.data.source ? sessionBindingIdentity({
|
|
717
|
+
agentId: params.data.source.agentId,
|
|
718
|
+
sessionId: params.data.source.sessionId,
|
|
719
|
+
sessionKey: params.data.source.sessionKey,
|
|
720
|
+
config: params.config
|
|
721
|
+
}) : void 0;
|
|
722
|
+
const sourceBinding = sourceIdentity ? await params.bindingStore.read(sourceIdentity) : void 0;
|
|
723
|
+
const inherited = current ?? sourceBinding;
|
|
724
|
+
const execPolicy = resolveConversationExecPolicy({
|
|
725
|
+
config: params.config,
|
|
726
|
+
agentId: params.data.agentId,
|
|
727
|
+
sessionKey: params.sessionKey
|
|
728
|
+
});
|
|
729
|
+
const agentLookup = buildAgentLookup({
|
|
730
|
+
agentDir: params.data.agentDir,
|
|
731
|
+
config: params.config
|
|
732
|
+
});
|
|
733
|
+
const bindingParams = {
|
|
734
|
+
bindingStore: params.bindingStore,
|
|
735
|
+
identity,
|
|
736
|
+
pluginConfig: params.pluginConfig,
|
|
737
|
+
workspaceDir: requested ? params.data.workspaceDir : inherited?.cwd ?? params.data.workspaceDir,
|
|
738
|
+
...agentLookup,
|
|
739
|
+
model: requested?.model ?? inherited?.model,
|
|
740
|
+
modelProvider: requested?.modelProvider ?? inherited?.modelProvider,
|
|
741
|
+
authProfileId: requested?.authProfileId ?? inherited?.authProfileId,
|
|
742
|
+
approvalPolicy: execPolicy.touched ? void 0 : inherited?.approvalPolicy,
|
|
743
|
+
sandbox: execPolicy.touched ? void 0 : inherited?.sandbox,
|
|
744
|
+
serviceTier: inherited?.serviceTier,
|
|
745
|
+
config: params.config,
|
|
746
|
+
sessionKey: params.sessionKey,
|
|
747
|
+
agentId: params.data.agentId
|
|
748
|
+
};
|
|
749
|
+
const threadId = requested?.threadId ?? (!current ? params.data.source?.threadId : void 0);
|
|
750
|
+
if (threadId && !options.forceNew) await attachExistingThread({
|
|
751
|
+
...bindingParams,
|
|
752
|
+
threadId
|
|
753
|
+
});
|
|
754
|
+
else await createThread(bindingParams);
|
|
755
|
+
const stored = await params.bindingStore.read(identity);
|
|
756
|
+
if (!stored) throw new Error("Codex conversation binding disappeared while initializing its thread.");
|
|
757
|
+
if (sourceIdentity && params.data.source && !current?.conversationSourceTransferComplete) await params.bindingStore.withLease(sourceIdentity, async () => {
|
|
758
|
+
const source = await params.bindingStore.read(sourceIdentity);
|
|
759
|
+
if (source && source.threadId === params.data.source?.threadId) await params.bindingStore.mutate(sourceIdentity, {
|
|
760
|
+
kind: "clear",
|
|
761
|
+
threadId: source.threadId
|
|
762
|
+
});
|
|
763
|
+
});
|
|
764
|
+
if (!await params.bindingStore.mutate(identity, {
|
|
765
|
+
kind: "patch",
|
|
766
|
+
threadId: stored.threadId,
|
|
767
|
+
patch: {
|
|
768
|
+
...params.data.start ? { conversationStartId: params.data.start.id } : {},
|
|
769
|
+
...sourceIdentity ? { conversationSourceTransferComplete: true } : {}
|
|
770
|
+
}
|
|
771
|
+
})) throw new Error("Codex conversation binding changed while initializing its thread.");
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
function resolveConversationExecPolicy(params) {
|
|
775
|
+
const agentId = params.agentId ?? (params.config ? resolveSessionAgentIds({
|
|
776
|
+
sessionKey: params.sessionKey,
|
|
777
|
+
config: params.config
|
|
778
|
+
}).sessionAgentId : void 0);
|
|
779
|
+
return resolveOpenClawExecPolicyForCodexAppServer({
|
|
780
|
+
config: params.config,
|
|
781
|
+
agentId,
|
|
782
|
+
execOverrides: readSessionExecOverrides({
|
|
783
|
+
config: params.config,
|
|
784
|
+
agentId,
|
|
785
|
+
sessionKey: params.sessionKey
|
|
786
|
+
}),
|
|
787
|
+
approvals: loadExecApprovals()
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
function readSessionExecOverrides(params) {
|
|
791
|
+
const sessionKey = params.sessionKey?.trim();
|
|
792
|
+
if (!params.config || !sessionKey) return;
|
|
793
|
+
if (!canReadSessionExecOverrides({
|
|
794
|
+
config: params.config,
|
|
795
|
+
agentId: params.agentId,
|
|
796
|
+
sessionKey
|
|
797
|
+
})) return;
|
|
798
|
+
const entry = getSessionEntry({
|
|
799
|
+
storePath: resolveStorePath(params.config.session?.store, { agentId: params.agentId }),
|
|
800
|
+
sessionKey,
|
|
801
|
+
readConsistency: "latest"
|
|
802
|
+
});
|
|
803
|
+
if (!entry?.execSecurity && !entry?.execAsk) return;
|
|
804
|
+
return {
|
|
805
|
+
security: entry.execSecurity,
|
|
806
|
+
ask: entry.execAsk
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function canReadSessionExecOverrides(params) {
|
|
810
|
+
const agentId = normalizeAgentIdOrDefault(params.agentId);
|
|
811
|
+
if (!agentId) return true;
|
|
812
|
+
const sessionAgentId = parseAgentIdFromSessionKey(params.sessionKey);
|
|
813
|
+
if (!sessionAgentId) return isDefaultAgentSessionKeyForAgent({
|
|
814
|
+
config: params.config,
|
|
815
|
+
agentId
|
|
816
|
+
});
|
|
817
|
+
return sessionAgentId === agentId;
|
|
818
|
+
}
|
|
819
|
+
function parseAgentIdFromSessionKey(sessionKey) {
|
|
820
|
+
const raw = sessionKey?.trim();
|
|
821
|
+
if (!raw) return;
|
|
822
|
+
const parts = raw.toLowerCase().split(":").filter(Boolean);
|
|
823
|
+
if (parts.length < 3 || parts[0] !== "agent" || !parts[2]) return;
|
|
824
|
+
return normalizeAgentIdOrDefault(parts[1]);
|
|
825
|
+
}
|
|
826
|
+
function isDefaultAgentSessionKeyForAgent(params) {
|
|
827
|
+
return normalizeAgentId(params.agentId) === resolveDefaultPolicyAgentId(params.config);
|
|
828
|
+
}
|
|
829
|
+
function resolveDefaultPolicyAgentId(config) {
|
|
830
|
+
const agents = (config.agents?.list ?? []).filter((entry) => entry !== null && typeof entry === "object");
|
|
831
|
+
return normalizeAgentId((agents.find((entry) => entry?.default) ?? agents[0])?.id);
|
|
832
|
+
}
|
|
833
|
+
function normalizeAgentIdOrDefault(value) {
|
|
834
|
+
const normalized = normalizeAgentId(value);
|
|
835
|
+
return normalized === DEFAULT_AGENT_ID && !(value ?? "").trim() ? void 0 : normalized;
|
|
836
|
+
}
|
|
837
|
+
function normalizeAgentId(value) {
|
|
838
|
+
const trimmed = (value ?? "").trim();
|
|
839
|
+
if (!trimmed) return DEFAULT_AGENT_ID;
|
|
840
|
+
const normalized = trimmed.toLowerCase();
|
|
841
|
+
if (VALID_AGENT_ID_PATTERN.test(trimmed)) return normalized;
|
|
842
|
+
return normalized.replace(INVALID_AGENT_ID_CHARS_PATTERN, "-").replace(LEADING_DASH_PATTERN, "").replace(TRAILING_DASH_PATTERN, "").slice(0, 64) || DEFAULT_AGENT_ID;
|
|
843
|
+
}
|
|
844
|
+
function isCodexThreadNotFoundError(error) {
|
|
845
|
+
const message = formatErrorMessage(error);
|
|
846
|
+
return /\bthread not found:/iu.test(message) || /\bbound Codex conversation has no thread binding\b/u.test(message);
|
|
847
|
+
}
|
|
848
|
+
function enqueueBoundTurn(key, run) {
|
|
849
|
+
return getGlobalState().queue.enqueue(key, run);
|
|
850
|
+
}
|
|
851
|
+
function resolveThreadRequestModelProvider(params) {
|
|
852
|
+
const modelProvider = params.modelProvider?.trim();
|
|
853
|
+
if (!modelProvider || modelProvider.toLowerCase() === "codex") return;
|
|
854
|
+
if (isCodexAppServerNativeAuthProfile(params) && modelProvider.toLowerCase() === "openai") return;
|
|
855
|
+
return modelProvider.toLowerCase() === "openai" ? "openai" : modelProvider;
|
|
856
|
+
}
|
|
857
|
+
function resolveOptionalThreadRequestModelSelection(params) {
|
|
858
|
+
if (!params.model?.trim()) return;
|
|
859
|
+
return resolveCodexAppServerRequestModelSelection({
|
|
860
|
+
model: params.model,
|
|
861
|
+
modelProvider: params.modelProvider,
|
|
862
|
+
authProfileId: params.authProfileId,
|
|
863
|
+
agentDir: params.agentDir,
|
|
864
|
+
config: params.config
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
function resolveModelBackedReviewerPolicyProvider(params) {
|
|
868
|
+
const modelProvider = params.modelProvider?.trim();
|
|
869
|
+
if (modelProvider && modelProvider.toLowerCase() !== "codex") return modelProvider.toLowerCase() === "openai" ? "openai" : modelProvider;
|
|
870
|
+
return isCodexAppServerNativeAuthProfile(params) ? "openai" : void 0;
|
|
871
|
+
}
|
|
872
|
+
function buildAgentLookup(params) {
|
|
873
|
+
const agentDir = params.agentDir?.trim();
|
|
874
|
+
return {
|
|
875
|
+
...agentDir ? { agentDir } : {},
|
|
876
|
+
...params.config ? { config: params.config } : {}
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function conversationBindingIdentity(data) {
|
|
880
|
+
return {
|
|
881
|
+
kind: "conversation",
|
|
882
|
+
bindingId: data.bindingId
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
//#endregion
|
|
59
886
|
//#region extensions/codex/src/migration/helpers.ts
|
|
60
887
|
async function exists(filePath) {
|
|
61
888
|
return await pathExists(filePath);
|
|
@@ -87,7 +914,7 @@ async function readJsonObject(filePath) {
|
|
|
87
914
|
//#endregion
|
|
88
915
|
//#region extensions/codex/src/migration/auth.ts
|
|
89
916
|
const OPENAI_PROVIDER_ID = "openai";
|
|
90
|
-
const OPENAI_CODEX_DEFAULT_MODEL = "openai/gpt-5.
|
|
917
|
+
const OPENAI_CODEX_DEFAULT_MODEL = "openai/gpt-5.6-sol";
|
|
91
918
|
const CODEX_IMPORT_DISPLAY_NAME = "Codex import";
|
|
92
919
|
const CODEX_REASON_AUTH_NOT_SELECTED = "auth credential migration not selected";
|
|
93
920
|
const CODEX_REASON_AUTH_PROFILE_EXISTS = "auth profile exists";
|
|
@@ -950,9 +1777,11 @@ function isLegacyDestructivePolicyRepair(existing, nextEntry) {
|
|
|
950
1777
|
const normalizedEntries = Object.entries(normalizedExisting);
|
|
951
1778
|
return normalizedEntries.length === Object.keys(nextEntry).length && normalizedEntries.every(([key, value]) => nextEntry[key] === value);
|
|
952
1779
|
}
|
|
953
|
-
function
|
|
1780
|
+
function readExistingPluginAllowDestructiveActions(existing, pluginName) {
|
|
954
1781
|
const existingEntry = isRecord(existing) ? existing : void 0;
|
|
955
|
-
|
|
1782
|
+
if (existingEntry?.pluginName !== pluginName) return;
|
|
1783
|
+
const normalized = normalizeExistingAllowDestructiveActions(existingEntry.allow_destructive_actions);
|
|
1784
|
+
return normalized === "auto" || normalized === "ask" ? normalized : void 0;
|
|
956
1785
|
}
|
|
957
1786
|
function buildPluginItems(ctx, plugins) {
|
|
958
1787
|
const baseCounts = /* @__PURE__ */ new Map();
|
|
@@ -971,7 +1800,10 @@ function buildPluginItems(ctx, plugins) {
|
|
|
971
1800
|
enabled: true,
|
|
972
1801
|
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
|
973
1802
|
pluginName: plugin.pluginName,
|
|
974
|
-
...
|
|
1803
|
+
...(() => {
|
|
1804
|
+
const allowDestructiveActions = readExistingPluginAllowDestructiveActions(existingPluginEntries[configKey], plugin.pluginName);
|
|
1805
|
+
return allowDestructiveActions ? { allow_destructive_actions: allowDestructiveActions } : {};
|
|
1806
|
+
})()
|
|
975
1807
|
};
|
|
976
1808
|
const conflict = !ctx.overwrite && hasExistingCodexPluginEntry(existingPluginEntries, configKey, plugin.pluginName, plannedEntry);
|
|
977
1809
|
items.push(createMigrationItem({
|
|
@@ -989,7 +1821,7 @@ function buildPluginItems(ctx, plugins) {
|
|
|
989
1821
|
pluginName: plugin.pluginName,
|
|
990
1822
|
sourceInstalled: plugin.installed === true,
|
|
991
1823
|
sourceEnabled: plugin.enabled === true,
|
|
992
|
-
...plannedEntry.allow_destructive_actions === "auto" ? { allowDestructiveActions:
|
|
1824
|
+
...plannedEntry.allow_destructive_actions === "auto" || plannedEntry.allow_destructive_actions === "ask" ? { allowDestructiveActions: plannedEntry.allow_destructive_actions } : {},
|
|
993
1825
|
...plugin.apps && plugin.apps.length > 0 && !shouldVerifyPluginApps(ctx) ? { sourceAppVerification: CODEX_PLUGIN_SOURCE_APP_VERIFICATION_UNVERIFIED } : {}
|
|
994
1826
|
}
|
|
995
1827
|
}));
|
|
@@ -1032,18 +1864,21 @@ function readCodexPluginMigrationConfigEntry(item, enabled) {
|
|
|
1032
1864
|
const marketplaceName = item.details?.marketplaceName;
|
|
1033
1865
|
const pluginName = item.details?.pluginName;
|
|
1034
1866
|
if (item.kind !== "plugin" || item.action !== "install" || typeof configKey !== "string" || marketplaceName !== "openai-curated" || typeof pluginName !== "string") return;
|
|
1867
|
+
const allowDestructiveActions = item.details?.allowDestructiveActions;
|
|
1035
1868
|
return {
|
|
1036
1869
|
configKey,
|
|
1037
1870
|
pluginName,
|
|
1038
1871
|
enabled,
|
|
1039
|
-
...
|
|
1872
|
+
...allowDestructiveActions === "auto" || allowDestructiveActions === "ask" ? { allowDestructiveActions } : {}
|
|
1040
1873
|
};
|
|
1041
1874
|
}
|
|
1042
1875
|
function readExistingAllowDestructiveActions(config) {
|
|
1043
1876
|
return normalizeExistingAllowDestructiveActions(readMigrationConfigPath(config, [...CODEX_PLUGIN_NATIVE_CONFIG_PATH, "allow_destructive_actions"]));
|
|
1044
1877
|
}
|
|
1045
1878
|
function normalizeExistingAllowDestructiveActions(value) {
|
|
1046
|
-
|
|
1879
|
+
if (value === "auto" || value === "on-request") return "auto";
|
|
1880
|
+
if (value === "ask") return "ask";
|
|
1881
|
+
return asBoolean(value);
|
|
1047
1882
|
}
|
|
1048
1883
|
function readExistingPluginPolicyRepairs(config) {
|
|
1049
1884
|
if (config === void 0) return {};
|
|
@@ -1397,11 +2232,6 @@ function targetCodexMarketplaceDiscoveryTimeoutMs(env = process.env) {
|
|
|
1397
2232
|
function isCodexPluginLoadWarningItem(item) {
|
|
1398
2233
|
return item.kind === "plugin" && item.action === "install" && item.status === "warning" && item.details?.warningReason === CODEX_PLUGIN_LOAD_WARNING;
|
|
1399
2234
|
}
|
|
1400
|
-
async function sleep(ms) {
|
|
1401
|
-
await new Promise((resolve) => {
|
|
1402
|
-
setTimeout(resolve, ms);
|
|
1403
|
-
});
|
|
1404
|
-
}
|
|
1405
2235
|
async function buildTargetCodexPluginAppCacheKey(ctx) {
|
|
1406
2236
|
const targets = resolveCodexMigrationTargets(ctx);
|
|
1407
2237
|
const appServer = resolveTargetCodexAppServer(ctx);
|
|
@@ -1539,12 +2369,221 @@ function buildCodexMigrationProvider(params = {}) {
|
|
|
1539
2369
|
};
|
|
1540
2370
|
}
|
|
1541
2371
|
//#endregion
|
|
1542
|
-
//#region extensions/codex/src/
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
2372
|
+
//#region extensions/codex/src/native-thread-tool.ts
|
|
2373
|
+
/**
|
|
2374
|
+
* Owner-only access to native Codex threads stored in the user's Codex home.
|
|
2375
|
+
*/
|
|
2376
|
+
const ListParamsSchema = Type.Object({
|
|
2377
|
+
action: Type.Literal("list"),
|
|
2378
|
+
archived: Type.Optional(Type.Boolean()),
|
|
2379
|
+
cursor: Type.Optional(Type.String()),
|
|
2380
|
+
limit: Type.Optional(Type.Integer({
|
|
2381
|
+
minimum: 1,
|
|
2382
|
+
maximum: 100
|
|
2383
|
+
})),
|
|
2384
|
+
search: Type.Optional(Type.String())
|
|
2385
|
+
}, { additionalProperties: false });
|
|
2386
|
+
const ReadParamsSchema = Type.Object({
|
|
2387
|
+
action: Type.Literal("read"),
|
|
2388
|
+
thread_id: Type.String(),
|
|
2389
|
+
include_turns: Type.Optional(Type.Boolean())
|
|
2390
|
+
}, { additionalProperties: false });
|
|
2391
|
+
const ForkParamsSchema = Type.Object({
|
|
2392
|
+
action: Type.Literal("fork"),
|
|
2393
|
+
thread_id: Type.String(),
|
|
2394
|
+
attach: Type.Optional(Type.Boolean({
|
|
2395
|
+
default: true,
|
|
2396
|
+
description: "Attach the fork to this OpenClaw session for its next turn."
|
|
2397
|
+
}))
|
|
2398
|
+
}, { additionalProperties: false });
|
|
2399
|
+
const RenameParamsSchema = Type.Object({
|
|
2400
|
+
action: Type.Literal("rename"),
|
|
2401
|
+
thread_id: Type.String(),
|
|
2402
|
+
name: Type.String()
|
|
2403
|
+
}, { additionalProperties: false });
|
|
2404
|
+
const ArchiveParamsSchema = Type.Object({
|
|
2405
|
+
action: Type.Literal("archive"),
|
|
2406
|
+
thread_id: Type.String(),
|
|
2407
|
+
confirm: Type.Literal(true, { description: "Required acknowledgement that the thread is closed in other Codex clients." })
|
|
2408
|
+
}, { additionalProperties: false });
|
|
2409
|
+
const UnarchiveParamsSchema = Type.Object({
|
|
2410
|
+
action: Type.Literal("unarchive"),
|
|
2411
|
+
thread_id: Type.String()
|
|
2412
|
+
}, { additionalProperties: false });
|
|
2413
|
+
const CodexThreadsParamsSchema = Type.Union([
|
|
2414
|
+
ListParamsSchema,
|
|
2415
|
+
ReadParamsSchema,
|
|
2416
|
+
ForkParamsSchema,
|
|
2417
|
+
RenameParamsSchema,
|
|
2418
|
+
ArchiveParamsSchema,
|
|
2419
|
+
UnarchiveParamsSchema
|
|
2420
|
+
]);
|
|
2421
|
+
function asRecord(value) {
|
|
2422
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2423
|
+
}
|
|
2424
|
+
function readBoolean(value, fallback = false) {
|
|
2425
|
+
return typeof value === "boolean" ? value : fallback;
|
|
2426
|
+
}
|
|
2427
|
+
function readLimit(value) {
|
|
2428
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 100 ? value : void 0;
|
|
2429
|
+
}
|
|
2430
|
+
function resolveToolSession(context, runtime) {
|
|
2431
|
+
const sessionKey = context.sessionKey?.trim();
|
|
2432
|
+
if (!sessionKey) return;
|
|
2433
|
+
const entry = runtime.agent.session.getSessionEntry({
|
|
2434
|
+
agentId: context.agentId,
|
|
2435
|
+
sessionKey,
|
|
2436
|
+
readConsistency: "latest"
|
|
2437
|
+
});
|
|
2438
|
+
const sessionId = context.sessionId?.trim() || entry?.sessionId?.trim();
|
|
2439
|
+
if (!sessionId) return;
|
|
2440
|
+
const storePath = runtime.agent.session.resolveStorePath(void 0, { agentId: context.agentId });
|
|
2441
|
+
return {
|
|
2442
|
+
sessionId,
|
|
2443
|
+
sessionFile: runtime.agent.session.resolveSessionFilePath(sessionId, entry, {
|
|
2444
|
+
agentId: context.agentId,
|
|
2445
|
+
sessionsDir: path.dirname(storePath)
|
|
2446
|
+
})
|
|
2447
|
+
};
|
|
1547
2448
|
}
|
|
2449
|
+
function readThreadId(params) {
|
|
2450
|
+
return readStringParam(params, "thread_id", {
|
|
2451
|
+
required: true,
|
|
2452
|
+
label: "thread_id"
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
function readThreadStatusType(value) {
|
|
2456
|
+
if (!isJsonObject(value) || !isJsonObject(value.thread) || !isJsonObject(value.thread.status)) return;
|
|
2457
|
+
return typeof value.thread.status.type === "string" ? value.thread.status.type : void 0;
|
|
2458
|
+
}
|
|
2459
|
+
/** Builds the native Codex thread tool only for owner runs in shared-user-home mode. */
|
|
2460
|
+
function createCodexThreadsTool(options) {
|
|
2461
|
+
if (options.context.senderIsOwner !== true) return null;
|
|
2462
|
+
if (readCodexPluginConfig(options.getPluginConfig()).appServer?.homeScope !== "user") return null;
|
|
2463
|
+
const request = options.request ?? codexControlRequest;
|
|
2464
|
+
const requestOptions = () => ({
|
|
2465
|
+
agentDir: options.context.agentDir,
|
|
2466
|
+
config: options.context.getRuntimeConfig?.() ?? options.context.runtimeConfig ?? options.context.config,
|
|
2467
|
+
sessionId: options.context.sessionId,
|
|
2468
|
+
sessionKey: options.context.sessionKey
|
|
2469
|
+
});
|
|
2470
|
+
const currentSession = () => resolveToolSession(options.context, options.runtime);
|
|
2471
|
+
const currentIdentity = (sessionId) => {
|
|
2472
|
+
return sessionBindingIdentity({
|
|
2473
|
+
sessionId,
|
|
2474
|
+
sessionKey: options.context.sessionKey,
|
|
2475
|
+
agentId: options.context.agentId,
|
|
2476
|
+
config: requestOptions().config
|
|
2477
|
+
});
|
|
2478
|
+
};
|
|
2479
|
+
const currentBinding = async (session) => session ? await options.bindingStore.read(currentIdentity(session.sessionId)) : void 0;
|
|
2480
|
+
return {
|
|
2481
|
+
name: "codex_threads",
|
|
2482
|
+
label: "Codex Threads",
|
|
2483
|
+
description: "List, read, fork, rename, archive, or restore native Codex threads. Fork to continue a thread safely across Codex clients; do not resume the same thread from two clients.",
|
|
2484
|
+
parameters: CodexThreadsParamsSchema,
|
|
2485
|
+
async execute(_toolCallId, rawParams) {
|
|
2486
|
+
const params = asRecord(rawParams);
|
|
2487
|
+
const action = readStringParam(params, "action", {
|
|
2488
|
+
required: true,
|
|
2489
|
+
label: "action"
|
|
2490
|
+
});
|
|
2491
|
+
const pluginConfig = options.getPluginConfig();
|
|
2492
|
+
if (action === "list") {
|
|
2493
|
+
const cursor = readStringParam(params, "cursor");
|
|
2494
|
+
const searchTerm = readStringParam(params, "search");
|
|
2495
|
+
return jsonResult(await request(pluginConfig, CODEX_CONTROL_METHODS.listThreads, {
|
|
2496
|
+
archived: readBoolean(params.archived),
|
|
2497
|
+
limit: readLimit(params.limit) ?? 20,
|
|
2498
|
+
modelProviders: [],
|
|
2499
|
+
sortKey: "recency_at",
|
|
2500
|
+
sortDirection: "desc",
|
|
2501
|
+
sourceKinds: [...CODEX_INTERACTIVE_THREAD_SOURCE_KINDS],
|
|
2502
|
+
...cursor ? { cursor } : {},
|
|
2503
|
+
...searchTerm ? { searchTerm } : {}
|
|
2504
|
+
}, requestOptions()));
|
|
2505
|
+
}
|
|
2506
|
+
const threadId = readThreadId(params);
|
|
2507
|
+
if (action === "read") return jsonResult(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2508
|
+
threadId,
|
|
2509
|
+
includeTurns: readBoolean(params.include_turns)
|
|
2510
|
+
}, requestOptions()));
|
|
2511
|
+
if (action === "rename") {
|
|
2512
|
+
const name = readStringParam(params, "name", {
|
|
2513
|
+
required: true,
|
|
2514
|
+
label: "name"
|
|
2515
|
+
});
|
|
2516
|
+
await request(pluginConfig, CODEX_CONTROL_METHODS.renameThread, {
|
|
2517
|
+
threadId,
|
|
2518
|
+
name
|
|
2519
|
+
}, requestOptions());
|
|
2520
|
+
return jsonResult({
|
|
2521
|
+
action,
|
|
2522
|
+
threadId,
|
|
2523
|
+
name
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
if (action === "unarchive") return jsonResult(await request(pluginConfig, CODEX_CONTROL_METHODS.unarchiveThread, { threadId }, requestOptions()));
|
|
2527
|
+
const session = currentSession();
|
|
2528
|
+
const binding = await currentBinding(session);
|
|
2529
|
+
if (action === "archive") {
|
|
2530
|
+
if (params.confirm !== true) throw new Error("confirm=true is required to archive a native Codex thread");
|
|
2531
|
+
if (binding?.threadId === threadId) {
|
|
2532
|
+
if (readThreadStatusType(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2533
|
+
threadId,
|
|
2534
|
+
includeTurns: false
|
|
2535
|
+
}, requestOptions())) === "active") throw new Error("cannot archive the Codex thread active in this OpenClaw session");
|
|
2536
|
+
}
|
|
2537
|
+
await request(pluginConfig, CODEX_CONTROL_METHODS.archiveThread, { threadId }, requestOptions());
|
|
2538
|
+
if (session && binding?.threadId === threadId) await options.bindingStore.mutate(currentIdentity(session.sessionId), {
|
|
2539
|
+
kind: "clear",
|
|
2540
|
+
threadId
|
|
2541
|
+
});
|
|
2542
|
+
return jsonResult({
|
|
2543
|
+
action,
|
|
2544
|
+
threadId
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
if (action !== "fork") throw new Error(`unsupported codex_threads action: ${action}`);
|
|
2548
|
+
const attach = readBoolean(params.attach, true);
|
|
2549
|
+
if (attach && !session) throw new Error("cannot attach a Codex fork without an active OpenClaw session");
|
|
2550
|
+
if (attach && binding?.threadId === threadId) {
|
|
2551
|
+
if (readThreadStatusType(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2552
|
+
threadId,
|
|
2553
|
+
includeTurns: false
|
|
2554
|
+
}, requestOptions())) === "active") throw new Error("cannot replace the Codex thread active in this OpenClaw turn");
|
|
2555
|
+
}
|
|
2556
|
+
const response = await request(pluginConfig, CODEX_CONTROL_METHODS.forkThread, {
|
|
2557
|
+
threadId,
|
|
2558
|
+
threadSource: "user"
|
|
2559
|
+
}, requestOptions());
|
|
2560
|
+
if (!isJsonObject(response) || !isJsonObject(response.thread)) throw new Error("Codex app-server returned an invalid thread/fork response");
|
|
2561
|
+
const forkThreadId = typeof response.thread.id === "string" && response.thread.id.trim() ? response.thread.id : void 0;
|
|
2562
|
+
if (!forkThreadId) throw new Error("Codex app-server thread/fork response did not include a thread id");
|
|
2563
|
+
if (attach && session) {
|
|
2564
|
+
if (!await options.bindingStore.mutate(currentIdentity(session.sessionId), {
|
|
2565
|
+
kind: "set",
|
|
2566
|
+
binding: {
|
|
2567
|
+
threadId: forkThreadId,
|
|
2568
|
+
cwd: typeof response.thread.cwd === "string" ? response.thread.cwd : options.context.workspaceDir ?? "",
|
|
2569
|
+
model: typeof response.model === "string" ? response.model : void 0,
|
|
2570
|
+
modelProvider: typeof response.modelProvider === "string" ? response.modelProvider : void 0,
|
|
2571
|
+
historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString()
|
|
2572
|
+
}
|
|
2573
|
+
})) throw new Error("Codex session binding changed before the fork could be attached");
|
|
2574
|
+
}
|
|
2575
|
+
return jsonResult({
|
|
2576
|
+
action,
|
|
2577
|
+
sourceThreadId: threadId,
|
|
2578
|
+
thread: response.thread,
|
|
2579
|
+
attached: attach
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
//#endregion
|
|
2585
|
+
//#region extensions/codex/src/web-search-provider.ts
|
|
2586
|
+
const loadCodexWebSearchRuntime = createLazyRuntimeModule(() => import("./web-search-provider.runtime-BSlriav6.js"));
|
|
1548
2587
|
const CodexWebSearchSchema = {
|
|
1549
2588
|
type: "object",
|
|
1550
2589
|
properties: { query: {
|
|
@@ -1576,6 +2615,13 @@ function createCodexWebSearchProvider(options = {}) {
|
|
|
1576
2615
|
}
|
|
1577
2616
|
//#endregion
|
|
1578
2617
|
//#region extensions/codex/index.ts
|
|
2618
|
+
const ENDED_SESSION_REASONS = /* @__PURE__ */ new Set([
|
|
2619
|
+
"new",
|
|
2620
|
+
"reset",
|
|
2621
|
+
"idle",
|
|
2622
|
+
"daily",
|
|
2623
|
+
"deleted"
|
|
2624
|
+
]);
|
|
1579
2625
|
var codex_default = definePluginEntry({
|
|
1580
2626
|
id: "codex",
|
|
1581
2627
|
name: "Codex",
|
|
@@ -1583,16 +2629,40 @@ var codex_default = definePluginEntry({
|
|
|
1583
2629
|
register(api) {
|
|
1584
2630
|
const resolveCurrentConfig = () => api.runtime.config?.current ? api.runtime.config.current() : void 0;
|
|
1585
2631
|
const resolveCurrentPluginConfig = () => resolveLivePluginConfigObject(resolveCurrentConfig, "codex", api.pluginConfig) ?? api.pluginConfig;
|
|
1586
|
-
api.
|
|
2632
|
+
const bindingStore = createLazyCodexAppServerBindingStore(api.runtime.state.openSyncKeyedStore({
|
|
2633
|
+
namespace: CODEX_APP_SERVER_BINDING_NAMESPACE,
|
|
2634
|
+
maxEntries: CODEX_APP_SERVER_BINDING_MAX_ENTRIES,
|
|
2635
|
+
overflowPolicy: "reject-new"
|
|
2636
|
+
}));
|
|
2637
|
+
api.registerAgentHarness(createCodexAppServerAgentHarness({
|
|
2638
|
+
bindingStore,
|
|
2639
|
+
resolveConfig: resolveCurrentConfig,
|
|
2640
|
+
resolvePluginConfig: resolveCurrentPluginConfig
|
|
2641
|
+
}));
|
|
1587
2642
|
api.registerProvider(buildCodexProvider({ pluginConfig: api.pluginConfig }));
|
|
1588
2643
|
api.registerMediaUnderstandingProvider(buildCodexMediaUnderstandingProvider({ pluginConfig: api.pluginConfig }));
|
|
1589
2644
|
api.registerWebSearchProvider(createCodexWebSearchProvider({ resolvePluginConfig: resolveCurrentPluginConfig }));
|
|
1590
2645
|
api.registerMigrationProvider(buildCodexMigrationProvider({ runtime: api.runtime }));
|
|
2646
|
+
api.registerTool((context) => createCodexThreadsTool({
|
|
2647
|
+
bindingStore,
|
|
2648
|
+
context,
|
|
2649
|
+
runtime: api.runtime,
|
|
2650
|
+
getPluginConfig: resolveCurrentPluginConfig
|
|
2651
|
+
}), { name: "codex_threads" });
|
|
2652
|
+
api.registerToolMetadata({
|
|
2653
|
+
toolName: "codex_threads",
|
|
2654
|
+
displayName: "Codex Threads",
|
|
2655
|
+
description: "Manage native Codex threads in the shared user Codex home.",
|
|
2656
|
+
risk: "high",
|
|
2657
|
+
tags: ["codex", "sessions"]
|
|
2658
|
+
});
|
|
1591
2659
|
for (const command of createCodexCliSessionNodeHostCommands()) api.registerNodeHostCommand(command);
|
|
1592
2660
|
for (const policy of createCodexCliSessionNodeInvokePolicies()) api.registerNodeInvokePolicy(policy);
|
|
1593
2661
|
api.registerCommand(createCodexCommand({
|
|
1594
2662
|
pluginConfig: api.pluginConfig,
|
|
2663
|
+
resolvePluginConfig: resolveCurrentPluginConfig,
|
|
1595
2664
|
deps: {
|
|
2665
|
+
bindingStore,
|
|
1596
2666
|
listCodexCliSessionsOnNode: (params) => listCodexCliSessionsOnNode({
|
|
1597
2667
|
runtime: api.runtime,
|
|
1598
2668
|
...params
|
|
@@ -1641,6 +2711,7 @@ var codex_default = definePluginEntry({
|
|
|
1641
2711
|
}
|
|
1642
2712
|
}));
|
|
1643
2713
|
api.on("inbound_claim", (event, ctx) => handleCodexConversationInboundClaim(event, ctx, {
|
|
2714
|
+
bindingStore,
|
|
1644
2715
|
pluginConfig: resolveCurrentPluginConfig(),
|
|
1645
2716
|
config: resolveCurrentConfig(),
|
|
1646
2717
|
resumeCodexCliSessionOnNode: (params) => resumeCodexCliSessionOnNode({
|
|
@@ -1648,7 +2719,35 @@ var codex_default = definePluginEntry({
|
|
|
1648
2719
|
...params
|
|
1649
2720
|
})
|
|
1650
2721
|
}));
|
|
1651
|
-
api.onConversationBindingResolved?.(handleCodexConversationBindingResolved);
|
|
2722
|
+
api.onConversationBindingResolved?.((event) => handleCodexConversationBindingResolved(event, { bindingStore }));
|
|
2723
|
+
api.on("after_compaction", async (event, ctx) => {
|
|
2724
|
+
const previousSessionId = event.previousSessionId?.trim();
|
|
2725
|
+
const sessionId = ctx.sessionId?.trim();
|
|
2726
|
+
if (!previousSessionId || !sessionId || previousSessionId === sessionId) return;
|
|
2727
|
+
const config = resolveCurrentConfig();
|
|
2728
|
+
const sessionKey = ctx.sessionKey?.trim();
|
|
2729
|
+
const { sessionBindingIdentity } = await import("./session-binding-C1ZXdP-x.js").then((n) => n.c);
|
|
2730
|
+
const identity = sessionBindingIdentity({
|
|
2731
|
+
sessionId,
|
|
2732
|
+
...sessionKey ? { sessionKey } : {},
|
|
2733
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
2734
|
+
...config ? { config } : {}
|
|
2735
|
+
});
|
|
2736
|
+
const adopted = await bindingStore.adoptSessionGeneration(identity, previousSessionId);
|
|
2737
|
+
if (adopted === "conflict") api.logger.warn?.(`codex: could not adopt compacted session generation ${sessionId} (${adopted}); secondary native compaction will skip`);
|
|
2738
|
+
});
|
|
2739
|
+
api.on("session_end", async (event, ctx) => {
|
|
2740
|
+
if (!event.reason || !ENDED_SESSION_REASONS.has(event.reason)) return;
|
|
2741
|
+
const sessionKey = event.sessionKey ?? ctx.sessionKey;
|
|
2742
|
+
const config = resolveCurrentConfig();
|
|
2743
|
+
const { sessionBindingIdentity } = await import("./session-binding-C1ZXdP-x.js").then((n) => n.c);
|
|
2744
|
+
await bindingStore.retireSessionGeneration(sessionBindingIdentity({
|
|
2745
|
+
sessionId: event.sessionId,
|
|
2746
|
+
...sessionKey ? { sessionKey } : {},
|
|
2747
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
2748
|
+
...config ? { config } : {}
|
|
2749
|
+
}));
|
|
2750
|
+
});
|
|
1652
2751
|
}
|
|
1653
2752
|
});
|
|
1654
2753
|
//#endregion
|