@openclaw/codex 2026.7.1-beta.1 → 2026.7.1-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/{app-server-policy-xi0ZmBGO.js → app-server-policy-BUk0GLMy.js} +5 -5
- package/dist/{attempt-notifications-l9LElGOU.js → attempt-notifications-BGsEIIDI.js} +5 -40
- package/dist/{command-handlers-vZy8xX5X.js → command-handlers-UVniG5Hl.js} +267 -232
- package/dist/command-rpc-BjgEPh19.js +784 -0
- package/dist/compact-CZzbcygx.js +603 -0
- package/dist/{computer-use-FPGG-mtX.js → computer-use-DDeySrnb.js} +3 -3
- package/dist/{config-C_whBx3M.js → config-CYEDnLJ2.js} +36 -15
- package/dist/conversation-binding-data-BHRpYbX3.js +121 -0
- package/dist/doctor-contract-api.js +439 -22
- package/dist/harness.js +29 -10
- package/dist/index.js +1119 -28
- package/dist/{media-understanding-provider-C5yYzx_6.js → media-understanding-provider-DgqRvU2_.js} +13 -8
- package/dist/media-understanding-provider.js +1 -1
- package/dist/{models-Ccy1_7nN.js → models-DDmO1zwd.js} +2 -2
- package/dist/notification-correlation-Bo7KB3ks.js +35 -0
- package/dist/{plugin-app-cache-key-CDioyur-.js → plugin-app-cache-key-o-AHbdaf.js} +8 -4
- package/dist/{protocol-dh-ETiNd.js → protocol-2POPqAY4.js} +2 -1
- package/dist/{protocol-validators-CB4mtyrs.js → protocol-validators-dZQ-UTOa.js} +117 -62
- package/dist/{provider-capabilities-CE5k8cmO.js → provider-capabilities-gTCwjfmh.js} +2920 -114
- package/dist/{provider-DosqwRb6.js → provider-cc_62eQE.js} +19 -10
- package/dist/provider.js +2 -2
- package/dist/{request-BQuSQF29.js → request-BcJyl8KL.js} +2 -2
- package/dist/{run-attempt-O3Zs32KI.js → run-attempt-8SPLiDlT.js} +1016 -2877
- package/dist/{sandbox-guard-pTY2COoC.js → sandbox-guard-DA2TQfZW.js} +3 -3
- package/dist/session-binding-Dc03iwRF.js +595 -0
- package/dist/session-binding-meta-B7aEMU7g.js +6 -0
- package/dist/{shared-client-rR6TDJA3.js → shared-client-4ICy3U6d.js} +892 -103
- package/dist/{side-question-DDVgFmpv.js → side-question-DkjXvYv7.js} +132 -140
- package/dist/{thread-lifecycle-DeSMZSXr.js → thread-lifecycle-BskXnNP-.js} +609 -444
- package/dist/{web-search-provider.runtime-Q89rQhsL.js → web-search-provider.runtime-B5F42P7x.js} +3 -3
- package/npm-shrinkwrap.json +34 -34
- package/openclaw.plugin.json +20 -0
- package/package.json +9 -6
- package/dist/client-IcTBKEqA.js +0 -732
- package/dist/client-factory-CwjdQQ4j.js +0 -18
- package/dist/compact-BzB9AqN1.js +0 -278
- package/dist/conversation-binding-CEE2UyYb.js +0 -1570
- package/dist/rate-limit-cache-C7qmZ0Jh.js +0 -26
- package/dist/session-binding-Cm0Gg7Z1.js +0 -339
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 {
|
|
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 { K as defaultCodexAppInventoryCache, U as ensureCodexPluginActivation, W as pluginReadParams, d as resolveCodexAppServerRequestModelSelection, t as CODEX_NATIVE_PERSONALITY_NONE } from "./thread-lifecycle-BskXnNP-.js";
|
|
6
|
+
import { t as buildCodexProvider } from "./provider-cc_62eQE.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 { i as normalizeCodexAppServerBindingModelProvider, o as sessionBindingIdentity, r as isCodexAppServerNativeAuthProfile } from "./session-binding-Dc03iwRF.js";
|
|
10
|
+
import { t as buildCodexMediaUnderstandingProvider } from "./media-understanding-provider-DgqRvU2_.js";
|
|
11
|
+
import { i as describeControlFailure, n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS } from "./plugin-app-cache-key-o-AHbdaf.js";
|
|
12
|
+
import { a as formatCodexDisplayText, t as resolveCodexAppServerForModelProvider } from "./app-server-policy-BUk0GLMy.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-BjgEPh19.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-Dc03iwRF.js").then((n) => n.s).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-UVniG5Hl.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);
|
|
@@ -1405,11 +2232,6 @@ function targetCodexMarketplaceDiscoveryTimeoutMs(env = process.env) {
|
|
|
1405
2232
|
function isCodexPluginLoadWarningItem(item) {
|
|
1406
2233
|
return item.kind === "plugin" && item.action === "install" && item.status === "warning" && item.details?.warningReason === CODEX_PLUGIN_LOAD_WARNING;
|
|
1407
2234
|
}
|
|
1408
|
-
async function sleep(ms) {
|
|
1409
|
-
await new Promise((resolve) => {
|
|
1410
|
-
setTimeout(resolve, ms);
|
|
1411
|
-
});
|
|
1412
|
-
}
|
|
1413
2235
|
async function buildTargetCodexPluginAppCacheKey(ctx) {
|
|
1414
2236
|
const targets = resolveCodexMigrationTargets(ctx);
|
|
1415
2237
|
const appServer = resolveTargetCodexAppServer(ctx);
|
|
@@ -1547,12 +2369,221 @@ function buildCodexMigrationProvider(params = {}) {
|
|
|
1547
2369
|
};
|
|
1548
2370
|
}
|
|
1549
2371
|
//#endregion
|
|
1550
|
-
//#region extensions/codex/src/
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
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
|
+
};
|
|
1555
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-B5F42P7x.js"));
|
|
1556
2587
|
const CodexWebSearchSchema = {
|
|
1557
2588
|
type: "object",
|
|
1558
2589
|
properties: { query: {
|
|
@@ -1584,6 +2615,13 @@ function createCodexWebSearchProvider(options = {}) {
|
|
|
1584
2615
|
}
|
|
1585
2616
|
//#endregion
|
|
1586
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
|
+
]);
|
|
1587
2625
|
var codex_default = definePluginEntry({
|
|
1588
2626
|
id: "codex",
|
|
1589
2627
|
name: "Codex",
|
|
@@ -1591,16 +2629,40 @@ var codex_default = definePluginEntry({
|
|
|
1591
2629
|
register(api) {
|
|
1592
2630
|
const resolveCurrentConfig = () => api.runtime.config?.current ? api.runtime.config.current() : void 0;
|
|
1593
2631
|
const resolveCurrentPluginConfig = () => resolveLivePluginConfigObject(resolveCurrentConfig, "codex", api.pluginConfig) ?? api.pluginConfig;
|
|
1594
|
-
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
|
+
}));
|
|
1595
2642
|
api.registerProvider(buildCodexProvider({ pluginConfig: api.pluginConfig }));
|
|
1596
2643
|
api.registerMediaUnderstandingProvider(buildCodexMediaUnderstandingProvider({ pluginConfig: api.pluginConfig }));
|
|
1597
2644
|
api.registerWebSearchProvider(createCodexWebSearchProvider({ resolvePluginConfig: resolveCurrentPluginConfig }));
|
|
1598
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
|
+
});
|
|
1599
2659
|
for (const command of createCodexCliSessionNodeHostCommands()) api.registerNodeHostCommand(command);
|
|
1600
2660
|
for (const policy of createCodexCliSessionNodeInvokePolicies()) api.registerNodeInvokePolicy(policy);
|
|
1601
2661
|
api.registerCommand(createCodexCommand({
|
|
1602
2662
|
pluginConfig: api.pluginConfig,
|
|
2663
|
+
resolvePluginConfig: resolveCurrentPluginConfig,
|
|
1603
2664
|
deps: {
|
|
2665
|
+
bindingStore,
|
|
1604
2666
|
listCodexCliSessionsOnNode: (params) => listCodexCliSessionsOnNode({
|
|
1605
2667
|
runtime: api.runtime,
|
|
1606
2668
|
...params
|
|
@@ -1649,6 +2711,7 @@ var codex_default = definePluginEntry({
|
|
|
1649
2711
|
}
|
|
1650
2712
|
}));
|
|
1651
2713
|
api.on("inbound_claim", (event, ctx) => handleCodexConversationInboundClaim(event, ctx, {
|
|
2714
|
+
bindingStore,
|
|
1652
2715
|
pluginConfig: resolveCurrentPluginConfig(),
|
|
1653
2716
|
config: resolveCurrentConfig(),
|
|
1654
2717
|
resumeCodexCliSessionOnNode: (params) => resumeCodexCliSessionOnNode({
|
|
@@ -1656,7 +2719,35 @@ var codex_default = definePluginEntry({
|
|
|
1656
2719
|
...params
|
|
1657
2720
|
})
|
|
1658
2721
|
}));
|
|
1659
|
-
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-Dc03iwRF.js").then((n) => n.s);
|
|
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-Dc03iwRF.js").then((n) => n.s);
|
|
2744
|
+
await bindingStore.retireSessionGeneration(sessionBindingIdentity({
|
|
2745
|
+
sessionId: event.sessionId,
|
|
2746
|
+
...sessionKey ? { sessionKey } : {},
|
|
2747
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
2748
|
+
...config ? { config } : {}
|
|
2749
|
+
}));
|
|
2750
|
+
});
|
|
1660
2751
|
}
|
|
1661
2752
|
});
|
|
1662
2753
|
//#endregion
|