@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.
Files changed (46) hide show
  1. package/README.md +27 -3
  2. package/dist/app-server-policy-DVcxdse_.js +49 -0
  3. package/dist/{attempt-notifications-BGsEIIDI.js → attempt-notifications-DEv9h7iC.js} +1 -1
  4. package/dist/auth-binding-C1czuHPA.js +62 -0
  5. package/dist/capabilities-42Dfn2TV.js +33 -0
  6. package/dist/cli-metadata.js +20 -0
  7. package/dist/{app-server-policy-C968Kgin.js → command-formatters-D6ZlGNmb.js} +8 -51
  8. package/dist/{command-handlers-Dv-pzAmg.js → command-handlers-Cpl9fUWv.js} +163 -60
  9. package/dist/{compact-4mVBriKT.js → compact-FnJmiPNA.js} +40 -10
  10. package/dist/computer-use-DVzr8OP1.js +685 -0
  11. package/dist/{config-CYEDnLJ2.js → config-c48K5HP9.js} +200 -29
  12. package/dist/conversation-control-DtQ-E7mr.js +288 -0
  13. package/dist/doctor-contract-api.js +120 -81
  14. package/dist/{provider-capabilities-CDnHbmUZ.js → dynamic-tools-BMLoaTeG.js} +5246 -5140
  15. package/dist/harness.js +55 -15
  16. package/dist/index.js +1435 -265
  17. package/dist/{media-understanding-provider-BHOu9-Gg.js → media-understanding-provider-D580L0P8.js} +28 -10
  18. package/dist/media-understanding-provider.js +1 -1
  19. package/dist/{models-DDmO1zwd.js → models-DuKzZA6G.js} +1 -1
  20. package/dist/notification-correlation-DfaCm0mx.js +67 -0
  21. package/dist/plugin-app-cache-key-BKNjHMEs.js +315 -0
  22. package/dist/{protocol-2POPqAY4.js → protocol-BMifTfdW.js} +3 -1
  23. package/dist/provider-catalog.js +66 -27
  24. package/dist/provider.js +306 -1
  25. package/dist/{provider-zjPfx5Fs.js → rate-limits-Dhp04Rqo.js} +4 -261
  26. package/dist/request-DborTWgw.js +111 -0
  27. package/dist/{run-attempt-FUyOjGCV.js → run-attempt-VPVJoYDP.js} +7897 -7136
  28. package/dist/runtime-artifact-C2ITXdhM.js +488 -0
  29. package/dist/{sandbox-guard-DA2TQfZW.js → sandbox-guard-D1Wky__S.js} +10 -1
  30. package/dist/{session-binding-C1ZXdP-x.js → session-binding-BMfX1OX-.js} +247 -88
  31. package/dist/session-catalog-7H112Tr_.js +2396 -0
  32. package/dist/session-cli-B28RhCyp.js +175 -0
  33. package/dist/{shared-client-4ICy3U6d.js → shared-client-D4mFI9al.js} +1997 -1297
  34. package/dist/{side-question-N4OaDer2.js → side-question-CgJBz52s.js} +135 -67
  35. package/dist/{thread-lifecycle-qWE88Dn2.js → thread-lifecycle-BgLXzjvV.js} +747 -317
  36. package/dist/transcript-mirror-DhLwFIL4.js +485 -0
  37. package/dist/transport-stdio-DXgGZ7Ib.js +88 -0
  38. package/dist/{web-search-provider.runtime-BSlriav6.js → web-search-provider.runtime-Bs-eTn5U.js} +3 -3
  39. package/npm-shrinkwrap.json +56 -30
  40. package/openclaw.plugin.json +168 -10
  41. package/package.json +8 -6
  42. package/dist/command-rpc-eLM_mtC7.js +0 -784
  43. package/dist/computer-use-Bmaz333N.js +0 -377
  44. package/dist/notification-correlation-Bo7KB3ks.js +0 -35
  45. package/dist/plugin-app-cache-key-BrhVdeEf.js +0 -98
  46. 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 { r as isJsonObject, t as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS } from "./protocol-2POPqAY4.js";
3
- import { d as resolveCodexAppServerRuntimeOptions, g as resolveOpenClawExecPolicyForCodexAppServer, i as codexSandboxPolicyForTurn, n as canUseCodexModelBackedApprovalsReviewerForModel, t as CODEX_PLUGINS_MARKETPLACE_NAME, u as readCodexPluginConfig } from "./config-CYEDnLJ2.js";
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 { G as pluginReadParams, W as ensureCodexPluginActivation, f as resolveCodexAppServerRequestModelSelection, q as defaultCodexAppInventoryCache, t as CODEX_NATIVE_PERSONALITY_NONE } from "./thread-lifecycle-qWE88Dn2.js";
6
- import { t as buildCodexProvider } from "./provider-zjPfx5Fs.js";
7
- import { _ as resolveCodexAppServerAuthAccountCacheKey, a as getLeasedSharedCodexAppServerClient, b as resolveCodexAppServerFallbackApiKeyCacheKey, o as releaseLeasedSharedCodexAppServerClient, r as clearSharedCodexAppServerClientIfCurrentAndWait, y as resolveCodexAppServerAuthProfileIdForAgent } from "./shared-client-4ICy3U6d.js";
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 normalizeCodexAppServerBindingModelProvider, i as isCodexAppServerNativeAuthProfile, s as sessionBindingIdentity } from "./session-binding-C1ZXdP-x.js";
10
- import { t as buildCodexMediaUnderstandingProvider } from "./media-understanding-provider-BHOu9-Gg.js";
11
- import { i as describeControlFailure, n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS } from "./plugin-app-cache-key-BrhVdeEf.js";
12
- import { a as formatCodexDisplayText, t as resolveCodexAppServerForModelProvider } from "./app-server-policy-C968Kgin.js";
13
- import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-DA2TQfZW.js";
14
- import { S as canMutateCodexHost, a as createCodexCliSessionNodeHostCommands, b as trackCodexConversationActiveTurn, c as listCodexCliSessionsOnNode, l as resolveCodexCliSessionForBindingOnNode, o as createCodexCliSessionNodeInvokePolicies, t as codexControlRequest, u as resumeCodexCliSessionOnNode, x as CODEX_NATIVE_EXECUTION_AUTH_ERROR } from "./command-rpc-eLM_mtC7.js";
15
- import { a as readCodexConversationBindingDataRecord, i as readCodexConversationBindingData } from "./conversation-binding-data-BHRpYbX3.js";
16
- import { n as readCodexNotificationThreadId, r as readCodexNotificationTurnId } from "./notification-correlation-Bo7KB3ks.js";
17
- import { t as requestCodexAppServerJson } from "./request-BcJyl8KL.js";
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-C1ZXdP-x.js").then((n) => n.c).then(({ createCodexAppServerBindingStore }) => createCodexAppServerBindingStore(state));
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 client = await getLeasedSharedCodexAppServerClient({
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 resolved.client.request("thread/start", {
487
- cwd: params.workspaceDir,
488
- ...resolved.model ? { model: resolved.model } : {},
489
- ...resolved.modelProvider ? { modelProvider: resolved.modelProvider } : {},
490
- personality: CODEX_NATIVE_PERSONALITY_NONE,
491
- ...buildThreadRequestRuntimeOptions(params, resolved),
492
- developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
493
- experimentalRawEvents: true
494
- }, { timeoutMs: resolved.runtime.requestTimeoutMs });
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 resolved.client.request(CODEX_CONTROL_METHODS.resumeThread, {
522
- threadId: params.threadId,
523
- ...resolved.model ? { model: resolved.model } : {},
524
- ...resolved.modelProvider ? { modelProvider: resolved.modelProvider } : {},
525
- personality: CODEX_NATIVE_PERSONALITY_NONE,
526
- ...buildThreadRequestRuntimeOptions(params, resolved)
527
- }, { timeoutMs: resolved.runtime.requestTimeoutMs }));
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
- releaseLeasedSharedCodexAppServerClient(resolved.client);
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
- releaseLeasedSharedCodexAppServerClient(resolved.client);
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 client = await getLeasedSharedCodexAppServerClient({
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 client.request("thread/start", {
611
- cwd: workspaceDir,
612
- ...modelSelection?.model ? { model: modelSelection.model } : {},
613
- ...modelSelection?.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
614
- personality: CODEX_NATIVE_PERSONALITY_NONE,
615
- approvalPolicy,
616
- approvalsReviewer: modelScopedRuntime.approvalsReviewer,
617
- ...modelScopedRuntime.networkProxy ? { config: modelScopedRuntime.networkProxy.configPatch } : { sandbox },
618
- ...serviceTier ? { serviceTier } : {},
619
- developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
620
- experimentalRawEvents: true
621
- }, { timeoutMs: runtime.requestTimeoutMs }));
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
- releaseLeasedSharedCodexAppServerClient(client);
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/source.ts
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 name = (typeof manifest.name === "string" ? manifest.name.trim() : "") || path.basename(dir);
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 codexSkills = await discoverSkillDirs({
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 uniqueSkillName(skill, counts) {
1706
- const base = sanitizeName(skill.name) || "codex-skill";
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
- return sanitizeName([
1709
- "codex",
1710
- sanitizeName(path.basename(path.dirname(skill.source))),
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
- async function buildSkillItems(params) {
1715
- const baseCounts = /* @__PURE__ */ new Map();
1716
- for (const skill of params.skills) {
1717
- const base = sanitizeName(skill.name) || "codex-skill";
1718
- baseCounts.set(base, (baseCounts.get(base) ?? 0) + 1);
1719
- }
1720
- const resolvedCounts = /* @__PURE__ */ new Map();
1721
- const planned = params.skills.map((skill) => {
1722
- const name = uniqueSkillName(skill, baseCounts);
1723
- resolvedCounts.set(name, (resolvedCounts.get(name) ?? 0) + 1);
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
- evaluatePluginMigrationEligibility: true,
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 buildCodexAuthItems({
1961
- ctx,
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
- const pluginItems = buildPluginItems(ctx, source.plugins);
1971
- items.push(...pluginItems);
1972
- const pluginConfigItem = buildPluginConfigItem(ctx, pluginItems);
1973
- if (pluginConfigItem) items.push(pluginConfigItem);
1974
- for (const archivePath of source.archivePaths) items.push(createMigrationItem({
1975
- id: archivePath.id,
1976
- kind: "archive",
1977
- action: "archive",
1978
- source: archivePath.path,
1979
- message: archivePath.message ?? "Archived in the migration report for manual review; not imported into live config.",
1980
- details: { archiveRelativePath: archivePath.relativePath }
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: "Inventory and promote Codex CLI skills while keeping Codex native plugins and hooks explicit.",
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({ input: ctx.source });
2349
- const found = hasCodexSource(source);
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
- sessionFile: runtime.agent.session.resolveSessionFilePath(sessionId, entry, {
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
- /** Builds the native Codex thread tool only for owner runs in shared-user-home mode. */
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
- if (readCodexPluginConfig(options.getPluginConfig()).appServer?.homeScope !== "user") return null;
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 requestOptions = () => ({
2743
+ const runtimeConfig = () => options.context.getRuntimeConfig?.() ?? options.context.runtimeConfig ?? options.context.config;
2744
+ const baseRequestOptions = () => ({
2465
2745
  agentDir: options.context.agentDir,
2466
- config: options.context.getRuntimeConfig?.() ?? options.context.runtimeConfig ?? options.context.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
- return sessionBindingIdentity({
2473
- sessionId,
2474
- sessionKey: options.context.sessionKey,
2475
- agentId: options.context.agentId,
2476
- config: requestOptions().config
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, read, fork, rename, archive, or restore native Codex threads. Fork to continue a thread safely across Codex clients; do not resume the same thread from two clients.",
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
- return jsonResult(await request(pluginConfig, CODEX_CONTROL_METHODS.listThreads, {
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") return jsonResult(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
2508
- threadId,
2509
- includeTurns: readBoolean(params.include_turns)
2510
- }, requestOptions()));
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") return jsonResult(await request(pluginConfig, CODEX_CONTROL_METHODS.unarchiveThread, { threadId }, requestOptions()));
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 (binding?.threadId === threadId) {
2532
- if (readThreadStatusType(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
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())) === "active") throw new Error("cannot archive the Codex thread active in this OpenClaw session");
2536
- }
2537
- await request(pluginConfig, CODEX_CONTROL_METHODS.archiveThread, { threadId }, requestOptions());
2538
- if (session && binding?.threadId === threadId) await options.bindingStore.mutate(currentIdentity(session.sessionId), {
2539
- kind: "clear",
2540
- threadId
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 && binding?.threadId === threadId) {
2551
- if (readThreadStatusType(await request(pluginConfig, CODEX_CONTROL_METHODS.readThread, {
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())) === "active") throw new Error("cannot replace the Codex thread active in this OpenClaw turn");
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
- }, requestOptions());
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
- return jsonResult({
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-BSlriav6.js"));
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 and Codex-managed GPT model catalog.",
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 resolveCurrentPluginConfig = () => resolveLivePluginConfigObject(resolveCurrentConfig, "codex", api.pluginConfig) ?? api.pluginConfig;
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) => handleCodexConversationInboundClaim(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) => handleCodexConversationBindingResolved(event, { bindingStore }));
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-C1ZXdP-x.js").then((n) => n.c);
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-C1ZXdP-x.js").then((n) => n.c);
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 } : {},