@openclaw/codex 2026.8.2 → 2026.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/api.js +3 -3
  2. package/dist/{attempt-client-cleanup-CpVfwctT.js → attempt-client-cleanup-DYHcRQAa.js} +6 -43
  3. package/dist/attempt-context-DmQUNWwT.js +634 -0
  4. package/dist/{binding-connection-CF7BmrDk.js → binding-connection-E6uDsPlx.js} +2 -2
  5. package/dist/{bounded-turn-DRC3hcjm.js → bounded-turn-wzJrwKKe.js} +8 -9
  6. package/dist/{capabilities-D6m_TdVy.js → capabilities-dIjNVCYH.js} +1 -1
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters--UToCokS.js → command-formatters-CLHhEB1Q.js} +3 -3
  9. package/dist/{command-handlers-CLFx2Ypa.js → command-handlers-CTGb2csu.js} +54 -55
  10. package/dist/{compact-5xqW1oCh.js → compact-_t0HQrUS.js} +33 -25
  11. package/dist/{config-CcYL77O6.js → config-BhmfC1s6.js} +12 -8
  12. package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
  13. package/dist/doctor-contract-api.js +31 -4
  14. package/dist/{dynamic-tools-BmeXjwNZ.js → dynamic-tools-BU9tmlYm.js} +448 -457
  15. package/dist/{effective-mcp-catalog-XXQKgK1U.js → effective-mcp-catalog-DzYBW4h1.js} +2 -2
  16. package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-DHRFXKbS.js} +4 -4
  17. package/dist/{event-projector-usage-B2r5vhWH.js → event-projector-usage-C5mcrGjp.js} +9 -137
  18. package/dist/{harness-BgeB7LnD.js → harness-DXVZ5II5.js} +13 -13
  19. package/dist/harness.js +1 -1
  20. package/dist/index.js +323 -313
  21. package/dist/{isolated-completion-CxTus3vZ.js → isolated-completion-kg9kv4iH.js} +4 -4
  22. package/dist/{launch-args-sXOAg0SX.js → launch-args-RXQwn8zV.js} +7 -1
  23. package/dist/{managed-binary-ZkGodW19.js → managed-binary-4-Vjz5GH.js} +14 -9
  24. package/dist/media-understanding-provider.js +1 -1
  25. package/dist/{model-catalog-DULBcniB.js → model-catalog-CUM68Hkl.js} +11 -11
  26. package/dist/{models-DaWfETQk.js → models-sYnEc0gd.js} +3 -3
  27. package/dist/{native-subagent-monitor-vXYUZH37.js → native-subagent-monitor-CnH147jH.js} +3 -3
  28. package/dist/{node-exec-server.runtime-DirKVZ5h.js → node-exec-server.runtime-CstNHCmC.js} +2 -2
  29. package/dist/{plugin-approval-roundtrip-CCWQGlAI.js → plugin-approval-roundtrip-CNe-Xrjx.js} +29 -3
  30. package/dist/{plugin-marketplace-discovery-BXjIFEiw.js → plugin-marketplace-discovery-CfFlKqDl.js} +6 -6
  31. package/dist/{plugin-metadata-cache-DQQm4gWT.js → plugin-metadata-cache-BFrPhb2c.js} +5 -5
  32. package/dist/provider-capabilities-DW5p4k9w.js +101 -0
  33. package/dist/{rate-limits-BqHEHXQJ.js → rate-limits-CawQJfUS.js} +1 -1
  34. package/dist/{request-BWxweEdK.js → request-DYAI5FQE.js} +18 -6
  35. package/dist/{run-attempt-BVLE4Gbi.js → run-attempt-D1hJO4fX.js} +483 -1865
  36. package/dist/{runtime-artifact-D9pYp4zL.js → runtime-artifact-C3jVimFn.js} +19 -12
  37. package/dist/{session-binding-D0zinCP1.js → session-binding-BHuTCRZQ.js} +8 -10
  38. package/dist/{session-catalog-eMhVm0Ql.js → session-catalog-yyvTWFOw.js} +138 -116
  39. package/dist/{session-cli-B2lyjJA0.js → session-cli-CmFEXMAM.js} +1 -1
  40. package/dist/session-initialization-BeWzjGiR.js +74 -0
  41. package/dist/{session-permission-policy-ScnAyG90.js → session-permission-policy-DeWVTCMj.js} +2 -2
  42. package/dist/{session-retirement-CtUtUTr9.js → session-retirement-BujDjXVF.js} +16 -9
  43. package/dist/{session-upstream-marker-gqrYIRHk.js → session-upstream-marker-C351-5Qw.js} +18 -2
  44. package/dist/{settled-turn-context-Bk3IUwod.js → settled-turn-context-D3SNWtUn.js} +6 -28
  45. package/dist/{settled-turn-finalizer-BXLN9PfJ.js → settled-turn-finalizer-BVJx4yoc.js} +5 -5
  46. package/dist/{shared-client-B1tT5U6r.js → shared-client-DxcfF9KX.js} +2748 -101
  47. package/dist/{side-question-CZ-mCw-t.js → side-question-BTZAII4l.js} +97 -63
  48. package/dist/{thread-lifecycle-CGGKP720.js → thread-lifecycle-DAcEJ_Om.js} +2039 -1487
  49. package/dist/{thread-ownership-RkaoZCmQ.js → thread-ownership-zGokd8i1.js} +10 -9
  50. package/dist/thread-policy-g4KbJPzp.js +54 -0
  51. package/dist/{thread-resume-BFt1-ZDa.js → thread-resume-C7pyVKeW.js} +15 -7
  52. package/dist/{transcript-mirror-1ep4XjzF.js → transcript-mirror-DTUxFWa_.js} +47 -10
  53. package/dist/{transport-stdio-NT_Dpd9V.js → transport-stdio-CyPldczm.js} +3 -1
  54. package/dist/upstream-session-fork-CYlBIGVt.js +966 -0
  55. package/dist/{usage-DUhd58gI.js → usage-Bj-rrzkR.js} +3 -3
  56. package/dist/{web-search-provider.runtime-BEmlE7ov.js → web-search-provider.runtime-D2FWSK4w.js} +3 -3
  57. package/openclaw.plugin.json +0 -30
  58. package/package.json +5 -5
  59. package/dist/protocol-validators-Bg5rpNxk.js +0 -2542
  60. package/dist/session-catalog-home-id-Dt2GiG5o.js +0 -112
  61. package/dist/upstream-session-fork-Dc8BFExw.js +0 -312
package/dist/index.js CHANGED
@@ -1,27 +1,26 @@
1
1
  import { registerCodexCliMetadata } from "./cli-metadata.js";
2
- import { n as createCodexAppServerNativeCompaction, t as createCodexAppServerAgentHarness } from "./harness-BgeB7LnD.js";
3
- import { D as resolveOpenClawExecPolicyForCodexAppServer, T as readCodexPluginConfig, c as assertCodexAppServerConnectionSecurity, i as resolveCodexAppServerRuntimeOptions, j as CODEX_PLUGINS_MARKETPLACE_NAME, n as codexSandboxPolicyForTurn, p as canUseCodexModelBackedApprovalsReviewerForModel, s as resolveCodexSupervisionAppServerRuntimeOptions, t as codexAppServerStartOptionsKey, v as readCodexRequirementsToml } from "./config-CcYL77O6.js";
4
- import { C as withLeasedCodexAppServerClientStartSelectionRetry, Ft as resolveCodexAppServerFallbackApiKeyCacheKey, Ht as isJsonObject, J as isCodexAppServerOverloadError, K as isCodexAppServerIndeterminateRequestCancellationError, Nt as resolveCodexAppServerAuthProfileIdForAgent, St as releaseCodexAppServerLiveThread, Z as isUnsupportedCodexAppServerVersionError, _ as retainSharedCodexAppServerClientByInstanceId, b as retireSharedCodexAppServerClientsBeforeDesktopGeneration, bt as isCodexAppServerLiveThreadClaimed, c as getLeasedSharedCodexAppServerClient, gt as consumeCodexAppServerLiveThread, h as releaseLeasedSharedCodexAppServerClient, jt as resolveCodexAppServerAuthAccountCacheKey, k as createCodexDesktopGenerationService, m as releaseCodexAppServerClientLease, o as clearSharedCodexAppServerClientIfCurrentAndWait, vt as hasCodexAppServerLiveThread, yt as isCodexAppServerClientRuntimeLive, zt as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS } from "./shared-client-B1tT5U6r.js";
5
- import { i as setManagedCodexPluginRoot } from "./managed-binary-ZkGodW19.js";
2
+ import { n as createCodexAppServerNativeCompaction, t as createCodexAppServerAgentHarness } from "./harness-DXVZ5II5.js";
3
+ import { $t as isJsonObject, B as isCodexAppServerIndeterminateRequestCancellationError, Bt as resolveCodexAppServerAuthProfileIdForAgent, C as withLeasedCodexAppServerClientStartSelectionRetry, Et as isCodexAppServerLiveThreadClaimed, G as isUnsupportedCodexAppServerVersionError, H as isCodexAppServerOverloadError, Ht as resolveCodexAppServerFallbackApiKeyCacheKey, J as CodexThreadDirectInputError, O as createCodexDesktopGenerationService, Rt as resolveCodexAppServerAuthAccountCacheKey, St as consumeCodexAppServerLiveThread, Tt as isCodexAppServerClientRuntimeLive, X as assertCodexThreadAcceptsDirectInput, Xt as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS, _ as retainSharedCodexAppServerClientByInstanceId, b as retireSharedCodexAppServerClientsBeforeDesktopGeneration, c as getLeasedSharedCodexAppServerClient, et as assertCodexThreadStartResponse, h as releaseLeasedSharedCodexAppServerClient, kt as releaseCodexAppServerLiveThread, m as releaseCodexAppServerClientLease, o as clearSharedCodexAppServerClientIfCurrentAndWait, wt as hasCodexAppServerLiveThread } from "./shared-client-DxcfF9KX.js";
4
+ import { E as readCodexPluginConfig, M as CODEX_PLUGINS_MARKETPLACE_NAME, O as resolveOpenClawExecPolicyForCodexAppServer, c as assertCodexAppServerConnectionSecurity, i as resolveCodexAppServerRuntimeOptions, m as canUseCodexModelBackedApprovalsReviewerForModel, n as codexSandboxPolicyForTurn, s as resolveCodexSupervisionAppServerRuntimeOptions, t as codexAppServerStartOptionsKey, y as readCodexRequirementsToml } from "./config-BhmfC1s6.js";
5
+ import { a as setManagedCodexPluginRoot } from "./managed-binary-4-Vjz5GH.js";
6
6
  import { n as CODEX_APP_SERVER_BINDING_NAMESPACE, t as CODEX_APP_SERVER_BINDING_MAX_ENTRIES } from "./session-binding-meta-B7aEMU7g.js";
7
- import { d as sessionBindingIdentity, n as assertCodexBindingMayBeReplaced, o as isCodexAppServerNativeAuthProfile, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-D0zinCP1.js";
8
- import { o as createCodexAppServerProcessReaperService } from "./transport-stdio-NT_Dpd9V.js";
9
- import { r as assertCodexThreadAcceptsDirectInput, s as assertCodexThreadStartResponse, t as CodexThreadDirectInputError } from "./protocol-validators-Bg5rpNxk.js";
10
- import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, f as unsubscribeCodexThreadBestEffort, g as isCodexNotificationForTurn, m as getCodexAppServerTurnRouter, o as interruptCodexTurnAndWaitBestEffort, u as retireUnsafeCodexTurnClientBestEffort } from "./attempt-client-cleanup-CpVfwctT.js";
11
- import { s as isAssistantCommentaryCompletionNotification } from "./event-projector-usage-B2r5vhWH.js";
12
- import { i as buildCodexPluginAppCacheKey, s as defaultCodexAppInventoryCache, t as resumeCodexAppServerThread } from "./thread-resume-BFt1-ZDa.js";
13
- import { C as buildCodexProjectDocThreadConfig, E as resolveCodexAppServerRequestModelSelection, M as CODEX_MANAGED_THREAD_NAMESPACE, N as createCodexManagedThreadStore, ct as ensureCodexPluginActivation, ft as pluginReadParams, it as buildDisabledAppsConfigPatch, j as CODEX_MANAGED_THREAD_MAX_ENTRIES, ot as mergeCodexThreadConfigs, ut as isOpenAiCuratedMarketplace, w as CODEX_NATIVE_PERSONALITY_NONE } from "./thread-lifecycle-CGGKP720.js";
14
- import { a as rollbackCodexAppServerBindingSubscription, c as withExclusiveCodexAppServerThread, i as retireCodexConversationThreadBinding, l as isIncognitoSessionKey, n as releaseCodexAppServerBindingSubscription, r as retainCodexAppServerBindingSubscription, s as withCodexConversationThreadActivity, t as isSameCodexAppServerThreadOwner } from "./thread-ownership-RkaoZCmQ.js";
15
- import { c as resolveCodexNativeSandboxBlock, i as requestCodexAppServerJson, o as withCodexAppServerJsonClient, s as resolveCodexNativeExecutionBlock } from "./request-BWxweEdK.js";
16
- import { p as projectBoundedCodexVisibleSessionHistory } from "./transcript-mirror-1ep4XjzF.js";
7
+ import { d as sessionBindingIdentity, n as assertCodexBindingMayBeReplaced, o as isCodexAppServerNativeAuthProfile, s as normalizeCodexAppServerBindingModelProvider } from "./session-binding-BHuTCRZQ.js";
8
+ import { o as createCodexAppServerProcessReaperService } from "./transport-stdio-CyPldczm.js";
9
+ import { a as closeCodexStartupClientBestEffort, c as isCodexAppServerUnsafeSubscriptionError, d as unsubscribeCodexThreadBestEffort, h as isCodexNotificationForTurn, l as retireUnsafeCodexTurnClientBestEffort, o as interruptCodexTurnAndWaitBestEffort, p as getCodexAppServerTurnRouter } from "./attempt-client-cleanup-DYHcRQAa.js";
10
+ import { o as isAssistantCommentaryCompletionNotification } from "./event-projector-usage-C5mcrGjp.js";
11
+ import { i as buildCodexPluginAppCacheKey, s as defaultCodexAppInventoryCache, t as resumeCodexAppServerThread } from "./thread-resume-C7pyVKeW.js";
12
+ import { Dt as mergeCodexThreadConfigs, E as resolveCodexAppServerRequestModelSelection, Et as buildDisabledAppsConfigPatch, J as createCodexManagedThreadStore, K as CODEX_MANAGED_THREAD_MAX_ENTRIES, Nt as pluginReadParams, jt as isOpenAiCuratedMarketplace, k as buildCodexProjectDocThreadConfig, kt as ensureCodexPluginActivation, q as CODEX_MANAGED_THREAD_NAMESPACE, w as CODEX_NATIVE_PERSONALITY_NONE } from "./thread-lifecycle-DAcEJ_Om.js";
13
+ import { a as rollbackCodexAppServerBindingSubscription, c as withExclusiveCodexAppServerThread, i as retireCodexConversationThreadBinding, l as isIncognitoSessionKey, n as releaseCodexAppServerBindingSubscription, r as retainCodexAppServerBindingSubscription, s as withCodexConversationThreadActivity, t as isSameCodexAppServerThreadOwner } from "./thread-ownership-zGokd8i1.js";
14
+ import { p as projectBoundedCodexVisibleSessionHistory } from "./transcript-mirror-DTUxFWa_.js";
15
+ import { c as resolveCodexNativeSandboxBlock, i as requestCodexAppServerJson, o as withCodexAppServerJsonClient, s as resolveCodexNativeExecutionBlock } from "./request-DYAI5FQE.js";
17
16
  import { buildCodexMediaUnderstandingProvider } from "./media-understanding-provider.js";
18
- import { n as describeControlFailure, t as CODEX_CONTROL_METHODS } from "./capabilities-D6m_TdVy.js";
19
- import { r as formatCodexDisplayText } from "./command-formatters--UToCokS.js";
20
- import { a as resolveCodexAppServerForModelProvider, i as resolveCodexSessionPermissionCwd, n as applyCodexSessionPermissionPolicy, t as CODEX_SESSION_PERMISSION_EXEC_MODES } from "./session-permission-policy-ScnAyG90.js";
21
- import { f as trackCodexConversationActiveTurn, g as canMutateCodexHost, h as CODEX_NATIVE_EXECUTION_AUTH_ERROR, t as discoverCodexMarketplacePlugins } from "./plugin-marketplace-discovery-BXjIFEiw.js";
17
+ import { n as describeControlFailure, t as CODEX_CONTROL_METHODS } from "./capabilities-dIjNVCYH.js";
18
+ import { a as formatCodexDisplayText } from "./command-formatters-CLHhEB1Q.js";
19
+ import { a as resolveCodexAppServerForModelProvider, i as resolveCodexSessionPermissionCwd, n as applyCodexSessionPermissionPolicy, t as CODEX_SESSION_PERMISSION_EXEC_MODES } from "./session-permission-policy-DeWVTCMj.js";
20
+ import { f as trackCodexConversationActiveTurn, g as canMutateCodexHost, h as CODEX_NATIVE_EXECUTION_AUTH_ERROR, t as discoverCodexMarketplacePlugins } from "./plugin-marketplace-discovery-CfFlKqDl.js";
22
21
  import { a as readCodexConversationBindingDataRecord, i as readCodexConversationBindingData, o as resolveCodexDefaultWorkspaceDir, r as createCodexConversationBindingData } from "./conversation-binding-data-BiodpIzq.js";
23
- import { n as resolveCodexBindingAppServerConnection } from "./binding-connection-CF7BmrDk.js";
24
- import { d as createCodexCliSessionNodeHostCommands, f as createCodexCliSessionNodeInvokePolicies, g as resumeCodexCliSessionOnNode, h as resolveCodexCliSessionForBindingOnNode, i as createCodexSessionCatalogNodeHostCommands, m as listCodexCliSessionsOnNode, n as createCodexSessionCatalogNodeInvokePolicies, o as assertCodexArchiveDescendantsUnowned, r as createCodexSessionCatalogControl, s as codexControlRequest, t as codexSessionCatalogRuntime } from "./session-catalog-eMhVm0Ql.js";
22
+ import { n as resolveCodexBindingAppServerConnection } from "./binding-connection-E6uDsPlx.js";
23
+ import { d as createCodexCliSessionNodeHostCommands, f as createCodexCliSessionNodeInvokePolicies, g as resumeCodexCliSessionOnNode, h as resolveCodexCliSessionForBindingOnNode, i as createCodexSessionCatalogNodeHostCommands, m as listCodexCliSessionsOnNode, n as createCodexSessionCatalogNodeInvokePolicies, o as assertCodexArchiveDescendantsUnowned, r as createCodexSessionCatalogControl, s as codexControlRequest, t as codexSessionCatalogRuntime } from "./session-catalog-yyvTWFOw.js";
25
24
  import { t as createCodexWebSearchProviderBase } from "./web-search-provider.shared-BrZmlqyR.js";
26
25
  import { mutateConfigFile } from "openclaw/plugin-sdk/config-mutation";
27
26
  import { normalizePluginsConfig, resolveEffectiveEnableState, resolveLivePluginConfigObject, resolvePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime";
@@ -31,6 +30,7 @@ import fs from "node:fs/promises";
31
30
  import path from "node:path";
32
31
  import { parseStrictNonNegativeInteger, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
33
32
  import { embeddedAgentLog, formatErrorMessage, resolveActiveEmbeddedRunSessionId, resolveSandboxContext } from "openclaw/plugin-sdk/agent-harness-runtime";
33
+ import { canonicalPathFromExistingAncestor, isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
34
34
  import os from "node:os";
35
35
  import { loadAuthProfileStoreWithoutExternalProfiles, resolveAgentConfig, resolveAgentWorkspaceDir, resolveDefaultAgentDir, resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
36
36
  import { applyAuthProfileConfig, buildApiKeyCredential, buildOauthProviderAuthResult, buildOpenAICodexCredentialExtra, readCodexCliCredentialsCached, resolveOpenAICodexAuthIdentity, resolveOpenAICodexImportProfileName, updateAuthProfileStoreWithLock } from "openclaw/plugin-sdk/provider-auth";
@@ -41,7 +41,6 @@ import { getSessionEntry, resolveStorePath, resolveTranscriptSessionKeyBySession
41
41
  import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
42
42
  import { fileURLToPath } from "node:url";
43
43
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
44
- import { canonicalPathFromExistingAncestor, isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
45
44
  import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
46
45
  import { readVisibleSessionTranscriptMessageEntries } from "openclaw/plugin-sdk/session-transcript-runtime";
47
46
  import { getSessionBindingService } from "openclaw/plugin-sdk/conversation-binding-runtime";
@@ -181,7 +180,7 @@ function waitForReconnect(delayMs, signal) {
181
180
  /** Defers schema compilation and auth loading until the first binding operation. */
182
181
  function createLazyCodexAppServerBindingStore(state, managedThreadState) {
183
182
  let resolved;
184
- const store = () => resolved ??= import("./session-binding-D0zinCP1.js").then((n) => n.f).then(({ createCodexAppServerBindingStore }) => createCodexAppServerBindingStore(state));
183
+ const store = () => resolved ??= import("./session-binding-BHuTCRZQ.js").then((n) => n.f).then(({ createCodexAppServerBindingStore }) => createCodexAppServerBindingStore(state));
185
184
  const managedThreads = managedThreadState ? createCodexManagedThreadStore(managedThreadState) : void 0;
186
185
  return {
187
186
  ...managedThreads ? { managedThreads } : {},
@@ -212,7 +211,7 @@ async function handleCodexCommand(ctx, options) {
212
211
  }
213
212
  }
214
213
  async function loadDefaultCodexSubcommandHandler() {
215
- const { handleCodexSubcommand } = await import("./command-handlers-CLFx2Ypa.js");
214
+ const { handleCodexSubcommand } = await import("./command-handlers-CTGb2csu.js");
216
215
  return handleCodexSubcommand;
217
216
  }
218
217
  //#endregion
@@ -525,11 +524,15 @@ async function startCodexConversationThread(params) {
525
524
  agentId: params.agentId
526
525
  };
527
526
  const threadId = params.threadId?.trim();
528
- if (threadId) await attachExistingThread({
529
- ...bindingParams,
530
- threadId
527
+ const bind = () => params.bindingStore.withLease(identity, () => bindThread(bindingParams, threadId));
528
+ const ownedThreadId = threadId ?? existingBinding?.threadId;
529
+ if (ownedThreadId) await withExclusiveCodexAppServerThread({
530
+ bindingStore: params.bindingStore,
531
+ identity,
532
+ threadId: ownedThreadId,
533
+ run: bind
531
534
  });
532
- else await bindThread(bindingParams);
535
+ else await bind();
533
536
  const storedBinding = await params.bindingStore.read(identity);
534
537
  if (!storedBinding) throw new Error("Codex session binding disappeared while starting its conversation thread.");
535
538
  return createCodexConversationBindingData({
@@ -770,14 +773,6 @@ async function writeThreadBindingFromResponse(params, resolved, client, response
770
773
  throw error;
771
774
  }
772
775
  }
773
- async function attachExistingThread(params) {
774
- await withExclusiveCodexAppServerThread({
775
- bindingStore: params.bindingStore,
776
- identity: params.identity,
777
- threadId: params.threadId,
778
- run: () => bindThread(params, params.threadId)
779
- });
780
- }
781
776
  async function bindThread(params, threadId) {
782
777
  const current = await params.bindingStore.read(params.identity);
783
778
  assertCodexBindingMayBeReplaced(current, "binding a conversation-bound Codex thread");
@@ -830,277 +825,286 @@ async function runBoundTurn(params) {
830
825
  const identity = conversationBindingIdentity(params.data);
831
826
  const binding = await params.bindingStore.read(identity);
832
827
  if (!binding?.threadId) throw new Error("bound Codex conversation has no thread binding");
833
- assertCodexBindingMayBeReplaced(binding, "running a conversation-bound Codex thread");
834
- let threadId = binding.threadId;
835
- const requestedWorkspaceDir = binding.cwd || params.data.workspaceDir;
836
- const reviewerModelProvider = resolveModelBackedReviewerPolicyProvider({
837
- authProfileId: binding.authProfileId,
838
- modelProvider: binding.modelProvider,
839
- ...agentLookup
840
- });
841
- const { runtime, workspaceDir } = await resolveConversationAppServerRuntime({
842
- pluginConfig: params.pluginConfig,
843
- config: params.config,
844
- agentId: params.data.source?.agentId ?? params.data.agentId,
845
- sessionKey: params.data.legacyBinding ? params.sessionKey : params.data.source?.sessionKey,
846
- source: params.data.source,
847
- workspaceDir: requestedWorkspaceDir,
848
- modelProvider: reviewerModelProvider,
849
- model: binding.model,
850
- agentDir: params.data.agentDir
851
- });
852
- const modelScopedRuntime = resolveCodexAppServerForModelProvider({
853
- appServer: runtime,
854
- provider: reviewerModelProvider,
855
- model: binding.model,
856
- config: params.config,
857
- env: process.env,
858
- agentDir: params.data.agentDir
859
- });
860
- const sessionRoot = modelScopedRuntime.sessionRoot;
861
- const approvalPolicy = modelScopedRuntime.approvalPolicy;
862
- const sandbox = modelScopedRuntime.sandbox;
863
- const permissionProfile = modelScopedRuntime.networkProxy?.profileName;
864
- const networkProxyConfigFingerprint = modelScopedRuntime.networkProxy?.configFingerprint;
865
- const networkProxyBindingChanged = binding.networkProxyProfileName !== permissionProfile || binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint;
866
- const serviceTier = binding.serviceTier ?? runtime.serviceTier;
867
- let useStickyNetworkProfile = permissionProfile !== void 0 && binding.networkProxyProfileName === permissionProfile && binding.networkProxyConfigFingerprint === networkProxyConfigFingerprint;
868
- assertNativeConversationApprovalPolicySupported(modelScopedRuntime);
869
- const modelSelection = binding.model ? resolveCodexAppServerRequestModelSelection({
870
- model: binding.model,
871
- modelProvider: binding.modelProvider,
872
- authProfileId: binding.authProfileId,
873
- ...agentLookup
874
- }) : void 0;
875
- const threadRequestRuntime = {
876
- runtime: modelScopedRuntime,
877
- workspaceDir,
878
- ...modelSelection
879
- };
880
- const clientOptions = {
881
- startOptions: runtime.start,
882
- timeoutMs: runtime.requestTimeoutMs,
883
- authProfileId: binding.authProfileId,
884
- ...agentLookup
885
- };
886
- let client = await getLeasedSharedCodexAppServerClient(clientOptions);
887
- const clientLease = { client };
888
- let activeTurnId;
889
- let activeTurnCleanup = () => void 0;
890
- let isolatedSubscriptionClient;
891
- let turnRoute;
892
- let liveThreadOwnership;
893
- let ownsNativeSubscription = false;
894
- let turnSucceeded = false;
895
- const assertResumeInputAllowed = async () => {
896
- const { thread } = await client.request("thread/read", {
897
- threadId,
898
- includeTurns: false
899
- }, { timeoutMs: runtime.requestTimeoutMs });
900
- assertCodexThreadAcceptsDirectInput(thread);
901
- };
902
- try {
903
- if (!networkProxyBindingChanged && binding.clientId !== client.getInstanceId()) await assertResumeInputAllowed();
904
- if (!params.incognito && isCodexAppServerClientRuntimeLive(client)) {
905
- const ownership = await consumeCodexAppServerLiveThread(client, threadId);
906
- if (ownership) {
907
- liveThreadOwnership = {
908
- client,
828
+ return await withExclusiveCodexAppServerThread({
829
+ bindingStore: params.bindingStore,
830
+ identity,
831
+ threadId: binding.threadId,
832
+ run: async () => {
833
+ const current = await params.bindingStore.read(identity);
834
+ if (!isSameCodexAppServerThreadOwner(current, binding)) throw new Error("Codex conversation binding changed before its turn.");
835
+ assertCodexBindingMayBeReplaced(binding, "running a conversation-bound Codex thread");
836
+ let threadId = binding.threadId;
837
+ const requestedWorkspaceDir = binding.cwd || params.data.workspaceDir;
838
+ const reviewerModelProvider = resolveModelBackedReviewerPolicyProvider({
839
+ authProfileId: binding.authProfileId,
840
+ modelProvider: binding.modelProvider,
841
+ ...agentLookup
842
+ });
843
+ const { runtime, workspaceDir } = await resolveConversationAppServerRuntime({
844
+ pluginConfig: params.pluginConfig,
845
+ config: params.config,
846
+ agentId: params.data.source?.agentId ?? params.data.agentId,
847
+ sessionKey: params.data.legacyBinding ? params.sessionKey : params.data.source?.sessionKey,
848
+ source: params.data.source,
849
+ workspaceDir: requestedWorkspaceDir,
850
+ modelProvider: reviewerModelProvider,
851
+ model: binding.model,
852
+ agentDir: params.data.agentDir
853
+ });
854
+ const modelScopedRuntime = resolveCodexAppServerForModelProvider({
855
+ appServer: runtime,
856
+ provider: reviewerModelProvider,
857
+ model: binding.model,
858
+ config: params.config,
859
+ env: process.env,
860
+ agentDir: params.data.agentDir
861
+ });
862
+ const sessionRoot = modelScopedRuntime.sessionRoot;
863
+ const approvalPolicy = modelScopedRuntime.approvalPolicy;
864
+ const sandbox = modelScopedRuntime.sandbox;
865
+ const permissionProfile = modelScopedRuntime.networkProxy?.profileName;
866
+ const networkProxyConfigFingerprint = modelScopedRuntime.networkProxy?.configFingerprint;
867
+ const networkProxyBindingChanged = binding.networkProxyProfileName !== permissionProfile || binding.networkProxyConfigFingerprint !== networkProxyConfigFingerprint;
868
+ const serviceTier = binding.serviceTier ?? runtime.serviceTier;
869
+ let useStickyNetworkProfile = permissionProfile !== void 0 && binding.networkProxyProfileName === permissionProfile && binding.networkProxyConfigFingerprint === networkProxyConfigFingerprint;
870
+ assertNativeConversationApprovalPolicySupported(modelScopedRuntime);
871
+ const modelSelection = binding.model ? resolveCodexAppServerRequestModelSelection({
872
+ model: binding.model,
873
+ modelProvider: binding.modelProvider,
874
+ authProfileId: binding.authProfileId,
875
+ ...agentLookup
876
+ }) : void 0;
877
+ const threadRequestRuntime = {
878
+ runtime: modelScopedRuntime,
879
+ workspaceDir,
880
+ ...modelSelection
881
+ };
882
+ const clientOptions = {
883
+ startOptions: runtime.start,
884
+ timeoutMs: runtime.requestTimeoutMs,
885
+ authProfileId: binding.authProfileId,
886
+ ...agentLookup
887
+ };
888
+ let client = await getLeasedSharedCodexAppServerClient(clientOptions);
889
+ const clientLease = { client };
890
+ let activeTurnId;
891
+ let activeTurnCleanup = () => void 0;
892
+ let isolatedSubscriptionClient;
893
+ let turnRoute;
894
+ let liveThreadOwnership;
895
+ let ownsNativeSubscription = false;
896
+ let turnSucceeded = false;
897
+ const assertResumeInputAllowed = async () => {
898
+ const { thread } = await client.request("thread/read", {
909
899
  threadId,
910
- ownership
911
- };
912
- ownsNativeSubscription = true;
913
- }
914
- }
915
- if (networkProxyBindingChanged) {
916
- const response = assertCodexThreadStartResponse(await withLeasedCodexAppServerClientStartSelectionRetry({
917
- lease: clientLease,
918
- options: clientOptions,
919
- run: async (requestClient, requestOptions) => await requestClient.request("thread/start", {
920
- ...buildConversationThreadRequest(threadRequestRuntime, serviceTier),
921
- developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
922
- experimentalRawEvents: true,
923
- ...params.incognito ? { ephemeral: true } : {}
924
- }, requestOptions()),
925
- onClientChange: (nextClient) => {
926
- client = nextClient;
900
+ includeTurns: false
901
+ }, { timeoutMs: runtime.requestTimeoutMs });
902
+ assertCodexThreadAcceptsDirectInput(thread);
903
+ };
904
+ try {
905
+ if (!networkProxyBindingChanged && binding.clientId !== client.getInstanceId()) await assertResumeInputAllowed();
906
+ if (!params.incognito && isCodexAppServerClientRuntimeLive(client)) {
907
+ const ownership = await consumeCodexAppServerLiveThread(client, threadId);
908
+ if (ownership) {
909
+ liveThreadOwnership = {
910
+ client,
911
+ threadId,
912
+ ownership
913
+ };
914
+ ownsNativeSubscription = true;
915
+ }
927
916
  }
928
- }));
929
- threadId = response.thread.id;
930
- ownsNativeSubscription = true;
931
- assertCodexThreadAcceptsDirectInput(response.thread);
932
- if (liveThreadOwnership && (liveThreadOwnership.threadId !== threadId || liveThreadOwnership.client !== client)) {
933
- const previousOwnership = liveThreadOwnership;
934
- try {
935
- await previousOwnership.ownership.release(previousOwnership.threadId);
936
- } catch (error) {
937
- if (!(isCodexAppServerClientRuntimeLive(previousOwnership.client) && await retainCodexAppServerBindingSubscription(previousOwnership.client, previousOwnership.threadId, previousOwnership.ownership).catch(() => false))) await closeCodexStartupClientBestEffort(previousOwnership.client);
938
- liveThreadOwnership = void 0;
939
- throw error;
917
+ if (networkProxyBindingChanged) {
918
+ const response = assertCodexThreadStartResponse(await withLeasedCodexAppServerClientStartSelectionRetry({
919
+ lease: clientLease,
920
+ options: clientOptions,
921
+ run: async (requestClient, requestOptions) => await requestClient.request("thread/start", {
922
+ ...buildConversationThreadRequest(threadRequestRuntime, serviceTier),
923
+ developerInstructions: CODEX_CONVERSATION_THREAD_DEVELOPER_INSTRUCTIONS,
924
+ experimentalRawEvents: true,
925
+ ...params.incognito ? { ephemeral: true } : {}
926
+ }, requestOptions()),
927
+ onClientChange: (nextClient) => {
928
+ client = nextClient;
929
+ }
930
+ }));
931
+ threadId = response.thread.id;
932
+ ownsNativeSubscription = true;
933
+ assertCodexThreadAcceptsDirectInput(response.thread);
934
+ if (liveThreadOwnership && (liveThreadOwnership.threadId !== threadId || liveThreadOwnership.client !== client)) {
935
+ const previousOwnership = liveThreadOwnership;
936
+ try {
937
+ await previousOwnership.ownership.release(previousOwnership.threadId);
938
+ } catch (error) {
939
+ if (!(isCodexAppServerClientRuntimeLive(previousOwnership.client) && await retainCodexAppServerBindingSubscription(previousOwnership.client, previousOwnership.threadId, previousOwnership.ownership).catch(() => false))) await closeCodexStartupClientBestEffort(previousOwnership.client);
940
+ liveThreadOwnership = void 0;
941
+ throw error;
942
+ }
943
+ liveThreadOwnership = void 0;
944
+ } else if (binding.threadId !== threadId) await releaseCodexAppServerBindingSubscription(binding);
945
+ if (!await params.bindingStore.mutate(identity, {
946
+ kind: "set",
947
+ binding: {
948
+ threadId,
949
+ clientId: client.getInstanceId(),
950
+ cwd: response.thread.cwd ?? workspaceDir,
951
+ authProfileId: binding.authProfileId,
952
+ model: response.model ?? modelSelection?.model ?? binding.model,
953
+ modelProvider: normalizeCodexAppServerBindingModelProvider({
954
+ authProfileId: binding.authProfileId,
955
+ modelProvider: response.modelProvider ?? modelSelection?.modelProvider ?? binding.modelProvider,
956
+ ...agentLookup
957
+ }),
958
+ serviceTier: serviceTier ?? void 0,
959
+ networkProxyProfileName: modelScopedRuntime.networkProxy?.profileName,
960
+ networkProxyConfigFingerprint: modelScopedRuntime.networkProxy?.configFingerprint,
961
+ conversationStartId: binding.conversationStartId,
962
+ conversationSourceTransferComplete: binding.conversationSourceTransferComplete,
963
+ historyCoveredThrough: binding.historyCoveredThrough
964
+ }
965
+ })) throw new Error("Codex conversation binding changed while rotating its thread.");
966
+ useStickyNetworkProfile = modelScopedRuntime.networkProxy !== void 0;
967
+ } else if (binding.clientId !== client.getInstanceId() || isCodexAppServerClientRuntimeLive(client) && !params.incognito && !liveThreadOwnership) {
968
+ if (binding.clientId === client.getInstanceId()) await assertResumeInputAllowed();
969
+ const response = await withLeasedCodexAppServerClientStartSelectionRetry({
970
+ lease: clientLease,
971
+ options: clientOptions,
972
+ run: async (requestClient, requestOptions) => await resumeCodexAppServerThread({
973
+ client: requestClient,
974
+ onSubscriptionReleased: () => {
975
+ isolatedSubscriptionClient = requestClient;
976
+ },
977
+ abandonClient: async () => {
978
+ await closeCodexStartupClientBestEffort(requestClient);
979
+ isolatedSubscriptionClient = requestClient;
980
+ },
981
+ request: {
982
+ threadId,
983
+ ...buildConversationThreadRequest(threadRequestRuntime, serviceTier)
984
+ },
985
+ requestResume: (request) => requestClient.request("thread/resume", request, requestOptions())
986
+ }),
987
+ onClientChange: (nextClient) => {
988
+ client = nextClient;
989
+ }
990
+ });
991
+ threadId = response.thread.id;
992
+ ownsNativeSubscription = true;
993
+ assertCodexThreadAcceptsDirectInput(response.thread);
994
+ if (!isSameCodexAppServerThreadOwner(binding, {
995
+ threadId,
996
+ clientId: client.getInstanceId()
997
+ })) await releaseCodexAppServerBindingSubscription(binding);
998
+ if (!await params.bindingStore.mutate(identity, {
999
+ kind: "patch",
1000
+ threadId: binding.threadId,
1001
+ patch: {
1002
+ clientId: client.getInstanceId(),
1003
+ cwd: response.thread.cwd ?? binding.cwd,
1004
+ model: response.model ?? modelSelection?.model ?? binding.model,
1005
+ modelProvider: normalizeCodexAppServerBindingModelProvider({
1006
+ authProfileId: binding.authProfileId,
1007
+ modelProvider: response.modelProvider ?? modelSelection?.modelProvider ?? binding.modelProvider,
1008
+ ...agentLookup
1009
+ })
1010
+ }
1011
+ })) throw new Error("Codex conversation binding changed while resuming on a new client.");
940
1012
  }
941
- liveThreadOwnership = void 0;
942
- } else if (binding.threadId !== threadId) await releaseCodexAppServerBindingSubscription(binding);
943
- if (!await params.bindingStore.mutate(identity, {
944
- kind: "set",
945
- binding: {
1013
+ const turnCollector = createCodexConversationTurnCollector(threadId);
1014
+ turnRoute = getCodexAppServerTurnRouter(client).reserveThread({
946
1015
  threadId,
947
- clientId: client.getInstanceId(),
948
- cwd: response.thread.cwd ?? workspaceDir,
949
- authProfileId: binding.authProfileId,
950
- model: response.model ?? modelSelection?.model ?? binding.model,
951
- modelProvider: normalizeCodexAppServerBindingModelProvider({
952
- authProfileId: binding.authProfileId,
953
- modelProvider: response.modelProvider ?? modelSelection?.modelProvider ?? binding.modelProvider,
954
- ...agentLookup
1016
+ onNotification: turnCollector.handleNotification
1017
+ });
1018
+ turnRoute.armTurn();
1019
+ activeTurnId = (await client.request("turn/start", {
1020
+ threadId,
1021
+ input: buildCodexConversationTurnInput({
1022
+ prompt: params.prompt,
1023
+ event: params.event
955
1024
  }),
956
- serviceTier: serviceTier ?? void 0,
957
- networkProxyProfileName: modelScopedRuntime.networkProxy?.profileName,
958
- networkProxyConfigFingerprint: modelScopedRuntime.networkProxy?.configFingerprint,
959
- conversationStartId: binding.conversationStartId,
960
- conversationSourceTransferComplete: binding.conversationSourceTransferComplete,
961
- historyCoveredThrough: binding.historyCoveredThrough
1025
+ cwd: workspaceDir,
1026
+ ...sessionRoot ? { runtimeWorkspaceRoots: [sessionRoot] } : {},
1027
+ approvalPolicy,
1028
+ approvalsReviewer: modelScopedRuntime.approvalsReviewer,
1029
+ ...useStickyNetworkProfile ? {} : { sandboxPolicy: codexSandboxPolicyForTurn(sandbox, sessionRoot ?? workspaceDir) },
1030
+ ...modelSelection?.model ? { model: modelSelection.model } : {},
1031
+ personality: CODEX_NATIVE_PERSONALITY_NONE,
1032
+ ...serviceTier ? { serviceTier } : {}
1033
+ }, { timeoutMs: runtime.requestTimeoutMs })).turn.id;
1034
+ activeTurnCleanup = trackCodexConversationActiveTurn({
1035
+ identity,
1036
+ client,
1037
+ threadId,
1038
+ turnId: activeTurnId
1039
+ });
1040
+ turnCollector.setTurnId(activeTurnId);
1041
+ await turnRoute.bindTurn(activeTurnId);
1042
+ const replyText = (await turnCollector.wait({ timeoutMs: params.timeoutMs ?? DEFAULT_BOUND_TURN_TIMEOUT_MS })).replyText.trim();
1043
+ turnSucceeded = true;
1044
+ return { reply: { text: replyText || "Codex completed without a text reply." } };
1045
+ } catch (error) {
1046
+ if (isCodexAppServerOverloadError(error) && error.method === "thread/resume") throw error;
1047
+ if (error instanceof CodexThreadDirectInputError) {
1048
+ if (params.incognito && ownsNativeSubscription) {
1049
+ if (!await unsubscribeCodexThreadBestEffort(client, {
1050
+ threadId,
1051
+ timeoutMs: 5e3
1052
+ })) await retireUnsafeCodexTurnClientBestEffort(client, "parent-owned thread unsubscribe");
1053
+ }
1054
+ throw error;
962
1055
  }
963
- })) throw new Error("Codex conversation binding changed while rotating its thread.");
964
- useStickyNetworkProfile = modelScopedRuntime.networkProxy !== void 0;
965
- } else if (binding.clientId !== client.getInstanceId() || isCodexAppServerClientRuntimeLive(client) && !params.incognito && !liveThreadOwnership) {
966
- if (binding.clientId === client.getInstanceId()) await assertResumeInputAllowed();
967
- const response = await withLeasedCodexAppServerClientStartSelectionRetry({
968
- lease: clientLease,
969
- options: clientOptions,
970
- run: async (requestClient, requestOptions) => await resumeCodexAppServerThread({
971
- client: requestClient,
972
- onSubscriptionReleased: () => {
973
- isolatedSubscriptionClient = requestClient;
974
- },
975
- abandonClient: async () => {
976
- await closeCodexStartupClientBestEffort(requestClient);
977
- isolatedSubscriptionClient = requestClient;
978
- },
979
- request: {
1056
+ if (error instanceof CodexConversationTurnTimeoutError && activeTurnId || turnRoute && isCodexAppServerIndeterminateRequestCancellationError(error)) {
1057
+ if (!await interruptCodexTurnAndWaitBestEffort(client, {
980
1058
  threadId,
981
- ...buildConversationThreadRequest(threadRequestRuntime, serviceTier)
982
- },
983
- requestResume: (request) => requestClient.request("thread/resume", request, requestOptions())
984
- }),
985
- onClientChange: (nextClient) => {
986
- client = nextClient;
1059
+ turnId: activeTurnId ?? ""
1060
+ })) {
1061
+ await retireUnsafeCodexTurnClientBestEffort(client, "turn interrupt");
1062
+ isolatedSubscriptionClient = client;
1063
+ }
987
1064
  }
988
- });
989
- threadId = response.thread.id;
990
- ownsNativeSubscription = true;
991
- assertCodexThreadAcceptsDirectInput(response.thread);
992
- if (!isSameCodexAppServerThreadOwner(binding, {
993
- threadId,
994
- clientId: client.getInstanceId()
995
- })) await releaseCodexAppServerBindingSubscription(binding);
996
- if (!await params.bindingStore.mutate(identity, {
997
- kind: "patch",
998
- threadId: binding.threadId,
999
- patch: {
1000
- clientId: client.getInstanceId(),
1001
- cwd: response.thread.cwd ?? binding.cwd,
1002
- model: response.model ?? modelSelection?.model ?? binding.model,
1003
- modelProvider: normalizeCodexAppServerBindingModelProvider({
1004
- authProfileId: binding.authProfileId,
1005
- modelProvider: response.modelProvider ?? modelSelection?.modelProvider ?? binding.modelProvider,
1006
- ...agentLookup
1007
- })
1065
+ if (params.incognito) {
1066
+ if (await params.bindingStore.mutate(identity, {
1067
+ kind: "clear",
1068
+ threadId
1069
+ }) && isolatedSubscriptionClient !== client) {
1070
+ if (!await unsubscribeCodexThreadBestEffort(client, {
1071
+ threadId,
1072
+ timeoutMs: 5e3
1073
+ })) await retireUnsafeCodexTurnClientBestEffort(client, "thread unsubscribe");
1074
+ }
1008
1075
  }
1009
- })) throw new Error("Codex conversation binding changed while resuming on a new client.");
1010
- }
1011
- const turnCollector = createCodexConversationTurnCollector(threadId);
1012
- turnRoute = getCodexAppServerTurnRouter(client).reserveThread({
1013
- threadId,
1014
- onNotification: turnCollector.handleNotification
1015
- });
1016
- turnRoute.armTurn();
1017
- activeTurnId = (await client.request("turn/start", {
1018
- threadId,
1019
- input: buildCodexConversationTurnInput({
1020
- prompt: params.prompt,
1021
- event: params.event
1022
- }),
1023
- cwd: workspaceDir,
1024
- ...sessionRoot ? { runtimeWorkspaceRoots: [sessionRoot] } : {},
1025
- approvalPolicy,
1026
- approvalsReviewer: modelScopedRuntime.approvalsReviewer,
1027
- ...useStickyNetworkProfile ? {} : { sandboxPolicy: codexSandboxPolicyForTurn(sandbox, sessionRoot ?? workspaceDir) },
1028
- ...modelSelection?.model ? { model: modelSelection.model } : {},
1029
- personality: CODEX_NATIVE_PERSONALITY_NONE,
1030
- ...serviceTier ? { serviceTier } : {}
1031
- }, { timeoutMs: runtime.requestTimeoutMs })).turn.id;
1032
- activeTurnCleanup = trackCodexConversationActiveTurn({
1033
- identity,
1034
- client,
1035
- threadId,
1036
- turnId: activeTurnId
1037
- });
1038
- turnCollector.setTurnId(activeTurnId);
1039
- await turnRoute.bindTurn(activeTurnId);
1040
- const replyText = (await turnCollector.wait({ timeoutMs: params.timeoutMs ?? DEFAULT_BOUND_TURN_TIMEOUT_MS })).replyText.trim();
1041
- turnSucceeded = true;
1042
- return { reply: { text: replyText || "Codex completed without a text reply." } };
1043
- } catch (error) {
1044
- if (isCodexAppServerOverloadError(error) && error.method === "thread/resume") throw error;
1045
- if (error instanceof CodexThreadDirectInputError) {
1046
- if (params.incognito && ownsNativeSubscription) {
1047
- if (!await unsubscribeCodexThreadBestEffort(client, {
1048
- threadId,
1049
- timeoutMs: 5e3
1050
- })) await retireUnsafeCodexTurnClientBestEffort(client, "parent-owned thread unsubscribe");
1051
- }
1052
- throw error;
1053
- }
1054
- if (error instanceof CodexConversationTurnTimeoutError && activeTurnId || turnRoute && isCodexAppServerIndeterminateRequestCancellationError(error)) {
1055
- if (!await interruptCodexTurnAndWaitBestEffort(client, {
1056
- threadId,
1057
- turnId: activeTurnId ?? ""
1058
- })) {
1059
- await retireUnsafeCodexTurnClientBestEffort(client, "turn interrupt");
1060
- isolatedSubscriptionClient = client;
1061
- }
1062
- }
1063
- if (params.incognito) {
1064
- if (await params.bindingStore.mutate(identity, {
1065
- kind: "clear",
1066
- threadId
1067
- }) && isolatedSubscriptionClient !== client) {
1068
- if (!await unsubscribeCodexThreadBestEffort(client, {
1069
- threadId,
1070
- timeoutMs: 5e3
1071
- })) await retireUnsafeCodexTurnClientBestEffort(client, "thread unsubscribe");
1072
- }
1073
- }
1074
- throw error;
1075
- } finally {
1076
- activeTurnCleanup();
1077
- turnRoute?.release();
1078
- try {
1079
- if (ownsNativeSubscription && isolatedSubscriptionClient !== client && !params.incognito && isCodexAppServerClientRuntimeLive(client)) {
1080
- const currentLiveThreadOwnership = liveThreadOwnership?.client === client && liveThreadOwnership.threadId === threadId ? liveThreadOwnership.ownership : void 0;
1081
- let retained = false;
1082
- if (turnSucceeded) retained = await params.bindingStore.withLease(identity, async () => {
1083
- const current = await params.bindingStore.read(identity);
1084
- if (current?.threadId !== threadId || current.clientId !== client.getInstanceId()) return false;
1085
- return await retainCodexAppServerBindingSubscription(client, threadId, currentLiveThreadOwnership);
1086
- });
1087
- if (!retained) {
1088
- if (!(currentLiveThreadOwnership ? await currentLiveThreadOwnership.release(threadId).then(() => true) : await unsubscribeCodexThreadBestEffort(client, {
1076
+ throw error;
1077
+ } finally {
1078
+ activeTurnCleanup();
1079
+ turnRoute?.release();
1080
+ try {
1081
+ if (ownsNativeSubscription && isolatedSubscriptionClient !== client && !params.incognito && isCodexAppServerClientRuntimeLive(client)) {
1082
+ const currentLiveThreadOwnership = liveThreadOwnership?.client === client && liveThreadOwnership.threadId === threadId ? liveThreadOwnership.ownership : void 0;
1083
+ let retained = false;
1084
+ if (turnSucceeded) retained = await params.bindingStore.withLease(identity, async () => {
1085
+ const latest = await params.bindingStore.read(identity);
1086
+ if (latest?.threadId !== threadId || latest.clientId !== client.getInstanceId()) return false;
1087
+ return await retainCodexAppServerBindingSubscription(client, threadId, currentLiveThreadOwnership);
1088
+ });
1089
+ if (!retained) {
1090
+ if (!(currentLiveThreadOwnership ? await currentLiveThreadOwnership.release(threadId).then(() => true) : await unsubscribeCodexThreadBestEffort(client, {
1091
+ threadId,
1092
+ timeoutMs: 5e3
1093
+ }))) await closeCodexStartupClientBestEffort(client);
1094
+ }
1095
+ }
1096
+ } catch (error) {
1097
+ embeddedAgentLog.warn("codex conversation subscription cleanup failed", {
1089
1098
  threadId,
1090
- timeoutMs: 5e3
1091
- }))) await closeCodexStartupClientBestEffort(client);
1099
+ reason: formatErrorMessage(error)
1100
+ });
1101
+ await closeCodexStartupClientBestEffort(client);
1102
+ } finally {
1103
+ releaseCodexAppServerClientLease(clientLease);
1092
1104
  }
1093
1105
  }
1094
- } catch (error) {
1095
- embeddedAgentLog.warn("codex conversation subscription cleanup failed", {
1096
- threadId,
1097
- reason: formatErrorMessage(error)
1098
- });
1099
- await closeCodexStartupClientBestEffort(client);
1100
- } finally {
1101
- releaseCodexAppServerClientLease(clientLease);
1102
1106
  }
1103
- }
1107
+ });
1104
1108
  }
1105
1109
  function assertNativeConversationApprovalPolicySupported(runtime) {
1106
1110
  if (runtime.approvalPolicy !== "never" && runtime.approvalsReviewer === "user") throw new Error(NATIVE_CONVERSATION_INTERACTIVE_APPROVALS_UNAVAILABLE);
@@ -1117,8 +1121,10 @@ async function runBoundTurnWithMissingThreadRecovery(params) {
1117
1121
  }
1118
1122
  async function prepareConversationBinding(params, options = {}) {
1119
1123
  const identity = conversationBindingIdentity(params.data);
1120
- await params.bindingStore.withLease(identity, async () => {
1124
+ const snapshot = await params.bindingStore.read(identity);
1125
+ const run = () => params.bindingStore.withLease(identity, async () => {
1121
1126
  const current = await params.bindingStore.read(identity);
1127
+ if (current?.threadId !== snapshot?.threadId || current?.clientId !== snapshot?.clientId) throw new Error("Codex conversation binding changed before preparation.");
1122
1128
  const requested = params.data.start && current?.conversationStartId !== params.data.start.id ? params.data.start : void 0;
1123
1129
  if (current && !requested && !options.forceNew) return;
1124
1130
  const sourceIdentity = params.data.source ? sessionBindingIdentity({
@@ -1152,11 +1158,7 @@ async function prepareConversationBinding(params, options = {}) {
1152
1158
  agentId: params.data.source?.agentId ?? params.data.agentId
1153
1159
  };
1154
1160
  const threadId = requested?.threadId;
1155
- if (threadId && !options.forceNew) await attachExistingThread({
1156
- ...bindingParams,
1157
- threadId
1158
- });
1159
- else await bindThread(bindingParams);
1161
+ await bindThread(bindingParams, options.forceNew ? void 0 : threadId);
1160
1162
  const stored = await params.bindingStore.read(identity);
1161
1163
  if (!stored) throw new Error("Codex conversation binding disappeared while initializing its thread.");
1162
1164
  if (sourceIdentity && params.data.source && !current?.conversationSourceTransferComplete) await params.bindingStore.withLease(sourceIdentity, async () => {
@@ -1187,6 +1189,14 @@ async function prepareConversationBinding(params, options = {}) {
1187
1189
  }
1188
1190
  })) throw new Error("Codex conversation binding changed while initializing its thread.");
1189
1191
  });
1192
+ const threadId = params.data.start?.threadId ?? snapshot?.threadId;
1193
+ if (threadId) await withExclusiveCodexAppServerThread({
1194
+ bindingStore: params.bindingStore,
1195
+ identity,
1196
+ threadId,
1197
+ run
1198
+ });
1199
+ else await run();
1190
1200
  }
1191
1201
  async function projectConversationSourceHistory(source, target, config) {
1192
1202
  const storePath = resolveStorePath(config?.session?.store, { agentId: source.agentId });
@@ -3267,7 +3277,7 @@ function createCodexNodeExecServerCommand() {
3267
3277
  try {
3268
3278
  if (!context.prepareExecAuthorization) throw new Error("Codex node execution requires node-local exec policy support; update the node.");
3269
3279
  const assertExecAuthorized = context.prepareExecAuthorization(request.authorization);
3270
- const { runCodexNodeExecServer } = await import("./node-exec-server.runtime-DirKVZ5h.js");
3280
+ const { runCodexNodeExecServer } = await import("./node-exec-server.runtime-CstNHCmC.js");
3271
3281
  return await runCodexNodeExecServer({
3272
3282
  workspaceDir: workspace.workspaceDir,
3273
3283
  io,
@@ -3292,6 +3302,10 @@ function createCodexNodeExecServerInvokePolicy() {
3292
3302
  return {
3293
3303
  commands: [CODEX_NODE_EXEC_SERVER_COMMAND],
3294
3304
  dangerous: true,
3305
+ standingApproval: {
3306
+ kind: "placement",
3307
+ scope: CODEX_NODE_EXEC_SERVER_CAPABILITY
3308
+ },
3295
3309
  classifyRisk: () => ({
3296
3310
  level: "high",
3297
3311
  family: CODEX_NODE_EXEC_SERVER_CAPABILITY
@@ -3334,21 +3348,16 @@ function createCodexNodeExecServerInvokePolicy() {
3334
3348
  };
3335
3349
  const nodeName = context.node?.displayName ?? context.nodeId;
3336
3350
  const approval = await context.approvals.request({
3337
- title: "Run Codex execution on node",
3338
- description: `Allows arbitrary processes and filesystem access across the node account, not only this workspace. ${nodeName}: ${placement.cwd}`,
3351
+ title: "Run Codex on this node placement",
3352
+ description: `Allows arbitrary processes and filesystem access across the node account, not only this workspace. Allow always applies only while this exact placement remains active. ${nodeName}: ${placement.cwd}`,
3339
3353
  severity: "critical",
3340
- allowedDecisions: ["allow-once"]
3354
+ allowedDecisions: ["allow-once", "allow-always"]
3341
3355
  });
3342
- if (approval.decision !== "allow-once") {
3356
+ if (approval.decision !== "allow-once" && approval.decision !== "allow-always") {
3343
3357
  if (approval.decision === "deny") return {
3344
3358
  ok: false,
3345
3359
  code: "CODEX_NODE_EXEC_APPROVAL_DENIED",
3346
- message: "Codex node execution was denied. Retry the action and choose Allow once to continue."
3347
- };
3348
- if (approval.decision === "allow-always") return {
3349
- ok: false,
3350
- code: "CODEX_NODE_EXEC_APPROVAL_INVALID",
3351
- message: "Codex node execution cannot use permanent approval. Retry the action and choose Allow once."
3360
+ message: "Codex node execution was denied. Retry the action and choose Allow once or Allow always to continue."
3352
3361
  };
3353
3362
  return {
3354
3363
  ok: false,
@@ -4148,7 +4157,7 @@ function createCodexSupervisionTools(options) {
4148
4157
  }
4149
4158
  //#endregion
4150
4159
  //#region extensions/codex/src/web-search-provider.ts
4151
- const loadCodexWebSearchRuntime = createLazyRuntimeModule(() => import("./web-search-provider.runtime-BEmlE7ov.js"));
4160
+ const loadCodexWebSearchRuntime = createLazyRuntimeModule(() => import("./web-search-provider.runtime-D2FWSK4w.js"));
4152
4161
  const CodexWebSearchSchema = {
4153
4162
  type: "object",
4154
4163
  properties: { query: {
@@ -4190,6 +4199,7 @@ var codex_default = definePluginEntry({
4190
4199
  id: "codex",
4191
4200
  name: "Codex",
4192
4201
  description: "Codex app-server harness and native session supervision.",
4202
+ reload: { noopPrefixes: ["plugins.entries.codex.config.codexPlugins"] },
4193
4203
  register(api) {
4194
4204
  setManagedCodexPluginRoot(api.rootDir);
4195
4205
  const resolveCurrentConfig = () => api.runtime.config?.current ? api.runtime.config.current() : void 0;
@@ -4383,7 +4393,7 @@ var codex_default = definePluginEntry({
4383
4393
  if (!previousSessionId || !sessionId || previousSessionId === sessionId) return;
4384
4394
  const config = resolveCurrentConfig();
4385
4395
  const sessionKey = ctx.sessionKey?.trim();
4386
- const { sessionBindingIdentity } = await import("./session-binding-D0zinCP1.js").then((n) => n.f);
4396
+ const { sessionBindingIdentity } = await import("./session-binding-BHuTCRZQ.js").then((n) => n.f);
4387
4397
  const identity = sessionBindingIdentity({
4388
4398
  sessionId,
4389
4399
  ...sessionKey ? { sessionKey } : {},
@@ -4401,7 +4411,7 @@ var codex_default = definePluginEntry({
4401
4411
  if (endedSessionKey && nextSessionKey && nextSessionKey !== endedSessionKey) return;
4402
4412
  if (event.nextSessionId?.trim() === event.sessionId.trim()) return;
4403
4413
  const config = resolveCurrentConfig();
4404
- const [{ sessionBindingIdentity }, { retireCodexAppServerSessionGeneration }] = await Promise.all([import("./session-binding-D0zinCP1.js").then((n) => n.f), import("./session-retirement-CtUtUTr9.js")]);
4414
+ const [{ sessionBindingIdentity }, { retireCodexAppServerSessionGeneration }] = await Promise.all([import("./session-binding-BHuTCRZQ.js").then((n) => n.f), import("./session-retirement-BujDjXVF.js")]);
4405
4415
  await retireCodexAppServerSessionGeneration({
4406
4416
  bindingStore,
4407
4417
  identity: sessionBindingIdentity({