@openclaw/codex 2026.9.2 → 2026.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +2 -0
  2. package/dist/api.js +29 -26
  3. package/dist/apply-CvT8tW5i.js +356 -0
  4. package/dist/{attempt-client-cleanup-Bp1ZOnk2.js → attempt-client-cleanup-DKHzbwt5.js} +38 -214
  5. package/dist/{attempt-context-CSazkWns.js → attempt-context-D44yeDII.js} +9 -7
  6. package/dist/{event-projector-usage-ZqEVbAEY.js → attempt-notifications-C9XufG6l.js} +3 -106
  7. package/dist/{auth-binding-Ds_QVm-X.js → auth-binding-CWlKVccJ.js} +2 -2
  8. package/dist/auth-cache-key-7orQae_j.js +498 -0
  9. package/dist/auth-profile-CwzO4_RG.js +43 -0
  10. package/dist/auth-profile-selection-DFu9e167.js +45 -0
  11. package/dist/binding-connection-D3udKbIE.js +60 -0
  12. package/dist/{bounded-turn-DxGJRYbY.js → bounded-turn-C_Hneydj.js} +28 -125
  13. package/dist/build-state-BCmg9-5p.js +19 -0
  14. package/dist/{capabilities-B1mgvpP8.js → capabilities-8vx68WLH.js} +1 -1
  15. package/dist/cli-metadata.js +1 -1
  16. package/dist/client-B57KWPQx.js +4005 -0
  17. package/dist/command-dispatch-BpduNGVf.js +21 -0
  18. package/dist/{command-formatters-DSbGMXA0.js → command-formatters-lxlkNZgU.js} +4 -3
  19. package/dist/{command-handlers-DOJvGv6-.js → command-handlers-tiL9iWj7.js} +788 -135
  20. package/dist/command-rpc-CR5EOEZX.js +233 -0
  21. package/dist/{compact-Y_xBqrHN.js → compact-B_H3AOd7.js} +19 -14
  22. package/dist/config-oIORaQ5T.js +106 -0
  23. package/dist/{config-CyapYSji.js → config-options-CXMq1T-C.js} +205 -593
  24. package/dist/config-runtime-D0zGNIHu.js +11 -0
  25. package/dist/{config-utils-C57JiJrU.js → config-utils-DujwEnhg.js} +2 -4
  26. package/dist/conversation-binding-DsB4-O7U.js +900 -0
  27. package/dist/{plugin-marketplace-discovery-B7gHXJ3d.js → conversation-control-CtUu1b3m.js} +11 -143
  28. package/dist/doctor-contract-api.js +5 -5
  29. package/dist/{dynamic-tools-BwpvTxaX.js → dynamic-tools-CmN5RAip.js} +133 -76
  30. package/dist/{effective-mcp-catalog-CY9Jn4bA.js → effective-mcp-catalog-Bd73bqA8.js} +3 -3
  31. package/dist/{elicitation-input-DMkeki8d.js → elicitation-input-DPoK8F7w.js} +8 -22
  32. package/dist/ephemeral-turn-D78_VmzO.js +101 -0
  33. package/dist/{event-projector-assistant-message-B9kp6iGC.js → event-projector-assistant-message-BmBM4qBV.js} +12 -25
  34. package/dist/event-projector-items-5VhsECCO.js +200 -0
  35. package/dist/event-projector-usage-BfXmpOec.js +102 -0
  36. package/dist/{harness-Czh2Sdoc.js → harness-C7rRa7bm.js} +48 -24
  37. package/dist/harness.js +1 -1
  38. package/dist/history-rejection-B4nOXNXF.js +13 -0
  39. package/dist/incognito-session-uhrBF6wJ.js +15 -0
  40. package/dist/index.js +406 -2915
  41. package/dist/inference-context-Dr_mzvk4.js +94 -0
  42. package/dist/inference-proxy-HGXKUIre.js +311 -0
  43. package/dist/{isolated-completion-CRh-51AK.js → isolated-completion-DWVb5U2J.js} +7 -4
  44. package/dist/launch-args-RXQwn8zV.js +90 -0
  45. package/dist/{managed-binary-DcpnJF9x.js → managed-binary-D22rNEcR.js} +13 -103
  46. package/dist/managed-thread-store-D1t5jh3G.js +66 -0
  47. package/dist/media-understanding-provider.js +4 -1
  48. package/dist/migration-provider-api.js +2 -0
  49. package/dist/{model-catalog-DT5vo6qo.js → model-catalog-Dq2n8f_p.js} +6 -5
  50. package/dist/{models-CcrgSMQR.js → models-PCKOim-h.js} +3 -3
  51. package/dist/native-execution-policy-umd0tgh4.js +128 -0
  52. package/dist/native-subagent-history-DjtPloaU.js +140 -0
  53. package/dist/native-subagent-history-owner-Pcp7JuVF.js +34 -0
  54. package/dist/{native-subagent-monitor-B3bvQGcg.js → native-subagent-monitor-fQ7eIXEf.js} +115 -460
  55. package/dist/native-subagent-task-ids-BzTdT0Uw.js +387 -0
  56. package/dist/node-cli-sessions-DKaOmVUh.js +527 -0
  57. package/dist/{node-exec-server.runtime-nfbUOtUB.js → node-exec-server.runtime-Cl2vapEi.js} +55 -24
  58. package/dist/plan-7uG-Fp36.js +947 -0
  59. package/dist/plugin-activation-BYQculil.js +138 -0
  60. package/dist/{thread-resume-BLgqkUc1.js → plugin-app-cache-key-d0eKxy7b.js} +4 -52
  61. package/dist/{plugin-approval-roundtrip-aQVdyL5g.js → plugin-approval-roundtrip-Bz3FT8W5.js} +1 -1
  62. package/dist/plugin-inventory-oL4Na1ZG.js +328 -0
  63. package/dist/plugin-marketplace-discovery-P-Vndfqq.js +137 -0
  64. package/dist/{plugin-metadata-cache-BwCLTY_U.js → plugin-metadata-cache-ClPJpRio.js} +17 -9
  65. package/dist/protocol-5bh1G-H7.js +275 -0
  66. package/dist/provider-MQ8PVkuE.js +50 -0
  67. package/dist/{provider-capabilities-BpG9Ltc5.js → provider-capabilities-BvCUL69A.js} +2 -2
  68. package/dist/{rate-limits-Fx_l4eNa.js → rate-limits-CBMmWBr9.js} +1 -1
  69. package/dist/request-B7hEkBGq.js +212 -0
  70. package/dist/rpc-error-ttN_QjEm.js +30 -0
  71. package/dist/{run-attempt-BzthFQSM.js → run-attempt-CvsAAGa0.js} +664 -377
  72. package/dist/{runtime-artifact-Bh_gf6-t.js → runtime-artifact-BJ0rXBZ_.js} +27 -27
  73. package/dist/sandbox-guard-BxgyoM8M.js +166 -0
  74. package/dist/{session-binding-4Ki04sW-.js → session-binding-CI8UuilT.js} +30 -48
  75. package/dist/{session-binding-record-CyaXZvTe.js → session-binding-record-Bcvslnhf.js} +12 -26
  76. package/dist/session-catalog-adoption-tcZi1IfX.js +251 -0
  77. package/dist/session-catalog-archive-CwKg8Xz2.js +56 -0
  78. package/dist/session-catalog-ct-AH01I.js +1477 -0
  79. package/dist/session-catalog-home-id-CwhLdC03.js +18 -0
  80. package/dist/session-catalog-node-adoption-B1tgDW-m.js +182 -0
  81. package/dist/session-catalog-node-continue-Ci-DXyJP.js +238 -0
  82. package/dist/session-catalog-provenance-D-dIfRAH.js +96 -0
  83. package/dist/{session-cli-BjLhpmS1.js → session-cli-DNN_jhGc.js} +3 -2
  84. package/dist/{session-history-DWmMqiqW.js → session-history-Cz-tOOYj.js} +6 -3
  85. package/dist/{session-upstream-marker-DReTpGz6.js → session-history-import-DwyijFI6.js} +5 -50
  86. package/dist/{session-history-read-LHldwvyh.js → session-history-read-cxPUxyVV.js} +21 -7
  87. package/dist/session-history-worker-runtime.js +18 -6
  88. package/dist/session-history.worker-BeK1YOyT.js +378 -0
  89. package/dist/session-history.worker.js +1 -1
  90. package/dist/{session-initialization-BCFVnLL4.js → session-initialization-Ct0_Hzmg.js} +2 -2
  91. package/dist/{session-permission-policy-BaueHmSj.js → session-permission-policy-BBmXA8Eg.js} +4 -7
  92. package/dist/{session-retirement-DNdmYdFc.js → session-retirement-B2YfbAAT.js} +6 -5
  93. package/dist/session-upstream-marker-D15C9NHp.js +46 -0
  94. package/dist/{settled-turn-context-BOM3Y1pl.js → settled-turn-context-Du6gdUDd.js} +9 -9
  95. package/dist/{settled-turn-finalizer-BI-b1sry.js → settled-turn-finalizer-Dn91stmc.js} +8 -7
  96. package/dist/shared-client-CscigXXL.js +3109 -0
  97. package/dist/{side-question-B6DWAw1A.js → side-question-BGMJQf4U.js} +65 -170
  98. package/dist/source-CElXU0Oy.js +518 -0
  99. package/dist/thread-archive-guard-DfpBr-Sz.js +53 -0
  100. package/dist/thread-history-page-D0JIp48V.js +361 -0
  101. package/dist/{thread-lifecycle-DPK30jad.js → thread-lifecycle-DXl1-I6b.js} +187 -639
  102. package/dist/{thread-ownership-CbucAmmp.js → thread-ownership-BsDRTsyp.js} +13 -15
  103. package/dist/thread-policy-BuEQfE0g.js +54 -0
  104. package/dist/thread-resume-DsDOZpFg.js +52 -0
  105. package/dist/timeout-BVw8aaM1.js +33 -0
  106. package/dist/transcript-history-projection-v17wXgdK.js +238 -0
  107. package/dist/{transcript-mirror-_njEB84z.js → transcript-mirror-C5CKgNy6.js} +50 -260
  108. package/dist/{transcript-mirror-attestation-Byl34PC-.js → transcript-mirror-attestation-DibE0krH.js} +17 -4
  109. package/dist/{transport-stdio-CzTngV0j.js → transport-stdio-BGjHoYLr.js} +73 -43
  110. package/dist/{upstream-session-fork-1fwbNKzQ.js → upstream-session-fork-BZerMte_.js} +27 -19
  111. package/dist/{usage-i9fv5-4m.js → usage-tCk-mg_x.js} +4 -3
  112. package/dist/version-omm6IPI3.js +12 -0
  113. package/dist/{web-search-provider.runtime-DKQn67A7.js → web-search-provider.runtime-B7bGbGWq.js} +3 -3
  114. package/openclaw.plugin.json +23 -1
  115. package/package.json +4 -4
  116. package/dist/request-C8H22rPi.js +0 -475
  117. package/dist/session-catalog-B47uwekb.js +0 -3272
  118. package/dist/session-history.worker-zZVcx0ME.js +0 -267
  119. package/dist/shared-client-CKhX2kFt.js +0 -7505
  120. package/dist/thread-policy-CTalhWFy.js +0 -105
@@ -1,33 +1,47 @@
1
- import { m as CODEX_PLUGINS_MARKETPLACE_NAME, n as isCodexFastServiceTier } from "./config-utils-C57JiJrU.js";
2
- import { l as sessionBindingIdentity, t as bindingStoreKey } from "./session-binding-record-CyaXZvTe.js";
3
- import { B as isJsonObject, i as resolveCodexAppServerRuntimeOptions } from "./config-CyapYSji.js";
4
- import { Bt as resolveCodexAppServerAuthAccountCacheKey, Et as hasCodexAppServerLiveThread, Ht as resolveCodexAppServerAuthProfileIdForAgent, Q as assertCodexThreadAcceptsDirectInput, Wt as resolveCodexAppServerFallbackApiKeyCacheKey, c as getLeasedSharedCodexAppServerClient, h as releaseLeasedSharedCodexAppServerClient, tt as assertCodexThreadResumeResponse, wt as consumeCodexAppServerLiveThread } from "./shared-client-CKhX2kFt.js";
5
- import { l as resolveCodexSessionBinding, n as assertCodexBindingMayBeReplaced, o as normalizeCodexAppServerBindingModelProvider, r as createCodexSessionGenerationSupersededError } from "./session-binding-4Ki04sW-.js";
6
- import { a as closeCodexStartupClientBestEffort } from "./attempt-client-cleanup-Bp1ZOnk2.js";
7
- import { t as listAllCodexAppServerModels } from "./models-CcrgSMQR.js";
8
- import { c as defaultCodexAppInventoryCache, i as buildCodexPluginAppCacheKey } from "./thread-resume-BLgqkUc1.js";
9
- import { At as refreshCodexPluginRuntimeState, Mt as isOpenAiCuratedMarketplaceName } from "./thread-lifecycle-DPK30jad.js";
10
- import { a as rollbackCodexAppServerBindingSubscription, c as withExclusiveCodexAppServerThread, i as retireCodexConversationThreadBinding, l as isIncognitoSessionKey, n as releaseCodexAppServerBindingSubscription, r as retainCodexAppServerBindingSubscription, s as withCodexConversationThreadActivity, t as isSameCodexAppServerThreadOwner } from "./thread-ownership-CbucAmmp.js";
11
- import { r as assertCodexSupervisionThreadLineage, s as resolveCodexBindingAppServerConnection } from "./thread-policy-CTalhWFy.js";
12
- import { c as resolveCodexNativeSandboxBlock, s as resolveCodexNativeExecutionBlock } from "./request-C8H22rPi.js";
13
- import { t as CODEX_CONTROL_METHODS } from "./capabilities-B1mgvpP8.js";
14
- import { s as summarizeCodexAccountUsage } from "./rate-limits-Fx_l4eNa.js";
15
- import { a as formatCodexDisplayText, c as formatComputerUseStatus, d as formatSkills, f as formatThreads, i as formatAccount, l as formatList, n as buildHelp, o as formatCodexStatus, r as escapeCodexChatText, s as formatCodexTextForDisplay, t as CODEX_RESUME_SAFE_THREAD_ID_PATTERN, u as formatModels } from "./command-formatters-DSbGMXA0.js";
16
- import { _ as hasCodexAdminScope, a as parseCodexPermissionsModeArg, c as setCodexConversationModel, d as stopCodexConversationTurn, g as canMutateCodexHost, h as CODEX_NATIVE_EXECUTION_AUTH_ERROR, i as parseCodexFastModeArg, l as setCodexConversationPermissions, m as CODEX_HOST_INSPECTION_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-B7gHXJ3d.js";
1
+ import { n as isCodexFastServiceTier } from "./config-utils-DujwEnhg.js";
2
+ import { c as CODEX_PLUGINS_MARKETPLACE_NAME, i as pluginReadParams, r as isOpenAiCuratedMarketplaceName, t as findCodexMarketplacePluginSummary } from "./plugin-inventory-oL4Na1ZG.js";
3
+ import { l as sessionBindingIdentity, n as assertCodexBindingMayBeReplaced, r as bindingStoreKey } from "./session-binding-record-Bcvslnhf.js";
4
+ import { o as isJsonObject } from "./protocol-5bh1G-H7.js";
5
+ import { _ as assertCodexThreadAcceptsDirectInput, s as isCodexAppServerIndeterminateRequestCancellationError, u as isCodexAppServerPrewriteRequestCancellationError, y as assertCodexThreadResumeResponse } from "./client-B57KWPQx.js";
6
+ import { c as resolveCodexAppServerPreparedApiKeyCacheKey, s as resolveCodexAppServerFallbackApiKeyCacheKey } from "./auth-cache-key-7orQae_j.js";
7
+ import { r as withCodexConversationThreadActivity, t as isIncognitoSessionKey } from "./incognito-session-uhrBF6wJ.js";
8
+ import { a as CODEX_HOST_INSPECTION_AUTH_ERROR, c as hasCodexAdminScope, i as CODEX_FULL_PERMISSIONS_AUTH_ERROR, n as filterCodexMarketplacePlugins, o as CODEX_NATIVE_EXECUTION_AUTH_ERROR, r as parseCodexPluginMarketplaceId, s as canMutateCodexHost, t as discoverCodexMarketplacePlugins } from "./plugin-marketplace-discovery-P-Vndfqq.js";
9
+ import { s as summarizeCodexAccountUsage } from "./rate-limits-CBMmWBr9.js";
10
+ import { a as formatCodexAccountLine, c as formatCodexTextForDisplay, d as formatModels, f as formatSkills, i as formatAccount, l as formatComputerUseStatus, n as buildHelp, o as formatCodexDisplayText, p as formatThreads, r as escapeCodexChatText, s as formatCodexStatus, t as CODEX_RESUME_SAFE_THREAD_ID_PATTERN, u as formatList } from "./command-formatters-lxlkNZgU.js";
17
11
  import { i as readCodexConversationBindingData, n as createCodexCliNodeConversationBindingData, o as resolveCodexDefaultWorkspaceDir, r as createCodexConversationBindingData } from "./conversation-binding-data-BiodpIzq.js";
18
- import { c as readCodexStatusProbes, l as requestOptions, p as formatCodexCliSessions, s as codexControlRequest, u as safeCodexControlRequest } from "./session-catalog-B47uwekb.js";
19
- import { i as readCodexComputerUseStatus, r as installCodexComputerUse, t as defaultCodexPluginMetadataCache } from "./plugin-metadata-cache-BwCLTY_U.js";
12
+ import { t as CodexAppServerRpcError } from "./rpc-error-ttN_QjEm.js";
13
+ import { t as CODEX_CONTROL_METHODS } from "./capabilities-8vx68WLH.js";
14
+ import { i as formatCodexCliSessions } from "./node-cli-sessions-DKaOmVUh.js";
15
+ import { r as buildCodexPluginAppCacheKey, s as defaultCodexAppInventoryCache } from "./plugin-app-cache-key-d0eKxy7b.js";
16
+ import { o as withCodexAppServerJsonClient } from "./request-B7hEkBGq.js";
17
+ import "./config-oIORaQ5T.js";
18
+ import { n as resolveCodexAppServerRuntimeOptions } from "./config-runtime-D0zGNIHu.js";
19
+ import { n as fingerprintCodexAppServerAuthBinding } from "./auth-binding-CWlKVccJ.js";
20
+ import { i as resolveCodexBindingAppServerConnection } from "./binding-connection-D3udKbIE.js";
21
+ import { a as resolveCodexAppServerAuthProfileStore, i as resolveCodexAppServerAuthProfileIdForAgent, n as normalizeCodexAppServerBindingModelProvider } from "./auth-profile-CwzO4_RG.js";
22
+ import { R as consumeCodexAppServerLiveThread, V as hasCodexAppServerLiveThread, c as getLeasedSharedCodexAppServerClient, et as resolveCodexAppServerAuthAccountCacheKey, h as releaseLeasedSharedCodexAppServerClient } from "./shared-client-CscigXXL.js";
23
+ import { a as closeCodexStartupClientBestEffort } from "./attempt-client-cleanup-DKHzbwt5.js";
24
+ import { t as listAllCodexAppServerModels } from "./models-PCKOim-h.js";
25
+ import { n as refreshCodexAppRuntimeState, r as refreshCodexPluginRuntimeState } from "./plugin-activation-BYQculil.js";
26
+ import { n as createCodexSessionGenerationSupersededError, o as resolveCodexSessionBinding } from "./session-binding-CI8UuilT.js";
27
+ import { r as assertCodexSupervisionThreadLineage } from "./thread-policy-BuEQfE0g.js";
28
+ import { a as rollbackCodexAppServerBindingSubscription, i as retireCodexConversationThreadBinding, n as releaseCodexAppServerBindingSubscription, r as retainCodexAppServerBindingSubscription, s as withExclusiveCodexAppServerThread, t as isSameCodexAppServerThreadOwner } from "./thread-ownership-BsDRTsyp.js";
29
+ import { n as resolveCodexNativeSandboxBlock, t as resolveCodexNativeExecutionBlock } from "./sandbox-guard-BxgyoM8M.js";
30
+ import { i as readCodexComputerUseStatus, r as installCodexComputerUse, t as defaultCodexPluginMetadataCache } from "./plugin-metadata-cache-ClPJpRio.js";
31
+ import { a as requestOptions, i as readCodexStatusProbes, o as safeCodexControlRequest, r as prepareCodexControlSessionAuth, t as codexControlRequest } from "./command-rpc-CR5EOEZX.js";
32
+ import { a as setCodexConversationFastMode, c as steerCodexConversationTurn, i as readCodexConversationActiveTurn, l as stopCodexConversationTurn, n as parseCodexFastModeArg, o as setCodexConversationModel, r as parseCodexPermissionsModeArg, s as setCodexConversationPermissions, t as formatPermissionsMode } from "./conversation-control-CtUu1b3m.js";
20
33
  import { normalizeOptionalString, normalizeUniqueStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
21
34
  import crypto from "node:crypto";
22
35
  import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
23
- import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
24
36
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
25
- import { isDeepStrictEqual } from "node:util";
26
- import { ensureAuthProfileStore, resolveAgentDir as resolveAgentDir$1, resolveAuthProfileEligibility, resolveProfileUnusableUntilForDisplay } from "openclaw/plugin-sdk/agent-runtime";
27
- import { findNormalizedProviderValue, resolveAuthProfileOrder } from "openclaw/plugin-sdk/provider-auth";
28
- import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
29
37
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
38
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
39
+ import { ensureAuthProfileStore, resolveAgentDir as resolveAgentDir$1, resolveAgentWorkspaceDir, resolveAuthProfileEligibility, resolveProfileUnusableUntilForDisplay } from "openclaw/plugin-sdk/agent-runtime";
40
+ import { isDeepStrictEqual } from "node:util";
30
41
  import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
42
+ import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
43
+ import { findNormalizedProviderValue, resolveAuthProfileOrder } from "openclaw/plugin-sdk/provider-auth";
44
+ import { renderMessagePresentationFallbackText } from "openclaw/plugin-sdk/interactive-runtime";
31
45
  import { MODEL_SELECTION_LOCKED_MESSAGE, isModelSelectionLocked, resolvePersistedSessionRuntimeId } from "openclaw/plugin-sdk/model-session-runtime";
32
46
  //#region extensions/codex/src/command-account.ts
33
47
  const OPENAI_PROVIDER_ID = "openai";
@@ -326,6 +340,453 @@ function formatRelativeReset(untilMs, nowMs) {
326
340
  const days = Math.ceil(durationMs / dayMs);
327
341
  return `in ${days} ${days === 1 ? "day" : "days"}`;
328
342
  }
343
+ function buildCodexPluginAppLinks(apps, options = {}) {
344
+ const blocks = apps.slice(0, 5).map((app) => {
345
+ const name = formatCodexDisplayText((app.name.trim() || app.id).slice(0, 80));
346
+ const url = safeCodexAppLink(app.installUrl);
347
+ return url ? {
348
+ type: "buttons",
349
+ buttons: [{
350
+ label: `Open ${name} in ChatGPT`,
351
+ action: {
352
+ type: "url",
353
+ url
354
+ }
355
+ }]
356
+ } : {
357
+ type: "text",
358
+ text: `${name}: ChatGPT setup/manage link unavailable. In Codex CLI, run /apps and select this app to continue.`
359
+ };
360
+ });
361
+ const remaining = apps.length - 5;
362
+ if (remaining > 0) blocks.push({
363
+ type: "text",
364
+ text: `${remaining} more apps are not shown. ${options.continuationCommand ? "Choose More apps to continue." : "In Codex CLI, run /apps to review the remaining apps."}`
365
+ });
366
+ if (options.continuationCommand) blocks.push({
367
+ type: "buttons",
368
+ buttons: [{
369
+ label: "More apps",
370
+ action: {
371
+ type: "command",
372
+ command: options.continuationCommand
373
+ }
374
+ }]
375
+ });
376
+ if (apps.length > 0) blocks.push({
377
+ type: "context",
378
+ text: "Use the same ChatGPT account and workspace as Codex in your browser. Opening an app page does not confirm that it is connected or callable in this conversation. If the app is not shown, in Codex CLI run /apps and select it."
379
+ });
380
+ return blocks;
381
+ }
382
+ function safeCodexAppLink(value) {
383
+ if (!value || value.length > 2048 || /[\s\p{Cc}\p{Cf}<>]/u.test(value)) return;
384
+ try {
385
+ const url = new URL(value);
386
+ const host = url.hostname;
387
+ const hosted = host === "chatgpt.com" || host === "chatgpt-staging.com" || host.endsWith(".chatgpt.com") || host.endsWith(".chatgpt-staging.com");
388
+ return url.protocol === "https:" && !url.username && !url.password && hosted ? value : void 0;
389
+ } catch {
390
+ return;
391
+ }
392
+ }
393
+ //#endregion
394
+ //#region extensions/codex/src/command-plugin-config.ts
395
+ /** Merge historical curated wire aliases only when they identify the same install source. */
396
+ function resolveCuratedMarketplaceAliases(plugins, requestedMarketplaceName) {
397
+ if (!isOpenAiCuratedMarketplaceName(requestedMarketplaceName)) return;
398
+ const sourceIdentities = new Set(plugins.map((plugin) => plugin.marketplacePath ? `local:${plugin.marketplacePath}` : plugin.remotePluginId ? `remote:${plugin.remotePluginId}` : void 0));
399
+ if (sourceIdentities.size !== 1 || sourceIdentities.has(void 0)) return;
400
+ const selected = plugins.find((plugin) => plugin.marketplaceName === requestedMarketplaceName) ?? plugins[0];
401
+ if (!selected) return;
402
+ return {
403
+ ...selected,
404
+ installed: plugins.some((plugin) => plugin.installed),
405
+ enabled: plugins.some((plugin) => plugin.installed && plugin.enabled),
406
+ available: plugins.every((plugin) => plugin.available),
407
+ ...selected.remotePluginId ? { mustShowInstallationInterstitial: plugins.some((plugin) => plugin.mustShowInstallationInterstitial === true) ? true : plugins.every((plugin) => plugin.mustShowInstallationInterstitial === false) ? false : null } : {},
408
+ ...plugins.some((plugin) => plugin.installPolicy === "NOT_AVAILABLE") ? { installPolicy: "NOT_AVAILABLE" } : {}
409
+ };
410
+ }
411
+ function persistedPluginName(plugin) {
412
+ return !plugin.marketplacePath && plugin.summaryId.endsWith(`@${plugin.marketplaceName}`) ? plugin.summaryId : plugin.pluginName;
413
+ }
414
+ function resolveConfiguredPluginKey(plugins, target) {
415
+ const requested = parseCodexPluginMarketplaceId(target);
416
+ const direct = plugins[target];
417
+ if (!requested) {
418
+ if (!direct) return { status: "missing" };
419
+ const qualifiedName = direct.pluginName ? parseCodexPluginMarketplaceId(direct.pluginName) : void 0;
420
+ if (qualifiedName && direct.marketplaceName && !marketplaceNamesRepresentSameCatalog(qualifiedName.marketplaceName, direct.marketplaceName)) return { status: "mismatched" };
421
+ const identity = resolveConfiguredPluginIdentity(direct);
422
+ if (!identity) return {
423
+ status: "matched",
424
+ configKey: target
425
+ };
426
+ const marketplaceName = isOpenAiCuratedMarketplaceName(identity.marketplaceName) ? CODEX_PLUGINS_MARKETPLACE_NAME : identity.marketplaceName;
427
+ const canonicalId = `${identity.pluginName}@${marketplaceName}`;
428
+ const canonical = plugins[canonicalId];
429
+ if (canonical && !matchesConfiguredPluginIdentity(canonical, identity, canonicalId)) return { status: "mismatched" };
430
+ return Object.values(plugins).filter((entry) => matchesConfiguredPluginIdentity(entry, identity, canonicalId)).length > 1 ? { status: "ambiguous" } : {
431
+ status: "matched",
432
+ configKey: target
433
+ };
434
+ }
435
+ if (direct && !matchesConfiguredPluginIdentity(direct, requested, target)) return { status: "mismatched" };
436
+ const matching = Object.entries(plugins).filter(([, entry]) => matchesConfiguredPluginIdentity(entry, requested, target));
437
+ if (matching.length > 1) return { status: "ambiguous" };
438
+ const configKey = matching[0]?.[0];
439
+ return configKey ? {
440
+ status: "matched",
441
+ configKey
442
+ } : { status: "missing" };
443
+ }
444
+ function resolveInstalledPluginKey(plugins, plugin) {
445
+ const discovered = resolveConfiguredPluginKey(plugins, plugin.id);
446
+ if (discovered.status === "ambiguous" || discovered.status === "mismatched") return discovered;
447
+ if (!isOpenAiCuratedMarketplaceName(plugin.marketplaceName)) return discovered;
448
+ const canonical = resolveConfiguredPluginKey(plugins, `${plugin.pluginName}@${CODEX_PLUGINS_MARKETPLACE_NAME}`);
449
+ if (canonical.status === "ambiguous" || canonical.status === "mismatched") return canonical;
450
+ if (discovered.status === "matched" && canonical.status === "matched" && discovered.configKey !== canonical.configKey) return { status: "ambiguous" };
451
+ return canonical.status === "matched" ? canonical : discovered;
452
+ }
453
+ function resolveConfiguredPluginIdentity(entry) {
454
+ if (!entry.pluginName || !entry.marketplaceName) return;
455
+ const qualified = parseCodexPluginMarketplaceId(entry.pluginName);
456
+ if (qualified) return marketplaceNamesRepresentSameCatalog(qualified.marketplaceName, entry.marketplaceName) ? {
457
+ pluginName: qualified.pluginName,
458
+ marketplaceName: entry.marketplaceName
459
+ } : void 0;
460
+ return parseCodexPluginMarketplaceId(`${entry.pluginName}@${entry.marketplaceName}`);
461
+ }
462
+ function matchesConfiguredPluginIdentity(entry, requested, target) {
463
+ const configuredName = entry.pluginName ? parseCodexPluginMarketplaceId(entry.pluginName) : void 0;
464
+ 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));
465
+ }
466
+ function marketplaceNamesRepresentSameCatalog(left, right) {
467
+ return left === right || isOpenAiCuratedMarketplaceName(left) && isOpenAiCuratedMarketplaceName(right);
468
+ }
469
+ function describeConfiguredPluginIdentityConflict(target, status) {
470
+ const identity = formatCodexDisplayText(target);
471
+ 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.`;
472
+ }
473
+ //#endregion
474
+ //#region extensions/codex/src/command-plugins-readiness.ts
475
+ /** Runtime support is not account-wide permission to browse, connect or invoke apps. */
476
+ async function readCodexHostedAppsSupport(context, account) {
477
+ if (account.status !== "known") return "unknown";
478
+ if (!isJsonObject(account.value.account) || account.value.account.type !== "chatgpt") return "sign_in_required";
479
+ const features = await readEvidence(async () => {
480
+ let cursor;
481
+ const visited = /* @__PURE__ */ new Set();
482
+ do {
483
+ const response = await context.request("experimentalFeature/list", {
484
+ ...context.threadId ? { threadId: context.threadId } : {},
485
+ ...cursor ? { cursor } : {},
486
+ limit: 100
487
+ });
488
+ const apps = response.data.find((feature) => feature.name === "apps");
489
+ if (apps) return apps.enabled;
490
+ cursor = response.nextCursor ?? void 0;
491
+ if (cursor && visited.has(cursor)) return;
492
+ if (cursor) visited.add(cursor);
493
+ } while (cursor);
494
+ });
495
+ if (features.status !== "known") return features.reason === "unsupported" ? "unsupported" : "unknown";
496
+ return features.value === void 0 ? "unknown" : features.value ? "supported" : "disabled";
497
+ }
498
+ function describeCodexHostedAppsSupport(support) {
499
+ switch (support) {
500
+ case "supported": return "Hosted apps: supported by this runtime; account connections and action permissions are checked separately.";
501
+ case "sign_in_required": return "Hosted apps require ChatGPT sign-in. Check /codex account; local Codex plugins remain available.";
502
+ case "disabled": return "Hosted apps are disabled in this Codex runtime. Check its effective apps feature configuration.";
503
+ case "unsupported": return "Hosted app support is unknown: this Codex version cannot report the required feature state. Update to OpenClaw's supported Codex version.";
504
+ default: return "Hosted app support is unknown. Check /codex account and retry this command.";
505
+ }
506
+ }
507
+ function pluginCatalogState(summary) {
508
+ if (!summary) return "unknown";
509
+ if (summary.availability === "DISABLED_BY_ADMIN" || summary.installPolicy === "NOT_AVAILABLE") return "blocked";
510
+ return summary.availability === "AVAILABLE" && (summary.installPolicy === "AVAILABLE" || summary.installPolicy === "INSTALLED_BY_DEFAULT") ? "available" : "unknown";
511
+ }
512
+ function codexPluginAppPageLinks(readiness) {
513
+ if (readiness.hostedSupport !== "supported" || pluginCatalogState(readiness.summary) !== "available" || readiness.metadata.status !== "known") return [];
514
+ const metadata = new Map(readiness.metadata.value.apps.map((app) => [app.id, app]));
515
+ return (readiness.detail?.apps ?? []).flatMap((app) => {
516
+ const authorized = metadata.get(app.id);
517
+ return authorized ? [{
518
+ ...app,
519
+ name: authorized.name,
520
+ installUrl: authorized.installUrl
521
+ }] : [];
522
+ });
523
+ }
524
+ /** Reads existing snapshots only. Neither metadata nor installation proves a live connection. */
525
+ async function readCodexPluginReadiness(params) {
526
+ const { context, current, configKey } = params;
527
+ const entry = current.plugins?.[configKey];
528
+ const identity = entry ? resolveConfiguredPluginIdentity(entry) : void 0;
529
+ if (!entry?.marketplaceName || !entry.pluginName || !identity) throw new Error("This configured plugin has no marketplace identity. Check /codex plugins list.");
530
+ const result = {
531
+ configKey,
532
+ commandId: `${identity.pluginName}@${identity.marketplaceName}`,
533
+ openClawEnabled: current.enabled === true && entry.enabled !== false,
534
+ agentId: context.agentId,
535
+ profileId: context.profileId,
536
+ workspaceDir: context.workspaceDir,
537
+ threadId: context.threadId,
538
+ runtime: {
539
+ status: "unavailable",
540
+ reason: "request_failed"
541
+ },
542
+ metadata: {
543
+ status: "unavailable",
544
+ reason: "request_failed"
545
+ },
546
+ account: await readEvidence(() => context.request("account/read", { refreshToken: false })),
547
+ hostedSupport: "unknown"
548
+ };
549
+ const installed = await readEvidence(() => context.request("plugin/installed", { cwds: [context.workspaceDir] }));
550
+ let selected = installed.status === "known" ? findCodexMarketplacePluginSummary(installed.value, entry.marketplaceName, entry.pluginName) : void 0;
551
+ if (!selected) {
552
+ const responses = [];
553
+ const catalog = await readEvidence(() => discoverCodexMarketplacePlugins({
554
+ workspaceDir: context.workspaceDir,
555
+ request: async (requestParams) => {
556
+ const response = await context.request("plugin/list", requestParams);
557
+ responses.push(response);
558
+ return response;
559
+ }
560
+ }));
561
+ if (catalog.status === "known") {
562
+ const matches = catalog.value.plugins.filter((plugin) => {
563
+ const configured = resolveInstalledPluginKey(current.plugins ?? {}, plugin);
564
+ return configured.status === "matched" && configured.configKey === configKey;
565
+ });
566
+ const candidate = matches.length === 1 ? matches[0] : resolveCuratedMarketplaceAliases(matches, entry.marketplaceName);
567
+ if (candidate) for (const response of responses) {
568
+ selected = findCodexMarketplacePluginSummary(response, candidate.marketplaceName, candidate.summaryId);
569
+ if (selected) {
570
+ selected.summary = {
571
+ ...selected.summary,
572
+ installed: candidate.installed,
573
+ enabled: candidate.enabled,
574
+ ...candidate.available ? {} : { availability: "DISABLED_BY_ADMIN" }
575
+ };
576
+ break;
577
+ }
578
+ }
579
+ } else result.diagnostic = describeUnavailableEvidence(catalog.reason);
580
+ }
581
+ if (!selected) {
582
+ await context.validateCurrent();
583
+ return result;
584
+ }
585
+ result.summary = selected.summary;
586
+ const selectedPlugin = selected;
587
+ const pluginName = selected.marketplace.remoteMarketplaceName ? selected.summary.remotePluginId : entry.pluginName;
588
+ if (!pluginName) return result;
589
+ const detail = await readEvidence(() => context.request("plugin/read", pluginReadParams(selectedPlugin.marketplace, pluginName)));
590
+ if (detail.status !== "known" || detail.value.plugin.summary.id !== selected.summary.id) {
591
+ if (detail.status === "unavailable") result.diagnostic = describeUnavailableEvidence(detail.reason);
592
+ await context.validateCurrent();
593
+ return result;
594
+ }
595
+ result.detail = detail.value.plugin;
596
+ result.summary = detail.value.plugin.summary;
597
+ const appIds = Array.from(new Set(result.detail.apps.map((app) => app.id))).toSorted();
598
+ if (appIds.length > 0) {
599
+ result.hostedSupport = await readCodexHostedAppsSupport(context, result.account);
600
+ const [runtime, metadata] = await Promise.all([readEvidence(() => context.request("app/installed", {
601
+ ...context.threadId ? { threadId: context.threadId } : {},
602
+ forceRefresh: false
603
+ })), readEvidence(async () => {
604
+ const responses = [];
605
+ for (let offset = 0; offset < appIds.length; offset += 100) responses.push(await context.request("app/read", {
606
+ appIds: appIds.slice(offset, offset + 100),
607
+ ...context.threadId ? { threadId: context.threadId } : {}
608
+ }));
609
+ return {
610
+ apps: responses.flatMap((response) => response.apps),
611
+ missingAppIds: responses.flatMap((response) => response.missingAppIds)
612
+ };
613
+ })]);
614
+ result.runtime = runtime;
615
+ result.metadata = metadata;
616
+ }
617
+ await context.validateCurrent();
618
+ return result;
619
+ }
620
+ function formatCodexPluginReadiness(readiness, page = 1) {
621
+ const summary = readiness.summary;
622
+ const catalog = pluginCatalogState(summary);
623
+ const hasApps = Boolean(readiness.detail?.apps.length);
624
+ const canRefreshHostedApps = readiness.hostedSupport === "supported";
625
+ const lines = [
626
+ `Plugin: ${display(readiness.commandId)}`,
627
+ `Agent: ${display(readiness.agentId)} · Profile: ${display(readiness.profileId ?? "native Codex account (profile unknown)")}`,
628
+ `Conversation workspace: ${display(readiness.workspaceDir)}`,
629
+ formatBoundAccount(readiness.account),
630
+ `Catalog: ${catalog === "blocked" ? "blocked by marketplace policy" : catalog}`,
631
+ `Bundle: ${summary ? summary.installed ? "installed" : "not installed" : "unknown"}`,
632
+ `Codex plugin: ${summary ? summary.enabled ? "enabled" : "disabled" : "unknown"}`,
633
+ `OpenClaw app access: ${readiness.openClawEnabled ? "enabled" : "disabled"} (shared Codex plugin configuration; takes effect on your next message).`,
634
+ ...hasApps ? [describeCodexHostedAppsSupport(readiness.hostedSupport)] : []
635
+ ];
636
+ if (catalog === "blocked") lines.push(summary?.disabledReason === "plan_not_eligible" ? "Next: this ChatGPT plan is not eligible for the plugin. Check its plan requirements." : summary?.disabledReason === "required_app_unavailable" ? "Next: a required hosted app is unavailable. Check access with the app or workspace owner." : summary?.disabledReason === "disabled_by_admin" || summary?.availability === "DISABLED_BY_ADMIN" ? "Next: ask the marketplace administrator to restore access." : "Next: check the plugin's marketplace requirements; its policy does not permit installation.");
637
+ else if (!readiness.openClawEnabled) lines.push(`Next: /codex plugins enable ${readiness.commandId}. Takes effect on your next message.`);
638
+ else if (summary && (!summary.installed || !summary.enabled)) lines.push(`Next: /codex plugins install ${readiness.commandId}. Takes effect on your next message.`);
639
+ const blocks = [{
640
+ type: "text",
641
+ text: lines.join("\n")
642
+ }];
643
+ if (readiness.diagnostic) blocks.push({
644
+ type: "text",
645
+ text: readiness.diagnostic
646
+ });
647
+ if (!readiness.detail) blocks.push({
648
+ type: "text",
649
+ text: "App details unavailable. Check the plugin in Codex, then run this status command again."
650
+ });
651
+ else {
652
+ const apps = readiness.detail.apps.toSorted((left, right) => left.id.localeCompare(right.id));
653
+ const pageCount = Math.max(1, Math.ceil(apps.length / 5));
654
+ if (page > pageCount) return { text: `No app page ${page}. Use /codex plugins status ${readiness.commandId} ${pageCount}.` };
655
+ const start = (page - 1) * 5;
656
+ const visible = apps.slice(start, start + 5);
657
+ const runtimeById = new Map(readiness.runtime.status === "known" ? readiness.runtime.value.apps.map((app) => [app.id, app]) : []);
658
+ if (visible.length > 0 && readiness.runtime.status === "unavailable") blocks.push({
659
+ type: "text",
660
+ text: describeUnavailableEvidence(readiness.runtime.reason)
661
+ });
662
+ blocks.push({
663
+ type: "text",
664
+ text: visible.length === 0 ? "No hosted apps declared. Skills and other plugin capabilities are not assessed here." : [
665
+ `Apps (page ${page}/${pageCount}):`,
666
+ `Runtime scope: ${readiness.threadId ? "current Codex thread" : "account (no bound Codex thread)"}.`,
667
+ ...visible.map((app) => {
668
+ const runtime = runtimeById.get(app.id);
669
+ const state = runtime ? `enabled: ${runtime.enabled}; callable: ${runtime.callable}` : readiness.runtime.status === "known" ? "not reported by app/installed" : "runtime flags unavailable";
670
+ return `- ${display(app.name)}: ${state}.`;
671
+ })
672
+ ].join("\n")
673
+ });
674
+ if (visible.length > 0) {
675
+ const authorized = new Map(codexPluginAppPageLinks(readiness).map((app) => [app.id, app]));
676
+ const links = visible.flatMap((app) => {
677
+ const link = authorized.get(app.id);
678
+ return link ? [link] : [];
679
+ });
680
+ blocks.push(...buildCodexPluginAppLinks(links, page < pageCount ? { continuationCommand: `/codex plugins status ${readiness.commandId} ${page + 1}` } : {}));
681
+ if (links.length < visible.length) blocks.push({
682
+ type: "text",
683
+ text: "Some app-page permissions are unknown or unavailable. A declared app or setup URL does not establish access. Retry this status command after checking the reported restriction."
684
+ });
685
+ blocks.push({
686
+ type: "text",
687
+ text: "Flags reflect Codex's runtime snapshot; status does not refresh hosted tools. After connecting, /codex plugins refresh refreshes hosted inventory for the current Codex account/runtime, across all apps. Use Check status separately to inspect this plugin without refreshing. OpenClaw app-access changes take effect on your next message; use /new or /reset after connecting."
688
+ });
689
+ blocks.push({
690
+ type: "buttons",
691
+ buttons: [...canRefreshHostedApps ? [{
692
+ label: "Refresh hosted apps",
693
+ action: {
694
+ type: "command",
695
+ command: "/codex plugins refresh"
696
+ }
697
+ }] : [], {
698
+ label: "Check status",
699
+ action: {
700
+ type: "command",
701
+ command: `/codex plugins status ${readiness.commandId}`
702
+ }
703
+ }]
704
+ });
705
+ }
706
+ }
707
+ const presentation = {
708
+ title: "Codex plugin status",
709
+ blocks
710
+ };
711
+ return {
712
+ text: renderMessagePresentationFallbackText({ presentation }),
713
+ presentation,
714
+ presentationTextMode: "fallback"
715
+ };
716
+ }
717
+ async function readEvidence(read) {
718
+ try {
719
+ return {
720
+ status: "known",
721
+ value: await read()
722
+ };
723
+ } catch (error) {
724
+ return {
725
+ status: "unavailable",
726
+ reason: error instanceof CodexAppServerRpcError && error.code === -32601 ? "unsupported" : "request_failed"
727
+ };
728
+ }
729
+ }
730
+ function describeUnavailableEvidence(reason) {
731
+ return reason === "unsupported" ? "This Codex app-server does not support the required status method. Update to OpenClaw's supported Codex version." : "Codex status could not be read. Check Codex sign-in and connectivity, then run this command again.";
732
+ }
733
+ function display(value) {
734
+ return formatCodexDisplayText(value.slice(0, 120));
735
+ }
736
+ function formatBoundAccount(evidence) {
737
+ const account = evidence.status === "known" ? evidence.value.account : void 0;
738
+ if (isJsonObject(account) && account.type === "chatgpt") {
739
+ const email = typeof account.email === "string" ? account.email : "email unknown";
740
+ const plan = typeof account.planType === "string" ? account.planType : "plan unknown";
741
+ return `ChatGPT account: ${formatCodexAccountLine(email.slice(0, 120))} (${display(plan)}).`;
742
+ }
743
+ if (isJsonObject(account) && typeof account.type === "string") return `Account type: ${display(account.type)}; ChatGPT account identity is not available.`;
744
+ return "Codex account: unknown. Check /codex account.";
745
+ }
746
+ //#endregion
747
+ //#region extensions/codex/src/command-apps-refresh.ts
748
+ /** Refreshes hosted app inventory for the current account/runtime. */
749
+ async function refreshCodexHostedApps(context) {
750
+ let account;
751
+ try {
752
+ account = await context.request("account/read", { refreshToken: false });
753
+ const support = await readCodexHostedAppsSupport({ request: context.request }, {
754
+ status: "known",
755
+ value: account
756
+ });
757
+ if (support !== "supported") return { text: `Hosted app inventory was not refreshed. ${describeCodexHostedAppsSupport(support)}` };
758
+ await refreshCodexAppRuntimeState({
759
+ request: context.request,
760
+ appCache: defaultCodexAppInventoryCache,
761
+ appCacheKey: context.appCacheKey
762
+ });
763
+ } catch (error) {
764
+ await context.validateCurrent();
765
+ return { text: `${error instanceof CodexAppServerRpcError && error.code === -32601 ? "This Codex app-server does not support the required app inventory methods. Update the Codex plugin and retry." : (isCodexAppServerIndeterminateRequestCancellationError(error) || isCodexAppServerPrewriteRequestCancellationError(error)) && "reason" in error && error.reason === "aborted" ? "The hosted app refresh was cancelled." : "Hosted app tools could not be refreshed. Check the Codex connection and try again."} Run /codex plugins refresh to retry for the current Codex account/runtime. Previous inventory was not confirmed; no conversation policy was changed.` };
766
+ }
767
+ const presentation = {
768
+ title: "Hosted app refresh",
769
+ blocks: [{
770
+ type: "text",
771
+ text: [
772
+ `Agent: ${formatCodexDisplayText(context.agentId.slice(0, 120))} · Profile: ${formatCodexDisplayText((context.profileId ?? "native Codex account (profile unknown)").slice(0, 120))}`,
773
+ formatBoundAccount({
774
+ status: "known",
775
+ value: account
776
+ }),
777
+ "Hosted app refresh request completed for the current Codex account/runtime, across all hosted apps. Codex does not report whether it replaced its snapshot; this does not verify a live connection. No plugin enablement or conversation policy was changed. Use /new or /reset after connecting."
778
+ ].join("\n")
779
+ }, {
780
+ type: "text",
781
+ text: "Use /codex plugins list to find configured plugins, then /codex plugins status <name>@<marketplace> to inspect one."
782
+ }]
783
+ };
784
+ return {
785
+ text: renderMessagePresentationFallbackText({ presentation }),
786
+ presentation,
787
+ presentationTextMode: "fallback"
788
+ };
789
+ }
329
790
  //#endregion
330
791
  //#region extensions/codex/src/command-diagnostics-state.ts
331
792
  /** Runtime state for diagnostics upload throttling and confirmation handshakes. */
@@ -386,6 +847,10 @@ function buildCodexSubcommandPickerReply() {
386
847
  label: "account",
387
848
  command: "/codex account"
388
849
  },
850
+ {
851
+ label: "refresh hosted apps",
852
+ command: "/codex plugins refresh"
853
+ },
389
854
  {
390
855
  label: "help",
391
856
  command: "/codex help"
@@ -1111,7 +1576,7 @@ async function resolvePreparedCodexCommandAuthority(deps, ctx) {
1111
1576
  assertCurrent
1112
1577
  };
1113
1578
  }
1114
- async function resolveCommandAppServerScope(deps, ctx, pluginConfig) {
1579
+ async function resolveCommandAppServerContext(deps, ctx, pluginConfig) {
1115
1580
  const authority = await resolvePreparedCodexCommandAuthority(deps, ctx);
1116
1581
  const { target, binding } = authority;
1117
1582
  const fallback = resolveCodexConversationControlScope(ctx);
@@ -1127,16 +1592,23 @@ async function resolveCommandAppServerScope(deps, ctx, pluginConfig) {
1127
1592
  pluginConfig
1128
1593
  });
1129
1594
  return {
1130
- agentId: target?.agentId ?? fallback.agentId,
1131
- agentDir,
1132
- ...connection.clientAuthProfileId !== void 0 ? { authProfileId: connection.clientAuthProfileId } : {},
1133
- ...connection.usesSupervisionConnection ? { startOptions: connection.appServer.start } : {},
1134
- ...authority.sessionKey ? { sessionKey: authority.sessionKey } : {},
1135
- ...authority.sessionId ? { sessionId: authority.sessionId } : {},
1136
- ...authority.storePath ? { storePath: authority.storePath } : {},
1137
- assertCurrent: authority.assertCurrent
1595
+ scope: {
1596
+ agentId: target?.agentId ?? fallback.agentId,
1597
+ agentDir,
1598
+ ...connection.clientAuthProfileId !== void 0 ? { authProfileId: connection.clientAuthProfileId } : {},
1599
+ ...connection.usesSupervisionConnection ? { startOptions: connection.appServer.start } : {},
1600
+ ...authority.sessionKey ? { sessionKey: authority.sessionKey } : {},
1601
+ ...authority.sessionId ? { sessionId: authority.sessionId } : {},
1602
+ ...authority.storePath ? { storePath: authority.storePath } : {},
1603
+ assertCurrent: authority.assertCurrent
1604
+ },
1605
+ target,
1606
+ binding
1138
1607
  };
1139
1608
  }
1609
+ async function resolveCommandAppServerScope(deps, ctx, pluginConfig) {
1610
+ return (await resolveCommandAppServerContext(deps, ctx, pluginConfig)).scope;
1611
+ }
1140
1612
  function conversationBindingIdentity(bindingId) {
1141
1613
  return {
1142
1614
  kind: "conversation",
@@ -2025,8 +2497,61 @@ function supervisedCommandGuard(deps, identity, binding) {
2025
2497
  };
2026
2498
  }
2027
2499
  //#endregion
2500
+ //#region extensions/codex/src/command-plugins-available.ts
2501
+ const AVAILABLE_PAGE_SIZE = 10;
2502
+ function formatCodexAvailablePlugins(plugins, warnings, query, page) {
2503
+ const filtered = filterCodexMarketplacePlugins(plugins, query);
2504
+ const pageCount = Math.max(1, Math.ceil(filtered.length / AVAILABLE_PAGE_SIZE));
2505
+ const start = (page - 1) * AVAILABLE_PAGE_SIZE;
2506
+ const visible = filtered.slice(start, start + AVAILABLE_PAGE_SIZE);
2507
+ const pageCommand = (target) => `/codex plugins available --page ${target}${query ? ` -- '${query.replaceAll("'", "'\\''")}'` : ""}`;
2508
+ const buttons = [];
2509
+ const lines = [];
2510
+ if (filtered.length === 0) lines.push(query ? `No Codex plugins match "${formatCodexDisplayText(query)}" in the returned catalogs. Try a shorter search or browse all plugins.` : "No Codex plugins were discovered for the current workspace.");
2511
+ else if (page > pageCount) {
2512
+ lines.push(`No plugin page ${page}. There are ${filtered.length} results across ${pageCount} pages.`);
2513
+ buttons.push({
2514
+ label: "First page",
2515
+ command: pageCommand(1)
2516
+ });
2517
+ } else {
2518
+ lines.push(`Showing ${start + 1}–${start + visible.length} of ${filtered.length}${query ? ` matches for "${formatCodexDisplayText(query)}"` : " plugins"} (page ${page}/${pageCount}).`);
2519
+ if (page > 1) buttons.push({
2520
+ label: "Previous page",
2521
+ command: pageCommand(page - 1)
2522
+ });
2523
+ if (page < pageCount) buttons.push({
2524
+ label: "Next page",
2525
+ command: pageCommand(page + 1)
2526
+ });
2527
+ lines.push(...visible.map((plugin) => {
2528
+ const state = plugin.installed ? plugin.enabled ? "installed" : "installed, disabled" : plugin.available ? "available" : "unavailable";
2529
+ const title = plugin.displayName ? `${formatCodexDisplayText(plugin.displayName)} — ` : "";
2530
+ const publisher = plugin.developerName ? formatCodexDisplayText(plugin.developerName) : "Not provided";
2531
+ const description = plugin.description ? formatCodexDisplayText(plugin.description) : "No description provided.";
2532
+ return `- ${title}${plugin.id} (${state})\n Publisher: ${publisher}. ${description}`;
2533
+ }));
2534
+ }
2535
+ lines.push(...warnings.map((warning) => `Warning: ${formatCodexDisplayText(warning)}`), "Search names, titles, publishers, marketplaces, or descriptions: /codex plugins available <query>", "To authorize one plugin, an owner or operator.admin must send:", "/codex plugins install <plugin>@<marketplace>");
2536
+ if (query) buttons.push({
2537
+ label: "Browse all plugins",
2538
+ command: "/codex plugins available"
2539
+ });
2540
+ buttons.push({
2541
+ label: "Plugin controls",
2542
+ command: "/codex plugins menu"
2543
+ });
2544
+ const presentation = buildCodexCommandPickerPresentation("Discoverable Codex plugins", lines.join("\n"), buttons);
2545
+ return {
2546
+ text: renderMessagePresentationFallbackText({ presentation }),
2547
+ presentation,
2548
+ presentationTextMode: "fallback"
2549
+ };
2550
+ }
2551
+ //#endregion
2028
2552
  //#region extensions/codex/src/command-plugins-management.ts
2029
2553
  const POLICY_REFRESH_HINT = "Takes effect on your next message.";
2554
+ const AVAILABLE_USAGE = "Usage: /codex plugins available [query] [--page <positive integer>]. Search text must be at most 100 characters; use -- before literal query text that contains options.";
2030
2555
  async function handleCodexPluginsSubcommand(ctx, rest, io, runtime) {
2031
2556
  const [verb = "list", ...args] = rest;
2032
2557
  const normalized = verb.toLowerCase();
@@ -2044,19 +2569,51 @@ async function handleCodexPluginsSubcommand(ctx, rest, io, runtime) {
2044
2569
  return { text: formatPluginList(current.plugins ?? {}, { globalEnabled: current.enabled === true }) };
2045
2570
  }
2046
2571
  if (normalized === "available") {
2047
- if (args.length > 0) return { text: "Usage: /codex plugins available" };
2048
2572
  if (!canMutateCodexHost(ctx)) return { text: "Only an owner or operator.admin gateway client can list available Codex plugins." };
2573
+ let page = 1;
2574
+ const queryParts = [];
2575
+ for (let index = 0; index < args.length; index += 1) {
2576
+ const arg = expectDefined(args[index], "current Codex plugin search argument");
2577
+ if (arg === "--") {
2578
+ queryParts.push(...args.slice(index + 1));
2579
+ break;
2580
+ }
2581
+ if (arg === "--page") {
2582
+ const parsedPage = parseStrictPositiveInteger(args[++index]);
2583
+ if (parsedPage === void 0) return { text: AVAILABLE_USAGE };
2584
+ page = parsedPage;
2585
+ } else queryParts.push(arg);
2586
+ }
2587
+ const query = queryParts.join(" ").trim();
2588
+ if (query.length > 100) return { text: AVAILABLE_USAGE };
2049
2589
  if (!runtime) return { text: "Codex plugin discovery is unavailable for this command." };
2050
2590
  try {
2051
2591
  const discovered = await discoverCodexMarketplacePlugins({
2052
2592
  request: runtime.list,
2053
2593
  workspaceDir: await runtime.workspaceDir()
2054
2594
  });
2055
- return { text: formatAvailablePlugins(discovered.plugins, discovered.warnings) };
2595
+ return formatCodexAvailablePlugins(discovered.plugins, discovered.warnings, query, page);
2056
2596
  } catch (error) {
2057
2597
  return { text: `Could not list Codex plugins: ${formatCodexDisplayText(errorMessage(error))}` };
2058
2598
  }
2059
2599
  }
2600
+ if (normalized === "status") {
2601
+ const requestedPlugin = args[0];
2602
+ const page = args[1] === void 0 ? 1 : Number(args[1]);
2603
+ if (!requestedPlugin || !parseCodexPluginMarketplaceId(requestedPlugin) || args.length > 2 || !Number.isSafeInteger(page) || page < 1) return { text: "Usage: /codex plugins status <name>@<marketplace> [page]. Use /codex plugins list to find a configured plugin." };
2604
+ if (!canMutateCodexHost(ctx)) return { text: "Only an owner or operator.admin gateway client can run /codex plugins status." };
2605
+ if (!runtime?.withContext) return { text: "Codex plugin status is unavailable. Check the configured Codex app-server, then run this command again." };
2606
+ return await runtime.withContext(async (context) => {
2607
+ const configured = resolveConfiguredPluginKey(context.current.plugins ?? {}, requestedPlugin);
2608
+ if (configured.status === "ambiguous" || configured.status === "mismatched") return { text: describeConfiguredPluginIdentityConflict(requestedPlugin, configured.status) };
2609
+ if (configured.status === "missing") return { text: "This plugin is not explicitly configured. Use /codex plugins list, or /codex plugins available to find an install command." };
2610
+ return formatCodexPluginReadiness(await readCodexPluginReadiness({
2611
+ context,
2612
+ current: context.current,
2613
+ configKey: configured.configKey
2614
+ }), page);
2615
+ });
2616
+ }
2060
2617
  if (normalized === "install") {
2061
2618
  if (args.length !== 1 || !args[0]) return { text: "Usage: /codex plugins install <plugin>@<marketplace>" };
2062
2619
  if (!canMutateCodexHost(ctx)) return { text: "Only an owner or operator.admin gateway client can run /codex plugins install." };
@@ -2098,9 +2655,11 @@ function buildPluginsMenuReply() {
2098
2655
  "",
2099
2656
  " 1. /codex plugins list",
2100
2657
  " 2. /codex plugins available",
2101
- " 3. /codex plugins enable",
2102
- " 4. /codex plugins disable",
2103
- " 5. /codex plugins help",
2658
+ " 3. /codex plugins status <name>@<marketplace>",
2659
+ " 4. /codex plugins refresh — refresh hosted apps",
2660
+ " 5. /codex plugins enable",
2661
+ " 6. /codex plugins disable",
2662
+ " 7. /codex plugins help",
2104
2663
  "",
2105
2664
  "Type '/codex' to go back to the main menu."
2106
2665
  ].join("\n"),
@@ -2113,6 +2672,10 @@ function buildPluginsMenuReply() {
2113
2672
  label: "available",
2114
2673
  command: "/codex plugins available"
2115
2674
  },
2675
+ {
2676
+ label: "Refresh hosted apps",
2677
+ command: "/codex plugins refresh"
2678
+ },
2116
2679
  {
2117
2680
  label: "enable",
2118
2681
  command: "/codex plugins enable"
@@ -2177,16 +2740,18 @@ function buildPluginsHelp() {
2177
2740
  "Codex plugin discovery and owner-approved installation:",
2178
2741
  "- /codex plugins (alias for list)",
2179
2742
  "- /codex plugins list show explicitly configured plugins",
2180
- "- /codex plugins available list discoverable Codex marketplaces",
2743
+ "- /codex plugins available [query] [--page <n>] search or browse Codex plugins",
2744
+ "- /codex plugins status <name>@<marketplace> [page] inspect app readiness without refreshing",
2745
+ "- /codex plugins refresh refresh all hosted apps for the current Codex account/runtime",
2181
2746
  "- /codex plugins install <name>@<marketplace> install and authorize one plugin",
2182
2747
  "- /codex plugins enable <name> enable a configured plugin",
2183
2748
  "- /codex plugins disable <name> disable a configured plugin",
2184
- "Only an owner or operator.admin can discover, install, enable, or disable plugins."
2749
+ "Only an owner or operator.admin can discover, inspect, refresh hosted apps, install, enable, or disable plugins."
2185
2750
  ].join("\n");
2186
2751
  }
2187
2752
  async function installCodexPlugin(requestedId, io, runtime) {
2188
2753
  const requested = parseCodexPluginMarketplaceId(requestedId);
2189
- if (!requested) return { text: "Invalid plugin identifier. Use /codex plugins install <plugin>@<marketplace> with ASCII letters, digits, underscores, or hyphens." };
2754
+ if (!requested) return { text: "Invalid plugin identifier. Use /codex plugins install <plugin>@<marketplace>. Both names allow ASCII letters, digits, underscores, and hyphens. Plugin names may also contain dots between nonempty segments." };
2190
2755
  let plugin;
2191
2756
  let workspaceDir;
2192
2757
  try {
@@ -2261,104 +2826,64 @@ async function installCodexPlugin(requestedId, io, runtime) {
2261
2826
  }
2262
2827
  const appsNeedingAuth = result?.appsNeedingAuth ?? [];
2263
2828
  if (appsNeedingAuth.length > 0) {
2264
- const apps = appsNeedingAuth.map((app) => formatCodexDisplayText(app.name)).slice(0, 5).join(", ");
2265
- 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}` };
2266
- }
2267
- const status = alreadyInstalled ? "was already installed in Codex and is now authorized" : "was installed and authorized";
2268
- return { text: `${formatCodexDisplayText(requestedId)} ${status}.${refreshWarning} ${POLICY_REFRESH_HINT}` };
2269
- }
2270
- /** Merge historical curated wire aliases only when they identify the same install source. */
2271
- function resolveCuratedMarketplaceAliases(plugins, requestedMarketplaceName) {
2272
- if (!isOpenAiCuratedMarketplaceName(requestedMarketplaceName)) return;
2273
- const sourceIdentities = new Set(plugins.map((plugin) => plugin.marketplacePath ? `local:${plugin.marketplacePath}` : plugin.remotePluginId ? `remote:${plugin.remotePluginId}` : void 0));
2274
- if (sourceIdentities.size !== 1 || sourceIdentities.has(void 0)) return;
2275
- const selected = plugins.find((plugin) => plugin.marketplaceName === requestedMarketplaceName) ?? plugins[0];
2276
- if (!selected) return;
2277
- return {
2278
- ...selected,
2279
- installed: plugins.some((plugin) => plugin.installed),
2280
- enabled: plugins.some((plugin) => plugin.installed && plugin.enabled),
2281
- available: plugins.every((plugin) => plugin.available),
2282
- ...selected.remotePluginId ? { mustShowInstallationInterstitial: plugins.some((plugin) => plugin.mustShowInstallationInterstitial === true) ? true : plugins.every((plugin) => plugin.mustShowInstallationInterstitial === false) ? false : null } : {},
2283
- ...plugins.some((plugin) => plugin.installPolicy === "NOT_AVAILABLE") ? { installPolicy: "NOT_AVAILABLE" } : {}
2284
- };
2285
- }
2286
- function persistedPluginName(plugin) {
2287
- return !plugin.marketplacePath && plugin.summaryId.endsWith(`@${plugin.marketplaceName}`) ? plugin.summaryId : plugin.pluginName;
2288
- }
2289
- function resolveConfiguredPluginKey(plugins, target) {
2290
- const requested = parseCodexPluginMarketplaceId(target);
2291
- const direct = plugins[target];
2292
- if (!requested) {
2293
- if (!direct) return { status: "missing" };
2294
- const qualifiedName = direct.pluginName ? parseCodexPluginMarketplaceId(direct.pluginName) : void 0;
2295
- if (qualifiedName && direct.marketplaceName && !marketplaceNamesRepresentSameCatalog(qualifiedName.marketplaceName, direct.marketplaceName)) return { status: "mismatched" };
2296
- const identity = resolveConfiguredPluginIdentity(direct);
2297
- if (!identity) return {
2298
- status: "matched",
2299
- configKey: target
2829
+ let appLinks = [];
2830
+ if (runtime.withContext) try {
2831
+ appLinks = await runtime.withContext(async (context) => {
2832
+ const configured = resolveConfiguredPluginKey(context.current.plugins ?? {}, requestedId);
2833
+ if (configured.status !== "matched") return [];
2834
+ const readiness = await readCodexPluginReadiness({
2835
+ context,
2836
+ current: context.current,
2837
+ configKey: configured.configKey
2838
+ });
2839
+ const pendingIds = new Set(appsNeedingAuth.map((app) => app.id));
2840
+ return codexPluginAppPageLinks(readiness).filter((app) => pendingIds.has(app.id));
2841
+ });
2842
+ } catch {}
2843
+ const authRequirement = appsNeedingAuth.length === 1 ? "1 app still requires" : `${appsNeedingAuth.length} apps still require`;
2844
+ const presentation = {
2845
+ title: "Codex plugin app setup",
2846
+ tone: "warning",
2847
+ blocks: [
2848
+ {
2849
+ type: "text",
2850
+ text: `${formatCodexDisplayText(requestedId)} bundle was installed in Codex. OpenClaw app access is configured. ${authRequirement} connector authentication in ChatGPT. Installation does not confirm app connections or current-conversation readiness.`
2851
+ },
2852
+ ...buildCodexPluginAppLinks(appLinks),
2853
+ ...appLinks.length < appsNeedingAuth.length ? [{
2854
+ type: "text",
2855
+ text: `Some app setup permissions could not be confirmed. Run /codex plugins status ${requestedId} to check the current account and restrictions.`
2856
+ }] : [],
2857
+ {
2858
+ type: "buttons",
2859
+ buttons: [...appLinks.length > 0 ? [{
2860
+ label: "Refresh hosted apps",
2861
+ action: {
2862
+ type: "command",
2863
+ command: "/codex plugins refresh"
2864
+ }
2865
+ }] : [], {
2866
+ label: "Check status",
2867
+ action: {
2868
+ type: "command",
2869
+ command: `/codex plugins status ${requestedId}`
2870
+ }
2871
+ }]
2872
+ },
2873
+ {
2874
+ type: "context",
2875
+ text: `${refreshWarning.trim()} ${POLICY_REFRESH_HINT}`.trim()
2876
+ }
2877
+ ]
2300
2878
  };
2301
- const marketplaceName = isOpenAiCuratedMarketplaceName(identity.marketplaceName) ? CODEX_PLUGINS_MARKETPLACE_NAME : identity.marketplaceName;
2302
- const canonicalId = `${identity.pluginName}@${marketplaceName}`;
2303
- const canonical = plugins[canonicalId];
2304
- if (canonical && !matchesConfiguredPluginIdentity(canonical, identity, canonicalId)) return { status: "mismatched" };
2305
- return Object.values(plugins).filter((entry) => matchesConfiguredPluginIdentity(entry, identity, canonicalId)).length > 1 ? { status: "ambiguous" } : {
2306
- status: "matched",
2307
- configKey: target
2879
+ return {
2880
+ text: renderMessagePresentationFallbackText({ presentation }),
2881
+ presentation,
2882
+ presentationTextMode: "fallback"
2308
2883
  };
2309
2884
  }
2310
- if (direct && !matchesConfiguredPluginIdentity(direct, requested, target)) return { status: "mismatched" };
2311
- const matching = Object.entries(plugins).filter(([, entry]) => matchesConfiguredPluginIdentity(entry, requested, target));
2312
- if (matching.length > 1) return { status: "ambiguous" };
2313
- const configKey = matching[0]?.[0];
2314
- return configKey ? {
2315
- status: "matched",
2316
- configKey
2317
- } : { status: "missing" };
2318
- }
2319
- function resolveInstalledPluginKey(plugins, plugin) {
2320
- const discovered = resolveConfiguredPluginKey(plugins, plugin.id);
2321
- if (discovered.status === "ambiguous" || discovered.status === "mismatched") return discovered;
2322
- if (!isOpenAiCuratedMarketplaceName(plugin.marketplaceName)) return discovered;
2323
- const canonical = resolveConfiguredPluginKey(plugins, `${plugin.pluginName}@${CODEX_PLUGINS_MARKETPLACE_NAME}`);
2324
- if (canonical.status === "ambiguous" || canonical.status === "mismatched") return canonical;
2325
- if (discovered.status === "matched" && canonical.status === "matched" && discovered.configKey !== canonical.configKey) return { status: "ambiguous" };
2326
- return canonical.status === "matched" ? canonical : discovered;
2327
- }
2328
- function resolveConfiguredPluginIdentity(entry) {
2329
- if (!entry.pluginName || !entry.marketplaceName) return;
2330
- const qualified = parseCodexPluginMarketplaceId(entry.pluginName);
2331
- if (qualified) return marketplaceNamesRepresentSameCatalog(qualified.marketplaceName, entry.marketplaceName) ? {
2332
- pluginName: qualified.pluginName,
2333
- marketplaceName: entry.marketplaceName
2334
- } : void 0;
2335
- return parseCodexPluginMarketplaceId(`${entry.pluginName}@${entry.marketplaceName}`);
2336
- }
2337
- function matchesConfiguredPluginIdentity(entry, requested, target) {
2338
- const configuredName = entry.pluginName ? parseCodexPluginMarketplaceId(entry.pluginName) : void 0;
2339
- 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));
2340
- }
2341
- function marketplaceNamesRepresentSameCatalog(left, right) {
2342
- return left === right || isOpenAiCuratedMarketplaceName(left) && isOpenAiCuratedMarketplaceName(right);
2343
- }
2344
- function describeConfiguredPluginIdentityConflict(target, status) {
2345
- const identity = formatCodexDisplayText(target);
2346
- 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.`;
2347
- }
2348
- function formatAvailablePlugins(plugins, warnings) {
2349
- if (plugins.length === 0) return ["No Codex plugins were discovered for the current workspace.", ...warnings.map((warning) => `Warning: ${formatCodexDisplayText(warning)}`)].join("\n");
2350
- return [
2351
- "Discoverable Codex plugins:",
2352
- ...plugins.slice(0, 30).map((plugin) => {
2353
- const state = plugin.installed ? plugin.enabled ? "installed" : "installed, disabled" : plugin.available ? "available" : "unavailable";
2354
- const description = plugin.description ? ` - ${formatCodexDisplayText(plugin.description)}` : "";
2355
- return `- ${plugin.id} (${state})${description}`;
2356
- }),
2357
- ...plugins.length > 30 ? ["- Additional plugins omitted."] : [],
2358
- ...warnings.map((warning) => `Warning: ${formatCodexDisplayText(warning)}`),
2359
- "To authorize one plugin, an owner or operator.admin must send:",
2360
- "/codex plugins install <plugin>@<marketplace>"
2361
- ].join("\n");
2885
+ const status = alreadyInstalled ? "bundle was already installed in Codex" : "bundle was installed in Codex";
2886
+ return { text: `${formatCodexDisplayText(requestedId)} ${status}. OpenClaw app access is configured.${refreshWarning} ${POLICY_REFRESH_HINT}` };
2362
2887
  }
2363
2888
  function errorMessage(error) {
2364
2889
  return error instanceof Error ? error.message : String(error);
@@ -2385,10 +2910,124 @@ function formatPluginList(plugins, options = {}) {
2385
2910
  ...rows.map((r) => ` ${r.state} ${r.displayKey.padEnd(keyW)} ${r.pluginName.padEnd(pluginW)} [${r.marketplace}]`),
2386
2911
  "",
2387
2912
  ...globalEnabled ? [] : ["Global codexPlugins.enabled is off; configured sub-plugins are inactive.", ""],
2913
+ "Inspect one configured plugin: /codex plugins status <name>@<marketplace> [page].",
2388
2914
  POLICY_REFRESH_HINT
2389
2915
  ].join("\n");
2390
2916
  }
2391
2917
  //#endregion
2918
+ //#region extensions/codex/src/command-plugins-runtime.ts
2919
+ const SCOPE_CHANGED_MESSAGE = "Codex account, conversation, or plugin policy changed. Run the command again.";
2920
+ async function withCodexPluginCommandContext(params, run) {
2921
+ const { deps, ctx, pluginConfig } = params;
2922
+ const current = await deps.codexPluginsManagementIo?.readConfig() ?? {};
2923
+ const initialPolicy = JSON.stringify(current);
2924
+ const { scope, target, binding } = await resolveCommandAppServerContext(deps, ctx, pluginConfig);
2925
+ const conversation = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
2926
+ const workspaceDir = binding?.cwd || (conversation?.kind === "codex-app-server-session" ? conversation.workspaceDir : void 0) || resolveAgentWorkspaceDir(ctx.config, scope.agentId);
2927
+ const configuredRuntime = resolveCodexAppServerRuntimeOptions({ pluginConfig });
2928
+ const appServer = scope.startOptions ? {
2929
+ ...configuredRuntime,
2930
+ start: scope.startOptions
2931
+ } : configuredRuntime;
2932
+ const auth = await prepareCodexControlSessionAuth({
2933
+ ...scope,
2934
+ config: ctx.config
2935
+ }, appServer.start);
2936
+ const preparedAuth = "preparedAuth" in auth.clientOptions ? auth.clientOptions.preparedAuth : void 0;
2937
+ const usesNativeAuth = scope.authProfileId === null || appServer.start.homeScope === "user";
2938
+ const profileId = usesNativeAuth ? void 0 : auth.authProfileId;
2939
+ const readAuthBinding = () => profileId ? fingerprintCodexAppServerAuthBinding({
2940
+ authProfileId: profileId,
2941
+ authProfileStore: resolveCodexAppServerAuthProfileStore({
2942
+ authProfileId: profileId,
2943
+ agentDir: scope.agentDir,
2944
+ config: ctx.config
2945
+ }),
2946
+ agentDir: scope.agentDir,
2947
+ config: ctx.config
2948
+ }) : Promise.resolve(void 0);
2949
+ const authBinding = "authBindingFingerprint" in auth.clientOptions ? auth.clientOptions.authBindingFingerprint : await readAuthBinding();
2950
+ const accountId = usesNativeAuth ? void 0 : preparedAuth?.kind === "api-key" ? resolveCodexAppServerPreparedApiKeyCacheKey(preparedAuth.apiKey) : preparedAuth?.kind === "profile" ? preparedAuth.snapshot.secretFreeCacheKey : await resolveCodexAppServerAuthAccountCacheKey({
2951
+ authProfileId: profileId,
2952
+ agentDir: scope.agentDir,
2953
+ config: ctx.config
2954
+ });
2955
+ if (await readAuthBinding() !== authBinding) throw new Error(SCOPE_CHANGED_MESSAGE);
2956
+ return await withCodexAppServerJsonClient({
2957
+ startOptions: appServer.start,
2958
+ pluginConfig,
2959
+ agentDir: scope.agentDir,
2960
+ config: ctx.config,
2961
+ sessionId: ctx.sessionId,
2962
+ sessionKey: ctx.sessionKey,
2963
+ timeoutMs: appServer.requestTimeoutMs,
2964
+ timeoutMessage: "Codex plugin request timed out. Check the Codex connection and retry.",
2965
+ assertCurrent: scope.assertCurrent,
2966
+ ...auth.clientOptions
2967
+ }, async (request, client, requestScope) => {
2968
+ const assertCurrent = () => {
2969
+ requestScope.assertCurrent();
2970
+ if (client.getCloseError()) throw new Error(SCOPE_CHANGED_MESSAGE);
2971
+ };
2972
+ const validateCurrent = async () => {
2973
+ assertCurrent();
2974
+ const currentTarget = await resolveControlTarget(ctx);
2975
+ const currentConversation = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
2976
+ const currentPolicy = JSON.stringify(await deps.codexPluginsManagementIo?.readConfig() ?? {});
2977
+ const currentAuthBinding = await readAuthBinding();
2978
+ assertCurrent();
2979
+ if (!isDeepStrictEqual(currentTarget, target) || !isDeepStrictEqual(currentConversation, conversation) || currentPolicy !== initialPolicy || currentAuthBinding !== authBinding) throw new Error(SCOPE_CHANGED_MESSAGE);
2980
+ };
2981
+ try {
2982
+ await request({
2983
+ method: "account/read",
2984
+ requestParams: { refreshToken: false },
2985
+ assertCurrent
2986
+ });
2987
+ } catch {
2988
+ requestScope.assertCurrent();
2989
+ throw new Error("Codex account startup could not be confirmed. Check /codex account and retry.");
2990
+ }
2991
+ const unsubscribe = client.addNotificationHandler((notification) => {
2992
+ if (notification.method === "account/updated") requestScope.abort(/* @__PURE__ */ new Error(SCOPE_CHANGED_MESSAGE));
2993
+ });
2994
+ try {
2995
+ await validateCurrent();
2996
+ const result = await run({
2997
+ request: async (method, requestParams) => {
2998
+ await validateCurrent();
2999
+ const response = await request({
3000
+ method,
3001
+ requestParams,
3002
+ assertCurrent
3003
+ });
3004
+ await validateCurrent();
3005
+ return response;
3006
+ },
3007
+ workspaceDir,
3008
+ agentId: scope.agentId,
3009
+ current,
3010
+ ...profileId ? { profileId } : {},
3011
+ ...binding?.clientId === client.getInstanceId() ? { threadId: binding.threadId } : {},
3012
+ appCacheKey: buildCodexPluginAppCacheKey({
3013
+ appServer,
3014
+ agentDir: scope.agentDir,
3015
+ authProfileId: profileId,
3016
+ accountId,
3017
+ envApiKeyFingerprint: usesNativeAuth || preparedAuth || profileId ? void 0 : resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions: appServer.start }),
3018
+ appServerVersion: client.getServerVersion(),
3019
+ runtimeIdentity: client.getRuntimeIdentity()
3020
+ }),
3021
+ validateCurrent
3022
+ });
3023
+ await validateCurrent();
3024
+ return result;
3025
+ } finally {
3026
+ unsubscribe();
3027
+ }
3028
+ });
3029
+ }
3030
+ //#endregion
2392
3031
  //#region extensions/codex/src/command-handlers.ts
2393
3032
  const CODEX_HOST_INSPECTION_SUBCOMMANDS = /* @__PURE__ */ new Set([
2394
3033
  "account",
@@ -2410,10 +3049,24 @@ async function handleCodexSubcommand(ctx, options) {
2410
3049
  const sandboxBlock = resolveCodexNativeCommandSandboxBlock(ctx, normalized, rest);
2411
3050
  if (sandboxBlock) return { text: sandboxBlock };
2412
3051
  if (normalized === "plugins") {
3052
+ if (rest[0]?.toLowerCase() === "refresh") {
3053
+ if (rest.length !== 1) return { text: "Usage: /codex plugins refresh — refresh hosted app inventory for the current Codex account/runtime." };
3054
+ if (!canMutateCodexHost(ctx)) return { text: "Only an owner or operator.admin gateway client can refresh hosted app inventory." };
3055
+ return await withCodexPluginCommandContext({
3056
+ deps,
3057
+ ctx,
3058
+ pluginConfig: options.pluginConfig
3059
+ }, (context) => refreshCodexHostedApps(context));
3060
+ }
2413
3061
  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." };
2414
3062
  let appServerScope;
2415
3063
  const getAppServerScope = () => appServerScope ??= resolveCommandAppServerScope(deps, ctx, options.pluginConfig);
2416
3064
  return await handleCodexPluginsSubcommand(ctx, rest, deps.codexPluginsManagementIo, {
3065
+ withContext: (run) => withCodexPluginCommandContext({
3066
+ deps,
3067
+ ctx,
3068
+ pluginConfig: options.pluginConfig
3069
+ }, run),
2417
3070
  workspaceDir: async () => {
2418
3071
  const data = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
2419
3072
  return (data?.kind === "codex-app-server-session" ? data.workspaceDir : void 0)?.trim() || deps.resolveCodexDefaultWorkspaceDir(options.pluginConfig);