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