@openclaw/codex 2026.7.1 → 2026.7.2-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -3
- package/dist/app-server-policy-Scc-Wevo.js +49 -0
- package/dist/{attempt-notifications-BGsEIIDI.js → attempt-notifications-BFmNhqFl.js} +3 -6
- package/dist/auth-binding-C1czuHPA.js +62 -0
- package/dist/cli-metadata.js +20 -0
- package/dist/{app-server-policy-C968Kgin.js → command-formatters-Dq9InZUK.js} +39 -51
- package/dist/{command-handlers-Dv-pzAmg.js → command-handlers-IgqmL_kv.js} +161 -61
- package/dist/{compact-4mVBriKT.js → compact-DCTmg3id.js} +39 -10
- package/dist/computer-use-CPcU8TjG.js +685 -0
- package/dist/conversation-control-CGkBlfc5.js +286 -0
- package/dist/doctor-contract-api.js +120 -81
- package/dist/{provider-capabilities-CDnHbmUZ.js → dynamic-tools-C_1tEs34.js} +6109 -5725
- package/dist/harness.js +59 -15
- package/dist/index.js +1460 -269
- package/dist/{media-understanding-provider-BHOu9-Gg.js → media-understanding-provider-D31dOJwb.js} +32 -15
- package/dist/media-understanding-provider.js +1 -1
- package/dist/{models-DDmO1zwd.js → models-uh26C8QU.js} +5 -9
- package/dist/notification-correlation-KmfV4EkP.js +67 -0
- package/dist/plugin-app-cache-key-Ceb-lm2c.js +315 -0
- package/dist/{provider-zjPfx5Fs.js → rate-limits-DyXaYAxU.js} +10 -266
- package/dist/request-B_oQsCXy.js +157 -0
- package/dist/{run-attempt-FUyOjGCV.js → run-attempt-MLzoMe_m.js} +7395 -6430
- package/dist/runtime-artifact-C2ITXdhM.js +488 -0
- package/dist/{sandbox-guard-DA2TQfZW.js → sandbox-guard-D1Wky__S.js} +10 -1
- package/dist/{config-CYEDnLJ2.js → session-binding-h1mmCGnl.js} +985 -31
- package/dist/session-catalog-CEvoXWHA.js +2412 -0
- package/dist/session-cli-XsEuWb_C.js +175 -0
- package/dist/{shared-client-4ICy3U6d.js → shared-client-JiAnW6pc.js} +2193 -1319
- package/dist/{side-question-N4OaDer2.js → side-question-DqDvIwSU.js} +140 -70
- package/dist/{thread-lifecycle-qWE88Dn2.js → thread-lifecycle-CN_pPtPh.js} +1826 -1188
- package/dist/{protocol-validators-dZQ-UTOa.js → transcript-mirror-D9rTxx2P.js} +485 -2
- package/dist/transport-stdio-DXgGZ7Ib.js +88 -0
- package/dist/usage-D5Rohxoc.js +25 -0
- package/dist/{web-search-provider.runtime-BSlriav6.js → web-search-provider.runtime-CKVBn3fP.js} +3 -3
- package/npm-shrinkwrap.json +56 -30
- package/openclaw.plugin.json +177 -14
- package/package.json +8 -6
- package/dist/command-rpc-eLM_mtC7.js +0 -784
- package/dist/computer-use-Bmaz333N.js +0 -377
- package/dist/notification-correlation-Bo7KB3ks.js +0 -35
- package/dist/plugin-app-cache-key-BrhVdeEf.js +0 -98
- package/dist/prompt-overlay.js +0 -15
- package/dist/protocol-2POPqAY4.js +0 -13
- package/dist/provider-catalog.js +0 -79
- package/dist/provider-discovery.js +0 -34
- package/dist/provider.js +0 -2
- package/dist/request-BcJyl8KL.js +0 -36
- package/dist/session-binding-C1ZXdP-x.js +0 -627
package/dist/index.js
CHANGED
|
@@ -1,59 +1,81 @@
|
|
|
1
|
+
import { registerCodexCliMetadata } from "./cli-metadata.js";
|
|
1
2
|
import { createCodexAppServerAgentHarness } from "./harness.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { _ as resolveCodexAppServerAuthAccountCacheKey, a as getLeasedSharedCodexAppServerClient, b as resolveCodexAppServerFallbackApiKeyCacheKey, o as releaseLeasedSharedCodexAppServerClient, r as clearSharedCodexAppServerClientIfCurrentAndWait, y as resolveCodexAppServerAuthProfileIdForAgent } from "./shared-client-4ICy3U6d.js";
|
|
3
|
+
import { C as resolveCodexAppServerFallbackApiKeyCacheKey, J as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS, S as resolveCodexAppServerAuthProfileIdForAgent, Z as isJsonObject, a as getLeasedSharedCodexAppServerClient, b as resolveCodexAppServerAuthAccountCacheKey, c as releaseLeasedSharedCodexAppServerClient, p as withLeasedCodexAppServerClientStartSelectionRetry, r as clearSharedCodexAppServerClientIfCurrentAndWait, s as releaseCodexAppServerClientLease } from "./shared-client-JiAnW6pc.js";
|
|
4
|
+
import { D as resolveOpenClawExecPolicyForCodexAppServer, E as resolveCodexSupervisionAppServerRuntimeOptions, a as isCodexAppServerNativeAuthProfile, b as resolveCodexAppServerRuntimeOptions, c as sessionBindingIdentity, f as assertCodexAppServerConnectionSecurity, h as codexSandboxPolicyForTurn, m as codexAppServerStartOptionsKey, n as assertCodexBindingMayBeReplaced, o as normalizeCodexAppServerBindingModelProvider, p as canUseCodexModelBackedApprovalsReviewerForModel, u as CODEX_PLUGINS_MARKETPLACE_NAME, y as readCodexPluginConfig } from "./session-binding-h1mmCGnl.js";
|
|
5
|
+
import { d as assertCodexThreadStartResponse } from "./transcript-mirror-D9rTxx2P.js";
|
|
6
|
+
import { i as defaultCodexAppInventoryCache, r as buildCodexPluginAppCacheKey } from "./plugin-app-cache-key-Ceb-lm2c.js";
|
|
7
|
+
import { X as ensureCodexPluginActivation, Z as pluginReadParams, c as resumeCodexAppServerThread, h as resolveCodexAppServerRequestModelSelection, p as CODEX_NATIVE_PERSONALITY_NONE } from "./thread-lifecycle-CN_pPtPh.js";
|
|
8
8
|
import { n as CODEX_APP_SERVER_BINDING_NAMESPACE, t as CODEX_APP_SERVER_BINDING_MAX_ENTRIES } from "./session-binding-meta-B7aEMU7g.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { a as
|
|
16
|
-
import { n as
|
|
17
|
-
import {
|
|
9
|
+
import { t as buildCodexMediaUnderstandingProvider } from "./media-understanding-provider-D31dOJwb.js";
|
|
10
|
+
import { d as CODEX_CONTROL_METHODS, f as describeControlFailure, r as formatCodexDisplayText } from "./command-formatters-Dq9InZUK.js";
|
|
11
|
+
import { t as resolveCodexAppServerForModelProvider } from "./app-server-policy-Scc-Wevo.js";
|
|
12
|
+
import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-D1Wky__S.js";
|
|
13
|
+
import { d as CODEX_NATIVE_EXECUTION_AUTH_ERROR, f as canMutateCodexHost, u as trackCodexConversationActiveTurn } from "./conversation-control-CGkBlfc5.js";
|
|
14
|
+
import { a as readCodexConversationBindingDataRecord, i as readCodexConversationBindingData, o as resolveCodexDefaultWorkspaceDir, r as createCodexConversationBindingData } from "./conversation-binding-data-BHRpYbX3.js";
|
|
15
|
+
import { a as resolveCodexBindingAppServerConnection, n as readCodexNotificationThreadId, r as readCodexNotificationTurnId } from "./notification-correlation-KmfV4EkP.js";
|
|
16
|
+
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-CEvoXWHA.js";
|
|
17
|
+
import { r as requestCodexAppServerJson } from "./request-B_oQsCXy.js";
|
|
18
18
|
import { t as createCodexWebSearchProviderBase } from "./web-search-provider.shared-BrZmlqyR.js";
|
|
19
19
|
import { mutateConfigFile } from "openclaw/plugin-sdk/config-mutation";
|
|
20
|
-
import { resolveLivePluginConfigObject, resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
20
|
+
import { normalizePluginsConfig, resolveEffectiveEnableState, resolveLivePluginConfigObject, resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
21
21
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
22
22
|
import fs from "node:fs/promises";
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import { parseStrictNonNegativeInteger, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
25
25
|
import os from "node:os";
|
|
26
|
-
import { loadAuthProfileStoreWithoutExternalProfiles, resolveAgentConfig, resolveAgentWorkspaceDir, resolveDefaultAgentId, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
|
|
26
|
+
import { loadAuthProfileStoreWithoutExternalProfiles, resolveAgentConfig, resolveAgentWorkspaceDir, resolveDefaultAgentDir, resolveDefaultAgentId, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
|
|
27
27
|
import { loadExecApprovals } from "openclaw/plugin-sdk/exec-approvals-runtime";
|
|
28
28
|
import { asBoolean, asOptionalRecord, isRecord, normalizeOptionalString, normalizeSingleOrTrimmedStringList, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
29
29
|
import { formatErrorMessage, resolveSandboxContext } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
30
|
-
import { jsonResult, readStringParam } from "openclaw/plugin-sdk/core";
|
|
31
30
|
import { applyAuthProfileConfig, buildApiKeyCredential, buildOauthProviderAuthResult, buildOpenAICodexCredentialExtra, readCodexCliCredentialsCached, resolveOpenAICodexAuthIdentity, resolveOpenAICodexImportProfileName, updateAuthProfileStoreWithLock } from "openclaw/plugin-sdk/provider-auth";
|
|
32
|
-
import { fileURLToPath } from "node:url";
|
|
33
|
-
import { pathExists } from "openclaw/plugin-sdk/security-runtime";
|
|
34
31
|
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
34
|
+
import { canonicalPathFromExistingAncestor, isPathInside, pathExists } from "openclaw/plugin-sdk/security-runtime";
|
|
35
35
|
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
36
|
+
import { ModelSelectionLockedError, isModelSelectionLocked } from "openclaw/plugin-sdk/model-session-runtime";
|
|
36
37
|
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";
|
|
38
|
+
import { archiveMigrationItem, copyMemoryMigrationFileItem, copyMigrationFileItem, withCachedMigrationConfigRuntime, writeMigrationReport } from "openclaw/plugin-sdk/migration-runtime";
|
|
38
39
|
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
|
39
40
|
import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
|
|
41
|
+
import { jsonResult, readStringParam } from "openclaw/plugin-sdk/core";
|
|
40
42
|
import { Type } from "typebox";
|
|
41
43
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
42
44
|
//#region extensions/codex/src/app-server/session-binding-store.ts
|
|
43
45
|
/** Defers schema compilation and auth loading until the first binding operation. */
|
|
44
46
|
function createLazyCodexAppServerBindingStore(state) {
|
|
45
47
|
let resolved;
|
|
46
|
-
const store = () => resolved ??= import("./session-binding-
|
|
48
|
+
const store = () => resolved ??= import("./session-binding-h1mmCGnl.js").then((n) => n.l).then(({ createCodexAppServerBindingStore }) => createCodexAppServerBindingStore(state));
|
|
47
49
|
return {
|
|
48
50
|
read: async (identity) => (await store()).read(identity),
|
|
51
|
+
hasOtherThreadOwner: async (threadId, currentIdentity) => (await store()).hasOtherThreadOwner(threadId, currentIdentity),
|
|
49
52
|
mutate: async (identity, mutation) => (await store()).mutate(identity, mutation),
|
|
50
53
|
prepareSessionGenerationReclaim: async (identity) => (await store()).prepareSessionGenerationReclaim(identity),
|
|
51
54
|
adoptSessionGeneration: async (identity, previousSessionId) => (await store()).adoptSessionGeneration(identity, previousSessionId),
|
|
52
55
|
retireSessionGeneration: async (identity) => (await store()).retireSessionGeneration(identity),
|
|
56
|
+
withThreadArchiveFence: async (run) => (await store()).withThreadArchiveFence(run),
|
|
53
57
|
withLease: async (identity, run) => (await store()).withLease(identity, run)
|
|
54
58
|
};
|
|
55
59
|
}
|
|
56
60
|
//#endregion
|
|
61
|
+
//#region extensions/codex/src/command-dispatch.ts
|
|
62
|
+
/** Dispatches a `/codex` command to the lazily loaded handler. */
|
|
63
|
+
async function handleCodexCommand(ctx, options) {
|
|
64
|
+
const { loadSubcommandHandler, resolvePluginConfig, ...subcommandOptions } = options;
|
|
65
|
+
try {
|
|
66
|
+
return await (loadSubcommandHandler ? await loadSubcommandHandler() : await loadDefaultCodexSubcommandHandler())(ctx, {
|
|
67
|
+
...subcommandOptions,
|
|
68
|
+
pluginConfig: resolvePluginConfig?.() ?? subcommandOptions.pluginConfig
|
|
69
|
+
});
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return { text: `Codex command failed: ${formatCodexDisplayText(describeControlFailure(error))}` };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function loadDefaultCodexSubcommandHandler() {
|
|
75
|
+
const { handleCodexSubcommand } = await import("./command-handlers-IgqmL_kv.js");
|
|
76
|
+
return handleCodexSubcommand;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
57
79
|
//#region extensions/codex/src/commands.ts
|
|
58
80
|
/** Creates the reserved `/codex` command definition exposed by the plugin. */
|
|
59
81
|
function createCodexCommand(options) {
|
|
@@ -73,22 +95,6 @@ function createCodexCommand(options) {
|
|
|
73
95
|
handler: (ctx) => handleCodexCommand(ctx, options)
|
|
74
96
|
};
|
|
75
97
|
}
|
|
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("./command-handlers-Dv-pzAmg.js");
|
|
90
|
-
return handleCodexSubcommand;
|
|
91
|
-
}
|
|
92
98
|
//#endregion
|
|
93
99
|
//#region extensions/codex/src/conversation-turn-collector.ts
|
|
94
100
|
const MAX_PENDING_NOTIFICATIONS_PER_TURN = 100;
|
|
@@ -322,6 +328,72 @@ function getGlobalState() {
|
|
|
322
328
|
globalState[CODEX_CONVERSATION_GLOBAL_STATE] ??= { queue: new KeyedAsyncQueue() };
|
|
323
329
|
return globalState[CODEX_CONVERSATION_GLOBAL_STATE];
|
|
324
330
|
}
|
|
331
|
+
async function startCodexConversationThread(params) {
|
|
332
|
+
const workspaceDir = params.workspaceDir?.trim() || resolveCodexDefaultWorkspaceDir(params.pluginConfig);
|
|
333
|
+
const agentDir = params.agentDir?.trim();
|
|
334
|
+
const agentLookup = buildAgentLookup({
|
|
335
|
+
agentDir,
|
|
336
|
+
config: params.config
|
|
337
|
+
});
|
|
338
|
+
const identity = sessionBindingIdentity({
|
|
339
|
+
agentId: params.agentId,
|
|
340
|
+
sessionId: params.sessionFile,
|
|
341
|
+
sessionKey: params.sessionKey,
|
|
342
|
+
config: params.config
|
|
343
|
+
});
|
|
344
|
+
const existingBinding = await params.bindingStore.read(identity);
|
|
345
|
+
assertCodexBindingMayBeReplaced(existingBinding, "starting a conversation-bound Codex thread");
|
|
346
|
+
const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
|
|
347
|
+
authProfileId: params.authProfileId ?? existingBinding?.authProfileId,
|
|
348
|
+
...agentLookup
|
|
349
|
+
});
|
|
350
|
+
if (params.threadId?.trim()) await attachExistingThread({
|
|
351
|
+
pluginConfig: params.pluginConfig,
|
|
352
|
+
bindingStore: params.bindingStore,
|
|
353
|
+
identity,
|
|
354
|
+
threadId: params.threadId.trim(),
|
|
355
|
+
workspaceDir,
|
|
356
|
+
...agentDir ? { agentDir } : {},
|
|
357
|
+
model: params.model,
|
|
358
|
+
modelProvider: params.modelProvider,
|
|
359
|
+
authProfileId,
|
|
360
|
+
approvalPolicy: params.approvalPolicy,
|
|
361
|
+
sandbox: params.sandbox,
|
|
362
|
+
serviceTier: params.serviceTier,
|
|
363
|
+
config: params.config,
|
|
364
|
+
sessionKey: params.sessionKey,
|
|
365
|
+
agentId: params.agentId
|
|
366
|
+
});
|
|
367
|
+
else await createThread({
|
|
368
|
+
pluginConfig: params.pluginConfig,
|
|
369
|
+
bindingStore: params.bindingStore,
|
|
370
|
+
identity,
|
|
371
|
+
workspaceDir,
|
|
372
|
+
...agentDir ? { agentDir } : {},
|
|
373
|
+
model: params.model,
|
|
374
|
+
modelProvider: params.modelProvider,
|
|
375
|
+
authProfileId,
|
|
376
|
+
approvalPolicy: params.approvalPolicy,
|
|
377
|
+
sandbox: params.sandbox,
|
|
378
|
+
serviceTier: params.serviceTier,
|
|
379
|
+
config: params.config,
|
|
380
|
+
sessionKey: params.sessionKey,
|
|
381
|
+
agentId: params.agentId
|
|
382
|
+
});
|
|
383
|
+
const storedBinding = await params.bindingStore.read(identity);
|
|
384
|
+
if (!storedBinding) throw new Error("Codex session binding disappeared while starting its conversation thread.");
|
|
385
|
+
return createCodexConversationBindingData({
|
|
386
|
+
source: {
|
|
387
|
+
agentId: identity.agentId,
|
|
388
|
+
sessionId: identity.sessionId,
|
|
389
|
+
threadId: storedBinding.threadId,
|
|
390
|
+
...identity.sessionKey ? { sessionKey: identity.sessionKey } : {}
|
|
391
|
+
},
|
|
392
|
+
workspaceDir,
|
|
393
|
+
...agentDir ? { agentDir } : {},
|
|
394
|
+
agentId: params.agentId
|
|
395
|
+
});
|
|
396
|
+
}
|
|
325
397
|
async function handleCodexConversationInboundClaim(event, ctx, options) {
|
|
326
398
|
const data = readCodexConversationBindingData(ctx.pluginBinding);
|
|
327
399
|
if (!data) return;
|
|
@@ -399,6 +471,7 @@ async function handleCodexConversationBindingResolved(event, options) {
|
|
|
399
471
|
if (!data || data.kind !== "codex-app-server-session") return;
|
|
400
472
|
const identity = conversationBindingIdentity(data);
|
|
401
473
|
const binding = await options.bindingStore.read(identity);
|
|
474
|
+
assertCodexBindingMayBeReplaced(binding, "clearing a denied conversation binding");
|
|
402
475
|
if (!data.start?.id || binding?.conversationStartId === data.start.id) await options.bindingStore.mutate(identity, { kind: "clear" });
|
|
403
476
|
}
|
|
404
477
|
async function resolveThreadBindingRuntime(params) {
|
|
@@ -452,19 +525,22 @@ async function resolveThreadBindingRuntime(params) {
|
|
|
452
525
|
agentDir: params.agentDir
|
|
453
526
|
})
|
|
454
527
|
});
|
|
455
|
-
const
|
|
528
|
+
const clientOptions = {
|
|
456
529
|
startOptions: runtime.start,
|
|
457
530
|
timeoutMs: runtime.requestTimeoutMs,
|
|
458
531
|
authProfileId: params.authProfileId,
|
|
459
532
|
...agentLookup
|
|
460
|
-
}
|
|
533
|
+
};
|
|
534
|
+
const client = await getLeasedSharedCodexAppServerClient(clientOptions);
|
|
461
535
|
return {
|
|
462
536
|
execPolicy,
|
|
463
537
|
runtime: modelScopedRuntime,
|
|
464
538
|
agentLookup,
|
|
465
539
|
model: modelSelection?.model,
|
|
466
540
|
modelProvider: modelSelection?.modelProvider ?? modelProvider,
|
|
467
|
-
client
|
|
541
|
+
client,
|
|
542
|
+
clientLease: { client },
|
|
543
|
+
clientOptions
|
|
468
544
|
};
|
|
469
545
|
}
|
|
470
546
|
function buildThreadRequestRuntimeOptions(params, resolved) {
|
|
@@ -483,22 +559,31 @@ function codexConversationSandboxOrPermissions(runtime, sandbox) {
|
|
|
483
559
|
return { sandbox };
|
|
484
560
|
}
|
|
485
561
|
async function requestNewConversationBindingThread(params, resolved) {
|
|
486
|
-
return await
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
562
|
+
return await withLeasedCodexAppServerClientStartSelectionRetry({
|
|
563
|
+
lease: resolved.clientLease,
|
|
564
|
+
options: resolved.clientOptions,
|
|
565
|
+
run: async (client, requestOptions) => await client.request("thread/start", {
|
|
566
|
+
cwd: params.workspaceDir,
|
|
567
|
+
...resolved.model ? { model: resolved.model } : {},
|
|
568
|
+
...resolved.modelProvider ? { modelProvider: resolved.modelProvider } : {},
|
|
569
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
570
|
+
...buildThreadRequestRuntimeOptions(params, resolved),
|
|
571
|
+
developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
|
|
572
|
+
experimentalRawEvents: true
|
|
573
|
+
}, requestOptions),
|
|
574
|
+
onClientChange: (client) => {
|
|
575
|
+
resolved.client = client;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
495
578
|
}
|
|
496
579
|
async function writeThreadBindingFromResponse(params, resolved, response) {
|
|
580
|
+
assertCodexBindingMayBeReplaced(await params.bindingStore.read(params.identity), "storing a conversation-bound Codex thread");
|
|
497
581
|
const runtimeApprovalPolicy = typeof resolved.runtime.approvalPolicy === "string" ? resolved.runtime.approvalPolicy : void 0;
|
|
498
582
|
if (!await params.bindingStore.mutate(params.identity, {
|
|
499
583
|
kind: "set",
|
|
500
584
|
binding: {
|
|
501
585
|
threadId: response.thread.id,
|
|
586
|
+
clientId: resolved.client.getInstanceId(),
|
|
502
587
|
cwd: response.thread.cwd ?? params.workspaceDir,
|
|
503
588
|
authProfileId: params.authProfileId,
|
|
504
589
|
model: response.model ?? resolved.model ?? params.model,
|
|
@@ -516,25 +601,34 @@ async function writeThreadBindingFromResponse(params, resolved, response) {
|
|
|
516
601
|
})) throw new Error("Codex conversation binding changed while storing its thread.");
|
|
517
602
|
}
|
|
518
603
|
async function attachExistingThread(params) {
|
|
604
|
+
assertCodexBindingMayBeReplaced(await params.bindingStore.read(params.identity), "attaching a conversation-bound Codex thread");
|
|
519
605
|
const resolved = await resolveThreadBindingRuntime(params);
|
|
520
606
|
try {
|
|
521
|
-
await writeThreadBindingFromResponse(params, resolved, resolved.runtime.networkProxy ? await requestNewConversationBindingThread(params, resolved) : await
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
607
|
+
await writeThreadBindingFromResponse(params, resolved, resolved.runtime.networkProxy ? await requestNewConversationBindingThread(params, resolved) : await withLeasedCodexAppServerClientStartSelectionRetry({
|
|
608
|
+
lease: resolved.clientLease,
|
|
609
|
+
options: resolved.clientOptions,
|
|
610
|
+
run: async (client, requestOptions) => await client.request(CODEX_CONTROL_METHODS.resumeThread, {
|
|
611
|
+
threadId: params.threadId,
|
|
612
|
+
...resolved.model ? { model: resolved.model } : {},
|
|
613
|
+
...resolved.modelProvider ? { modelProvider: resolved.modelProvider } : {},
|
|
614
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
615
|
+
...buildThreadRequestRuntimeOptions(params, resolved)
|
|
616
|
+
}, requestOptions),
|
|
617
|
+
onClientChange: (client) => {
|
|
618
|
+
resolved.client = client;
|
|
619
|
+
}
|
|
620
|
+
}));
|
|
528
621
|
} finally {
|
|
529
|
-
|
|
622
|
+
releaseCodexAppServerClientLease(resolved.clientLease);
|
|
530
623
|
}
|
|
531
624
|
}
|
|
532
625
|
async function createThread(params) {
|
|
626
|
+
assertCodexBindingMayBeReplaced(await params.bindingStore.read(params.identity), "creating a conversation-bound Codex thread");
|
|
533
627
|
const resolved = await resolveThreadBindingRuntime(params);
|
|
534
628
|
try {
|
|
535
629
|
await writeThreadBindingFromResponse(params, resolved, await requestNewConversationBindingThread(params, resolved));
|
|
536
630
|
} finally {
|
|
537
|
-
|
|
631
|
+
releaseCodexAppServerClientLease(resolved.clientLease);
|
|
538
632
|
}
|
|
539
633
|
}
|
|
540
634
|
async function runBoundTurn(params) {
|
|
@@ -545,6 +639,7 @@ async function runBoundTurn(params) {
|
|
|
545
639
|
const identity = conversationBindingIdentity(params.data);
|
|
546
640
|
const binding = await params.bindingStore.read(identity);
|
|
547
641
|
if (!binding?.threadId) throw new Error("bound Codex conversation has no thread binding");
|
|
642
|
+
assertCodexBindingMayBeReplaced(binding, "running a conversation-bound Codex thread");
|
|
548
643
|
let threadId = binding.threadId;
|
|
549
644
|
const workspaceDir = binding.cwd || params.data.workspaceDir;
|
|
550
645
|
const reviewerModelProvider = resolveModelBackedReviewerPolicyProvider({
|
|
@@ -597,33 +692,43 @@ async function runBoundTurn(params) {
|
|
|
597
692
|
authProfileId: binding.authProfileId,
|
|
598
693
|
...agentLookup
|
|
599
694
|
}) : void 0;
|
|
600
|
-
const
|
|
695
|
+
const clientOptions = {
|
|
601
696
|
startOptions: runtime.start,
|
|
602
697
|
timeoutMs: runtime.requestTimeoutMs,
|
|
603
698
|
authProfileId: binding.authProfileId,
|
|
604
699
|
...agentLookup
|
|
605
|
-
}
|
|
700
|
+
};
|
|
701
|
+
let client = await getLeasedSharedCodexAppServerClient(clientOptions);
|
|
702
|
+
const clientLease = { client };
|
|
606
703
|
let notificationCleanup = () => void 0;
|
|
607
704
|
let requestCleanup = () => void 0;
|
|
608
705
|
try {
|
|
609
706
|
if (networkProxyBindingChanged) {
|
|
610
|
-
const response = assertCodexThreadStartResponse(await
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
707
|
+
const response = assertCodexThreadStartResponse(await withLeasedCodexAppServerClientStartSelectionRetry({
|
|
708
|
+
lease: clientLease,
|
|
709
|
+
options: clientOptions,
|
|
710
|
+
run: async (requestClient, requestOptions) => await requestClient.request("thread/start", {
|
|
711
|
+
cwd: workspaceDir,
|
|
712
|
+
...modelSelection?.model ? { model: modelSelection.model } : {},
|
|
713
|
+
...modelSelection?.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
714
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
715
|
+
approvalPolicy,
|
|
716
|
+
approvalsReviewer: modelScopedRuntime.approvalsReviewer,
|
|
717
|
+
...modelScopedRuntime.networkProxy ? { config: modelScopedRuntime.networkProxy.configPatch } : { sandbox },
|
|
718
|
+
...serviceTier ? { serviceTier } : {},
|
|
719
|
+
developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
|
|
720
|
+
experimentalRawEvents: true
|
|
721
|
+
}, requestOptions),
|
|
722
|
+
onClientChange: (nextClient) => {
|
|
723
|
+
client = nextClient;
|
|
724
|
+
}
|
|
725
|
+
}));
|
|
622
726
|
threadId = response.thread.id;
|
|
623
727
|
if (!await params.bindingStore.mutate(identity, {
|
|
624
728
|
kind: "set",
|
|
625
729
|
binding: {
|
|
626
730
|
threadId,
|
|
731
|
+
clientId: client.getInstanceId(),
|
|
627
732
|
cwd: response.thread.cwd ?? workspaceDir,
|
|
628
733
|
authProfileId: binding.authProfileId,
|
|
629
734
|
model: response.model ?? modelSelection?.model ?? binding.model,
|
|
@@ -643,6 +748,46 @@ async function runBoundTurn(params) {
|
|
|
643
748
|
}
|
|
644
749
|
})) throw new Error("Codex conversation binding changed while rotating its thread.");
|
|
645
750
|
useStickyNetworkProfile = modelScopedRuntime.networkProxy !== void 0;
|
|
751
|
+
} else if (binding.clientId !== client.getInstanceId()) {
|
|
752
|
+
const response = await withLeasedCodexAppServerClientStartSelectionRetry({
|
|
753
|
+
lease: clientLease,
|
|
754
|
+
options: clientOptions,
|
|
755
|
+
run: async (requestClient, requestOptions) => await resumeCodexAppServerThread({
|
|
756
|
+
client: requestClient,
|
|
757
|
+
abandonClient: async () => {
|
|
758
|
+
await requestClient.closeAndWait();
|
|
759
|
+
},
|
|
760
|
+
request: {
|
|
761
|
+
threadId,
|
|
762
|
+
...modelSelection?.model ? { model: modelSelection.model } : {},
|
|
763
|
+
...modelSelection?.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
764
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
765
|
+
approvalPolicy,
|
|
766
|
+
approvalsReviewer: modelScopedRuntime.approvalsReviewer,
|
|
767
|
+
...codexConversationSandboxOrPermissions(modelScopedRuntime, sandbox),
|
|
768
|
+
...serviceTier ? { serviceTier } : {}
|
|
769
|
+
},
|
|
770
|
+
...requestOptions
|
|
771
|
+
}),
|
|
772
|
+
onClientChange: (nextClient) => {
|
|
773
|
+
client = nextClient;
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
threadId = response.thread.id;
|
|
777
|
+
if (!await params.bindingStore.mutate(identity, {
|
|
778
|
+
kind: "patch",
|
|
779
|
+
threadId: binding.threadId,
|
|
780
|
+
patch: {
|
|
781
|
+
clientId: client.getInstanceId(),
|
|
782
|
+
cwd: response.thread.cwd ?? binding.cwd,
|
|
783
|
+
model: response.model ?? modelSelection?.model ?? binding.model,
|
|
784
|
+
modelProvider: normalizeCodexAppServerBindingModelProvider({
|
|
785
|
+
authProfileId: binding.authProfileId,
|
|
786
|
+
modelProvider: response.modelProvider ?? modelSelection?.modelProvider ?? binding.modelProvider,
|
|
787
|
+
...agentLookup
|
|
788
|
+
})
|
|
789
|
+
}
|
|
790
|
+
})) throw new Error("Codex conversation binding changed while resuming on a new client.");
|
|
646
791
|
}
|
|
647
792
|
const collector = createCodexConversationTurnCollector(threadId);
|
|
648
793
|
notificationCleanup = client.addNotificationHandler((notification) => collector.handleNotification(notification));
|
|
@@ -683,6 +828,7 @@ async function runBoundTurn(params) {
|
|
|
683
828
|
}, { timeoutMs: runtime.requestTimeoutMs })).turn.id;
|
|
684
829
|
const activeCleanup = trackCodexConversationActiveTurn({
|
|
685
830
|
identity,
|
|
831
|
+
client,
|
|
686
832
|
threadId,
|
|
687
833
|
turnId
|
|
688
834
|
});
|
|
@@ -691,7 +837,7 @@ async function runBoundTurn(params) {
|
|
|
691
837
|
} finally {
|
|
692
838
|
notificationCleanup();
|
|
693
839
|
requestCleanup();
|
|
694
|
-
|
|
840
|
+
releaseCodexAppServerClientLease(clientLease);
|
|
695
841
|
}
|
|
696
842
|
}
|
|
697
843
|
function assertNativeConversationApprovalPolicySupported(params) {
|
|
@@ -720,6 +866,8 @@ async function prepareConversationBinding(params, options = {}) {
|
|
|
720
866
|
config: params.config
|
|
721
867
|
}) : void 0;
|
|
722
868
|
const sourceBinding = sourceIdentity ? await params.bindingStore.read(sourceIdentity) : void 0;
|
|
869
|
+
assertCodexBindingMayBeReplaced(current, "initializing a conversation-bound Codex thread");
|
|
870
|
+
assertCodexBindingMayBeReplaced(sourceBinding, "transferring a session into a conversation-bound Codex thread");
|
|
723
871
|
const inherited = current ?? sourceBinding;
|
|
724
872
|
const execPolicy = resolveConversationExecPolicy({
|
|
725
873
|
config: params.config,
|
|
@@ -882,6 +1030,44 @@ function conversationBindingIdentity(data) {
|
|
|
882
1030
|
bindingId: data.bindingId
|
|
883
1031
|
};
|
|
884
1032
|
}
|
|
1033
|
+
const codexConversationBindingRuntime = {
|
|
1034
|
+
startThread: startCodexConversationThread,
|
|
1035
|
+
handleInboundClaim: handleCodexConversationInboundClaim,
|
|
1036
|
+
handleBindingResolved: handleCodexConversationBindingResolved
|
|
1037
|
+
};
|
|
1038
|
+
//#endregion
|
|
1039
|
+
//#region extensions/codex/src/migration/apply-report.ts
|
|
1040
|
+
function codexPluginActivationReportState(result) {
|
|
1041
|
+
switch (result.reason) {
|
|
1042
|
+
case "already_active":
|
|
1043
|
+
case "installed": return {
|
|
1044
|
+
installed: true,
|
|
1045
|
+
enabled: true
|
|
1046
|
+
};
|
|
1047
|
+
case "auth_required": return {
|
|
1048
|
+
installed: true,
|
|
1049
|
+
enabled: false
|
|
1050
|
+
};
|
|
1051
|
+
case "disabled":
|
|
1052
|
+
case "marketplace_missing":
|
|
1053
|
+
case "plugin_missing": return {
|
|
1054
|
+
installed: false,
|
|
1055
|
+
enabled: false
|
|
1056
|
+
};
|
|
1057
|
+
case "refresh_failed": return {
|
|
1058
|
+
installed: true,
|
|
1059
|
+
enabled: false
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
return result.reason;
|
|
1063
|
+
}
|
|
1064
|
+
function sanitizeAppsNeedingAuth(apps) {
|
|
1065
|
+
return apps.map((app) => ({
|
|
1066
|
+
id: app.id,
|
|
1067
|
+
name: app.name,
|
|
1068
|
+
needsAuth: app.needsAuth
|
|
1069
|
+
}));
|
|
1070
|
+
}
|
|
885
1071
|
//#endregion
|
|
886
1072
|
//#region extensions/codex/src/migration/helpers.ts
|
|
887
1073
|
async function exists(filePath) {
|
|
@@ -1272,16 +1458,112 @@ async function buildCodexAuthConfigPatchItems(params) {
|
|
|
1272
1458
|
return items;
|
|
1273
1459
|
}
|
|
1274
1460
|
//#endregion
|
|
1275
|
-
//#region extensions/codex/src/migration/
|
|
1461
|
+
//#region extensions/codex/src/migration/memory-plan.ts
|
|
1462
|
+
const MIGRATION_REASON_TARGET_NOT_REGULAR = "target is not a regular file";
|
|
1463
|
+
async function lstatIfExists(filePath) {
|
|
1464
|
+
try {
|
|
1465
|
+
return await fs.lstat(filePath);
|
|
1466
|
+
} catch (error) {
|
|
1467
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : void 0;
|
|
1468
|
+
if (code === "ENOENT" || code === "ENOTDIR") return;
|
|
1469
|
+
throw error;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
async function assertSafeMemoryDestination(params) {
|
|
1473
|
+
const [canonicalSource, canonicalWorkspace, canonicalTarget] = await Promise.all([
|
|
1474
|
+
fs.realpath(path.dirname(params.source)),
|
|
1475
|
+
canonicalPathFromExistingAncestor(params.workspaceDir),
|
|
1476
|
+
canonicalPathFromExistingAncestor(params.target)
|
|
1477
|
+
]);
|
|
1478
|
+
if (!isPathInside(canonicalWorkspace, canonicalTarget)) throw new Error("Codex memory import destination must stay in the selected workspace.");
|
|
1479
|
+
if (isPathInside(canonicalSource, canonicalTarget) || isPathInside(canonicalTarget, canonicalSource)) throw new Error("Codex memory source and OpenClaw import destination must be separate paths.");
|
|
1480
|
+
}
|
|
1481
|
+
async function buildCodexMemoryItems(params) {
|
|
1482
|
+
const items = [];
|
|
1483
|
+
for (const memory of params.memoryFiles) {
|
|
1484
|
+
const target = path.join(params.workspaceDir, "memory", "imports", "codex", path.basename(memory.path));
|
|
1485
|
+
const targetStat = await lstatIfExists(target);
|
|
1486
|
+
const targetExists = targetStat !== void 0;
|
|
1487
|
+
const targetNotRegular = targetExists && !targetStat.isFile();
|
|
1488
|
+
if (!targetNotRegular) await assertSafeMemoryDestination({
|
|
1489
|
+
source: memory.path,
|
|
1490
|
+
workspaceDir: params.workspaceDir,
|
|
1491
|
+
target
|
|
1492
|
+
});
|
|
1493
|
+
const targetConflict = targetNotRegular || targetExists && !params.overwrite;
|
|
1494
|
+
items.push(createMigrationItem({
|
|
1495
|
+
id: memory.id,
|
|
1496
|
+
kind: "memory",
|
|
1497
|
+
action: "copy",
|
|
1498
|
+
source: memory.path,
|
|
1499
|
+
target,
|
|
1500
|
+
status: targetConflict ? "conflict" : "planned",
|
|
1501
|
+
reason: targetNotRegular ? MIGRATION_REASON_TARGET_NOT_REGULAR : targetConflict ? MIGRATION_REASON_TARGET_EXISTS : void 0,
|
|
1502
|
+
message: "Copy consolidated Codex memory into the OpenClaw memory index.",
|
|
1503
|
+
details: {
|
|
1504
|
+
sourceType: "codex-memory",
|
|
1505
|
+
sourceLabel: memory.label,
|
|
1506
|
+
collectionId: "codex",
|
|
1507
|
+
collectionLabel: "Codex",
|
|
1508
|
+
relativePath: path.basename(memory.path)
|
|
1509
|
+
}
|
|
1510
|
+
}));
|
|
1511
|
+
}
|
|
1512
|
+
return items;
|
|
1513
|
+
}
|
|
1514
|
+
//#endregion
|
|
1515
|
+
//#region extensions/codex/src/migration/skill-plan.ts
|
|
1516
|
+
function uniqueSkillName(skill, counts) {
|
|
1517
|
+
const base = sanitizeName(skill.name) || "codex-skill";
|
|
1518
|
+
if ((counts.get(base) ?? 0) <= 1) return base;
|
|
1519
|
+
return sanitizeName([
|
|
1520
|
+
"codex",
|
|
1521
|
+
sanitizeName(path.basename(path.dirname(skill.source))),
|
|
1522
|
+
base
|
|
1523
|
+
].filter(Boolean).join("-")) || base;
|
|
1524
|
+
}
|
|
1525
|
+
async function buildCodexSkillItems(params) {
|
|
1526
|
+
const baseCounts = /* @__PURE__ */ new Map();
|
|
1527
|
+
for (const skill of params.skills) {
|
|
1528
|
+
const base = sanitizeName(skill.name) || "codex-skill";
|
|
1529
|
+
baseCounts.set(base, (baseCounts.get(base) ?? 0) + 1);
|
|
1530
|
+
}
|
|
1531
|
+
const resolvedCounts = /* @__PURE__ */ new Map();
|
|
1532
|
+
const planned = params.skills.map((skill) => {
|
|
1533
|
+
const name = uniqueSkillName(skill, baseCounts);
|
|
1534
|
+
resolvedCounts.set(name, (resolvedCounts.get(name) ?? 0) + 1);
|
|
1535
|
+
return {
|
|
1536
|
+
skill,
|
|
1537
|
+
name,
|
|
1538
|
+
target: path.join(params.workspaceDir, "skills", name)
|
|
1539
|
+
};
|
|
1540
|
+
});
|
|
1541
|
+
const items = [];
|
|
1542
|
+
for (const item of planned) {
|
|
1543
|
+
const collides = (resolvedCounts.get(item.name) ?? 0) > 1;
|
|
1544
|
+
const targetExists = await exists(item.target);
|
|
1545
|
+
items.push(createMigrationItem({
|
|
1546
|
+
id: `skill:${item.name}`,
|
|
1547
|
+
kind: "skill",
|
|
1548
|
+
action: "copy",
|
|
1549
|
+
source: item.skill.source,
|
|
1550
|
+
target: item.target,
|
|
1551
|
+
status: collides ? "conflict" : targetExists && !params.overwrite ? "conflict" : "planned",
|
|
1552
|
+
reason: collides ? `multiple Codex skills normalize to "${item.name}"` : targetExists && !params.overwrite ? MIGRATION_REASON_TARGET_EXISTS : void 0,
|
|
1553
|
+
message: `Copy ${item.skill.sourceLabel} into this OpenClaw agent workspace.`,
|
|
1554
|
+
details: {
|
|
1555
|
+
skillName: item.name,
|
|
1556
|
+
sourceLabel: item.skill.sourceLabel
|
|
1557
|
+
}
|
|
1558
|
+
}));
|
|
1559
|
+
}
|
|
1560
|
+
return items;
|
|
1561
|
+
}
|
|
1562
|
+
//#endregion
|
|
1563
|
+
//#region extensions/codex/src/migration/source-files.ts
|
|
1276
1564
|
const SKILL_FILENAME = "SKILL.md";
|
|
1277
1565
|
const MAX_SCAN_DEPTH = 6;
|
|
1278
1566
|
const MAX_DISCOVERED_DIRS = 2e3;
|
|
1279
|
-
function defaultCodexHome() {
|
|
1280
|
-
return resolveHomePath(process.env.CODEX_HOME?.trim() || "~/.codex");
|
|
1281
|
-
}
|
|
1282
|
-
function personalAgentsSkillsDir() {
|
|
1283
|
-
return path.join(resolveUserHomeDir(), ".agents", "skills");
|
|
1284
|
-
}
|
|
1285
1567
|
async function safeReadDir(dir) {
|
|
1286
1568
|
return await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1287
1569
|
}
|
|
@@ -1300,10 +1582,7 @@ async function discoverSkillDirs(params) {
|
|
|
1300
1582
|
});
|
|
1301
1583
|
return;
|
|
1302
1584
|
}
|
|
1303
|
-
for (const entry of await safeReadDir(dir))
|
|
1304
|
-
if (!entry.isDirectory()) continue;
|
|
1305
|
-
await visit(path.join(dir, entry.name), depth + 1);
|
|
1306
|
-
}
|
|
1585
|
+
for (const entry of await safeReadDir(dir)) if (entry.isDirectory()) await visit(path.join(dir, entry.name), depth + 1);
|
|
1307
1586
|
}
|
|
1308
1587
|
await visit(params.root, 0);
|
|
1309
1588
|
return discovered;
|
|
@@ -1317,9 +1596,9 @@ async function discoverPluginDirs(codexHome) {
|
|
|
1317
1596
|
const manifestPath = path.join(dir, ".codex-plugin", "plugin.json");
|
|
1318
1597
|
if (await exists(manifestPath)) {
|
|
1319
1598
|
const manifest = await readJsonObject(manifestPath);
|
|
1320
|
-
const
|
|
1599
|
+
const manifestName = typeof manifest.name === "string" ? manifest.name.trim() : "";
|
|
1321
1600
|
discovered.set(dir, {
|
|
1322
|
-
name,
|
|
1601
|
+
name: manifestName || path.basename(dir),
|
|
1323
1602
|
source: dir,
|
|
1324
1603
|
manifestPath,
|
|
1325
1604
|
sourceKind: "cache",
|
|
@@ -1328,14 +1607,51 @@ async function discoverPluginDirs(codexHome) {
|
|
|
1328
1607
|
});
|
|
1329
1608
|
return;
|
|
1330
1609
|
}
|
|
1331
|
-
for (const entry of await safeReadDir(dir))
|
|
1332
|
-
if (!entry.isDirectory()) continue;
|
|
1333
|
-
await visit(path.join(dir, entry.name), depth + 1);
|
|
1334
|
-
}
|
|
1610
|
+
for (const entry of await safeReadDir(dir)) if (entry.isDirectory()) await visit(path.join(dir, entry.name), depth + 1);
|
|
1335
1611
|
}
|
|
1336
1612
|
await visit(root, 0);
|
|
1337
1613
|
return [...discovered.values()].toSorted((a, b) => a.source.localeCompare(b.source));
|
|
1338
1614
|
}
|
|
1615
|
+
async function discoverCodexMemoryFile(candidate) {
|
|
1616
|
+
try {
|
|
1617
|
+
const stat = await fs.lstat(candidate.path);
|
|
1618
|
+
if (stat.isSymbolicLink()) throw new Error(`Codex memory source must not be a symbolic link: ${candidate.path}`);
|
|
1619
|
+
if (!stat.isFile()) throw new Error(`Codex memory source must be a regular file: ${candidate.path}`);
|
|
1620
|
+
return candidate;
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return;
|
|
1623
|
+
throw error;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
async function discoverCodexMemorySources(codexHome) {
|
|
1627
|
+
const memoriesDir = path.join(codexHome, "memories");
|
|
1628
|
+
const memoryFiles = (await Promise.all([{
|
|
1629
|
+
id: "memory:codex:MEMORY.md",
|
|
1630
|
+
label: "Codex consolidated memory",
|
|
1631
|
+
name: "MEMORY.md"
|
|
1632
|
+
}, {
|
|
1633
|
+
id: "memory:codex:memory_summary.md",
|
|
1634
|
+
label: "Codex memory summary",
|
|
1635
|
+
name: "memory_summary.md"
|
|
1636
|
+
}].map(async (candidate) => await discoverCodexMemoryFile({
|
|
1637
|
+
id: candidate.id,
|
|
1638
|
+
label: candidate.label,
|
|
1639
|
+
path: path.join(memoriesDir, candidate.name)
|
|
1640
|
+
})))).filter((entry) => entry !== void 0);
|
|
1641
|
+
return {
|
|
1642
|
+
...await isDirectory(memoriesDir) ? { memoriesDir } : {},
|
|
1643
|
+
memoryFiles
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
//#endregion
|
|
1647
|
+
//#region extensions/codex/src/migration/source.ts
|
|
1648
|
+
function defaultCodexHome() {
|
|
1649
|
+
const configuredHome = process.env.CODEX_HOME;
|
|
1650
|
+
return resolveHomePath(configuredHome !== void 0 && configuredHome.length > 0 ? configuredHome : "~/.codex");
|
|
1651
|
+
}
|
|
1652
|
+
function personalAgentsSkillsDir() {
|
|
1653
|
+
return path.join(resolveUserHomeDir(), ".agents", "skills");
|
|
1654
|
+
}
|
|
1339
1655
|
async function discoverInstalledCuratedPlugins(codexHome, options = {}) {
|
|
1340
1656
|
const requestOptions = { startOptions: sourceCodexAppServerStartOptions(codexHome) };
|
|
1341
1657
|
try {
|
|
@@ -1366,6 +1682,7 @@ function sourceCodexAppServerStartOptions(codexHome) {
|
|
|
1366
1682
|
transport: "stdio",
|
|
1367
1683
|
command: "codex",
|
|
1368
1684
|
commandSource: "managed",
|
|
1685
|
+
managedCommandOrder: "desktop-first",
|
|
1369
1686
|
args: [
|
|
1370
1687
|
"app-server",
|
|
1371
1688
|
"--listen",
|
|
@@ -1612,38 +1929,39 @@ async function discoverCodexSource(inputOrOptions) {
|
|
|
1612
1929
|
const authPath = path.join(codexHome, "auth.json");
|
|
1613
1930
|
const modelsCachePath = path.join(codexHome, "models_cache.json");
|
|
1614
1931
|
const hooksPath = path.join(codexHome, "hooks", "hooks.json");
|
|
1615
|
-
const
|
|
1932
|
+
const { memoriesDir, memoryFiles } = await discoverCodexMemorySources(codexHome);
|
|
1933
|
+
const codexSkills = options.memoryOnly ? [] : await discoverSkillDirs({
|
|
1616
1934
|
root: codexSkillsDir,
|
|
1617
1935
|
sourceLabel: "Codex skill",
|
|
1618
1936
|
excludeSystem: true
|
|
1619
1937
|
});
|
|
1620
|
-
const personalAgentSkills = await discoverSkillDirs({
|
|
1938
|
+
const personalAgentSkills = options.memoryOnly ? [] : await discoverSkillDirs({
|
|
1621
1939
|
root: agentsSkillsDir,
|
|
1622
1940
|
sourceLabel: "personal AgentSkill"
|
|
1623
1941
|
});
|
|
1624
|
-
const sourcePluginDiscovery = await discoverInstalledCuratedPlugins(codexHome, options);
|
|
1942
|
+
const sourcePluginDiscovery = options.memoryOnly ? { plugins: [] } : await discoverInstalledCuratedPlugins(codexHome, options);
|
|
1625
1943
|
const sourcePluginNames = new Set(sourcePluginDiscovery.plugins.flatMap((plugin) => plugin.pluginName ? [plugin.pluginName] : []));
|
|
1626
|
-
const cachedPlugins = (await discoverPluginDirs(codexHome)).filter((plugin) => {
|
|
1944
|
+
const cachedPlugins = (options.memoryOnly ? [] : await discoverPluginDirs(codexHome)).filter((plugin) => {
|
|
1627
1945
|
const normalizedName = sanitizePluginName(plugin.name);
|
|
1628
1946
|
return !sourcePluginNames.has(normalizedName);
|
|
1629
1947
|
});
|
|
1630
1948
|
const plugins = [...sourcePluginDiscovery.plugins, ...cachedPlugins].toSorted((a, b) => a.source.localeCompare(b.source));
|
|
1631
1949
|
const archivePaths = [];
|
|
1632
|
-
if (await exists(configPath)) archivePaths.push({
|
|
1950
|
+
if (!options.memoryOnly && await exists(configPath)) archivePaths.push({
|
|
1633
1951
|
id: "archive:config.toml",
|
|
1634
1952
|
path: configPath,
|
|
1635
1953
|
relativePath: "config.toml",
|
|
1636
1954
|
message: "Codex config is archived for manual review; it is not activated automatically"
|
|
1637
1955
|
});
|
|
1638
|
-
if (await exists(hooksPath)) archivePaths.push({
|
|
1956
|
+
if (!options.memoryOnly && await exists(hooksPath)) archivePaths.push({
|
|
1639
1957
|
id: "archive:hooks/hooks.json",
|
|
1640
1958
|
path: hooksPath,
|
|
1641
1959
|
relativePath: "hooks/hooks.json",
|
|
1642
1960
|
message: "Codex native hooks are archived for manual review because they can execute commands"
|
|
1643
1961
|
});
|
|
1644
1962
|
const skills = [...codexSkills, ...personalAgentSkills].toSorted((a, b) => a.source.localeCompare(b.source));
|
|
1645
|
-
const hasAuth = await exists(authPath);
|
|
1646
|
-
const high = Boolean(codexSkills.length || plugins.length || archivePaths.length || hasAuth);
|
|
1963
|
+
const hasAuth = !options.memoryOnly && await exists(authPath);
|
|
1964
|
+
const high = Boolean(memoryFiles.length || codexSkills.length || plugins.length || archivePaths.length || hasAuth);
|
|
1647
1965
|
const medium = personalAgentSkills.length > 0;
|
|
1648
1966
|
return {
|
|
1649
1967
|
root: codexHome,
|
|
@@ -1655,6 +1973,8 @@ async function discoverCodexSource(inputOrOptions) {
|
|
|
1655
1973
|
...hasAuth ? { authPath } : {},
|
|
1656
1974
|
...await exists(modelsCachePath) ? { modelsCachePath } : {},
|
|
1657
1975
|
...await exists(hooksPath) ? { hooksPath } : {},
|
|
1976
|
+
...memoriesDir ? { memoriesDir } : {},
|
|
1977
|
+
memoryFiles,
|
|
1658
1978
|
skills,
|
|
1659
1979
|
plugins,
|
|
1660
1980
|
...sourcePluginDiscovery.error ? { pluginDiscoveryError: sourcePluginDiscovery.error } : {},
|
|
@@ -1671,7 +1991,7 @@ function sanitizePluginName(value) {
|
|
|
1671
1991
|
//#region extensions/codex/src/migration/targets.ts
|
|
1672
1992
|
function resolveCodexMigrationTargets(ctx) {
|
|
1673
1993
|
const cfg = ctx.config;
|
|
1674
|
-
const agentId = resolveDefaultAgentId(cfg);
|
|
1994
|
+
const agentId = ctx.targetAgentId ?? resolveDefaultAgentId(cfg);
|
|
1675
1995
|
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
|
1676
1996
|
const configuredAgentDir = resolveAgentConfig(cfg, agentId)?.agentDir?.trim();
|
|
1677
1997
|
return {
|
|
@@ -1702,69 +2022,23 @@ const CODEX_PLUGIN_NATIVE_CONFIG_PATH = [
|
|
|
1702
2022
|
];
|
|
1703
2023
|
const MIGRATION_REASON_PLUGIN_EXISTS = "plugin exists";
|
|
1704
2024
|
const CODEX_PLUGIN_SOURCE_APP_VERIFICATION_UNVERIFIED = "not_run";
|
|
1705
|
-
function
|
|
1706
|
-
const base = sanitizeName(
|
|
2025
|
+
function uniquePluginConfigKey(plugin, counts, usedCounts) {
|
|
2026
|
+
const base = sanitizeName(plugin.pluginName ?? plugin.name) || "codex-plugin";
|
|
1707
2027
|
if ((counts.get(base) ?? 0) <= 1) return base;
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
base
|
|
1712
|
-
].filter(Boolean).join("-")) || base;
|
|
2028
|
+
const next = (usedCounts.get(base) ?? 0) + 1;
|
|
2029
|
+
usedCounts.set(base, next);
|
|
2030
|
+
return sanitizeName(`${base}-${next}`) || base;
|
|
1713
2031
|
}
|
|
1714
|
-
|
|
1715
|
-
const
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
return {
|
|
1725
|
-
skill,
|
|
1726
|
-
name,
|
|
1727
|
-
target: path.join(params.workspaceDir, "skills", name)
|
|
1728
|
-
};
|
|
1729
|
-
});
|
|
1730
|
-
const items = [];
|
|
1731
|
-
for (const item of planned) {
|
|
1732
|
-
const collides = (resolvedCounts.get(item.name) ?? 0) > 1;
|
|
1733
|
-
const targetExists = await exists(item.target);
|
|
1734
|
-
items.push(createMigrationItem({
|
|
1735
|
-
id: `skill:${item.name}`,
|
|
1736
|
-
kind: "skill",
|
|
1737
|
-
action: "copy",
|
|
1738
|
-
source: item.skill.source,
|
|
1739
|
-
target: item.target,
|
|
1740
|
-
status: collides ? "conflict" : targetExists && !params.overwrite ? "conflict" : "planned",
|
|
1741
|
-
reason: collides ? `multiple Codex skills normalize to "${item.name}"` : targetExists && !params.overwrite ? MIGRATION_REASON_TARGET_EXISTS : void 0,
|
|
1742
|
-
message: `Copy ${item.skill.sourceLabel} into this OpenClaw agent workspace.`,
|
|
1743
|
-
details: {
|
|
1744
|
-
skillName: item.name,
|
|
1745
|
-
sourceLabel: item.skill.sourceLabel
|
|
1746
|
-
}
|
|
1747
|
-
}));
|
|
1748
|
-
}
|
|
1749
|
-
return items;
|
|
1750
|
-
}
|
|
1751
|
-
function uniquePluginConfigKey(plugin, counts, usedCounts) {
|
|
1752
|
-
const base = sanitizeName(plugin.pluginName ?? plugin.name) || "codex-plugin";
|
|
1753
|
-
if ((counts.get(base) ?? 0) <= 1) return base;
|
|
1754
|
-
const next = (usedCounts.get(base) ?? 0) + 1;
|
|
1755
|
-
usedCounts.set(base, next);
|
|
1756
|
-
return sanitizeName(`${base}-${next}`) || base;
|
|
1757
|
-
}
|
|
1758
|
-
function readExistingCodexPluginEntries(config) {
|
|
1759
|
-
const entries = readMigrationConfigPath(config, [...CODEX_PLUGIN_NATIVE_CONFIG_PATH, "plugins"]);
|
|
1760
|
-
return isRecord(entries) ? entries : {};
|
|
1761
|
-
}
|
|
1762
|
-
function hasExistingCodexPluginEntry(existingEntries, configKey, pluginName, nextEntry) {
|
|
1763
|
-
const existingEntry = existingEntries[configKey];
|
|
1764
|
-
if (existingEntry !== void 0) return !isLegacyDestructivePolicyRepair(existingEntry, nextEntry);
|
|
1765
|
-
return Object.values(existingEntries).some((entry) => {
|
|
1766
|
-
if (!isRecord(entry)) return false;
|
|
1767
|
-
return entry.pluginName === pluginName;
|
|
2032
|
+
function readExistingCodexPluginEntries(config) {
|
|
2033
|
+
const entries = readMigrationConfigPath(config, [...CODEX_PLUGIN_NATIVE_CONFIG_PATH, "plugins"]);
|
|
2034
|
+
return isRecord(entries) ? entries : {};
|
|
2035
|
+
}
|
|
2036
|
+
function hasExistingCodexPluginEntry(existingEntries, configKey, pluginName, nextEntry) {
|
|
2037
|
+
const existingEntry = existingEntries[configKey];
|
|
2038
|
+
if (existingEntry !== void 0) return !isLegacyDestructivePolicyRepair(existingEntry, nextEntry);
|
|
2039
|
+
return Object.values(existingEntries).some((entry) => {
|
|
2040
|
+
if (!isRecord(entry)) return false;
|
|
2041
|
+
return entry.pluginName === pluginName;
|
|
1768
2042
|
});
|
|
1769
2043
|
}
|
|
1770
2044
|
function isLegacyDestructivePolicyRepair(existing, nextEntry) {
|
|
@@ -1851,7 +2125,7 @@ function buildPluginItems(ctx, plugins) {
|
|
|
1851
2125
|
id: `plugin:${sanitizeName(plugin.name) || sanitizeName(path.basename(plugin.source))}:${manualIndex}`,
|
|
1852
2126
|
source: plugin.source,
|
|
1853
2127
|
message: plugin.message ?? `Codex native plugin "${plugin.name}" was found but not activated automatically.`,
|
|
1854
|
-
recommendation: "Review the plugin bundle first, then install trusted compatible plugins with openclaw plugins install <path
|
|
2128
|
+
recommendation: "Review the plugin bundle first, then install trusted compatible plugins with openclaw plugins install <path> --force."
|
|
1855
2129
|
}));
|
|
1856
2130
|
}
|
|
1857
2131
|
return items;
|
|
@@ -1950,35 +2224,44 @@ function buildPluginConfigItem(ctx, pluginItems) {
|
|
|
1950
2224
|
}
|
|
1951
2225
|
async function buildCodexMigrationPlan(ctx) {
|
|
1952
2226
|
const targets = resolveCodexMigrationTargets(ctx);
|
|
2227
|
+
const memoryOnly = ctx.itemKinds !== void 0 && ctx.itemKinds.length > 0 && ctx.itemKinds.every((kind) => kind === "memory");
|
|
1953
2228
|
const source = await discoverCodexSource({
|
|
1954
2229
|
input: ctx.source,
|
|
1955
|
-
|
|
2230
|
+
memoryOnly,
|
|
2231
|
+
evaluatePluginMigrationEligibility: !memoryOnly,
|
|
1956
2232
|
verifyPluginApps: shouldVerifyPluginApps(ctx)
|
|
1957
2233
|
});
|
|
1958
2234
|
if (!hasCodexSource(source)) throw new Error(`Codex state was not found at ${source.root}. Pass --from <path> if it lives elsewhere.`);
|
|
1959
2235
|
const items = [];
|
|
1960
|
-
items.push(...await
|
|
1961
|
-
|
|
1962
|
-
source,
|
|
1963
|
-
targets
|
|
1964
|
-
}));
|
|
1965
|
-
items.push(...await buildSkillItems({
|
|
1966
|
-
skills: source.skills,
|
|
2236
|
+
items.push(...await buildCodexMemoryItems({
|
|
2237
|
+
memoryFiles: source.memoryFiles,
|
|
1967
2238
|
workspaceDir: targets.workspaceDir,
|
|
1968
2239
|
overwrite: ctx.overwrite
|
|
1969
2240
|
}));
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
2241
|
+
if (!memoryOnly) {
|
|
2242
|
+
items.push(...await buildCodexAuthItems({
|
|
2243
|
+
ctx,
|
|
2244
|
+
source,
|
|
2245
|
+
targets
|
|
2246
|
+
}));
|
|
2247
|
+
items.push(...await buildCodexSkillItems({
|
|
2248
|
+
skills: source.skills,
|
|
2249
|
+
workspaceDir: targets.workspaceDir,
|
|
2250
|
+
overwrite: ctx.overwrite
|
|
2251
|
+
}));
|
|
2252
|
+
const pluginItems = buildPluginItems(ctx, source.plugins);
|
|
2253
|
+
items.push(...pluginItems);
|
|
2254
|
+
const pluginConfigItem = buildPluginConfigItem(ctx, pluginItems);
|
|
2255
|
+
if (pluginConfigItem) items.push(pluginConfigItem);
|
|
2256
|
+
for (const archivePath of source.archivePaths) items.push(createMigrationItem({
|
|
2257
|
+
id: archivePath.id,
|
|
2258
|
+
kind: "archive",
|
|
2259
|
+
action: "archive",
|
|
2260
|
+
source: archivePath.path,
|
|
2261
|
+
message: archivePath.message ?? "Archived in the migration report for manual review; not imported into live config.",
|
|
2262
|
+
details: { archiveRelativePath: archivePath.relativePath }
|
|
2263
|
+
}));
|
|
2264
|
+
}
|
|
1982
2265
|
const warnings = [
|
|
1983
2266
|
...!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."] : [],
|
|
1984
2267
|
...items.some((item) => item.status === "conflict") ? ["Conflicts were found. Re-run with --overwrite to replace conflicting migration targets after item-level backups."] : [],
|
|
@@ -1992,7 +2275,7 @@ async function buildCodexMigrationPlan(ctx) {
|
|
|
1992
2275
|
summary: summarizeMigrationItems(items),
|
|
1993
2276
|
items,
|
|
1994
2277
|
warnings,
|
|
1995
|
-
nextSteps: ["Run openclaw doctor after applying the migration.", "Review skipped or auth-required Codex plugin/config/hook items before exposing them in OpenClaw sessions."],
|
|
2278
|
+
nextSteps: memoryOnly ? [] : ["Run openclaw doctor after applying the migration.", "Review skipped or auth-required Codex plugin/config/hook items before exposing them in OpenClaw sessions."],
|
|
1996
2279
|
metadata: {
|
|
1997
2280
|
agentDir: targets.agentDir,
|
|
1998
2281
|
codexHome: source.codexHome,
|
|
@@ -2053,6 +2336,7 @@ async function applyCodexMigrationPlan(params) {
|
|
|
2053
2336
|
codexHome,
|
|
2054
2337
|
authPath: path.join(codexHome, "auth.json"),
|
|
2055
2338
|
modelsCachePath: path.join(codexHome, "models_cache.json"),
|
|
2339
|
+
memoryFiles: [],
|
|
2056
2340
|
skills: [],
|
|
2057
2341
|
plugins: [],
|
|
2058
2342
|
archivePaths: []
|
|
@@ -2084,6 +2368,10 @@ async function applyCodexMigrationPlan(params) {
|
|
|
2084
2368
|
} else if (item.kind === "plugin" && item.action === "install") items.push(await applyCodexPluginInstallItem(applyCtx, item));
|
|
2085
2369
|
else if (item.kind === "manual") items.push(applyMigrationManualItem(item));
|
|
2086
2370
|
else if (item.action === "archive") items.push(await archiveMigrationItem(item, reportDir));
|
|
2371
|
+
else if (item.kind === "memory") items.push(await copyMemoryMigrationFileItem(item, reportDir, {
|
|
2372
|
+
workspaceDir: targets.workspaceDir,
|
|
2373
|
+
overwrite: params.ctx.overwrite
|
|
2374
|
+
}));
|
|
2087
2375
|
else items.push(await copyMigrationFileItem(item, reportDir, { overwrite: params.ctx.overwrite }));
|
|
2088
2376
|
}
|
|
2089
2377
|
const result = {
|
|
@@ -2306,47 +2594,23 @@ function readCodexPluginPolicy(item) {
|
|
|
2306
2594
|
destructiveApprovalMode: "allow"
|
|
2307
2595
|
};
|
|
2308
2596
|
}
|
|
2309
|
-
function codexPluginActivationReportState(result) {
|
|
2310
|
-
switch (result.reason) {
|
|
2311
|
-
case "already_active":
|
|
2312
|
-
case "installed": return {
|
|
2313
|
-
installed: true,
|
|
2314
|
-
enabled: true
|
|
2315
|
-
};
|
|
2316
|
-
case "auth_required": return {
|
|
2317
|
-
installed: true,
|
|
2318
|
-
enabled: false
|
|
2319
|
-
};
|
|
2320
|
-
case "disabled":
|
|
2321
|
-
case "marketplace_missing":
|
|
2322
|
-
case "plugin_missing": return {
|
|
2323
|
-
installed: false,
|
|
2324
|
-
enabled: false
|
|
2325
|
-
};
|
|
2326
|
-
case "refresh_failed": return {
|
|
2327
|
-
installed: true,
|
|
2328
|
-
enabled: false
|
|
2329
|
-
};
|
|
2330
|
-
}
|
|
2331
|
-
return result.reason;
|
|
2332
|
-
}
|
|
2333
|
-
function sanitizeAppsNeedingAuth(apps) {
|
|
2334
|
-
return apps.map((app) => ({
|
|
2335
|
-
id: app.id,
|
|
2336
|
-
name: app.name,
|
|
2337
|
-
needsAuth: app.needsAuth
|
|
2338
|
-
}));
|
|
2339
|
-
}
|
|
2340
2597
|
//#endregion
|
|
2341
2598
|
//#region extensions/codex/src/migration/provider.ts
|
|
2599
|
+
function isMemoryOnlyMigration(ctx) {
|
|
2600
|
+
return Boolean(ctx.itemKinds && ctx.itemKinds.length > 0 && ctx.itemKinds.every((kind) => kind === "memory"));
|
|
2601
|
+
}
|
|
2342
2602
|
function buildCodexMigrationProvider(params = {}) {
|
|
2343
2603
|
return {
|
|
2344
2604
|
id: "codex",
|
|
2345
2605
|
label: "Codex",
|
|
2346
|
-
description: "
|
|
2606
|
+
description: "Import Codex memory and skills while keeping Codex native plugins and hooks explicit.",
|
|
2607
|
+
supportedItemKinds: ["memory"],
|
|
2347
2608
|
async detect(ctx) {
|
|
2348
|
-
const source = await discoverCodexSource({
|
|
2349
|
-
|
|
2609
|
+
const source = await discoverCodexSource({
|
|
2610
|
+
input: ctx.source,
|
|
2611
|
+
memoryOnly: isMemoryOnlyMigration(ctx)
|
|
2612
|
+
});
|
|
2613
|
+
const found = isMemoryOnlyMigration(ctx) ? source.memoryFiles.length > 0 : hasCodexSource(source);
|
|
2350
2614
|
return {
|
|
2351
2615
|
found,
|
|
2352
2616
|
source: source.root,
|
|
@@ -2357,6 +2621,7 @@ function buildCodexMigrationProvider(params = {}) {
|
|
|
2357
2621
|
},
|
|
2358
2622
|
plan: buildCodexMigrationPlan,
|
|
2359
2623
|
prepareApply(ctx) {
|
|
2624
|
+
if (isMemoryOnlyMigration(ctx)) return;
|
|
2360
2625
|
return prepareTargetCodexAppServer(ctx);
|
|
2361
2626
|
},
|
|
2362
2627
|
async apply(ctx, plan) {
|
|
@@ -2418,7 +2683,7 @@ const CodexThreadsParamsSchema = Type.Union([
|
|
|
2418
2683
|
ArchiveParamsSchema,
|
|
2419
2684
|
UnarchiveParamsSchema
|
|
2420
2685
|
]);
|
|
2421
|
-
function asRecord(value) {
|
|
2686
|
+
function asRecord$1(value) {
|
|
2422
2687
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2423
2688
|
}
|
|
2424
2689
|
function readBoolean(value, fallback = false) {
|
|
@@ -2437,13 +2702,9 @@ function resolveToolSession(context, runtime) {
|
|
|
2437
2702
|
});
|
|
2438
2703
|
const sessionId = context.sessionId?.trim() || entry?.sessionId?.trim();
|
|
2439
2704
|
if (!sessionId) return;
|
|
2440
|
-
const storePath = runtime.agent.session.resolveStorePath(void 0, { agentId: context.agentId });
|
|
2441
2705
|
return {
|
|
2442
2706
|
sessionId,
|
|
2443
|
-
|
|
2444
|
-
agentId: context.agentId,
|
|
2445
|
-
sessionsDir: path.dirname(storePath)
|
|
2446
|
-
})
|
|
2707
|
+
modelSelectionLocked: isModelSelectionLocked(entry)
|
|
2447
2708
|
};
|
|
2448
2709
|
}
|
|
2449
2710
|
function readThreadId(params) {
|
|
@@ -2456,43 +2717,107 @@ function readThreadStatusType(value) {
|
|
|
2456
2717
|
if (!isJsonObject(value) || !isJsonObject(value.thread) || !isJsonObject(value.thread.status)) return;
|
|
2457
2718
|
return typeof value.thread.status.type === "string" ? value.thread.status.type : void 0;
|
|
2458
2719
|
}
|
|
2459
|
-
|
|
2720
|
+
function assertThreadMayBeArchived(value, expectedThreadId) {
|
|
2721
|
+
if (!isJsonObject(value) || !isJsonObject(value.thread)) throw new Error("Codex app-server returned an invalid thread/read response");
|
|
2722
|
+
if (value.thread.id !== expectedThreadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
2723
|
+
const status = readThreadStatusType(value);
|
|
2724
|
+
if (status === "active") throw new Error("cannot archive an active Codex thread; wait for its turn to finish");
|
|
2725
|
+
if (status !== "idle" && status !== "notLoaded") throw new Error("cannot verify that the Codex thread is idle; refusing to archive");
|
|
2726
|
+
}
|
|
2727
|
+
function assertThreadMayBeForked(value, expectedThreadId) {
|
|
2728
|
+
if (!isJsonObject(value) || !isJsonObject(value.thread)) throw new Error("Codex app-server returned an invalid thread/read response");
|
|
2729
|
+
if (value.thread.id !== expectedThreadId) throw new Error("Codex app-server returned a different thread than requested");
|
|
2730
|
+
const status = readThreadStatusType(value);
|
|
2731
|
+
if (status !== "idle" && status !== "notLoaded") throw new Error("cannot fork a Codex thread unless it is idle or not loaded");
|
|
2732
|
+
}
|
|
2733
|
+
function redactNativeThreadTranscriptFields(value) {
|
|
2734
|
+
if (!isJsonObject(value)) return value;
|
|
2735
|
+
const redacted = { ...value };
|
|
2736
|
+
delete redacted.preview;
|
|
2737
|
+
delete redacted.turns;
|
|
2738
|
+
return redacted;
|
|
2739
|
+
}
|
|
2740
|
+
function redactNativeThreadResponse(value) {
|
|
2741
|
+
if (!isJsonObject(value)) return value;
|
|
2742
|
+
const redacted = { ...value };
|
|
2743
|
+
if (Array.isArray(redacted.data)) redacted.data = redacted.data.map(redactNativeThreadTranscriptFields);
|
|
2744
|
+
if (isJsonObject(redacted.thread)) redacted.thread = redactNativeThreadTranscriptFields(redacted.thread);
|
|
2745
|
+
return redacted;
|
|
2746
|
+
}
|
|
2747
|
+
/** Builds the native Codex thread tool only for owner runs with native-home access. */
|
|
2460
2748
|
function createCodexThreadsTool(options) {
|
|
2461
2749
|
if (options.context.senderIsOwner !== true) return null;
|
|
2462
|
-
|
|
2750
|
+
const configured = readCodexPluginConfig(options.getPluginConfig());
|
|
2751
|
+
if (configured.appServer?.homeScope !== "user" && configured.supervision?.enabled !== true) return null;
|
|
2463
2752
|
const request = options.request ?? codexControlRequest;
|
|
2464
|
-
const
|
|
2753
|
+
const runtimeConfig = () => options.context.getRuntimeConfig?.() ?? options.context.runtimeConfig ?? options.context.config;
|
|
2754
|
+
const baseRequestOptions = () => ({
|
|
2465
2755
|
agentDir: options.context.agentDir,
|
|
2466
|
-
config:
|
|
2756
|
+
config: runtimeConfig(),
|
|
2467
2757
|
sessionId: options.context.sessionId,
|
|
2468
2758
|
sessionKey: options.context.sessionKey
|
|
2469
2759
|
});
|
|
2470
2760
|
const currentSession = () => resolveToolSession(options.context, options.runtime);
|
|
2471
|
-
const currentIdentity = (sessionId) => {
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
});
|
|
2478
|
-
};
|
|
2761
|
+
const currentIdentity = (sessionId) => sessionBindingIdentity({
|
|
2762
|
+
sessionId,
|
|
2763
|
+
sessionKey: options.context.sessionKey,
|
|
2764
|
+
agentId: options.context.agentId,
|
|
2765
|
+
config: runtimeConfig()
|
|
2766
|
+
});
|
|
2479
2767
|
const currentBinding = async (session) => session ? await options.bindingStore.read(currentIdentity(session.sessionId)) : void 0;
|
|
2768
|
+
const requestOptions = async (pluginConfig) => {
|
|
2769
|
+
const plugin = readCodexPluginConfig(pluginConfig);
|
|
2770
|
+
const session = currentSession();
|
|
2771
|
+
const binding = await currentBinding(session);
|
|
2772
|
+
if (binding?.connectionScope === "supervision") {
|
|
2773
|
+
const connection = resolveCodexBindingAppServerConnection({
|
|
2774
|
+
binding,
|
|
2775
|
+
pluginConfig
|
|
2776
|
+
});
|
|
2777
|
+
return {
|
|
2778
|
+
...baseRequestOptions(),
|
|
2779
|
+
startOptions: connection.appServer.start,
|
|
2780
|
+
authProfileId: connection.clientAuthProfileId
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
if (plugin.appServer?.homeScope === "user") {
|
|
2784
|
+
const connection = resolveCodexBindingAppServerConnection({
|
|
2785
|
+
binding,
|
|
2786
|
+
pluginConfig
|
|
2787
|
+
});
|
|
2788
|
+
return {
|
|
2789
|
+
...baseRequestOptions(),
|
|
2790
|
+
startOptions: connection.appServer.start,
|
|
2791
|
+
authProfileId: null
|
|
2792
|
+
};
|
|
2793
|
+
}
|
|
2794
|
+
if (plugin.supervision?.enabled !== true) throw new Error("Codex native thread access is disabled for this run.");
|
|
2795
|
+
return {
|
|
2796
|
+
...baseRequestOptions(),
|
|
2797
|
+
startOptions: resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig }).start,
|
|
2798
|
+
authProfileId: null
|
|
2799
|
+
};
|
|
2800
|
+
};
|
|
2480
2801
|
return {
|
|
2481
2802
|
name: "codex_threads",
|
|
2482
2803
|
label: "Codex Threads",
|
|
2483
|
-
description: "List
|
|
2804
|
+
description: "List and inspect native Codex threads. When supervision is enabled, raw transcript reads and every mutation require their matching supervision policy option.",
|
|
2484
2805
|
parameters: CodexThreadsParamsSchema,
|
|
2485
2806
|
async execute(_toolCallId, rawParams) {
|
|
2486
|
-
const params = asRecord(rawParams);
|
|
2807
|
+
const params = asRecord$1(rawParams);
|
|
2487
2808
|
const action = readStringParam(params, "action", {
|
|
2488
2809
|
required: true,
|
|
2489
2810
|
label: "action"
|
|
2490
2811
|
});
|
|
2491
2812
|
const pluginConfig = options.getPluginConfig();
|
|
2813
|
+
const plugin = readCodexPluginConfig(pluginConfig);
|
|
2814
|
+
const supervision = plugin.supervision;
|
|
2815
|
+
const mayReadRawTranscripts = supervision?.enabled !== true || supervision.allowRawTranscripts === true;
|
|
2492
2816
|
if (action === "list") {
|
|
2493
2817
|
const cursor = readStringParam(params, "cursor");
|
|
2494
2818
|
const searchTerm = readStringParam(params, "search");
|
|
2495
|
-
|
|
2819
|
+
if (searchTerm && !mayReadRawTranscripts) throw new Error("Codex native thread search is disabled while raw transcript access is disabled.");
|
|
2820
|
+
const response = await request(pluginConfig, CODEX_CONTROL_METHODS.listThreads, {
|
|
2496
2821
|
archived: readBoolean(params.archived),
|
|
2497
2822
|
limit: readLimit(params.limit) ?? 20,
|
|
2498
2823
|
modelProviders: [],
|
|
@@ -2501,13 +2826,20 @@ function createCodexThreadsTool(options) {
|
|
|
2501
2826
|
sourceKinds: [...CODEX_INTERACTIVE_THREAD_SOURCE_KINDS],
|
|
2502
2827
|
...cursor ? { cursor } : {},
|
|
2503
2828
|
...searchTerm ? { searchTerm } : {}
|
|
2504
|
-
}, requestOptions())
|
|
2829
|
+
}, await requestOptions(pluginConfig));
|
|
2830
|
+
return jsonResult(mayReadRawTranscripts ? response : redactNativeThreadResponse(response));
|
|
2505
2831
|
}
|
|
2506
2832
|
const threadId = readThreadId(params);
|
|
2507
|
-
if (action === "read")
|
|
2508
|
-
|
|
2509
|
-
includeTurns
|
|
2510
|
-
|
|
2833
|
+
if (action === "read") {
|
|
2834
|
+
const includeTurns = readBoolean(params.include_turns);
|
|
2835
|
+
if (includeTurns && !mayReadRawTranscripts) throw new Error("Codex raw transcript reads are disabled for this codex plugin supervision config.");
|
|
2836
|
+
const response = await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2837
|
+
threadId,
|
|
2838
|
+
includeTurns
|
|
2839
|
+
}, await requestOptions(pluginConfig));
|
|
2840
|
+
return jsonResult(mayReadRawTranscripts ? response : redactNativeThreadResponse(response));
|
|
2841
|
+
}
|
|
2842
|
+
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.");
|
|
2511
2843
|
if (action === "rename") {
|
|
2512
2844
|
const name = readStringParam(params, "name", {
|
|
2513
2845
|
required: true,
|
|
@@ -2516,28 +2848,50 @@ function createCodexThreadsTool(options) {
|
|
|
2516
2848
|
await request(pluginConfig, CODEX_CONTROL_METHODS.renameThread, {
|
|
2517
2849
|
threadId,
|
|
2518
2850
|
name
|
|
2519
|
-
}, requestOptions());
|
|
2851
|
+
}, await requestOptions(pluginConfig));
|
|
2520
2852
|
return jsonResult({
|
|
2521
2853
|
action,
|
|
2522
2854
|
threadId,
|
|
2523
2855
|
name
|
|
2524
2856
|
});
|
|
2525
2857
|
}
|
|
2526
|
-
if (action === "unarchive")
|
|
2858
|
+
if (action === "unarchive") {
|
|
2859
|
+
const response = await request(pluginConfig, CODEX_CONTROL_METHODS.unarchiveThread, { threadId }, await requestOptions(pluginConfig));
|
|
2860
|
+
return jsonResult(mayReadRawTranscripts ? response : redactNativeThreadResponse(response));
|
|
2861
|
+
}
|
|
2527
2862
|
const session = currentSession();
|
|
2528
2863
|
const binding = await currentBinding(session);
|
|
2529
2864
|
if (action === "archive") {
|
|
2530
2865
|
if (params.confirm !== true) throw new Error("confirm=true is required to archive a native Codex thread");
|
|
2531
|
-
if (
|
|
2532
|
-
|
|
2866
|
+
if (!session) throw new Error("cannot safely archive a native Codex thread without a session identity");
|
|
2867
|
+
const identity = currentIdentity(session.sessionId);
|
|
2868
|
+
await options.bindingStore.withThreadArchiveFence(async () => {
|
|
2869
|
+
const archivedBinding = await currentBinding(session);
|
|
2870
|
+
if (archivedBinding?.threadId === threadId) {
|
|
2871
|
+
if (session.modelSelectionLocked) throw new ModelSelectionLockedError();
|
|
2872
|
+
assertCodexBindingMayBeReplaced(archivedBinding, "archiving its bound native thread");
|
|
2873
|
+
}
|
|
2874
|
+
assertThreadMayBeArchived(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2533
2875
|
threadId,
|
|
2534
2876
|
includeTurns: false
|
|
2535
|
-
}, requestOptions())
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2877
|
+
}, await requestOptions(pluginConfig)), threadId);
|
|
2878
|
+
if (await options.bindingStore.hasOtherThreadOwner(threadId, identity)) throw new Error("cannot archive a native Codex thread owned by another OpenClaw session");
|
|
2879
|
+
await assertCodexArchiveDescendantsUnowned({
|
|
2880
|
+
bindingStore: options.bindingStore,
|
|
2881
|
+
threadId,
|
|
2882
|
+
listPage: async (listParams) => await request(pluginConfig, CODEX_CONTROL_METHODS.listThreads, listParams, await requestOptions(pluginConfig)),
|
|
2883
|
+
assertDescendantIdle: async (descendantThreadId) => {
|
|
2884
|
+
assertThreadMayBeArchived(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2885
|
+
threadId: descendantThreadId,
|
|
2886
|
+
includeTurns: false
|
|
2887
|
+
}, await requestOptions(pluginConfig)), descendantThreadId);
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
await request(pluginConfig, CODEX_CONTROL_METHODS.archiveThread, { threadId }, await requestOptions(pluginConfig));
|
|
2891
|
+
if (archivedBinding?.threadId === threadId) await options.bindingStore.mutate(identity, {
|
|
2892
|
+
kind: "clear",
|
|
2893
|
+
threadId
|
|
2894
|
+
});
|
|
2541
2895
|
});
|
|
2542
2896
|
return jsonResult({
|
|
2543
2897
|
action,
|
|
@@ -2547,16 +2901,21 @@ function createCodexThreadsTool(options) {
|
|
|
2547
2901
|
if (action !== "fork") throw new Error(`unsupported codex_threads action: ${action}`);
|
|
2548
2902
|
const attach = readBoolean(params.attach, true);
|
|
2549
2903
|
if (attach && !session) throw new Error("cannot attach a Codex fork without an active OpenClaw session");
|
|
2550
|
-
if (attach &&
|
|
2551
|
-
|
|
2904
|
+
if (attach && session?.modelSelectionLocked) throw new ModelSelectionLockedError();
|
|
2905
|
+
const usesSupervisionConnection = binding?.connectionScope === "supervision" || plugin.appServer?.homeScope !== "user" && supervision?.enabled === true;
|
|
2906
|
+
if (attach && usesSupervisionConnection) throw new Error("Supervised Codex forks must stay detached; set attach=false.");
|
|
2907
|
+
if (attach) {
|
|
2908
|
+
assertCodexBindingMayBeReplaced(binding, "attaching a different native fork");
|
|
2909
|
+
assertThreadMayBeForked(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
|
|
2552
2910
|
threadId,
|
|
2553
2911
|
includeTurns: false
|
|
2554
|
-
}, requestOptions())
|
|
2912
|
+
}, await requestOptions(pluginConfig)), threadId);
|
|
2555
2913
|
}
|
|
2556
2914
|
const response = await request(pluginConfig, CODEX_CONTROL_METHODS.forkThread, {
|
|
2557
2915
|
threadId,
|
|
2558
|
-
threadSource: "user"
|
|
2559
|
-
|
|
2916
|
+
threadSource: "user",
|
|
2917
|
+
excludeTurns: true
|
|
2918
|
+
}, await requestOptions(pluginConfig));
|
|
2560
2919
|
if (!isJsonObject(response) || !isJsonObject(response.thread)) throw new Error("Codex app-server returned an invalid thread/fork response");
|
|
2561
2920
|
const forkThreadId = typeof response.thread.id === "string" && response.thread.id.trim() ? response.thread.id : void 0;
|
|
2562
2921
|
if (!forkThreadId) throw new Error("Codex app-server thread/fork response did not include a thread id");
|
|
@@ -2572,18 +2931,802 @@ function createCodexThreadsTool(options) {
|
|
|
2572
2931
|
}
|
|
2573
2932
|
})) throw new Error("Codex session binding changed before the fork could be attached");
|
|
2574
2933
|
}
|
|
2575
|
-
|
|
2934
|
+
const result = {
|
|
2576
2935
|
action,
|
|
2577
2936
|
sourceThreadId: threadId,
|
|
2578
2937
|
thread: response.thread,
|
|
2579
2938
|
attached: attach
|
|
2939
|
+
};
|
|
2940
|
+
return jsonResult(mayReadRawTranscripts ? result : redactNativeThreadResponse(result));
|
|
2941
|
+
}
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
//#endregion
|
|
2945
|
+
//#region extensions/codex/src/supervision-tools.ts
|
|
2946
|
+
/**
|
|
2947
|
+
* Compatibility tools for the retired Codex Supervisor plugin.
|
|
2948
|
+
*
|
|
2949
|
+
* Read operations and active-turn controls use the Codex plugin's canonical
|
|
2950
|
+
* shared app-server client. Idle threads are never resumed or started here:
|
|
2951
|
+
* continuation belongs to the Codex harness, which installs approval and tool
|
|
2952
|
+
* handlers before it starts or resumes the harness-owned Codex thread.
|
|
2953
|
+
*/
|
|
2954
|
+
/** Legacy endpoint env retained for the shipped Supervisor tool contract. */
|
|
2955
|
+
const LEGACY_CODEX_SUPERVISOR_ENDPOINTS_ENV = "OPENCLAW_CODEX_SUPERVISOR_ENDPOINTS";
|
|
2956
|
+
/** Legacy standalone-MCP transcript gate. Agent tools use canonical config. */
|
|
2957
|
+
const LEGACY_CODEX_SUPERVISOR_RAW_TRANSCRIPTS_ENV = "OPENCLAW_CODEX_SUPERVISOR_ALLOW_RAW_TRANSCRIPTS";
|
|
2958
|
+
/** Legacy standalone-MCP write gate. Agent tools use canonical config. */
|
|
2959
|
+
const LEGACY_CODEX_SUPERVISOR_WRITE_CONTROLS_ENV = "OPENCLAW_CODEX_SUPERVISOR_ALLOW_WRITE_CONTROLS";
|
|
2960
|
+
const CODEX_SUPERVISION_COMPAT_TOOL_NAMES = [
|
|
2961
|
+
"codex_endpoint_probe",
|
|
2962
|
+
"codex_sessions_list",
|
|
2963
|
+
"codex_session_read",
|
|
2964
|
+
"codex_session_send",
|
|
2965
|
+
"codex_session_interrupt"
|
|
2966
|
+
];
|
|
2967
|
+
const EmptyParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
2968
|
+
const SessionsListParamsSchema = Type.Object({
|
|
2969
|
+
include_stored: Type.Optional(Type.Boolean()),
|
|
2970
|
+
max_stored_sessions: Type.Optional(Type.Integer({
|
|
2971
|
+
minimum: 1,
|
|
2972
|
+
maximum: 1e3
|
|
2973
|
+
}))
|
|
2974
|
+
}, { additionalProperties: false });
|
|
2975
|
+
const SessionReadParamsSchema = Type.Object({
|
|
2976
|
+
endpoint_id: Type.Optional(Type.String()),
|
|
2977
|
+
thread_id: Type.String(),
|
|
2978
|
+
include_turns: Type.Optional(Type.Boolean())
|
|
2979
|
+
}, { additionalProperties: false });
|
|
2980
|
+
const SessionSendParamsSchema = Type.Object({
|
|
2981
|
+
endpoint_id: Type.Optional(Type.String()),
|
|
2982
|
+
thread_id: Type.String(),
|
|
2983
|
+
text: Type.String(),
|
|
2984
|
+
mode: Type.Optional(Type.Union([
|
|
2985
|
+
Type.Literal("auto"),
|
|
2986
|
+
Type.Literal("start"),
|
|
2987
|
+
Type.Literal("steer")
|
|
2988
|
+
]))
|
|
2989
|
+
}, { additionalProperties: false });
|
|
2990
|
+
const SessionInterruptParamsSchema = Type.Object({
|
|
2991
|
+
endpoint_id: Type.Optional(Type.String()),
|
|
2992
|
+
thread_id: Type.String(),
|
|
2993
|
+
turn_id: Type.Optional(Type.String())
|
|
2994
|
+
}, { additionalProperties: false });
|
|
2995
|
+
const ALL_CODEX_THREAD_SOURCE_KINDS = [
|
|
2996
|
+
"cli",
|
|
2997
|
+
"vscode",
|
|
2998
|
+
"exec",
|
|
2999
|
+
"appServer",
|
|
3000
|
+
"subAgent",
|
|
3001
|
+
"subAgentReview",
|
|
3002
|
+
"subAgentCompact",
|
|
3003
|
+
"subAgentThreadSpawn",
|
|
3004
|
+
"subAgentOther",
|
|
3005
|
+
"unknown"
|
|
3006
|
+
];
|
|
3007
|
+
const DEFAULT_MAX_STORED_SESSIONS = 200;
|
|
3008
|
+
const PAGE_LIMIT = 100;
|
|
3009
|
+
const MAX_COMPAT_PAGINATION_PAGES = 100;
|
|
3010
|
+
const MAX_COMPAT_CURSOR_LENGTH = 4096;
|
|
3011
|
+
const MAX_COMPAT_THREAD_ID_LENGTH = 4096;
|
|
3012
|
+
var CodexSupervisionPolicyError = class extends Error {};
|
|
3013
|
+
function asRecord(value) {
|
|
3014
|
+
return isRecord(value) ? value : {};
|
|
3015
|
+
}
|
|
3016
|
+
function asRecordArray(value) {
|
|
3017
|
+
return Array.isArray(value) ? value.filter(isRecord) : [];
|
|
3018
|
+
}
|
|
3019
|
+
function readCompatNextCursor(value, method) {
|
|
3020
|
+
if (value === null || value === void 0) return;
|
|
3021
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.length > MAX_COMPAT_CURSOR_LENGTH) throw new Error(`Codex ${method} returned an invalid nextCursor`);
|
|
3022
|
+
return value;
|
|
3023
|
+
}
|
|
3024
|
+
function readCompatThreadId(value, method, index) {
|
|
3025
|
+
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}]`);
|
|
3026
|
+
return value;
|
|
3027
|
+
}
|
|
3028
|
+
function readLoadedThreadIds(data) {
|
|
3029
|
+
if (data.length > PAGE_LIMIT) throw new Error(`Codex thread/loaded/list returned more than ${PAGE_LIMIT} entries`);
|
|
3030
|
+
return data.map((entry, index) => readCompatThreadId(entry, "thread/loaded/list", index));
|
|
3031
|
+
}
|
|
3032
|
+
function readStoredThreads(data, maxEntries) {
|
|
3033
|
+
if (data.length > maxEntries) throw new Error(`Codex thread/list returned more than ${maxEntries} entries`);
|
|
3034
|
+
return data.map((entry, index) => {
|
|
3035
|
+
if (!isRecord(entry)) throw new Error(`Codex thread/list returned an invalid entry at data[${index}]`);
|
|
3036
|
+
readCompatThreadId(entry.id, "thread/list", index);
|
|
3037
|
+
return entry;
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
function readBooleanParam(params, key) {
|
|
3041
|
+
return params[key] === true;
|
|
3042
|
+
}
|
|
3043
|
+
function readIntegerParam(params, key) {
|
|
3044
|
+
const value = params[key];
|
|
3045
|
+
if (value === void 0) return;
|
|
3046
|
+
if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${key} must be an integer`);
|
|
3047
|
+
if (value < 1 || value > 1e3) throw new Error(`${key} must be between 1 and 1000`);
|
|
3048
|
+
return value;
|
|
3049
|
+
}
|
|
3050
|
+
function readModeParam(params) {
|
|
3051
|
+
const mode = readStringParam(params, "mode");
|
|
3052
|
+
if (!mode) return;
|
|
3053
|
+
if (mode === "auto" || mode === "start" || mode === "steer") return mode;
|
|
3054
|
+
throw new Error("mode must be auto, start, or steer");
|
|
3055
|
+
}
|
|
3056
|
+
function normalizeEndpointId(value, index) {
|
|
3057
|
+
const trimmed = value.trim();
|
|
3058
|
+
return trimmed ? trimmed.replace(/[^a-zA-Z0-9_.:-]/g, "-") : `endpoint-${index + 1}`;
|
|
3059
|
+
}
|
|
3060
|
+
function normalizeConfiguredEndpoint(endpoint, index) {
|
|
3061
|
+
return {
|
|
3062
|
+
id: normalizeEndpointId(endpoint.id ?? endpoint.label ?? "", index),
|
|
3063
|
+
...endpoint.label?.trim() ? { label: endpoint.label.trim() } : {},
|
|
3064
|
+
configured: endpoint
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
function parseEndpointRecord(value) {
|
|
3068
|
+
if (!isRecord(value)) return;
|
|
3069
|
+
const transport = typeof value.transport === "string" ? value.transport : void 0;
|
|
3070
|
+
const common = {
|
|
3071
|
+
...typeof value.id === "string" ? { id: value.id } : {},
|
|
3072
|
+
...typeof value.label === "string" ? { label: value.label } : {}
|
|
3073
|
+
};
|
|
3074
|
+
if (transport === "websocket" && typeof value.url === "string") return {
|
|
3075
|
+
...common,
|
|
3076
|
+
transport,
|
|
3077
|
+
url: value.url,
|
|
3078
|
+
...typeof value.authTokenEnv === "string" ? { authTokenEnv: value.authTokenEnv } : {}
|
|
3079
|
+
};
|
|
3080
|
+
if (transport === "stdio-proxy" || transport === void 0) {
|
|
3081
|
+
const args = Array.isArray(value.args) ? value.args.filter((entry) => typeof entry === "string") : void 0;
|
|
3082
|
+
return {
|
|
3083
|
+
...common,
|
|
3084
|
+
transport: "stdio-proxy",
|
|
3085
|
+
...typeof value.command === "string" ? { command: value.command } : {},
|
|
3086
|
+
...args && args.length > 0 ? { args } : {},
|
|
3087
|
+
...typeof value.cwd === "string" ? { cwd: value.cwd } : {}
|
|
3088
|
+
};
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
function endpointFromToken(token, index) {
|
|
3092
|
+
const trimmed = token.trim();
|
|
3093
|
+
if (!trimmed) return;
|
|
3094
|
+
if (trimmed.startsWith("ws://") || trimmed.startsWith("wss://") || trimmed.startsWith("unix://")) return {
|
|
3095
|
+
id: normalizeEndpointId("", index),
|
|
3096
|
+
transport: "websocket",
|
|
3097
|
+
url: trimmed
|
|
3098
|
+
};
|
|
3099
|
+
if (trimmed === "local" || trimmed === "proxy" || trimmed === "stdio") return {
|
|
3100
|
+
id: "local",
|
|
3101
|
+
label: "local Codex app-server",
|
|
3102
|
+
transport: "stdio-proxy"
|
|
3103
|
+
};
|
|
3104
|
+
const separatorIndex = trimmed.indexOf("=");
|
|
3105
|
+
const id = separatorIndex >= 0 ? trimmed.slice(0, separatorIndex) : trimmed;
|
|
3106
|
+
const url = separatorIndex >= 0 ? trimmed.slice(separatorIndex + 1) : void 0;
|
|
3107
|
+
if (url?.startsWith("ws://") || url?.startsWith("wss://") || url?.startsWith("unix://")) return {
|
|
3108
|
+
id: normalizeEndpointId(id, index),
|
|
3109
|
+
transport: "websocket",
|
|
3110
|
+
url
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
function requireUniqueEndpointIds(endpoints) {
|
|
3114
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3115
|
+
for (const endpoint of endpoints) {
|
|
3116
|
+
if (seen.has(endpoint.id)) throw new Error(`duplicate Codex supervisor endpoint id: ${endpoint.id}`);
|
|
3117
|
+
seen.add(endpoint.id);
|
|
3118
|
+
}
|
|
3119
|
+
return endpoints;
|
|
3120
|
+
}
|
|
3121
|
+
function readLegacyEnvEndpoints(env) {
|
|
3122
|
+
const raw = env[LEGACY_CODEX_SUPERVISOR_ENDPOINTS_ENV]?.trim();
|
|
3123
|
+
if (!raw) return;
|
|
3124
|
+
if (raw.startsWith("[")) {
|
|
3125
|
+
const parsed = JSON.parse(raw);
|
|
3126
|
+
if (!Array.isArray(parsed)) throw new Error(`${LEGACY_CODEX_SUPERVISOR_ENDPOINTS_ENV} must be a JSON array`);
|
|
3127
|
+
return parsed.map((entry) => parseEndpointRecord(entry)).filter((entry) => Boolean(entry));
|
|
3128
|
+
}
|
|
3129
|
+
return raw.split(",").map(endpointFromToken).filter((entry) => Boolean(entry));
|
|
3130
|
+
}
|
|
3131
|
+
function resolveEndpoints(pluginConfig, env, runtimeConfig) {
|
|
3132
|
+
const configured = readCodexPluginConfig(pluginConfig).supervision?.endpoints;
|
|
3133
|
+
const endpoints = configured?.length ? configured : readLegacyEnvEndpoints(env);
|
|
3134
|
+
return (endpoints ? requireUniqueEndpointIds(endpoints.map(normalizeConfiguredEndpoint)) : [{
|
|
3135
|
+
id: "local",
|
|
3136
|
+
label: "local Codex app-server"
|
|
3137
|
+
}]).map((endpoint) => {
|
|
3138
|
+
const resolved = {
|
|
3139
|
+
id: endpoint.id,
|
|
3140
|
+
connectionKey: supervisionEndpointConnectionKey({
|
|
3141
|
+
endpoint,
|
|
3142
|
+
pluginConfig,
|
|
3143
|
+
env,
|
|
3144
|
+
runtimeConfig
|
|
3145
|
+
})
|
|
3146
|
+
};
|
|
3147
|
+
if (endpoint.label !== void 0) resolved.label = endpoint.label;
|
|
3148
|
+
if (endpoint.configured !== void 0) resolved.configured = endpoint.configured;
|
|
3149
|
+
return resolved;
|
|
3150
|
+
});
|
|
3151
|
+
}
|
|
3152
|
+
function resolveEndpointStartOptions(params) {
|
|
3153
|
+
const base = resolveCodexSupervisionAppServerRuntimeOptions({
|
|
3154
|
+
pluginConfig: params.pluginConfig,
|
|
3155
|
+
env: params.env
|
|
3156
|
+
}).start;
|
|
3157
|
+
const configured = params.endpoint.configured;
|
|
3158
|
+
if (!configured) return base;
|
|
3159
|
+
if (!("url" in configured)) return {
|
|
3160
|
+
transport: "stdio",
|
|
3161
|
+
homeScope: "user",
|
|
3162
|
+
command: configured.command?.trim() || "codex",
|
|
3163
|
+
commandSource: "config",
|
|
3164
|
+
args: configured.args?.length ? [...configured.args] : [
|
|
3165
|
+
"app-server",
|
|
3166
|
+
"--listen",
|
|
3167
|
+
"stdio://"
|
|
3168
|
+
],
|
|
3169
|
+
...configured.cwd !== void 0 ? { cwd: configured.cwd } : {},
|
|
3170
|
+
headers: {}
|
|
3171
|
+
};
|
|
3172
|
+
const tokenEnv = configured.authTokenEnv?.trim();
|
|
3173
|
+
const authToken = tokenEnv ? params.env[tokenEnv]?.trim() : void 0;
|
|
3174
|
+
const startOptions = {
|
|
3175
|
+
transport: configured.url.startsWith("unix://") ? "unix" : "websocket",
|
|
3176
|
+
...configured.url.startsWith("unix://") ? { homeScope: "user" } : {},
|
|
3177
|
+
command: base.command,
|
|
3178
|
+
...base.commandSource ? { commandSource: base.commandSource } : {},
|
|
3179
|
+
...base.managedFallbackCommandPaths ? { managedFallbackCommandPaths: [...base.managedFallbackCommandPaths] } : {},
|
|
3180
|
+
args: [...base.args],
|
|
3181
|
+
url: configured.url,
|
|
3182
|
+
...authToken ? { authToken } : {},
|
|
3183
|
+
headers: {}
|
|
3184
|
+
};
|
|
3185
|
+
if (params.validateSecurity !== false) assertCodexAppServerConnectionSecurity(startOptions);
|
|
3186
|
+
return startOptions;
|
|
3187
|
+
}
|
|
3188
|
+
function supervisionEndpointConnectionKey(params) {
|
|
3189
|
+
const startOptions = resolveEndpointStartOptions({
|
|
3190
|
+
...params,
|
|
3191
|
+
validateSecurity: false
|
|
3192
|
+
});
|
|
3193
|
+
const usesNativeAuth = params.endpoint.configured !== void 0 || startOptions.homeScope === "user";
|
|
3194
|
+
const agentDir = usesNativeAuth ? void 0 : resolveDefaultAgentDir(params.runtimeConfig ?? {});
|
|
3195
|
+
const authProfileId = usesNativeAuth ? void 0 : resolveCodexAppServerAuthProfileIdForAgent({
|
|
3196
|
+
agentDir,
|
|
3197
|
+
config: params.runtimeConfig
|
|
3198
|
+
});
|
|
3199
|
+
const fallbackApiKeyCacheKey = authProfileId ? void 0 : resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions });
|
|
3200
|
+
return JSON.stringify({
|
|
3201
|
+
homeScope: startOptions.homeScope ?? null,
|
|
3202
|
+
startOptions: codexAppServerStartOptionsKey(startOptions, {
|
|
3203
|
+
authProfileId,
|
|
3204
|
+
agentDir,
|
|
3205
|
+
fallbackApiKeyCacheKey
|
|
3206
|
+
})
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3209
|
+
function createCanonicalEndpointRequest(options) {
|
|
3210
|
+
return async (endpoint, method, requestParams) => {
|
|
3211
|
+
const pluginConfig = options.getPluginConfig();
|
|
3212
|
+
const env = options.env ?? process.env;
|
|
3213
|
+
const runtime = resolveCodexSupervisionAppServerRuntimeOptions({
|
|
3214
|
+
pluginConfig,
|
|
3215
|
+
env
|
|
3216
|
+
});
|
|
3217
|
+
const config = options.getRuntimeConfig?.();
|
|
3218
|
+
const startOptions = resolveEndpointStartOptions({
|
|
3219
|
+
endpoint,
|
|
3220
|
+
pluginConfig,
|
|
3221
|
+
env
|
|
3222
|
+
});
|
|
3223
|
+
return await requestCodexAppServerJson({
|
|
3224
|
+
method,
|
|
3225
|
+
requestParams,
|
|
3226
|
+
timeoutMs: runtime.requestTimeoutMs,
|
|
3227
|
+
startOptions,
|
|
3228
|
+
...endpoint.configured || startOptions.homeScope === "user" ? { authProfileId: null } : {},
|
|
3229
|
+
...config ? { config } : {}
|
|
3230
|
+
});
|
|
3231
|
+
};
|
|
3232
|
+
}
|
|
3233
|
+
function statusType(thread) {
|
|
3234
|
+
const status = isRecord(thread.status) ? thread.status.type : thread.status;
|
|
3235
|
+
return typeof status === "string" ? status : "unknown";
|
|
3236
|
+
}
|
|
3237
|
+
function sourceLabel(value) {
|
|
3238
|
+
if (typeof value === "string") return value;
|
|
3239
|
+
if (!isRecord(value)) return;
|
|
3240
|
+
if (typeof value.custom === "string") return `custom:${value.custom}`;
|
|
3241
|
+
return Object.keys(value).toSorted()[0];
|
|
3242
|
+
}
|
|
3243
|
+
function toSession(endpointId, thread, humanAttached) {
|
|
3244
|
+
if (typeof thread.id !== "string") return;
|
|
3245
|
+
const source = sourceLabel(thread.source);
|
|
3246
|
+
return {
|
|
3247
|
+
endpointId,
|
|
3248
|
+
threadId: thread.id,
|
|
3249
|
+
status: statusType(thread),
|
|
3250
|
+
...typeof thread.sessionId === "string" ? { sessionId: thread.sessionId } : {},
|
|
3251
|
+
...typeof thread.cwd === "string" ? { cwd: thread.cwd } : {},
|
|
3252
|
+
...typeof thread.preview === "string" ? { preview: thread.preview } : {},
|
|
3253
|
+
...typeof thread.name === "string" || thread.name === null ? { name: thread.name } : {},
|
|
3254
|
+
...source ? { source } : {},
|
|
3255
|
+
...typeof thread.updatedAt === "number" ? { updatedAt: thread.updatedAt } : {},
|
|
3256
|
+
...humanAttached !== void 0 ? { humanAttached } : {}
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
function threadFromRead(value) {
|
|
3260
|
+
return isRecord(value) && isRecord(value.thread) ? value.thread : void 0;
|
|
3261
|
+
}
|
|
3262
|
+
function isLoadedThreadReadMiss(error) {
|
|
3263
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3264
|
+
return message.includes("thread not found") || message.includes("thread not loaded");
|
|
3265
|
+
}
|
|
3266
|
+
async function readThread(params) {
|
|
3267
|
+
try {
|
|
3268
|
+
const thread = threadFromRead(await params.request(params.endpoint, "thread/read", {
|
|
3269
|
+
threadId: params.threadId,
|
|
3270
|
+
includeTurns: params.includeTurns
|
|
3271
|
+
}));
|
|
3272
|
+
if (!thread) throw new Error("Codex thread/read returned an invalid response");
|
|
3273
|
+
return thread;
|
|
3274
|
+
} catch (error) {
|
|
3275
|
+
if (!params.includeTurns || !String(error).includes("not materialized yet")) throw error;
|
|
3276
|
+
const thread = threadFromRead(await params.request(params.endpoint, "thread/read", {
|
|
3277
|
+
threadId: params.threadId,
|
|
3278
|
+
includeTurns: false
|
|
3279
|
+
}));
|
|
3280
|
+
if (!thread) throw new Error("Codex thread/read returned an invalid response", { cause: error });
|
|
3281
|
+
return thread;
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
async function listLoadedSessions(request, endpoint) {
|
|
3285
|
+
const sessions = [];
|
|
3286
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
3287
|
+
let cursor;
|
|
3288
|
+
for (let pageIndex = 0; pageIndex < MAX_COMPAT_PAGINATION_PAGES; pageIndex += 1) {
|
|
3289
|
+
const listed = await request(endpoint, "thread/loaded/list", {
|
|
3290
|
+
limit: PAGE_LIMIT,
|
|
3291
|
+
...cursor ? { cursor } : {}
|
|
3292
|
+
});
|
|
3293
|
+
if (!isRecord(listed) || !Array.isArray(listed.data)) throw new Error("Codex thread/loaded/list returned an invalid response");
|
|
3294
|
+
const threadIds = readLoadedThreadIds(listed.data);
|
|
3295
|
+
for (const threadId of threadIds) {
|
|
3296
|
+
if (sessions.some((entry) => entry.threadId === threadId)) continue;
|
|
3297
|
+
try {
|
|
3298
|
+
const thread = await readThread({
|
|
3299
|
+
request,
|
|
3300
|
+
endpoint,
|
|
3301
|
+
threadId,
|
|
3302
|
+
includeTurns: false
|
|
3303
|
+
});
|
|
3304
|
+
const session = toSession(endpoint.id, thread, true);
|
|
3305
|
+
if (session) sessions.push(session);
|
|
3306
|
+
} catch (error) {
|
|
3307
|
+
if (!isLoadedThreadReadMiss(error)) throw error;
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
const nextCursor = readCompatNextCursor(listed.nextCursor, "thread/loaded/list");
|
|
3311
|
+
if (nextCursor && seenCursors.has(nextCursor)) throw new Error(`Codex thread/loaded/list returned repeated cursor ${nextCursor}`);
|
|
3312
|
+
if (nextCursor) seenCursors.add(nextCursor);
|
|
3313
|
+
cursor = nextCursor;
|
|
3314
|
+
if (!cursor) break;
|
|
3315
|
+
}
|
|
3316
|
+
if (cursor) throw new Error(`Codex thread/loaded/list exceeded ${MAX_COMPAT_PAGINATION_PAGES} pages with a continuation cursor`);
|
|
3317
|
+
return sessions;
|
|
3318
|
+
}
|
|
3319
|
+
async function listStoredSessions(params) {
|
|
3320
|
+
const sessions = [];
|
|
3321
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
3322
|
+
let cursor;
|
|
3323
|
+
for (let pageIndex = 0; pageIndex < MAX_COMPAT_PAGINATION_PAGES; pageIndex += 1) {
|
|
3324
|
+
const remaining = params.limit - sessions.length;
|
|
3325
|
+
if (remaining <= 0) break;
|
|
3326
|
+
const pageLimit = Math.min(PAGE_LIMIT, remaining);
|
|
3327
|
+
const listed = await params.request(params.endpoint, "thread/list", {
|
|
3328
|
+
archived: false,
|
|
3329
|
+
limit: pageLimit,
|
|
3330
|
+
sourceKinds: [...ALL_CODEX_THREAD_SOURCE_KINDS],
|
|
3331
|
+
modelProviders: [],
|
|
3332
|
+
sortKey: "recency_at",
|
|
3333
|
+
sortDirection: "desc",
|
|
3334
|
+
useStateDbOnly: true,
|
|
3335
|
+
...cursor ? { cursor } : {}
|
|
3336
|
+
});
|
|
3337
|
+
if (!isRecord(listed) || !Array.isArray(listed.data)) throw new Error("Codex thread/list returned an invalid response");
|
|
3338
|
+
for (const thread of readStoredThreads(listed.data, pageLimit)) {
|
|
3339
|
+
if (sessions.length >= params.limit) break;
|
|
3340
|
+
const session = toSession(params.endpoint.id, thread);
|
|
3341
|
+
if (session && !sessions.some((entry) => entry.threadId === session.threadId)) sessions.push(session);
|
|
3342
|
+
}
|
|
3343
|
+
const nextCursor = readCompatNextCursor(listed.nextCursor, "thread/list");
|
|
3344
|
+
if (nextCursor && sessions.length < params.limit && seenCursors.has(nextCursor)) throw new Error(`Codex thread/list returned repeated cursor ${nextCursor}`);
|
|
3345
|
+
if (nextCursor) seenCursors.add(nextCursor);
|
|
3346
|
+
cursor = nextCursor;
|
|
3347
|
+
if (!cursor || sessions.length >= params.limit) break;
|
|
3348
|
+
}
|
|
3349
|
+
if (cursor && sessions.length < params.limit) throw new Error(`Codex thread/list exceeded ${MAX_COMPAT_PAGINATION_PAGES} pages with a continuation cursor`);
|
|
3350
|
+
return sessions;
|
|
3351
|
+
}
|
|
3352
|
+
async function listSessionSnapshot(params) {
|
|
3353
|
+
const sessions = [];
|
|
3354
|
+
const errors = [];
|
|
3355
|
+
for (const endpoint of params.endpoints) try {
|
|
3356
|
+
const loaded = await listLoadedSessions(params.request, endpoint);
|
|
3357
|
+
sessions.push(...loaded);
|
|
3358
|
+
if (params.includeStored) {
|
|
3359
|
+
const stored = await listStoredSessions({
|
|
3360
|
+
request: params.request,
|
|
3361
|
+
endpoint,
|
|
3362
|
+
limit: params.maxStoredSessions ?? DEFAULT_MAX_STORED_SESSIONS
|
|
2580
3363
|
});
|
|
3364
|
+
for (const session of stored) if (!sessions.some((entry) => entry.endpointId === endpoint.id && entry.threadId === session.threadId)) sessions.push(session);
|
|
2581
3365
|
}
|
|
3366
|
+
} catch (error) {
|
|
3367
|
+
if (error instanceof CodexSupervisionPolicyError) throw error;
|
|
3368
|
+
errors.push({
|
|
3369
|
+
endpointId: endpoint.id,
|
|
3370
|
+
ok: false,
|
|
3371
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
3372
|
+
});
|
|
3373
|
+
}
|
|
3374
|
+
return {
|
|
3375
|
+
sessions,
|
|
3376
|
+
errors
|
|
3377
|
+
};
|
|
3378
|
+
}
|
|
3379
|
+
async function resolveEndpointForThread(params) {
|
|
3380
|
+
if (params.endpointId) {
|
|
3381
|
+
const endpoint = params.endpoints.find((entry) => entry.id === params.endpointId);
|
|
3382
|
+
if (!endpoint) throw new Error(`Unknown Codex supervisor endpoint: ${params.endpointId}`);
|
|
3383
|
+
return endpoint;
|
|
3384
|
+
}
|
|
3385
|
+
const matches = [];
|
|
3386
|
+
for (const endpoint of params.endpoints) try {
|
|
3387
|
+
if ((await readThread({
|
|
3388
|
+
request: params.request,
|
|
3389
|
+
endpoint,
|
|
3390
|
+
threadId: params.threadId,
|
|
3391
|
+
includeTurns: false
|
|
3392
|
+
})).id === params.threadId) matches.push(endpoint);
|
|
3393
|
+
} catch (error) {
|
|
3394
|
+
if (error instanceof CodexSupervisionPolicyError) throw error;
|
|
3395
|
+
if (!isLoadedThreadReadMiss(error)) continue;
|
|
3396
|
+
}
|
|
3397
|
+
if (matches.length === 1) return expectDefined(matches[0], "single matching Codex supervision endpoint");
|
|
3398
|
+
if (matches.length > 1) throw new Error(`Codex thread id is ambiguous across endpoints: ${params.threadId}`);
|
|
3399
|
+
throw new Error(`Codex thread not found: ${params.threadId}`);
|
|
3400
|
+
}
|
|
3401
|
+
function findInProgressTurnId(thread) {
|
|
3402
|
+
const turns = asRecordArray(thread.turns);
|
|
3403
|
+
for (const turn of turns.toReversed()) if (turn.status === "inProgress" && typeof turn.id === "string") return turn.id;
|
|
3404
|
+
}
|
|
3405
|
+
async function resolveInProgressTurnId(params) {
|
|
3406
|
+
const inline = findInProgressTurnId(params.thread);
|
|
3407
|
+
if (inline) return inline;
|
|
3408
|
+
try {
|
|
3409
|
+
const response = await params.request(params.endpoint, "thread/turns/list", {
|
|
3410
|
+
threadId: params.threadId,
|
|
3411
|
+
limit: 10,
|
|
3412
|
+
sortDirection: "desc",
|
|
3413
|
+
itemsView: "summary"
|
|
3414
|
+
});
|
|
3415
|
+
return isRecord(response) ? findInProgressTurnId({ turns: response.data }) : void 0;
|
|
3416
|
+
} catch (error) {
|
|
3417
|
+
if (error instanceof CodexSupervisionPolicyError) throw error;
|
|
3418
|
+
return;
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
function redactString(value) {
|
|
3422
|
+
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]");
|
|
3423
|
+
}
|
|
3424
|
+
/** Redacts secret-bearing fields before legacy tool results leave the plugin. */
|
|
3425
|
+
function redactCodexSupervisionValue(value, key = "") {
|
|
3426
|
+
if (typeof value === "string") return /authorization|password|secret|token|api[-_]?key/i.test(key) ? "[redacted]" : redactString(value);
|
|
3427
|
+
if (Array.isArray(value)) return value.map((entry) => redactCodexSupervisionValue(entry));
|
|
3428
|
+
if (!isRecord(value)) return value;
|
|
3429
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [entryKey, redactCodexSupervisionValue(entryValue, entryKey)]));
|
|
3430
|
+
}
|
|
3431
|
+
function redactEndpointUrl(value) {
|
|
3432
|
+
if (value.startsWith("unix://")) return "unix://";
|
|
3433
|
+
try {
|
|
3434
|
+
const url = new URL(value);
|
|
3435
|
+
url.username = "";
|
|
3436
|
+
url.password = "";
|
|
3437
|
+
if (url.search) url.search = "?[redacted]";
|
|
3438
|
+
return url.toString();
|
|
3439
|
+
} catch {
|
|
3440
|
+
return "[redacted]";
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
function endpointResult(endpoint, pluginConfig, env) {
|
|
3444
|
+
const configured = endpoint.configured;
|
|
3445
|
+
if (configured && (configured.transport === "stdio-proxy" || configured.transport === void 0)) return {
|
|
3446
|
+
id: endpoint.id,
|
|
3447
|
+
transport: "stdio-proxy",
|
|
3448
|
+
...endpoint.label ? { label: endpoint.label } : {}
|
|
3449
|
+
};
|
|
3450
|
+
if (configured?.transport === "websocket") return {
|
|
3451
|
+
id: endpoint.id,
|
|
3452
|
+
transport: "websocket",
|
|
3453
|
+
...endpoint.label ? { label: endpoint.label } : {},
|
|
3454
|
+
url: redactEndpointUrl(configured.url)
|
|
3455
|
+
};
|
|
3456
|
+
const start = resolveCodexSupervisionAppServerRuntimeOptions({
|
|
3457
|
+
pluginConfig,
|
|
3458
|
+
env
|
|
3459
|
+
}).start;
|
|
3460
|
+
return {
|
|
3461
|
+
id: endpoint.id,
|
|
3462
|
+
transport: start.transport === "stdio" ? "stdio-proxy" : "websocket",
|
|
3463
|
+
...endpoint.label ? { label: endpoint.label } : {},
|
|
3464
|
+
...start.transport === "stdio" ? {} : { url: redactEndpointUrl(start.transport === "unix" ? start.url ?? "unix://" : start.url ?? "") }
|
|
3465
|
+
};
|
|
3466
|
+
}
|
|
3467
|
+
function sanitizeSessionListResult(result, includeTranscriptDerivedFields) {
|
|
3468
|
+
return {
|
|
3469
|
+
sessions: result.sessions.map((session) => {
|
|
3470
|
+
const sanitized = redactCodexSupervisionValue(session);
|
|
3471
|
+
if (!includeTranscriptDerivedFields) {
|
|
3472
|
+
delete sanitized.preview;
|
|
3473
|
+
delete sanitized.name;
|
|
3474
|
+
}
|
|
3475
|
+
return sanitized;
|
|
3476
|
+
}),
|
|
3477
|
+
errors: includeTranscriptDerivedFields ? redactCodexSupervisionValue(result.errors) : result.errors.map(({ endpointId, ok }) => ({
|
|
3478
|
+
endpointId,
|
|
3479
|
+
ok
|
|
3480
|
+
}))
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
function requireSupervisionEnabled(pluginConfig) {
|
|
3484
|
+
if (readCodexPluginConfig(pluginConfig).supervision?.enabled !== true) throw new CodexSupervisionPolicyError("Codex supervision is disabled in the codex plugin config.");
|
|
3485
|
+
}
|
|
3486
|
+
function requireOwnerAccess(options) {
|
|
3487
|
+
if (!options.senderIsOwner) throw new CodexSupervisionPolicyError("Codex supervision compatibility tools require an owner-authorized sender.");
|
|
3488
|
+
}
|
|
3489
|
+
function resolveToolPolicy(options, pluginConfig) {
|
|
3490
|
+
const config = readCodexPluginConfig(pluginConfig).supervision;
|
|
3491
|
+
const env = options.env ?? process.env;
|
|
3492
|
+
return {
|
|
3493
|
+
allowRawTranscripts: config?.allowRawTranscripts === true || options.useLegacyMcpPolicyEnv === true && env[LEGACY_CODEX_SUPERVISOR_RAW_TRANSCRIPTS_ENV] === "1",
|
|
3494
|
+
allowWriteControls: config?.allowWriteControls === true || options.useLegacyMcpPolicyEnv === true && env[LEGACY_CODEX_SUPERVISOR_WRITE_CONTROLS_ENV] === "1"
|
|
2582
3495
|
};
|
|
2583
3496
|
}
|
|
3497
|
+
function requireRawTranscriptAccess(options, pluginConfig) {
|
|
3498
|
+
if (!resolveToolPolicy(options, pluginConfig).allowRawTranscripts) throw new CodexSupervisionPolicyError("Codex session reads are disabled for this codex plugin supervision config.");
|
|
3499
|
+
}
|
|
3500
|
+
function requireWriteAccess(options, pluginConfig) {
|
|
3501
|
+
if (!resolveToolPolicy(options, pluginConfig).allowWriteControls) throw new CodexSupervisionPolicyError("Codex write controls are disabled for this codex plugin supervision config.");
|
|
3502
|
+
}
|
|
3503
|
+
function requireLiveToolPolicy(options, policy) {
|
|
3504
|
+
requireOwnerAccess(options);
|
|
3505
|
+
const pluginConfig = options.getPluginConfig();
|
|
3506
|
+
requireSupervisionEnabled(pluginConfig);
|
|
3507
|
+
if (policy === "raw-transcripts") requireRawTranscriptAccess(options, pluginConfig);
|
|
3508
|
+
else if (policy === "write-controls") requireWriteAccess(options, pluginConfig);
|
|
3509
|
+
return {
|
|
3510
|
+
pluginConfig,
|
|
3511
|
+
endpoints: resolveEndpoints(pluginConfig, options.env ?? process.env, options.getRuntimeConfig?.())
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3514
|
+
function requireCurrentEndpoint(options, policy, endpoint) {
|
|
3515
|
+
const { endpoints } = requireLiveToolPolicy(options, policy);
|
|
3516
|
+
const currentEndpoint = endpoints.find((candidate) => candidate.id === endpoint.id);
|
|
3517
|
+
if (!currentEndpoint || currentEndpoint.connectionKey !== endpoint.connectionKey) throw new CodexSupervisionPolicyError(`Codex supervision endpoint ${endpoint.id} was removed or changed during the request.`);
|
|
3518
|
+
return currentEndpoint;
|
|
3519
|
+
}
|
|
3520
|
+
function requireCurrentEndpointSet(options, expected) {
|
|
3521
|
+
const current = requireLiveToolPolicy(options, "enabled");
|
|
3522
|
+
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.");
|
|
3523
|
+
return { pluginConfig: current.pluginConfig };
|
|
3524
|
+
}
|
|
3525
|
+
function createPolicyGuardedRequest(options, request, policy) {
|
|
3526
|
+
return async (endpoint, method, params) => {
|
|
3527
|
+
return await request(requireCurrentEndpoint(options, policy, endpoint), method, params);
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
function idleContinuationError(threadId) {
|
|
3531
|
+
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.`);
|
|
3532
|
+
}
|
|
3533
|
+
/** Builds the five shipped Codex Supervisor compatibility tools. */
|
|
3534
|
+
function createCodexSupervisionTools(options) {
|
|
3535
|
+
const baseRequest = options.request ?? createCanonicalEndpointRequest(options);
|
|
3536
|
+
const request = createPolicyGuardedRequest(options, baseRequest, "enabled");
|
|
3537
|
+
const rawTranscriptRequest = createPolicyGuardedRequest(options, baseRequest, "raw-transcripts");
|
|
3538
|
+
const writeRequest = createPolicyGuardedRequest(options, baseRequest, "write-controls");
|
|
3539
|
+
const current = () => {
|
|
3540
|
+
return requireLiveToolPolicy(options, "enabled");
|
|
3541
|
+
};
|
|
3542
|
+
return [
|
|
3543
|
+
{
|
|
3544
|
+
name: "codex_endpoint_probe",
|
|
3545
|
+
label: "Codex Endpoint Probe",
|
|
3546
|
+
description: "Check configured Codex app-server endpoints.",
|
|
3547
|
+
parameters: EmptyParamsSchema,
|
|
3548
|
+
execute: async () => {
|
|
3549
|
+
const { pluginConfig, endpoints } = current();
|
|
3550
|
+
const health = [];
|
|
3551
|
+
for (const endpoint of endpoints) try {
|
|
3552
|
+
await request(endpoint, "thread/loaded/list", { limit: 1 });
|
|
3553
|
+
health.push({
|
|
3554
|
+
endpointId: endpoint.id,
|
|
3555
|
+
ok: true
|
|
3556
|
+
});
|
|
3557
|
+
} catch (error) {
|
|
3558
|
+
if (error instanceof CodexSupervisionPolicyError) throw error;
|
|
3559
|
+
health.push({
|
|
3560
|
+
endpointId: endpoint.id,
|
|
3561
|
+
ok: false
|
|
3562
|
+
});
|
|
3563
|
+
}
|
|
3564
|
+
requireCurrentEndpointSet(options, endpoints);
|
|
3565
|
+
return jsonResult({
|
|
3566
|
+
summary: `codex endpoints: ${health.filter((entry) => entry.ok).length}/${health.length} ok`,
|
|
3567
|
+
endpoints: endpoints.map((endpoint) => endpointResult(endpoint, pluginConfig, options.env ?? process.env)),
|
|
3568
|
+
health
|
|
3569
|
+
});
|
|
3570
|
+
}
|
|
3571
|
+
},
|
|
3572
|
+
{
|
|
3573
|
+
name: "codex_sessions_list",
|
|
3574
|
+
label: "Codex Sessions List",
|
|
3575
|
+
description: "List Codex sessions visible to the OpenClaw supervisor.",
|
|
3576
|
+
parameters: SessionsListParamsSchema,
|
|
3577
|
+
execute: async (_toolCallId, rawParams) => {
|
|
3578
|
+
const params = asRecord(rawParams);
|
|
3579
|
+
const { endpoints } = current();
|
|
3580
|
+
const result = await listSessionSnapshot({
|
|
3581
|
+
endpoints,
|
|
3582
|
+
request,
|
|
3583
|
+
includeStored: readBooleanParam(params, "include_stored"),
|
|
3584
|
+
maxStoredSessions: readIntegerParam(params, "max_stored_sessions")
|
|
3585
|
+
});
|
|
3586
|
+
const { pluginConfig } = requireCurrentEndpointSet(options, endpoints);
|
|
3587
|
+
return jsonResult({
|
|
3588
|
+
summary: `codex sessions: ${result.sessions.length}`,
|
|
3589
|
+
...sanitizeSessionListResult(result, resolveToolPolicy(options, pluginConfig).allowRawTranscripts)
|
|
3590
|
+
});
|
|
3591
|
+
}
|
|
3592
|
+
},
|
|
3593
|
+
{
|
|
3594
|
+
name: "codex_session_read",
|
|
3595
|
+
label: "Codex Session Read",
|
|
3596
|
+
description: "Read one Codex session transcript from app-server.",
|
|
3597
|
+
parameters: SessionReadParamsSchema,
|
|
3598
|
+
execute: async (_toolCallId, rawParams) => {
|
|
3599
|
+
const { endpoints, pluginConfig } = current();
|
|
3600
|
+
requireRawTranscriptAccess(options, pluginConfig);
|
|
3601
|
+
const params = asRecord(rawParams);
|
|
3602
|
+
const threadId = readStringParam(params, "thread_id", { required: true });
|
|
3603
|
+
const endpoint = await resolveEndpointForThread({
|
|
3604
|
+
endpoints,
|
|
3605
|
+
request: rawTranscriptRequest,
|
|
3606
|
+
endpointId: readStringParam(params, "endpoint_id"),
|
|
3607
|
+
threadId
|
|
3608
|
+
});
|
|
3609
|
+
const thread = await readThread({
|
|
3610
|
+
request: rawTranscriptRequest,
|
|
3611
|
+
endpoint,
|
|
3612
|
+
threadId,
|
|
3613
|
+
includeTurns: readBooleanParam(params, "include_turns")
|
|
3614
|
+
});
|
|
3615
|
+
requireCurrentEndpoint(options, "raw-transcripts", endpoint);
|
|
3616
|
+
return jsonResult({
|
|
3617
|
+
summary: `codex session: ${threadId}`,
|
|
3618
|
+
response: redactCodexSupervisionValue({ thread })
|
|
3619
|
+
});
|
|
3620
|
+
}
|
|
3621
|
+
},
|
|
3622
|
+
{
|
|
3623
|
+
name: "codex_session_send",
|
|
3624
|
+
label: "Codex Session Send",
|
|
3625
|
+
description: "Steer an active Codex turn. Idle sessions must be continued through Codex Sessions.",
|
|
3626
|
+
parameters: SessionSendParamsSchema,
|
|
3627
|
+
execute: async (_toolCallId, rawParams) => {
|
|
3628
|
+
const { endpoints, pluginConfig } = current();
|
|
3629
|
+
requireWriteAccess(options, pluginConfig);
|
|
3630
|
+
const params = asRecord(rawParams);
|
|
3631
|
+
const threadId = readStringParam(params, "thread_id", { required: true });
|
|
3632
|
+
const text = readStringParam(params, "text", {
|
|
3633
|
+
required: true,
|
|
3634
|
+
allowEmpty: false
|
|
3635
|
+
});
|
|
3636
|
+
if ((readModeParam(params) ?? "auto") === "start") throw idleContinuationError(threadId);
|
|
3637
|
+
const endpoint = await resolveEndpointForThread({
|
|
3638
|
+
endpoints,
|
|
3639
|
+
request: writeRequest,
|
|
3640
|
+
endpointId: readStringParam(params, "endpoint_id"),
|
|
3641
|
+
threadId
|
|
3642
|
+
});
|
|
3643
|
+
const thread = await readThread({
|
|
3644
|
+
request: writeRequest,
|
|
3645
|
+
endpoint,
|
|
3646
|
+
threadId,
|
|
3647
|
+
includeTurns: true
|
|
3648
|
+
});
|
|
3649
|
+
requireCurrentEndpoint(options, "write-controls", endpoint);
|
|
3650
|
+
if (statusType(thread) !== "active") throw idleContinuationError(threadId);
|
|
3651
|
+
const turnId = await resolveInProgressTurnId({
|
|
3652
|
+
request: writeRequest,
|
|
3653
|
+
endpoint,
|
|
3654
|
+
thread,
|
|
3655
|
+
threadId
|
|
3656
|
+
});
|
|
3657
|
+
if (!turnId) throw new Error(`Codex thread ${threadId} is active but no in-progress turn is readable`);
|
|
3658
|
+
await writeRequest(endpoint, "turn/steer", {
|
|
3659
|
+
threadId,
|
|
3660
|
+
expectedTurnId: turnId,
|
|
3661
|
+
input: [{
|
|
3662
|
+
type: "text",
|
|
3663
|
+
text,
|
|
3664
|
+
text_elements: []
|
|
3665
|
+
}]
|
|
3666
|
+
});
|
|
3667
|
+
const result = {
|
|
3668
|
+
endpointId: endpoint.id,
|
|
3669
|
+
threadId,
|
|
3670
|
+
mode: "steer",
|
|
3671
|
+
turnId
|
|
3672
|
+
};
|
|
3673
|
+
return jsonResult({
|
|
3674
|
+
summary: `codex steer: ${turnId}`,
|
|
3675
|
+
result
|
|
3676
|
+
});
|
|
3677
|
+
}
|
|
3678
|
+
},
|
|
3679
|
+
{
|
|
3680
|
+
name: "codex_session_interrupt",
|
|
3681
|
+
label: "Codex Session Interrupt",
|
|
3682
|
+
description: "Interrupt an active Codex turn.",
|
|
3683
|
+
parameters: SessionInterruptParamsSchema,
|
|
3684
|
+
execute: async (_toolCallId, rawParams) => {
|
|
3685
|
+
const { endpoints, pluginConfig } = current();
|
|
3686
|
+
requireWriteAccess(options, pluginConfig);
|
|
3687
|
+
const params = asRecord(rawParams);
|
|
3688
|
+
const threadId = readStringParam(params, "thread_id", { required: true });
|
|
3689
|
+
const endpoint = await resolveEndpointForThread({
|
|
3690
|
+
endpoints,
|
|
3691
|
+
request: writeRequest,
|
|
3692
|
+
endpointId: readStringParam(params, "endpoint_id"),
|
|
3693
|
+
threadId
|
|
3694
|
+
});
|
|
3695
|
+
const thread = await readThread({
|
|
3696
|
+
request: writeRequest,
|
|
3697
|
+
endpoint,
|
|
3698
|
+
threadId,
|
|
3699
|
+
includeTurns: true
|
|
3700
|
+
});
|
|
3701
|
+
requireCurrentEndpoint(options, "write-controls", endpoint);
|
|
3702
|
+
if (statusType(thread) !== "active") throw new Error(`Codex thread ${threadId} has no active turn to interrupt`);
|
|
3703
|
+
const turnId = readStringParam(params, "turn_id") ?? await resolveInProgressTurnId({
|
|
3704
|
+
request: writeRequest,
|
|
3705
|
+
endpoint,
|
|
3706
|
+
thread,
|
|
3707
|
+
threadId
|
|
3708
|
+
});
|
|
3709
|
+
if (!turnId) throw new Error(`Codex thread ${threadId} has no readable in-progress turn`);
|
|
3710
|
+
await writeRequest(endpoint, "turn/interrupt", {
|
|
3711
|
+
threadId,
|
|
3712
|
+
turnId
|
|
3713
|
+
});
|
|
3714
|
+
const result = {
|
|
3715
|
+
endpointId: endpoint.id,
|
|
3716
|
+
threadId,
|
|
3717
|
+
turnId
|
|
3718
|
+
};
|
|
3719
|
+
return jsonResult({
|
|
3720
|
+
summary: `codex interrupted: ${turnId}`,
|
|
3721
|
+
result
|
|
3722
|
+
});
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
];
|
|
3726
|
+
}
|
|
2584
3727
|
//#endregion
|
|
2585
3728
|
//#region extensions/codex/src/web-search-provider.ts
|
|
2586
|
-
const loadCodexWebSearchRuntime = createLazyRuntimeModule(() => import("./web-search-provider.runtime-
|
|
3729
|
+
const loadCodexWebSearchRuntime = createLazyRuntimeModule(() => import("./web-search-provider.runtime-CKVBn3fP.js"));
|
|
2587
3730
|
const CodexWebSearchSchema = {
|
|
2588
3731
|
type: "object",
|
|
2589
3732
|
properties: { query: {
|
|
@@ -2625,21 +3768,66 @@ const ENDED_SESSION_REASONS = /* @__PURE__ */ new Set([
|
|
|
2625
3768
|
var codex_default = definePluginEntry({
|
|
2626
3769
|
id: "codex",
|
|
2627
3770
|
name: "Codex",
|
|
2628
|
-
description: "Codex app-server harness and
|
|
3771
|
+
description: "Codex app-server harness and native session supervision.",
|
|
2629
3772
|
register(api) {
|
|
2630
3773
|
const resolveCurrentConfig = () => api.runtime.config?.current ? api.runtime.config.current() : void 0;
|
|
2631
|
-
const
|
|
2632
|
-
|
|
3774
|
+
const resolvePluginConfig = (resolveConfig) => {
|
|
3775
|
+
const liveConfig = resolveConfig();
|
|
3776
|
+
if (!liveConfig) return api.pluginConfig;
|
|
3777
|
+
const livePluginConfig = resolveLivePluginConfigObject(() => liveConfig, "codex", api.pluginConfig);
|
|
3778
|
+
if (!resolveEffectiveEnableState({
|
|
3779
|
+
id: "codex",
|
|
3780
|
+
origin: "bundled",
|
|
3781
|
+
config: normalizePluginsConfig(liveConfig.plugins),
|
|
3782
|
+
rootConfig: liveConfig,
|
|
3783
|
+
enabledByDefault: readCodexPluginConfig(livePluginConfig).supervision?.enabled === true
|
|
3784
|
+
}).enabled) return;
|
|
3785
|
+
return livePluginConfig;
|
|
3786
|
+
};
|
|
3787
|
+
const resolveCurrentPluginConfig = () => resolvePluginConfig(resolveCurrentConfig);
|
|
3788
|
+
let bindingStateStore;
|
|
3789
|
+
const openBindingStateStore = () => bindingStateStore ??= api.runtime.state.openSyncKeyedStore({
|
|
2633
3790
|
namespace: CODEX_APP_SERVER_BINDING_NAMESPACE,
|
|
2634
3791
|
maxEntries: CODEX_APP_SERVER_BINDING_MAX_ENTRIES,
|
|
2635
3792
|
overflowPolicy: "reject-new"
|
|
2636
|
-
})
|
|
3793
|
+
});
|
|
3794
|
+
const bindingStore = createLazyCodexAppServerBindingStore({
|
|
3795
|
+
entries: () => openBindingStateStore().entries(),
|
|
3796
|
+
lookup: (key) => openBindingStateStore().lookup(key),
|
|
3797
|
+
get update() {
|
|
3798
|
+
const store = openBindingStateStore();
|
|
3799
|
+
return store.update?.bind(store);
|
|
3800
|
+
}
|
|
3801
|
+
});
|
|
3802
|
+
registerCodexCliMetadata(api);
|
|
3803
|
+
const sessionCatalogControl = createCodexSessionCatalogControl({
|
|
3804
|
+
getPluginConfig: resolveCurrentPluginConfig,
|
|
3805
|
+
getRuntimeConfig: resolveCurrentConfig
|
|
3806
|
+
});
|
|
3807
|
+
if (readCodexPluginConfig(resolveCurrentPluginConfig()).sessionCatalog?.enabled !== false) {
|
|
3808
|
+
codexSessionCatalogRuntime.register({
|
|
3809
|
+
api,
|
|
3810
|
+
bindingStore,
|
|
3811
|
+
control: sessionCatalogControl,
|
|
3812
|
+
getRuntimeConfig: resolveCurrentConfig
|
|
3813
|
+
});
|
|
3814
|
+
for (const command of createCodexSessionCatalogNodeHostCommands(sessionCatalogControl)) api.registerNodeHostCommand(command);
|
|
3815
|
+
}
|
|
3816
|
+
for (const policy of createCodexSessionCatalogNodeInvokePolicies()) api.registerNodeInvokePolicy(policy);
|
|
3817
|
+
if (readCodexPluginConfig(resolveCurrentPluginConfig()).supervision?.enabled === true) api.registerTool((context) => {
|
|
3818
|
+
if (context.senderIsOwner !== true) return [];
|
|
3819
|
+
const resolveToolRuntimeConfig = () => context.getRuntimeConfig?.() ?? context.runtimeConfig ?? context.config ?? resolveCurrentConfig();
|
|
3820
|
+
return createCodexSupervisionTools({
|
|
3821
|
+
getPluginConfig: () => resolvePluginConfig(resolveToolRuntimeConfig),
|
|
3822
|
+
getRuntimeConfig: resolveToolRuntimeConfig,
|
|
3823
|
+
senderIsOwner: context.senderIsOwner
|
|
3824
|
+
});
|
|
3825
|
+
}, { names: [...CODEX_SUPERVISION_COMPAT_TOOL_NAMES] });
|
|
2637
3826
|
api.registerAgentHarness(createCodexAppServerAgentHarness({
|
|
2638
3827
|
bindingStore,
|
|
2639
3828
|
resolveConfig: resolveCurrentConfig,
|
|
2640
3829
|
resolvePluginConfig: resolveCurrentPluginConfig
|
|
2641
3830
|
}));
|
|
2642
|
-
api.registerProvider(buildCodexProvider({ pluginConfig: api.pluginConfig }));
|
|
2643
3831
|
api.registerMediaUnderstandingProvider(buildCodexMediaUnderstandingProvider({ pluginConfig: api.pluginConfig }));
|
|
2644
3832
|
api.registerWebSearchProvider(createCodexWebSearchProvider({ resolvePluginConfig: resolveCurrentPluginConfig }));
|
|
2645
3833
|
api.registerMigrationProvider(buildCodexMigrationProvider({ runtime: api.runtime }));
|
|
@@ -2710,7 +3898,7 @@ var codex_default = definePluginEntry({
|
|
|
2710
3898
|
}
|
|
2711
3899
|
}
|
|
2712
3900
|
}));
|
|
2713
|
-
api.on("inbound_claim", (event, ctx) =>
|
|
3901
|
+
api.on("inbound_claim", (event, ctx) => codexConversationBindingRuntime.handleInboundClaim(event, ctx, {
|
|
2714
3902
|
bindingStore,
|
|
2715
3903
|
pluginConfig: resolveCurrentPluginConfig(),
|
|
2716
3904
|
config: resolveCurrentConfig(),
|
|
@@ -2719,14 +3907,14 @@ var codex_default = definePluginEntry({
|
|
|
2719
3907
|
...params
|
|
2720
3908
|
})
|
|
2721
3909
|
}));
|
|
2722
|
-
api.onConversationBindingResolved?.((event) =>
|
|
3910
|
+
api.onConversationBindingResolved?.((event) => codexConversationBindingRuntime.handleBindingResolved(event, { bindingStore }));
|
|
2723
3911
|
api.on("after_compaction", async (event, ctx) => {
|
|
2724
3912
|
const previousSessionId = event.previousSessionId?.trim();
|
|
2725
3913
|
const sessionId = ctx.sessionId?.trim();
|
|
2726
3914
|
if (!previousSessionId || !sessionId || previousSessionId === sessionId) return;
|
|
2727
3915
|
const config = resolveCurrentConfig();
|
|
2728
3916
|
const sessionKey = ctx.sessionKey?.trim();
|
|
2729
|
-
const { sessionBindingIdentity } = await import("./session-binding-
|
|
3917
|
+
const { sessionBindingIdentity } = await import("./session-binding-h1mmCGnl.js").then((n) => n.l);
|
|
2730
3918
|
const identity = sessionBindingIdentity({
|
|
2731
3919
|
sessionId,
|
|
2732
3920
|
...sessionKey ? { sessionKey } : {},
|
|
@@ -2739,8 +3927,11 @@ var codex_default = definePluginEntry({
|
|
|
2739
3927
|
api.on("session_end", async (event, ctx) => {
|
|
2740
3928
|
if (!event.reason || !ENDED_SESSION_REASONS.has(event.reason)) return;
|
|
2741
3929
|
const sessionKey = event.sessionKey ?? ctx.sessionKey;
|
|
3930
|
+
const endedSessionKey = sessionKey?.trim();
|
|
3931
|
+
const nextSessionKey = event.nextSessionKey?.trim();
|
|
3932
|
+
if (endedSessionKey && nextSessionKey && nextSessionKey !== endedSessionKey) return;
|
|
2742
3933
|
const config = resolveCurrentConfig();
|
|
2743
|
-
const { sessionBindingIdentity } = await import("./session-binding-
|
|
3934
|
+
const { sessionBindingIdentity } = await import("./session-binding-h1mmCGnl.js").then((n) => n.l);
|
|
2744
3935
|
await bindingStore.retireSessionGeneration(sessionBindingIdentity({
|
|
2745
3936
|
sessionId: event.sessionId,
|
|
2746
3937
|
...sessionKey ? { sessionKey } : {},
|