@openclaw/codex 2026.7.2-beta.7 → 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 (55) hide show
  1. package/README.md +2 -0
  2. package/dist/{app-server-policy-BSaO2JIT.js → app-server-policy-zcBDc1JU.js} +1 -1
  3. package/dist/attempt-client-cleanup-DMLXUfa8.js +735 -0
  4. package/dist/{notification-correlation-CU6_AnOb.js → binding-connection-I5Bo-AsX.js} +3 -37
  5. package/dist/{bounded-turn-D2ZAgZVz.js → bounded-turn-qgZmzy_4.js} +50 -49
  6. package/dist/{capabilities-BFDTVYr4.js → capabilities-CybGI1oz.js} +8 -2
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters-C336eRVS.js → command-formatters-B1AEz8ZA.js} +18 -18
  9. package/dist/{command-handlers-DAHUSry9.js → command-handlers-Db8pu_ds.js} +484 -95
  10. package/dist/{compact-D_uvHurS.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-D2gYkSBO.js → dynamic-tools-BVdyWkUD.js} +2597 -1221
  14. package/dist/{effective-mcp-catalog-Bbd98Inw.js → effective-mcp-catalog-DIIhAtOZ.js} +20 -15
  15. package/dist/{event-projector-assistant-message-DTgnPPVF.js → event-projector-assistant-message-BwxV4d6N.js} +11 -23
  16. package/dist/{attempt-notifications-AP-a-Q5k.js → event-projector-usage-e5huIQWu.js} +113 -29
  17. package/dist/harness.js +80 -14
  18. package/dist/image-payload-sanitizer-CnfoUPCE.js +65 -0
  19. package/dist/index.js +591 -240
  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-BBMKU4jA.js → models-BFlpn1Ex.js} +12 -17
  24. package/dist/native-subagent-monitor-DyTGmTUx.js +3111 -0
  25. package/dist/{plugin-app-cache-key-DztPafOA.js → plugin-app-cache-key-C3H2CVp6.js} +5 -5
  26. package/dist/plugin-marketplace-discovery-CzrGJdCR.js +385 -0
  27. package/dist/{computer-use-CYEc8U3w.js → plugin-metadata-cache-CNJPj4_b.js} +105 -5
  28. package/dist/protocol-validators-B4F7L8Rf.js +2346 -0
  29. package/dist/{rate-limits-CU-gVCCS.js → rate-limits-DZXmOL8-.js} +17 -35
  30. package/dist/{request-qKh331yu.js → request-BRl5_UfX.js} +3 -3
  31. package/dist/{run-attempt-CY9WlDLc.js → run-attempt-P8BdxC1I.js} +4837 -9047
  32. package/dist/{runtime-artifact-CmtG_t1L.js → runtime-artifact-BVSPdd6g.js} +8 -23
  33. package/dist/{sandbox-guard-NuhpXmPB.js → sandbox-guard-Cmwc9eIk.js} +6 -13
  34. package/dist/{session-binding-BXpo9ymA.js → session-binding-DPxIvMgE.js} +246 -265
  35. package/dist/{session-catalog-DyOgR4WH.js → session-catalog-xwNi_SDM.js} +159 -109
  36. package/dist/{session-cli-2PZaFLRh.js → session-cli-7hgXZDV7.js} +1 -1
  37. package/dist/session-retirement-DoMMJFKm.js +36 -0
  38. package/dist/{session-upstream-marker-B6U7tLw5.js → session-upstream-marker-CrIFX06J.js} +1 -1
  39. package/dist/{settled-turn-finalizer-CXY2Z9dT.js → settled-turn-finalizer-BRWBO4MO.js} +21 -13
  40. package/dist/{shared-client-DTbvH-XV.js → shared-client-kIBbGjk4.js} +587 -223
  41. package/dist/{side-question-_ulBk1AL.js → side-question-9HAL26l9.js} +101 -52
  42. package/dist/{thread-lifecycle-DF53ogiA.js → thread-lifecycle-Bu1BTDXr.js} +694 -753
  43. package/dist/thread-ownership-6kOQHlyC.js +92 -0
  44. package/dist/thread-resume-CPrJNQVM.js +30 -0
  45. package/dist/transcript-mirror-B9gGgnJS.js +570 -0
  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-Dy4Xvuoj.js → upstream-session-fork-CE-_LdnP.js} +5 -5
  49. package/dist/{usage-CQ6GhD5-.js → usage-CHlV0e-o.js} +3 -3
  50. package/dist/{web-search-provider.runtime-CCFhg2z5.js → web-search-provider.runtime-DRw2Vl42.js} +8 -9
  51. package/openclaw.plugin.json +18 -3
  52. package/package.json +5 -5
  53. package/dist/conversation-control-xsIeXbo4.js +0 -287
  54. package/dist/event-projector-usage-Xp4AlR2k.js +0 -86
  55. package/dist/transcript-mirror-DzsdNCf5.js +0 -6924
@@ -1,25 +1,29 @@
1
- import { I as resolveCodexAppServerAuthProfileIdForAgent, W as isJsonObject } from "./shared-client-DTbvH-XV.js";
2
- import { c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, i as createCodexSessionGenerationSupersededError, j as isCodexFastServiceTier, n as assertCodexBindingMayBeReplaced, r as bindingStoreKey, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-BXpo9ymA.js";
3
- import { g as assertCodexThreadResumeResponse } from "./transcript-mirror-DzsdNCf5.js";
4
- import { t as listAllCodexAppServerModels } from "./models-BBMKU4jA.js";
5
- import { t as CODEX_CONTROL_METHODS } from "./capabilities-BFDTVYr4.js";
6
- import { s as summarizeCodexAccountUsage } from "./rate-limits-CU-gVCCS.js";
7
- import { a as formatComputerUseStatus, c as formatSkills, i as formatCodexStatus, l as formatThreads, n as formatAccount, o as formatList, r as formatCodexDisplayText, s as formatModels, t as buildHelp, u as readString$1 } from "./command-formatters-C336eRVS.js";
8
- import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-NuhpXmPB.js";
9
- import { a as setCodexConversationFastMode, c as steerCodexConversationTurn, d as CODEX_NATIVE_EXECUTION_AUTH_ERROR, f as canMutateCodexHost, i as readCodexConversationActiveTurn, l as stopCodexConversationTurn, n as parseCodexFastModeArg, o as setCodexConversationModel, r as parseCodexPermissionsModeArg, s as setCodexConversationPermissions, t as formatPermissionsMode } from "./conversation-control-xsIeXbo4.js";
10
- import { i as readCodexConversationBindingData, n as createCodexCliNodeConversationBindingData, o as resolveCodexDefaultWorkspaceDir, r as createCodexConversationBindingData } from "./conversation-binding-data-BHRpYbX3.js";
11
- import { a as resolveCodexBindingAppServerConnection } from "./notification-correlation-CU6_AnOb.js";
12
- import { c as requestOptions, l as safeCodexControlRequest, o as codexControlRequest, p as formatCodexCliSessions, s as readCodexStatusProbes } from "./session-catalog-DyOgR4WH.js";
13
- import { n as installCodexComputerUse, r as readCodexComputerUseStatus } from "./computer-use-CYEc8U3w.js";
1
+ import { D as CODEX_PLUGINS_MARKETPLACE_NAME, P as isCodexFastServiceTier, c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, g as resolveCodexAppServerRuntimeOptions, i as createCodexSessionGenerationSupersededError, n as assertCodexBindingMayBeReplaced, r as bindingStoreKey, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-DPxIvMgE.js";
2
+ import { Q as resolveCodexAppServerAuthAccountCacheKey, V as consumeCodexAppServerLiveThread, a as getLeasedSharedCodexAppServerClient, ct as isJsonObject, et as resolveCodexAppServerAuthProfileIdForAgent, l as releaseLeasedSharedCodexAppServerClient, tt as resolveCodexAppServerFallbackApiKeyCacheKey } from "./shared-client-kIBbGjk4.js";
3
+ import { i as assertCodexThreadResumeResponse } from "./protocol-validators-B4F7L8Rf.js";
4
+ import { t as listAllCodexAppServerModels } from "./models-BFlpn1Ex.js";
5
+ import { i as defaultCodexAppInventoryCache, r as buildCodexPluginAppCacheKey } from "./plugin-app-cache-key-C3H2CVp6.js";
6
+ import { Y as isOpenAiCuratedMarketplaceName, q as refreshCodexPluginRuntimeState } from "./thread-lifecycle-Bu1BTDXr.js";
7
+ import { t as isIncognitoSessionKey } from "./incognito-session-ET_ufDH-.js";
8
+ import { t as CODEX_CONTROL_METHODS } from "./capabilities-CybGI1oz.js";
9
+ import { s as summarizeCodexAccountUsage } from "./rate-limits-DZXmOL8-.js";
10
+ import { a as formatComputerUseStatus, c as formatSkills, i as formatCodexStatus, l as formatThreads, n as formatAccount, o as formatList, r as formatCodexDisplayText, s as formatModels, t as buildHelp } from "./command-formatters-B1AEz8ZA.js";
11
+ import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-Cmwc9eIk.js";
12
+ import { a as rollbackCodexAppServerBindingSubscription, i as retireCodexConversationThreadBinding, n as releaseCodexAppServerBindingSubscription, o as withCodexConversationThreadActivity, r as retainCodexAppServerBindingSubscription, s as withExclusiveCodexAppServerThread, t as isSameCodexAppServerThreadOwner } from "./thread-ownership-6kOQHlyC.js";
13
+ import { a as parseCodexPermissionsModeArg, c as setCodexConversationModel, d as stopCodexConversationTurn, i as parseCodexFastModeArg, l as setCodexConversationPermissions, m as canMutateCodexHost, n as parseCodexPluginMarketplaceId, o as readCodexConversationActiveTurn, p as CODEX_NATIVE_EXECUTION_AUTH_ERROR, r as formatPermissionsMode, s as setCodexConversationFastMode, t as discoverCodexMarketplacePlugins, u as steerCodexConversationTurn } from "./plugin-marketplace-discovery-CzrGJdCR.js";
14
+ import { i as readCodexConversationBindingData, n as createCodexCliNodeConversationBindingData, o as resolveCodexDefaultWorkspaceDir, r as createCodexConversationBindingData } from "./conversation-binding-data-CW2GuupV.js";
15
+ import { n as resolveCodexBindingAppServerConnection } from "./binding-connection-I5Bo-AsX.js";
16
+ import { c as readCodexStatusProbes, l as requestOptions, p as formatCodexCliSessions, s as codexControlRequest, u as safeCodexControlRequest } from "./session-catalog-xwNi_SDM.js";
17
+ import { i as readCodexComputerUseStatus, r as installCodexComputerUse, t as defaultCodexPluginMetadataCache } from "./plugin-metadata-cache-CNJPj4_b.js";
18
+ import { normalizeOptionalString, normalizeUniqueStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
14
19
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
15
20
  import crypto from "node:crypto";
16
21
  import { ensureAuthProfileStore, findNormalizedProviderValue, resolveAgentDir, resolveAuthProfileEligibility, resolveAuthProfileOrder, resolveProfileUnusableUntilForDisplay, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
17
22
  import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
18
- import { normalizeOptionalString, normalizeUniqueStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
19
23
  import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
20
24
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
21
25
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
22
- import { MODEL_SELECTION_LOCKED_MESSAGE, isModelSelectionLocked } from "openclaw/plugin-sdk/model-session-runtime";
26
+ import { MODEL_SELECTION_LOCKED_MESSAGE, isModelSelectionLocked, resolvePersistedSessionRuntimeId } from "openclaw/plugin-sdk/model-session-runtime";
23
27
  //#region extensions/codex/src/command-account.ts
24
28
  const OPENAI_PROVIDER_ID = "openai";
25
29
  const OPENAI_CODEX_PROVIDER_ID = OPENAI_PROVIDER_ID;
@@ -148,9 +152,9 @@ function isActiveProfileCandidate(params, profileId) {
148
152
  function resolveLiveAccountProfileId(params) {
149
153
  if (!params.account.ok || !isJsonObject(params.account.value)) return;
150
154
  const account = isJsonObject(params.account.value.account) ? params.account.value.account : params.account.value;
151
- const type = readString(account, "type")?.toLowerCase();
155
+ const type = normalizeOptionalString(account.type)?.toLowerCase();
152
156
  if (type === "chatgpt") {
153
- const email = readString(account, "email")?.toLowerCase();
157
+ const email = normalizeOptionalString(account.email)?.toLowerCase();
154
158
  const firstSubscription = params.order.find((profileId) => isChatGptSubscriptionProfile(params.store.profiles[profileId]));
155
159
  if (!email) return firstSubscription;
156
160
  return params.order.find((profileId) => {
@@ -231,10 +235,6 @@ function formatSubscriptionUsageLine(usage) {
231
235
  function formatUsageLineForDisplay(value) {
232
236
  return value.replace(/^weekly\b/u, "Weekly").replace(/\bshort-term\b/u, "Short-term");
233
237
  }
234
- function readString(record, key) {
235
- const value = record[key];
236
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
237
- }
238
238
  function isChatGptSubscriptionProfile(credential) {
239
239
  return credential?.type === "oauth" || credential?.type === "token";
240
240
  }
@@ -1276,7 +1276,7 @@ async function sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, n
1276
1276
  });
1277
1277
  continue;
1278
1278
  }
1279
- const responseThreadId = isJsonObject(response.value) ? readString$1(response.value, "threadId") : void 0;
1279
+ const responseThreadId = isJsonObject(response.value) ? normalizeOptionalString(response.value.threadId) : void 0;
1280
1280
  sent.push({
1281
1281
  ...target,
1282
1282
  threadId: responseThreadId ?? target.threadId
@@ -1442,10 +1442,35 @@ async function bindConversation(deps, ctx, pluginConfig, args) {
1442
1442
  async function detachConversation(deps, ctx) {
1443
1443
  if (isCurrentSessionModelSelectionLocked(ctx)) return MODEL_SELECTION_LOCKED_MESSAGE;
1444
1444
  const data = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
1445
- if (data?.kind === "codex-app-server-session") assertCodexBindingMayBeReplaced(await deps.bindingStore.read(conversationBindingIdentity(data.bindingId)), "detaching its conversation binding");
1446
- const detached = await ctx.detachConversationBinding();
1447
- if (data?.kind === "codex-app-server-session") await deps.bindingStore.mutate(conversationBindingIdentity(data.bindingId), { kind: "clear" });
1448
- return detached.removed ? "Detached this conversation from Codex." : "No Codex conversation binding was attached.";
1445
+ const identity = data?.kind === "codex-app-server-session" ? conversationBindingIdentity(data.bindingId) : void 0;
1446
+ const sourceSessionKey = data?.kind === "codex-app-server-session" ? data.source?.sessionKey : void 0;
1447
+ let expectedThreadId;
1448
+ let expectedStartId;
1449
+ if (data?.kind === "codex-app-server-session") {
1450
+ const binding = await deps.bindingStore.read(identity);
1451
+ assertCodexBindingMayBeReplaced(binding, "detaching its conversation binding");
1452
+ if (deps.readCodexConversationActiveTurn(identity)) return "This Codex conversation has an active run; use /codex stop before detaching it.";
1453
+ expectedThreadId = binding?.threadId;
1454
+ expectedStartId = binding?.conversationStartId;
1455
+ }
1456
+ const detachPublicConversation = async () => {
1457
+ return (await ctx.detachConversationBinding()).removed ? "Detached this conversation from Codex." : "No Codex conversation binding was attached.";
1458
+ };
1459
+ if (identity && expectedThreadId) return await withCodexConversationThreadActivity(identity.bindingId, async () => {
1460
+ let detachedPublicConversation;
1461
+ if (!await retireCodexConversationThreadBinding({
1462
+ bindingStore: deps.bindingStore,
1463
+ identity,
1464
+ expectedThreadId,
1465
+ ...expectedStartId ? { expectedStartId } : {},
1466
+ ...isIncognitoSessionKey(sourceSessionKey) ? { allowUntracked: true } : {},
1467
+ afterClear: async () => {
1468
+ detachedPublicConversation = await detachPublicConversation();
1469
+ }
1470
+ })) return "This Codex conversation binding changed while detaching; try again.";
1471
+ return detachedPublicConversation;
1472
+ });
1473
+ return await detachPublicConversation();
1449
1474
  }
1450
1475
  async function describeConversationBinding(deps, ctx) {
1451
1476
  const current = await ctx.getCurrentConversationBinding();
@@ -1507,52 +1532,83 @@ async function resumeThread(deps, ctx, pluginConfig, args) {
1507
1532
  agentId: scope.agentId,
1508
1533
  config: ctx.config
1509
1534
  });
1510
- return await deps.bindingStore.withLease(identity, async () => {
1511
- if (!await reclaimCurrentCodexSessionGeneration({
1512
- bindingStore: deps.bindingStore,
1513
- identity,
1514
- config: ctx.config
1515
- })) throw createCodexSessionGenerationSupersededError(identity.sessionId);
1516
- const currentBinding = await deps.bindingStore.read(identity);
1517
- assertCodexBindingMayBeReplaced(currentBinding, "attaching a different resumed thread");
1518
- const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
1519
- authProfileId: currentBinding?.authProfileId,
1520
- agentDir: scope.agentDir,
1521
- config: ctx.config
1522
- });
1523
- const response = assertCodexThreadResumeResponse(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.resumeThread, {
1524
- threadId: normalizedThreadId,
1525
- excludeTurns: true
1526
- }, {
1527
- config: ctx.config,
1528
- agentDir: scope.agentDir,
1529
- authProfileId,
1530
- sessionKey: ctx.sessionKey,
1531
- sessionId: ctx.sessionId
1532
- }));
1533
- const effectiveThreadId = response.thread.id;
1534
- if (effectiveThreadId !== normalizedThreadId) throw new Error(`Codex thread/resume returned ${effectiveThreadId} for ${normalizedThreadId}`);
1535
- const resumedCwd = response.thread.cwd;
1536
- if (typeof resumedCwd !== "string") throw new Error(`Codex thread/resume returned no cwd for ${normalizedThreadId}`);
1537
- const modelProvider = normalizeCodexAppServerBindingModelProvider({
1538
- authProfileId,
1539
- modelProvider: response.modelProvider ?? void 0,
1540
- agentDir: scope.agentDir,
1541
- config: ctx.config
1542
- });
1543
- assertCodexBindingMayBeReplaced(await deps.bindingStore.read(identity), "committing a different resumed thread");
1544
- if (!await deps.bindingStore.mutate(identity, {
1545
- kind: "set",
1546
- binding: {
1547
- threadId: effectiveThreadId,
1548
- cwd: resumedCwd,
1535
+ return await withExclusiveCodexAppServerThread({
1536
+ bindingStore: deps.bindingStore,
1537
+ identity,
1538
+ threadId: normalizedThreadId,
1539
+ run: async () => await deps.bindingStore.withLease(identity, async () => {
1540
+ if (!await reclaimCurrentCodexSessionGeneration({
1541
+ bindingStore: deps.bindingStore,
1542
+ identity,
1543
+ config: ctx.config
1544
+ })) throw createCodexSessionGenerationSupersededError(identity.sessionId);
1545
+ const currentBinding = await deps.bindingStore.read(identity);
1546
+ assertCodexBindingMayBeReplaced(currentBinding, "attaching a different resumed thread");
1547
+ const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
1548
+ authProfileId: currentBinding?.authProfileId,
1549
+ agentDir: scope.agentDir,
1550
+ config: ctx.config
1551
+ });
1552
+ let committedResponse = false;
1553
+ const commitResumedThread = async (value, client) => {
1554
+ const response = assertCodexThreadResumeResponse(value);
1555
+ const effectiveThreadId = response.thread.id;
1556
+ if (effectiveThreadId !== normalizedThreadId) throw new Error(`Codex thread/resume returned ${effectiveThreadId} for ${normalizedThreadId}`);
1557
+ const resumedCwd = response.thread.cwd;
1558
+ if (typeof resumedCwd !== "string") throw new Error(`Codex thread/resume returned no cwd for ${normalizedThreadId}`);
1559
+ const modelProvider = normalizeCodexAppServerBindingModelProvider({
1560
+ authProfileId,
1561
+ modelProvider: response.modelProvider ?? void 0,
1562
+ agentDir: scope.agentDir,
1563
+ config: ctx.config
1564
+ });
1565
+ const bindingBeforeCommit = await deps.bindingStore.read(identity);
1566
+ assertCodexBindingMayBeReplaced(bindingBeforeCommit, "committing a different resumed thread");
1567
+ const clientId = client?.getInstanceId();
1568
+ const sameOwner = client ? isSameCodexAppServerThreadOwner(bindingBeforeCommit, {
1569
+ threadId: effectiveThreadId,
1570
+ clientId
1571
+ }) : bindingBeforeCommit?.threadId === effectiveThreadId;
1572
+ let retained = false;
1573
+ try {
1574
+ if (client) {
1575
+ retained = await retainCodexAppServerBindingSubscription(client, effectiveThreadId, sameOwner ? await consumeCodexAppServerLiveThread(client, effectiveThreadId) : void 0);
1576
+ if (!retained) throw new Error("Codex resumed thread lost its native subscription owner.");
1577
+ }
1578
+ if (bindingBeforeCommit && !sameOwner) await releaseCodexAppServerBindingSubscription(bindingBeforeCommit);
1579
+ if (!await deps.bindingStore.mutate(identity, {
1580
+ kind: "set",
1581
+ binding: {
1582
+ ...bindingBeforeCommit?.threadId === effectiveThreadId ? bindingBeforeCommit : {},
1583
+ threadId: effectiveThreadId,
1584
+ ...clientId ? { clientId } : {},
1585
+ cwd: resumedCwd,
1586
+ authProfileId,
1587
+ model: response.model,
1588
+ modelProvider,
1589
+ historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString()
1590
+ }
1591
+ })) throw new Error("Codex thread binding changed while attaching the resumed thread.");
1592
+ } catch (error) {
1593
+ if (client && !sameOwner) await rollbackCodexAppServerBindingSubscription(client, effectiveThreadId, retained);
1594
+ throw error;
1595
+ }
1596
+ committedResponse = true;
1597
+ };
1598
+ const response = await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.resumeThread, {
1599
+ threadId: normalizedThreadId,
1600
+ excludeTurns: true
1601
+ }, {
1602
+ config: ctx.config,
1603
+ agentDir: scope.agentDir,
1549
1604
  authProfileId,
1550
- model: response.model,
1551
- modelProvider,
1552
- historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString()
1553
- }
1554
- })) throw new Error("Codex thread binding changed while attaching the resumed thread.");
1555
- return `Attached this OpenClaw session to Codex thread ${formatCodexDisplayText(effectiveThreadId)}.`;
1605
+ sessionKey: ctx.sessionKey,
1606
+ sessionId: ctx.sessionId,
1607
+ onResponse: commitResumedThread
1608
+ });
1609
+ if (!committedResponse) await commitResumedThread(response);
1610
+ return `Attached this OpenClaw session to Codex thread ${formatCodexDisplayText(normalizedThreadId)}.`;
1611
+ })
1556
1612
  });
1557
1613
  }
1558
1614
  async function bindCodexCliNodeSession(deps, ctx, parsed) {
@@ -1717,8 +1773,8 @@ async function handleNativeGoal(deps, ctx, pluginConfig, args) {
1717
1773
  function formatNativeGoal(response) {
1718
1774
  const goal = isJsonObject(response) && isJsonObject(response.goal) ? response.goal : void 0;
1719
1775
  if (!goal) return "No Codex goal is active.";
1720
- const objective = readString$1(goal, "objective") ?? "unknown";
1721
- const status = readString$1(goal, "status") ?? "unknown";
1776
+ const objective = normalizeOptionalString(goal.objective) ?? "unknown";
1777
+ const status = normalizeOptionalString(goal.status) ?? "unknown";
1722
1778
  const tokensUsed = typeof goal.tokensUsed === "number" ? goal.tokensUsed : 0;
1723
1779
  const tokenBudget = typeof goal.tokenBudget === "number" ? goal.tokenBudget : void 0;
1724
1780
  return [
@@ -1758,8 +1814,16 @@ async function setConversationModel(deps, ctx, pluginConfig, args) {
1758
1814
  const target = await resolveControlTarget(ctx);
1759
1815
  if (!target) return "Cannot set Codex model because this command did not include a stable binding identity.";
1760
1816
  if (!normalized) {
1817
+ const currentSession = ctx.sessionId && ctx.sessionKey ? getSessionEntry({
1818
+ storePath: resolveStorePath(ctx.config.session?.store, { agentId: target.agentId }),
1819
+ sessionKey: ctx.sessionKey,
1820
+ hydrateSkillPromptRefs: false,
1821
+ readConsistency: "latest"
1822
+ }) : void 0;
1823
+ const selectedModel = currentSession && currentSession.sessionId === ctx.sessionId ? currentSession.modelOverride : void 0;
1761
1824
  const binding = await deps.bindingStore.read(target.identity);
1762
- return binding?.model ? `Codex model: ${formatCodexDisplayText(binding.model)}` : "Usage: /codex model <model>";
1825
+ const activeModel = selectedModel ?? binding?.model;
1826
+ return activeModel ? `Codex model: ${formatCodexDisplayText(activeModel)}` : "Usage: /codex model <model>";
1763
1827
  }
1764
1828
  return await deps.setCodexConversationModel({
1765
1829
  identity: target.identity,
@@ -1767,7 +1831,12 @@ async function setConversationModel(deps, ctx, pluginConfig, args) {
1767
1831
  pluginConfig,
1768
1832
  model: normalized,
1769
1833
  agentDir: target.agentDir,
1770
- config: ctx.config
1834
+ config: ctx.config,
1835
+ ...ctx.sessionId && ctx.sessionKey ? { session: {
1836
+ agentId: target.agentId,
1837
+ sessionId: ctx.sessionId,
1838
+ sessionKey: ctx.sessionKey
1839
+ } } : {}
1771
1840
  });
1772
1841
  }
1773
1842
  async function setConversationFastMode(deps, ctx, args) {
@@ -1797,32 +1866,54 @@ async function setConversationPermissions(deps, ctx, args) {
1797
1866
  });
1798
1867
  }
1799
1868
  async function startThreadAction(deps, ctx, pluginConfig, kind, args) {
1800
- const label = kind === "compact" ? "compaction" : "review";
1801
- if (args.length > 0) return `Usage: /codex ${label === "compaction" ? "compact" : label}`;
1869
+ if (args.length > 0) return `Usage: /codex ${kind}`;
1802
1870
  const target = await resolveControlTarget(ctx);
1803
- if (!target) return `Cannot start Codex ${label} because this command did not include a stable binding identity.`;
1871
+ if (!target) return `Cannot start Codex ${kind === "compact" ? "compaction" : "review"} because this command did not include a stable binding identity.`;
1804
1872
  const binding = await deps.bindingStore.read(target.identity);
1805
1873
  if (!binding?.threadId) return `No Codex thread is attached to this OpenClaw session yet.`;
1874
+ if (kind === "compact") {
1875
+ const sessionTarget = ctx.sessionTarget;
1876
+ if (!ctx.sessionId || !ctx.sessionKey || !sessionTarget || sessionTarget.sessionId !== ctx.sessionId || sessionTarget.sessionKey !== ctx.sessionKey) return "Codex compaction is unavailable because this command is not bound to a complete session identity.";
1877
+ const currentSession = getSessionEntry({
1878
+ storePath: sessionTarget.storePath,
1879
+ sessionKey: ctx.sessionKey,
1880
+ hydrateSkillPromptRefs: false,
1881
+ readConsistency: "latest"
1882
+ });
1883
+ if (currentSession?.sessionId !== ctx.sessionId || resolvePersistedSessionRuntimeId(currentSession) !== "codex") return "Codex compaction is unavailable because the current OpenClaw session is not using the Codex runtime.";
1884
+ if (target.identity.kind === "conversation") {
1885
+ if (!isSameCodexAppServerThreadOwner(binding, ctx.sessionId ? await deps.bindingStore.read(sessionBindingIdentity({
1886
+ sessionId: ctx.sessionId,
1887
+ sessionKey: ctx.sessionKey,
1888
+ agentId: sessionTarget.agentId,
1889
+ config: ctx.config
1890
+ })) : void 0)) return "Codex compaction is unavailable because the conversation-bound thread differs from the current session binding. Resume that thread into the session first.";
1891
+ }
1892
+ const compactCurrent = ctx.runtimeContext?.compactCurrent;
1893
+ if (!compactCurrent) return "Codex compaction is unavailable because this command is not bound to a session.";
1894
+ const result = await compactCurrent();
1895
+ return result.compacted ? `Compacted Codex session (${result.tokensAfter ?? "unknown"} tokens after).` : `Codex compaction did not complete: ${formatCodexDisplayText(result.reason ?? "no reason returned")}.`;
1896
+ }
1806
1897
  const connection = resolveCodexBindingAppServerConnection({
1807
1898
  binding,
1808
1899
  authProfileId: binding.authProfileId,
1809
1900
  pluginConfig
1810
1901
  });
1811
- await deps.codexControlRequest(pluginConfig, kind === "compact" ? CODEX_CONTROL_METHODS.compact : CODEX_CONTROL_METHODS.review, kind === "review" ? {
1902
+ await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.review, {
1812
1903
  threadId: binding.threadId,
1813
1904
  target: { type: "uncommittedChanges" }
1814
- } : { threadId: binding.threadId }, {
1905
+ }, {
1815
1906
  agentDir: target.agentDir,
1816
1907
  authProfileId: connection.clientAuthProfileId,
1817
1908
  config: ctx.config,
1818
1909
  ...connection.usesSupervisionConnection ? { startOptions: connection.appServer.start } : {}
1819
1910
  });
1820
- return `Started Codex ${label} for thread ${formatCodexDisplayText(binding.threadId)}.`;
1911
+ return `Started Codex review for thread ${formatCodexDisplayText(binding.threadId)}.`;
1821
1912
  }
1822
1913
  //#endregion
1823
1914
  //#region extensions/codex/src/command-plugins-management.ts
1824
1915
  const POLICY_REFRESH_HINT = "New Codex conversations pick this up automatically. Use /new or /reset to refresh the current one.";
1825
- async function handleCodexPluginsSubcommand(ctx, rest, io) {
1916
+ async function handleCodexPluginsSubcommand(ctx, rest, io, runtime) {
1826
1917
  const [verb = "list", ...args] = rest;
1827
1918
  const normalized = verb.toLowerCase();
1828
1919
  if (normalized === "menu") {
@@ -1838,22 +1929,51 @@ async function handleCodexPluginsSubcommand(ctx, rest, io) {
1838
1929
  const current = await io.readConfig();
1839
1930
  return { text: formatPluginList(current.plugins ?? {}, { globalEnabled: current.enabled === true }) };
1840
1931
  }
1932
+ if (normalized === "available") {
1933
+ if (args.length > 0) return { text: "Usage: /codex plugins available" };
1934
+ if (!canMutateCodexHost(ctx)) return { text: "Only an owner or operator.admin gateway client can list available Codex plugins." };
1935
+ if (!runtime) return { text: "Codex plugin discovery is unavailable for this command." };
1936
+ try {
1937
+ const discovered = await discoverCodexMarketplacePlugins({
1938
+ request: runtime.list,
1939
+ workspaceDir: await runtime.workspaceDir()
1940
+ });
1941
+ return { text: formatAvailablePlugins(discovered.plugins, discovered.warnings) };
1942
+ } catch (error) {
1943
+ return { text: `Could not list Codex plugins: ${formatCodexDisplayText(errorMessage(error))}` };
1944
+ }
1945
+ }
1946
+ if (normalized === "install") {
1947
+ if (args.length !== 1 || !args[0]) return { text: "Usage: /codex plugins install <plugin>@<marketplace>" };
1948
+ if (!canMutateCodexHost(ctx)) return { text: "Only an owner or operator.admin gateway client can run /codex plugins install." };
1949
+ if (!runtime) return { text: "Codex plugin installation is unavailable for this command." };
1950
+ return await installCodexPlugin(args[0], io, runtime);
1951
+ }
1841
1952
  const target = args[0];
1842
1953
  if (normalized === "enable" || normalized === "disable") {
1843
1954
  if (args.length === 0) return buildPluginNamePickerReply(normalized, await io.readConfig());
1844
1955
  if (!target || args.length > 1) return { text: `Usage: /codex plugins ${normalized} <name>` };
1845
1956
  if (!canMutateCodexHost(ctx)) return { text: `Only an owner or operator.admin gateway client can run /codex plugins ${normalized}.` };
1846
1957
  const wantEnabled = normalized === "enable";
1847
- if (!((await io.readConfig()).plugins ?? {})[target]) return { text: `Codex sub-plugin '${formatCodexDisplayText(target)}' is not configured. Run '/codex plugins list' to see configured plugins.` };
1958
+ const current = (await io.readConfig()).plugins ?? {};
1959
+ const exact = current[target];
1960
+ const requested = parseCodexPluginMarketplaceId(target);
1961
+ const configured = exact && requested && !matchesConfiguredPluginIdentity(exact, requested, target) ? {
1962
+ status: "matched",
1963
+ configKey: target
1964
+ } : resolveConfiguredPluginKey(current, target);
1965
+ if (configured.status === "ambiguous" || configured.status === "mismatched") return { text: describeConfiguredPluginIdentityConflict(target, configured.status) };
1966
+ if (configured.status === "missing") return { text: `Codex sub-plugin '${formatCodexDisplayText(target)}' is not configured. Run '/codex plugins list' to see configured plugins.` };
1967
+ const configKey = configured.configKey;
1848
1968
  await io.mutate((block) => {
1849
1969
  if (wantEnabled) block.enabled = true;
1850
1970
  block.plugins ??= {};
1851
- block.plugins[target] = {
1852
- ...block.plugins[target],
1971
+ block.plugins[configKey] = {
1972
+ ...block.plugins[configKey],
1853
1973
  enabled: wantEnabled
1854
1974
  };
1855
1975
  });
1856
- return { text: `${formatCodexDisplayText(target)}: ${wantEnabled ? "enabled" : "disabled"} in openclaw.json. ${POLICY_REFRESH_HINT}` };
1976
+ return { text: `${formatCodexDisplayText(configKey)}: ${wantEnabled ? "enabled" : "disabled"} in openclaw.json. ${POLICY_REFRESH_HINT}` };
1857
1977
  }
1858
1978
  return { text: `Unknown /codex plugins subcommand: ${formatCodexDisplayText(verb)}\n\n${buildPluginsHelp()}` };
1859
1979
  }
@@ -1863,9 +1983,10 @@ function buildPluginsMenuReply() {
1863
1983
  "Codex sub-plugins. Pick a sub-action or type:",
1864
1984
  "",
1865
1985
  " 1. /codex plugins list",
1866
- " 2. /codex plugins enable",
1867
- " 3. /codex plugins disable",
1868
- " 4. /codex plugins help",
1986
+ " 2. /codex plugins available",
1987
+ " 3. /codex plugins enable",
1988
+ " 4. /codex plugins disable",
1989
+ " 5. /codex plugins help",
1869
1990
  "",
1870
1991
  "Type '/codex' to go back to the main menu."
1871
1992
  ].join("\n"),
@@ -1874,6 +1995,10 @@ function buildPluginsMenuReply() {
1874
1995
  label: "list",
1875
1996
  command: "/codex plugins list"
1876
1997
  },
1998
+ {
1999
+ label: "available",
2000
+ command: "/codex plugins available"
2001
+ },
1877
2002
  {
1878
2003
  label: "enable",
1879
2004
  command: "/codex plugins enable"
@@ -1935,13 +2060,195 @@ function buildPluginNamePickerReply(verb, current) {
1935
2060
  }
1936
2061
  function buildPluginsHelp() {
1937
2062
  return [
1938
- "Codex sub-plugin management (writes only to ~/.openclaw/openclaw.json, never to ~/.codex/config.toml):",
1939
- "- /codex plugins (alias for list)",
1940
- "- /codex plugins list show all configured Codex sub-plugins",
1941
- "- /codex plugins enable <name> enable a configured sub-plugin",
1942
- "- /codex plugins disable <name> disable a configured sub-plugin"
2063
+ "Codex plugin discovery and owner-approved installation:",
2064
+ "- /codex plugins (alias for list)",
2065
+ "- /codex plugins list show explicitly configured plugins",
2066
+ "- /codex plugins available list discoverable Codex marketplaces",
2067
+ "- /codex plugins install <name>@<marketplace> install and authorize one plugin",
2068
+ "- /codex plugins enable <name> enable a configured plugin",
2069
+ "- /codex plugins disable <name> disable a configured plugin",
2070
+ "Only an owner or operator.admin can discover, install, enable, or disable plugins."
1943
2071
  ].join("\n");
1944
2072
  }
2073
+ async function installCodexPlugin(requestedId, io, runtime) {
2074
+ const requested = parseCodexPluginMarketplaceId(requestedId);
2075
+ if (!requested) return { text: "Invalid plugin identifier. Use /codex plugins install <plugin>@<marketplace> with ASCII letters, digits, underscores, or hyphens." };
2076
+ let plugin;
2077
+ let workspaceDir;
2078
+ try {
2079
+ workspaceDir = await runtime.workspaceDir();
2080
+ const matching = (await discoverCodexMarketplacePlugins({
2081
+ request: runtime.list,
2082
+ workspaceDir
2083
+ })).plugins.filter((candidate) => candidate.pluginName === requested.pluginName && marketplaceNamesRepresentSameCatalog(candidate.marketplaceName, requested.marketplaceName));
2084
+ if (matching.length > 1) {
2085
+ plugin = resolveCuratedMarketplaceAliases(matching, requested.marketplaceName);
2086
+ if (!plugin) return { text: `Multiple available Codex plugins match '${formatCodexDisplayText(requestedId)}'; the marketplace identity must be unique.` };
2087
+ } else plugin = matching[0];
2088
+ } catch (error) {
2089
+ return { text: `Could not verify the requested Codex plugin: ${formatCodexDisplayText(errorMessage(error))}` };
2090
+ }
2091
+ if (!plugin) return { text: `${formatCodexDisplayText(requestedId)} was not found. Run /codex plugins available to inspect the current marketplaces.` };
2092
+ if (!plugin.available) return { text: `${formatCodexDisplayText(requestedId)} is unavailable or disabled by its marketplace administrator.` };
2093
+ const alreadyInstalled = plugin.installed && plugin.enabled;
2094
+ if (!alreadyInstalled && !plugin.marketplacePath && plugin.remotePluginId) {
2095
+ if (plugin.mustShowInstallationInterstitial === true) return { text: `${formatCodexDisplayText(requestedId)} requires a Codex installation confirmation that OpenClaw cannot display. Install it in Codex first, then rerun this command to authorize it here.` };
2096
+ if (plugin.mustShowInstallationInterstitial !== false) return { text: `${formatCodexDisplayText(requestedId)} cannot be installed because Codex did not provide its required installation-confirmation policy. Install it in Codex first, then rerun this command to authorize it here.` };
2097
+ }
2098
+ try {
2099
+ const configured = resolveInstalledPluginKey((await io.readConfig()).plugins ?? {}, plugin);
2100
+ if (configured.status === "ambiguous" || configured.status === "mismatched") return { text: describeConfiguredPluginIdentityConflict(requestedId, configured.status) };
2101
+ } catch (error) {
2102
+ return { text: `Could not verify existing Codex plugin authorization: ${formatCodexDisplayText(errorMessage(error))}` };
2103
+ }
2104
+ let result;
2105
+ if (!alreadyInstalled) {
2106
+ const requestParams = plugin.marketplacePath ? {
2107
+ marketplacePath: plugin.marketplacePath,
2108
+ pluginName: plugin.pluginName
2109
+ } : plugin.remotePluginId ? {
2110
+ remoteMarketplaceName: plugin.marketplaceName,
2111
+ pluginName: plugin.remotePluginId
2112
+ } : void 0;
2113
+ if (!requestParams) return { text: `${formatCodexDisplayText(requestedId)} cannot be installed because its marketplace did not provide a trusted local path or remote plugin identifier.` };
2114
+ try {
2115
+ result = await runtime.install(requestParams);
2116
+ } catch (error) {
2117
+ return { text: `Could not install ${formatCodexDisplayText(requestedId)}: ${formatCodexDisplayText(errorMessage(error))}` };
2118
+ }
2119
+ }
2120
+ const selectedPlugin = plugin;
2121
+ try {
2122
+ await io.mutate((block) => {
2123
+ block.plugins ??= {};
2124
+ const configured = resolveInstalledPluginKey(block.plugins, selectedPlugin);
2125
+ if (configured.status === "ambiguous" || configured.status === "mismatched") throw new Error(describeConfiguredPluginIdentityConflict(selectedPlugin.id, configured.status));
2126
+ const curated = isOpenAiCuratedMarketplaceName(selectedPlugin.marketplaceName);
2127
+ const canonicalId = curated ? `${selectedPlugin.pluginName}@${CODEX_PLUGINS_MARKETPLACE_NAME}` : selectedPlugin.id;
2128
+ const configKey = configured.status === "matched" ? configured.configKey : canonicalId;
2129
+ const existing = block.plugins[configKey];
2130
+ block.enabled = true;
2131
+ const updated = {
2132
+ ...existing,
2133
+ enabled: true,
2134
+ marketplaceName: existing?.marketplaceName ?? (curated ? "openai-curated" : selectedPlugin.marketplaceName),
2135
+ pluginName: existing?.pluginName ?? (curated ? selectedPlugin.pluginName : persistedPluginName(selectedPlugin))
2136
+ };
2137
+ block.plugins[configKey] = updated;
2138
+ });
2139
+ } catch (error) {
2140
+ return { text: `${formatCodexDisplayText(requestedId)} was installed in Codex but could not be authorized in OpenClaw and will not be exposed: ${formatCodexDisplayText(errorMessage(error))}` };
2141
+ }
2142
+ let refreshWarning = "";
2143
+ if (runtime.refresh) try {
2144
+ refreshWarning = (await runtime.refresh(workspaceDir)).diagnostics.map((diagnostic) => ` ${formatCodexDisplayText(diagnostic.message)}`).join("");
2145
+ } catch (error) {
2146
+ refreshWarning = ` Runtime refresh requires a new conversation: ${formatCodexDisplayText(errorMessage(error))}`;
2147
+ }
2148
+ const appsNeedingAuth = result?.appsNeedingAuth ?? [];
2149
+ if (appsNeedingAuth.length > 0) {
2150
+ const apps = appsNeedingAuth.map((app) => formatCodexDisplayText(app.name)).slice(0, 5).join(", ");
2151
+ return { text: `${formatCodexDisplayText(requestedId)} was installed and authorized, but ${apps} still require connector authentication. Complete sign-in before using those apps.${refreshWarning} ${POLICY_REFRESH_HINT}` };
2152
+ }
2153
+ const status = alreadyInstalled ? "was already installed in Codex and is now authorized" : "was installed and authorized";
2154
+ return { text: `${formatCodexDisplayText(requestedId)} ${status}.${refreshWarning} ${POLICY_REFRESH_HINT}` };
2155
+ }
2156
+ /** Merge historical curated wire aliases only when they identify the same install source. */
2157
+ function resolveCuratedMarketplaceAliases(plugins, requestedMarketplaceName) {
2158
+ if (!isOpenAiCuratedMarketplaceName(requestedMarketplaceName)) return;
2159
+ const sourceIdentities = new Set(plugins.map((plugin) => plugin.marketplacePath ? `local:${plugin.marketplacePath}` : plugin.remotePluginId ? `remote:${plugin.remotePluginId}` : void 0));
2160
+ if (sourceIdentities.size !== 1 || sourceIdentities.has(void 0)) return;
2161
+ const selected = plugins.find((plugin) => plugin.marketplaceName === requestedMarketplaceName) ?? plugins[0];
2162
+ if (!selected) return;
2163
+ return {
2164
+ ...selected,
2165
+ installed: plugins.some((plugin) => plugin.installed),
2166
+ enabled: plugins.some((plugin) => plugin.installed && plugin.enabled),
2167
+ available: plugins.every((plugin) => plugin.available),
2168
+ ...selected.remotePluginId ? { mustShowInstallationInterstitial: plugins.some((plugin) => plugin.mustShowInstallationInterstitial === true) ? true : plugins.every((plugin) => plugin.mustShowInstallationInterstitial === false) ? false : null } : {},
2169
+ ...plugins.some((plugin) => plugin.installPolicy === "NOT_AVAILABLE") ? { installPolicy: "NOT_AVAILABLE" } : {}
2170
+ };
2171
+ }
2172
+ function persistedPluginName(plugin) {
2173
+ return !plugin.marketplacePath && plugin.summaryId.endsWith(`@${plugin.marketplaceName}`) ? plugin.summaryId : plugin.pluginName;
2174
+ }
2175
+ function resolveConfiguredPluginKey(plugins, target) {
2176
+ const requested = parseCodexPluginMarketplaceId(target);
2177
+ const direct = plugins[target];
2178
+ if (!requested) {
2179
+ if (!direct) return { status: "missing" };
2180
+ const qualifiedName = direct.pluginName ? parseCodexPluginMarketplaceId(direct.pluginName) : void 0;
2181
+ if (qualifiedName && direct.marketplaceName && !marketplaceNamesRepresentSameCatalog(qualifiedName.marketplaceName, direct.marketplaceName)) return { status: "mismatched" };
2182
+ const identity = resolveConfiguredPluginIdentity(direct);
2183
+ if (!identity) return {
2184
+ status: "matched",
2185
+ configKey: target
2186
+ };
2187
+ const marketplaceName = isOpenAiCuratedMarketplaceName(identity.marketplaceName) ? CODEX_PLUGINS_MARKETPLACE_NAME : identity.marketplaceName;
2188
+ const canonicalId = `${identity.pluginName}@${marketplaceName}`;
2189
+ const canonical = plugins[canonicalId];
2190
+ if (canonical && !matchesConfiguredPluginIdentity(canonical, identity, canonicalId)) return { status: "mismatched" };
2191
+ return Object.values(plugins).filter((entry) => matchesConfiguredPluginIdentity(entry, identity, canonicalId)).length > 1 ? { status: "ambiguous" } : {
2192
+ status: "matched",
2193
+ configKey: target
2194
+ };
2195
+ }
2196
+ if (direct && !matchesConfiguredPluginIdentity(direct, requested, target)) return { status: "mismatched" };
2197
+ const matching = Object.entries(plugins).filter(([, entry]) => matchesConfiguredPluginIdentity(entry, requested, target));
2198
+ if (matching.length > 1) return { status: "ambiguous" };
2199
+ const configKey = matching[0]?.[0];
2200
+ return configKey ? {
2201
+ status: "matched",
2202
+ configKey
2203
+ } : { status: "missing" };
2204
+ }
2205
+ function resolveInstalledPluginKey(plugins, plugin) {
2206
+ const discovered = resolveConfiguredPluginKey(plugins, plugin.id);
2207
+ if (discovered.status === "ambiguous" || discovered.status === "mismatched") return discovered;
2208
+ if (!isOpenAiCuratedMarketplaceName(plugin.marketplaceName)) return discovered;
2209
+ const canonical = resolveConfiguredPluginKey(plugins, `${plugin.pluginName}@${CODEX_PLUGINS_MARKETPLACE_NAME}`);
2210
+ if (canonical.status === "ambiguous" || canonical.status === "mismatched") return canonical;
2211
+ if (discovered.status === "matched" && canonical.status === "matched" && discovered.configKey !== canonical.configKey) return { status: "ambiguous" };
2212
+ return canonical.status === "matched" ? canonical : discovered;
2213
+ }
2214
+ function resolveConfiguredPluginIdentity(entry) {
2215
+ if (!entry.pluginName || !entry.marketplaceName) return;
2216
+ const qualified = parseCodexPluginMarketplaceId(entry.pluginName);
2217
+ if (qualified) return marketplaceNamesRepresentSameCatalog(qualified.marketplaceName, entry.marketplaceName) ? {
2218
+ pluginName: qualified.pluginName,
2219
+ marketplaceName: entry.marketplaceName
2220
+ } : void 0;
2221
+ return parseCodexPluginMarketplaceId(`${entry.pluginName}@${entry.marketplaceName}`);
2222
+ }
2223
+ function matchesConfiguredPluginIdentity(entry, requested, target) {
2224
+ const configuredName = entry.pluginName ? parseCodexPluginMarketplaceId(entry.pluginName) : void 0;
2225
+ return typeof entry.marketplaceName === "string" && marketplaceNamesRepresentSameCatalog(entry.marketplaceName, requested.marketplaceName) && (entry.pluginName === requested.pluginName || entry.pluginName === target || configuredName?.pluginName === requested.pluginName && marketplaceNamesRepresentSameCatalog(configuredName.marketplaceName, requested.marketplaceName));
2226
+ }
2227
+ function marketplaceNamesRepresentSameCatalog(left, right) {
2228
+ return left === right || isOpenAiCuratedMarketplaceName(left) && isOpenAiCuratedMarketplaceName(right);
2229
+ }
2230
+ function describeConfiguredPluginIdentityConflict(target, status) {
2231
+ const identity = formatCodexDisplayText(target);
2232
+ return status === "ambiguous" ? `Multiple configured Codex plugins match '${identity}'; resolve duplicate plugin policies first.` : `Configured Codex plugin key '${identity}' points to a different plugin identity; resolve the configuration conflict first.`;
2233
+ }
2234
+ function formatAvailablePlugins(plugins, warnings) {
2235
+ if (plugins.length === 0) return ["No Codex plugins were discovered for the current workspace.", ...warnings.map((warning) => `Warning: ${formatCodexDisplayText(warning)}`)].join("\n");
2236
+ return [
2237
+ "Discoverable Codex plugins:",
2238
+ ...plugins.slice(0, 30).map((plugin) => {
2239
+ const state = plugin.installed ? plugin.enabled ? "installed" : "installed, disabled" : plugin.available ? "available" : "unavailable";
2240
+ const description = plugin.description ? ` - ${formatCodexDisplayText(plugin.description)}` : "";
2241
+ return `- ${plugin.id} (${state})${description}`;
2242
+ }),
2243
+ ...plugins.length > 30 ? ["- Additional plugins omitted."] : [],
2244
+ ...warnings.map((warning) => `Warning: ${formatCodexDisplayText(warning)}`),
2245
+ "To authorize one plugin, an owner or operator.admin must send:",
2246
+ "/codex plugins install <plugin>@<marketplace>"
2247
+ ].join("\n");
2248
+ }
2249
+ function errorMessage(error) {
2250
+ return error instanceof Error ? error.message : String(error);
2251
+ }
1945
2252
  function formatPluginList(plugins, options = {}) {
1946
2253
  const globalEnabled = options.globalEnabled === true;
1947
2254
  const keys = Object.keys(plugins).toSorted();
@@ -1981,7 +2288,76 @@ async function handleCodexSubcommand(ctx, options) {
1981
2288
  if (sandboxBlock) return { text: sandboxBlock };
1982
2289
  if (normalized === "plugins") {
1983
2290
  if (!deps.codexPluginsManagementIo) return { text: "Codex sub-plugin management is not wired up (codexPluginsManagementIo dep is undefined). Edit ~/.openclaw/openclaw.json or use `openclaw config patch` until the runtime exposes the IO." };
1984
- return await handleCodexPluginsSubcommand(ctx, rest, deps.codexPluginsManagementIo);
2291
+ let appServerScope;
2292
+ const getAppServerScope = () => appServerScope ??= resolveCommandAppServerScope(deps, ctx, options.pluginConfig);
2293
+ return await handleCodexPluginsSubcommand(ctx, rest, deps.codexPluginsManagementIo, {
2294
+ workspaceDir: async () => {
2295
+ const data = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
2296
+ return (data?.kind === "codex-app-server-session" ? data.workspaceDir : void 0)?.trim() || deps.resolveCodexDefaultWorkspaceDir(options.pluginConfig);
2297
+ },
2298
+ list: async (requestParams) => {
2299
+ const scope = await getAppServerScope();
2300
+ return await deps.codexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.listPlugins, requestParams, {
2301
+ ...scope,
2302
+ config: ctx.config
2303
+ });
2304
+ },
2305
+ install: async (requestParams) => {
2306
+ const scope = await getAppServerScope();
2307
+ return await deps.codexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.installPlugin, requestParams, {
2308
+ ...scope,
2309
+ config: ctx.config
2310
+ });
2311
+ },
2312
+ refresh: async (workspaceDir) => {
2313
+ const scope = await getAppServerScope();
2314
+ const configuredAppServer = resolveCodexAppServerRuntimeOptions({ pluginConfig: options.pluginConfig });
2315
+ const appServer = scope.startOptions ? {
2316
+ ...configuredAppServer,
2317
+ start: scope.startOptions
2318
+ } : configuredAppServer;
2319
+ const authProfileId = scope.authProfileId ?? void 0;
2320
+ const accountId = await resolveCodexAppServerAuthAccountCacheKey({
2321
+ authProfileId,
2322
+ agentDir: scope.agentDir,
2323
+ config: ctx.config
2324
+ });
2325
+ const client = await getLeasedSharedCodexAppServerClient({
2326
+ startOptions: appServer.start,
2327
+ pluginConfig: options.pluginConfig,
2328
+ authProfileId: scope.authProfileId,
2329
+ agentDir: scope.agentDir,
2330
+ config: ctx.config
2331
+ });
2332
+ try {
2333
+ const appCacheKey = buildCodexPluginAppCacheKey({
2334
+ appServer,
2335
+ agentDir: scope.agentDir,
2336
+ authProfileId,
2337
+ accountId,
2338
+ envApiKeyFingerprint: authProfileId ? void 0 : resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions: appServer.start }),
2339
+ appServerVersion: client.getServerVersion(),
2340
+ runtimeIdentity: client.getRuntimeIdentity()
2341
+ });
2342
+ defaultCodexPluginMetadataCache.invalidate(appCacheKey);
2343
+ return await refreshCodexPluginRuntimeState({
2344
+ configCwd: workspaceDir,
2345
+ appCache: defaultCodexAppInventoryCache,
2346
+ appCacheKey,
2347
+ metadataCache: defaultCodexPluginMetadataCache,
2348
+ request: async (method, requestParams) => {
2349
+ const requestMethod = resolvePluginRuntimeRefreshMethod(method);
2350
+ return await deps.codexControlRequest(options.pluginConfig, requestMethod, requestParams, {
2351
+ ...scope,
2352
+ config: ctx.config
2353
+ });
2354
+ }
2355
+ });
2356
+ } finally {
2357
+ releaseLeasedSharedCodexAppServerClient(client);
2358
+ }
2359
+ }
2360
+ });
1985
2361
  }
1986
2362
  if (normalized === "status") {
1987
2363
  if (rest.length > 0) return { text: "Usage: /codex status" };
@@ -2062,5 +2438,18 @@ async function handleCodexSubcommand(ctx, options) {
2062
2438
  }
2063
2439
  return { text: `Unknown Codex command: ${formatCodexDisplayText(subcommand)}\n\n${buildHelp()}` };
2064
2440
  }
2441
+ function resolvePluginRuntimeRefreshMethod(method) {
2442
+ const recognized = [
2443
+ CODEX_CONTROL_METHODS.listPlugins,
2444
+ CODEX_CONTROL_METHODS.listSkills,
2445
+ CODEX_CONTROL_METHODS.listHooks,
2446
+ CODEX_CONTROL_METHODS.reloadMcpServers,
2447
+ CODEX_CONTROL_METHODS.installedApps,
2448
+ CODEX_CONTROL_METHODS.listApps,
2449
+ CODEX_CONTROL_METHODS.readApps
2450
+ ].find((candidate) => candidate === method);
2451
+ if (!recognized) throw new Error(`Unexpected Codex plugin refresh method: ${method}`);
2452
+ return recognized;
2453
+ }
2065
2454
  //#endregion
2066
2455
  export { handleCodexSubcommand };