@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.
Files changed (42) hide show
  1. package/dist/{app-server-policy-BPTiVNsW.js → app-server-policy-C968Kgin.js} +5 -5
  2. package/dist/{attempt-notifications-BYnndyFl.js → attempt-notifications-BGsEIIDI.js} +29 -40
  3. package/dist/{command-handlers-DEPTJYk0.js → command-handlers-Dv-pzAmg.js} +278 -237
  4. package/dist/command-rpc-eLM_mtC7.js +784 -0
  5. package/dist/compact-4mVBriKT.js +603 -0
  6. package/dist/{computer-use-ClrJwoFp.js → computer-use-Bmaz333N.js} +3 -3
  7. package/dist/{config-CszD0vP3.js → config-CYEDnLJ2.js} +44 -18
  8. package/dist/conversation-binding-data-BHRpYbX3.js +121 -0
  9. package/dist/doctor-contract-api.js +590 -22
  10. package/dist/harness.js +29 -10
  11. package/dist/index.js +1134 -35
  12. package/dist/{media-understanding-provider-ZUQHN7gT.js → media-understanding-provider-BHOu9-Gg.js} +13 -8
  13. package/dist/media-understanding-provider.js +1 -1
  14. package/dist/{models-CAmfBWRl.js → models-DDmO1zwd.js} +2 -2
  15. package/dist/notification-correlation-Bo7KB3ks.js +35 -0
  16. package/dist/{plugin-app-cache-key-C9ILt7bl.js → plugin-app-cache-key-BrhVdeEf.js} +13 -5
  17. package/dist/{protocol-dh-ETiNd.js → protocol-2POPqAY4.js} +2 -1
  18. package/dist/{protocol-validators-B19q5BIX.js → protocol-validators-dZQ-UTOa.js} +205 -56
  19. package/dist/provider-capabilities-DzI8fgEu.js +6172 -0
  20. package/dist/provider-catalog.js +4 -2
  21. package/dist/{provider-B-OHpbD3.js → provider-zjPfx5Fs.js} +94 -23
  22. package/dist/provider.js +2 -2
  23. package/dist/{request-Dcd452Nk.js → request-BcJyl8KL.js} +2 -2
  24. package/dist/{run-attempt-CXxtFC9d.js → run-attempt-DNSNk_G8.js} +1250 -2992
  25. package/dist/{sandbox-guard-3tnjhjFb.js → sandbox-guard-DA2TQfZW.js} +5 -3
  26. package/dist/session-binding-C1ZXdP-x.js +627 -0
  27. package/dist/session-binding-meta-B7aEMU7g.js +6 -0
  28. package/dist/shared-client-4ICy3U6d.js +1853 -0
  29. package/dist/{side-question-C-DhgJOd.js → side-question-CMOpEhvY.js} +134 -142
  30. package/dist/{thread-lifecycle-DtT3-ehU.js → thread-lifecycle-qWE88Dn2.js} +803 -481
  31. package/dist/{web-search-provider.runtime-CkwxrXQj.js → web-search-provider.runtime-BSlriav6.js} +3 -3
  32. package/npm-shrinkwrap.json +34 -34
  33. package/openclaw.plugin.json +23 -3
  34. package/package.json +9 -6
  35. package/dist/client-CLrtWgrD.js +0 -729
  36. package/dist/client-factory-Bm6HsGob.js +0 -18
  37. package/dist/compact-aQJ0ZKhe.js +0 -278
  38. package/dist/conversation-binding-t0XMLz-j.js +0 -1557
  39. package/dist/provider-capabilities-D7hnTO76.js +0 -3201
  40. package/dist/rate-limit-cache-C7qmZ0Jh.js +0 -26
  41. package/dist/session-binding-HOuPt-E0.js +0 -338
  42. 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 { F as ensureCodexPluginActivation, I as pluginReadParams, R as defaultCodexAppInventoryCache } from "./thread-lifecycle-DtT3-ehU.js";
3
- import { d as resolveCodexAppServerRuntimeOptions, t as CODEX_PLUGINS_MARKETPLACE_NAME, u as readCodexPluginConfig } from "./config-CszD0vP3.js";
4
- import { t as buildCodexProvider } from "./provider-B-OHpbD3.js";
5
- import { t as buildCodexMediaUnderstandingProvider } from "./media-understanding-provider-ZUQHN7gT.js";
6
- import { i as describeControlFailure, n as buildCodexPluginAppCacheKey } from "./plugin-app-cache-key-C9ILt7bl.js";
7
- import { a as formatCodexDisplayText } from "./app-server-policy-BPTiVNsW.js";
8
- import { a as getLeasedSharedCodexAppServerClient, d as resolveCodexAppServerAuthAccountCacheKey, m as resolveCodexAppServerFallbackApiKeyCacheKey, o as releaseLeasedSharedCodexAppServerClient, p as resolveCodexAppServerAuthProfileIdForAgent, r as clearSharedCodexAppServerClientIfCurrentAndWait } from "./shared-client-Dtx5i7Ez.js";
9
- import { a as createCodexCliSessionNodeInvokePolicies, c as resolveCodexCliSessionForBindingOnNode, i as createCodexCliSessionNodeHostCommands, l as resumeCodexCliSessionOnNode, n as handleCodexConversationInboundClaim, s as listCodexCliSessionsOnNode, t as handleCodexConversationBindingResolved } from "./conversation-binding-t0XMLz-j.js";
10
- import { t as requestCodexAppServerJson } from "./request-Dcd452Nk.js";
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, subcommandOptions);
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-DEPTJYk0.js");
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.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 isLegacyDestructivePolicyConfigEntryRepair(existing, pluginName) {
1780
+ function readExistingPluginAllowDestructiveActions(existing, pluginName) {
954
1781
  const existingEntry = isRecord(existing) ? existing : void 0;
955
- return existingEntry?.allow_destructive_actions === "on-request" && existingEntry.pluginName === pluginName;
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
- ...isLegacyDestructivePolicyConfigEntryRepair(existingPluginEntries[configKey], plugin.pluginName) ? { allow_destructive_actions: "auto" } : {}
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: "auto" } : {},
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
- ...item.details?.allowDestructiveActions === "auto" ? { allowDestructiveActions: "auto" } : {}
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
- return value === "auto" || value === "on-request" ? "auto" : asBoolean(value);
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/web-search-provider.ts
1543
- let codexWebSearchRuntimePromise;
1544
- function loadCodexWebSearchRuntime() {
1545
- codexWebSearchRuntimePromise ??= import("./web-search-provider.runtime-CkwxrXQj.js");
1546
- return codexWebSearchRuntimePromise;
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.registerAgentHarness(createCodexAppServerAgentHarness({ resolvePluginConfig: resolveCurrentPluginConfig }));
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