@openclaw/codex 2026.7.2-beta.5 → 2026.7.33
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 +3 -27
- package/dist/{command-formatters-ico4ZFnk.js → .setup/app-server-policy-C_qTMEMy.mjs} +51 -9
- package/dist/{attempt-notifications-CMR2_fmR.js → .setup/attempt-notifications-F8krwWL4.mjs} +6 -3
- package/dist/{command-handlers-CRmnwI2N.js → .setup/command-handlers-DSRFfor4.mjs} +1156 -1319
- package/dist/.setup/command-rpc-BvYsfQ7G.mjs +786 -0
- package/dist/{compact-dEN49UH6.js → .setup/compact-BNgh7I4t.mjs} +10 -40
- package/dist/.setup/computer-use-C3QkmSO3.mjs +377 -0
- package/dist/.setup/config-CYEDnLJ2.mjs +1138 -0
- package/dist/{conversation-binding-data-BHRpYbX3.js → .setup/conversation-binding-data-CrRxtDBu.mjs} +1 -1
- package/dist/{bounded-turn-FEbUXoYT.js → .setup/media-understanding-provider-EbJCyDqe.mjs} +193 -92
- package/dist/{models-BA01NEez.js → .setup/models-C_aZk03A.mjs} +11 -6
- package/dist/.setup/notification-correlation-iYs4WieS.mjs +35 -0
- package/dist/.setup/plugin-app-cache-key-DR8jV9oS.mjs +99 -0
- package/dist/.setup/protocol-2POPqAY4.mjs +13 -0
- package/dist/{protocol-validators-Dm4eXZmt.js → .setup/protocol-validators-dZQ-UTOa.mjs} +111 -334
- package/dist/{rate-limits-S_PXs0ul.js → .setup/provider-DGxDs6g8.mjs} +269 -13
- package/dist/{dynamic-tools-LIt9ojep.js → .setup/provider-capabilities-DS2N_zc1.mjs} +5465 -3399
- package/dist/.setup/request-DFR0JyoV.mjs +36 -0
- package/dist/.setup/run-attempt-C7xEKFVB.mjs +8180 -0
- package/dist/{sandbox-guard-D1Wky__S.js → .setup/sandbox-guard-DA2TQfZW.mjs} +1 -10
- package/dist/.setup/session-binding-DuihlMJc.mjs +628 -0
- package/dist/.setup/shared-client-B2KmLPRw.mjs +1854 -0
- package/dist/{side-question-CO12sHxo.js → .setup/side-question-Cv6JXWCS.mjs} +84 -159
- package/dist/{thread-lifecycle-DRg_FdzC.js → .setup/thread-lifecycle-u6CZdOOH.mjs} +1159 -1799
- package/dist/{web-search-provider.runtime-CaQMYDbp.js → .setup/web-search-provider.runtime-Ce2zOHxW.mjs} +3 -3
- package/dist/doctor-contract-api.js +122 -88
- package/dist/harness.js +20 -85
- package/dist/index.js +381 -1537
- package/dist/media-understanding-provider.js +1 -173
- package/dist/prompt-overlay.js +15 -0
- package/dist/provider-catalog.js +79 -0
- package/dist/provider-discovery.js +34 -0
- package/dist/provider.js +2 -0
- package/dist/web-search-contract-api.js +1 -1
- package/openclaw.plugin.json +178 -221
- package/package.json +8 -10
- package/dist/app-server-policy-BmDgseo_.js +0 -49
- package/dist/attempt-client-cleanup-Ia52yRWc.js +0 -127
- package/dist/auth-binding-B4FfbcCg.js +0 -62
- package/dist/capabilities-BHRSI6Cn.js +0 -36
- package/dist/cli-metadata.js +0 -20
- package/dist/computer-use-BjGylO-E.js +0 -695
- package/dist/conversation-control-DE7d9DOj.js +0 -287
- package/dist/event-projector-assistant-message-DTgnPPVF.js +0 -101
- package/dist/event-projector-usage-tvgb1NCX.js +0 -69
- package/dist/incognito-session-ET_ufDH-.js +0 -2
- package/dist/notification-correlation-Bj_Ak_jI.js +0 -67
- package/dist/plugin-app-cache-key-DSXX-rXO.js +0 -315
- package/dist/realtime-voice-api-BPcA1uN9.js +0 -494
- package/dist/realtime-voice-api.js +0 -2
- package/dist/request-CXpPennK.js +0 -157
- package/dist/run-attempt-C5GTPJOE.js +0 -11935
- package/dist/runtime-artifact-C2ITXdhM.js +0 -488
- package/dist/session-binding-DlVQ-Pxs.js +0 -2161
- package/dist/session-catalog-CP2oCcEb.js +0 -2444
- package/dist/session-cli-CqZ0Fp2a.js +0 -175
- package/dist/session-upstream-marker-QE-m5QFG.js +0 -82
- package/dist/settled-turn-finalizer-CA_2QXIE.js +0 -303
- package/dist/shared-client-BVKE40c-.js +0 -2952
- package/dist/transcript-mirror-uKA-A4tB.js +0 -550
- package/dist/transport-stdio-DXgGZ7Ib.js +0 -88
- package/dist/upstream-session-fork-_Vrz4WJr.js +0 -320
- package/dist/usage-UpVbdX5G.js +0 -25
- /package/dist/{rolldown-runtime-8H4AJuhK.js → .setup/rolldown-runtime-8H4AJuhK.mjs} +0 -0
- /package/dist/{session-binding-meta-B7aEMU7g.js → .setup/session-binding-meta-B7aEMU7g.mjs} +0 -0
- /package/dist/{web-search-provider.shared-BrZmlqyR.js → .setup/web-search-provider.shared-BrZmlqyR.mjs} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,86 +1,59 @@
|
|
|
1
|
-
import { registerCodexCliMetadata } from "./cli-metadata.js";
|
|
2
1
|
import { createCodexAppServerAgentHarness } from "./harness.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { buildCodexMediaUnderstandingProvider } from "
|
|
12
|
-
import { n as
|
|
13
|
-
import {
|
|
14
|
-
import { r as
|
|
15
|
-
import { t as
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { d as createCodexCliSessionNodeHostCommands, f as createCodexCliSessionNodeInvokePolicies, g as resumeCodexCliSessionOnNode, h as resolveCodexCliSessionForBindingOnNode, i as createCodexSessionCatalogNodeInvokePolicies, m as listCodexCliSessionsOnNode, n as createCodexSessionCatalogControl, o as codexControlRequest, r as createCodexSessionCatalogNodeHostCommands, t as codexSessionCatalogRuntime, u as assertCodexArchiveDescendantsUnowned } from "./session-catalog-CP2oCcEb.js";
|
|
21
|
-
import { r as requestCodexAppServerJson } from "./request-CXpPennK.js";
|
|
22
|
-
import { t as createCodexWebSearchProviderBase } from "./web-search-provider.shared-BrZmlqyR.js";
|
|
2
|
+
import { r as isJsonObject, t as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS } from "./.setup/protocol-2POPqAY4.mjs";
|
|
3
|
+
import { d as resolveCodexAppServerRuntimeOptions, g as resolveOpenClawExecPolicyForCodexAppServer, i as codexSandboxPolicyForTurn, n as canUseCodexModelBackedApprovalsReviewerForModel, t as CODEX_PLUGINS_MARKETPLACE_NAME, u as readCodexPluginConfig } from "./.setup/config-CYEDnLJ2.mjs";
|
|
4
|
+
import { r as assertCodexThreadStartResponse } from "./.setup/protocol-validators-dZQ-UTOa.mjs";
|
|
5
|
+
import { G as pluginReadParams, W as ensureCodexPluginActivation, f as resolveCodexAppServerRequestModelSelection, q as defaultCodexAppInventoryCache, t as CODEX_NATIVE_PERSONALITY_NONE } from "./.setup/thread-lifecycle-u6CZdOOH.mjs";
|
|
6
|
+
import { t as buildCodexProvider } from "./.setup/provider-DGxDs6g8.mjs";
|
|
7
|
+
import { _ as resolveCodexAppServerAuthAccountCacheKey, a as getLeasedSharedCodexAppServerClient, b as resolveCodexAppServerFallbackApiKeyCacheKey, o as releaseLeasedSharedCodexAppServerClient, r as clearSharedCodexAppServerClientIfCurrentAndWait, y as resolveCodexAppServerAuthProfileIdForAgent } from "./.setup/shared-client-B2KmLPRw.mjs";
|
|
8
|
+
import { n as CODEX_APP_SERVER_BINDING_NAMESPACE, t as CODEX_APP_SERVER_BINDING_MAX_ENTRIES } from "./.setup/session-binding-meta-B7aEMU7g.mjs";
|
|
9
|
+
import { a as normalizeCodexAppServerBindingModelProvider, i as isCodexAppServerNativeAuthProfile, s as sessionBindingIdentity } from "./.setup/session-binding-DuihlMJc.mjs";
|
|
10
|
+
import { t as buildCodexMediaUnderstandingProvider } from "./.setup/media-understanding-provider-EbJCyDqe.mjs";
|
|
11
|
+
import { i as describeControlFailure, n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS } from "./.setup/plugin-app-cache-key-DR8jV9oS.mjs";
|
|
12
|
+
import { a as formatCodexDisplayText, t as resolveCodexAppServerForModelProvider } from "./.setup/app-server-policy-C_qTMEMy.mjs";
|
|
13
|
+
import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./.setup/sandbox-guard-DA2TQfZW.mjs";
|
|
14
|
+
import { S as canMutateCodexHost, a as createCodexCliSessionNodeHostCommands, b as trackCodexConversationActiveTurn, c as listCodexCliSessionsOnNode, l as resolveCodexCliSessionForBindingOnNode, o as createCodexCliSessionNodeInvokePolicies, t as codexControlRequest, u as resumeCodexCliSessionOnNode, x as CODEX_NATIVE_EXECUTION_AUTH_ERROR } from "./.setup/command-rpc-BvYsfQ7G.mjs";
|
|
15
|
+
import { a as readCodexConversationBindingDataRecord, i as readCodexConversationBindingData } from "./.setup/conversation-binding-data-CrRxtDBu.mjs";
|
|
16
|
+
import { n as readCodexNotificationThreadId, r as readCodexNotificationTurnId } from "./.setup/notification-correlation-iYs4WieS.mjs";
|
|
17
|
+
import { t as requestCodexAppServerJson } from "./.setup/request-DFR0JyoV.mjs";
|
|
18
|
+
import { t as createCodexWebSearchProviderBase } from "./.setup/web-search-provider.shared-BrZmlqyR.mjs";
|
|
23
19
|
import { mutateConfigFile } from "openclaw/plugin-sdk/config-mutation";
|
|
24
|
-
import {
|
|
20
|
+
import { resolveLivePluginConfigObject, resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
25
21
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
26
22
|
import fs from "node:fs/promises";
|
|
27
23
|
import path from "node:path";
|
|
28
24
|
import { parseStrictNonNegativeInteger, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
29
|
-
import { formatErrorMessage, resolveSandboxContext } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
30
|
-
import { loadAuthProfileStoreWithoutExternalProfiles, resolveAgentConfig, resolveAgentWorkspaceDir, resolveDefaultAgentDir, resolveDefaultAgentId, resolveSessionAgentIds as resolveSessionAgentIds$1 } from "openclaw/plugin-sdk/agent-runtime";
|
|
31
25
|
import os from "node:os";
|
|
32
|
-
import {
|
|
26
|
+
import { loadAuthProfileStoreWithoutExternalProfiles, resolveAgentConfig, resolveAgentWorkspaceDir, resolveDefaultAgentId, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
|
|
33
27
|
import { loadExecApprovals } from "openclaw/plugin-sdk/exec-approvals-runtime";
|
|
34
28
|
import { asBoolean, asOptionalRecord, isRecord, normalizeOptionalString, normalizeSingleOrTrimmedStringList, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
35
|
-
import {
|
|
29
|
+
import { formatErrorMessage, resolveSandboxContext } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
30
|
+
import { jsonResult, readStringParam } from "openclaw/plugin-sdk/core";
|
|
31
|
+
import { applyAuthProfileConfig, buildApiKeyCredential, buildOauthProviderAuthResult, buildOpenAICodexCredentialExtra, readCodexCliCredentialsCached, resolveOpenAICodexAuthIdentity, resolveOpenAICodexImportProfileName, updateAuthProfileStoreWithLock } from "openclaw/plugin-sdk/provider-auth";
|
|
36
32
|
import { fileURLToPath } from "node:url";
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
33
|
+
import { pathExists } from "openclaw/plugin-sdk/security-runtime";
|
|
34
|
+
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
39
35
|
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import { archiveMigrationItem, copyMemoryMigrationFileItem, copyMigrationFileItem, withCachedMigrationConfigRuntime, writeMigrationReport } from "openclaw/plugin-sdk/migration-runtime";
|
|
36
|
+
import { MIGRATION_REASON_TARGET_EXISTS, applyMigrationManualItem, createMigrationItem, createMigrationManualItem, hasMigrationConfigPatchConflict, markMigrationItemConflict, markMigrationItemError, markMigrationItemSkipped, readMigrationConfigPath, summarizeMigrationItems, writeMigrationConfigPath } from "openclaw/plugin-sdk/migration";
|
|
37
|
+
import { archiveMigrationItem, copyMigrationFileItem, withCachedMigrationConfigRuntime, writeMigrationReport } from "openclaw/plugin-sdk/migration-runtime";
|
|
43
38
|
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
|
44
39
|
import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
|
|
45
|
-
import { jsonResult, readStringParam } from "openclaw/plugin-sdk/core";
|
|
46
40
|
import { Type } from "typebox";
|
|
47
41
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
48
42
|
//#region extensions/codex/src/app-server/session-binding-store.ts
|
|
49
43
|
/** Defers schema compilation and auth loading until the first binding operation. */
|
|
50
44
|
function createLazyCodexAppServerBindingStore(state) {
|
|
51
45
|
let resolved;
|
|
52
|
-
const store = () => resolved ??= import("
|
|
46
|
+
const store = () => resolved ??= import("./.setup/session-binding-DuihlMJc.mjs").then((n) => n.c).then(({ createCodexAppServerBindingStore }) => createCodexAppServerBindingStore(state));
|
|
53
47
|
return {
|
|
54
48
|
read: async (identity) => (await store()).read(identity),
|
|
55
|
-
hasOtherThreadOwner: async (threadId, currentIdentity) => (await store()).hasOtherThreadOwner(threadId, currentIdentity),
|
|
56
49
|
mutate: async (identity, mutation) => (await store()).mutate(identity, mutation),
|
|
57
50
|
prepareSessionGenerationReclaim: async (identity) => (await store()).prepareSessionGenerationReclaim(identity),
|
|
58
51
|
adoptSessionGeneration: async (identity, previousSessionId) => (await store()).adoptSessionGeneration(identity, previousSessionId),
|
|
59
|
-
resetSessionGeneration: async (identity) => (await store()).resetSessionGeneration(identity),
|
|
60
52
|
retireSessionGeneration: async (identity) => (await store()).retireSessionGeneration(identity),
|
|
61
|
-
withThreadArchiveFence: async (run) => (await store()).withThreadArchiveFence(run),
|
|
62
53
|
withLease: async (identity, run) => (await store()).withLease(identity, run)
|
|
63
54
|
};
|
|
64
55
|
}
|
|
65
56
|
//#endregion
|
|
66
|
-
//#region extensions/codex/src/command-dispatch.ts
|
|
67
|
-
/** Dispatches a `/codex` command to the lazily loaded handler. */
|
|
68
|
-
async function handleCodexCommand(ctx, options) {
|
|
69
|
-
const { loadSubcommandHandler, resolvePluginConfig, ...subcommandOptions } = options;
|
|
70
|
-
try {
|
|
71
|
-
return await (loadSubcommandHandler ? await loadSubcommandHandler() : await loadDefaultCodexSubcommandHandler())(ctx, {
|
|
72
|
-
...subcommandOptions,
|
|
73
|
-
pluginConfig: resolvePluginConfig?.() ?? subcommandOptions.pluginConfig
|
|
74
|
-
});
|
|
75
|
-
} catch (error) {
|
|
76
|
-
return { text: `Codex command failed: ${formatCodexDisplayText(describeControlFailure(error))}` };
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
async function loadDefaultCodexSubcommandHandler() {
|
|
80
|
-
const { handleCodexSubcommand } = await import("./command-handlers-CRmnwI2N.js");
|
|
81
|
-
return handleCodexSubcommand;
|
|
82
|
-
}
|
|
83
|
-
//#endregion
|
|
84
57
|
//#region extensions/codex/src/commands.ts
|
|
85
58
|
/** Creates the reserved `/codex` command definition exposed by the plugin. */
|
|
86
59
|
function createCodexCommand(options) {
|
|
@@ -100,6 +73,22 @@ function createCodexCommand(options) {
|
|
|
100
73
|
handler: (ctx) => handleCodexCommand(ctx, options)
|
|
101
74
|
};
|
|
102
75
|
}
|
|
76
|
+
/** Dispatches a `/codex` command to the subcommand handler and formats failures for chat. */
|
|
77
|
+
async function handleCodexCommand(ctx, options) {
|
|
78
|
+
const { loadSubcommandHandler, resolvePluginConfig, ...subcommandOptions } = options;
|
|
79
|
+
try {
|
|
80
|
+
return await (loadSubcommandHandler ? await loadSubcommandHandler() : await loadDefaultCodexSubcommandHandler())(ctx, {
|
|
81
|
+
...subcommandOptions,
|
|
82
|
+
pluginConfig: resolvePluginConfig?.() ?? subcommandOptions.pluginConfig
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return { text: `Codex command failed: ${formatCodexDisplayText(describeControlFailure(error))}` };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function loadDefaultCodexSubcommandHandler() {
|
|
89
|
+
const { handleCodexSubcommand } = await import("./.setup/command-handlers-DSRFfor4.mjs");
|
|
90
|
+
return handleCodexSubcommand;
|
|
91
|
+
}
|
|
103
92
|
//#endregion
|
|
104
93
|
//#region extensions/codex/src/conversation-turn-collector.ts
|
|
105
94
|
const MAX_PENDING_NOTIFICATIONS_PER_TURN = 100;
|
|
@@ -295,7 +284,7 @@ function readLocalMediaPath(value) {
|
|
|
295
284
|
}
|
|
296
285
|
//#endregion
|
|
297
286
|
//#region extensions/codex/src/conversation-binding.ts
|
|
298
|
-
const DEFAULT_BOUND_TURN_TIMEOUT_MS =
|
|
287
|
+
const DEFAULT_BOUND_TURN_TIMEOUT_MS = 12e5;
|
|
299
288
|
const DEFAULT_AGENT_ID = "main";
|
|
300
289
|
const VALID_AGENT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
|
301
290
|
const INVALID_AGENT_ID_CHARS_PATTERN = /[^a-z0-9_-]+/g;
|
|
@@ -333,72 +322,6 @@ function getGlobalState() {
|
|
|
333
322
|
globalState[CODEX_CONVERSATION_GLOBAL_STATE] ??= { queue: new KeyedAsyncQueue() };
|
|
334
323
|
return globalState[CODEX_CONVERSATION_GLOBAL_STATE];
|
|
335
324
|
}
|
|
336
|
-
async function startCodexConversationThread(params) {
|
|
337
|
-
const workspaceDir = params.workspaceDir?.trim() || resolveCodexDefaultWorkspaceDir(params.pluginConfig);
|
|
338
|
-
const agentDir = params.agentDir?.trim();
|
|
339
|
-
const agentLookup = buildAgentLookup({
|
|
340
|
-
agentDir,
|
|
341
|
-
config: params.config
|
|
342
|
-
});
|
|
343
|
-
const identity = sessionBindingIdentity({
|
|
344
|
-
agentId: params.agentId,
|
|
345
|
-
sessionId: params.sessionFile,
|
|
346
|
-
sessionKey: params.sessionKey,
|
|
347
|
-
config: params.config
|
|
348
|
-
});
|
|
349
|
-
const existingBinding = await params.bindingStore.read(identity);
|
|
350
|
-
assertCodexBindingMayBeReplaced(existingBinding, "starting a conversation-bound Codex thread");
|
|
351
|
-
const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
|
|
352
|
-
authProfileId: params.authProfileId ?? existingBinding?.authProfileId,
|
|
353
|
-
...agentLookup
|
|
354
|
-
});
|
|
355
|
-
if (params.threadId?.trim()) await attachExistingThread({
|
|
356
|
-
pluginConfig: params.pluginConfig,
|
|
357
|
-
bindingStore: params.bindingStore,
|
|
358
|
-
identity,
|
|
359
|
-
threadId: params.threadId.trim(),
|
|
360
|
-
workspaceDir,
|
|
361
|
-
...agentDir ? { agentDir } : {},
|
|
362
|
-
model: params.model,
|
|
363
|
-
modelProvider: params.modelProvider,
|
|
364
|
-
authProfileId,
|
|
365
|
-
approvalPolicy: params.approvalPolicy,
|
|
366
|
-
sandbox: params.sandbox,
|
|
367
|
-
serviceTier: params.serviceTier,
|
|
368
|
-
config: params.config,
|
|
369
|
-
sessionKey: params.sessionKey,
|
|
370
|
-
agentId: params.agentId
|
|
371
|
-
});
|
|
372
|
-
else await createThread({
|
|
373
|
-
pluginConfig: params.pluginConfig,
|
|
374
|
-
bindingStore: params.bindingStore,
|
|
375
|
-
identity,
|
|
376
|
-
workspaceDir,
|
|
377
|
-
...agentDir ? { agentDir } : {},
|
|
378
|
-
model: params.model,
|
|
379
|
-
modelProvider: params.modelProvider,
|
|
380
|
-
authProfileId,
|
|
381
|
-
approvalPolicy: params.approvalPolicy,
|
|
382
|
-
sandbox: params.sandbox,
|
|
383
|
-
serviceTier: params.serviceTier,
|
|
384
|
-
config: params.config,
|
|
385
|
-
sessionKey: params.sessionKey,
|
|
386
|
-
agentId: params.agentId
|
|
387
|
-
});
|
|
388
|
-
const storedBinding = await params.bindingStore.read(identity);
|
|
389
|
-
if (!storedBinding) throw new Error("Codex session binding disappeared while starting its conversation thread.");
|
|
390
|
-
return createCodexConversationBindingData({
|
|
391
|
-
source: {
|
|
392
|
-
agentId: identity.agentId,
|
|
393
|
-
sessionId: identity.sessionId,
|
|
394
|
-
threadId: storedBinding.threadId,
|
|
395
|
-
...identity.sessionKey ? { sessionKey: identity.sessionKey } : {}
|
|
396
|
-
},
|
|
397
|
-
workspaceDir,
|
|
398
|
-
...agentDir ? { agentDir } : {},
|
|
399
|
-
agentId: params.agentId
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
325
|
async function handleCodexConversationInboundClaim(event, ctx, options) {
|
|
403
326
|
const data = readCodexConversationBindingData(ctx.pluginBinding);
|
|
404
327
|
if (!data) return;
|
|
@@ -476,7 +399,6 @@ async function handleCodexConversationBindingResolved(event, options) {
|
|
|
476
399
|
if (!data || data.kind !== "codex-app-server-session") return;
|
|
477
400
|
const identity = conversationBindingIdentity(data);
|
|
478
401
|
const binding = await options.bindingStore.read(identity);
|
|
479
|
-
assertCodexBindingMayBeReplaced(binding, "clearing a denied conversation binding");
|
|
480
402
|
if (!data.start?.id || binding?.conversationStartId === data.start.id) await options.bindingStore.mutate(identity, { kind: "clear" });
|
|
481
403
|
}
|
|
482
404
|
async function resolveThreadBindingRuntime(params) {
|
|
@@ -530,22 +452,19 @@ async function resolveThreadBindingRuntime(params) {
|
|
|
530
452
|
agentDir: params.agentDir
|
|
531
453
|
})
|
|
532
454
|
});
|
|
533
|
-
const
|
|
455
|
+
const client = await getLeasedSharedCodexAppServerClient({
|
|
534
456
|
startOptions: runtime.start,
|
|
535
457
|
timeoutMs: runtime.requestTimeoutMs,
|
|
536
458
|
authProfileId: params.authProfileId,
|
|
537
459
|
...agentLookup
|
|
538
|
-
};
|
|
539
|
-
const client = await getLeasedSharedCodexAppServerClient(clientOptions);
|
|
460
|
+
});
|
|
540
461
|
return {
|
|
541
462
|
execPolicy,
|
|
542
463
|
runtime: modelScopedRuntime,
|
|
543
464
|
agentLookup,
|
|
544
465
|
model: modelSelection?.model,
|
|
545
466
|
modelProvider: modelSelection?.modelProvider ?? modelProvider,
|
|
546
|
-
client
|
|
547
|
-
clientLease: { client },
|
|
548
|
-
clientOptions
|
|
467
|
+
client
|
|
549
468
|
};
|
|
550
469
|
}
|
|
551
470
|
function buildThreadRequestRuntimeOptions(params, resolved) {
|
|
@@ -564,32 +483,22 @@ function codexConversationSandboxOrPermissions(runtime, sandbox) {
|
|
|
564
483
|
return { sandbox };
|
|
565
484
|
}
|
|
566
485
|
async function requestNewConversationBindingThread(params, resolved) {
|
|
567
|
-
return await
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
|
|
577
|
-
experimentalRawEvents: true,
|
|
578
|
-
...isIncognitoSessionKey(params.sessionKey) ? { ephemeral: true } : {}
|
|
579
|
-
}, requestOptions),
|
|
580
|
-
onClientChange: (client) => {
|
|
581
|
-
resolved.client = client;
|
|
582
|
-
}
|
|
583
|
-
});
|
|
486
|
+
return await resolved.client.request("thread/start", {
|
|
487
|
+
cwd: params.workspaceDir,
|
|
488
|
+
...resolved.model ? { model: resolved.model } : {},
|
|
489
|
+
...resolved.modelProvider ? { modelProvider: resolved.modelProvider } : {},
|
|
490
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
491
|
+
...buildThreadRequestRuntimeOptions(params, resolved),
|
|
492
|
+
developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
|
|
493
|
+
experimentalRawEvents: true
|
|
494
|
+
}, { timeoutMs: resolved.runtime.requestTimeoutMs });
|
|
584
495
|
}
|
|
585
496
|
async function writeThreadBindingFromResponse(params, resolved, response) {
|
|
586
|
-
assertCodexBindingMayBeReplaced(await params.bindingStore.read(params.identity), "storing a conversation-bound Codex thread");
|
|
587
497
|
const runtimeApprovalPolicy = typeof resolved.runtime.approvalPolicy === "string" ? resolved.runtime.approvalPolicy : void 0;
|
|
588
498
|
if (!await params.bindingStore.mutate(params.identity, {
|
|
589
499
|
kind: "set",
|
|
590
500
|
binding: {
|
|
591
501
|
threadId: response.thread.id,
|
|
592
|
-
clientId: resolved.client.getInstanceId(),
|
|
593
502
|
cwd: response.thread.cwd ?? params.workspaceDir,
|
|
594
503
|
authProfileId: params.authProfileId,
|
|
595
504
|
model: response.model ?? resolved.model ?? params.model,
|
|
@@ -607,34 +516,25 @@ async function writeThreadBindingFromResponse(params, resolved, response) {
|
|
|
607
516
|
})) throw new Error("Codex conversation binding changed while storing its thread.");
|
|
608
517
|
}
|
|
609
518
|
async function attachExistingThread(params) {
|
|
610
|
-
assertCodexBindingMayBeReplaced(await params.bindingStore.read(params.identity), "attaching a conversation-bound Codex thread");
|
|
611
519
|
const resolved = await resolveThreadBindingRuntime(params);
|
|
612
520
|
try {
|
|
613
|
-
await writeThreadBindingFromResponse(params, resolved, resolved.runtime.networkProxy ? await requestNewConversationBindingThread(params, resolved) : await
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
621
|
-
...buildThreadRequestRuntimeOptions(params, resolved)
|
|
622
|
-
}, requestOptions),
|
|
623
|
-
onClientChange: (client) => {
|
|
624
|
-
resolved.client = client;
|
|
625
|
-
}
|
|
626
|
-
}));
|
|
521
|
+
await writeThreadBindingFromResponse(params, resolved, resolved.runtime.networkProxy ? await requestNewConversationBindingThread(params, resolved) : await resolved.client.request(CODEX_CONTROL_METHODS.resumeThread, {
|
|
522
|
+
threadId: params.threadId,
|
|
523
|
+
...resolved.model ? { model: resolved.model } : {},
|
|
524
|
+
...resolved.modelProvider ? { modelProvider: resolved.modelProvider } : {},
|
|
525
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
526
|
+
...buildThreadRequestRuntimeOptions(params, resolved)
|
|
527
|
+
}, { timeoutMs: resolved.runtime.requestTimeoutMs }));
|
|
627
528
|
} finally {
|
|
628
|
-
|
|
529
|
+
releaseLeasedSharedCodexAppServerClient(resolved.client);
|
|
629
530
|
}
|
|
630
531
|
}
|
|
631
532
|
async function createThread(params) {
|
|
632
|
-
assertCodexBindingMayBeReplaced(await params.bindingStore.read(params.identity), "creating a conversation-bound Codex thread");
|
|
633
533
|
const resolved = await resolveThreadBindingRuntime(params);
|
|
634
534
|
try {
|
|
635
535
|
await writeThreadBindingFromResponse(params, resolved, await requestNewConversationBindingThread(params, resolved));
|
|
636
536
|
} finally {
|
|
637
|
-
|
|
537
|
+
releaseLeasedSharedCodexAppServerClient(resolved.client);
|
|
638
538
|
}
|
|
639
539
|
}
|
|
640
540
|
async function runBoundTurn(params) {
|
|
@@ -645,7 +545,6 @@ async function runBoundTurn(params) {
|
|
|
645
545
|
const identity = conversationBindingIdentity(params.data);
|
|
646
546
|
const binding = await params.bindingStore.read(identity);
|
|
647
547
|
if (!binding?.threadId) throw new Error("bound Codex conversation has no thread binding");
|
|
648
|
-
assertCodexBindingMayBeReplaced(binding, "running a conversation-bound Codex thread");
|
|
649
548
|
let threadId = binding.threadId;
|
|
650
549
|
const workspaceDir = binding.cwd || params.data.workspaceDir;
|
|
651
550
|
const reviewerModelProvider = resolveModelBackedReviewerPolicyProvider({
|
|
@@ -698,44 +597,33 @@ async function runBoundTurn(params) {
|
|
|
698
597
|
authProfileId: binding.authProfileId,
|
|
699
598
|
...agentLookup
|
|
700
599
|
}) : void 0;
|
|
701
|
-
const
|
|
600
|
+
const client = await getLeasedSharedCodexAppServerClient({
|
|
702
601
|
startOptions: runtime.start,
|
|
703
602
|
timeoutMs: runtime.requestTimeoutMs,
|
|
704
603
|
authProfileId: binding.authProfileId,
|
|
705
604
|
...agentLookup
|
|
706
|
-
};
|
|
707
|
-
let client = await getLeasedSharedCodexAppServerClient(clientOptions);
|
|
708
|
-
const clientLease = { client };
|
|
605
|
+
});
|
|
709
606
|
let notificationCleanup = () => void 0;
|
|
710
607
|
let requestCleanup = () => void 0;
|
|
711
608
|
try {
|
|
712
609
|
if (networkProxyBindingChanged) {
|
|
713
|
-
const response = assertCodexThreadStartResponse(await
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
|
|
726
|
-
experimentalRawEvents: true,
|
|
727
|
-
...isIncognitoSessionKey(params.sessionKey) ? { ephemeral: true } : {}
|
|
728
|
-
}, requestOptions),
|
|
729
|
-
onClientChange: (nextClient) => {
|
|
730
|
-
client = nextClient;
|
|
731
|
-
}
|
|
732
|
-
}));
|
|
610
|
+
const response = assertCodexThreadStartResponse(await client.request("thread/start", {
|
|
611
|
+
cwd: workspaceDir,
|
|
612
|
+
...modelSelection?.model ? { model: modelSelection.model } : {},
|
|
613
|
+
...modelSelection?.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
614
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
615
|
+
approvalPolicy,
|
|
616
|
+
approvalsReviewer: modelScopedRuntime.approvalsReviewer,
|
|
617
|
+
...modelScopedRuntime.networkProxy ? { config: modelScopedRuntime.networkProxy.configPatch } : { sandbox },
|
|
618
|
+
...serviceTier ? { serviceTier } : {},
|
|
619
|
+
developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
|
|
620
|
+
experimentalRawEvents: true
|
|
621
|
+
}, { timeoutMs: runtime.requestTimeoutMs }));
|
|
733
622
|
threadId = response.thread.id;
|
|
734
623
|
if (!await params.bindingStore.mutate(identity, {
|
|
735
624
|
kind: "set",
|
|
736
625
|
binding: {
|
|
737
626
|
threadId,
|
|
738
|
-
clientId: client.getInstanceId(),
|
|
739
627
|
cwd: response.thread.cwd ?? workspaceDir,
|
|
740
628
|
authProfileId: binding.authProfileId,
|
|
741
629
|
model: response.model ?? modelSelection?.model ?? binding.model,
|
|
@@ -755,46 +643,6 @@ async function runBoundTurn(params) {
|
|
|
755
643
|
}
|
|
756
644
|
})) throw new Error("Codex conversation binding changed while rotating its thread.");
|
|
757
645
|
useStickyNetworkProfile = modelScopedRuntime.networkProxy !== void 0;
|
|
758
|
-
} else if (binding.clientId !== client.getInstanceId()) {
|
|
759
|
-
const response = await withLeasedCodexAppServerClientStartSelectionRetry({
|
|
760
|
-
lease: clientLease,
|
|
761
|
-
options: clientOptions,
|
|
762
|
-
run: async (requestClient, requestOptions) => await resumeCodexAppServerThread({
|
|
763
|
-
client: requestClient,
|
|
764
|
-
abandonClient: async () => {
|
|
765
|
-
await requestClient.closeAndWait();
|
|
766
|
-
},
|
|
767
|
-
request: {
|
|
768
|
-
threadId,
|
|
769
|
-
...modelSelection?.model ? { model: modelSelection.model } : {},
|
|
770
|
-
...modelSelection?.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
771
|
-
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
772
|
-
approvalPolicy,
|
|
773
|
-
approvalsReviewer: modelScopedRuntime.approvalsReviewer,
|
|
774
|
-
...codexConversationSandboxOrPermissions(modelScopedRuntime, sandbox),
|
|
775
|
-
...serviceTier ? { serviceTier } : {}
|
|
776
|
-
},
|
|
777
|
-
...requestOptions
|
|
778
|
-
}),
|
|
779
|
-
onClientChange: (nextClient) => {
|
|
780
|
-
client = nextClient;
|
|
781
|
-
}
|
|
782
|
-
});
|
|
783
|
-
threadId = response.thread.id;
|
|
784
|
-
if (!await params.bindingStore.mutate(identity, {
|
|
785
|
-
kind: "patch",
|
|
786
|
-
threadId: binding.threadId,
|
|
787
|
-
patch: {
|
|
788
|
-
clientId: client.getInstanceId(),
|
|
789
|
-
cwd: response.thread.cwd ?? binding.cwd,
|
|
790
|
-
model: response.model ?? modelSelection?.model ?? binding.model,
|
|
791
|
-
modelProvider: normalizeCodexAppServerBindingModelProvider({
|
|
792
|
-
authProfileId: binding.authProfileId,
|
|
793
|
-
modelProvider: response.modelProvider ?? modelSelection?.modelProvider ?? binding.modelProvider,
|
|
794
|
-
...agentLookup
|
|
795
|
-
})
|
|
796
|
-
}
|
|
797
|
-
})) throw new Error("Codex conversation binding changed while resuming on a new client.");
|
|
798
646
|
}
|
|
799
647
|
const collector = createCodexConversationTurnCollector(threadId);
|
|
800
648
|
notificationCleanup = client.addNotificationHandler((notification) => collector.handleNotification(notification));
|
|
@@ -835,29 +683,15 @@ async function runBoundTurn(params) {
|
|
|
835
683
|
}, { timeoutMs: runtime.requestTimeoutMs })).turn.id;
|
|
836
684
|
const activeCleanup = trackCodexConversationActiveTurn({
|
|
837
685
|
identity,
|
|
838
|
-
client,
|
|
839
686
|
threadId,
|
|
840
687
|
turnId
|
|
841
688
|
});
|
|
842
689
|
collector.setTurnId(turnId);
|
|
843
690
|
return { reply: { text: (await collector.wait({ timeoutMs: params.timeoutMs ?? DEFAULT_BOUND_TURN_TIMEOUT_MS }).finally(activeCleanup)).replyText.trim() || "Codex completed without a text reply." } };
|
|
844
|
-
} catch (error) {
|
|
845
|
-
if (isIncognitoSessionKey(params.sessionKey)) {
|
|
846
|
-
const bindingReleased = await params.bindingStore.mutate(identity, {
|
|
847
|
-
kind: "clear",
|
|
848
|
-
threadId
|
|
849
|
-
});
|
|
850
|
-
const timedOut = error instanceof Error && error.message === "codex app-server bound turn timed out";
|
|
851
|
-
if (bindingReleased && !timedOut) await unsubscribeCodexThreadBestEffort(client, {
|
|
852
|
-
threadId,
|
|
853
|
-
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
854
|
-
});
|
|
855
|
-
}
|
|
856
|
-
throw error;
|
|
857
691
|
} finally {
|
|
858
692
|
notificationCleanup();
|
|
859
693
|
requestCleanup();
|
|
860
|
-
|
|
694
|
+
releaseLeasedSharedCodexAppServerClient(client);
|
|
861
695
|
}
|
|
862
696
|
}
|
|
863
697
|
function assertNativeConversationApprovalPolicySupported(params) {
|
|
@@ -886,8 +720,6 @@ async function prepareConversationBinding(params, options = {}) {
|
|
|
886
720
|
config: params.config
|
|
887
721
|
}) : void 0;
|
|
888
722
|
const sourceBinding = sourceIdentity ? await params.bindingStore.read(sourceIdentity) : void 0;
|
|
889
|
-
assertCodexBindingMayBeReplaced(current, "initializing a conversation-bound Codex thread");
|
|
890
|
-
assertCodexBindingMayBeReplaced(sourceBinding, "transferring a session into a conversation-bound Codex thread");
|
|
891
723
|
const inherited = current ?? sourceBinding;
|
|
892
724
|
const execPolicy = resolveConversationExecPolicy({
|
|
893
725
|
config: params.config,
|
|
@@ -940,7 +772,7 @@ async function prepareConversationBinding(params, options = {}) {
|
|
|
940
772
|
});
|
|
941
773
|
}
|
|
942
774
|
function resolveConversationExecPolicy(params) {
|
|
943
|
-
const agentId = params.agentId ?? (params.config ? resolveSessionAgentIds
|
|
775
|
+
const agentId = params.agentId ?? (params.config ? resolveSessionAgentIds({
|
|
944
776
|
sessionKey: params.sessionKey,
|
|
945
777
|
config: params.config
|
|
946
778
|
}).sessionAgentId : void 0);
|
|
@@ -963,8 +795,9 @@ function readSessionExecOverrides(params) {
|
|
|
963
795
|
agentId: params.agentId,
|
|
964
796
|
sessionKey
|
|
965
797
|
})) return;
|
|
798
|
+
const storePath = resolveStorePath(params.config.session?.store, { agentId: params.agentId });
|
|
966
799
|
const entry = getSessionEntry({
|
|
967
|
-
storePath
|
|
800
|
+
storePath,
|
|
968
801
|
sessionKey,
|
|
969
802
|
readConsistency: "latest"
|
|
970
803
|
});
|
|
@@ -1050,44 +883,6 @@ function conversationBindingIdentity(data) {
|
|
|
1050
883
|
bindingId: data.bindingId
|
|
1051
884
|
};
|
|
1052
885
|
}
|
|
1053
|
-
const codexConversationBindingRuntime = {
|
|
1054
|
-
startThread: startCodexConversationThread,
|
|
1055
|
-
handleInboundClaim: handleCodexConversationInboundClaim,
|
|
1056
|
-
handleBindingResolved: handleCodexConversationBindingResolved
|
|
1057
|
-
};
|
|
1058
|
-
//#endregion
|
|
1059
|
-
//#region extensions/codex/src/migration/apply-report.ts
|
|
1060
|
-
function codexPluginActivationReportState(result) {
|
|
1061
|
-
switch (result.reason) {
|
|
1062
|
-
case "already_active":
|
|
1063
|
-
case "installed": return {
|
|
1064
|
-
installed: true,
|
|
1065
|
-
enabled: true
|
|
1066
|
-
};
|
|
1067
|
-
case "auth_required": return {
|
|
1068
|
-
installed: true,
|
|
1069
|
-
enabled: false
|
|
1070
|
-
};
|
|
1071
|
-
case "disabled":
|
|
1072
|
-
case "marketplace_missing":
|
|
1073
|
-
case "plugin_missing": return {
|
|
1074
|
-
installed: false,
|
|
1075
|
-
enabled: false
|
|
1076
|
-
};
|
|
1077
|
-
case "refresh_failed": return {
|
|
1078
|
-
installed: true,
|
|
1079
|
-
enabled: false
|
|
1080
|
-
};
|
|
1081
|
-
}
|
|
1082
|
-
return result.reason;
|
|
1083
|
-
}
|
|
1084
|
-
function sanitizeAppsNeedingAuth(apps) {
|
|
1085
|
-
return apps.map((app) => ({
|
|
1086
|
-
id: app.id,
|
|
1087
|
-
name: app.name,
|
|
1088
|
-
needsAuth: app.needsAuth
|
|
1089
|
-
}));
|
|
1090
|
-
}
|
|
1091
886
|
//#endregion
|
|
1092
887
|
//#region extensions/codex/src/migration/helpers.ts
|
|
1093
888
|
async function exists(filePath) {
|
|
@@ -1141,6 +936,11 @@ async function readModelRefs(source) {
|
|
|
1141
936
|
refs.add(OPENAI_CODEX_DEFAULT_MODEL);
|
|
1142
937
|
return [...refs].toSorted();
|
|
1143
938
|
}
|
|
939
|
+
function readProviderAuthModelConfigs(result) {
|
|
940
|
+
const models = result.configPatch?.agents?.defaults?.models;
|
|
941
|
+
if (isRecord(models)) return { ...models };
|
|
942
|
+
return { [normalizeOptionalString(result.defaultModel) ?? OPENAI_CODEX_DEFAULT_MODEL]: {} };
|
|
943
|
+
}
|
|
1144
944
|
async function buildCodexOAuthCredential(source) {
|
|
1145
945
|
const credential = readCodexCliCredentialsCached({
|
|
1146
946
|
codexHome: source.codexHome,
|
|
@@ -1175,11 +975,13 @@ async function buildCodexOAuthCredential(source) {
|
|
|
1175
975
|
kind: "oauth",
|
|
1176
976
|
provider: OPENAI_PROVIDER_ID,
|
|
1177
977
|
profileId: profile.profileId,
|
|
1178
|
-
result
|
|
978
|
+
result,
|
|
979
|
+
modelConfigs: readProviderAuthModelConfigs(result)
|
|
1179
980
|
} : null;
|
|
1180
981
|
}
|
|
1181
982
|
async function buildCodexApiKeyCredential(source) {
|
|
1182
|
-
const
|
|
983
|
+
const raw = await readJsonObject(source.authPath);
|
|
984
|
+
const key = normalizeOptionalString(raw.OPENAI_API_KEY);
|
|
1183
985
|
if (!key) return null;
|
|
1184
986
|
return {
|
|
1185
987
|
kind: "api_key",
|
|
@@ -1233,7 +1035,7 @@ function hasAuthProfileConfigConflict(config, profile, overwrite) {
|
|
|
1233
1035
|
function hasCurrentAuthProfileConfigConflict(ctx, profile) {
|
|
1234
1036
|
let config = ctx.config;
|
|
1235
1037
|
try {
|
|
1236
|
-
config =
|
|
1038
|
+
config = ctx.runtime?.config?.current?.() ?? config;
|
|
1237
1039
|
} catch {}
|
|
1238
1040
|
return hasAuthProfileConfigConflict(config, profile, Boolean(ctx.overwrite));
|
|
1239
1041
|
}
|
|
@@ -1254,8 +1056,30 @@ function applyDefaultModelIfMissing(cfg) {
|
|
|
1254
1056
|
}
|
|
1255
1057
|
};
|
|
1256
1058
|
}
|
|
1059
|
+
function mergeModelConfigEntry(existing, patch) {
|
|
1060
|
+
if (existing && isRecord(existing) && isRecord(patch)) return {
|
|
1061
|
+
...existing,
|
|
1062
|
+
...patch
|
|
1063
|
+
};
|
|
1064
|
+
return existing ?? patch;
|
|
1065
|
+
}
|
|
1066
|
+
function applyOAuthModelConfigsToConfig(cfg, credential) {
|
|
1067
|
+
const existingModels = cfg.agents?.defaults?.models ?? {};
|
|
1068
|
+
const models = credential.result.replaceDefaultModels ? { ...credential.modelConfigs } : { ...existingModels };
|
|
1069
|
+
if (!credential.result.replaceDefaultModels) for (const [modelRef, modelConfig] of Object.entries(credential.modelConfigs)) models[modelRef] = mergeModelConfigEntry(models[modelRef], modelConfig);
|
|
1070
|
+
return {
|
|
1071
|
+
...cfg,
|
|
1072
|
+
agents: {
|
|
1073
|
+
...cfg.agents,
|
|
1074
|
+
defaults: {
|
|
1075
|
+
...cfg.agents?.defaults,
|
|
1076
|
+
models
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1257
1081
|
function applyOAuthConfigToConfig(cfg, credential, profileId) {
|
|
1258
|
-
let next =
|
|
1082
|
+
let next = applyOAuthModelConfigsToConfig(cfg, credential);
|
|
1259
1083
|
const profile = credential.result.profiles[0];
|
|
1260
1084
|
if (profile) next = applyAuthProfileConfig(next, {
|
|
1261
1085
|
profileId,
|
|
@@ -1279,25 +1103,30 @@ function applyApiKeyConfigToConfig(cfg, credential, profileId) {
|
|
|
1279
1103
|
function shouldReturnAuthConfigPatch(ctx) {
|
|
1280
1104
|
return ctx.providerOptions?.configPatchMode === CODEX_CONFIG_PATCH_MODE_RETURN$1;
|
|
1281
1105
|
}
|
|
1282
|
-
function
|
|
1283
|
-
if (credential.kind === "api_key") return {
|
|
1284
|
-
profileId,
|
|
1285
|
-
provider: credential.provider,
|
|
1286
|
-
mode: "api_key",
|
|
1287
|
-
displayName: CODEX_IMPORT_DISPLAY_NAME
|
|
1288
|
-
};
|
|
1106
|
+
function oauthAuthProfileConfig(credential, profileId) {
|
|
1289
1107
|
const profile = credential.result.profiles[0];
|
|
1290
1108
|
if (!profile || profile.credential.type !== "oauth") return null;
|
|
1291
1109
|
return {
|
|
1292
1110
|
profileId,
|
|
1293
1111
|
provider: profile.credential.provider,
|
|
1294
1112
|
mode: "oauth",
|
|
1295
|
-
...profile.credential.email ? { email: profile.credential.email } : {},
|
|
1296
|
-
...profile.credential.displayName ? { displayName: profile.credential.displayName } : {}
|
|
1113
|
+
..."email" in profile.credential && profile.credential.email ? { email: profile.credential.email } : {},
|
|
1114
|
+
..."displayName" in profile.credential && profile.credential.displayName ? { displayName: profile.credential.displayName } : {}
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
function apiKeyAuthProfileConfig(credential, profileId) {
|
|
1118
|
+
return {
|
|
1119
|
+
profileId,
|
|
1120
|
+
provider: credential.provider,
|
|
1121
|
+
mode: "api_key",
|
|
1122
|
+
displayName: CODEX_IMPORT_DISPLAY_NAME
|
|
1297
1123
|
};
|
|
1298
1124
|
}
|
|
1125
|
+
function authProfileConfigForCredential(credential, profileId) {
|
|
1126
|
+
return credential.kind === "oauth" ? oauthAuthProfileConfig(credential, profileId) : apiKeyAuthProfileConfig(credential, profileId);
|
|
1127
|
+
}
|
|
1299
1128
|
async function applyCodexAuthProfileConfig(ctx, profile, applyConfig) {
|
|
1300
|
-
const configApi =
|
|
1129
|
+
const configApi = ctx.runtime?.config;
|
|
1301
1130
|
if (!configApi?.current || !configApi.mutateConfigFile) return "unavailable";
|
|
1302
1131
|
try {
|
|
1303
1132
|
await configApi.mutateConfigFile({
|
|
@@ -1314,13 +1143,13 @@ async function applyCodexAuthProfileConfig(ctx, profile, applyConfig) {
|
|
|
1314
1143
|
return error instanceof CodexAuthConfigConflict ? "conflict" : "unavailable";
|
|
1315
1144
|
}
|
|
1316
1145
|
}
|
|
1317
|
-
async function
|
|
1318
|
-
const profile =
|
|
1146
|
+
async function applyOAuthConfig(ctx, credential, profileId) {
|
|
1147
|
+
const profile = oauthAuthProfileConfig(credential, profileId);
|
|
1319
1148
|
if (!profile) return "unavailable";
|
|
1320
|
-
return applyCodexAuthProfileConfig(ctx, profile, (config) =>
|
|
1149
|
+
return applyCodexAuthProfileConfig(ctx, profile, (config) => applyOAuthConfigToConfig(config, credential, profileId));
|
|
1321
1150
|
}
|
|
1322
|
-
function
|
|
1323
|
-
return
|
|
1151
|
+
async function applyApiKeyConfig(ctx, credential, profileId) {
|
|
1152
|
+
return applyCodexAuthProfileConfig(ctx, apiKeyAuthProfileConfig(credential, profileId), (config) => applyApiKeyConfigToConfig(config, credential, profileId));
|
|
1324
1153
|
}
|
|
1325
1154
|
async function buildCodexAuthItems(params) {
|
|
1326
1155
|
const credentials = await readCodexAuthCredentials(params.source);
|
|
@@ -1353,27 +1182,26 @@ async function buildCodexAuthItems(params) {
|
|
|
1353
1182
|
});
|
|
1354
1183
|
});
|
|
1355
1184
|
}
|
|
1356
|
-
async function
|
|
1185
|
+
async function applyCodexAuthItem(params) {
|
|
1357
1186
|
const { ctx, item, source, targets } = params;
|
|
1358
|
-
if (item.status !== "planned") return
|
|
1187
|
+
if (item.status !== "planned") return item;
|
|
1359
1188
|
const profileId = typeof item.details?.profileId === "string" ? item.details.profileId : "";
|
|
1360
1189
|
const provider = typeof item.details?.provider === "string" ? item.details.provider : "";
|
|
1361
1190
|
const sourceProfileId = typeof item.details?.sourceProfileId === "string" ? item.details.sourceProfileId : void 0;
|
|
1362
|
-
if (!profileId || !provider) return
|
|
1191
|
+
if (!profileId || !provider) return markMigrationItemError(item, CODEX_REASON_MISSING_AUTH_METADATA);
|
|
1363
1192
|
const credential = (await readCodexAuthCredentials(source)).find((candidate) => candidate.provider === provider);
|
|
1364
|
-
if (!credential) return
|
|
1365
|
-
if (credential.kind === "oauth" && sourceProfileId && credential.profileId !== sourceProfileId) return
|
|
1193
|
+
if (!credential) return markMigrationItemSkipped(item, CODEX_REASON_AUTH_NO_LONGER_PRESENT);
|
|
1194
|
+
if (credential.kind === "oauth" && sourceProfileId && credential.profileId !== sourceProfileId) return markMigrationItemSkipped(item, CODEX_REASON_AUTH_NO_LONGER_PRESENT);
|
|
1366
1195
|
const oauthProfile = credential.kind === "oauth" ? credential.result.profiles[0] : void 0;
|
|
1367
1196
|
const oauthCredential = oauthProfile?.credential.type === "oauth" ? oauthProfile.credential : void 0;
|
|
1368
|
-
if (credential.kind === "oauth" && !oauthCredential) return
|
|
1197
|
+
if (credential.kind === "oauth" && !oauthCredential) return markMigrationItemError(item, CODEX_REASON_MISSING_AUTH_METADATA);
|
|
1369
1198
|
const configProfile = authProfileConfigForCredential(credential, profileId);
|
|
1370
|
-
if (!configProfile) return
|
|
1371
|
-
if (hasCurrentAuthProfileConfigConflict(ctx, configProfile)) return
|
|
1199
|
+
if (!configProfile) return markMigrationItemError(item, CODEX_REASON_MISSING_AUTH_METADATA);
|
|
1200
|
+
if (hasCurrentAuthProfileConfigConflict(ctx, configProfile)) return markMigrationItemConflict(item, CODEX_REASON_AUTH_PROFILE_EXISTS);
|
|
1372
1201
|
let conflicted = false;
|
|
1373
1202
|
let wrote = false;
|
|
1374
1203
|
const store = await updateAuthProfileStoreWithLock({
|
|
1375
1204
|
agentDir: targets.agentDir,
|
|
1376
|
-
stateDir: ctx.stateDir,
|
|
1377
1205
|
updater: (freshStore) => {
|
|
1378
1206
|
const existing = freshStore.profiles[profileId];
|
|
1379
1207
|
if (!ctx.overwrite && existing) {
|
|
@@ -1392,11 +1220,11 @@ async function applyCodexAuthItems(params) {
|
|
|
1392
1220
|
return true;
|
|
1393
1221
|
}
|
|
1394
1222
|
});
|
|
1395
|
-
if (conflicted) return
|
|
1396
|
-
if (!store?.profiles[profileId]) return
|
|
1397
|
-
const configResult = shouldReturnAuthConfigPatch(ctx) ? "unavailable" : await
|
|
1398
|
-
if (configResult === "conflict") return
|
|
1399
|
-
|
|
1223
|
+
if (conflicted) return markMigrationItemConflict(item, CODEX_REASON_AUTH_PROFILE_EXISTS);
|
|
1224
|
+
if (!store?.profiles[profileId]) return markMigrationItemError(item, CODEX_REASON_AUTH_PROFILE_WRITE_FAILED);
|
|
1225
|
+
const configResult = shouldReturnAuthConfigPatch(ctx) ? "unavailable" : credential.kind === "oauth" ? await applyOAuthConfig(ctx, credential, profileId) : await applyApiKeyConfig(ctx, credential, profileId);
|
|
1226
|
+
if (configResult === "conflict") return markMigrationItemConflict(item, CODEX_REASON_AUTH_PROFILE_EXISTS);
|
|
1227
|
+
return {
|
|
1400
1228
|
...item,
|
|
1401
1229
|
status: "migrated",
|
|
1402
1230
|
details: {
|
|
@@ -1406,10 +1234,18 @@ async function applyCodexAuthItems(params) {
|
|
|
1406
1234
|
...shouldReturnAuthConfigPatch(ctx) ? { configPatchReturned: true } : {}
|
|
1407
1235
|
}
|
|
1408
1236
|
};
|
|
1409
|
-
return [migratedItem, ...shouldReturnAuthConfigPatch(ctx) ? buildCodexAuthConfigPatchItems(ctx, migratedItem, credential, profileId) : []];
|
|
1410
1237
|
}
|
|
1411
|
-
function buildCodexAuthConfigPatchItems(
|
|
1412
|
-
const
|
|
1238
|
+
async function buildCodexAuthConfigPatchItems(params) {
|
|
1239
|
+
const { ctx, item, source } = params;
|
|
1240
|
+
if (item.status !== "migrated" || !shouldReturnAuthConfigPatch(ctx)) return [];
|
|
1241
|
+
const profileId = typeof item.details?.profileId === "string" ? item.details.profileId : "";
|
|
1242
|
+
const provider = typeof item.details?.provider === "string" ? item.details.provider : "";
|
|
1243
|
+
const sourceProfileId = typeof item.details?.sourceProfileId === "string" ? item.details.sourceProfileId : void 0;
|
|
1244
|
+
if (!profileId || !provider) return [];
|
|
1245
|
+
const credential = (await readCodexAuthCredentials(source)).find((candidate) => candidate.provider === provider);
|
|
1246
|
+
if (!credential) return [];
|
|
1247
|
+
if (credential.kind === "oauth" && sourceProfileId && credential.profileId !== sourceProfileId) return [];
|
|
1248
|
+
const next = credential.kind === "oauth" ? applyOAuthConfigToConfig(ctx.config, credential, profileId) : applyApiKeyConfigToConfig(ctx.config, credential, profileId);
|
|
1413
1249
|
const items = [];
|
|
1414
1250
|
if (next.auth) items.push(createMigrationItem({
|
|
1415
1251
|
id: `${item.id}:config:auth`,
|
|
@@ -1438,10 +1274,16 @@ function buildCodexAuthConfigPatchItems(ctx, item, credential, profileId) {
|
|
|
1438
1274
|
return items;
|
|
1439
1275
|
}
|
|
1440
1276
|
//#endregion
|
|
1441
|
-
//#region extensions/codex/src/migration/source
|
|
1277
|
+
//#region extensions/codex/src/migration/source.ts
|
|
1442
1278
|
const SKILL_FILENAME = "SKILL.md";
|
|
1443
1279
|
const MAX_SCAN_DEPTH = 6;
|
|
1444
1280
|
const MAX_DISCOVERED_DIRS = 2e3;
|
|
1281
|
+
function defaultCodexHome() {
|
|
1282
|
+
return resolveHomePath(process.env.CODEX_HOME?.trim() || "~/.codex");
|
|
1283
|
+
}
|
|
1284
|
+
function personalAgentsSkillsDir() {
|
|
1285
|
+
return path.join(resolveUserHomeDir(), ".agents", "skills");
|
|
1286
|
+
}
|
|
1445
1287
|
async function safeReadDir(dir) {
|
|
1446
1288
|
return await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1447
1289
|
}
|
|
@@ -1460,7 +1302,10 @@ async function discoverSkillDirs(params) {
|
|
|
1460
1302
|
});
|
|
1461
1303
|
return;
|
|
1462
1304
|
}
|
|
1463
|
-
for (const entry of await safeReadDir(dir))
|
|
1305
|
+
for (const entry of await safeReadDir(dir)) {
|
|
1306
|
+
if (!entry.isDirectory()) continue;
|
|
1307
|
+
await visit(path.join(dir, entry.name), depth + 1);
|
|
1308
|
+
}
|
|
1464
1309
|
}
|
|
1465
1310
|
await visit(params.root, 0);
|
|
1466
1311
|
return discovered;
|
|
@@ -1474,56 +1319,25 @@ async function discoverPluginDirs(codexHome) {
|
|
|
1474
1319
|
const manifestPath = path.join(dir, ".codex-plugin", "plugin.json");
|
|
1475
1320
|
if (await exists(manifestPath)) {
|
|
1476
1321
|
const manifest = await readJsonObject(manifestPath);
|
|
1477
|
-
const
|
|
1322
|
+
const name = (typeof manifest.name === "string" ? manifest.name.trim() : "") || path.basename(dir);
|
|
1478
1323
|
discovered.set(dir, {
|
|
1479
|
-
name
|
|
1324
|
+
name,
|
|
1480
1325
|
source: dir,
|
|
1326
|
+
manifestPath,
|
|
1327
|
+
sourceKind: "cache",
|
|
1481
1328
|
migratable: false,
|
|
1482
1329
|
message: "Cached Codex plugin bundle found. Review manually unless the plugin is also installed in the source Codex app-server inventory"
|
|
1483
1330
|
});
|
|
1484
1331
|
return;
|
|
1485
1332
|
}
|
|
1486
|
-
for (const entry of await safeReadDir(dir))
|
|
1333
|
+
for (const entry of await safeReadDir(dir)) {
|
|
1334
|
+
if (!entry.isDirectory()) continue;
|
|
1335
|
+
await visit(path.join(dir, entry.name), depth + 1);
|
|
1336
|
+
}
|
|
1487
1337
|
}
|
|
1488
1338
|
await visit(root, 0);
|
|
1489
1339
|
return [...discovered.values()].toSorted((a, b) => a.source.localeCompare(b.source));
|
|
1490
1340
|
}
|
|
1491
|
-
async function discoverCodexMemoryFile(candidate) {
|
|
1492
|
-
try {
|
|
1493
|
-
const stat = await fs.lstat(candidate.path);
|
|
1494
|
-
if (stat.isSymbolicLink()) throw new Error(`Codex memory source must not be a symbolic link: ${candidate.path}`);
|
|
1495
|
-
if (!stat.isFile()) throw new Error(`Codex memory source must be a regular file: ${candidate.path}`);
|
|
1496
|
-
return candidate;
|
|
1497
|
-
} catch (error) {
|
|
1498
|
-
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return;
|
|
1499
|
-
throw error;
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
async function discoverCodexMemorySources(codexHome) {
|
|
1503
|
-
const memoriesDir = path.join(codexHome, "memories");
|
|
1504
|
-
return (await Promise.all([{
|
|
1505
|
-
id: "memory:codex:MEMORY.md",
|
|
1506
|
-
label: "Codex consolidated memory",
|
|
1507
|
-
name: "MEMORY.md"
|
|
1508
|
-
}, {
|
|
1509
|
-
id: "memory:codex:memory_summary.md",
|
|
1510
|
-
label: "Codex memory summary",
|
|
1511
|
-
name: "memory_summary.md"
|
|
1512
|
-
}].map(async (candidate) => await discoverCodexMemoryFile({
|
|
1513
|
-
id: candidate.id,
|
|
1514
|
-
label: candidate.label,
|
|
1515
|
-
path: path.join(memoriesDir, candidate.name)
|
|
1516
|
-
})))).filter((entry) => entry !== void 0);
|
|
1517
|
-
}
|
|
1518
|
-
//#endregion
|
|
1519
|
-
//#region extensions/codex/src/migration/source.ts
|
|
1520
|
-
function defaultCodexHome() {
|
|
1521
|
-
const configuredHome = process.env.CODEX_HOME;
|
|
1522
|
-
return resolveHomePath(configuredHome !== void 0 && configuredHome.length > 0 ? configuredHome : "~/.codex");
|
|
1523
|
-
}
|
|
1524
|
-
function personalAgentsSkillsDir() {
|
|
1525
|
-
return path.join(resolveUserHomeDir(), ".agents", "skills");
|
|
1526
|
-
}
|
|
1527
1341
|
async function discoverInstalledCuratedPlugins(codexHome, options = {}) {
|
|
1528
1342
|
const requestOptions = { startOptions: sourceCodexAppServerStartOptions(codexHome) };
|
|
1529
1343
|
try {
|
|
@@ -1554,7 +1368,6 @@ function sourceCodexAppServerStartOptions(codexHome) {
|
|
|
1554
1368
|
transport: "stdio",
|
|
1555
1369
|
command: "codex",
|
|
1556
1370
|
commandSource: "managed",
|
|
1557
|
-
managedCommandOrder: "desktop-first",
|
|
1558
1371
|
args: [
|
|
1559
1372
|
"app-server",
|
|
1560
1373
|
"--listen",
|
|
@@ -1585,6 +1398,7 @@ function buildInstalledPluginSource(plugin) {
|
|
|
1585
1398
|
pluginName,
|
|
1586
1399
|
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
|
1587
1400
|
source: `${CODEX_PLUGINS_MARKETPLACE_NAME}/${pluginName}`,
|
|
1401
|
+
sourceKind: "app-server",
|
|
1588
1402
|
migratable: true,
|
|
1589
1403
|
installed: plugin.installed,
|
|
1590
1404
|
enabled: plugin.enabled
|
|
@@ -1791,7 +1605,8 @@ function pluginNameFromSummary(summary) {
|
|
|
1791
1605
|
if (normalized) return normalized;
|
|
1792
1606
|
}
|
|
1793
1607
|
}
|
|
1794
|
-
async function discoverCodexSource(
|
|
1608
|
+
async function discoverCodexSource(inputOrOptions) {
|
|
1609
|
+
const options = typeof inputOrOptions === "string" || inputOrOptions === void 0 ? { input: inputOrOptions } : inputOrOptions;
|
|
1795
1610
|
const codexHome = resolveHomePath(options.input?.trim() || defaultCodexHome());
|
|
1796
1611
|
const codexSkillsDir = path.join(codexHome, "skills");
|
|
1797
1612
|
const agentsSkillsDir = personalAgentsSkillsDir();
|
|
@@ -1799,39 +1614,38 @@ async function discoverCodexSource(options = {}) {
|
|
|
1799
1614
|
const authPath = path.join(codexHome, "auth.json");
|
|
1800
1615
|
const modelsCachePath = path.join(codexHome, "models_cache.json");
|
|
1801
1616
|
const hooksPath = path.join(codexHome, "hooks", "hooks.json");
|
|
1802
|
-
const
|
|
1803
|
-
const codexSkills = options.memoryOnly ? [] : await discoverSkillDirs({
|
|
1617
|
+
const codexSkills = await discoverSkillDirs({
|
|
1804
1618
|
root: codexSkillsDir,
|
|
1805
1619
|
sourceLabel: "Codex skill",
|
|
1806
1620
|
excludeSystem: true
|
|
1807
1621
|
});
|
|
1808
|
-
const personalAgentSkills =
|
|
1622
|
+
const personalAgentSkills = await discoverSkillDirs({
|
|
1809
1623
|
root: agentsSkillsDir,
|
|
1810
1624
|
sourceLabel: "personal AgentSkill"
|
|
1811
1625
|
});
|
|
1812
|
-
const sourcePluginDiscovery =
|
|
1626
|
+
const sourcePluginDiscovery = await discoverInstalledCuratedPlugins(codexHome, options);
|
|
1813
1627
|
const sourcePluginNames = new Set(sourcePluginDiscovery.plugins.flatMap((plugin) => plugin.pluginName ? [plugin.pluginName] : []));
|
|
1814
|
-
const cachedPlugins = (
|
|
1628
|
+
const cachedPlugins = (await discoverPluginDirs(codexHome)).filter((plugin) => {
|
|
1815
1629
|
const normalizedName = sanitizePluginName(plugin.name);
|
|
1816
1630
|
return !sourcePluginNames.has(normalizedName);
|
|
1817
1631
|
});
|
|
1818
1632
|
const plugins = [...sourcePluginDiscovery.plugins, ...cachedPlugins].toSorted((a, b) => a.source.localeCompare(b.source));
|
|
1819
1633
|
const archivePaths = [];
|
|
1820
|
-
if (
|
|
1634
|
+
if (await exists(configPath)) archivePaths.push({
|
|
1821
1635
|
id: "archive:config.toml",
|
|
1822
1636
|
path: configPath,
|
|
1823
1637
|
relativePath: "config.toml",
|
|
1824
1638
|
message: "Codex config is archived for manual review; it is not activated automatically"
|
|
1825
1639
|
});
|
|
1826
|
-
if (
|
|
1640
|
+
if (await exists(hooksPath)) archivePaths.push({
|
|
1827
1641
|
id: "archive:hooks/hooks.json",
|
|
1828
1642
|
path: hooksPath,
|
|
1829
1643
|
relativePath: "hooks/hooks.json",
|
|
1830
1644
|
message: "Codex native hooks are archived for manual review because they can execute commands"
|
|
1831
1645
|
});
|
|
1832
1646
|
const skills = [...codexSkills, ...personalAgentSkills].toSorted((a, b) => a.source.localeCompare(b.source));
|
|
1833
|
-
const hasAuth =
|
|
1834
|
-
const high = Boolean(
|
|
1647
|
+
const hasAuth = await exists(authPath);
|
|
1648
|
+
const high = Boolean(codexSkills.length || plugins.length || archivePaths.length || hasAuth);
|
|
1835
1649
|
const medium = personalAgentSkills.length > 0;
|
|
1836
1650
|
return {
|
|
1837
1651
|
root: codexHome,
|
|
@@ -1839,9 +1653,10 @@ async function discoverCodexSource(options = {}) {
|
|
|
1839
1653
|
codexHome,
|
|
1840
1654
|
...await isDirectory(codexSkillsDir) ? { codexSkillsDir } : {},
|
|
1841
1655
|
...await isDirectory(agentsSkillsDir) ? { personalAgentsSkillsDir: agentsSkillsDir } : {},
|
|
1656
|
+
...await exists(configPath) ? { configPath } : {},
|
|
1842
1657
|
...hasAuth ? { authPath } : {},
|
|
1843
1658
|
...await exists(modelsCachePath) ? { modelsCachePath } : {},
|
|
1844
|
-
|
|
1659
|
+
...await exists(hooksPath) ? { hooksPath } : {},
|
|
1845
1660
|
skills,
|
|
1846
1661
|
plugins,
|
|
1847
1662
|
...sourcePluginDiscovery.error ? { pluginDiscoveryError: sourcePluginDiscovery.error } : {},
|
|
@@ -1858,7 +1673,7 @@ function sanitizePluginName(value) {
|
|
|
1858
1673
|
//#region extensions/codex/src/migration/targets.ts
|
|
1859
1674
|
function resolveCodexMigrationTargets(ctx) {
|
|
1860
1675
|
const cfg = ctx.config;
|
|
1861
|
-
const agentId =
|
|
1676
|
+
const agentId = resolveDefaultAgentId(cfg);
|
|
1862
1677
|
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
|
1863
1678
|
const configuredAgentDir = resolveAgentConfig(cfg, agentId)?.agentDir?.trim();
|
|
1864
1679
|
return {
|
|
@@ -1889,52 +1704,6 @@ const CODEX_PLUGIN_NATIVE_CONFIG_PATH = [
|
|
|
1889
1704
|
];
|
|
1890
1705
|
const MIGRATION_REASON_PLUGIN_EXISTS = "plugin exists";
|
|
1891
1706
|
const CODEX_PLUGIN_SOURCE_APP_VERIFICATION_UNVERIFIED = "not_run";
|
|
1892
|
-
const MIGRATION_REASON_TARGET_NOT_REGULAR = "target is not a regular file";
|
|
1893
|
-
async function lstatIfExists(filePath) {
|
|
1894
|
-
try {
|
|
1895
|
-
return await fs.lstat(filePath);
|
|
1896
|
-
} catch (error) {
|
|
1897
|
-
const code = extractErrorCode(error);
|
|
1898
|
-
if (code === "ENOENT" || code === "ENOTDIR") return;
|
|
1899
|
-
throw error;
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
async function buildCodexMemoryItems(params) {
|
|
1903
|
-
const items = [];
|
|
1904
|
-
for (const memory of params.memoryFiles) {
|
|
1905
|
-
const target = path.join(params.workspaceDir, "memory", "imports", "codex", path.basename(memory.path));
|
|
1906
|
-
const targetStat = await lstatIfExists(target);
|
|
1907
|
-
const targetNotRegular = targetStat !== void 0 && !targetStat.isFile();
|
|
1908
|
-
if (!targetNotRegular) {
|
|
1909
|
-
const [source, workspace, destination] = await Promise.all([
|
|
1910
|
-
fs.realpath(path.dirname(memory.path)),
|
|
1911
|
-
canonicalPathFromExistingAncestor(params.workspaceDir),
|
|
1912
|
-
canonicalPathFromExistingAncestor(target)
|
|
1913
|
-
]);
|
|
1914
|
-
if (!isPathInside(workspace, destination)) throw new Error("Codex memory import destination must stay in the selected workspace.");
|
|
1915
|
-
if (isPathInside(source, destination) || isPathInside(destination, source)) throw new Error("Codex memory source and OpenClaw import destination must be separate paths.");
|
|
1916
|
-
}
|
|
1917
|
-
const targetConflict = targetStat !== void 0 && !params.overwrite;
|
|
1918
|
-
items.push(createMigrationItem({
|
|
1919
|
-
id: memory.id,
|
|
1920
|
-
kind: "memory",
|
|
1921
|
-
action: "copy",
|
|
1922
|
-
source: memory.path,
|
|
1923
|
-
target,
|
|
1924
|
-
status: targetNotRegular || targetConflict ? "conflict" : "planned",
|
|
1925
|
-
reason: targetNotRegular ? MIGRATION_REASON_TARGET_NOT_REGULAR : targetConflict ? MIGRATION_REASON_TARGET_EXISTS : void 0,
|
|
1926
|
-
message: "Copy consolidated Codex memory into the OpenClaw memory index.",
|
|
1927
|
-
details: {
|
|
1928
|
-
sourceType: "codex-memory",
|
|
1929
|
-
sourceLabel: memory.label,
|
|
1930
|
-
collectionId: "codex",
|
|
1931
|
-
collectionLabel: "Codex",
|
|
1932
|
-
relativePath: path.basename(memory.path)
|
|
1933
|
-
}
|
|
1934
|
-
}));
|
|
1935
|
-
}
|
|
1936
|
-
return items;
|
|
1937
|
-
}
|
|
1938
1707
|
function uniqueSkillName(skill, counts) {
|
|
1939
1708
|
const base = sanitizeName(skill.name) || "codex-skill";
|
|
1940
1709
|
if ((counts.get(base) ?? 0) <= 1) return base;
|
|
@@ -1944,43 +1713,49 @@ function uniqueSkillName(skill, counts) {
|
|
|
1944
1713
|
base
|
|
1945
1714
|
].filter(Boolean).join("-")) || base;
|
|
1946
1715
|
}
|
|
1947
|
-
async function
|
|
1948
|
-
const
|
|
1716
|
+
async function buildSkillItems(params) {
|
|
1717
|
+
const baseCounts = /* @__PURE__ */ new Map();
|
|
1949
1718
|
for (const skill of params.skills) {
|
|
1950
1719
|
const base = sanitizeName(skill.name) || "codex-skill";
|
|
1951
|
-
|
|
1720
|
+
baseCounts.set(base, (baseCounts.get(base) ?? 0) + 1);
|
|
1952
1721
|
}
|
|
1722
|
+
const resolvedCounts = /* @__PURE__ */ new Map();
|
|
1953
1723
|
const planned = params.skills.map((skill) => {
|
|
1954
|
-
const name = uniqueSkillName(skill,
|
|
1724
|
+
const name = uniqueSkillName(skill, baseCounts);
|
|
1725
|
+
resolvedCounts.set(name, (resolvedCounts.get(name) ?? 0) + 1);
|
|
1955
1726
|
return {
|
|
1956
1727
|
skill,
|
|
1957
1728
|
name,
|
|
1958
1729
|
target: path.join(params.workspaceDir, "skills", name)
|
|
1959
1730
|
};
|
|
1960
1731
|
});
|
|
1961
|
-
const
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
}, /* @__PURE__ */ new Map());
|
|
1965
|
-
return await Promise.all(planned.map(async (item) => {
|
|
1966
|
-
const collision = (resolvedCounts.get(item.name) ?? 0) > 1;
|
|
1732
|
+
const items = [];
|
|
1733
|
+
for (const item of planned) {
|
|
1734
|
+
const collides = (resolvedCounts.get(item.name) ?? 0) > 1;
|
|
1967
1735
|
const targetExists = await exists(item.target);
|
|
1968
|
-
|
|
1969
|
-
return createMigrationItem({
|
|
1736
|
+
items.push(createMigrationItem({
|
|
1970
1737
|
id: `skill:${item.name}`,
|
|
1971
1738
|
kind: "skill",
|
|
1972
1739
|
action: "copy",
|
|
1973
1740
|
source: item.skill.source,
|
|
1974
1741
|
target: item.target,
|
|
1975
|
-
status: conflict ? "conflict" : "planned",
|
|
1976
|
-
reason:
|
|
1742
|
+
status: collides ? "conflict" : targetExists && !params.overwrite ? "conflict" : "planned",
|
|
1743
|
+
reason: collides ? `multiple Codex skills normalize to "${item.name}"` : targetExists && !params.overwrite ? MIGRATION_REASON_TARGET_EXISTS : void 0,
|
|
1977
1744
|
message: `Copy ${item.skill.sourceLabel} into this OpenClaw agent workspace.`,
|
|
1978
1745
|
details: {
|
|
1979
1746
|
skillName: item.name,
|
|
1980
1747
|
sourceLabel: item.skill.sourceLabel
|
|
1981
1748
|
}
|
|
1982
|
-
});
|
|
1983
|
-
}
|
|
1749
|
+
}));
|
|
1750
|
+
}
|
|
1751
|
+
return items;
|
|
1752
|
+
}
|
|
1753
|
+
function uniquePluginConfigKey(plugin, counts, usedCounts) {
|
|
1754
|
+
const base = sanitizeName(plugin.pluginName ?? plugin.name) || "codex-plugin";
|
|
1755
|
+
if ((counts.get(base) ?? 0) <= 1) return base;
|
|
1756
|
+
const next = (usedCounts.get(base) ?? 0) + 1;
|
|
1757
|
+
usedCounts.set(base, next);
|
|
1758
|
+
return sanitizeName(`${base}-${next}`) || base;
|
|
1984
1759
|
}
|
|
1985
1760
|
function readExistingCodexPluginEntries(config) {
|
|
1986
1761
|
const entries = readMigrationConfigPath(config, [...CODEX_PLUGIN_NATIVE_CONFIG_PATH, "plugins"]);
|
|
@@ -2011,12 +1786,18 @@ function readExistingPluginAllowDestructiveActions(existing, pluginName) {
|
|
|
2011
1786
|
return normalized === "auto" || normalized === "ask" ? normalized : void 0;
|
|
2012
1787
|
}
|
|
2013
1788
|
function buildPluginItems(ctx, plugins) {
|
|
1789
|
+
const baseCounts = /* @__PURE__ */ new Map();
|
|
1790
|
+
for (const plugin of plugins.filter((entry) => entry.migratable)) {
|
|
1791
|
+
const base = sanitizeName(plugin.pluginName ?? plugin.name) || "codex-plugin";
|
|
1792
|
+
baseCounts.set(base, (baseCounts.get(base) ?? 0) + 1);
|
|
1793
|
+
}
|
|
2014
1794
|
const existingPluginEntries = readExistingCodexPluginEntries(ctx.config);
|
|
1795
|
+
const usedCounts = /* @__PURE__ */ new Map();
|
|
2015
1796
|
let manualIndex = 0;
|
|
2016
1797
|
const items = [];
|
|
2017
1798
|
for (const plugin of plugins) {
|
|
2018
1799
|
if (plugin.migratable && plugin.marketplaceName === "openai-curated" && plugin.pluginName) {
|
|
2019
|
-
const configKey = plugin
|
|
1800
|
+
const configKey = uniquePluginConfigKey(plugin, baseCounts, usedCounts);
|
|
2020
1801
|
const plannedEntry = {
|
|
2021
1802
|
enabled: true,
|
|
2022
1803
|
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
|
@@ -2033,7 +1814,6 @@ function buildPluginItems(ctx, plugins) {
|
|
|
2033
1814
|
action: "install",
|
|
2034
1815
|
status: conflict ? "conflict" : "planned",
|
|
2035
1816
|
reason: conflict ? MIGRATION_REASON_PLUGIN_EXISTS : void 0,
|
|
2036
|
-
applyPhase: "after-promotion",
|
|
2037
1817
|
source: plugin.source,
|
|
2038
1818
|
target: `plugins.entries.codex.config.codexPlugins.plugins.${configKey}`,
|
|
2039
1819
|
message: `Install Codex plugin "${plugin.pluginName}" in the OpenClaw-managed Codex app-server runtime.`,
|
|
@@ -2051,6 +1831,12 @@ function buildPluginItems(ctx, plugins) {
|
|
|
2051
1831
|
}
|
|
2052
1832
|
manualIndex += 1;
|
|
2053
1833
|
if (plugin.migrationBlock && plugin.pluginName) {
|
|
1834
|
+
const details = {
|
|
1835
|
+
pluginName: plugin.pluginName,
|
|
1836
|
+
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
|
1837
|
+
...plugin.migrationBlock.apps ? { apps: plugin.migrationBlock.apps } : {},
|
|
1838
|
+
...plugin.migrationBlock.error ? { error: plugin.migrationBlock.error } : {}
|
|
1839
|
+
};
|
|
2054
1840
|
items.push(createMigrationItem({
|
|
2055
1841
|
id: `plugin:${sanitizeName(plugin.name) || sanitizeName(path.basename(plugin.source))}:${manualIndex}`,
|
|
2056
1842
|
kind: "manual",
|
|
@@ -2059,12 +1845,7 @@ function buildPluginItems(ctx, plugins) {
|
|
|
2059
1845
|
status: "skipped",
|
|
2060
1846
|
reason: plugin.migrationBlock.code,
|
|
2061
1847
|
message: plugin.message ?? `Codex native plugin "${plugin.name}" was found but not activated automatically.`,
|
|
2062
|
-
details: {
|
|
2063
|
-
pluginName: plugin.pluginName,
|
|
2064
|
-
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
|
2065
|
-
...plugin.migrationBlock.apps ? { apps: plugin.migrationBlock.apps } : {},
|
|
2066
|
-
...plugin.migrationBlock.error ? { error: plugin.migrationBlock.error } : {}
|
|
2067
|
-
}
|
|
1848
|
+
details: { ...details }
|
|
2068
1849
|
}));
|
|
2069
1850
|
continue;
|
|
2070
1851
|
}
|
|
@@ -2072,7 +1853,7 @@ function buildPluginItems(ctx, plugins) {
|
|
|
2072
1853
|
id: `plugin:${sanitizeName(plugin.name) || sanitizeName(path.basename(plugin.source))}:${manualIndex}`,
|
|
2073
1854
|
source: plugin.source,
|
|
2074
1855
|
message: plugin.message ?? `Codex native plugin "${plugin.name}" was found but not activated automatically.`,
|
|
2075
|
-
recommendation: "Review the plugin bundle first, then install trusted compatible plugins with openclaw plugins install <path
|
|
1856
|
+
recommendation: "Review the plugin bundle first, then install trusted compatible plugins with openclaw plugins install <path>."
|
|
2076
1857
|
}));
|
|
2077
1858
|
}
|
|
2078
1859
|
return items;
|
|
@@ -2102,6 +1883,7 @@ function normalizeExistingAllowDestructiveActions(value) {
|
|
|
2102
1883
|
return asBoolean(value);
|
|
2103
1884
|
}
|
|
2104
1885
|
function readExistingPluginPolicyRepairs(config) {
|
|
1886
|
+
if (config === void 0) return {};
|
|
2105
1887
|
return Object.fromEntries(Object.entries(readExistingCodexPluginEntries(config)).flatMap(([configKey, entry]) => {
|
|
2106
1888
|
const pluginEntry = isRecord(entry) ? entry : void 0;
|
|
2107
1889
|
if (pluginEntry?.allow_destructive_actions !== "on-request") return [];
|
|
@@ -2111,9 +1893,9 @@ function readExistingPluginPolicyRepairs(config) {
|
|
|
2111
1893
|
}]];
|
|
2112
1894
|
}));
|
|
2113
1895
|
}
|
|
2114
|
-
function buildCodexPluginsConfigValue(entries,
|
|
1896
|
+
function buildCodexPluginsConfigValue(entries, params = {}) {
|
|
2115
1897
|
const plugins = {
|
|
2116
|
-
...readExistingPluginPolicyRepairs(config),
|
|
1898
|
+
...readExistingPluginPolicyRepairs(params.config),
|
|
2117
1899
|
...Object.fromEntries(entries.toSorted((a, b) => a.configKey.localeCompare(b.configKey)).map((entry) => [entry.configKey, {
|
|
2118
1900
|
enabled: entry.enabled,
|
|
2119
1901
|
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
|
@@ -2125,7 +1907,7 @@ function buildCodexPluginsConfigValue(entries, config) {
|
|
|
2125
1907
|
enabled: true,
|
|
2126
1908
|
config: { codexPlugins: {
|
|
2127
1909
|
enabled: true,
|
|
2128
|
-
allow_destructive_actions: readExistingAllowDestructiveActions(config) ?? true,
|
|
1910
|
+
allow_destructive_actions: params.config === void 0 ? true : readExistingAllowDestructiveActions(params.config) ?? true,
|
|
2129
1911
|
plugins
|
|
2130
1912
|
} }
|
|
2131
1913
|
};
|
|
@@ -2152,7 +1934,7 @@ function hasCodexPluginConfigConflict(config, value) {
|
|
|
2152
1934
|
function buildPluginConfigItem(ctx, pluginItems) {
|
|
2153
1935
|
const entries = pluginItems.filter((item) => item.status === "planned").map((item) => readCodexPluginMigrationConfigEntry(item, true)).filter((entry) => entry !== void 0);
|
|
2154
1936
|
if (entries.length === 0) return;
|
|
2155
|
-
const value = buildCodexPluginsConfigValue(entries, ctx.config);
|
|
1937
|
+
const value = buildCodexPluginsConfigValue(entries, { config: ctx.config });
|
|
2156
1938
|
const conflict = !ctx.overwrite && hasCodexPluginConfigConflict(ctx.config, value);
|
|
2157
1939
|
return createMigrationItem({
|
|
2158
1940
|
id: CODEX_PLUGIN_CONFIG_ITEM_ID,
|
|
@@ -2161,7 +1943,6 @@ function buildPluginConfigItem(ctx, pluginItems) {
|
|
|
2161
1943
|
target: "plugins.entries.codex.config.codexPlugins",
|
|
2162
1944
|
status: conflict ? "conflict" : "planned",
|
|
2163
1945
|
reason: conflict ? MIGRATION_REASON_TARGET_EXISTS : void 0,
|
|
2164
|
-
applyPhase: "after-promotion",
|
|
2165
1946
|
message: "Enable OpenClaw's Codex plugin integration and record migrated source-installed curated plugins.",
|
|
2166
1947
|
details: {
|
|
2167
1948
|
path: [...CODEX_PLUGIN_CONFIG_PATH],
|
|
@@ -2171,44 +1952,35 @@ function buildPluginConfigItem(ctx, pluginItems) {
|
|
|
2171
1952
|
}
|
|
2172
1953
|
async function buildCodexMigrationPlan(ctx) {
|
|
2173
1954
|
const targets = resolveCodexMigrationTargets(ctx);
|
|
2174
|
-
const memoryOnly = ctx.itemKinds !== void 0 && ctx.itemKinds.length > 0 && ctx.itemKinds.every((kind) => kind === "memory");
|
|
2175
1955
|
const source = await discoverCodexSource({
|
|
2176
1956
|
input: ctx.source,
|
|
2177
|
-
|
|
2178
|
-
evaluatePluginMigrationEligibility: !memoryOnly,
|
|
1957
|
+
evaluatePluginMigrationEligibility: true,
|
|
2179
1958
|
verifyPluginApps: shouldVerifyPluginApps(ctx)
|
|
2180
1959
|
});
|
|
2181
1960
|
if (!hasCodexSource(source)) throw new Error(`Codex state was not found at ${source.root}. Pass --from <path> if it lives elsewhere.`);
|
|
2182
1961
|
const items = [];
|
|
2183
|
-
items.push(...await
|
|
2184
|
-
|
|
1962
|
+
items.push(...await buildCodexAuthItems({
|
|
1963
|
+
ctx,
|
|
1964
|
+
source,
|
|
1965
|
+
targets
|
|
1966
|
+
}));
|
|
1967
|
+
items.push(...await buildSkillItems({
|
|
1968
|
+
skills: source.skills,
|
|
2185
1969
|
workspaceDir: targets.workspaceDir,
|
|
2186
1970
|
overwrite: ctx.overwrite
|
|
2187
1971
|
}));
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
items.push(...pluginItems);
|
|
2201
|
-
const pluginConfigItem = buildPluginConfigItem(ctx, pluginItems);
|
|
2202
|
-
if (pluginConfigItem) items.push(pluginConfigItem);
|
|
2203
|
-
for (const archivePath of source.archivePaths) items.push(createMigrationItem({
|
|
2204
|
-
id: archivePath.id,
|
|
2205
|
-
kind: "archive",
|
|
2206
|
-
action: "archive",
|
|
2207
|
-
source: archivePath.path,
|
|
2208
|
-
message: archivePath.message ?? "Archived in the migration report for manual review; not imported into live config.",
|
|
2209
|
-
details: { archiveRelativePath: archivePath.relativePath }
|
|
2210
|
-
}));
|
|
2211
|
-
}
|
|
1972
|
+
const pluginItems = buildPluginItems(ctx, source.plugins);
|
|
1973
|
+
items.push(...pluginItems);
|
|
1974
|
+
const pluginConfigItem = buildPluginConfigItem(ctx, pluginItems);
|
|
1975
|
+
if (pluginConfigItem) items.push(pluginConfigItem);
|
|
1976
|
+
for (const archivePath of source.archivePaths) items.push(createMigrationItem({
|
|
1977
|
+
id: archivePath.id,
|
|
1978
|
+
kind: "archive",
|
|
1979
|
+
action: "archive",
|
|
1980
|
+
source: archivePath.path,
|
|
1981
|
+
message: archivePath.message ?? "Archived in the migration report for manual review; not imported into live config.",
|
|
1982
|
+
details: { archiveRelativePath: archivePath.relativePath }
|
|
1983
|
+
}));
|
|
2212
1984
|
const warnings = [
|
|
2213
1985
|
...!ctx.includeSecrets && items.some((item) => item.kind === "auth") ? ["Auth credentials were detected but skipped. Re-run interactively or pass --include-secrets to import supported credentials."] : [],
|
|
2214
1986
|
...items.some((item) => item.status === "conflict") ? ["Conflicts were found. Re-run with --overwrite to replace conflicting migration targets after item-level backups."] : [],
|
|
@@ -2222,7 +1994,7 @@ async function buildCodexMigrationPlan(ctx) {
|
|
|
2222
1994
|
summary: summarizeMigrationItems(items),
|
|
2223
1995
|
items,
|
|
2224
1996
|
warnings,
|
|
2225
|
-
nextSteps:
|
|
1997
|
+
nextSteps: ["Run openclaw doctor after applying the migration.", "Review skipped or auth-required Codex plugin/config/hook items before exposing them in OpenClaw sessions."],
|
|
2226
1998
|
metadata: {
|
|
2227
1999
|
agentDir: targets.agentDir,
|
|
2228
2000
|
codexHome: source.codexHome,
|
|
@@ -2278,9 +2050,14 @@ async function applyCodexMigrationPlan(params) {
|
|
|
2278
2050
|
const targets = resolveCodexMigrationTargets(params.ctx);
|
|
2279
2051
|
const codexHome = typeof plan.metadata?.codexHome === "string" && plan.metadata.codexHome.trim() ? plan.metadata.codexHome : plan.source;
|
|
2280
2052
|
const authSource = {
|
|
2053
|
+
root: plan.source,
|
|
2054
|
+
confidence: "high",
|
|
2281
2055
|
codexHome,
|
|
2282
2056
|
authPath: path.join(codexHome, "auth.json"),
|
|
2283
|
-
modelsCachePath: path.join(codexHome, "models_cache.json")
|
|
2057
|
+
modelsCachePath: path.join(codexHome, "models_cache.json"),
|
|
2058
|
+
skills: [],
|
|
2059
|
+
plugins: [],
|
|
2060
|
+
archivePaths: []
|
|
2284
2061
|
};
|
|
2285
2062
|
const runtime = withCachedMigrationConfigRuntime(params.ctx.runtime ?? params.runtime, params.ctx.config);
|
|
2286
2063
|
const applyCtx = {
|
|
@@ -2293,19 +2070,22 @@ async function applyCodexMigrationPlan(params) {
|
|
|
2293
2070
|
continue;
|
|
2294
2071
|
}
|
|
2295
2072
|
if (item.id === "config:codex-plugins") items.push(await applyCodexPluginConfigItem(applyCtx, item, items));
|
|
2296
|
-
else if (item.kind === "auth")
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2073
|
+
else if (item.kind === "auth") {
|
|
2074
|
+
const authItem = await applyCodexAuthItem({
|
|
2075
|
+
ctx: applyCtx,
|
|
2076
|
+
item,
|
|
2077
|
+
source: authSource,
|
|
2078
|
+
targets
|
|
2079
|
+
});
|
|
2080
|
+
items.push(authItem);
|
|
2081
|
+
items.push(...await buildCodexAuthConfigPatchItems({
|
|
2082
|
+
ctx: applyCtx,
|
|
2083
|
+
item: authItem,
|
|
2084
|
+
source: authSource
|
|
2085
|
+
}));
|
|
2086
|
+
} else if (item.kind === "plugin" && item.action === "install") items.push(await applyCodexPluginInstallItem(applyCtx, item));
|
|
2303
2087
|
else if (item.kind === "manual") items.push(applyMigrationManualItem(item));
|
|
2304
2088
|
else if (item.action === "archive") items.push(await archiveMigrationItem(item, reportDir));
|
|
2305
|
-
else if (item.kind === "memory") items.push(await copyMemoryMigrationFileItem(item, reportDir, {
|
|
2306
|
-
workspaceDir: targets.workspaceDir,
|
|
2307
|
-
overwrite: params.ctx.overwrite
|
|
2308
|
-
}));
|
|
2309
2089
|
else items.push(await copyMigrationFileItem(item, reportDir, { overwrite: params.ctx.overwrite }));
|
|
2310
2090
|
}
|
|
2311
2091
|
const result = {
|
|
@@ -2437,7 +2217,8 @@ async function requestTargetCodexAppServerJson(params) {
|
|
|
2437
2217
|
});
|
|
2438
2218
|
if (hasOpenAiCuratedMarketplace(lastResponse)) return lastResponse;
|
|
2439
2219
|
if (Date.now() >= discoveryDeadline) return lastResponse;
|
|
2440
|
-
|
|
2220
|
+
const waitMs = Math.min(TARGET_CODEX_MARKETPLACE_DISCOVERY_POLL_MS, discoveryDeadline - Date.now());
|
|
2221
|
+
await sleep(waitMs);
|
|
2441
2222
|
} while (Date.now() < discoveryDeadline);
|
|
2442
2223
|
return lastResponse;
|
|
2443
2224
|
}
|
|
@@ -2476,21 +2257,13 @@ async function buildTargetCodexPluginAppCacheKey(ctx) {
|
|
|
2476
2257
|
});
|
|
2477
2258
|
}
|
|
2478
2259
|
async function applyCodexPluginConfigItem(ctx, item, appliedItems) {
|
|
2479
|
-
if (appliedItems.filter((candidate) => candidate.kind === "plugin" && candidate.action === "install" && readCodexPluginPolicy(candidate) !== void 0 && !isCodexPluginConfigTerminal(candidate)).length > 0) return {
|
|
2480
|
-
...item,
|
|
2481
|
-
status: "warning",
|
|
2482
|
-
reason: "selected Codex plugin activation is incomplete"
|
|
2483
|
-
};
|
|
2484
2260
|
const entries = appliedItems.map(readAppliedPluginConfigEntry).filter((entry) => entry !== void 0);
|
|
2485
|
-
if (entries.length === 0) return
|
|
2486
|
-
...markMigrationItemSkipped(item, "no selected Codex plugins"),
|
|
2487
|
-
deferredCompletion: true
|
|
2488
|
-
};
|
|
2261
|
+
if (entries.length === 0) return markMigrationItemSkipped(item, "no selected Codex plugins");
|
|
2489
2262
|
const returnPatch = shouldReturnCodexPluginConfigPatch(ctx);
|
|
2490
|
-
const configApi =
|
|
2263
|
+
const configApi = ctx.runtime?.config;
|
|
2491
2264
|
const currentConfig = returnPatch ? ctx.config : configApi?.current?.();
|
|
2492
2265
|
if (!currentConfig) return markMigrationItemError(item, "config runtime unavailable");
|
|
2493
|
-
const value = buildCodexPluginsConfigValue(entries, currentConfig);
|
|
2266
|
+
const value = buildCodexPluginsConfigValue(entries, { config: currentConfig });
|
|
2494
2267
|
if (!ctx.overwrite && hasCodexPluginConfigConflict(currentConfig, value)) return markMigrationItemConflict(item, MIGRATION_REASON_TARGET_EXISTS);
|
|
2495
2268
|
const migratedItem = {
|
|
2496
2269
|
...item,
|
|
@@ -2518,42 +2291,65 @@ async function applyCodexPluginConfigItem(ctx, item, appliedItems) {
|
|
|
2518
2291
|
return markMigrationItemError(item, error instanceof Error ? error.message : String(error));
|
|
2519
2292
|
}
|
|
2520
2293
|
}
|
|
2521
|
-
function isCodexPluginConfigTerminal(item) {
|
|
2522
|
-
return item.status === "migrated" || item.status === "skipped" && (item.deferredCompletion === true || item.reason === CODEX_PLUGIN_NOT_SELECTED_REASON || item.reason === CODEX_PLUGIN_AUTH_REQUIRED_REASON);
|
|
2523
|
-
}
|
|
2524
2294
|
function readAppliedPluginConfigEntry(item) {
|
|
2525
|
-
if (item.status === "migrated"
|
|
2526
|
-
if (item.status === "skipped" && item.reason === CODEX_PLUGIN_AUTH_REQUIRED_REASON) return readCodexPluginMigrationConfigEntry(item, false);
|
|
2295
|
+
if (item.status === "migrated") return readCodexPluginMigrationConfigEntry(item, true);
|
|
2296
|
+
if (item.status === "skipped" && item.reason !== CODEX_PLUGIN_NOT_SELECTED_REASON && item.reason === CODEX_PLUGIN_AUTH_REQUIRED_REASON) return readCodexPluginMigrationConfigEntry(item, false);
|
|
2527
2297
|
}
|
|
2528
2298
|
function readCodexPluginPolicy(item) {
|
|
2529
|
-
const
|
|
2530
|
-
|
|
2299
|
+
const configKey = item.details?.configKey;
|
|
2300
|
+
const marketplaceName = item.details?.marketplaceName;
|
|
2301
|
+
const pluginName = item.details?.pluginName;
|
|
2302
|
+
if (typeof configKey !== "string" || marketplaceName !== "openai-curated" || typeof pluginName !== "string") return;
|
|
2531
2303
|
return {
|
|
2532
|
-
configKey
|
|
2304
|
+
configKey,
|
|
2533
2305
|
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
|
2534
|
-
pluginName
|
|
2306
|
+
pluginName,
|
|
2535
2307
|
enabled: true,
|
|
2536
2308
|
allowDestructiveActions: true,
|
|
2537
2309
|
destructiveApprovalMode: "allow"
|
|
2538
2310
|
};
|
|
2539
2311
|
}
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2312
|
+
function codexPluginActivationReportState(result) {
|
|
2313
|
+
switch (result.reason) {
|
|
2314
|
+
case "already_active":
|
|
2315
|
+
case "installed": return {
|
|
2316
|
+
installed: true,
|
|
2317
|
+
enabled: true
|
|
2318
|
+
};
|
|
2319
|
+
case "auth_required": return {
|
|
2320
|
+
installed: true,
|
|
2321
|
+
enabled: false
|
|
2322
|
+
};
|
|
2323
|
+
case "disabled":
|
|
2324
|
+
case "marketplace_missing":
|
|
2325
|
+
case "plugin_missing": return {
|
|
2326
|
+
installed: false,
|
|
2327
|
+
enabled: false
|
|
2328
|
+
};
|
|
2329
|
+
case "refresh_failed": return {
|
|
2330
|
+
installed: true,
|
|
2331
|
+
enabled: false
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
return result.reason;
|
|
2335
|
+
}
|
|
2336
|
+
function sanitizeAppsNeedingAuth(apps) {
|
|
2337
|
+
return apps.map((app) => ({
|
|
2338
|
+
id: app.id,
|
|
2339
|
+
name: app.name,
|
|
2340
|
+
needsAuth: app.needsAuth
|
|
2341
|
+
}));
|
|
2342
|
+
}
|
|
2343
|
+
//#endregion
|
|
2344
|
+
//#region extensions/codex/src/migration/provider.ts
|
|
2345
|
+
function buildCodexMigrationProvider(params = {}) {
|
|
2346
|
+
return {
|
|
2347
|
+
id: "codex",
|
|
2548
2348
|
label: "Codex",
|
|
2549
|
-
description: "
|
|
2550
|
-
supportedItemKinds: ["memory"],
|
|
2349
|
+
description: "Inventory and promote Codex CLI skills while keeping Codex native plugins and hooks explicit.",
|
|
2551
2350
|
async detect(ctx) {
|
|
2552
|
-
const source = await discoverCodexSource({
|
|
2553
|
-
|
|
2554
|
-
memoryOnly: isMemoryOnlyMigration(ctx)
|
|
2555
|
-
});
|
|
2556
|
-
const found = isMemoryOnlyMigration(ctx) ? source.memoryFiles.length > 0 : hasCodexSource(source);
|
|
2351
|
+
const source = await discoverCodexSource({ input: ctx.source });
|
|
2352
|
+
const found = hasCodexSource(source);
|
|
2557
2353
|
return {
|
|
2558
2354
|
found,
|
|
2559
2355
|
source: source.root,
|
|
@@ -2563,9 +2359,7 @@ function buildCodexMigrationProvider(params = {}) {
|
|
|
2563
2359
|
};
|
|
2564
2360
|
},
|
|
2565
2361
|
plan: buildCodexMigrationPlan,
|
|
2566
|
-
deferredApply: { retrySafe: true },
|
|
2567
2362
|
prepareApply(ctx) {
|
|
2568
|
-
if (isMemoryOnlyMigration(ctx)) return;
|
|
2569
2363
|
return prepareTargetCodexAppServer(ctx);
|
|
2570
2364
|
},
|
|
2571
2365
|
async apply(ctx, plan) {
|
|
@@ -2627,7 +2421,7 @@ const CodexThreadsParamsSchema = Type.Union([
|
|
|
2627
2421
|
ArchiveParamsSchema,
|
|
2628
2422
|
UnarchiveParamsSchema
|
|
2629
2423
|
]);
|
|
2630
|
-
function asRecord
|
|
2424
|
+
function asRecord(value) {
|
|
2631
2425
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2632
2426
|
}
|
|
2633
2427
|
function readBoolean(value, fallback = false) {
|
|
@@ -2646,9 +2440,13 @@ function resolveToolSession(context, runtime) {
|
|
|
2646
2440
|
});
|
|
2647
2441
|
const sessionId = context.sessionId?.trim() || entry?.sessionId?.trim();
|
|
2648
2442
|
if (!sessionId) return;
|
|
2443
|
+
const storePath = runtime.agent.session.resolveStorePath(void 0, { agentId: context.agentId });
|
|
2649
2444
|
return {
|
|
2650
2445
|
sessionId,
|
|
2651
|
-
|
|
2446
|
+
sessionFile: runtime.agent.session.resolveSessionFilePath(sessionId, entry, {
|
|
2447
|
+
agentId: context.agentId,
|
|
2448
|
+
sessionsDir: path.dirname(storePath)
|
|
2449
|
+
})
|
|
2652
2450
|
};
|
|
2653
2451
|
}
|
|
2654
2452
|
function readThreadId(params) {
|
|
@@ -2661,106 +2459,42 @@ function readThreadStatusType(value) {
|
|
|
2661
2459
|
if (!isJsonObject(value) || !isJsonObject(value.thread) || !isJsonObject(value.thread.status)) return;
|
|
2662
2460
|
return typeof value.thread.status.type === "string" ? value.thread.status.type : void 0;
|
|
2663
2461
|
}
|
|
2664
|
-
|
|
2665
|
-
if (!isJsonObject(value) || !isJsonObject(value.thread)) throw new Error("Codex app-server returned an invalid thread/read response");
|
|
2666
|
-
if (value.thread.id !== expectedThreadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
2667
|
-
const status = readThreadStatusType(value);
|
|
2668
|
-
if (status === "active") throw new Error("cannot archive an active Codex thread; wait for its turn to finish");
|
|
2669
|
-
if (status !== "idle" && status !== "notLoaded") throw new Error("cannot verify that the Codex thread is idle; refusing to archive");
|
|
2670
|
-
}
|
|
2671
|
-
function assertThreadMayBeForked(value, expectedThreadId) {
|
|
2672
|
-
if (!isJsonObject(value) || !isJsonObject(value.thread)) throw new Error("Codex app-server returned an invalid thread/read response");
|
|
2673
|
-
if (value.thread.id !== expectedThreadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
2674
|
-
const status = readThreadStatusType(value);
|
|
2675
|
-
if (status !== "idle" && status !== "notLoaded") throw new Error("cannot fork a Codex thread unless it is idle or not loaded");
|
|
2676
|
-
}
|
|
2677
|
-
function redactNativeThreadTranscriptFields(value) {
|
|
2678
|
-
if (!isJsonObject(value)) return value;
|
|
2679
|
-
const redacted = { ...value };
|
|
2680
|
-
delete redacted.preview;
|
|
2681
|
-
delete redacted.turns;
|
|
2682
|
-
return redacted;
|
|
2683
|
-
}
|
|
2684
|
-
function redactNativeThreadResponse(value) {
|
|
2685
|
-
if (!isJsonObject(value)) return value;
|
|
2686
|
-
const redacted = { ...value };
|
|
2687
|
-
if (Array.isArray(redacted.data)) redacted.data = redacted.data.map(redactNativeThreadTranscriptFields);
|
|
2688
|
-
if (isJsonObject(redacted.thread)) redacted.thread = redactNativeThreadTranscriptFields(redacted.thread);
|
|
2689
|
-
return redacted;
|
|
2690
|
-
}
|
|
2691
|
-
/** Builds the native Codex thread tool only for owner runs with native-home access. */
|
|
2462
|
+
/** Builds the native Codex thread tool only for owner runs in shared-user-home mode. */
|
|
2692
2463
|
function createCodexThreadsTool(options) {
|
|
2693
2464
|
if (options.context.senderIsOwner !== true) return null;
|
|
2694
|
-
|
|
2695
|
-
if (configured.appServer?.homeScope !== "user" && configured.supervision?.enabled !== true) return null;
|
|
2465
|
+
if (readCodexPluginConfig(options.getPluginConfig()).appServer?.homeScope !== "user") return null;
|
|
2696
2466
|
const request = options.request ?? codexControlRequest;
|
|
2697
|
-
const
|
|
2698
|
-
const baseRequestOptions = () => ({
|
|
2467
|
+
const requestOptions = () => ({
|
|
2699
2468
|
agentDir: options.context.agentDir,
|
|
2700
|
-
config:
|
|
2469
|
+
config: options.context.getRuntimeConfig?.() ?? options.context.runtimeConfig ?? options.context.config,
|
|
2701
2470
|
sessionId: options.context.sessionId,
|
|
2702
2471
|
sessionKey: options.context.sessionKey
|
|
2703
2472
|
});
|
|
2704
2473
|
const currentSession = () => resolveToolSession(options.context, options.runtime);
|
|
2705
|
-
const currentIdentity = (sessionId) =>
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
const requestOptions = async (pluginConfig) => {
|
|
2713
|
-
const plugin = readCodexPluginConfig(pluginConfig);
|
|
2714
|
-
const session = currentSession();
|
|
2715
|
-
const binding = await currentBinding(session);
|
|
2716
|
-
if (binding?.connectionScope === "supervision") {
|
|
2717
|
-
const connection = resolveCodexBindingAppServerConnection({
|
|
2718
|
-
binding,
|
|
2719
|
-
pluginConfig
|
|
2720
|
-
});
|
|
2721
|
-
return {
|
|
2722
|
-
...baseRequestOptions(),
|
|
2723
|
-
startOptions: connection.appServer.start,
|
|
2724
|
-
authProfileId: connection.clientAuthProfileId
|
|
2725
|
-
};
|
|
2726
|
-
}
|
|
2727
|
-
if (plugin.appServer?.homeScope === "user") {
|
|
2728
|
-
const connection = resolveCodexBindingAppServerConnection({
|
|
2729
|
-
binding,
|
|
2730
|
-
pluginConfig
|
|
2731
|
-
});
|
|
2732
|
-
return {
|
|
2733
|
-
...baseRequestOptions(),
|
|
2734
|
-
startOptions: connection.appServer.start,
|
|
2735
|
-
authProfileId: null
|
|
2736
|
-
};
|
|
2737
|
-
}
|
|
2738
|
-
if (plugin.supervision?.enabled !== true) throw new Error("Codex native thread access is disabled for this run.");
|
|
2739
|
-
return {
|
|
2740
|
-
...baseRequestOptions(),
|
|
2741
|
-
startOptions: resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig }).start,
|
|
2742
|
-
authProfileId: null
|
|
2743
|
-
};
|
|
2474
|
+
const currentIdentity = (sessionId) => {
|
|
2475
|
+
return sessionBindingIdentity({
|
|
2476
|
+
sessionId,
|
|
2477
|
+
sessionKey: options.context.sessionKey,
|
|
2478
|
+
agentId: options.context.agentId,
|
|
2479
|
+
config: requestOptions().config
|
|
2480
|
+
});
|
|
2744
2481
|
};
|
|
2482
|
+
const currentBinding = async (session) => session ? await options.bindingStore.read(currentIdentity(session.sessionId)) : void 0;
|
|
2745
2483
|
return {
|
|
2746
2484
|
name: "codex_threads",
|
|
2747
2485
|
label: "Codex Threads",
|
|
2748
|
-
description: "
|
|
2486
|
+
description: "List, read, fork, rename, archive, or restore native Codex threads. Fork to continue a thread safely across Codex clients; do not resume the same thread from two clients.",
|
|
2749
2487
|
parameters: CodexThreadsParamsSchema,
|
|
2750
2488
|
async execute(_toolCallId, rawParams) {
|
|
2751
|
-
const params = asRecord
|
|
2489
|
+
const params = asRecord(rawParams);
|
|
2752
2490
|
const action = readStringParam(params, "action", {
|
|
2753
2491
|
required: true,
|
|
2754
2492
|
label: "action"
|
|
2755
2493
|
});
|
|
2756
2494
|
const pluginConfig = options.getPluginConfig();
|
|
2757
|
-
const plugin = readCodexPluginConfig(pluginConfig);
|
|
2758
|
-
const supervision = plugin.supervision;
|
|
2759
|
-
const mayReadRawTranscripts = supervision?.enabled !== true || supervision.allowRawTranscripts === true;
|
|
2760
2495
|
if (action === "list") {
|
|
2761
2496
|
const cursor = readStringParam(params, "cursor");
|
|
2762
2497
|
const searchTerm = readStringParam(params, "search");
|
|
2763
|
-
if (searchTerm && !mayReadRawTranscripts) throw new Error("Codex native thread search is disabled while raw transcript access is disabled.");
|
|
2764
2498
|
const response = await request(pluginConfig, CODEX_CONTROL_METHODS.listThreads, {
|
|
2765
2499
|
archived: readBoolean(params.archived),
|
|
2766
2500
|
limit: readLimit(params.limit) ?? 20,
|
|
@@ -2770,20 +2504,17 @@ function createCodexThreadsTool(options) {
|
|
|
2770
2504
|
sourceKinds: [...CODEX_INTERACTIVE_THREAD_SOURCE_KINDS],
|
|
2771
2505
|
...cursor ? { cursor } : {},
|
|
2772
2506
|
...searchTerm ? { searchTerm } : {}
|
|
2773
|
-
},
|
|
2774
|
-
return jsonResult(
|
|
2507
|
+
}, requestOptions());
|
|
2508
|
+
return jsonResult(response);
|
|
2775
2509
|
}
|
|
2776
2510
|
const threadId = readThreadId(params);
|
|
2777
2511
|
if (action === "read") {
|
|
2778
|
-
const includeTurns = readBoolean(params.include_turns);
|
|
2779
|
-
if (includeTurns && !mayReadRawTranscripts) throw new Error("Codex raw transcript reads are disabled for this codex plugin supervision config.");
|
|
2780
2512
|
const response = await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2781
2513
|
threadId,
|
|
2782
|
-
includeTurns
|
|
2783
|
-
},
|
|
2784
|
-
return jsonResult(
|
|
2514
|
+
includeTurns: readBoolean(params.include_turns)
|
|
2515
|
+
}, requestOptions());
|
|
2516
|
+
return jsonResult(response);
|
|
2785
2517
|
}
|
|
2786
|
-
if ((action === "fork" || action === "rename" || action === "archive" || action === "unarchive") && supervision?.enabled === true && supervision.allowWriteControls !== true) throw new Error("Codex native thread mutations are disabled for this codex plugin supervision config.");
|
|
2787
2518
|
if (action === "rename") {
|
|
2788
2519
|
const name = readStringParam(params, "name", {
|
|
2789
2520
|
required: true,
|
|
@@ -2792,7 +2523,7 @@ function createCodexThreadsTool(options) {
|
|
|
2792
2523
|
await request(pluginConfig, CODEX_CONTROL_METHODS.renameThread, {
|
|
2793
2524
|
threadId,
|
|
2794
2525
|
name
|
|
2795
|
-
},
|
|
2526
|
+
}, requestOptions());
|
|
2796
2527
|
return jsonResult({
|
|
2797
2528
|
action,
|
|
2798
2529
|
threadId,
|
|
@@ -2800,42 +2531,23 @@ function createCodexThreadsTool(options) {
|
|
|
2800
2531
|
});
|
|
2801
2532
|
}
|
|
2802
2533
|
if (action === "unarchive") {
|
|
2803
|
-
const response = await request(pluginConfig, CODEX_CONTROL_METHODS.unarchiveThread, { threadId },
|
|
2804
|
-
return jsonResult(
|
|
2534
|
+
const response = await request(pluginConfig, CODEX_CONTROL_METHODS.unarchiveThread, { threadId }, requestOptions());
|
|
2535
|
+
return jsonResult(response);
|
|
2805
2536
|
}
|
|
2806
2537
|
const session = currentSession();
|
|
2807
2538
|
const binding = await currentBinding(session);
|
|
2808
2539
|
if (action === "archive") {
|
|
2809
2540
|
if (params.confirm !== true) throw new Error("confirm=true is required to archive a native Codex thread");
|
|
2810
|
-
if (
|
|
2811
|
-
|
|
2812
|
-
await options.bindingStore.withThreadArchiveFence(async () => {
|
|
2813
|
-
const archivedBinding = await currentBinding(session);
|
|
2814
|
-
if (archivedBinding?.threadId === threadId) {
|
|
2815
|
-
if (session.modelSelectionLocked) throw new ModelSelectionLockedError();
|
|
2816
|
-
assertCodexBindingMayBeReplaced(archivedBinding, "archiving its bound native thread");
|
|
2817
|
-
}
|
|
2818
|
-
assertThreadMayBeArchived(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2541
|
+
if (binding?.threadId === threadId) {
|
|
2542
|
+
if (readThreadStatusType(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2819
2543
|
threadId,
|
|
2820
2544
|
includeTurns: false
|
|
2821
|
-
},
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
assertDescendantIdle: async (descendantThreadId) => {
|
|
2828
|
-
assertThreadMayBeArchived(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2829
|
-
threadId: descendantThreadId,
|
|
2830
|
-
includeTurns: false
|
|
2831
|
-
}, await requestOptions(pluginConfig)), descendantThreadId);
|
|
2832
|
-
}
|
|
2833
|
-
});
|
|
2834
|
-
await request(pluginConfig, CODEX_CONTROL_METHODS.archiveThread, { threadId }, await requestOptions(pluginConfig));
|
|
2835
|
-
if (archivedBinding?.threadId === threadId) await options.bindingStore.mutate(identity, {
|
|
2836
|
-
kind: "clear",
|
|
2837
|
-
threadId
|
|
2838
|
-
});
|
|
2545
|
+
}, requestOptions())) === "active") throw new Error("cannot archive the Codex thread active in this OpenClaw session");
|
|
2546
|
+
}
|
|
2547
|
+
await request(pluginConfig, CODEX_CONTROL_METHODS.archiveThread, { threadId }, requestOptions());
|
|
2548
|
+
if (session && binding?.threadId === threadId) await options.bindingStore.mutate(currentIdentity(session.sessionId), {
|
|
2549
|
+
kind: "clear",
|
|
2550
|
+
threadId
|
|
2839
2551
|
});
|
|
2840
2552
|
return jsonResult({
|
|
2841
2553
|
action,
|
|
@@ -2845,21 +2557,16 @@ function createCodexThreadsTool(options) {
|
|
|
2845
2557
|
if (action !== "fork") throw new Error(`unsupported codex_threads action: ${action}`);
|
|
2846
2558
|
const attach = readBoolean(params.attach, true);
|
|
2847
2559
|
if (attach && !session) throw new Error("cannot attach a Codex fork without an active OpenClaw session");
|
|
2848
|
-
if (attach &&
|
|
2849
|
-
|
|
2850
|
-
if (attach && usesSupervisionConnection) throw new Error("Supervised Codex forks must stay detached; set attach=false.");
|
|
2851
|
-
if (attach) {
|
|
2852
|
-
assertCodexBindingMayBeReplaced(binding, "attaching a different native fork");
|
|
2853
|
-
assertThreadMayBeForked(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2560
|
+
if (attach && binding?.threadId === threadId) {
|
|
2561
|
+
if (readThreadStatusType(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2854
2562
|
threadId,
|
|
2855
2563
|
includeTurns: false
|
|
2856
|
-
},
|
|
2564
|
+
}, requestOptions())) === "active") throw new Error("cannot replace the Codex thread active in this OpenClaw turn");
|
|
2857
2565
|
}
|
|
2858
2566
|
const response = await request(pluginConfig, CODEX_CONTROL_METHODS.forkThread, {
|
|
2859
2567
|
threadId,
|
|
2860
|
-
threadSource: "user"
|
|
2861
|
-
|
|
2862
|
-
}, await requestOptions(pluginConfig));
|
|
2568
|
+
threadSource: "user"
|
|
2569
|
+
}, requestOptions());
|
|
2863
2570
|
if (!isJsonObject(response) || !isJsonObject(response.thread)) throw new Error("Codex app-server returned an invalid thread/fork response");
|
|
2864
2571
|
const forkThreadId = typeof response.thread.id === "string" && response.thread.id.trim() ? response.thread.id : void 0;
|
|
2865
2572
|
if (!forkThreadId) throw new Error("Codex app-server thread/fork response did not include a thread id");
|
|
@@ -2875,802 +2582,18 @@ function createCodexThreadsTool(options) {
|
|
|
2875
2582
|
}
|
|
2876
2583
|
})) throw new Error("Codex session binding changed before the fork could be attached");
|
|
2877
2584
|
}
|
|
2878
|
-
|
|
2585
|
+
return jsonResult({
|
|
2879
2586
|
action,
|
|
2880
2587
|
sourceThreadId: threadId,
|
|
2881
2588
|
thread: response.thread,
|
|
2882
2589
|
attached: attach
|
|
2883
|
-
};
|
|
2884
|
-
return jsonResult(mayReadRawTranscripts ? result : redactNativeThreadResponse(result));
|
|
2885
|
-
}
|
|
2886
|
-
};
|
|
2887
|
-
}
|
|
2888
|
-
//#endregion
|
|
2889
|
-
//#region extensions/codex/src/supervision-tools.ts
|
|
2890
|
-
/**
|
|
2891
|
-
* Compatibility tools for the retired Codex Supervisor plugin.
|
|
2892
|
-
*
|
|
2893
|
-
* Read operations and active-turn controls use the Codex plugin's canonical
|
|
2894
|
-
* shared app-server client. Idle threads are never resumed or started here:
|
|
2895
|
-
* continuation belongs to the Codex harness, which installs approval and tool
|
|
2896
|
-
* handlers before it starts or resumes the harness-owned Codex thread.
|
|
2897
|
-
*/
|
|
2898
|
-
/** Legacy endpoint env retained for the shipped Supervisor tool contract. */
|
|
2899
|
-
const LEGACY_CODEX_SUPERVISOR_ENDPOINTS_ENV = "OPENCLAW_CODEX_SUPERVISOR_ENDPOINTS";
|
|
2900
|
-
/** Legacy standalone-MCP transcript gate. Agent tools use canonical config. */
|
|
2901
|
-
const LEGACY_CODEX_SUPERVISOR_RAW_TRANSCRIPTS_ENV = "OPENCLAW_CODEX_SUPERVISOR_ALLOW_RAW_TRANSCRIPTS";
|
|
2902
|
-
/** Legacy standalone-MCP write gate. Agent tools use canonical config. */
|
|
2903
|
-
const LEGACY_CODEX_SUPERVISOR_WRITE_CONTROLS_ENV = "OPENCLAW_CODEX_SUPERVISOR_ALLOW_WRITE_CONTROLS";
|
|
2904
|
-
const CODEX_SUPERVISION_COMPAT_TOOL_NAMES = [
|
|
2905
|
-
"codex_endpoint_probe",
|
|
2906
|
-
"codex_sessions_list",
|
|
2907
|
-
"codex_session_read",
|
|
2908
|
-
"codex_session_send",
|
|
2909
|
-
"codex_session_interrupt"
|
|
2910
|
-
];
|
|
2911
|
-
const EmptyParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
2912
|
-
const SessionsListParamsSchema = Type.Object({
|
|
2913
|
-
include_stored: Type.Optional(Type.Boolean()),
|
|
2914
|
-
max_stored_sessions: Type.Optional(Type.Integer({
|
|
2915
|
-
minimum: 1,
|
|
2916
|
-
maximum: 1e3
|
|
2917
|
-
}))
|
|
2918
|
-
}, { additionalProperties: false });
|
|
2919
|
-
const SessionReadParamsSchema = Type.Object({
|
|
2920
|
-
endpoint_id: Type.Optional(Type.String()),
|
|
2921
|
-
thread_id: Type.String(),
|
|
2922
|
-
include_turns: Type.Optional(Type.Boolean())
|
|
2923
|
-
}, { additionalProperties: false });
|
|
2924
|
-
const SessionSendParamsSchema = Type.Object({
|
|
2925
|
-
endpoint_id: Type.Optional(Type.String()),
|
|
2926
|
-
thread_id: Type.String(),
|
|
2927
|
-
text: Type.String(),
|
|
2928
|
-
mode: Type.Optional(Type.Union([
|
|
2929
|
-
Type.Literal("auto"),
|
|
2930
|
-
Type.Literal("start"),
|
|
2931
|
-
Type.Literal("steer")
|
|
2932
|
-
]))
|
|
2933
|
-
}, { additionalProperties: false });
|
|
2934
|
-
const SessionInterruptParamsSchema = Type.Object({
|
|
2935
|
-
endpoint_id: Type.Optional(Type.String()),
|
|
2936
|
-
thread_id: Type.String(),
|
|
2937
|
-
turn_id: Type.Optional(Type.String())
|
|
2938
|
-
}, { additionalProperties: false });
|
|
2939
|
-
const ALL_CODEX_THREAD_SOURCE_KINDS = [
|
|
2940
|
-
"cli",
|
|
2941
|
-
"vscode",
|
|
2942
|
-
"exec",
|
|
2943
|
-
"appServer",
|
|
2944
|
-
"subAgent",
|
|
2945
|
-
"subAgentReview",
|
|
2946
|
-
"subAgentCompact",
|
|
2947
|
-
"subAgentThreadSpawn",
|
|
2948
|
-
"subAgentOther",
|
|
2949
|
-
"unknown"
|
|
2950
|
-
];
|
|
2951
|
-
const DEFAULT_MAX_STORED_SESSIONS = 200;
|
|
2952
|
-
const PAGE_LIMIT = 100;
|
|
2953
|
-
const MAX_COMPAT_PAGINATION_PAGES = 100;
|
|
2954
|
-
const MAX_COMPAT_CURSOR_LENGTH = 4096;
|
|
2955
|
-
const MAX_COMPAT_THREAD_ID_LENGTH = 4096;
|
|
2956
|
-
var CodexSupervisionPolicyError = class extends Error {};
|
|
2957
|
-
function asRecord(value) {
|
|
2958
|
-
return isRecord(value) ? value : {};
|
|
2959
|
-
}
|
|
2960
|
-
function asRecordArray(value) {
|
|
2961
|
-
return Array.isArray(value) ? value.filter(isRecord) : [];
|
|
2962
|
-
}
|
|
2963
|
-
function readCompatNextCursor(value, method) {
|
|
2964
|
-
if (value === null || value === void 0) return;
|
|
2965
|
-
if (typeof value !== "string" || value.trim().length === 0 || value.length > MAX_COMPAT_CURSOR_LENGTH) throw new Error(`Codex ${method} returned an invalid nextCursor`);
|
|
2966
|
-
return value;
|
|
2967
|
-
}
|
|
2968
|
-
function readCompatThreadId(value, method, index) {
|
|
2969
|
-
if (typeof value !== "string" || value.trim().length === 0 || value.length > MAX_COMPAT_THREAD_ID_LENGTH) throw new Error(`Codex ${method} returned an invalid thread id at data[${index}]`);
|
|
2970
|
-
return value;
|
|
2971
|
-
}
|
|
2972
|
-
function readLoadedThreadIds(data) {
|
|
2973
|
-
if (data.length > PAGE_LIMIT) throw new Error(`Codex thread/loaded/list returned more than ${PAGE_LIMIT} entries`);
|
|
2974
|
-
return data.map((entry, index) => readCompatThreadId(entry, "thread/loaded/list", index));
|
|
2975
|
-
}
|
|
2976
|
-
function readStoredThreads(data, maxEntries) {
|
|
2977
|
-
if (data.length > maxEntries) throw new Error(`Codex thread/list returned more than ${maxEntries} entries`);
|
|
2978
|
-
return data.map((entry, index) => {
|
|
2979
|
-
if (!isRecord(entry)) throw new Error(`Codex thread/list returned an invalid entry at data[${index}]`);
|
|
2980
|
-
readCompatThreadId(entry.id, "thread/list", index);
|
|
2981
|
-
return entry;
|
|
2982
|
-
});
|
|
2983
|
-
}
|
|
2984
|
-
function readBooleanParam(params, key) {
|
|
2985
|
-
return params[key] === true;
|
|
2986
|
-
}
|
|
2987
|
-
function readIntegerParam(params, key) {
|
|
2988
|
-
const value = params[key];
|
|
2989
|
-
if (value === void 0) return;
|
|
2990
|
-
if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${key} must be an integer`);
|
|
2991
|
-
if (value < 1 || value > 1e3) throw new Error(`${key} must be between 1 and 1000`);
|
|
2992
|
-
return value;
|
|
2993
|
-
}
|
|
2994
|
-
function readModeParam(params) {
|
|
2995
|
-
const mode = readStringParam(params, "mode");
|
|
2996
|
-
if (!mode) return;
|
|
2997
|
-
if (mode === "auto" || mode === "start" || mode === "steer") return mode;
|
|
2998
|
-
throw new Error("mode must be auto, start, or steer");
|
|
2999
|
-
}
|
|
3000
|
-
function normalizeEndpointId(value, index) {
|
|
3001
|
-
const trimmed = value.trim();
|
|
3002
|
-
return trimmed ? trimmed.replace(/[^a-zA-Z0-9_.:-]/g, "-") : `endpoint-${index + 1}`;
|
|
3003
|
-
}
|
|
3004
|
-
function normalizeConfiguredEndpoint(endpoint, index) {
|
|
3005
|
-
return {
|
|
3006
|
-
id: normalizeEndpointId(endpoint.id ?? endpoint.label ?? "", index),
|
|
3007
|
-
...endpoint.label?.trim() ? { label: endpoint.label.trim() } : {},
|
|
3008
|
-
configured: endpoint
|
|
3009
|
-
};
|
|
3010
|
-
}
|
|
3011
|
-
function parseEndpointRecord(value) {
|
|
3012
|
-
if (!isRecord(value)) return;
|
|
3013
|
-
const transport = typeof value.transport === "string" ? value.transport : void 0;
|
|
3014
|
-
const common = {
|
|
3015
|
-
...typeof value.id === "string" ? { id: value.id } : {},
|
|
3016
|
-
...typeof value.label === "string" ? { label: value.label } : {}
|
|
3017
|
-
};
|
|
3018
|
-
if (transport === "websocket" && typeof value.url === "string") return {
|
|
3019
|
-
...common,
|
|
3020
|
-
transport,
|
|
3021
|
-
url: value.url,
|
|
3022
|
-
...typeof value.authTokenEnv === "string" ? { authTokenEnv: value.authTokenEnv } : {}
|
|
3023
|
-
};
|
|
3024
|
-
if (transport === "stdio-proxy" || transport === void 0) {
|
|
3025
|
-
const args = Array.isArray(value.args) ? value.args.filter((entry) => typeof entry === "string") : void 0;
|
|
3026
|
-
return {
|
|
3027
|
-
...common,
|
|
3028
|
-
transport: "stdio-proxy",
|
|
3029
|
-
...typeof value.command === "string" ? { command: value.command } : {},
|
|
3030
|
-
...args && args.length > 0 ? { args } : {},
|
|
3031
|
-
...typeof value.cwd === "string" ? { cwd: value.cwd } : {}
|
|
3032
|
-
};
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
|
-
function endpointFromToken(token, index) {
|
|
3036
|
-
const trimmed = token.trim();
|
|
3037
|
-
if (!trimmed) return;
|
|
3038
|
-
if (trimmed.startsWith("ws://") || trimmed.startsWith("wss://") || trimmed.startsWith("unix://")) return {
|
|
3039
|
-
id: normalizeEndpointId("", index),
|
|
3040
|
-
transport: "websocket",
|
|
3041
|
-
url: trimmed
|
|
3042
|
-
};
|
|
3043
|
-
if (trimmed === "local" || trimmed === "proxy" || trimmed === "stdio") return {
|
|
3044
|
-
id: "local",
|
|
3045
|
-
label: "local Codex app-server",
|
|
3046
|
-
transport: "stdio-proxy"
|
|
3047
|
-
};
|
|
3048
|
-
const separatorIndex = trimmed.indexOf("=");
|
|
3049
|
-
const id = separatorIndex >= 0 ? trimmed.slice(0, separatorIndex) : trimmed;
|
|
3050
|
-
const url = separatorIndex >= 0 ? trimmed.slice(separatorIndex + 1) : void 0;
|
|
3051
|
-
if (url?.startsWith("ws://") || url?.startsWith("wss://") || url?.startsWith("unix://")) return {
|
|
3052
|
-
id: normalizeEndpointId(id, index),
|
|
3053
|
-
transport: "websocket",
|
|
3054
|
-
url
|
|
3055
|
-
};
|
|
3056
|
-
}
|
|
3057
|
-
function requireUniqueEndpointIds(endpoints) {
|
|
3058
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3059
|
-
for (const endpoint of endpoints) {
|
|
3060
|
-
if (seen.has(endpoint.id)) throw new Error(`duplicate Codex supervisor endpoint id: ${endpoint.id}`);
|
|
3061
|
-
seen.add(endpoint.id);
|
|
3062
|
-
}
|
|
3063
|
-
return endpoints;
|
|
3064
|
-
}
|
|
3065
|
-
function readLegacyEnvEndpoints(env) {
|
|
3066
|
-
const raw = env[LEGACY_CODEX_SUPERVISOR_ENDPOINTS_ENV]?.trim();
|
|
3067
|
-
if (!raw) return;
|
|
3068
|
-
if (raw.startsWith("[")) {
|
|
3069
|
-
const parsed = JSON.parse(raw);
|
|
3070
|
-
if (!Array.isArray(parsed)) throw new Error(`${LEGACY_CODEX_SUPERVISOR_ENDPOINTS_ENV} must be a JSON array`);
|
|
3071
|
-
return parsed.map((entry) => parseEndpointRecord(entry)).filter((entry) => Boolean(entry));
|
|
3072
|
-
}
|
|
3073
|
-
return raw.split(",").map(endpointFromToken).filter((entry) => Boolean(entry));
|
|
3074
|
-
}
|
|
3075
|
-
function resolveEndpoints(pluginConfig, env, runtimeConfig) {
|
|
3076
|
-
const configured = readCodexPluginConfig(pluginConfig).supervision?.endpoints;
|
|
3077
|
-
const endpoints = configured?.length ? configured : readLegacyEnvEndpoints(env);
|
|
3078
|
-
return (endpoints ? requireUniqueEndpointIds(endpoints.map(normalizeConfiguredEndpoint)) : [{
|
|
3079
|
-
id: "local",
|
|
3080
|
-
label: "local Codex app-server"
|
|
3081
|
-
}]).map((endpoint) => {
|
|
3082
|
-
const resolved = {
|
|
3083
|
-
id: endpoint.id,
|
|
3084
|
-
connectionKey: supervisionEndpointConnectionKey({
|
|
3085
|
-
endpoint,
|
|
3086
|
-
pluginConfig,
|
|
3087
|
-
env,
|
|
3088
|
-
runtimeConfig
|
|
3089
|
-
})
|
|
3090
|
-
};
|
|
3091
|
-
if (endpoint.label !== void 0) resolved.label = endpoint.label;
|
|
3092
|
-
if (endpoint.configured !== void 0) resolved.configured = endpoint.configured;
|
|
3093
|
-
return resolved;
|
|
3094
|
-
});
|
|
3095
|
-
}
|
|
3096
|
-
function resolveEndpointStartOptions(params) {
|
|
3097
|
-
const base = resolveCodexSupervisionAppServerRuntimeOptions({
|
|
3098
|
-
pluginConfig: params.pluginConfig,
|
|
3099
|
-
env: params.env
|
|
3100
|
-
}).start;
|
|
3101
|
-
const configured = params.endpoint.configured;
|
|
3102
|
-
if (!configured) return base;
|
|
3103
|
-
if (!("url" in configured)) return {
|
|
3104
|
-
transport: "stdio",
|
|
3105
|
-
homeScope: "user",
|
|
3106
|
-
command: configured.command?.trim() || "codex",
|
|
3107
|
-
commandSource: "config",
|
|
3108
|
-
args: configured.args?.length ? [...configured.args] : [
|
|
3109
|
-
"app-server",
|
|
3110
|
-
"--listen",
|
|
3111
|
-
"stdio://"
|
|
3112
|
-
],
|
|
3113
|
-
...configured.cwd !== void 0 ? { cwd: configured.cwd } : {},
|
|
3114
|
-
headers: {}
|
|
3115
|
-
};
|
|
3116
|
-
const tokenEnv = configured.authTokenEnv?.trim();
|
|
3117
|
-
const authToken = tokenEnv ? params.env[tokenEnv]?.trim() : void 0;
|
|
3118
|
-
const startOptions = {
|
|
3119
|
-
transport: configured.url.startsWith("unix://") ? "unix" : "websocket",
|
|
3120
|
-
...configured.url.startsWith("unix://") ? { homeScope: "user" } : {},
|
|
3121
|
-
command: base.command,
|
|
3122
|
-
...base.commandSource ? { commandSource: base.commandSource } : {},
|
|
3123
|
-
...base.managedFallbackCommandPaths ? { managedFallbackCommandPaths: [...base.managedFallbackCommandPaths] } : {},
|
|
3124
|
-
args: [...base.args],
|
|
3125
|
-
url: configured.url,
|
|
3126
|
-
...authToken ? { authToken } : {},
|
|
3127
|
-
headers: {}
|
|
3128
|
-
};
|
|
3129
|
-
if (params.validateSecurity !== false) assertCodexAppServerConnectionSecurity(startOptions);
|
|
3130
|
-
return startOptions;
|
|
3131
|
-
}
|
|
3132
|
-
function supervisionEndpointConnectionKey(params) {
|
|
3133
|
-
const startOptions = resolveEndpointStartOptions({
|
|
3134
|
-
...params,
|
|
3135
|
-
validateSecurity: false
|
|
3136
|
-
});
|
|
3137
|
-
const usesNativeAuth = params.endpoint.configured !== void 0 || startOptions.homeScope === "user";
|
|
3138
|
-
const agentDir = usesNativeAuth ? void 0 : resolveDefaultAgentDir(params.runtimeConfig ?? {});
|
|
3139
|
-
const authProfileId = usesNativeAuth ? void 0 : resolveCodexAppServerAuthProfileIdForAgent({
|
|
3140
|
-
agentDir,
|
|
3141
|
-
config: params.runtimeConfig
|
|
3142
|
-
});
|
|
3143
|
-
const fallbackApiKeyCacheKey = authProfileId ? void 0 : resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions });
|
|
3144
|
-
return JSON.stringify({
|
|
3145
|
-
homeScope: startOptions.homeScope ?? null,
|
|
3146
|
-
startOptions: codexAppServerStartOptionsKey(startOptions, {
|
|
3147
|
-
authProfileId,
|
|
3148
|
-
agentDir,
|
|
3149
|
-
fallbackApiKeyCacheKey
|
|
3150
|
-
})
|
|
3151
|
-
});
|
|
3152
|
-
}
|
|
3153
|
-
function createCanonicalEndpointRequest(options) {
|
|
3154
|
-
return async (endpoint, method, requestParams) => {
|
|
3155
|
-
const pluginConfig = options.getPluginConfig();
|
|
3156
|
-
const env = options.env ?? process.env;
|
|
3157
|
-
const runtime = resolveCodexSupervisionAppServerRuntimeOptions({
|
|
3158
|
-
pluginConfig,
|
|
3159
|
-
env
|
|
3160
|
-
});
|
|
3161
|
-
const config = options.getRuntimeConfig?.();
|
|
3162
|
-
const startOptions = resolveEndpointStartOptions({
|
|
3163
|
-
endpoint,
|
|
3164
|
-
pluginConfig,
|
|
3165
|
-
env
|
|
3166
|
-
});
|
|
3167
|
-
return await requestCodexAppServerJson({
|
|
3168
|
-
method,
|
|
3169
|
-
requestParams,
|
|
3170
|
-
timeoutMs: runtime.requestTimeoutMs,
|
|
3171
|
-
startOptions,
|
|
3172
|
-
...endpoint.configured || startOptions.homeScope === "user" ? { authProfileId: null } : {},
|
|
3173
|
-
...config ? { config } : {}
|
|
3174
|
-
});
|
|
3175
|
-
};
|
|
3176
|
-
}
|
|
3177
|
-
function statusType(thread) {
|
|
3178
|
-
const status = isRecord(thread.status) ? thread.status.type : thread.status;
|
|
3179
|
-
return typeof status === "string" ? status : "unknown";
|
|
3180
|
-
}
|
|
3181
|
-
function sourceLabel(value) {
|
|
3182
|
-
if (typeof value === "string") return value;
|
|
3183
|
-
if (!isRecord(value)) return;
|
|
3184
|
-
if (typeof value.custom === "string") return `custom:${value.custom}`;
|
|
3185
|
-
return Object.keys(value).toSorted()[0];
|
|
3186
|
-
}
|
|
3187
|
-
function toSession(endpointId, thread, humanAttached) {
|
|
3188
|
-
if (typeof thread.id !== "string") return;
|
|
3189
|
-
const source = sourceLabel(thread.source);
|
|
3190
|
-
return {
|
|
3191
|
-
endpointId,
|
|
3192
|
-
threadId: thread.id,
|
|
3193
|
-
status: statusType(thread),
|
|
3194
|
-
...typeof thread.sessionId === "string" ? { sessionId: thread.sessionId } : {},
|
|
3195
|
-
...typeof thread.cwd === "string" ? { cwd: thread.cwd } : {},
|
|
3196
|
-
...typeof thread.preview === "string" ? { preview: thread.preview } : {},
|
|
3197
|
-
...typeof thread.name === "string" || thread.name === null ? { name: thread.name } : {},
|
|
3198
|
-
...source ? { source } : {},
|
|
3199
|
-
...typeof thread.updatedAt === "number" ? { updatedAt: thread.updatedAt } : {},
|
|
3200
|
-
...humanAttached !== void 0 ? { humanAttached } : {}
|
|
3201
|
-
};
|
|
3202
|
-
}
|
|
3203
|
-
function threadFromRead(value) {
|
|
3204
|
-
return isRecord(value) && isRecord(value.thread) ? value.thread : void 0;
|
|
3205
|
-
}
|
|
3206
|
-
function isLoadedThreadReadMiss(error) {
|
|
3207
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3208
|
-
return message.includes("thread not found") || message.includes("thread not loaded");
|
|
3209
|
-
}
|
|
3210
|
-
async function readThread(params) {
|
|
3211
|
-
try {
|
|
3212
|
-
const thread = threadFromRead(await params.request(params.endpoint, "thread/read", {
|
|
3213
|
-
threadId: params.threadId,
|
|
3214
|
-
includeTurns: params.includeTurns
|
|
3215
|
-
}));
|
|
3216
|
-
if (!thread) throw new Error("Codex thread/read returned an invalid response");
|
|
3217
|
-
return thread;
|
|
3218
|
-
} catch (error) {
|
|
3219
|
-
if (!params.includeTurns || !String(error).includes("not materialized yet")) throw error;
|
|
3220
|
-
const thread = threadFromRead(await params.request(params.endpoint, "thread/read", {
|
|
3221
|
-
threadId: params.threadId,
|
|
3222
|
-
includeTurns: false
|
|
3223
|
-
}));
|
|
3224
|
-
if (!thread) throw new Error("Codex thread/read returned an invalid response", { cause: error });
|
|
3225
|
-
return thread;
|
|
3226
|
-
}
|
|
3227
|
-
}
|
|
3228
|
-
async function listLoadedSessions(request, endpoint) {
|
|
3229
|
-
const sessions = [];
|
|
3230
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
3231
|
-
let cursor;
|
|
3232
|
-
for (let pageIndex = 0; pageIndex < MAX_COMPAT_PAGINATION_PAGES; pageIndex += 1) {
|
|
3233
|
-
const listed = await request(endpoint, "thread/loaded/list", {
|
|
3234
|
-
limit: PAGE_LIMIT,
|
|
3235
|
-
...cursor ? { cursor } : {}
|
|
3236
|
-
});
|
|
3237
|
-
if (!isRecord(listed) || !Array.isArray(listed.data)) throw new Error("Codex thread/loaded/list returned an invalid response");
|
|
3238
|
-
const threadIds = readLoadedThreadIds(listed.data);
|
|
3239
|
-
for (const threadId of threadIds) {
|
|
3240
|
-
if (sessions.some((entry) => entry.threadId === threadId)) continue;
|
|
3241
|
-
try {
|
|
3242
|
-
const thread = await readThread({
|
|
3243
|
-
request,
|
|
3244
|
-
endpoint,
|
|
3245
|
-
threadId,
|
|
3246
|
-
includeTurns: false
|
|
3247
|
-
});
|
|
3248
|
-
const session = toSession(endpoint.id, thread, true);
|
|
3249
|
-
if (session) sessions.push(session);
|
|
3250
|
-
} catch (error) {
|
|
3251
|
-
if (!isLoadedThreadReadMiss(error)) throw error;
|
|
3252
|
-
}
|
|
3253
|
-
}
|
|
3254
|
-
const nextCursor = readCompatNextCursor(listed.nextCursor, "thread/loaded/list");
|
|
3255
|
-
if (nextCursor && seenCursors.has(nextCursor)) throw new Error(`Codex thread/loaded/list returned repeated cursor ${nextCursor}`);
|
|
3256
|
-
if (nextCursor) seenCursors.add(nextCursor);
|
|
3257
|
-
cursor = nextCursor;
|
|
3258
|
-
if (!cursor) break;
|
|
3259
|
-
}
|
|
3260
|
-
if (cursor) throw new Error(`Codex thread/loaded/list exceeded ${MAX_COMPAT_PAGINATION_PAGES} pages with a continuation cursor`);
|
|
3261
|
-
return sessions;
|
|
3262
|
-
}
|
|
3263
|
-
async function listStoredSessions(params) {
|
|
3264
|
-
const sessions = [];
|
|
3265
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
3266
|
-
let cursor;
|
|
3267
|
-
for (let pageIndex = 0; pageIndex < MAX_COMPAT_PAGINATION_PAGES; pageIndex += 1) {
|
|
3268
|
-
const remaining = params.limit - sessions.length;
|
|
3269
|
-
if (remaining <= 0) break;
|
|
3270
|
-
const pageLimit = Math.min(PAGE_LIMIT, remaining);
|
|
3271
|
-
const listed = await params.request(params.endpoint, "thread/list", {
|
|
3272
|
-
archived: false,
|
|
3273
|
-
limit: pageLimit,
|
|
3274
|
-
sourceKinds: [...ALL_CODEX_THREAD_SOURCE_KINDS],
|
|
3275
|
-
modelProviders: [],
|
|
3276
|
-
sortKey: "recency_at",
|
|
3277
|
-
sortDirection: "desc",
|
|
3278
|
-
useStateDbOnly: true,
|
|
3279
|
-
...cursor ? { cursor } : {}
|
|
3280
|
-
});
|
|
3281
|
-
if (!isRecord(listed) || !Array.isArray(listed.data)) throw new Error("Codex thread/list returned an invalid response");
|
|
3282
|
-
for (const thread of readStoredThreads(listed.data, pageLimit)) {
|
|
3283
|
-
if (sessions.length >= params.limit) break;
|
|
3284
|
-
const session = toSession(params.endpoint.id, thread);
|
|
3285
|
-
if (session && !sessions.some((entry) => entry.threadId === session.threadId)) sessions.push(session);
|
|
3286
|
-
}
|
|
3287
|
-
const nextCursor = readCompatNextCursor(listed.nextCursor, "thread/list");
|
|
3288
|
-
if (nextCursor && sessions.length < params.limit && seenCursors.has(nextCursor)) throw new Error(`Codex thread/list returned repeated cursor ${nextCursor}`);
|
|
3289
|
-
if (nextCursor) seenCursors.add(nextCursor);
|
|
3290
|
-
cursor = nextCursor;
|
|
3291
|
-
if (!cursor || sessions.length >= params.limit) break;
|
|
3292
|
-
}
|
|
3293
|
-
if (cursor && sessions.length < params.limit) throw new Error(`Codex thread/list exceeded ${MAX_COMPAT_PAGINATION_PAGES} pages with a continuation cursor`);
|
|
3294
|
-
return sessions;
|
|
3295
|
-
}
|
|
3296
|
-
async function listSessionSnapshot(params) {
|
|
3297
|
-
const sessions = [];
|
|
3298
|
-
const errors = [];
|
|
3299
|
-
for (const endpoint of params.endpoints) try {
|
|
3300
|
-
const loaded = await listLoadedSessions(params.request, endpoint);
|
|
3301
|
-
sessions.push(...loaded);
|
|
3302
|
-
if (params.includeStored) {
|
|
3303
|
-
const stored = await listStoredSessions({
|
|
3304
|
-
request: params.request,
|
|
3305
|
-
endpoint,
|
|
3306
|
-
limit: params.maxStoredSessions ?? DEFAULT_MAX_STORED_SESSIONS
|
|
3307
2590
|
});
|
|
3308
|
-
for (const session of stored) if (!sessions.some((entry) => entry.endpointId === endpoint.id && entry.threadId === session.threadId)) sessions.push(session);
|
|
3309
2591
|
}
|
|
3310
|
-
} catch (error) {
|
|
3311
|
-
if (error instanceof CodexSupervisionPolicyError) throw error;
|
|
3312
|
-
errors.push({
|
|
3313
|
-
endpointId: endpoint.id,
|
|
3314
|
-
ok: false,
|
|
3315
|
-
detail: error instanceof Error ? error.message : String(error)
|
|
3316
|
-
});
|
|
3317
|
-
}
|
|
3318
|
-
return {
|
|
3319
|
-
sessions,
|
|
3320
|
-
errors
|
|
3321
2592
|
};
|
|
3322
2593
|
}
|
|
3323
|
-
async function resolveEndpointForThread(params) {
|
|
3324
|
-
if (params.endpointId) {
|
|
3325
|
-
const endpoint = params.endpoints.find((entry) => entry.id === params.endpointId);
|
|
3326
|
-
if (!endpoint) throw new Error(`Unknown Codex supervisor endpoint: ${params.endpointId}`);
|
|
3327
|
-
return endpoint;
|
|
3328
|
-
}
|
|
3329
|
-
const matches = [];
|
|
3330
|
-
for (const endpoint of params.endpoints) try {
|
|
3331
|
-
if ((await readThread({
|
|
3332
|
-
request: params.request,
|
|
3333
|
-
endpoint,
|
|
3334
|
-
threadId: params.threadId,
|
|
3335
|
-
includeTurns: false
|
|
3336
|
-
})).id === params.threadId) matches.push(endpoint);
|
|
3337
|
-
} catch (error) {
|
|
3338
|
-
if (error instanceof CodexSupervisionPolicyError) throw error;
|
|
3339
|
-
if (!isLoadedThreadReadMiss(error)) continue;
|
|
3340
|
-
}
|
|
3341
|
-
if (matches.length === 1) return expectDefined(matches[0], "single matching Codex supervision endpoint");
|
|
3342
|
-
if (matches.length > 1) throw new Error(`Codex thread id is ambiguous across endpoints: ${params.threadId}`);
|
|
3343
|
-
throw new Error(`Codex thread not found: ${params.threadId}`);
|
|
3344
|
-
}
|
|
3345
|
-
function findInProgressTurnId(thread) {
|
|
3346
|
-
const turns = asRecordArray(thread.turns);
|
|
3347
|
-
for (const turn of turns.toReversed()) if (turn.status === "inProgress" && typeof turn.id === "string") return turn.id;
|
|
3348
|
-
}
|
|
3349
|
-
async function resolveInProgressTurnId(params) {
|
|
3350
|
-
const inline = findInProgressTurnId(params.thread);
|
|
3351
|
-
if (inline) return inline;
|
|
3352
|
-
try {
|
|
3353
|
-
const response = await params.request(params.endpoint, "thread/turns/list", {
|
|
3354
|
-
threadId: params.threadId,
|
|
3355
|
-
limit: 10,
|
|
3356
|
-
sortDirection: "desc",
|
|
3357
|
-
itemsView: "summary"
|
|
3358
|
-
});
|
|
3359
|
-
return isRecord(response) ? findInProgressTurnId({ turns: response.data }) : void 0;
|
|
3360
|
-
} catch (error) {
|
|
3361
|
-
if (error instanceof CodexSupervisionPolicyError) throw error;
|
|
3362
|
-
return;
|
|
3363
|
-
}
|
|
3364
|
-
}
|
|
3365
|
-
function redactString(value) {
|
|
3366
|
-
return value.replace(/\b(?:sk|glpat|xox[baprs])-[-_a-zA-Z0-9]{12,}\b/g, "[redacted]").replace(/\b(?:ghp|gho|ghu|ghs)_[-_a-zA-Z0-9]{12,}\b/g, "[redacted]").replace(/\bBearer\s+[-._~+/a-zA-Z0-9]+=*/g, "Bearer [redacted]");
|
|
3367
|
-
}
|
|
3368
|
-
/** Redacts secret-bearing fields before legacy tool results leave the plugin. */
|
|
3369
|
-
function redactCodexSupervisionValue(value, key = "") {
|
|
3370
|
-
if (typeof value === "string") return /authorization|password|secret|token|api[-_]?key/i.test(key) ? "[redacted]" : redactString(value);
|
|
3371
|
-
if (Array.isArray(value)) return value.map((entry) => redactCodexSupervisionValue(entry));
|
|
3372
|
-
if (!isRecord(value)) return value;
|
|
3373
|
-
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [entryKey, redactCodexSupervisionValue(entryValue, entryKey)]));
|
|
3374
|
-
}
|
|
3375
|
-
function redactEndpointUrl(value) {
|
|
3376
|
-
if (value.startsWith("unix://")) return "unix://";
|
|
3377
|
-
try {
|
|
3378
|
-
const url = new URL(value);
|
|
3379
|
-
url.username = "";
|
|
3380
|
-
url.password = "";
|
|
3381
|
-
if (url.search) url.search = "?[redacted]";
|
|
3382
|
-
return url.toString();
|
|
3383
|
-
} catch {
|
|
3384
|
-
return "[redacted]";
|
|
3385
|
-
}
|
|
3386
|
-
}
|
|
3387
|
-
function endpointResult(endpoint, pluginConfig, env) {
|
|
3388
|
-
const configured = endpoint.configured;
|
|
3389
|
-
if (configured && (configured.transport === "stdio-proxy" || configured.transport === void 0)) return {
|
|
3390
|
-
id: endpoint.id,
|
|
3391
|
-
transport: "stdio-proxy",
|
|
3392
|
-
...endpoint.label ? { label: endpoint.label } : {}
|
|
3393
|
-
};
|
|
3394
|
-
if (configured?.transport === "websocket") return {
|
|
3395
|
-
id: endpoint.id,
|
|
3396
|
-
transport: "websocket",
|
|
3397
|
-
...endpoint.label ? { label: endpoint.label } : {},
|
|
3398
|
-
url: redactEndpointUrl(configured.url)
|
|
3399
|
-
};
|
|
3400
|
-
const start = resolveCodexSupervisionAppServerRuntimeOptions({
|
|
3401
|
-
pluginConfig,
|
|
3402
|
-
env
|
|
3403
|
-
}).start;
|
|
3404
|
-
return {
|
|
3405
|
-
id: endpoint.id,
|
|
3406
|
-
transport: start.transport === "stdio" ? "stdio-proxy" : "websocket",
|
|
3407
|
-
...endpoint.label ? { label: endpoint.label } : {},
|
|
3408
|
-
...start.transport === "stdio" ? {} : { url: redactEndpointUrl(start.transport === "unix" ? start.url ?? "unix://" : start.url ?? "") }
|
|
3409
|
-
};
|
|
3410
|
-
}
|
|
3411
|
-
function sanitizeSessionListResult(result, includeTranscriptDerivedFields) {
|
|
3412
|
-
return {
|
|
3413
|
-
sessions: result.sessions.map((session) => {
|
|
3414
|
-
const sanitized = redactCodexSupervisionValue(session);
|
|
3415
|
-
if (!includeTranscriptDerivedFields) {
|
|
3416
|
-
delete sanitized.preview;
|
|
3417
|
-
delete sanitized.name;
|
|
3418
|
-
}
|
|
3419
|
-
return sanitized;
|
|
3420
|
-
}),
|
|
3421
|
-
errors: includeTranscriptDerivedFields ? redactCodexSupervisionValue(result.errors) : result.errors.map(({ endpointId, ok }) => ({
|
|
3422
|
-
endpointId,
|
|
3423
|
-
ok
|
|
3424
|
-
}))
|
|
3425
|
-
};
|
|
3426
|
-
}
|
|
3427
|
-
function requireSupervisionEnabled(pluginConfig) {
|
|
3428
|
-
if (readCodexPluginConfig(pluginConfig).supervision?.enabled !== true) throw new CodexSupervisionPolicyError("Codex supervision is disabled in the codex plugin config.");
|
|
3429
|
-
}
|
|
3430
|
-
function requireOwnerAccess(options) {
|
|
3431
|
-
if (!options.senderIsOwner) throw new CodexSupervisionPolicyError("Codex supervision compatibility tools require an owner-authorized sender.");
|
|
3432
|
-
}
|
|
3433
|
-
function resolveToolPolicy(options, pluginConfig) {
|
|
3434
|
-
const config = readCodexPluginConfig(pluginConfig).supervision;
|
|
3435
|
-
const env = options.env ?? process.env;
|
|
3436
|
-
return {
|
|
3437
|
-
allowRawTranscripts: config?.allowRawTranscripts === true || options.useLegacyMcpPolicyEnv === true && env[LEGACY_CODEX_SUPERVISOR_RAW_TRANSCRIPTS_ENV] === "1",
|
|
3438
|
-
allowWriteControls: config?.allowWriteControls === true || options.useLegacyMcpPolicyEnv === true && env[LEGACY_CODEX_SUPERVISOR_WRITE_CONTROLS_ENV] === "1"
|
|
3439
|
-
};
|
|
3440
|
-
}
|
|
3441
|
-
function requireRawTranscriptAccess(options, pluginConfig) {
|
|
3442
|
-
if (!resolveToolPolicy(options, pluginConfig).allowRawTranscripts) throw new CodexSupervisionPolicyError("Codex session reads are disabled for this codex plugin supervision config.");
|
|
3443
|
-
}
|
|
3444
|
-
function requireWriteAccess(options, pluginConfig) {
|
|
3445
|
-
if (!resolveToolPolicy(options, pluginConfig).allowWriteControls) throw new CodexSupervisionPolicyError("Codex write controls are disabled for this codex plugin supervision config.");
|
|
3446
|
-
}
|
|
3447
|
-
function requireLiveToolPolicy(options, policy) {
|
|
3448
|
-
requireOwnerAccess(options);
|
|
3449
|
-
const pluginConfig = options.getPluginConfig();
|
|
3450
|
-
requireSupervisionEnabled(pluginConfig);
|
|
3451
|
-
if (policy === "raw-transcripts") requireRawTranscriptAccess(options, pluginConfig);
|
|
3452
|
-
else if (policy === "write-controls") requireWriteAccess(options, pluginConfig);
|
|
3453
|
-
return {
|
|
3454
|
-
pluginConfig,
|
|
3455
|
-
endpoints: resolveEndpoints(pluginConfig, options.env ?? process.env, options.getRuntimeConfig?.())
|
|
3456
|
-
};
|
|
3457
|
-
}
|
|
3458
|
-
function requireCurrentEndpoint(options, policy, endpoint) {
|
|
3459
|
-
const { endpoints } = requireLiveToolPolicy(options, policy);
|
|
3460
|
-
const currentEndpoint = endpoints.find((candidate) => candidate.id === endpoint.id);
|
|
3461
|
-
if (!currentEndpoint || currentEndpoint.connectionKey !== endpoint.connectionKey) throw new CodexSupervisionPolicyError(`Codex supervision endpoint ${endpoint.id} was removed or changed during the request.`);
|
|
3462
|
-
return currentEndpoint;
|
|
3463
|
-
}
|
|
3464
|
-
function requireCurrentEndpointSet(options, expected) {
|
|
3465
|
-
const current = requireLiveToolPolicy(options, "enabled");
|
|
3466
|
-
if (!(current.endpoints.length === expected.length && expected.every((endpoint) => current.endpoints.some((candidate) => candidate.id === endpoint.id && candidate.connectionKey === endpoint.connectionKey)))) throw new CodexSupervisionPolicyError("Codex supervision endpoint configuration changed during the request.");
|
|
3467
|
-
return { pluginConfig: current.pluginConfig };
|
|
3468
|
-
}
|
|
3469
|
-
function createPolicyGuardedRequest(options, request, policy) {
|
|
3470
|
-
return async (endpoint, method, params) => {
|
|
3471
|
-
return await request(requireCurrentEndpoint(options, policy, endpoint), method, params);
|
|
3472
|
-
};
|
|
3473
|
-
}
|
|
3474
|
-
function idleContinuationError(threadId) {
|
|
3475
|
-
return /* @__PURE__ */ new Error(`Codex thread ${threadId} is idle. Continue it from Codex Sessions so OpenClaw can install the Codex harness approval and tool handlers before resume.`);
|
|
3476
|
-
}
|
|
3477
|
-
/** Builds the five shipped Codex Supervisor compatibility tools. */
|
|
3478
|
-
function createCodexSupervisionTools(options) {
|
|
3479
|
-
const baseRequest = options.request ?? createCanonicalEndpointRequest(options);
|
|
3480
|
-
const request = createPolicyGuardedRequest(options, baseRequest, "enabled");
|
|
3481
|
-
const rawTranscriptRequest = createPolicyGuardedRequest(options, baseRequest, "raw-transcripts");
|
|
3482
|
-
const writeRequest = createPolicyGuardedRequest(options, baseRequest, "write-controls");
|
|
3483
|
-
const current = () => {
|
|
3484
|
-
return requireLiveToolPolicy(options, "enabled");
|
|
3485
|
-
};
|
|
3486
|
-
return [
|
|
3487
|
-
{
|
|
3488
|
-
name: "codex_endpoint_probe",
|
|
3489
|
-
label: "Codex Endpoint Probe",
|
|
3490
|
-
description: "Check configured Codex app-server endpoints.",
|
|
3491
|
-
parameters: EmptyParamsSchema,
|
|
3492
|
-
execute: async () => {
|
|
3493
|
-
const { pluginConfig, endpoints } = current();
|
|
3494
|
-
const health = [];
|
|
3495
|
-
for (const endpoint of endpoints) try {
|
|
3496
|
-
await request(endpoint, "thread/loaded/list", { limit: 1 });
|
|
3497
|
-
health.push({
|
|
3498
|
-
endpointId: endpoint.id,
|
|
3499
|
-
ok: true
|
|
3500
|
-
});
|
|
3501
|
-
} catch (error) {
|
|
3502
|
-
if (error instanceof CodexSupervisionPolicyError) throw error;
|
|
3503
|
-
health.push({
|
|
3504
|
-
endpointId: endpoint.id,
|
|
3505
|
-
ok: false
|
|
3506
|
-
});
|
|
3507
|
-
}
|
|
3508
|
-
requireCurrentEndpointSet(options, endpoints);
|
|
3509
|
-
return jsonResult({
|
|
3510
|
-
summary: `codex endpoints: ${health.filter((entry) => entry.ok).length}/${health.length} ok`,
|
|
3511
|
-
endpoints: endpoints.map((endpoint) => endpointResult(endpoint, pluginConfig, options.env ?? process.env)),
|
|
3512
|
-
health
|
|
3513
|
-
});
|
|
3514
|
-
}
|
|
3515
|
-
},
|
|
3516
|
-
{
|
|
3517
|
-
name: "codex_sessions_list",
|
|
3518
|
-
label: "Codex Sessions List",
|
|
3519
|
-
description: "List Codex sessions visible to the OpenClaw supervisor.",
|
|
3520
|
-
parameters: SessionsListParamsSchema,
|
|
3521
|
-
execute: async (_toolCallId, rawParams) => {
|
|
3522
|
-
const params = asRecord(rawParams);
|
|
3523
|
-
const { endpoints } = current();
|
|
3524
|
-
const result = await listSessionSnapshot({
|
|
3525
|
-
endpoints,
|
|
3526
|
-
request,
|
|
3527
|
-
includeStored: readBooleanParam(params, "include_stored"),
|
|
3528
|
-
maxStoredSessions: readIntegerParam(params, "max_stored_sessions")
|
|
3529
|
-
});
|
|
3530
|
-
const { pluginConfig } = requireCurrentEndpointSet(options, endpoints);
|
|
3531
|
-
return jsonResult({
|
|
3532
|
-
summary: `codex sessions: ${result.sessions.length}`,
|
|
3533
|
-
...sanitizeSessionListResult(result, resolveToolPolicy(options, pluginConfig).allowRawTranscripts)
|
|
3534
|
-
});
|
|
3535
|
-
}
|
|
3536
|
-
},
|
|
3537
|
-
{
|
|
3538
|
-
name: "codex_session_read",
|
|
3539
|
-
label: "Codex Session Read",
|
|
3540
|
-
description: "Read one Codex session transcript from app-server.",
|
|
3541
|
-
parameters: SessionReadParamsSchema,
|
|
3542
|
-
execute: async (_toolCallId, rawParams) => {
|
|
3543
|
-
const { endpoints, pluginConfig } = current();
|
|
3544
|
-
requireRawTranscriptAccess(options, pluginConfig);
|
|
3545
|
-
const params = asRecord(rawParams);
|
|
3546
|
-
const threadId = readStringParam(params, "thread_id", { required: true });
|
|
3547
|
-
const endpoint = await resolveEndpointForThread({
|
|
3548
|
-
endpoints,
|
|
3549
|
-
request: rawTranscriptRequest,
|
|
3550
|
-
endpointId: readStringParam(params, "endpoint_id"),
|
|
3551
|
-
threadId
|
|
3552
|
-
});
|
|
3553
|
-
const thread = await readThread({
|
|
3554
|
-
request: rawTranscriptRequest,
|
|
3555
|
-
endpoint,
|
|
3556
|
-
threadId,
|
|
3557
|
-
includeTurns: readBooleanParam(params, "include_turns")
|
|
3558
|
-
});
|
|
3559
|
-
requireCurrentEndpoint(options, "raw-transcripts", endpoint);
|
|
3560
|
-
return jsonResult({
|
|
3561
|
-
summary: `codex session: ${threadId}`,
|
|
3562
|
-
response: redactCodexSupervisionValue({ thread })
|
|
3563
|
-
});
|
|
3564
|
-
}
|
|
3565
|
-
},
|
|
3566
|
-
{
|
|
3567
|
-
name: "codex_session_send",
|
|
3568
|
-
label: "Codex Session Send",
|
|
3569
|
-
description: "Steer an active Codex turn. Idle sessions must be continued through Codex Sessions.",
|
|
3570
|
-
parameters: SessionSendParamsSchema,
|
|
3571
|
-
execute: async (_toolCallId, rawParams) => {
|
|
3572
|
-
const { endpoints, pluginConfig } = current();
|
|
3573
|
-
requireWriteAccess(options, pluginConfig);
|
|
3574
|
-
const params = asRecord(rawParams);
|
|
3575
|
-
const threadId = readStringParam(params, "thread_id", { required: true });
|
|
3576
|
-
const text = readStringParam(params, "text", {
|
|
3577
|
-
required: true,
|
|
3578
|
-
allowEmpty: false
|
|
3579
|
-
});
|
|
3580
|
-
if ((readModeParam(params) ?? "auto") === "start") throw idleContinuationError(threadId);
|
|
3581
|
-
const endpoint = await resolveEndpointForThread({
|
|
3582
|
-
endpoints,
|
|
3583
|
-
request: writeRequest,
|
|
3584
|
-
endpointId: readStringParam(params, "endpoint_id"),
|
|
3585
|
-
threadId
|
|
3586
|
-
});
|
|
3587
|
-
const thread = await readThread({
|
|
3588
|
-
request: writeRequest,
|
|
3589
|
-
endpoint,
|
|
3590
|
-
threadId,
|
|
3591
|
-
includeTurns: true
|
|
3592
|
-
});
|
|
3593
|
-
requireCurrentEndpoint(options, "write-controls", endpoint);
|
|
3594
|
-
if (statusType(thread) !== "active") throw idleContinuationError(threadId);
|
|
3595
|
-
const turnId = await resolveInProgressTurnId({
|
|
3596
|
-
request: writeRequest,
|
|
3597
|
-
endpoint,
|
|
3598
|
-
thread,
|
|
3599
|
-
threadId
|
|
3600
|
-
});
|
|
3601
|
-
if (!turnId) throw new Error(`Codex thread ${threadId} is active but no in-progress turn is readable`);
|
|
3602
|
-
await writeRequest(endpoint, "turn/steer", {
|
|
3603
|
-
threadId,
|
|
3604
|
-
expectedTurnId: turnId,
|
|
3605
|
-
input: [{
|
|
3606
|
-
type: "text",
|
|
3607
|
-
text,
|
|
3608
|
-
text_elements: []
|
|
3609
|
-
}]
|
|
3610
|
-
});
|
|
3611
|
-
const result = {
|
|
3612
|
-
endpointId: endpoint.id,
|
|
3613
|
-
threadId,
|
|
3614
|
-
mode: "steer",
|
|
3615
|
-
turnId
|
|
3616
|
-
};
|
|
3617
|
-
return jsonResult({
|
|
3618
|
-
summary: `codex steer: ${turnId}`,
|
|
3619
|
-
result
|
|
3620
|
-
});
|
|
3621
|
-
}
|
|
3622
|
-
},
|
|
3623
|
-
{
|
|
3624
|
-
name: "codex_session_interrupt",
|
|
3625
|
-
label: "Codex Session Interrupt",
|
|
3626
|
-
description: "Interrupt an active Codex turn.",
|
|
3627
|
-
parameters: SessionInterruptParamsSchema,
|
|
3628
|
-
execute: async (_toolCallId, rawParams) => {
|
|
3629
|
-
const { endpoints, pluginConfig } = current();
|
|
3630
|
-
requireWriteAccess(options, pluginConfig);
|
|
3631
|
-
const params = asRecord(rawParams);
|
|
3632
|
-
const threadId = readStringParam(params, "thread_id", { required: true });
|
|
3633
|
-
const endpoint = await resolveEndpointForThread({
|
|
3634
|
-
endpoints,
|
|
3635
|
-
request: writeRequest,
|
|
3636
|
-
endpointId: readStringParam(params, "endpoint_id"),
|
|
3637
|
-
threadId
|
|
3638
|
-
});
|
|
3639
|
-
const thread = await readThread({
|
|
3640
|
-
request: writeRequest,
|
|
3641
|
-
endpoint,
|
|
3642
|
-
threadId,
|
|
3643
|
-
includeTurns: true
|
|
3644
|
-
});
|
|
3645
|
-
requireCurrentEndpoint(options, "write-controls", endpoint);
|
|
3646
|
-
if (statusType(thread) !== "active") throw new Error(`Codex thread ${threadId} has no active turn to interrupt`);
|
|
3647
|
-
const turnId = readStringParam(params, "turn_id") ?? await resolveInProgressTurnId({
|
|
3648
|
-
request: writeRequest,
|
|
3649
|
-
endpoint,
|
|
3650
|
-
thread,
|
|
3651
|
-
threadId
|
|
3652
|
-
});
|
|
3653
|
-
if (!turnId) throw new Error(`Codex thread ${threadId} has no readable in-progress turn`);
|
|
3654
|
-
await writeRequest(endpoint, "turn/interrupt", {
|
|
3655
|
-
threadId,
|
|
3656
|
-
turnId
|
|
3657
|
-
});
|
|
3658
|
-
const result = {
|
|
3659
|
-
endpointId: endpoint.id,
|
|
3660
|
-
threadId,
|
|
3661
|
-
turnId
|
|
3662
|
-
};
|
|
3663
|
-
return jsonResult({
|
|
3664
|
-
summary: `codex interrupted: ${turnId}`,
|
|
3665
|
-
result
|
|
3666
|
-
});
|
|
3667
|
-
}
|
|
3668
|
-
}
|
|
3669
|
-
];
|
|
3670
|
-
}
|
|
3671
2594
|
//#endregion
|
|
3672
2595
|
//#region extensions/codex/src/web-search-provider.ts
|
|
3673
|
-
const loadCodexWebSearchRuntime = createLazyRuntimeModule(() => import("
|
|
2596
|
+
const loadCodexWebSearchRuntime = createLazyRuntimeModule(() => import("./.setup/web-search-provider.runtime-Ce2zOHxW.mjs"));
|
|
3674
2597
|
const CodexWebSearchSchema = {
|
|
3675
2598
|
type: "object",
|
|
3676
2599
|
properties: { query: {
|
|
@@ -3712,96 +2635,21 @@ const ENDED_SESSION_REASONS = /* @__PURE__ */ new Set([
|
|
|
3712
2635
|
var codex_default = definePluginEntry({
|
|
3713
2636
|
id: "codex",
|
|
3714
2637
|
name: "Codex",
|
|
3715
|
-
description: "Codex app-server harness and
|
|
2638
|
+
description: "Codex app-server harness and Codex-managed GPT model catalog.",
|
|
3716
2639
|
register(api) {
|
|
3717
2640
|
const resolveCurrentConfig = () => api.runtime.config?.current ? api.runtime.config.current() : void 0;
|
|
3718
|
-
const
|
|
3719
|
-
|
|
3720
|
-
if (!liveConfig) return api.pluginConfig;
|
|
3721
|
-
const livePluginConfig = resolveLivePluginConfigObject(() => liveConfig, "codex", api.pluginConfig);
|
|
3722
|
-
if (!resolveEffectiveEnableState({
|
|
3723
|
-
id: "codex",
|
|
3724
|
-
origin: "bundled",
|
|
3725
|
-
config: normalizePluginsConfig(liveConfig.plugins),
|
|
3726
|
-
rootConfig: liveConfig,
|
|
3727
|
-
enabledByDefault: livePluginConfig !== void 0
|
|
3728
|
-
}).enabled) return;
|
|
3729
|
-
return livePluginConfig;
|
|
3730
|
-
};
|
|
3731
|
-
const resolveCurrentPluginConfig = () => resolvePluginConfig(resolveCurrentConfig);
|
|
3732
|
-
if (api.registrationMode === "full") {
|
|
3733
|
-
const realtimeBrowserSession = configureCodexRealtimeBrowserSession({
|
|
3734
|
-
getConfig: resolveCurrentConfig,
|
|
3735
|
-
getPluginConfig: resolveCurrentPluginConfig
|
|
3736
|
-
});
|
|
3737
|
-
api.registerHttpRoute({
|
|
3738
|
-
path: CODEX_REALTIME_OFFER_PATH,
|
|
3739
|
-
auth: "plugin",
|
|
3740
|
-
match: "exact",
|
|
3741
|
-
handler: realtimeBrowserSession.handler
|
|
3742
|
-
});
|
|
3743
|
-
api.registerService({
|
|
3744
|
-
id: "codex-oauth-realtime-browser-session-warmup",
|
|
3745
|
-
start: () => {
|
|
3746
|
-
realtimeBrowserSession.warmup().catch((error) => {
|
|
3747
|
-
api.logger.debug?.(`Codex OAuth realtime warmup unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
3748
|
-
});
|
|
3749
|
-
}
|
|
3750
|
-
});
|
|
3751
|
-
api.lifecycle.registerRuntimeLifecycle({
|
|
3752
|
-
id: "codex-oauth-realtime-browser-session",
|
|
3753
|
-
description: "Release Codex OAuth realtime browser sessions when the plugin stops",
|
|
3754
|
-
cleanup: async ({ reason }) => {
|
|
3755
|
-
if (reason === "reset" || reason === "delete") return;
|
|
3756
|
-
await realtimeBrowserSession.cleanup();
|
|
3757
|
-
}
|
|
3758
|
-
});
|
|
3759
|
-
}
|
|
3760
|
-
let bindingStateStore;
|
|
3761
|
-
const openBindingStateStore = () => bindingStateStore ??= api.runtime.state.openSyncKeyedStore({
|
|
2641
|
+
const resolveCurrentPluginConfig = () => resolveLivePluginConfigObject(resolveCurrentConfig, "codex", api.pluginConfig) ?? api.pluginConfig;
|
|
2642
|
+
const bindingStore = createLazyCodexAppServerBindingStore(api.runtime.state.openSyncKeyedStore({
|
|
3762
2643
|
namespace: CODEX_APP_SERVER_BINDING_NAMESPACE,
|
|
3763
2644
|
maxEntries: CODEX_APP_SERVER_BINDING_MAX_ENTRIES,
|
|
3764
2645
|
overflowPolicy: "reject-new"
|
|
3765
|
-
});
|
|
3766
|
-
const bindingStore = createLazyCodexAppServerBindingStore({
|
|
3767
|
-
entries: () => openBindingStateStore().entries(),
|
|
3768
|
-
lookup: (key) => openBindingStateStore().lookup(key),
|
|
3769
|
-
get update() {
|
|
3770
|
-
const store = openBindingStateStore();
|
|
3771
|
-
return store.update?.bind(store);
|
|
3772
|
-
}
|
|
3773
|
-
});
|
|
3774
|
-
registerCodexCliMetadata(api);
|
|
3775
|
-
const sessionCatalogControl = createCodexSessionCatalogControl({
|
|
3776
|
-
getPluginConfig: resolveCurrentPluginConfig,
|
|
3777
|
-
getRuntimeConfig: resolveCurrentConfig
|
|
3778
|
-
});
|
|
3779
|
-
if (readCodexPluginConfig(resolveCurrentPluginConfig()).sessionCatalog?.enabled !== false) {
|
|
3780
|
-
codexSessionCatalogRuntime.register({
|
|
3781
|
-
api,
|
|
3782
|
-
bindingStore,
|
|
3783
|
-
control: sessionCatalogControl,
|
|
3784
|
-
getRuntimeConfig: resolveCurrentConfig
|
|
3785
|
-
});
|
|
3786
|
-
for (const command of createCodexSessionCatalogNodeHostCommands(sessionCatalogControl)) api.registerNodeHostCommand(command);
|
|
3787
|
-
}
|
|
3788
|
-
for (const policy of createCodexSessionCatalogNodeInvokePolicies()) api.registerNodeInvokePolicy(policy);
|
|
3789
|
-
if (readCodexPluginConfig(resolveCurrentPluginConfig()).supervision?.enabled === true) api.registerTool((context) => {
|
|
3790
|
-
if (context.senderIsOwner !== true) return [];
|
|
3791
|
-
const resolveToolRuntimeConfig = () => context.getRuntimeConfig?.() ?? context.runtimeConfig ?? context.config ?? resolveCurrentConfig();
|
|
3792
|
-
return createCodexSupervisionTools({
|
|
3793
|
-
getPluginConfig: () => resolvePluginConfig(resolveToolRuntimeConfig),
|
|
3794
|
-
getRuntimeConfig: resolveToolRuntimeConfig,
|
|
3795
|
-
senderIsOwner: context.senderIsOwner
|
|
3796
|
-
});
|
|
3797
|
-
}, { names: [...CODEX_SUPERVISION_COMPAT_TOOL_NAMES] });
|
|
2646
|
+
}));
|
|
3798
2647
|
api.registerAgentHarness(createCodexAppServerAgentHarness({
|
|
3799
2648
|
bindingStore,
|
|
3800
|
-
sessionCatalogControl,
|
|
3801
2649
|
resolveConfig: resolveCurrentConfig,
|
|
3802
|
-
resolvePluginConfig: resolveCurrentPluginConfig
|
|
3803
|
-
runtime: api.runtime
|
|
2650
|
+
resolvePluginConfig: resolveCurrentPluginConfig
|
|
3804
2651
|
}));
|
|
2652
|
+
api.registerProvider(buildCodexProvider({ pluginConfig: api.pluginConfig }));
|
|
3805
2653
|
api.registerMediaUnderstandingProvider(buildCodexMediaUnderstandingProvider({ pluginConfig: api.pluginConfig }));
|
|
3806
2654
|
api.registerWebSearchProvider(createCodexWebSearchProvider({ resolvePluginConfig: resolveCurrentPluginConfig }));
|
|
3807
2655
|
api.registerMigrationProvider(buildCodexMigrationProvider({ runtime: api.runtime }));
|
|
@@ -3872,7 +2720,7 @@ var codex_default = definePluginEntry({
|
|
|
3872
2720
|
}
|
|
3873
2721
|
}
|
|
3874
2722
|
}));
|
|
3875
|
-
api.on("inbound_claim", (event, ctx) =>
|
|
2723
|
+
api.on("inbound_claim", (event, ctx) => handleCodexConversationInboundClaim(event, ctx, {
|
|
3876
2724
|
bindingStore,
|
|
3877
2725
|
pluginConfig: resolveCurrentPluginConfig(),
|
|
3878
2726
|
config: resolveCurrentConfig(),
|
|
@@ -3881,14 +2729,14 @@ var codex_default = definePluginEntry({
|
|
|
3881
2729
|
...params
|
|
3882
2730
|
})
|
|
3883
2731
|
}));
|
|
3884
|
-
api.onConversationBindingResolved?.((event) =>
|
|
2732
|
+
api.onConversationBindingResolved?.((event) => handleCodexConversationBindingResolved(event, { bindingStore }));
|
|
3885
2733
|
api.on("after_compaction", async (event, ctx) => {
|
|
3886
2734
|
const previousSessionId = event.previousSessionId?.trim();
|
|
3887
2735
|
const sessionId = ctx.sessionId?.trim();
|
|
3888
2736
|
if (!previousSessionId || !sessionId || previousSessionId === sessionId) return;
|
|
3889
2737
|
const config = resolveCurrentConfig();
|
|
3890
2738
|
const sessionKey = ctx.sessionKey?.trim();
|
|
3891
|
-
const { sessionBindingIdentity } = await import("
|
|
2739
|
+
const { sessionBindingIdentity } = await import("./.setup/session-binding-DuihlMJc.mjs").then((n) => n.c);
|
|
3892
2740
|
const identity = sessionBindingIdentity({
|
|
3893
2741
|
sessionId,
|
|
3894
2742
|
...sessionKey ? { sessionKey } : {},
|
|
@@ -3901,12 +2749,8 @@ var codex_default = definePluginEntry({
|
|
|
3901
2749
|
api.on("session_end", async (event, ctx) => {
|
|
3902
2750
|
if (!event.reason || !ENDED_SESSION_REASONS.has(event.reason)) return;
|
|
3903
2751
|
const sessionKey = event.sessionKey ?? ctx.sessionKey;
|
|
3904
|
-
const endedSessionKey = sessionKey?.trim();
|
|
3905
|
-
const nextSessionKey = event.nextSessionKey?.trim();
|
|
3906
|
-
if (endedSessionKey && nextSessionKey && nextSessionKey !== endedSessionKey) return;
|
|
3907
|
-
if (event.nextSessionId?.trim() === event.sessionId.trim()) return;
|
|
3908
2752
|
const config = resolveCurrentConfig();
|
|
3909
|
-
const { sessionBindingIdentity } = await import("
|
|
2753
|
+
const { sessionBindingIdentity } = await import("./.setup/session-binding-DuihlMJc.mjs").then((n) => n.c);
|
|
3910
2754
|
await bindingStore.retireSessionGeneration(sessionBindingIdentity({
|
|
3911
2755
|
sessionId: event.sessionId,
|
|
3912
2756
|
...sessionKey ? { sessionKey } : {},
|