@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.
- package/README.md +2 -0
- package/dist/api.js +147 -0
- package/dist/app-server-policy-bg5QPSVP.js +25 -0
- package/dist/attempt-client-cleanup-URjUlCHg.js +735 -0
- package/dist/binding-connection-DAvZwp96.js +45 -0
- package/dist/{bounded-turn-D2ZAgZVz.js → bounded-turn-4BjrZEwW.js} +63 -56
- package/dist/{capabilities-BFDTVYr4.js → capabilities-CH3K57v4.js} +8 -2
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-C336eRVS.js → command-formatters-D0nZ3qtd.js} +18 -18
- package/dist/{command-handlers-DAHUSry9.js → command-handlers-DC86lgT4.js} +507 -101
- package/dist/command-rpc-xUIIonsO.js +90 -0
- package/dist/{compact-D_uvHurS.js → compact-COqgBRny.js} +234 -214
- package/dist/{session-binding-BXpo9ymA.js → config-CpNeo5AE.js} +284 -1163
- package/dist/context-compaction-activity-BOeJf7yo.js +58 -0
- package/dist/{conversation-binding-data-BHRpYbX3.js → conversation-binding-data-CW2GuupV.js} +6 -10
- package/dist/doctor-contract-api.js +48 -50
- package/dist/{dynamic-tools-D2gYkSBO.js → dynamic-tools-fJZhl4CE.js} +3442 -1433
- package/dist/{effective-mcp-catalog-Bbd98Inw.js → effective-mcp-catalog-4YKn51aG.js} +20 -15
- package/dist/elicitation-input-DMkeki8d.js +71 -0
- package/dist/{event-projector-assistant-message-DTgnPPVF.js → event-projector-assistant-message-BwxV4d6N.js} +11 -23
- package/dist/{attempt-notifications-AP-a-Q5k.js → event-projector-usage-CLwNEJqW.js} +113 -29
- package/dist/harness-DoKQcF8n.js +258 -0
- package/dist/harness.js +2 -160
- package/dist/image-payload-sanitizer-CnfoUPCE.js +65 -0
- package/dist/index.js +730 -256
- package/dist/isolated-completion-DPMtlH9r.js +75 -0
- package/dist/local-runtime-attribution-B_2mnqhb.js +19 -0
- package/dist/managed-binary-By4FZC6i.js +212 -0
- package/dist/media-understanding-provider.js +5 -5
- package/dist/model-catalog-CmGoqVKB.js +48 -0
- package/dist/{models-BBMKU4jA.js → models-CZw1wClT.js} +12 -17
- package/dist/native-subagent-monitor-GGq3RnOo.js +3225 -0
- package/dist/node-exec-server.runtime-Bsu1LdFZ.js +192 -0
- package/dist/{plugin-app-cache-key-DztPafOA.js → plugin-app-cache-key-DMNQ8Zi8.js} +18 -6
- package/dist/plugin-approval-roundtrip-C75Yiiwc.js +95 -0
- package/dist/plugin-marketplace-discovery-CSWDkMrP.js +399 -0
- package/dist/{computer-use-CYEc8U3w.js → plugin-metadata-cache-BXH5NF-4.js} +109 -6
- package/dist/protocol-validators-VAVyEnDp.js +2399 -0
- package/dist/{rate-limits-CU-gVCCS.js → rate-limits-BI1dMHDG.js} +17 -35
- package/dist/{request-qKh331yu.js → request-DSK0W6YI.js} +3 -3
- package/dist/{run-attempt-CY9WlDLc.js → run-attempt-BV3HzuCf.js} +3416 -7194
- package/dist/{runtime-artifact-CmtG_t1L.js → runtime-artifact-BvyT1uyd.js} +11 -29
- package/dist/{sandbox-guard-NuhpXmPB.js → sandbox-guard-C1qEeZ5C.js} +29 -30
- package/dist/session-binding-BTrLf9Qm.js +874 -0
- package/dist/{session-catalog-DyOgR4WH.js → session-catalog-ezP_UcRM.js} +1584 -1289
- package/dist/session-catalog-home-id-BNwAJ9D7.js +18 -0
- package/dist/{session-cli-2PZaFLRh.js → session-cli-B1hKvfkI.js} +20 -7
- package/dist/session-retirement-CE4iMiLh.js +36 -0
- package/dist/{session-upstream-marker-B6U7tLw5.js → session-upstream-marker-D_2npHlA.js} +1 -1
- package/dist/{settled-turn-finalizer-CXY2Z9dT.js → settled-turn-finalizer-Dx1onIqg.js} +40 -28
- package/dist/{shared-client-DTbvH-XV.js → shared-client-Dw-rzii0.js} +1315 -469
- package/dist/{side-question-_ulBk1AL.js → side-question-DbXkJZzg.js} +140 -69
- package/dist/{thread-lifecycle-DF53ogiA.js → thread-lifecycle-DKdrnTMX.js} +927 -768
- package/dist/thread-ownership-i2NBwDUL.js +92 -0
- package/dist/thread-resume-CdJk2Ehf.js +30 -0
- package/dist/transcript-mirror-BU0_LKAL.js +588 -0
- package/dist/{transport-stdio-DXgGZ7Ib.js → transport-stdio-DhoGy33Z.js} +13 -2
- package/dist/upstream-prompt-provenance-CFmsKwr9.js +39 -0
- package/dist/{upstream-session-fork-Dy4Xvuoj.js → upstream-session-fork-BDVJCLAD.js} +14 -8
- package/dist/{usage-CQ6GhD5-.js → usage-9dKJCgcy.js} +3 -3
- package/dist/{web-search-provider.runtime-CCFhg2z5.js → web-search-provider.runtime-DCdx4DGu.js} +8 -9
- package/openclaw.plugin.json +49 -4
- package/package.json +6 -5
- package/dist/app-server-policy-BSaO2JIT.js +0 -49
- package/dist/conversation-control-xsIeXbo4.js +0 -287
- package/dist/event-projector-usage-Xp4AlR2k.js +0 -86
- package/dist/notification-correlation-CU6_AnOb.js +0 -67
- package/dist/transcript-mirror-DzsdNCf5.js +0 -6924
|
@@ -1,25 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { a as
|
|
12
|
-
import {
|
|
13
|
-
import { n as
|
|
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 {
|
|
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 =
|
|
157
|
+
const type = normalizeOptionalString(account.type)?.toLowerCase();
|
|
152
158
|
if (type === "chatgpt") {
|
|
153
|
-
const email =
|
|
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) ?
|
|
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
|
-
|
|
1446
|
-
const
|
|
1447
|
-
|
|
1448
|
-
|
|
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: ${
|
|
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
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
|
|
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 =
|
|
1721
|
-
const status =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
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
|
-
|
|
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 ${
|
|
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,
|
|
1919
|
+
await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.review, {
|
|
1812
1920
|
threadId: binding.threadId,
|
|
1813
1921
|
target: { type: "uncommittedChanges" }
|
|
1814
|
-
}
|
|
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
|
|
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
|
-
|
|
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[
|
|
1852
|
-
...block.plugins[
|
|
1988
|
+
block.plugins[configKey] = {
|
|
1989
|
+
...block.plugins[configKey],
|
|
1853
1990
|
enabled: wantEnabled
|
|
1854
1991
|
};
|
|
1855
1992
|
});
|
|
1856
|
-
return { text: `${formatCodexDisplayText(
|
|
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
|
|
1867
|
-
" 3. /codex plugins
|
|
1868
|
-
" 4. /codex plugins
|
|
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
|
|
1939
|
-
"- /codex plugins
|
|
1940
|
-
"- /codex plugins list
|
|
1941
|
-
"- /codex plugins
|
|
1942
|
-
"- /codex plugins
|
|
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
|
-
|
|
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 };
|