@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
@@ -1,10 +1,11 @@
1
- import { n as resolveCodexAppServerSpawnEnv } from "./transport-stdio-NT_Dpd9V.js";
1
+ import { i as resolvePackagedCodexNativeCommand } from "./managed-binary-4-Vjz5GH.js";
2
+ import { n as resolveCodexAppServerSpawnEnv } from "./transport-stdio-CyPldczm.js";
2
3
  import fs from "node:fs/promises";
3
4
  import path from "node:path";
5
+ import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
4
6
  import { createHash } from "node:crypto";
5
7
  import { constants } from "node:fs";
6
8
  import { resolveWindowsExecutablePath, resolveWindowsSpawnProgram } from "openclaw/plugin-sdk/windows-spawn";
7
- import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
8
9
  //#region extensions/codex/src/app-server/runtime-artifact.ts
9
10
  /** Exact local runtime artifact identity for verified Codex setup turns. */
10
11
  const ARTIFACT_ID_PREFIX = "codex-app-server:v1:";
@@ -363,6 +364,8 @@ async function captureFilesystemDescriptor(params) {
363
364
  const spawnCwd = path.resolve(params.startOptions.cwd ?? process.cwd());
364
365
  const commandPath = await resolveCommandPath(params.startOptions.command, env, spawnCwd);
365
366
  const commandRealPath = await fs.realpath(commandPath);
367
+ let nativeCommand = params.spawnIdentity.nativeCommand;
368
+ let nativeCandidate = commandRealPath;
366
369
  let invocationPaths;
367
370
  if (process.platform === "win32") {
368
371
  const program = resolveWindowsSpawnProgram({
@@ -372,7 +375,10 @@ async function captureFilesystemDescriptor(params) {
372
375
  execPath: process.execPath,
373
376
  packageName: "@openai/codex"
374
377
  });
375
- if (program.resolution === "node-entrypoint" && !params.spawnIdentity.nativeCommand) throw new Error("Codex runtime cannot attest a custom Node launcher without its native target");
378
+ const entrypoint = program.leadingArgv[0];
379
+ if (program.resolution === "node-entrypoint" && entrypoint && !nativeCommand) nativeCommand = resolvePackagedCodexNativeCommand(await fs.realpath(entrypoint));
380
+ if (program.resolution === "node-entrypoint" && !nativeCommand) throw new Error("Codex runtime cannot attest a custom Node launcher without its native target");
381
+ nativeCandidate = program.command;
376
382
  const invocationCandidates = [
377
383
  commandRealPath,
378
384
  program.command,
@@ -383,17 +389,18 @@ async function captureFilesystemDescriptor(params) {
383
389
  const resolved = await resolveCommandPath(candidate, env, spawnCwd);
384
390
  invocationPaths.push(await fs.realpath(resolved));
385
391
  }
386
- } else invocationPaths = await resolvePosixInvocationPaths({
387
- commandRealPath,
388
- env,
389
- cwd: spawnCwd,
390
- nativeCommand: params.spawnIdentity.nativeCommand
391
- });
392
+ } else {
393
+ nativeCommand ??= resolvePackagedCodexNativeCommand(commandRealPath);
394
+ invocationPaths = await resolvePosixInvocationPaths({
395
+ commandRealPath,
396
+ env,
397
+ cwd: spawnCwd,
398
+ nativeCommand
399
+ });
400
+ }
392
401
  invocationPaths = [...new Set(invocationPaths)].toSorted(compareArtifactNames);
393
402
  if (invocationPaths.length > MAX_ARTIFACT_INVOCATION_PATHS) throw new Error("Codex runtime launcher exceeds the bounded invocation file count");
394
- const nativeCandidate = params.spawnIdentity.nativeCommand ?? invocationPaths[0];
395
- if (!nativeCandidate) throw new Error("Codex runtime did not resolve a native executable");
396
- const nativePath = await fs.realpath(await resolveCommandPath(nativeCandidate, env, spawnCwd));
403
+ const nativePath = await fs.realpath(await resolveCommandPath(nativeCommand ?? nativeCandidate, env, spawnCwd));
397
404
  const packageRoot = await resolvePackageRoot(nativePath);
398
405
  const configuredCodeModeHost = readEffectiveSpawnEnvironmentValue(env, CODE_MODE_HOST_PATH_ENV)?.trim();
399
406
  const adjacentCodeModeHost = path.join(path.dirname(nativePath), process.platform === "win32" ? "codex-code-mode-host.exe" : "codex-code-mode-host");
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { A as normalizeCodexServiceTier, N as CODEX_PLUGIN_MARKETPLACE_NAME_PATTERN } from "./config-CcYL77O6.js";
2
+ import { P as CODEX_PLUGIN_MARKETPLACE_NAME_PATTERN, j as normalizeCodexServiceTier } from "./config-BhmfC1s6.js";
3
3
  import { n as CODEX_APP_SERVER_BINDING_NAMESPACE, t as CODEX_APP_SERVER_BINDING_MAX_ENTRIES } from "./session-binding-meta-B7aEMU7g.js";
4
4
  import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
5
5
  import { AgentHarnessSessionSupersededError, embeddedAgentLog } from "openclaw/plugin-sdk/agent-harness-runtime";
@@ -31,7 +31,8 @@ var session_binding_exports = /* @__PURE__ */ __exportAll({
31
31
  reclaimCurrentCodexSessionGeneration: () => reclaimCurrentCodexSessionGeneration,
32
32
  resolveCodexRunSessionBindingAuthority: () => resolveCodexRunSessionBindingAuthority,
33
33
  scopeCodexRunBindingStore: () => scopeCodexRunBindingStore,
34
- sessionBindingIdentity: () => sessionBindingIdentity
34
+ sessionBindingIdentity: () => sessionBindingIdentity,
35
+ validateBindingForWrite: () => validateBindingForWrite
35
36
  });
36
37
  const CODEX_APP_SERVER_NATIVE_AUTH_PROVIDER = "openai";
37
38
  const PUBLIC_OPENAI_MODEL_PROVIDER = "openai";
@@ -333,9 +334,11 @@ function scopeCodexRunBindingStore(params) {
333
334
  }
334
335
  /** Lets the authoritative OpenClaw session generation claim a stale stable binding row. */
335
336
  async function reclaimCurrentCodexSessionGeneration(params) {
337
+ params.assertCurrent?.();
336
338
  const sessionKey = params.identity.sessionKey?.trim();
337
339
  if (!sessionKey) return true;
338
340
  const plan = await params.bindingStore.prepareSessionGenerationReclaim(params.identity);
341
+ params.assertCurrent?.();
339
342
  if (plan.kind === "resolved") return plan.result;
340
343
  try {
341
344
  const storePath = params.storePath?.trim() || resolveStorePath(params.config?.session?.store, { agentId: params.identity.agentId });
@@ -352,7 +355,7 @@ async function reclaimCurrentCodexSessionGeneration(params) {
352
355
  return await params.bindingStore.mutate(params.identity, {
353
356
  kind: "reclaim-generation",
354
357
  expectedPreviousSessionId: plan.expectedPreviousSessionId
355
- });
358
+ }, params.assertCurrent);
356
359
  }
357
360
  /** Creates the single binding facade owned by the Codex plugin runtime. */
358
361
  function createCodexAppServerBindingStore(state) {
@@ -640,9 +643,8 @@ function createCodexAppServerBindingStore(state) {
640
643
  const active = ownsGeneration ? storedActive : void 0;
641
644
  const retiredGeneration = current?.state === "cleared" && current.retired === true && ownsGeneration;
642
645
  const preservesSupervisionOwner = mutation.kind === "set" && active?.binding.connectionScope === "supervision" && isSameSupervisionOwner(active.binding, mutation.binding);
643
- const clearsPendingSupervisionOwner = mutation.kind === "clear" && matchesPendingSupervisionClear(active?.binding, mutation.threadId, mutation.expectedPendingSupervisionBranch);
644
646
  const replacesExpectedOrdinaryOwner = mutation.kind === "replace-thread" && active?.binding.threadId === mutation.expectedThreadId && active.binding.connectionScope !== "supervision" && mutation.binding.connectionScope !== "supervision" && mutation.binding.threadId !== mutation.expectedThreadId;
645
- if (mutation.kind === "set" && (mutation.if?.kind === "absent" && storedActive || current !== void 0 && !ownsGeneration || retiredGeneration || active?.binding.connectionScope === "supervision" && !preservesSupervisionOwner) || mutation.kind === "patch" && active?.binding.threadId !== mutation.threadId || mutation.kind === "replace-thread" && !replacesExpectedOrdinaryOwner || (mutation.kind === "patch-pending-supervision-branch" || mutation.kind === "commit-pending-supervision-branch") && !matchesPendingSupervisionBranch(active?.binding, mutation.expected) || mutation.kind === "clear" && (!ownsGeneration || (mutation.expectedPendingSupervisionBranch ? !clearsPendingSupervisionOwner : mutation.threadId !== void 0 && active?.binding.threadId !== mutation.threadId || active?.binding.connectionScope === "supervision"))) return { result: false };
647
+ if (mutation.kind === "set" && (mutation.if?.kind === "absent" && storedActive || current !== void 0 && !ownsGeneration || retiredGeneration || active?.binding.connectionScope === "supervision" && !preservesSupervisionOwner) || mutation.kind === "patch" && active?.binding.threadId !== mutation.threadId || mutation.kind === "replace-thread" && !replacesExpectedOrdinaryOwner || (mutation.kind === "patch-pending-supervision-branch" || mutation.kind === "commit-pending-supervision-branch") && !matchesPendingSupervisionBranch(active?.binding, mutation.expected) || mutation.kind === "clear" && (!ownsGeneration || mutation.threadId !== void 0 && active?.binding.threadId !== mutation.threadId || active?.binding.connectionScope === "supervision")) return { result: false };
646
648
  if (mutation.kind === "clear" && retiredGeneration) return { result: true };
647
649
  if (mutation.kind === "clear") return {
648
650
  result: true,
@@ -798,10 +800,6 @@ function matchesPendingSupervisionBranch(binding, expected) {
798
800
  function isSameSupervisionOwner(current, replacement) {
799
801
  return replacement.connectionScope === "supervision" && replacement.threadId === current.threadId && replacement.supervisionSourceThreadId === current.supervisionSourceThreadId;
800
802
  }
801
- function matchesPendingSupervisionClear(binding, threadId, expected) {
802
- if (!expected || threadId !== expected.sourceThreadId) return false;
803
- return !binding || binding.connectionScope === "supervision" && binding.supervisionSourceThreadId === expected.sourceThreadId && matchesPendingSupervisionBranch(binding, expected);
804
- }
805
803
  /** Stable plugin-state key for one current binding owner. */
806
804
  function bindingStoreKey(identity) {
807
805
  if (identity.kind === "session") {
@@ -944,4 +942,4 @@ function normalizeCodexAppServerBindingModelProvider(params) {
944
942
  return modelProvider;
945
943
  }
946
944
  //#endregion
947
- export { hashCodexAppServerBindingFingerprint as a, reclaimCurrentCodexSessionGeneration as c, sessionBindingIdentity as d, session_binding_exports as f, createCodexSessionGenerationSupersededError as i, resolveCodexRunSessionBindingAuthority as l, assertCodexBindingMayBeReplaced as n, isCodexAppServerNativeAuthProfile as o, bindingStoreKey as r, normalizeCodexAppServerBindingModelProvider as s, CODEX_APP_SERVER_BINDING_GUARDED_REQUEST_TIMEOUT_MS as t, scopeCodexRunBindingStore as u };
945
+ export { hashCodexAppServerBindingFingerprint as a, reclaimCurrentCodexSessionGeneration as c, sessionBindingIdentity as d, session_binding_exports as f, createCodexSessionGenerationSupersededError as i, resolveCodexRunSessionBindingAuthority as l, assertCodexBindingMayBeReplaced as n, isCodexAppServerNativeAuthProfile as o, validateBindingForWrite as p, bindingStoreKey as r, normalizeCodexAppServerBindingModelProvider as s, CODEX_APP_SERVER_BINDING_GUARDED_REQUEST_TIMEOUT_MS as t, scopeCodexRunBindingStore as u };
@@ -1,24 +1,23 @@
1
- import { F as resolveCodexAppServerLocalHomeDir, I as resolveCodexAppServerUserHomeDir, P as resolveCodexAppServerHomeDir, T as readCodexPluginConfig, i as resolveCodexAppServerRuntimeOptions, s as resolveCodexSupervisionAppServerRuntimeOptions } from "./config-CcYL77O6.js";
2
- import { $ as CodexAppServerRpcError, Et as withTimeout, Ht as isJsonObject, Lt as resolveCodexAppServerPreparedAuthHandoff, Mt as resolveCodexAppServerAuthProfileId, Pt as resolveCodexAppServerAuthProfileStore, Q as resolveCodexAppServerClientInstanceId, Rt as CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES, c as getLeasedSharedCodexAppServerClient, h as releaseLeasedSharedCodexAppServerClient, zt as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS } from "./shared-client-B1tT5U6r.js";
3
- import { c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity } from "./session-binding-D0zinCP1.js";
4
- import { i as assertCodexThreadForkParams } from "./protocol-validators-Bg5rpNxk.js";
5
- import { a as closeCodexStartupClientBestEffort } from "./attempt-client-cleanup-CpVfwctT.js";
6
- import { n as listCodexAppServerModels } from "./models-DaWfETQk.js";
7
- import { a as replaceCodexCatalogConnectionHomes, n as buildCodexAppServerConnectionFingerprint, t as resumeCodexAppServerThread } from "./thread-resume-BFt1-ZDa.js";
8
- import { i as readCodexSessionMeta, n as codexCatalogHomeId, r as isOpenClawManagedCodexThread, t as canonicalCodexCatalogHome } from "./session-catalog-home-id-Dt2GiG5o.js";
9
- import { i as requestCodexAppServerJson, o as withCodexAppServerJsonClient, r as requestCodexAppServerClientJson } from "./request-BWxweEdK.js";
10
- import { m as projectCodexUserItemText, r as importCodexThreadHistoryToTranscript } from "./transcript-mirror-1ep4XjzF.js";
11
- import { n as describeControlFailure, t as CODEX_CONTROL_METHODS } from "./capabilities-D6m_TdVy.js";
12
- import { r as formatCodexDisplayText } from "./command-formatters--UToCokS.js";
1
+ import { $t as isJsonObject, Jt as readCodexSessionMeta, K as resolveCodexAppServerClientInstanceId, Nt as withTimeout, Vt as resolveCodexAppServerAuthProfileStore, Wt as resolveCodexAppServerPreparedAuthHandoff, Xt as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS, Yt as CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES, Z as assertCodexThreadForkParams, c as getLeasedSharedCodexAppServerClient, h as releaseLeasedSharedCodexAppServerClient, q as CodexAppServerRpcError, qt as isOpenClawManagedCodexThread, y as retireSharedCodexAppServerClientIfCurrent, zt as resolveCodexAppServerAuthProfileId } from "./shared-client-DxcfF9KX.js";
2
+ import { E as readCodexPluginConfig, F as resolveCodexAppServerHomeDir, I as resolveCodexAppServerLocalHomeDir, L as resolveCodexAppServerUserHomeDir, i as resolveCodexAppServerRuntimeOptions, s as resolveCodexSupervisionAppServerRuntimeOptions } from "./config-BhmfC1s6.js";
3
+ import { c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity } from "./session-binding-BHuTCRZQ.js";
4
+ import { a as closeCodexStartupClientBestEffort } from "./attempt-client-cleanup-DYHcRQAa.js";
5
+ import { n as listCodexAppServerModels } from "./models-sYnEc0gd.js";
6
+ import { a as replaceCodexCatalogConnectionHomes, n as buildCodexAppServerConnectionFingerprint, t as resumeCodexAppServerThread } from "./thread-resume-C7pyVKeW.js";
7
+ import { g as codexCatalogHomeId, h as canonicalCodexCatalogHome, m as projectCodexUserItemText, r as importCodexThreadHistoryToTranscript } from "./transcript-mirror-DTUxFWa_.js";
8
+ import { i as requestCodexAppServerJson, o as withCodexAppServerJsonClient, r as requestCodexAppServerClientJson } from "./request-DYAI5FQE.js";
9
+ import { n as describeControlFailure, t as CODEX_CONTROL_METHODS } from "./capabilities-dIjNVCYH.js";
10
+ import { a as formatCodexDisplayText } from "./command-formatters-CLHhEB1Q.js";
13
11
  import { n as createCodexCliNodeConversationBindingData } from "./conversation-binding-data-BiodpIzq.js";
14
12
  import { n as prepareCodexAppServerAuthBinding } from "./auth-binding-Ds_QVm-X.js";
15
- import { i as createImportedCodexSession, n as codexUpstreamBaseline, r as codexUpstreamContinueResult, t as codexLastTerminalTurnId } from "./session-upstream-marker-gqrYIRHk.js";
13
+ import { i as createImportedCodexSession, n as codexUpstreamBaseline, r as codexUpstreamContinueResult, t as codexLastTerminalTurnId } from "./session-upstream-marker-C351-5Qw.js";
16
14
  import { asFiniteNumber, isRecord, normalizeBoundedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
17
15
  import fs from "node:fs/promises";
18
16
  import path from "node:path";
19
17
  import { timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
20
18
  import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
21
19
  import { prepareAgentRuntimeAuth } from "openclaw/plugin-sdk/agent-harness-runtime";
20
+ import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
22
21
  import fsSync from "node:fs";
23
22
  import os from "node:os";
24
23
  import { listAgentIds, resolveAgentDir as resolveAgentDir$1, resolveAgentWorkspaceDir, resolveAllowedModelRef, resolveDefaultAgentDir, resolveDefaultAgentId, resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
@@ -34,8 +33,7 @@ import process$1 from "node:process";
34
33
  import { resolveSessionModelRef } from "openclaw/plugin-sdk/model-session-runtime";
35
34
  import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
36
35
  import { createSessionCatalogAdoptionCoordinator, listSessionCatalogEntries, sessionCatalogAdoptedSessionKey, sessionCatalogAdoptedSourceKey } from "openclaw/plugin-sdk/session-catalog";
37
- import { decodeNodePtyResumeParams, resolveNodeHostExecutable, runNodePtyCommand } from "openclaw/plugin-sdk/node-host";
38
- import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
36
+ import { decodeNodePtyResumeParams, decodeNodePtyStartParams, resolveNodeHostExecutable, runNodePtyCommand } from "openclaw/plugin-sdk/node-host";
39
37
  //#region extensions/codex/src/jsonl-lines.ts
40
38
  const JSONL_STREAM_THRESHOLD_BYTES = 4194304;
41
39
  const JSONL_READ_CHUNK_BYTES = 1048576;
@@ -650,6 +648,7 @@ async function codexControlRequest(pluginConfig, method, requestParams, options
650
648
  const auth = await prepareControlAuth(options, startOptions);
651
649
  const controlRequestOptions = {
652
650
  timeoutMs: options.timeoutMs ?? runtime.requestTimeoutMs,
651
+ assertCurrent: options.assertCurrent,
653
652
  startOptions,
654
653
  config: options.config,
655
654
  sessionKey: options.sessionKey,
@@ -659,7 +658,7 @@ async function codexControlRequest(pluginConfig, method, requestParams, options
659
658
  ...auth.clientOptions
660
659
  };
661
660
  if (options.onResponse || options.beforeRequest) return await withCodexAppServerJsonClient(controlRequestOptions, async (request, client, scope) => {
662
- await options.beforeRequest?.(request);
661
+ await options.beforeRequest?.(request, client, scope);
663
662
  scope.assertCurrent();
664
663
  let response;
665
664
  if (method === "thread/resume") {
@@ -1297,54 +1296,26 @@ async function findAdoptedSessionEntry(params) {
1297
1296
  const adopted = await listAdoptedSessionEntries(params);
1298
1297
  return adopted.get(sessionCatalogAdoptedSourceKey(params.sourceHomeId ?? "gateway:local", params.threadId)) ?? (params.sourceHomeId && params.allowLegacy === true ? adopted.get(sessionCatalogAdoptedSourceKey("gateway:local", params.threadId)) : void 0);
1299
1298
  }
1300
- async function clearCreatedAdoptionBinding(params) {
1301
- let cleared = false;
1302
- let clearError;
1303
- try {
1304
- cleared = await params.bindingStore.mutate(params.identity, {
1305
- kind: "clear",
1306
- threadId: params.sourceThreadId,
1307
- expectedPendingSupervisionBranch: params.expectedPending
1308
- });
1309
- } catch (error) {
1310
- clearError = error;
1311
- }
1312
- if (cleared) return;
1313
- let current;
1314
- try {
1315
- current = await params.bindingStore.read(params.identity);
1316
- } catch (readError) {
1317
- throw new AggregateError([
1318
- params.cause,
1319
- ...clearError ? [clearError] : [],
1320
- readError
1321
- ], `OpenClaw session creation failed and the Codex binding could not be verified for ${params.sourceThreadId}`, { cause: readError });
1322
- }
1323
- if (!matchesPendingSupervisionOwner(current, params.expectedPending)) return;
1324
- throw new AggregateError([params.cause, ...clearError ? [clearError] : []], `OpenClaw session creation failed and the Codex binding could not be cleared for ${params.sourceThreadId}`, { cause: params.cause });
1325
- }
1326
1299
  function matchesPendingAdoptionBinding(binding, expected) {
1327
1300
  const historyCoveredThrough = binding?.historyCoveredThrough;
1328
1301
  return binding?.threadId === expected.sourceThreadId && binding.connectionScope === "supervision" && binding.supervisionSourceThreadId === expected.sourceThreadId && binding.cwd === expected.cwd && binding.conversationSourceTransferComplete === true && binding.preserveNativeModel === true && binding.pendingSupervisionBranch?.sourceThreadId === expected.sourceThreadId && binding.pendingSupervisionBranch.connectionFingerprint === expected.connectionFingerprint && binding.pendingSupervisionBranch.lastTurnId === expected.lastTurnId && (binding.pendingSupervisionBranch.cleanupThreadIds?.length ?? 0) === 0 && typeof historyCoveredThrough === "string" && Number.isFinite(Date.parse(historyCoveredThrough));
1329
1302
  }
1330
- function matchesPendingSupervisionOwner(binding, expected) {
1331
- const pending = binding?.pendingSupervisionBranch;
1332
- const cleanupThreadIds = pending?.cleanupThreadIds ?? [];
1333
- const expectedCleanupThreadIds = expected.cleanupThreadIds ?? [];
1334
- return binding?.threadId === expected.sourceThreadId && binding.connectionScope === "supervision" && binding.supervisionSourceThreadId === expected.sourceThreadId && pending?.sourceThreadId === expected.sourceThreadId && pending.connectionFingerprint === expected.connectionFingerprint && pending.lastTurnId === expected.lastTurnId && cleanupThreadIds.length === expectedCleanupThreadIds.length && cleanupThreadIds.every((threadId, index) => threadId === expectedCleanupThreadIds[index]);
1335
- }
1336
1303
  async function ensurePendingAdoptionBinding(params) {
1337
1304
  const pending = {
1338
1305
  sourceThreadId: params.sourceThreadId,
1339
1306
  connectionFingerprint: params.connectionFingerprint,
1340
1307
  ...params.lastTurnId ? { lastTurnId: params.lastTurnId } : {}
1341
1308
  };
1342
- if (!await reclaimCurrentCodexSessionGeneration({
1309
+ const ownsGeneration = await reclaimCurrentCodexSessionGeneration({
1310
+ assertCurrent: params.initialization.assertCurrent,
1343
1311
  bindingStore: params.bindingStore,
1344
1312
  identity: params.identity,
1345
1313
  config: params.config
1346
- })) throw new Error(`failed to claim the OpenClaw session generation for ${params.sourceThreadId}`);
1314
+ });
1315
+ params.initialization.assertCurrent();
1316
+ if (!ownsGeneration) throw new Error(`failed to claim the OpenClaw session generation for ${params.sourceThreadId}`);
1347
1317
  const existing = await params.bindingStore.read(params.identity);
1318
+ params.initialization.assertCurrent();
1348
1319
  if (existing) {
1349
1320
  if (matchesPendingAdoptionBinding(existing, params)) return;
1350
1321
  throw new Error(`OpenClaw session is already bound to Codex thread ${existing.threadId}`);
@@ -1359,19 +1330,7 @@ async function ensurePendingAdoptionBinding(params) {
1359
1330
  preserveNativeModel: true,
1360
1331
  pendingSupervisionBranch: pending
1361
1332
  };
1362
- let stored;
1363
- try {
1364
- stored = await params.bindingStore.mutate(params.identity, {
1365
- kind: "set",
1366
- if: { kind: "absent" },
1367
- binding
1368
- });
1369
- } catch (error) {
1370
- if (matchesPendingAdoptionBinding(await params.bindingStore.read(params.identity), params)) return;
1371
- throw error;
1372
- }
1373
- if (stored) return;
1374
- if (!matchesPendingAdoptionBinding(await params.bindingStore.read(params.identity), params)) throw new Error(`failed to bind OpenClaw session to Codex thread ${params.sourceThreadId}`);
1333
+ await params.initialization.bind(binding);
1375
1334
  }
1376
1335
  async function createOrReuseAdoptedSession(params) {
1377
1336
  const runtime = params.api.runtime;
@@ -1382,8 +1341,6 @@ async function createOrReuseAdoptedSession(params) {
1382
1341
  };
1383
1342
  const existing = await findAdoptedSessionEntry(lookup);
1384
1343
  if (existing) return existing;
1385
- let createdBindingIdentity;
1386
- let createdPendingBinding;
1387
1344
  try {
1388
1345
  const spawnedCwd = params.sourceThread.cwd?.trim() || void 0;
1389
1346
  const pendingLastTurnId = codexLastTerminalTurnId(params.sourceThread, boundCatalogSessionId);
@@ -1393,6 +1350,7 @@ async function createOrReuseAdoptedSession(params) {
1393
1350
  };
1394
1351
  const created = await createImportedCodexSession({
1395
1352
  runtime: params.api.runtime,
1353
+ bindingStore: params.bindingStore,
1396
1354
  config: params.config,
1397
1355
  key: adoptionSessionKey(params.sourceThread.id, params.sourceHomeId),
1398
1356
  agentId: params.agentId,
@@ -1409,21 +1367,17 @@ async function createOrReuseAdoptedSession(params) {
1409
1367
  modelLocked: true
1410
1368
  } } }
1411
1369
  },
1412
- afterImport: async (entry) => {
1413
- createdBindingIdentity = sessionBindingIdentity({
1370
+ afterImport: async (entry, initialization) => {
1371
+ const identity = sessionBindingIdentity({
1414
1372
  sessionId: entry.sessionId,
1415
1373
  sessionKey: entry.key,
1416
1374
  config: params.config
1417
1375
  });
1418
- createdPendingBinding = {
1419
- sourceThreadId: params.sourceThread.id,
1420
- connectionFingerprint: params.connectionFingerprint,
1421
- ...pendingLastTurnId ? { lastTurnId: pendingLastTurnId } : {}
1422
- };
1423
1376
  await ensurePendingAdoptionBinding({
1424
1377
  bindingStore: params.bindingStore,
1425
1378
  config: params.config,
1426
- identity: createdBindingIdentity,
1379
+ identity,
1380
+ initialization,
1427
1381
  sourceThreadId: params.sourceThread.id,
1428
1382
  connectionFingerprint: params.connectionFingerprint,
1429
1383
  cwd: spawnedCwd ?? "",
@@ -1442,19 +1396,8 @@ async function createOrReuseAdoptedSession(params) {
1442
1396
  boundThreadId: params.sourceThread.id
1443
1397
  };
1444
1398
  } catch (error) {
1445
- let raced = await findAdoptedSessionEntry(lookup);
1399
+ const raced = await findAdoptedSessionEntry(lookup);
1446
1400
  if (raced) return raced;
1447
- if (createdBindingIdentity && createdPendingBinding) {
1448
- await clearCreatedAdoptionBinding({
1449
- bindingStore: params.bindingStore,
1450
- identity: createdBindingIdentity,
1451
- sourceThreadId: params.sourceThread.id,
1452
- expectedPending: createdPendingBinding,
1453
- cause: error
1454
- });
1455
- raced = await findAdoptedSessionEntry(lookup);
1456
- if (raced) return raced;
1457
- }
1458
1401
  throw error;
1459
1402
  }
1460
1403
  }
@@ -1627,7 +1570,8 @@ async function listPairedNode(params) {
1627
1570
  label: nodeLabel(params.node),
1628
1571
  kind: "node",
1629
1572
  nodeId: params.node.nodeId,
1630
- canContinueCodex: canContinueCodexOnNode(params.node)
1573
+ canContinueCodex: canContinueCodexOnNode(params.node),
1574
+ ...params.terminalCapabilities
1631
1575
  };
1632
1576
  if (params.node.connected !== true) {
1633
1577
  const host = {
@@ -1642,6 +1586,15 @@ async function listPairedNode(params) {
1642
1586
  params.onHost?.(host);
1643
1587
  return host;
1644
1588
  }
1589
+ if (!params.node.commands?.includes("codex.appServer.threads.list.v1")) {
1590
+ const host = {
1591
+ ...common,
1592
+ connected: true,
1593
+ sessions: []
1594
+ };
1595
+ params.onHost?.(host);
1596
+ return host;
1597
+ }
1645
1598
  const eventualHost = Promise.resolve().then(async () => {
1646
1599
  const page = filterCatalogPageByTitle(parseCatalogPage(unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
1647
1600
  nodeId: params.node.nodeId,
@@ -1839,6 +1792,33 @@ async function continueNodeCodexSession(params) {
1839
1792
  //#endregion
1840
1793
  //#region extensions/codex/src/session-catalog-terminal.ts
1841
1794
  const CODEX_TERMINAL_RESUME_COMMAND = "codex.terminal.resume.v1";
1795
+ const CODEX_TERMINAL_START_COMMAND = "codex.terminal.start.v1";
1796
+ function createCodexTerminalStartNodeHostCommand() {
1797
+ return {
1798
+ command: CODEX_TERMINAL_START_COMMAND,
1799
+ cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
1800
+ dangerous: false,
1801
+ duplex: true,
1802
+ isAvailable: ({ env }) => Boolean(resolveNodeHostExecutable("codex", {
1803
+ env,
1804
+ strategy: "direct"
1805
+ })),
1806
+ handle: async (paramsJSON, io) => {
1807
+ if (!io) throw new Error("Codex terminal command requires duplex transport");
1808
+ const params = decodeNodePtyStartParams(paramsJSON);
1809
+ const resolution = resolveNodeHostExecutable("codex", { strategy: "direct" });
1810
+ if (!resolution) throw new Error("Codex CLI is unavailable; install codex on this node and reconnect");
1811
+ return JSON.stringify(await runNodePtyCommand({
1812
+ file: resolution.executable,
1813
+ args: params.initialMessage !== void 0 ? ["--", params.initialMessage] : [],
1814
+ cwd: params.cwd,
1815
+ requiredCwd: true,
1816
+ cols: params.cols,
1817
+ rows: params.rows
1818
+ }, io));
1819
+ }
1820
+ };
1821
+ }
1842
1822
  function resolveCodexCatalogTerminalHome(sources) {
1843
1823
  const runtimeConfig = sources.getRuntimeConfig();
1844
1824
  if (!runtimeConfig) throw new Error("OpenClaw runtime config is unavailable");
@@ -1858,7 +1838,10 @@ function resolveLocalCodexTerminalResolution(env = process.env) {
1858
1838
  }
1859
1839
  function codexNodeTerminalCapability(node) {
1860
1840
  const commands = node.invocableCommands ?? node.commands;
1861
- return node.connected === true && commands?.includes("codex.terminal.resume.v1") === true ? { canOpenTerminalCodex: true } : {};
1841
+ return {
1842
+ canOpenTerminalCodex: node.connected === true && commands?.includes("codex.terminal.resume.v1") === true,
1843
+ canStartTerminal: node.connected === true && node.invocableCommands?.includes("codex.terminal.start.v1") === true
1844
+ };
1862
1845
  }
1863
1846
  function createCodexTerminalNodeHostCommand(bindRequest, configSources) {
1864
1847
  return {
@@ -1972,7 +1955,17 @@ async function openCodexCatalogTerminal(params) {
1972
1955
  };
1973
1956
  }
1974
1957
  async function startCodexCatalogTerminal(params) {
1975
- if (params.nodeId) throw new CatalogParamsError("Paired-node Codex terminal start is unavailable; omit hostId to start on the gateway host");
1958
+ if (params.nodeId) return {
1959
+ kind: "node",
1960
+ nodeId: params.nodeId,
1961
+ command: CODEX_TERMINAL_START_COMMAND,
1962
+ paramsJSON: JSON.stringify({
1963
+ cwd: params.cwd,
1964
+ initialMessage: params.initialMessage
1965
+ }),
1966
+ cwd: params.cwd,
1967
+ title: "codex"
1968
+ };
1976
1969
  const resolution = resolveLocalCodexTerminalResolution();
1977
1970
  if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable; install Codex or add codex to PATH, then try again");
1978
1971
  return {
@@ -2107,13 +2100,14 @@ async function listCodexSessionCatalog(params) {
2107
2100
  if (!(!requestedHostIds || query.hostIds?.some((hostId) => hostId.startsWith("node:")))) return { hosts: await Promise.all(localHosts) };
2108
2101
  let nodes;
2109
2102
  try {
2110
- nodes = (await (params.listNodes?.() ?? params.runtime.nodes.list())).nodes.filter((node) => node.gatewayLocal !== true && node.commands?.includes("codex.appServer.threads.list.v1") && (!requestedHostIds || requestedHostIds.has(`node:${node.nodeId}`))).slice(0, 100 - localHosts.length);
2103
+ nodes = (await (params.listNodes?.() ?? params.runtime.nodes.list())).nodes.filter((node) => node.gatewayLocal !== true && (node.commands?.includes("codex.appServer.threads.list.v1") || codexNodeTerminalCapability(node).canStartTerminal) && (!requestedHostIds || requestedHostIds.has(`node:${node.nodeId}`))).slice(0, 100 - localHosts.length);
2111
2104
  } catch (error) {
2112
2105
  const registryHost = {
2113
2106
  hostId: "node:registry",
2114
2107
  label: "Paired nodes",
2115
2108
  kind: "node",
2116
2109
  connected: false,
2110
+ canStartTerminal: false,
2117
2111
  sessions: [],
2118
2112
  error: catalogError("NODE_LIST_FAILED", error)
2119
2113
  };
@@ -2126,17 +2120,15 @@ async function listCodexSessionCatalog(params) {
2126
2120
  runtime: params.runtime,
2127
2121
  sessionEntries: params.sessionEntries
2128
2122
  });
2129
- const nodeHosts = nodes.toSorted(compareNodeLabels).map(async (node) => {
2130
- const host = await listPairedNode({
2131
- agentId,
2132
- runtime: params.runtime,
2133
- node,
2134
- query,
2135
- adoptedSessions: adoptedNodeSessions,
2136
- ...params.onHost ? { onHost: params.onHost } : {}
2137
- });
2138
- return Object.assign(host, codexNodeTerminalCapability(node));
2139
- });
2123
+ const nodeHosts = nodes.toSorted(compareNodeLabels).map((node) => listPairedNode({
2124
+ agentId,
2125
+ runtime: params.runtime,
2126
+ node,
2127
+ query,
2128
+ adoptedSessions: adoptedNodeSessions,
2129
+ terminalCapabilities: codexNodeTerminalCapability(node),
2130
+ ...params.onHost ? { onHost: params.onHost } : {}
2131
+ }));
2140
2132
  return { hosts: await Promise.all([...localHosts, ...nodeHosts]) };
2141
2133
  }
2142
2134
  /** Builds the node-local read-only Codex app-server catalog command. */
@@ -2218,7 +2210,8 @@ function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources
2218
2210
  }
2219
2211
  }
2220
2212
  },
2221
- createCodexTerminalNodeHostCommand(bindRequest, configSources)
2213
+ createCodexTerminalNodeHostCommand(bindRequest, configSources),
2214
+ createCodexTerminalStartNodeHostCommand()
2222
2215
  ];
2223
2216
  }
2224
2217
  function readNodeTranscriptParams(value) {
@@ -2586,18 +2579,19 @@ function createCodexCatalogRequestSnapshot(requestTimeoutMs, request) {
2586
2579
  requestTimeoutMs,
2587
2580
  listThreads: (params, timeoutMs) => request(CODEX_CONTROL_METHODS.listThreads, params, timeoutMs),
2588
2581
  listThreadTurns: (params) => request(CODEX_CONTROL_METHODS.listThreadTurns, params),
2589
- forkThread: (params) => request(CODEX_CONTROL_METHODS.forkThread, assertCodexThreadForkParams(params)),
2582
+ forkThread: (params, assertCurrent) => request(CODEX_CONTROL_METHODS.forkThread, assertCodexThreadForkParams(params), void 0, assertCurrent),
2590
2583
  readThread: async (threadId, includeTurns, timeoutMs) => (await request(CODEX_CONTROL_METHODS.readThread, {
2591
2584
  threadId,
2592
2585
  includeTurns
2593
2586
  }, timeoutMs)).thread,
2594
- archiveThread: async (threadId) => {
2595
- await request(CODEX_CONTROL_METHODS.archiveThread, { threadId });
2587
+ archiveThread: async (threadId, assertCurrent) => {
2588
+ await request(CODEX_CONTROL_METHODS.archiveThread, { threadId }, void 0, assertCurrent);
2596
2589
  }
2597
2590
  };
2598
2591
  }
2599
2592
  function createCodexSessionCatalogControlFromRequests(params) {
2600
2593
  return {
2594
+ forkContext: params.forkContext,
2601
2595
  ...params.clientId ? { clientId: params.clientId } : {},
2602
2596
  ...params.connectionFingerprint ? { connectionFingerprint: params.connectionFingerprint } : {},
2603
2597
  withPinnedConnection: params.withPinnedConnection,
@@ -2653,6 +2647,7 @@ function createCodexSessionCatalogControlFromRequests(params) {
2653
2647
  };
2654
2648
  return await withTimeout(verify(), requests.requestTimeoutMs, "Codex session eligibility could not be verified", unverified);
2655
2649
  },
2650
+ retireConnection: params.retireConnection,
2656
2651
  async listPage(pageParams) {
2657
2652
  const limit = normalizeLimit(pageParams.limit, "limit");
2658
2653
  const search = pageParams.searchTerm?.trim().toLocaleLowerCase() || void 0;
@@ -2714,11 +2709,11 @@ function createCodexSessionCatalogControlFromRequests(params) {
2714
2709
  async listTurnPage(listParams) {
2715
2710
  return await params.createRequestSnapshot().listThreadTurns(listParams);
2716
2711
  },
2717
- async forkThread(forkParams) {
2718
- return await params.createRequestSnapshot().forkThread(forkParams);
2712
+ async forkThread(forkParams, assertCurrent) {
2713
+ return await params.createRequestSnapshot().forkThread(forkParams, assertCurrent);
2719
2714
  },
2720
- async archiveThread(threadId) {
2721
- await params.createRequestSnapshot().archiveThread(threadId);
2715
+ async archiveThread(threadId, assertCurrent) {
2716
+ await params.createRequestSnapshot().archiveThread(threadId, assertCurrent);
2722
2717
  }
2723
2718
  };
2724
2719
  }
@@ -2763,8 +2758,9 @@ function createCodexSessionCatalogControl(params) {
2763
2758
  const pluginConfig = getPluginConfig();
2764
2759
  const runtime = source?.appServer ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig });
2765
2760
  const requestOptions = resolveRequestOptions(runtime.start, agentId, source);
2766
- return createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs) => await codexControlRequest(pluginConfig, method, requestParams, {
2761
+ return createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs, assertCurrent) => await codexControlRequest(pluginConfig, method, requestParams, {
2767
2762
  ...requestOptions,
2763
+ assertCurrent,
2768
2764
  ...timeoutMs === void 0 ? {} : { timeoutMs }
2769
2765
  }));
2770
2766
  };
@@ -2780,15 +2776,26 @@ function createCodexSessionCatalogControl(params) {
2780
2776
  timeoutMs: runtime.requestTimeoutMs
2781
2777
  });
2782
2778
  try {
2783
- const requests = createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs) => await requestCodexAppServerClientJson({
2779
+ const requests = createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs, assertCurrent) => await requestCodexAppServerClientJson({
2784
2780
  client,
2785
2781
  method,
2786
2782
  requestParams,
2787
2783
  config: runtimeConfig,
2788
- timeoutMs: timeoutMs ?? runtime.requestTimeoutMs
2784
+ timeoutMs: timeoutMs ?? runtime.requestTimeoutMs,
2785
+ assertCurrent
2789
2786
  }));
2790
2787
  const pinnedControl = createCodexSessionCatalogControlFromRequests({
2788
+ forkContext: {
2789
+ client,
2790
+ appServer: runtime,
2791
+ pluginConfig,
2792
+ agentDir,
2793
+ localSessionsRoot: source?.localSessionsRoot
2794
+ },
2791
2795
  clientId: resolveCodexAppServerClientInstanceId(client),
2796
+ retireConnection: () => {
2797
+ retireSharedCodexAppServerClientIfCurrent(client);
2798
+ },
2792
2799
  connectionFingerprint: buildCodexAppServerConnectionFingerprint(runtime, agentDir),
2793
2800
  createRequestSnapshot: () => requests,
2794
2801
  ...source?.localSessionsRoot ? { localSessionsRoot: source.localSessionsRoot } : {},
@@ -2884,14 +2891,21 @@ function createCodexSessionCatalogNodeInvokePolicies() {
2884
2891
  commands: [
2885
2892
  CODEX_APP_SERVER_THREADS_LIST_COMMAND,
2886
2893
  CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
2887
- CODEX_TERMINAL_RESUME_COMMAND
2894
+ CODEX_TERMINAL_RESUME_COMMAND,
2895
+ CODEX_TERMINAL_START_COMMAND
2888
2896
  ],
2889
2897
  defaultPlatforms: [
2890
2898
  "macos",
2891
2899
  "linux",
2892
2900
  "windows"
2893
2901
  ],
2894
- handle: (context) => context.command === "codex.terminal.resume.v1" ? { ok: true } : context.invokeNode()
2902
+ handle: (context) => {
2903
+ if (context.command === "codex.terminal.start.v1") return context.client?.scopes?.includes("operator.admin") && context.config.gateway?.cliAgents?.enabled === true && context.config.gateway?.terminal?.enabled !== false ? { ok: true } : {
2904
+ ok: false,
2905
+ message: "Native terminal start requires operator.admin and enabled CLI agents and terminals"
2906
+ };
2907
+ return context.command === "codex.terminal.resume.v1" ? { ok: true } : context.invokeNode();
2908
+ }
2895
2909
  }];
2896
2910
  }
2897
2911
  function toGenericCatalogHost(host, localTerminalAvailable) {
@@ -2981,8 +2995,11 @@ function registerCodexSessionCatalog(params) {
2981
2995
  const localTerminalAvailable = resolveLocalCodexTerminalExecutable() !== void 0;
2982
2996
  const { agentId: requestedAgentId, allowProcessHomeFallback, listNodes, onHost, sessionEntries, ...gatewayQuery } = query;
2983
2997
  const agentId = resolveRequestAgentId(requestedAgentId);
2984
- const mapHost = (host) => toGenericCatalogHost(host, localTerminalAvailable);
2985
2998
  const localHomes = [...catalogHomes(agentId, allowProcessHomeFallback)];
2999
+ const mapHost = (host) => ({
3000
+ ...toGenericCatalogHost(host, localTerminalAvailable),
3001
+ canStartTerminal: host.kind === "gateway" ? localTerminalAvailable && localHomes.some((home) => home.hostId === host.hostId && home.appServer.start.transport === "stdio") : host.canStartTerminal === true
3002
+ });
2986
3003
  return (await listCodexSessionCatalog({
2987
3004
  agentId,
2988
3005
  bindingStore: params.bindingStore,
@@ -3077,11 +3094,16 @@ function registerCodexSessionCatalog(params) {
3077
3094
  });
3078
3095
  },
3079
3096
  startTerminalSession: async (request) => {
3080
- if (!request.nodeId && catalogHomes(request.agentId, request.allowProcessHomeFallback).length === 0) throw new CatalogParamsError("local Codex sessions are unavailable in isolated state");
3097
+ const source = request.nodeId ? void 0 : resolveLocalCatalogHomeForThread({
3098
+ homes: [...catalogHomes(request.agentId, request.allowProcessHomeFallback)],
3099
+ hostId: request.hostId ?? "gateway:local"
3100
+ });
3101
+ if (source && source.appServer.start.transport !== "stdio") throw new CatalogParamsError("Native terminal start requires a local Codex source");
3081
3102
  return await startCodexCatalogTerminal({
3082
3103
  getPluginConfig: params.getPluginConfig,
3083
3104
  getRuntimeConfig: params.getRuntimeConfig,
3084
- ...request
3105
+ ...request,
3106
+ source
3085
3107
  });
3086
3108
  }
3087
3109
  });
@@ -1,4 +1,4 @@
1
- import "./session-catalog-eMhVm0Ql.js";
1
+ import "./session-catalog-yyvTWFOw.js";
2
2
  import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
4
4
  import { sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";