@openclaw/codex 2026.7.2-beta.5 → 2026.8.1-beta.2

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 (59) hide show
  1. package/README.md +2 -0
  2. package/dist/{app-server-policy-BmDgseo_.js → app-server-policy-zcBDc1JU.js} +1 -1
  3. package/dist/attempt-client-cleanup-DMLXUfa8.js +735 -0
  4. package/dist/binding-connection-I5Bo-AsX.js +33 -0
  5. package/dist/{bounded-turn-FEbUXoYT.js → bounded-turn-qgZmzy_4.js} +71 -63
  6. package/dist/{capabilities-BHRSI6Cn.js → capabilities-CybGI1oz.js} +8 -2
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters-ico4ZFnk.js → command-formatters-B1AEz8ZA.js} +18 -18
  9. package/dist/{command-handlers-CRmnwI2N.js → command-handlers-Db8pu_ds.js} +484 -95
  10. package/dist/{compact-dEN49UH6.js → compact-LspQaGfl.js} +213 -199
  11. package/dist/{conversation-binding-data-BHRpYbX3.js → conversation-binding-data-CW2GuupV.js} +6 -10
  12. package/dist/doctor-contract-api.js +40 -43
  13. package/dist/{dynamic-tools-LIt9ojep.js → dynamic-tools-BVdyWkUD.js} +2984 -1221
  14. package/dist/effective-mcp-catalog-DIIhAtOZ.js +119 -0
  15. package/dist/{event-projector-assistant-message-DTgnPPVF.js → event-projector-assistant-message-BwxV4d6N.js} +11 -23
  16. package/dist/{attempt-notifications-CMR2_fmR.js → event-projector-usage-e5huIQWu.js} +113 -29
  17. package/dist/harness.js +83 -13
  18. package/dist/image-payload-sanitizer-CnfoUPCE.js +65 -0
  19. package/dist/index.js +703 -329
  20. package/dist/isolated-completion-C58NbL40.js +75 -0
  21. package/dist/local-runtime-attribution-B_2mnqhb.js +19 -0
  22. package/dist/media-understanding-provider.js +5 -5
  23. package/dist/{models-BA01NEez.js → models-BFlpn1Ex.js} +12 -17
  24. package/dist/native-subagent-monitor-DyTGmTUx.js +3111 -0
  25. package/dist/{plugin-app-cache-key-DSXX-rXO.js → plugin-app-cache-key-C3H2CVp6.js} +135 -42
  26. package/dist/plugin-marketplace-discovery-CzrGJdCR.js +385 -0
  27. package/dist/{computer-use-BjGylO-E.js → plugin-metadata-cache-CNJPj4_b.js} +211 -113
  28. package/dist/protocol-validators-B4F7L8Rf.js +2346 -0
  29. package/dist/{rate-limits-S_PXs0ul.js → rate-limits-DZXmOL8-.js} +17 -35
  30. package/dist/{request-CXpPennK.js → request-BRl5_UfX.js} +4 -4
  31. package/dist/{run-attempt-C5GTPJOE.js → run-attempt-P8BdxC1I.js} +1996 -5728
  32. package/dist/{runtime-artifact-C2ITXdhM.js → runtime-artifact-BVSPdd6g.js} +91 -40
  33. package/dist/{sandbox-guard-D1Wky__S.js → sandbox-guard-Cmwc9eIk.js} +10 -13
  34. package/dist/{session-binding-DlVQ-Pxs.js → session-binding-DPxIvMgE.js} +305 -271
  35. package/dist/{session-catalog-CP2oCcEb.js → session-catalog-xwNi_SDM.js} +418 -144
  36. package/dist/{session-cli-CqZ0Fp2a.js → session-cli-7hgXZDV7.js} +1 -1
  37. package/dist/session-retirement-DoMMJFKm.js +36 -0
  38. package/dist/{session-upstream-marker-QE-m5QFG.js → session-upstream-marker-CrIFX06J.js} +1 -1
  39. package/dist/{settled-turn-finalizer-CA_2QXIE.js → settled-turn-finalizer-BRWBO4MO.js} +36 -15
  40. package/dist/{shared-client-BVKE40c-.js → shared-client-kIBbGjk4.js} +721 -240
  41. package/dist/{side-question-CO12sHxo.js → side-question-9HAL26l9.js} +102 -53
  42. package/dist/{thread-lifecycle-DRg_FdzC.js → thread-lifecycle-Bu1BTDXr.js} +1355 -660
  43. package/dist/thread-ownership-6kOQHlyC.js +92 -0
  44. package/dist/thread-resume-CPrJNQVM.js +30 -0
  45. package/dist/{transcript-mirror-uKA-A4tB.js → transcript-mirror-B9gGgnJS.js} +202 -182
  46. package/dist/{transport-stdio-DXgGZ7Ib.js → transport-stdio-DIImwDVS.js} +11 -0
  47. package/dist/upstream-prompt-provenance-ClRBije0.js +41 -0
  48. package/dist/{upstream-session-fork-_Vrz4WJr.js → upstream-session-fork-CE-_LdnP.js} +5 -5
  49. package/dist/{usage-UpVbdX5G.js → usage-CHlV0e-o.js} +3 -3
  50. package/dist/{web-search-provider.runtime-CaQMYDbp.js → web-search-provider.runtime-DRw2Vl42.js} +8 -9
  51. package/openclaw.plugin.json +18 -3
  52. package/package.json +6 -7
  53. package/dist/attempt-client-cleanup-Ia52yRWc.js +0 -127
  54. package/dist/conversation-control-DE7d9DOj.js +0 -287
  55. package/dist/event-projector-usage-tvgb1NCX.js +0 -69
  56. package/dist/notification-correlation-Bj_Ak_jI.js +0 -67
  57. package/dist/protocol-validators-Dm4eXZmt.js +0 -6337
  58. package/dist/realtime-voice-api-BPcA1uN9.js +0 -494
  59. package/dist/realtime-voice-api.js +0 -2
@@ -1,11 +1,11 @@
1
- import { U as resolveCodexAppServerHomeDir } from "./shared-client-BVKE40c-.js";
2
- import { x as resolveCodexAppServerUserHomeDir } from "./session-binding-DlVQ-Pxs.js";
1
+ import { A as resolveCodexAppServerUserHomeDir } from "./session-binding-DPxIvMgE.js";
2
+ import { dt as resolveCodexAppServerLocalHomeDir, ut as resolveCodexAppServerHomeDir } from "./shared-client-kIBbGjk4.js";
3
3
  import { createRequire } from "node:module";
4
+ import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
5
+ import { readPluginPackageVersion } from "openclaw/plugin-sdk/extension-shared";
4
6
  import { isFutureDateTimestampMs, resolveDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
5
7
  import { OPENCLAW_VERSION, embeddedAgentLog } from "openclaw/plugin-sdk/agent-harness-runtime";
6
8
  import { createHash } from "node:crypto";
7
- import { readPluginPackageVersion } from "openclaw/plugin-sdk/extension-shared";
8
- import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
9
9
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
10
10
  //#region extensions/codex/src/app-server/app-inventory-cache.ts
11
11
  /**
@@ -14,7 +14,7 @@ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
14
14
  */
15
15
  /** Default app inventory cache freshness window. */
16
16
  const CODEX_APP_INVENTORY_CACHE_TTL_MS = 3600 * 1e3;
17
- const CODEX_TARGETED_APP_INVENTORY_LIMIT = 1e3;
17
+ const CODEX_APP_READ_BATCH_LIMIT = 100;
18
18
  const MAX_SERIALIZED_ERROR_MESSAGE_LENGTH = 500;
19
19
  /** In-memory app inventory cache with coalesced refreshes per key. */
20
20
  var CodexAppInventoryCache = class {
@@ -55,15 +55,25 @@ var CodexAppInventoryCache = class {
55
55
  refreshNow(params) {
56
56
  return this.refresh(params);
57
57
  }
58
- /** Marks a key stale and records the reason as a diagnostic. */
59
- invalidate(key, reason, nowMs = Date.now()) {
58
+ /**
59
+ * Marks a key stale and records the reason as a diagnostic. A scope names
60
+ * the app ids the invalidation concerns so a covering targeted refresh can
61
+ * clear it; without one, only a complete refresh revalidates the entry.
62
+ */
63
+ invalidate(key, reason, nowMs = Date.now(), invalidatedAppIds) {
60
64
  this.revision += 1;
65
+ this.refreshTokens.set(key, (this.refreshTokens.get(key) ?? 0) + 1);
66
+ this.inFlight.delete(key);
61
67
  const diagnostic = {
62
68
  message: reason,
63
69
  atMs: nowMs
64
70
  };
65
71
  const entry = this.entries.get(key);
66
72
  if (entry) {
73
+ const scope = invalidatedAppIds?.filter(Boolean) ?? [];
74
+ if (!entry.invalidated) entry.invalidatedAppIds = scope.length ? [...scope].toSorted() : void 0;
75
+ else if (entry.invalidatedAppIds && scope.length) entry.invalidatedAppIds = Array.from(/* @__PURE__ */ new Set([...entry.invalidatedAppIds, ...scope])).toSorted();
76
+ else entry.invalidatedAppIds = void 0;
67
77
  entry.invalidated = true;
68
78
  entry.lastError = diagnostic;
69
79
  entry.revision = this.revision;
@@ -83,42 +93,60 @@ var CodexAppInventoryCache = class {
83
93
  return this.revision;
84
94
  }
85
95
  scheduleRefresh(params) {
86
- if (this.inFlight.has(params.key) && !params.forceRefetch) return true;
87
- const promise = this.refresh(params);
88
- this.inFlight.set(params.key, promise);
89
- promise.catch(() => void 0);
96
+ const existing = this.inFlight.get(params.key);
97
+ if (existing && !params.forceRefetch && doesInFlightRefreshCover(existing, params)) return true;
98
+ this.refresh(params).catch(() => void 0);
90
99
  return true;
91
100
  }
92
101
  async refresh(params) {
93
102
  const existing = this.inFlight.get(params.key);
94
- if (existing && !params.forceRefetch) return existing;
103
+ if (existing && !params.forceRefetch && doesInFlightRefreshCover(existing, params)) return existing.promise;
95
104
  const refreshToken = (this.refreshTokens.get(params.key) ?? 0) + 1;
96
105
  this.refreshTokens.set(params.key, refreshToken);
97
- const promise = this.refreshUncoalesced(params, refreshToken);
98
- this.inFlight.set(params.key, promise);
106
+ const previousRefresh = params.forceRefetch ? void 0 : existing?.promise;
107
+ const promise = this.refreshUncoalesced(params, refreshToken, previousRefresh);
108
+ const currentRefresh = {
109
+ promise,
110
+ targetAppIds: new Set(params.targetAppIds?.filter(Boolean) ?? [])
111
+ };
112
+ this.inFlight.set(params.key, currentRefresh);
99
113
  try {
100
114
  return await promise;
101
115
  } finally {
102
- if (this.inFlight.get(params.key) === promise) this.inFlight.delete(params.key);
116
+ if (this.inFlight.get(params.key) === currentRefresh) this.inFlight.delete(params.key);
103
117
  }
104
118
  }
105
- async refreshUncoalesced(params, refreshToken) {
119
+ async refreshUncoalesced(params, refreshToken, previousRefresh) {
106
120
  const nowMs = resolveDateTimestampMs(params.nowMs);
107
121
  try {
108
- const apps = await listAllApps(params.request, params.forceRefetch ?? false, params.targetAppIds);
122
+ let previousRefreshSucceeded = false;
123
+ if (previousRefresh) try {
124
+ await previousRefresh;
125
+ previousRefreshSucceeded = true;
126
+ } catch {}
127
+ const inventory = await readInstalledApps(params.request, {
128
+ forceRefresh: params.forceRefetch === true || !this.entries.has(params.key) && !previousRefreshSucceeded,
129
+ targetAppIds: params.targetAppIds
130
+ });
109
131
  this.revision += 1;
110
132
  const expiresAtMs = resolveExpiresAtMsFromDurationMs(this.ttlMs, { nowMs }) ?? 0;
111
133
  const snapshot = {
112
134
  key: params.key,
113
- apps,
135
+ apps: inventory.apps,
136
+ installedApps: inventory.installedApps,
137
+ ...params.targetAppIds?.some(Boolean) ? { targetAppIds: Array.from(new Set(params.targetAppIds.filter(Boolean))).toSorted() } : {},
114
138
  fetchedAtMs: nowMs,
115
139
  expiresAtMs,
116
140
  revision: this.revision
117
141
  };
118
142
  if (this.refreshTokens.get(params.key) === refreshToken) {
143
+ const existingEntry = this.entries.get(params.key);
144
+ const published = resolvePublishedInventorySnapshot(existingEntry, snapshot, nowMs);
145
+ const remaining = resolveRemainingInvalidationScope(existingEntry, snapshot);
119
146
  this.entries.set(params.key, {
120
- ...snapshot,
121
- invalidated: false
147
+ ...published,
148
+ ...remaining,
149
+ ...remaining.invalidated && existingEntry?.lastError ? { lastError: existingEntry.lastError } : {}
122
150
  });
123
151
  this.diagnostics.delete(params.key);
124
152
  }
@@ -140,6 +168,59 @@ var CodexAppInventoryCache = class {
140
168
  }
141
169
  }
142
170
  };
171
+ /**
172
+ * Publish policy for refreshed snapshots. A complete refresh replaces the
173
+ * entry, but a targeted refresh only rewrites its own target rows in place —
174
+ * replacing the whole entry with a narrow snapshot makes agents that share
175
+ * the runtime identity see each other's plugin apps vanish and force a hosted
176
+ * connector refresh per turn. The refreshed snapshot stays authoritative for
177
+ * its target set, so target rows it no longer returns are deleted.
178
+ */
179
+ function resolvePublishedInventorySnapshot(existing, snapshot, nowMs) {
180
+ if (!snapshot.targetAppIds?.length || !existing) return snapshot;
181
+ if (!isFutureDateTimestampMs(existing.expiresAtMs, { nowMs })) return snapshot;
182
+ const refreshedTargetIds = new Set(snapshot.targetAppIds);
183
+ const { targetAppIds: snapshotTargetAppIds, ...snapshotBase } = snapshot;
184
+ return {
185
+ ...snapshotBase,
186
+ fetchedAtMs: existing.fetchedAtMs,
187
+ expiresAtMs: existing.expiresAtMs,
188
+ apps: mergeRefreshedRows(existing.apps, snapshot.apps, refreshedTargetIds),
189
+ installedApps: mergeRefreshedRows(existing.installedApps, snapshot.installedApps, refreshedTargetIds),
190
+ ...existing.targetAppIds?.length ? { targetAppIds: Array.from(/* @__PURE__ */ new Set([...existing.targetAppIds, ...snapshotTargetAppIds])).toSorted() } : {}
191
+ };
192
+ }
193
+ /** Replaces refreshed target rows in place, deletes vanished ones, appends new ones. */
194
+ function mergeRefreshedRows(existingRows, refreshedRows, refreshedTargetIds) {
195
+ const refreshedById = new Map(refreshedRows.map((row) => [row.id, row]));
196
+ const existingIds = new Set(existingRows.map((row) => row.id));
197
+ return [...existingRows.flatMap((row) => {
198
+ if (!refreshedTargetIds.has(row.id)) return [row];
199
+ const refreshed = refreshedById.get(row.id);
200
+ return refreshed ? [refreshed] : [];
201
+ }), ...refreshedRows.filter((row) => !existingIds.has(row.id))];
202
+ }
203
+ /**
204
+ * A refresh retires exactly the invalidation scope it re-read: a complete
205
+ * refresh clears everything; a targeted one subtracts its target ids so
206
+ * separate covering refreshes accumulate until no scope remains. Unscoped
207
+ * invalidations require a complete refresh.
208
+ */
209
+ function resolveRemainingInvalidationScope(existing, snapshot) {
210
+ if (!existing?.invalidated || !snapshot.targetAppIds?.length) return { invalidated: false };
211
+ if (!existing.invalidatedAppIds) return { invalidated: true };
212
+ const refreshedTargetIds = new Set(snapshot.targetAppIds);
213
+ const remaining = existing.invalidatedAppIds.filter((appId) => !refreshedTargetIds.has(appId));
214
+ return remaining.length > 0 ? {
215
+ invalidated: true,
216
+ invalidatedAppIds: remaining
217
+ } : { invalidated: false };
218
+ }
219
+ function doesInFlightRefreshCover(existing, params) {
220
+ if (existing.targetAppIds.size === 0) return true;
221
+ const requestedAppIds = new Set(params.targetAppIds?.filter(Boolean) ?? []);
222
+ return requestedAppIds.size > 0 && Array.from(requestedAppIds).every((appId) => existing.targetAppIds.has(appId));
223
+ }
143
224
  /** Serializes a refresh failure without leaking large or sensitive error data. */
144
225
  function serializeCodexAppInventoryError(error) {
145
226
  const record = isRecord(error) ? error : void 0;
@@ -175,29 +256,41 @@ function normalizeRuntimeIdentityForCacheKey(value) {
175
256
  }).toSorted(([left], [right]) => left.localeCompare(right));
176
257
  return entries.length > 0 ? Object.fromEntries(entries) : null;
177
258
  }
178
- async function listAllApps(request, forceRefetch, targetAppIds = []) {
179
- const apps = [];
180
- const targetIds = new Set(targetAppIds.filter(Boolean));
181
- const remainingTargetIds = new Set(targetIds);
182
- const seenCursors = /* @__PURE__ */ new Set();
183
- let cursor;
184
- do {
185
- const response = await request("app/list", {
186
- cursor,
187
- limit: targetIds.size > 0 ? CODEX_TARGETED_APP_INVENTORY_LIMIT : 100,
188
- forceRefetch
189
- });
190
- apps.push(...response.data);
191
- for (const app of response.data) remainingTargetIds.delete(app.id);
192
- if (targetIds.size > 0 && remainingTargetIds.size === 0) break;
193
- cursor = response.nextCursor;
194
- if (cursor && seenCursors.has(cursor)) throw new Error(`app/list returned repeated cursor ${cursor}`);
195
- if (cursor) seenCursors.add(cursor);
196
- } while (cursor);
197
- return apps;
259
+ async function readInstalledApps(request, options) {
260
+ const installed = await request("app/installed", { forceRefresh: options.forceRefresh });
261
+ const targetIds = new Set((options.targetAppIds ?? []).filter(Boolean));
262
+ const apps = targetIds.size === 0 ? installed.apps : installed.apps.filter((app) => targetIds.has(app.id));
263
+ if (apps.length === 0) return {
264
+ apps: [],
265
+ installedApps: []
266
+ };
267
+ const metadataResponses = await Promise.all(Array.from({ length: Math.ceil(apps.length / CODEX_APP_READ_BATCH_LIMIT) }, (_, index) => request("app/read", { appIds: apps.slice(index * CODEX_APP_READ_BATCH_LIMIT, (index + 1) * CODEX_APP_READ_BATCH_LIMIT).map((app) => app.id) })));
268
+ const metadataById = new Map(metadataResponses.flatMap((response) => response.apps).map((metadata) => [metadata.id, metadata]));
269
+ return {
270
+ apps: apps.flatMap((installedApp) => {
271
+ const metadata = metadataById.get(installedApp.id);
272
+ if (!metadata) return [];
273
+ return [{
274
+ id: installedApp.id,
275
+ name: metadata.name,
276
+ description: metadata.description ?? null,
277
+ logoUrl: metadata.iconUrl ?? null,
278
+ logoUrlDark: metadata.iconUrlDark ?? null,
279
+ distributionChannel: metadata.distributionChannel ?? null,
280
+ branding: null,
281
+ appMetadata: null,
282
+ labels: null,
283
+ installUrl: metadata.installUrl ?? null,
284
+ isAccessible: true,
285
+ isEnabled: installedApp.enabled,
286
+ pluginDisplayNames: metadata.pluginDisplayNames
287
+ }];
288
+ }),
289
+ installedApps: apps
290
+ };
198
291
  }
199
292
  function stripEntryState(entry) {
200
- const { invalidated: _invalidated, ...snapshot } = entry;
293
+ const { invalidated: _invalidated, invalidatedAppIds: _invalidatedAppIds, ...snapshot } = entry;
201
294
  return snapshot;
202
295
  }
203
296
  function fingerprintInventoryCacheKey(key) {
@@ -277,7 +370,7 @@ function resolveCodexAppServerConnectionHome(start, agentDir) {
277
370
  if (start.transport === "unix" && (!start.url || start.url === "unix://")) return resolveCodexAppServerUserHomeDir(start.env ?? process.env);
278
371
  if (start.transport !== "stdio") return null;
279
372
  if (start.homeScope === "user") return resolveCodexAppServerUserHomeDir(process.env);
280
- return agentDir ? resolveCodexAppServerHomeDir(agentDir) : null;
373
+ return agentDir ? resolveCodexAppServerLocalHomeDir(start, agentDir) : null;
281
374
  }
282
375
  /** Serializes app-server endpoint identity, including credential fingerprints. */
283
376
  function resolveCodexPluginAppCacheEndpoint(appServer) {
@@ -0,0 +1,385 @@
1
+ import { P as isCodexFastServiceTier, o as isCodexAppServerNativeAuthProfile, r as bindingStoreKey, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-DPxIvMgE.js";
2
+ import { a as getLeasedSharedCodexAppServerClient, l as releaseLeasedSharedCodexAppServerClient } from "./shared-client-kIBbGjk4.js";
3
+ import { g as resolveCodexAppServerRequestModelSelection, v as resolveCodexBindingModelProviderFallback } from "./thread-lifecycle-Bu1BTDXr.js";
4
+ import { r as formatCodexDisplayText } from "./command-formatters-B1AEz8ZA.js";
5
+ import { n as resolveCodexBindingAppServerConnection } from "./binding-connection-I5Bo-AsX.js";
6
+ import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
7
+ import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
8
+ import { patchSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
9
+ import { ModelSelectionLockedError, applyModelOverrideWithAuthProfileCompatibility } from "openclaw/plugin-sdk/model-session-runtime";
10
+ //#region extensions/codex/src/command-authorization.ts
11
+ const CODEX_NATIVE_EXECUTION_AUTH_ERROR = "Only an owner or operator.admin can control Codex native execution.";
12
+ function canMutateCodexHost(ctx) {
13
+ return ctx.senderIsOwner === true || ctx.gatewayClientScopes?.includes("operator.admin") === true;
14
+ }
15
+ //#endregion
16
+ //#region extensions/codex/src/conversation-control.ts
17
+ const CODEX_CONVERSATION_CONTROL_STATE = Symbol.for("openclaw.codex.conversationControl");
18
+ function getActiveTurns() {
19
+ const globalState = globalThis;
20
+ globalState[CODEX_CONVERSATION_CONTROL_STATE] ??= /* @__PURE__ */ new Map();
21
+ return globalState[CODEX_CONVERSATION_CONTROL_STATE];
22
+ }
23
+ function trackCodexConversationActiveTurn(active) {
24
+ const activeTurns = getActiveTurns();
25
+ const key = bindingStoreKey(active.identity);
26
+ activeTurns.set(key, active);
27
+ return () => {
28
+ if (activeTurns.get(key)?.turnId === active.turnId) activeTurns.delete(key);
29
+ };
30
+ }
31
+ function readCodexConversationActiveTurn(identity) {
32
+ return getActiveTurns().get(bindingStoreKey(identity));
33
+ }
34
+ async function stopCodexConversationTurn(params) {
35
+ const active = readCodexConversationActiveTurn(params.identity);
36
+ if (!active) return {
37
+ stopped: false,
38
+ message: "No active Codex run to stop."
39
+ };
40
+ const lookup = buildBindingLookup(params);
41
+ const binding = await params.bindingStore.read(params.identity);
42
+ if (binding?.threadId !== active.threadId) return {
43
+ stopped: false,
44
+ message: "The active Codex run no longer matches this session binding."
45
+ };
46
+ const connection = resolveCodexBindingAppServerConnection({
47
+ binding,
48
+ authProfileId: binding?.authProfileId,
49
+ pluginConfig: params.pluginConfig
50
+ });
51
+ const runtime = connection.appServer;
52
+ const client = active.client ?? await getLeasedSharedCodexAppServerClient({
53
+ startOptions: runtime.start,
54
+ timeoutMs: runtime.requestTimeoutMs,
55
+ authProfileId: connection.clientAuthProfileId,
56
+ ...lookup
57
+ });
58
+ try {
59
+ await client.request("turn/interrupt", {
60
+ threadId: active.threadId,
61
+ turnId: active.turnId
62
+ }, { timeoutMs: runtime.requestTimeoutMs });
63
+ } finally {
64
+ if (!active.client) releaseLeasedSharedCodexAppServerClient(client);
65
+ }
66
+ return {
67
+ stopped: true,
68
+ message: "Codex stop requested."
69
+ };
70
+ }
71
+ async function steerCodexConversationTurn(params) {
72
+ const active = readCodexConversationActiveTurn(params.identity);
73
+ const text = params.message.trim();
74
+ if (!text) return {
75
+ steered: false,
76
+ message: "Usage: /codex steer <message>"
77
+ };
78
+ if (!active) return {
79
+ steered: false,
80
+ message: "No active Codex run to steer."
81
+ };
82
+ const lookup = buildBindingLookup(params);
83
+ const binding = await params.bindingStore.read(params.identity);
84
+ if (binding?.threadId !== active.threadId) return {
85
+ steered: false,
86
+ message: "The active Codex run no longer matches this session binding."
87
+ };
88
+ const connection = resolveCodexBindingAppServerConnection({
89
+ binding,
90
+ authProfileId: binding?.authProfileId,
91
+ pluginConfig: params.pluginConfig
92
+ });
93
+ const runtime = connection.appServer;
94
+ const client = active.client ?? await getLeasedSharedCodexAppServerClient({
95
+ startOptions: runtime.start,
96
+ timeoutMs: runtime.requestTimeoutMs,
97
+ authProfileId: connection.clientAuthProfileId,
98
+ ...lookup
99
+ });
100
+ try {
101
+ await client.request("turn/steer", {
102
+ threadId: active.threadId,
103
+ expectedTurnId: active.turnId,
104
+ input: [{
105
+ type: "text",
106
+ text,
107
+ text_elements: []
108
+ }]
109
+ }, { timeoutMs: runtime.requestTimeoutMs });
110
+ } finally {
111
+ if (!active.client) releaseLeasedSharedCodexAppServerClient(client);
112
+ }
113
+ return {
114
+ steered: true,
115
+ message: "Sent steer message to Codex."
116
+ };
117
+ }
118
+ async function setCodexConversationModel(params) {
119
+ const model = params.model.trim();
120
+ if (!model) return "Usage: /codex model <model>";
121
+ const lookup = buildBindingLookup(params);
122
+ const binding = await requireThreadBinding(params.bindingStore, params.identity);
123
+ if (binding.connectionScope === "supervision") throw new ModelSelectionLockedError();
124
+ const modelSelection = resolveCodexAppServerRequestModelSelection({
125
+ model,
126
+ modelProvider: resolveConversationControlModelProvider({
127
+ authProfileId: binding.authProfileId,
128
+ bindingModel: binding.model,
129
+ bindingModelProvider: binding.modelProvider,
130
+ currentModel: model,
131
+ ...lookup
132
+ }),
133
+ authProfileId: binding.authProfileId,
134
+ ...lookup
135
+ });
136
+ const nextModelProvider = normalizeCodexAppServerBindingModelProvider({
137
+ authProfileId: binding.authProfileId,
138
+ modelProvider: modelSelection.modelProvider,
139
+ ...lookup
140
+ });
141
+ const nextModel = modelSelection.model;
142
+ const modelChanged = nextModel !== binding.model || nextModelProvider !== binding.modelProvider;
143
+ const session = params.session ?? (params.identity.kind === "session" && params.identity.sessionKey ? {
144
+ agentId: params.identity.agentId,
145
+ sessionId: params.identity.sessionId,
146
+ sessionKey: params.identity.sessionKey
147
+ } : void 0);
148
+ if (session) {
149
+ if (!await patchSessionEntry({
150
+ agentId: session.agentId,
151
+ storePath: resolveStorePath(params.config?.session?.store, { agentId: session.agentId }),
152
+ sessionKey: session.sessionKey,
153
+ requireWriteSuccess: true,
154
+ replaceEntry: true,
155
+ update: (entry) => {
156
+ if (entry.sessionId !== session.sessionId) throw new Error("Codex session changed while applying the model selection.");
157
+ applyModelOverrideWithAuthProfileCompatibility({
158
+ cfg: params.config ?? {},
159
+ agentDir: params.agentDir ?? resolveAgentDir(params.config ?? {}, session.agentId),
160
+ entry,
161
+ currentProvider: binding.modelProvider ?? "openai",
162
+ selection: {
163
+ provider: nextModelProvider ?? "openai",
164
+ model: nextModel
165
+ },
166
+ markLiveSwitchPending: true
167
+ });
168
+ return entry;
169
+ }
170
+ })) throw new Error("Codex session changed while applying the model selection.");
171
+ if (params.identity.kind === "conversation") await patchThreadBinding(params.bindingStore, params.identity, binding.threadId, {
172
+ model: nextModel,
173
+ modelProvider: nextModelProvider,
174
+ ...modelChanged && binding.contextEngine?.projection ? { contextEngine: {
175
+ ...binding.contextEngine,
176
+ projection: void 0
177
+ } } : {}
178
+ });
179
+ else if (modelChanged && binding.contextEngine?.projection) await patchThreadBinding(params.bindingStore, params.identity, binding.threadId, { contextEngine: {
180
+ ...binding.contextEngine,
181
+ projection: void 0
182
+ } });
183
+ } else await patchThreadBinding(params.bindingStore, params.identity, binding.threadId, {
184
+ model: nextModel,
185
+ modelProvider: nextModelProvider,
186
+ ...modelChanged && binding.contextEngine?.projection ? { contextEngine: {
187
+ ...binding.contextEngine,
188
+ projection: void 0
189
+ } } : {}
190
+ });
191
+ return `Codex model set to ${formatCodexDisplayText(nextModel)}.`;
192
+ }
193
+ async function setCodexConversationFastMode(params) {
194
+ const binding = await requireThreadBinding(params.bindingStore, params.identity);
195
+ if (params.enabled == null) return `Codex fast mode: ${isCodexFastServiceTier(binding.serviceTier) ? "on" : "off"}.`;
196
+ const serviceTier = params.enabled ? "priority" : "flex";
197
+ await patchThreadBinding(params.bindingStore, params.identity, binding.threadId, { serviceTier });
198
+ return `Codex fast mode ${params.enabled ? "enabled" : "disabled"}.`;
199
+ }
200
+ async function setCodexConversationPermissions(params) {
201
+ const binding = await requireThreadBinding(params.bindingStore, params.identity);
202
+ if (!params.mode) return `Codex permissions: ${formatPermissionsMode(binding)}.`;
203
+ const policy = permissionsForMode(params.mode);
204
+ await patchThreadBinding(params.bindingStore, params.identity, binding.threadId, {
205
+ approvalPolicy: policy.approvalPolicy,
206
+ sandbox: policy.sandbox
207
+ });
208
+ return `Codex permissions set to ${params.mode === "yolo" ? "full access" : "default"}.`;
209
+ }
210
+ function parseCodexFastModeArg(arg) {
211
+ const normalized = arg?.trim().toLowerCase();
212
+ if (!normalized || normalized === "status") return;
213
+ if (normalized === "on" || normalized === "true" || normalized === "fast") return true;
214
+ if (normalized === "off" || normalized === "false" || normalized === "flex") return false;
215
+ }
216
+ function parseCodexPermissionsModeArg(arg) {
217
+ const normalized = arg?.trim().toLowerCase();
218
+ if (!normalized || normalized === "status") return;
219
+ if (normalized === "yolo" || normalized === "full" || normalized === "full-access") return "yolo";
220
+ if (normalized === "default" || normalized === "guardian") return "default";
221
+ }
222
+ function formatPermissionsMode(binding) {
223
+ return binding.approvalPolicy === "never" && binding.sandbox === "danger-full-access" ? "full access" : "default";
224
+ }
225
+ async function requireThreadBinding(bindingStore, identity) {
226
+ const binding = await bindingStore.read(identity);
227
+ if (!binding?.threadId) throw new Error("No Codex thread is attached to this OpenClaw session yet.");
228
+ return binding;
229
+ }
230
+ async function patchThreadBinding(bindingStore, identity, threadId, patch) {
231
+ if (!await bindingStore.mutate(identity, {
232
+ kind: "patch",
233
+ threadId,
234
+ patch
235
+ })) throw new Error("Codex thread binding changed while applying the control update.");
236
+ }
237
+ function buildBindingLookup(params) {
238
+ const agentDir = params.agentDir?.trim();
239
+ return {
240
+ ...agentDir ? { agentDir } : {},
241
+ ...params.config ? { config: params.config } : {}
242
+ };
243
+ }
244
+ function resolveConversationControlModelProvider(params) {
245
+ const modelProvider = resolveCodexBindingModelProviderFallback({
246
+ currentModel: params.currentModel,
247
+ bindingModel: params.bindingModel,
248
+ bindingModelProvider: params.bindingModelProvider
249
+ })?.trim();
250
+ if (!modelProvider || modelProvider.toLowerCase() === "codex") return;
251
+ if (isCodexAppServerNativeAuthProfile(params) && modelProvider.toLowerCase() === "openai") return;
252
+ return modelProvider.toLowerCase() === "openai" ? "openai" : modelProvider;
253
+ }
254
+ function permissionsForMode(mode) {
255
+ return mode === "yolo" ? {
256
+ approvalPolicy: "never",
257
+ sandbox: "danger-full-access"
258
+ } : {
259
+ approvalPolicy: "on-request",
260
+ sandbox: "workspace-write"
261
+ };
262
+ }
263
+ //#endregion
264
+ //#region extensions/codex/src/plugin-marketplace-discovery.ts
265
+ /** Read-only discovery of Codex-owned local, curated, and remote plugin marketplaces. */
266
+ const PLUGIN_SEGMENT_PATTERN = /^[A-Za-z0-9_-]+$/;
267
+ const MAX_PLUGIN_DESCRIPTION_LENGTH = 160;
268
+ const SUPPLEMENTAL_MARKETPLACE_KINDS = [
269
+ "workspace-directory",
270
+ "shared-with-me",
271
+ "created-by-me-remote",
272
+ "vertical"
273
+ ];
274
+ /** Validates the same identifier segments required by Codex's stable PluginId parser. */
275
+ function parseCodexPluginMarketplaceId(value) {
276
+ const separator = value.lastIndexOf("@");
277
+ if (separator <= 0 || separator === value.length - 1) return;
278
+ const pluginName = value.slice(0, separator);
279
+ const marketplaceName = value.slice(separator + 1);
280
+ return PLUGIN_SEGMENT_PATTERN.test(pluginName) && PLUGIN_SEGMENT_PATTERN.test(marketplaceName) ? {
281
+ pluginName,
282
+ marketplaceName
283
+ } : void 0;
284
+ }
285
+ /** Lists local/global first and separately requests workspace, shared, and personal catalogs. */
286
+ async function discoverCodexMarketplacePlugins(params) {
287
+ const requestParams = { cwds: [params.workspaceDir] };
288
+ const primary = await params.request(requestParams);
289
+ const warnings = (primary.marketplaceLoadErrors ?? []).map((error) => boundedCatalogText(error.message));
290
+ const marketplaces = [...primary.marketplaces];
291
+ try {
292
+ const supplemental = await params.request({
293
+ ...requestParams,
294
+ marketplaceKinds: [...SUPPLEMENTAL_MARKETPLACE_KINDS]
295
+ });
296
+ marketplaces.push(...supplemental.marketplaces);
297
+ warnings.push(...(supplemental.marketplaceLoadErrors ?? []).map((error) => boundedCatalogText(error.message)));
298
+ } catch (error) {
299
+ let recoveredSupplementalMarketplace = false;
300
+ for (const kind of SUPPLEMENTAL_MARKETPLACE_KINDS) try {
301
+ const supplemental = await params.request({
302
+ ...requestParams,
303
+ marketplaceKinds: [kind]
304
+ });
305
+ marketplaces.push(...supplemental.marketplaces);
306
+ recoveredSupplementalMarketplace ||= supplemental.marketplaces.length > 0;
307
+ warnings.push(...(supplemental.marketplaceLoadErrors ?? []).map((loadError) => boundedCatalogText(loadError.message)));
308
+ } catch (kindError) {
309
+ warnings.push(boundedCatalogText(`${kind} marketplace unavailable: ${kindError instanceof Error ? kindError.message : String(kindError)}`));
310
+ }
311
+ if (!recoveredSupplementalMarketplace && warnings.length === 0) warnings.push(boundedCatalogText(`Additional marketplaces could not be listed: ${error instanceof Error ? error.message : String(error)}`));
312
+ }
313
+ const discovered = /* @__PURE__ */ new Map();
314
+ const ambiguous = /* @__PURE__ */ new Set();
315
+ for (const marketplace of marketplaces) {
316
+ if (!PLUGIN_SEGMENT_PATTERN.test(marketplace.name)) continue;
317
+ for (const summary of marketplace.plugins) {
318
+ const pluginName = pluginSlug(summary, marketplace.name);
319
+ if (!pluginName) continue;
320
+ const id = `${pluginName}@${marketplace.name}`;
321
+ if (ambiguous.has(id)) continue;
322
+ const previous = discovered.get(id);
323
+ const next = {
324
+ id,
325
+ pluginName,
326
+ marketplaceName: marketplace.name,
327
+ installed: summary.installed,
328
+ enabled: summary.enabled,
329
+ available: summary.availability !== "DISABLED_BY_ADMIN" && summary.installPolicy !== "NOT_AVAILABLE",
330
+ ...summary.installPolicy ? { installPolicy: summary.installPolicy } : {},
331
+ ...summary.authPolicy ? { authPolicy: summary.authPolicy } : {},
332
+ ...marketplace.path ? { marketplacePath: marketplace.path } : {},
333
+ ...summary.remotePluginId?.trim() ? {
334
+ remotePluginId: summary.remotePluginId.trim(),
335
+ mustShowInstallationInterstitial: summary.mustShowInstallationInterstitial ?? null
336
+ } : {},
337
+ summaryId: summary.id
338
+ };
339
+ const description = pluginDescription(summary);
340
+ if (description) next.description = description;
341
+ if (previous && (previous.marketplacePath !== next.marketplacePath || previous.remotePluginId !== next.remotePluginId)) {
342
+ discovered.delete(id);
343
+ ambiguous.add(id);
344
+ warnings.push(`Multiple discovered plugins share ${id}; installation requires a unique identity.`);
345
+ continue;
346
+ }
347
+ if (!previous) discovered.set(id, next);
348
+ else {
349
+ const preferred = !previous.installed && next.installed || !previous.enabled && next.installed && next.enabled ? next : previous;
350
+ discovered.set(id, {
351
+ ...preferred,
352
+ available: previous.available && next.available,
353
+ ...preferred.remotePluginId ? { mustShowInstallationInterstitial: previous.mustShowInstallationInterstitial === true || next.mustShowInstallationInterstitial === true ? true : previous.mustShowInstallationInterstitial === false && next.mustShowInstallationInterstitial === false ? false : null } : {},
354
+ ...previous.installPolicy === "NOT_AVAILABLE" || next.installPolicy === "NOT_AVAILABLE" ? { installPolicy: "NOT_AVAILABLE" } : {}
355
+ });
356
+ }
357
+ }
358
+ }
359
+ return {
360
+ plugins: [...discovered.values()].toSorted((left, right) => left.id.localeCompare(right.id)),
361
+ warnings
362
+ };
363
+ }
364
+ function pluginSlug(summary, marketplaceName) {
365
+ const qualified = parseCodexPluginMarketplaceId(summary.id);
366
+ if (qualified?.marketplaceName === marketplaceName) return qualified.pluginName;
367
+ const identitySegment = summary.id.split("/").at(-1);
368
+ if (identitySegment && PLUGIN_SEGMENT_PATTERN.test(identitySegment)) return identitySegment;
369
+ return PLUGIN_SEGMENT_PATTERN.test(summary.name) ? summary.name : void 0;
370
+ }
371
+ function pluginDescription(summary) {
372
+ const description = asOptionalRecord(summary.interface)?.shortDescription;
373
+ if (typeof description !== "string") return;
374
+ return boundedCatalogText(description) || void 0;
375
+ }
376
+ function boundedCatalogText(value) {
377
+ let sanitized = "";
378
+ for (const character of value) {
379
+ const codePoint = character.codePointAt(0);
380
+ sanitized += codePoint !== void 0 && (codePoint <= 31 || codePoint >= 127 && codePoint <= 159) ? " " : character;
381
+ }
382
+ return sanitized.replace(/\s+/g, " ").trim().slice(0, MAX_PLUGIN_DESCRIPTION_LENGTH);
383
+ }
384
+ //#endregion
385
+ export { parseCodexPermissionsModeArg as a, setCodexConversationModel as c, stopCodexConversationTurn as d, trackCodexConversationActiveTurn as f, parseCodexFastModeArg as i, setCodexConversationPermissions as l, canMutateCodexHost as m, parseCodexPluginMarketplaceId as n, readCodexConversationActiveTurn as o, CODEX_NATIVE_EXECUTION_AUTH_ERROR as p, formatPermissionsMode as r, setCodexConversationFastMode as s, discoverCodexMarketplacePlugins as t, steerCodexConversationTurn as u };