@openclaw/codex 2026.7.2-beta.7 → 2026.8.1-beta.3

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 (68) hide show
  1. package/README.md +2 -0
  2. package/dist/api.js +147 -0
  3. package/dist/app-server-policy-bg5QPSVP.js +25 -0
  4. package/dist/attempt-client-cleanup-URjUlCHg.js +735 -0
  5. package/dist/binding-connection-DAvZwp96.js +45 -0
  6. package/dist/{bounded-turn-D2ZAgZVz.js → bounded-turn-4BjrZEwW.js} +63 -56
  7. package/dist/{capabilities-BFDTVYr4.js → capabilities-CH3K57v4.js} +8 -2
  8. package/dist/cli-metadata.js +1 -1
  9. package/dist/{command-formatters-C336eRVS.js → command-formatters-D0nZ3qtd.js} +18 -18
  10. package/dist/{command-handlers-DAHUSry9.js → command-handlers-DC86lgT4.js} +507 -101
  11. package/dist/command-rpc-xUIIonsO.js +90 -0
  12. package/dist/{compact-D_uvHurS.js → compact-COqgBRny.js} +234 -214
  13. package/dist/{session-binding-BXpo9ymA.js → config-CpNeo5AE.js} +284 -1163
  14. package/dist/context-compaction-activity-BOeJf7yo.js +58 -0
  15. package/dist/{conversation-binding-data-BHRpYbX3.js → conversation-binding-data-CW2GuupV.js} +6 -10
  16. package/dist/doctor-contract-api.js +48 -50
  17. package/dist/{dynamic-tools-D2gYkSBO.js → dynamic-tools-fJZhl4CE.js} +3442 -1433
  18. package/dist/{effective-mcp-catalog-Bbd98Inw.js → effective-mcp-catalog-4YKn51aG.js} +20 -15
  19. package/dist/elicitation-input-DMkeki8d.js +71 -0
  20. package/dist/{event-projector-assistant-message-DTgnPPVF.js → event-projector-assistant-message-BwxV4d6N.js} +11 -23
  21. package/dist/{attempt-notifications-AP-a-Q5k.js → event-projector-usage-CLwNEJqW.js} +113 -29
  22. package/dist/harness-DoKQcF8n.js +258 -0
  23. package/dist/harness.js +2 -160
  24. package/dist/image-payload-sanitizer-CnfoUPCE.js +65 -0
  25. package/dist/index.js +730 -256
  26. package/dist/isolated-completion-DPMtlH9r.js +75 -0
  27. package/dist/local-runtime-attribution-B_2mnqhb.js +19 -0
  28. package/dist/managed-binary-By4FZC6i.js +212 -0
  29. package/dist/media-understanding-provider.js +5 -5
  30. package/dist/model-catalog-CmGoqVKB.js +48 -0
  31. package/dist/{models-BBMKU4jA.js → models-CZw1wClT.js} +12 -17
  32. package/dist/native-subagent-monitor-GGq3RnOo.js +3225 -0
  33. package/dist/node-exec-server.runtime-Bsu1LdFZ.js +192 -0
  34. package/dist/{plugin-app-cache-key-DztPafOA.js → plugin-app-cache-key-DMNQ8Zi8.js} +18 -6
  35. package/dist/plugin-approval-roundtrip-C75Yiiwc.js +95 -0
  36. package/dist/plugin-marketplace-discovery-CSWDkMrP.js +399 -0
  37. package/dist/{computer-use-CYEc8U3w.js → plugin-metadata-cache-BXH5NF-4.js} +109 -6
  38. package/dist/protocol-validators-VAVyEnDp.js +2399 -0
  39. package/dist/{rate-limits-CU-gVCCS.js → rate-limits-BI1dMHDG.js} +17 -35
  40. package/dist/{request-qKh331yu.js → request-DSK0W6YI.js} +3 -3
  41. package/dist/{run-attempt-CY9WlDLc.js → run-attempt-BV3HzuCf.js} +3416 -7194
  42. package/dist/{runtime-artifact-CmtG_t1L.js → runtime-artifact-BvyT1uyd.js} +11 -29
  43. package/dist/{sandbox-guard-NuhpXmPB.js → sandbox-guard-C1qEeZ5C.js} +29 -30
  44. package/dist/session-binding-BTrLf9Qm.js +874 -0
  45. package/dist/{session-catalog-DyOgR4WH.js → session-catalog-ezP_UcRM.js} +1584 -1289
  46. package/dist/session-catalog-home-id-BNwAJ9D7.js +18 -0
  47. package/dist/{session-cli-2PZaFLRh.js → session-cli-B1hKvfkI.js} +20 -7
  48. package/dist/session-retirement-CE4iMiLh.js +36 -0
  49. package/dist/{session-upstream-marker-B6U7tLw5.js → session-upstream-marker-D_2npHlA.js} +1 -1
  50. package/dist/{settled-turn-finalizer-CXY2Z9dT.js → settled-turn-finalizer-Dx1onIqg.js} +40 -28
  51. package/dist/{shared-client-DTbvH-XV.js → shared-client-Dw-rzii0.js} +1315 -469
  52. package/dist/{side-question-_ulBk1AL.js → side-question-DbXkJZzg.js} +140 -69
  53. package/dist/{thread-lifecycle-DF53ogiA.js → thread-lifecycle-DKdrnTMX.js} +927 -768
  54. package/dist/thread-ownership-i2NBwDUL.js +92 -0
  55. package/dist/thread-resume-CdJk2Ehf.js +30 -0
  56. package/dist/transcript-mirror-BU0_LKAL.js +588 -0
  57. package/dist/{transport-stdio-DXgGZ7Ib.js → transport-stdio-DhoGy33Z.js} +13 -2
  58. package/dist/upstream-prompt-provenance-CFmsKwr9.js +39 -0
  59. package/dist/{upstream-session-fork-Dy4Xvuoj.js → upstream-session-fork-BDVJCLAD.js} +14 -8
  60. package/dist/{usage-CQ6GhD5-.js → usage-9dKJCgcy.js} +3 -3
  61. package/dist/{web-search-provider.runtime-CCFhg2z5.js → web-search-provider.runtime-DCdx4DGu.js} +8 -9
  62. package/openclaw.plugin.json +49 -4
  63. package/package.json +6 -5
  64. package/dist/app-server-policy-BSaO2JIT.js +0 -49
  65. package/dist/conversation-control-xsIeXbo4.js +0 -287
  66. package/dist/event-projector-usage-Xp4AlR2k.js +0 -86
  67. package/dist/notification-correlation-CU6_AnOb.js +0 -67
  68. package/dist/transcript-mirror-DzsdNCf5.js +0 -6924
@@ -1,25 +1,31 @@
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 { M as isCodexFastServiceTier, _ as CODEX_PLUGINS_MARKETPLACE_NAME, i as resolveCodexAppServerRuntimeOptions } from "./config-CpNeo5AE.js";
2
+ import { U as consumeCodexAppServerLiveThread, a as getLeasedSharedCodexAppServerClient, et as resolveCodexAppServerAuthAccountCacheKey, l as releaseLeasedSharedCodexAppServerClient, nt as resolveCodexAppServerAuthProfileIdForAgent, rt as resolveCodexAppServerFallbackApiKeyCacheKey, ut as isJsonObject } from "./shared-client-Dw-rzii0.js";
3
+ import { c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity, i as createCodexSessionGenerationSupersededError, n as assertCodexBindingMayBeReplaced, r as bindingStoreKey, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-BTrLf9Qm.js";
4
+ import { i as assertCodexThreadResumeResponse } from "./protocol-validators-VAVyEnDp.js";
5
+ import { t as listAllCodexAppServerModels } from "./models-CZw1wClT.js";
6
+ import { o as defaultCodexAppInventoryCache, r as buildCodexPluginAppCacheKey } from "./plugin-app-cache-key-DMNQ8Zi8.js";
7
+ import { et as refreshCodexPluginRuntimeState, nt as isOpenAiCuratedMarketplaceName } from "./thread-lifecycle-DKdrnTMX.js";
8
+ import { t as isIncognitoSessionKey } from "./incognito-session-ET_ufDH-.js";
9
+ import { t as CODEX_CONTROL_METHODS } from "./capabilities-CH3K57v4.js";
10
+ import { s as summarizeCodexAccountUsage } from "./rate-limits-BI1dMHDG.js";
11
+ 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-D0nZ3qtd.js";
12
+ import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-C1qEeZ5C.js";
13
+ 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-i2NBwDUL.js";
14
+ import { a as parseCodexPermissionsModeArg, c as setCodexConversationModel, d as stopCodexConversationTurn, g as hasCodexAdminScope, h as canMutateCodexHost, i as parseCodexFastModeArg, l as setCodexConversationPermissions, m as CODEX_NATIVE_EXECUTION_AUTH_ERROR, n as parseCodexPluginMarketplaceId, o as readCodexConversationActiveTurn, p as CODEX_FULL_PERMISSIONS_AUTH_ERROR, r as formatPermissionsMode, s as setCodexConversationFastMode, t as discoverCodexMarketplacePlugins, u as steerCodexConversationTurn } from "./plugin-marketplace-discovery-CSWDkMrP.js";
15
+ import { i as readCodexConversationBindingData, n as createCodexCliNodeConversationBindingData, o as resolveCodexDefaultWorkspaceDir, r as createCodexConversationBindingData } from "./conversation-binding-data-CW2GuupV.js";
16
+ import { n as resolveCodexBindingAppServerConnection } from "./binding-connection-DAvZwp96.js";
17
+ import { l as formatCodexCliSessions } from "./session-catalog-ezP_UcRM.js";
18
+ import { i as safeCodexControlRequest, n as readCodexStatusProbes, r as requestOptions, t as codexControlRequest } from "./command-rpc-xUIIonsO.js";
19
+ import { i as readCodexComputerUseStatus, r as installCodexComputerUse, t as defaultCodexPluginMetadataCache } from "./plugin-metadata-cache-BXH5NF-4.js";
20
+ import { normalizeOptionalString, normalizeUniqueStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
14
21
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
15
22
  import crypto from "node:crypto";
16
23
  import { ensureAuthProfileStore, findNormalizedProviderValue, resolveAgentDir, resolveAuthProfileEligibility, resolveAuthProfileOrder, resolveProfileUnusableUntilForDisplay, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
17
- import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
18
- import { normalizeOptionalString, normalizeUniqueStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
19
24
  import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
20
25
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
21
26
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
22
- import { MODEL_SELECTION_LOCKED_MESSAGE, isModelSelectionLocked } from "openclaw/plugin-sdk/model-session-runtime";
27
+ import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
28
+ import { MODEL_SELECTION_LOCKED_MESSAGE, isModelSelectionLocked, resolvePersistedSessionRuntimeId } from "openclaw/plugin-sdk/model-session-runtime";
23
29
  //#region extensions/codex/src/command-account.ts
24
30
  const OPENAI_PROVIDER_ID = "openai";
25
31
  const OPENAI_CODEX_PROVIDER_ID = OPENAI_PROVIDER_ID;
@@ -148,9 +154,9 @@ function isActiveProfileCandidate(params, profileId) {
148
154
  function resolveLiveAccountProfileId(params) {
149
155
  if (!params.account.ok || !isJsonObject(params.account.value)) return;
150
156
  const account = isJsonObject(params.account.value.account) ? params.account.value.account : params.account.value;
151
- const type = readString(account, "type")?.toLowerCase();
157
+ const type = normalizeOptionalString(account.type)?.toLowerCase();
152
158
  if (type === "chatgpt") {
153
- const email = readString(account, "email")?.toLowerCase();
159
+ const email = normalizeOptionalString(account.email)?.toLowerCase();
154
160
  const firstSubscription = params.order.find((profileId) => isChatGptSubscriptionProfile(params.store.profiles[profileId]));
155
161
  if (!email) return firstSubscription;
156
162
  return params.order.find((profileId) => {
@@ -231,10 +237,6 @@ function formatSubscriptionUsageLine(usage) {
231
237
  function formatUsageLineForDisplay(value) {
232
238
  return value.replace(/^weekly\b/u, "Weekly").replace(/\bshort-term\b/u, "Short-term");
233
239
  }
234
- function readString(record, key) {
235
- const value = record[key];
236
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
237
- }
238
240
  function isChatGptSubscriptionProfile(credential) {
239
241
  return credential?.type === "oauth" || credential?.type === "token";
240
242
  }
@@ -1276,7 +1278,7 @@ async function sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, n
1276
1278
  });
1277
1279
  continue;
1278
1280
  }
1279
- const responseThreadId = isJsonObject(response.value) ? readString$1(response.value, "threadId") : void 0;
1281
+ const responseThreadId = isJsonObject(response.value) ? normalizeOptionalString(response.value.threadId) : void 0;
1280
1282
  sent.push({
1281
1283
  ...target,
1282
1284
  threadId: responseThreadId ?? target.threadId
@@ -1442,10 +1444,35 @@ async function bindConversation(deps, ctx, pluginConfig, args) {
1442
1444
  async function detachConversation(deps, ctx) {
1443
1445
  if (isCurrentSessionModelSelectionLocked(ctx)) return MODEL_SELECTION_LOCKED_MESSAGE;
1444
1446
  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.";
1447
+ const identity = data?.kind === "codex-app-server-session" ? conversationBindingIdentity(data.bindingId) : void 0;
1448
+ const sourceSessionKey = data?.kind === "codex-app-server-session" ? data.source?.sessionKey : void 0;
1449
+ let expectedThreadId;
1450
+ let expectedStartId;
1451
+ if (data?.kind === "codex-app-server-session") {
1452
+ const binding = await deps.bindingStore.read(identity);
1453
+ assertCodexBindingMayBeReplaced(binding, "detaching its conversation binding");
1454
+ if (deps.readCodexConversationActiveTurn(identity)) return "This Codex conversation has an active run; use /codex stop before detaching it.";
1455
+ expectedThreadId = binding?.threadId;
1456
+ expectedStartId = binding?.conversationStartId;
1457
+ }
1458
+ const detachPublicConversation = async () => {
1459
+ return (await ctx.detachConversationBinding()).removed ? "Detached this conversation from Codex." : "No Codex conversation binding was attached.";
1460
+ };
1461
+ if (identity && expectedThreadId) return await withCodexConversationThreadActivity(identity.bindingId, async () => {
1462
+ let detachedPublicConversation;
1463
+ if (!await retireCodexConversationThreadBinding({
1464
+ bindingStore: deps.bindingStore,
1465
+ identity,
1466
+ expectedThreadId,
1467
+ ...expectedStartId ? { expectedStartId } : {},
1468
+ ...isIncognitoSessionKey(sourceSessionKey) ? { allowUntracked: true } : {},
1469
+ afterClear: async () => {
1470
+ detachedPublicConversation = await detachPublicConversation();
1471
+ }
1472
+ })) return "This Codex conversation binding changed while detaching; try again.";
1473
+ return detachedPublicConversation;
1474
+ });
1475
+ return await detachPublicConversation();
1449
1476
  }
1450
1477
  async function describeConversationBinding(deps, ctx) {
1451
1478
  const current = await ctx.getCurrentConversationBinding();
@@ -1462,13 +1489,23 @@ async function describeConversationBinding(deps, ctx) {
1462
1489
  const identity = conversationBindingIdentity(data.bindingId);
1463
1490
  const threadBinding = await deps.bindingStore.read(identity);
1464
1491
  const active = deps.readCodexConversationActiveTurn(identity);
1492
+ const sessionKey = ctx.sessionKey?.trim();
1493
+ const { agentId } = resolveCodexConversationControlScope(ctx);
1494
+ const sessionEntry = sessionKey ? getSessionEntry({
1495
+ agentId,
1496
+ storePath: resolveStorePath(ctx.config.session?.store, { agentId }),
1497
+ sessionKey,
1498
+ hydrateSkillPromptRefs: false,
1499
+ readConsistency: "latest"
1500
+ }) : void 0;
1501
+ const permissionMode = !ctx.sessionId || sessionEntry?.sessionId === ctx.sessionId ? sessionEntry?.permissionMode : void 0;
1465
1502
  return [
1466
1503
  "Codex conversation binding:",
1467
1504
  `- Thread: ${formatCodexDisplayText(threadBinding?.threadId ?? "unknown")}`,
1468
1505
  `- Workspace: ${formatCodexDisplayText(data.workspaceDir)}`,
1469
1506
  `- Model: ${formatCodexDisplayText(threadBinding?.model ?? "default")}`,
1470
1507
  `- Fast: ${isCodexFastServiceTier(threadBinding?.serviceTier) ? "on" : "off"}`,
1471
- `- Permissions: ${threadBinding ? formatPermissionsMode(threadBinding) : "default"}`,
1508
+ `- Permissions: ${formatPermissionsMode(permissionMode)}`,
1472
1509
  `- Active run: ${formatCodexDisplayText(active ? active.turnId : "none")}`,
1473
1510
  `- Binding: ${formatCodexDisplayText(data.bindingId)}`
1474
1511
  ].join("\n");
@@ -1507,52 +1544,83 @@ async function resumeThread(deps, ctx, pluginConfig, args) {
1507
1544
  agentId: scope.agentId,
1508
1545
  config: ctx.config
1509
1546
  });
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,
1547
+ return await withExclusiveCodexAppServerThread({
1548
+ bindingStore: deps.bindingStore,
1549
+ identity,
1550
+ threadId: normalizedThreadId,
1551
+ run: async () => await deps.bindingStore.withLease(identity, async () => {
1552
+ if (!await reclaimCurrentCodexSessionGeneration({
1553
+ bindingStore: deps.bindingStore,
1554
+ identity,
1555
+ config: ctx.config
1556
+ })) throw createCodexSessionGenerationSupersededError(identity.sessionId);
1557
+ const currentBinding = await deps.bindingStore.read(identity);
1558
+ assertCodexBindingMayBeReplaced(currentBinding, "attaching a different resumed thread");
1559
+ const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
1560
+ authProfileId: currentBinding?.authProfileId,
1561
+ agentDir: scope.agentDir,
1562
+ config: ctx.config
1563
+ });
1564
+ let committedResponse = false;
1565
+ const commitResumedThread = async (value, client) => {
1566
+ const response = assertCodexThreadResumeResponse(value);
1567
+ const effectiveThreadId = response.thread.id;
1568
+ if (effectiveThreadId !== normalizedThreadId) throw new Error(`Codex thread/resume returned ${effectiveThreadId} for ${normalizedThreadId}`);
1569
+ const resumedCwd = response.thread.cwd;
1570
+ if (typeof resumedCwd !== "string") throw new Error(`Codex thread/resume returned no cwd for ${normalizedThreadId}`);
1571
+ const modelProvider = normalizeCodexAppServerBindingModelProvider({
1572
+ authProfileId,
1573
+ modelProvider: response.modelProvider ?? void 0,
1574
+ agentDir: scope.agentDir,
1575
+ config: ctx.config
1576
+ });
1577
+ const bindingBeforeCommit = await deps.bindingStore.read(identity);
1578
+ assertCodexBindingMayBeReplaced(bindingBeforeCommit, "committing a different resumed thread");
1579
+ const clientId = client?.getInstanceId();
1580
+ const sameOwner = client ? isSameCodexAppServerThreadOwner(bindingBeforeCommit, {
1581
+ threadId: effectiveThreadId,
1582
+ clientId
1583
+ }) : bindingBeforeCommit?.threadId === effectiveThreadId;
1584
+ let retained = false;
1585
+ try {
1586
+ if (client) {
1587
+ retained = await retainCodexAppServerBindingSubscription(client, effectiveThreadId, sameOwner ? await consumeCodexAppServerLiveThread(client, effectiveThreadId) : void 0);
1588
+ if (!retained) throw new Error("Codex resumed thread lost its native subscription owner.");
1589
+ }
1590
+ if (bindingBeforeCommit && !sameOwner) await releaseCodexAppServerBindingSubscription(bindingBeforeCommit);
1591
+ if (!await deps.bindingStore.mutate(identity, {
1592
+ kind: "set",
1593
+ binding: {
1594
+ ...bindingBeforeCommit?.threadId === effectiveThreadId ? bindingBeforeCommit : {},
1595
+ threadId: effectiveThreadId,
1596
+ ...clientId ? { clientId } : {},
1597
+ cwd: resumedCwd,
1598
+ authProfileId,
1599
+ model: response.model,
1600
+ modelProvider,
1601
+ historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString()
1602
+ }
1603
+ })) throw new Error("Codex thread binding changed while attaching the resumed thread.");
1604
+ } catch (error) {
1605
+ if (client && !sameOwner) await rollbackCodexAppServerBindingSubscription(client, effectiveThreadId, retained);
1606
+ throw error;
1607
+ }
1608
+ committedResponse = true;
1609
+ };
1610
+ const response = await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.resumeThread, {
1611
+ threadId: normalizedThreadId,
1612
+ excludeTurns: true
1613
+ }, {
1614
+ config: ctx.config,
1615
+ agentDir: scope.agentDir,
1549
1616
  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)}.`;
1617
+ sessionKey: ctx.sessionKey,
1618
+ sessionId: ctx.sessionId,
1619
+ onResponse: commitResumedThread
1620
+ });
1621
+ if (!committedResponse) await commitResumedThread(response);
1622
+ return `Attached this OpenClaw session to Codex thread ${formatCodexDisplayText(normalizedThreadId)}.`;
1623
+ })
1556
1624
  });
1557
1625
  }
1558
1626
  async function bindCodexCliNodeSession(deps, ctx, parsed) {
@@ -1717,8 +1785,8 @@ async function handleNativeGoal(deps, ctx, pluginConfig, args) {
1717
1785
  function formatNativeGoal(response) {
1718
1786
  const goal = isJsonObject(response) && isJsonObject(response.goal) ? response.goal : void 0;
1719
1787
  if (!goal) return "No Codex goal is active.";
1720
- const objective = readString$1(goal, "objective") ?? "unknown";
1721
- const status = readString$1(goal, "status") ?? "unknown";
1788
+ const objective = normalizeOptionalString(goal.objective) ?? "unknown";
1789
+ const status = normalizeOptionalString(goal.status) ?? "unknown";
1722
1790
  const tokensUsed = typeof goal.tokensUsed === "number" ? goal.tokensUsed : 0;
1723
1791
  const tokenBudget = typeof goal.tokenBudget === "number" ? goal.tokenBudget : void 0;
1724
1792
  return [
@@ -1758,8 +1826,16 @@ async function setConversationModel(deps, ctx, pluginConfig, args) {
1758
1826
  const target = await resolveControlTarget(ctx);
1759
1827
  if (!target) return "Cannot set Codex model because this command did not include a stable binding identity.";
1760
1828
  if (!normalized) {
1829
+ const currentSession = ctx.sessionId && ctx.sessionKey ? getSessionEntry({
1830
+ storePath: resolveStorePath(ctx.config.session?.store, { agentId: target.agentId }),
1831
+ sessionKey: ctx.sessionKey,
1832
+ hydrateSkillPromptRefs: false,
1833
+ readConsistency: "latest"
1834
+ }) : void 0;
1835
+ const selectedModel = currentSession && currentSession.sessionId === ctx.sessionId ? currentSession.modelOverride ?? currentSession.model : void 0;
1761
1836
  const binding = await deps.bindingStore.read(target.identity);
1762
- return binding?.model ? `Codex model: ${formatCodexDisplayText(binding.model)}` : "Usage: /codex model <model>";
1837
+ const activeModel = selectedModel ?? binding?.model;
1838
+ return activeModel ? `Codex model: ${formatCodexDisplayText(activeModel)}` : "Usage: /codex model <model>";
1763
1839
  }
1764
1840
  return await deps.setCodexConversationModel({
1765
1841
  identity: target.identity,
@@ -1767,7 +1843,12 @@ async function setConversationModel(deps, ctx, pluginConfig, args) {
1767
1843
  pluginConfig,
1768
1844
  model: normalized,
1769
1845
  agentDir: target.agentDir,
1770
- config: ctx.config
1846
+ config: ctx.config,
1847
+ ...ctx.sessionId && ctx.sessionKey ? { session: {
1848
+ agentId: target.agentId,
1849
+ sessionId: ctx.sessionId,
1850
+ sessionKey: ctx.sessionKey
1851
+ } } : {}
1771
1852
  });
1772
1853
  }
1773
1854
  async function setConversationFastMode(deps, ctx, args) {
@@ -1786,43 +1867,70 @@ async function setConversationFastMode(deps, ctx, args) {
1786
1867
  async function setConversationPermissions(deps, ctx, args) {
1787
1868
  if (args.length > 1) return "Usage: /codex permissions [default|yolo|status]";
1788
1869
  const target = await resolveControlTarget(ctx);
1789
- if (!target) return "Cannot set Codex permissions because this command did not include a stable binding identity.";
1870
+ if (!target || !ctx.sessionId || !ctx.sessionKey) return "Cannot set Codex permissions because this command did not include a complete session identity.";
1790
1871
  const value = args[0];
1791
1872
  const parsed = parseCodexPermissionsModeArg(value);
1792
1873
  if (value && !parsed && value.trim().toLowerCase() !== "status") return "Usage: /codex permissions [default|yolo|status]";
1874
+ if (parsed === "yolo" && !hasCodexAdminScope(ctx)) return CODEX_FULL_PERMISSIONS_AUTH_ERROR;
1793
1875
  return await deps.setCodexConversationPermissions({
1794
- identity: target.identity,
1795
- bindingStore: deps.bindingStore,
1796
- mode: parsed
1876
+ mode: parsed,
1877
+ config: ctx.config,
1878
+ session: {
1879
+ agentId: target.agentId,
1880
+ sessionId: ctx.sessionId,
1881
+ sessionKey: ctx.sessionKey
1882
+ }
1797
1883
  });
1798
1884
  }
1799
1885
  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}`;
1886
+ if (args.length > 0) return `Usage: /codex ${kind}`;
1802
1887
  const target = await resolveControlTarget(ctx);
1803
- if (!target) return `Cannot start Codex ${label} because this command did not include a stable binding identity.`;
1888
+ if (!target) return `Cannot start Codex ${kind === "compact" ? "compaction" : "review"} because this command did not include a stable binding identity.`;
1804
1889
  const binding = await deps.bindingStore.read(target.identity);
1805
1890
  if (!binding?.threadId) return `No Codex thread is attached to this OpenClaw session yet.`;
1891
+ if (kind === "compact") {
1892
+ const sessionTarget = ctx.sessionTarget;
1893
+ 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.";
1894
+ const currentSession = getSessionEntry({
1895
+ storePath: sessionTarget.storePath,
1896
+ sessionKey: ctx.sessionKey,
1897
+ hydrateSkillPromptRefs: false,
1898
+ readConsistency: "latest"
1899
+ });
1900
+ if (currentSession?.sessionId !== ctx.sessionId || resolvePersistedSessionRuntimeId(currentSession) !== "codex") return "Codex compaction is unavailable because the current OpenClaw session is not using the Codex runtime.";
1901
+ if (target.identity.kind === "conversation") {
1902
+ if (!isSameCodexAppServerThreadOwner(binding, ctx.sessionId ? await deps.bindingStore.read(sessionBindingIdentity({
1903
+ sessionId: ctx.sessionId,
1904
+ sessionKey: ctx.sessionKey,
1905
+ agentId: sessionTarget.agentId,
1906
+ config: ctx.config
1907
+ })) : 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.";
1908
+ }
1909
+ const compactCurrent = ctx.runtimeContext?.compactCurrent;
1910
+ if (!compactCurrent) return "Codex compaction is unavailable because this command is not bound to a session.";
1911
+ const result = await compactCurrent();
1912
+ return result.compacted ? `Compacted Codex session (${result.tokensAfter ?? "unknown"} tokens after).` : `Codex compaction did not complete: ${formatCodexDisplayText(result.reason ?? "no reason returned")}.`;
1913
+ }
1806
1914
  const connection = resolveCodexBindingAppServerConnection({
1807
1915
  binding,
1808
1916
  authProfileId: binding.authProfileId,
1809
1917
  pluginConfig
1810
1918
  });
1811
- await deps.codexControlRequest(pluginConfig, kind === "compact" ? CODEX_CONTROL_METHODS.compact : CODEX_CONTROL_METHODS.review, kind === "review" ? {
1919
+ await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.review, {
1812
1920
  threadId: binding.threadId,
1813
1921
  target: { type: "uncommittedChanges" }
1814
- } : { threadId: binding.threadId }, {
1922
+ }, {
1815
1923
  agentDir: target.agentDir,
1816
1924
  authProfileId: connection.clientAuthProfileId,
1817
1925
  config: ctx.config,
1818
1926
  ...connection.usesSupervisionConnection ? { startOptions: connection.appServer.start } : {}
1819
1927
  });
1820
- return `Started Codex ${label} for thread ${formatCodexDisplayText(binding.threadId)}.`;
1928
+ return `Started Codex review for thread ${formatCodexDisplayText(binding.threadId)}.`;
1821
1929
  }
1822
1930
  //#endregion
1823
1931
  //#region extensions/codex/src/command-plugins-management.ts
1824
1932
  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) {
1933
+ async function handleCodexPluginsSubcommand(ctx, rest, io, runtime) {
1826
1934
  const [verb = "list", ...args] = rest;
1827
1935
  const normalized = verb.toLowerCase();
1828
1936
  if (normalized === "menu") {
@@ -1838,22 +1946,51 @@ async function handleCodexPluginsSubcommand(ctx, rest, io) {
1838
1946
  const current = await io.readConfig();
1839
1947
  return { text: formatPluginList(current.plugins ?? {}, { globalEnabled: current.enabled === true }) };
1840
1948
  }
1949
+ if (normalized === "available") {
1950
+ if (args.length > 0) return { text: "Usage: /codex plugins available" };
1951
+ if (!canMutateCodexHost(ctx)) return { text: "Only an owner or operator.admin gateway client can list available Codex plugins." };
1952
+ if (!runtime) return { text: "Codex plugin discovery is unavailable for this command." };
1953
+ try {
1954
+ const discovered = await discoverCodexMarketplacePlugins({
1955
+ request: runtime.list,
1956
+ workspaceDir: await runtime.workspaceDir()
1957
+ });
1958
+ return { text: formatAvailablePlugins(discovered.plugins, discovered.warnings) };
1959
+ } catch (error) {
1960
+ return { text: `Could not list Codex plugins: ${formatCodexDisplayText(errorMessage(error))}` };
1961
+ }
1962
+ }
1963
+ if (normalized === "install") {
1964
+ if (args.length !== 1 || !args[0]) return { text: "Usage: /codex plugins install <plugin>@<marketplace>" };
1965
+ if (!canMutateCodexHost(ctx)) return { text: "Only an owner or operator.admin gateway client can run /codex plugins install." };
1966
+ if (!runtime) return { text: "Codex plugin installation is unavailable for this command." };
1967
+ return await installCodexPlugin(args[0], io, runtime);
1968
+ }
1841
1969
  const target = args[0];
1842
1970
  if (normalized === "enable" || normalized === "disable") {
1843
1971
  if (args.length === 0) return buildPluginNamePickerReply(normalized, await io.readConfig());
1844
1972
  if (!target || args.length > 1) return { text: `Usage: /codex plugins ${normalized} <name>` };
1845
1973
  if (!canMutateCodexHost(ctx)) return { text: `Only an owner or operator.admin gateway client can run /codex plugins ${normalized}.` };
1846
1974
  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.` };
1975
+ const current = (await io.readConfig()).plugins ?? {};
1976
+ const exact = current[target];
1977
+ const requested = parseCodexPluginMarketplaceId(target);
1978
+ const configured = exact && requested && !matchesConfiguredPluginIdentity(exact, requested, target) ? {
1979
+ status: "matched",
1980
+ configKey: target
1981
+ } : resolveConfiguredPluginKey(current, target);
1982
+ if (configured.status === "ambiguous" || configured.status === "mismatched") return { text: describeConfiguredPluginIdentityConflict(target, configured.status) };
1983
+ if (configured.status === "missing") return { text: `Codex sub-plugin '${formatCodexDisplayText(target)}' is not configured. Run '/codex plugins list' to see configured plugins.` };
1984
+ const configKey = configured.configKey;
1848
1985
  await io.mutate((block) => {
1849
1986
  if (wantEnabled) block.enabled = true;
1850
1987
  block.plugins ??= {};
1851
- block.plugins[target] = {
1852
- ...block.plugins[target],
1988
+ block.plugins[configKey] = {
1989
+ ...block.plugins[configKey],
1853
1990
  enabled: wantEnabled
1854
1991
  };
1855
1992
  });
1856
- return { text: `${formatCodexDisplayText(target)}: ${wantEnabled ? "enabled" : "disabled"} in openclaw.json. ${POLICY_REFRESH_HINT}` };
1993
+ return { text: `${formatCodexDisplayText(configKey)}: ${wantEnabled ? "enabled" : "disabled"} in openclaw.json. ${POLICY_REFRESH_HINT}` };
1857
1994
  }
1858
1995
  return { text: `Unknown /codex plugins subcommand: ${formatCodexDisplayText(verb)}\n\n${buildPluginsHelp()}` };
1859
1996
  }
@@ -1863,9 +2000,10 @@ function buildPluginsMenuReply() {
1863
2000
  "Codex sub-plugins. Pick a sub-action or type:",
1864
2001
  "",
1865
2002
  " 1. /codex plugins list",
1866
- " 2. /codex plugins enable",
1867
- " 3. /codex plugins disable",
1868
- " 4. /codex plugins help",
2003
+ " 2. /codex plugins available",
2004
+ " 3. /codex plugins enable",
2005
+ " 4. /codex plugins disable",
2006
+ " 5. /codex plugins help",
1869
2007
  "",
1870
2008
  "Type '/codex' to go back to the main menu."
1871
2009
  ].join("\n"),
@@ -1874,6 +2012,10 @@ function buildPluginsMenuReply() {
1874
2012
  label: "list",
1875
2013
  command: "/codex plugins list"
1876
2014
  },
2015
+ {
2016
+ label: "available",
2017
+ command: "/codex plugins available"
2018
+ },
1877
2019
  {
1878
2020
  label: "enable",
1879
2021
  command: "/codex plugins enable"
@@ -1935,13 +2077,195 @@ function buildPluginNamePickerReply(verb, current) {
1935
2077
  }
1936
2078
  function buildPluginsHelp() {
1937
2079
  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"
2080
+ "Codex plugin discovery and owner-approved installation:",
2081
+ "- /codex plugins (alias for list)",
2082
+ "- /codex plugins list show explicitly configured plugins",
2083
+ "- /codex plugins available list discoverable Codex marketplaces",
2084
+ "- /codex plugins install <name>@<marketplace> install and authorize one plugin",
2085
+ "- /codex plugins enable <name> enable a configured plugin",
2086
+ "- /codex plugins disable <name> disable a configured plugin",
2087
+ "Only an owner or operator.admin can discover, install, enable, or disable plugins."
2088
+ ].join("\n");
2089
+ }
2090
+ async function installCodexPlugin(requestedId, io, runtime) {
2091
+ const requested = parseCodexPluginMarketplaceId(requestedId);
2092
+ if (!requested) return { text: "Invalid plugin identifier. Use /codex plugins install <plugin>@<marketplace> with ASCII letters, digits, underscores, or hyphens." };
2093
+ let plugin;
2094
+ let workspaceDir;
2095
+ try {
2096
+ workspaceDir = await runtime.workspaceDir();
2097
+ const matching = (await discoverCodexMarketplacePlugins({
2098
+ request: runtime.list,
2099
+ workspaceDir
2100
+ })).plugins.filter((candidate) => candidate.pluginName === requested.pluginName && marketplaceNamesRepresentSameCatalog(candidate.marketplaceName, requested.marketplaceName));
2101
+ if (matching.length > 1) {
2102
+ plugin = resolveCuratedMarketplaceAliases(matching, requested.marketplaceName);
2103
+ if (!plugin) return { text: `Multiple available Codex plugins match '${formatCodexDisplayText(requestedId)}'; the marketplace identity must be unique.` };
2104
+ } else plugin = matching[0];
2105
+ } catch (error) {
2106
+ return { text: `Could not verify the requested Codex plugin: ${formatCodexDisplayText(errorMessage(error))}` };
2107
+ }
2108
+ if (!plugin) return { text: `${formatCodexDisplayText(requestedId)} was not found. Run /codex plugins available to inspect the current marketplaces.` };
2109
+ if (!plugin.available) return { text: `${formatCodexDisplayText(requestedId)} is unavailable or disabled by its marketplace administrator.` };
2110
+ const alreadyInstalled = plugin.installed && plugin.enabled;
2111
+ if (!alreadyInstalled && !plugin.marketplacePath && plugin.remotePluginId) {
2112
+ 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.` };
2113
+ 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.` };
2114
+ }
2115
+ try {
2116
+ const configured = resolveInstalledPluginKey((await io.readConfig()).plugins ?? {}, plugin);
2117
+ if (configured.status === "ambiguous" || configured.status === "mismatched") return { text: describeConfiguredPluginIdentityConflict(requestedId, configured.status) };
2118
+ } catch (error) {
2119
+ return { text: `Could not verify existing Codex plugin authorization: ${formatCodexDisplayText(errorMessage(error))}` };
2120
+ }
2121
+ let result;
2122
+ if (!alreadyInstalled) {
2123
+ const requestParams = plugin.marketplacePath ? {
2124
+ marketplacePath: plugin.marketplacePath,
2125
+ pluginName: plugin.pluginName
2126
+ } : plugin.remotePluginId ? {
2127
+ remoteMarketplaceName: plugin.marketplaceName,
2128
+ pluginName: plugin.remotePluginId
2129
+ } : void 0;
2130
+ if (!requestParams) return { text: `${formatCodexDisplayText(requestedId)} cannot be installed because its marketplace did not provide a trusted local path or remote plugin identifier.` };
2131
+ try {
2132
+ result = await runtime.install(requestParams);
2133
+ } catch (error) {
2134
+ return { text: `Could not install ${formatCodexDisplayText(requestedId)}: ${formatCodexDisplayText(errorMessage(error))}` };
2135
+ }
2136
+ }
2137
+ const selectedPlugin = plugin;
2138
+ try {
2139
+ await io.mutate((block) => {
2140
+ block.plugins ??= {};
2141
+ const configured = resolveInstalledPluginKey(block.plugins, selectedPlugin);
2142
+ if (configured.status === "ambiguous" || configured.status === "mismatched") throw new Error(describeConfiguredPluginIdentityConflict(selectedPlugin.id, configured.status));
2143
+ const curated = isOpenAiCuratedMarketplaceName(selectedPlugin.marketplaceName);
2144
+ const canonicalId = curated ? `${selectedPlugin.pluginName}@${CODEX_PLUGINS_MARKETPLACE_NAME}` : selectedPlugin.id;
2145
+ const configKey = configured.status === "matched" ? configured.configKey : canonicalId;
2146
+ const existing = block.plugins[configKey];
2147
+ block.enabled = true;
2148
+ const updated = {
2149
+ ...existing,
2150
+ enabled: true,
2151
+ marketplaceName: existing?.marketplaceName ?? (curated ? "openai-curated" : selectedPlugin.marketplaceName),
2152
+ pluginName: existing?.pluginName ?? (curated ? selectedPlugin.pluginName : persistedPluginName(selectedPlugin))
2153
+ };
2154
+ block.plugins[configKey] = updated;
2155
+ });
2156
+ } catch (error) {
2157
+ return { text: `${formatCodexDisplayText(requestedId)} was installed in Codex but could not be authorized in OpenClaw and will not be exposed: ${formatCodexDisplayText(errorMessage(error))}` };
2158
+ }
2159
+ let refreshWarning = "";
2160
+ if (runtime.refresh) try {
2161
+ refreshWarning = (await runtime.refresh(workspaceDir)).diagnostics.map((diagnostic) => ` ${formatCodexDisplayText(diagnostic.message)}`).join("");
2162
+ } catch (error) {
2163
+ refreshWarning = ` Runtime refresh requires a new conversation: ${formatCodexDisplayText(errorMessage(error))}`;
2164
+ }
2165
+ const appsNeedingAuth = result?.appsNeedingAuth ?? [];
2166
+ if (appsNeedingAuth.length > 0) {
2167
+ const apps = appsNeedingAuth.map((app) => formatCodexDisplayText(app.name)).slice(0, 5).join(", ");
2168
+ 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}` };
2169
+ }
2170
+ const status = alreadyInstalled ? "was already installed in Codex and is now authorized" : "was installed and authorized";
2171
+ return { text: `${formatCodexDisplayText(requestedId)} ${status}.${refreshWarning} ${POLICY_REFRESH_HINT}` };
2172
+ }
2173
+ /** Merge historical curated wire aliases only when they identify the same install source. */
2174
+ function resolveCuratedMarketplaceAliases(plugins, requestedMarketplaceName) {
2175
+ if (!isOpenAiCuratedMarketplaceName(requestedMarketplaceName)) return;
2176
+ const sourceIdentities = new Set(plugins.map((plugin) => plugin.marketplacePath ? `local:${plugin.marketplacePath}` : plugin.remotePluginId ? `remote:${plugin.remotePluginId}` : void 0));
2177
+ if (sourceIdentities.size !== 1 || sourceIdentities.has(void 0)) return;
2178
+ const selected = plugins.find((plugin) => plugin.marketplaceName === requestedMarketplaceName) ?? plugins[0];
2179
+ if (!selected) return;
2180
+ return {
2181
+ ...selected,
2182
+ installed: plugins.some((plugin) => plugin.installed),
2183
+ enabled: plugins.some((plugin) => plugin.installed && plugin.enabled),
2184
+ available: plugins.every((plugin) => plugin.available),
2185
+ ...selected.remotePluginId ? { mustShowInstallationInterstitial: plugins.some((plugin) => plugin.mustShowInstallationInterstitial === true) ? true : plugins.every((plugin) => plugin.mustShowInstallationInterstitial === false) ? false : null } : {},
2186
+ ...plugins.some((plugin) => plugin.installPolicy === "NOT_AVAILABLE") ? { installPolicy: "NOT_AVAILABLE" } : {}
2187
+ };
2188
+ }
2189
+ function persistedPluginName(plugin) {
2190
+ return !plugin.marketplacePath && plugin.summaryId.endsWith(`@${plugin.marketplaceName}`) ? plugin.summaryId : plugin.pluginName;
2191
+ }
2192
+ function resolveConfiguredPluginKey(plugins, target) {
2193
+ const requested = parseCodexPluginMarketplaceId(target);
2194
+ const direct = plugins[target];
2195
+ if (!requested) {
2196
+ if (!direct) return { status: "missing" };
2197
+ const qualifiedName = direct.pluginName ? parseCodexPluginMarketplaceId(direct.pluginName) : void 0;
2198
+ if (qualifiedName && direct.marketplaceName && !marketplaceNamesRepresentSameCatalog(qualifiedName.marketplaceName, direct.marketplaceName)) return { status: "mismatched" };
2199
+ const identity = resolveConfiguredPluginIdentity(direct);
2200
+ if (!identity) return {
2201
+ status: "matched",
2202
+ configKey: target
2203
+ };
2204
+ const marketplaceName = isOpenAiCuratedMarketplaceName(identity.marketplaceName) ? CODEX_PLUGINS_MARKETPLACE_NAME : identity.marketplaceName;
2205
+ const canonicalId = `${identity.pluginName}@${marketplaceName}`;
2206
+ const canonical = plugins[canonicalId];
2207
+ if (canonical && !matchesConfiguredPluginIdentity(canonical, identity, canonicalId)) return { status: "mismatched" };
2208
+ return Object.values(plugins).filter((entry) => matchesConfiguredPluginIdentity(entry, identity, canonicalId)).length > 1 ? { status: "ambiguous" } : {
2209
+ status: "matched",
2210
+ configKey: target
2211
+ };
2212
+ }
2213
+ if (direct && !matchesConfiguredPluginIdentity(direct, requested, target)) return { status: "mismatched" };
2214
+ const matching = Object.entries(plugins).filter(([, entry]) => matchesConfiguredPluginIdentity(entry, requested, target));
2215
+ if (matching.length > 1) return { status: "ambiguous" };
2216
+ const configKey = matching[0]?.[0];
2217
+ return configKey ? {
2218
+ status: "matched",
2219
+ configKey
2220
+ } : { status: "missing" };
2221
+ }
2222
+ function resolveInstalledPluginKey(plugins, plugin) {
2223
+ const discovered = resolveConfiguredPluginKey(plugins, plugin.id);
2224
+ if (discovered.status === "ambiguous" || discovered.status === "mismatched") return discovered;
2225
+ if (!isOpenAiCuratedMarketplaceName(plugin.marketplaceName)) return discovered;
2226
+ const canonical = resolveConfiguredPluginKey(plugins, `${plugin.pluginName}@${CODEX_PLUGINS_MARKETPLACE_NAME}`);
2227
+ if (canonical.status === "ambiguous" || canonical.status === "mismatched") return canonical;
2228
+ if (discovered.status === "matched" && canonical.status === "matched" && discovered.configKey !== canonical.configKey) return { status: "ambiguous" };
2229
+ return canonical.status === "matched" ? canonical : discovered;
2230
+ }
2231
+ function resolveConfiguredPluginIdentity(entry) {
2232
+ if (!entry.pluginName || !entry.marketplaceName) return;
2233
+ const qualified = parseCodexPluginMarketplaceId(entry.pluginName);
2234
+ if (qualified) return marketplaceNamesRepresentSameCatalog(qualified.marketplaceName, entry.marketplaceName) ? {
2235
+ pluginName: qualified.pluginName,
2236
+ marketplaceName: entry.marketplaceName
2237
+ } : void 0;
2238
+ return parseCodexPluginMarketplaceId(`${entry.pluginName}@${entry.marketplaceName}`);
2239
+ }
2240
+ function matchesConfiguredPluginIdentity(entry, requested, target) {
2241
+ const configuredName = entry.pluginName ? parseCodexPluginMarketplaceId(entry.pluginName) : void 0;
2242
+ 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));
2243
+ }
2244
+ function marketplaceNamesRepresentSameCatalog(left, right) {
2245
+ return left === right || isOpenAiCuratedMarketplaceName(left) && isOpenAiCuratedMarketplaceName(right);
2246
+ }
2247
+ function describeConfiguredPluginIdentityConflict(target, status) {
2248
+ const identity = formatCodexDisplayText(target);
2249
+ 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.`;
2250
+ }
2251
+ function formatAvailablePlugins(plugins, warnings) {
2252
+ if (plugins.length === 0) return ["No Codex plugins were discovered for the current workspace.", ...warnings.map((warning) => `Warning: ${formatCodexDisplayText(warning)}`)].join("\n");
2253
+ return [
2254
+ "Discoverable Codex plugins:",
2255
+ ...plugins.slice(0, 30).map((plugin) => {
2256
+ const state = plugin.installed ? plugin.enabled ? "installed" : "installed, disabled" : plugin.available ? "available" : "unavailable";
2257
+ const description = plugin.description ? ` - ${formatCodexDisplayText(plugin.description)}` : "";
2258
+ return `- ${plugin.id} (${state})${description}`;
2259
+ }),
2260
+ ...plugins.length > 30 ? ["- Additional plugins omitted."] : [],
2261
+ ...warnings.map((warning) => `Warning: ${formatCodexDisplayText(warning)}`),
2262
+ "To authorize one plugin, an owner or operator.admin must send:",
2263
+ "/codex plugins install <plugin>@<marketplace>"
1943
2264
  ].join("\n");
1944
2265
  }
2266
+ function errorMessage(error) {
2267
+ return error instanceof Error ? error.message : String(error);
2268
+ }
1945
2269
  function formatPluginList(plugins, options = {}) {
1946
2270
  const globalEnabled = options.globalEnabled === true;
1947
2271
  const keys = Object.keys(plugins).toSorted();
@@ -1981,7 +2305,76 @@ async function handleCodexSubcommand(ctx, options) {
1981
2305
  if (sandboxBlock) return { text: sandboxBlock };
1982
2306
  if (normalized === "plugins") {
1983
2307
  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);
2308
+ let appServerScope;
2309
+ const getAppServerScope = () => appServerScope ??= resolveCommandAppServerScope(deps, ctx, options.pluginConfig);
2310
+ return await handleCodexPluginsSubcommand(ctx, rest, deps.codexPluginsManagementIo, {
2311
+ workspaceDir: async () => {
2312
+ const data = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
2313
+ return (data?.kind === "codex-app-server-session" ? data.workspaceDir : void 0)?.trim() || deps.resolveCodexDefaultWorkspaceDir(options.pluginConfig);
2314
+ },
2315
+ list: async (requestParams) => {
2316
+ const scope = await getAppServerScope();
2317
+ return await deps.codexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.listPlugins, requestParams, {
2318
+ ...scope,
2319
+ config: ctx.config
2320
+ });
2321
+ },
2322
+ install: async (requestParams) => {
2323
+ const scope = await getAppServerScope();
2324
+ return await deps.codexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.installPlugin, requestParams, {
2325
+ ...scope,
2326
+ config: ctx.config
2327
+ });
2328
+ },
2329
+ refresh: async (workspaceDir) => {
2330
+ const scope = await getAppServerScope();
2331
+ const configuredAppServer = resolveCodexAppServerRuntimeOptions({ pluginConfig: options.pluginConfig });
2332
+ const appServer = scope.startOptions ? {
2333
+ ...configuredAppServer,
2334
+ start: scope.startOptions
2335
+ } : configuredAppServer;
2336
+ const authProfileId = scope.authProfileId ?? void 0;
2337
+ const accountId = await resolveCodexAppServerAuthAccountCacheKey({
2338
+ authProfileId,
2339
+ agentDir: scope.agentDir,
2340
+ config: ctx.config
2341
+ });
2342
+ const client = await getLeasedSharedCodexAppServerClient({
2343
+ startOptions: appServer.start,
2344
+ pluginConfig: options.pluginConfig,
2345
+ authProfileId: scope.authProfileId,
2346
+ agentDir: scope.agentDir,
2347
+ config: ctx.config
2348
+ });
2349
+ try {
2350
+ const appCacheKey = buildCodexPluginAppCacheKey({
2351
+ appServer,
2352
+ agentDir: scope.agentDir,
2353
+ authProfileId,
2354
+ accountId,
2355
+ envApiKeyFingerprint: authProfileId ? void 0 : resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions: appServer.start }),
2356
+ appServerVersion: client.getServerVersion(),
2357
+ runtimeIdentity: client.getRuntimeIdentity()
2358
+ });
2359
+ defaultCodexPluginMetadataCache.invalidate(appCacheKey);
2360
+ return await refreshCodexPluginRuntimeState({
2361
+ configCwd: workspaceDir,
2362
+ appCache: defaultCodexAppInventoryCache,
2363
+ appCacheKey,
2364
+ metadataCache: defaultCodexPluginMetadataCache,
2365
+ request: async (method, requestParams) => {
2366
+ const requestMethod = resolvePluginRuntimeRefreshMethod(method);
2367
+ return await deps.codexControlRequest(options.pluginConfig, requestMethod, requestParams, {
2368
+ ...scope,
2369
+ config: ctx.config
2370
+ });
2371
+ }
2372
+ });
2373
+ } finally {
2374
+ releaseLeasedSharedCodexAppServerClient(client);
2375
+ }
2376
+ }
2377
+ });
1985
2378
  }
1986
2379
  if (normalized === "status") {
1987
2380
  if (rest.length > 0) return { text: "Usage: /codex status" };
@@ -2062,5 +2455,18 @@ async function handleCodexSubcommand(ctx, options) {
2062
2455
  }
2063
2456
  return { text: `Unknown Codex command: ${formatCodexDisplayText(subcommand)}\n\n${buildHelp()}` };
2064
2457
  }
2458
+ function resolvePluginRuntimeRefreshMethod(method) {
2459
+ const recognized = [
2460
+ CODEX_CONTROL_METHODS.listPlugins,
2461
+ CODEX_CONTROL_METHODS.listSkills,
2462
+ CODEX_CONTROL_METHODS.listHooks,
2463
+ CODEX_CONTROL_METHODS.reloadMcpServers,
2464
+ CODEX_CONTROL_METHODS.installedApps,
2465
+ CODEX_CONTROL_METHODS.listApps,
2466
+ CODEX_CONTROL_METHODS.readApps
2467
+ ].find((candidate) => candidate === method);
2468
+ if (!recognized) throw new Error(`Unexpected Codex plugin refresh method: ${method}`);
2469
+ return recognized;
2470
+ }
2065
2471
  //#endregion
2066
2472
  export { handleCodexSubcommand };