@openclaw/codex 2026.7.1 → 2026.7.2-beta.2

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