@openclaw/codex 2026.9.1-beta.1 → 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 (73) hide show
  1. package/dist/api.js +5 -6
  2. package/dist/{attempt-client-cleanup-bfsmcQ6m.js → attempt-client-cleanup-DYHcRQAa.js} +31 -45
  3. package/dist/attempt-context-DmQUNWwT.js +634 -0
  4. package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
  5. package/dist/{binding-connection-BMvPX73o.js → binding-connection-E6uDsPlx.js} +2 -2
  6. package/dist/{bounded-turn-B7iBUcuP.js → bounded-turn-wzJrwKKe.js} +15 -20
  7. package/dist/{capabilities-BRMh0aIq.js → capabilities-dIjNVCYH.js} +1 -1
  8. package/dist/cli-metadata.js +1 -1
  9. package/dist/{command-formatters-DEsIufkt.js → command-formatters-CLHhEB1Q.js} +5 -4
  10. package/dist/{command-handlers-D_-1_qRp.js → command-handlers-CTGb2csu.js} +124 -101
  11. package/dist/{compact-BglNFZrQ.js → compact-_t0HQrUS.js} +38 -29
  12. package/dist/{config-giW8X0QA.js → config-BhmfC1s6.js} +294 -282
  13. package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
  14. package/dist/{conversation-binding-data-CW2GuupV.js → conversation-binding-data-BiodpIzq.js} +2 -1
  15. package/dist/doctor-contract-api.js +143 -12
  16. package/dist/{dynamic-tools-Dnlfa--w.js → dynamic-tools-BU9tmlYm.js} +599 -655
  17. package/dist/{effective-mcp-catalog-CTeGrnfO.js → effective-mcp-catalog-DzYBW4h1.js} +2 -2
  18. package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-DHRFXKbS.js} +4 -4
  19. package/dist/{event-projector-usage-Bk1I90Bg.js → event-projector-usage-C5mcrGjp.js} +15 -148
  20. package/dist/{harness-Csz0F-DK.js → harness-DXVZ5II5.js} +42 -52
  21. package/dist/harness.js +1 -1
  22. package/dist/index.js +458 -404
  23. package/dist/{isolated-completion-Cv6FqygM.js → isolated-completion-kg9kv4iH.js} +4 -4
  24. package/dist/launch-args-RXQwn8zV.js +90 -0
  25. package/dist/{managed-binary-DFYK4DWq.js → managed-binary-4-Vjz5GH.js} +14 -9
  26. package/dist/media-understanding-provider.js +1 -1
  27. package/dist/model-catalog-CUM68Hkl.js +101 -0
  28. package/dist/{models-TS3HBU-q.js → models-sYnEc0gd.js} +25 -28
  29. package/dist/{native-subagent-monitor-4Mfq2HDs.js → native-subagent-monitor-CnH147jH.js} +55 -115
  30. package/dist/{node-exec-server.runtime-BpWqJO1V.js → node-exec-server.runtime-CstNHCmC.js} +9 -6
  31. package/dist/{plugin-approval-roundtrip-bUm777tR.js → plugin-approval-roundtrip-CNe-Xrjx.js} +30 -3
  32. package/dist/{plugin-marketplace-discovery-B9DvaAG-.js → plugin-marketplace-discovery-CfFlKqDl.js} +15 -14
  33. package/dist/{plugin-metadata-cache-CTCT3w5L.js → plugin-metadata-cache-BFrPhb2c.js} +6 -6
  34. package/dist/provider-capabilities-DW5p4k9w.js +101 -0
  35. package/dist/{rate-limits-C5_D8Gmb.js → rate-limits-CawQJfUS.js} +3 -2
  36. package/dist/{sandbox-guard-kuWTn9C5.js → request-DYAI5FQE.js} +195 -2
  37. package/dist/{run-attempt-DYc0QCBF.js → run-attempt-D1hJO4fX.js} +828 -2100
  38. package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-C3jVimFn.js} +21 -14
  39. package/dist/{session-binding-Bcynfd-_.js → session-binding-BHuTCRZQ.js} +198 -127
  40. package/dist/{session-catalog-DaLKVtpH.js → session-catalog-yyvTWFOw.js} +564 -408
  41. package/dist/{session-cli-DMc4oS2w.js → session-cli-CmFEXMAM.js} +1 -1
  42. package/dist/session-history-C5J1AKzG.js +87 -0
  43. package/dist/session-initialization-BeWzjGiR.js +74 -0
  44. package/dist/{session-permission-policy-BrNH1bjy.js → session-permission-policy-DeWVTCMj.js} +2 -2
  45. package/dist/session-retirement-BujDjXVF.js +97 -0
  46. package/dist/{session-upstream-marker-BIkusb95.js → session-upstream-marker-C351-5Qw.js} +19 -4
  47. package/dist/settled-turn-context-D3SNWtUn.js +268 -0
  48. package/dist/settled-turn-finalizer-BVJx4yoc.js +104 -0
  49. package/dist/{shared-client-DKz23hHY.js → shared-client-DxcfF9KX.js} +3480 -679
  50. package/dist/{side-question-DjA_jsNe.js → side-question-BTZAII4l.js} +108 -70
  51. package/dist/{thread-lifecycle-L7oEkTlq.js → thread-lifecycle-DAcEJ_Om.js} +2188 -1915
  52. package/dist/{thread-ownership-DZeuKH1j.js → thread-ownership-zGokd8i1.js} +25 -14
  53. package/dist/thread-policy-g4KbJPzp.js +54 -0
  54. package/dist/{plugin-app-cache-key-C1CAdfcP.js → thread-resume-C7pyVKeW.js} +59 -7
  55. package/dist/{transcript-mirror-DsCMWBca.js → transcript-mirror-DTUxFWa_.js} +79 -17
  56. package/dist/transport-stdio-CyPldczm.js +726 -0
  57. package/dist/{upstream-prompt-provenance-CFmsKwr9.js → upstream-prompt-provenance-LphB8slG.js} +8 -4
  58. package/dist/upstream-session-fork-CYlBIGVt.js +966 -0
  59. package/dist/{usage-ROIaW3wo.js → usage-Bj-rrzkR.js} +3 -3
  60. package/dist/{web-search-provider.runtime-ClaNPV7N.js → web-search-provider.runtime-D2FWSK4w.js} +3 -3
  61. package/openclaw.plugin.json +1 -30
  62. package/package.json +6 -6
  63. package/dist/command-rpc-CCeCkf_v.js +0 -90
  64. package/dist/incognito-session-ET_ufDH-.js +0 -2
  65. package/dist/model-catalog-i9rNBthX.js +0 -48
  66. package/dist/protocol-validators-VeJxxJ0U.js +0 -2421
  67. package/dist/request-DXXGHko0.js +0 -157
  68. package/dist/session-catalog-home-id-BNwAJ9D7.js +0 -18
  69. package/dist/session-retirement-CcNC3YSY.js +0 -36
  70. package/dist/settled-turn-finalizer-Dc3ApjU4.js +0 -328
  71. package/dist/thread-resume-BE9qNEW0.js +0 -30
  72. package/dist/transport-stdio-DhoGy33Z.js +0 -99
  73. package/dist/upstream-session-fork-DlaHxSLC.js +0 -326
@@ -1,40 +1,42 @@
1
- import { S as resolveCodexAppServerUserHomeDir, h as readCodexPluginConfig, s as resolveCodexSupervisionAppServerRuntimeOptions } from "./config-giW8X0QA.js";
2
- import { G as CodexAppServerRpcError, St as resolveCodexAppServerLocalHomeDir, U as resolveCodexAppServerClientInstanceId, ht as CODEX_INTERACTIVE_THREAD_SOURCE_KINDS, mt as CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES, o as getLeasedSharedCodexAppServerClient, p as releaseLeasedSharedCodexAppServerClient, rt as withTimeout, vt as isJsonObject, xt as resolveCodexAppServerHomeDir } from "./shared-client-DKz23hHY.js";
3
- import { c as reclaimCurrentCodexSessionGeneration, d as sessionBindingIdentity } from "./session-binding-Bcynfd-_.js";
4
- import { n as assertCodexThreadForkParams } from "./protocol-validators-VeJxxJ0U.js";
5
- import { i as replaceCodexCatalogConnectionHomes, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-C1CAdfcP.js";
6
- import { n as codexCatalogHomeId, t as canonicalCodexCatalogHome } from "./session-catalog-home-id-BNwAJ9D7.js";
7
- import { r as importCodexThreadHistoryToTranscript } from "./transcript-mirror-DsCMWBca.js";
8
- import { t as CODEX_CONTROL_METHODS } from "./capabilities-BRMh0aIq.js";
9
- import { r as formatCodexDisplayText } from "./command-formatters-DEsIufkt.js";
10
- import { n as createCodexCliNodeConversationBindingData } from "./conversation-binding-data-CW2GuupV.js";
11
- import { n as requestCodexAppServerClientJson } from "./request-DXXGHko0.js";
12
- import { t as codexControlRequest } from "./command-rpc-CCeCkf_v.js";
13
- import { i as createImportedCodexSession, n as codexUpstreamBaseline, r as codexUpstreamContinueResult, t as codexLastTerminalTurnId } from "./session-upstream-marker-BIkusb95.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";
11
+ import { n as createCodexCliNodeConversationBindingData } from "./conversation-binding-data-BiodpIzq.js";
12
+ import { n as prepareCodexAppServerAuthBinding } from "./auth-binding-Ds_QVm-X.js";
13
+ import { i as createImportedCodexSession, n as codexUpstreamBaseline, r as codexUpstreamContinueResult, t as codexLastTerminalTurnId } from "./session-upstream-marker-C351-5Qw.js";
14
14
  import { asFiniteNumber, isRecord, normalizeBoundedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
15
15
  import fs from "node:fs/promises";
16
16
  import path from "node:path";
17
17
  import { timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
18
18
  import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
19
+ import { prepareAgentRuntimeAuth } from "openclaw/plugin-sdk/agent-harness-runtime";
20
+ import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
19
21
  import fsSync from "node:fs";
20
22
  import os from "node:os";
21
- import { listAgentIds, resolveAgentDir, resolveAllowedModelRef, resolveDefaultAgentDir, resolveDefaultAgentId, resolveDefaultModelForAgent, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
23
+ import { listAgentIds, resolveAgentDir as resolveAgentDir$1, resolveAgentWorkspaceDir, resolveAllowedModelRef, resolveDefaultAgentDir, resolveDefaultAgentId, resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
22
24
  import { runCommandBuffered } from "openclaw/plugin-sdk/process-runtime";
25
+ import { resolveSessionAgentIdsStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
23
26
  import { materializeWindowsSpawnProgram, resolveWindowsSpawnProgram } from "openclaw/plugin-sdk/windows-spawn";
24
- import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
27
+ import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
25
28
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
26
29
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
27
30
  import { sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
28
31
  import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
29
- import { root } from "openclaw/plugin-sdk/file-access-runtime";
30
32
  import process$1 from "node:process";
33
+ import { resolveSessionModelRef } from "openclaw/plugin-sdk/model-session-runtime";
34
+ import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
31
35
  import { createSessionCatalogAdoptionCoordinator, listSessionCatalogEntries, sessionCatalogAdoptedSessionKey, sessionCatalogAdoptedSourceKey } from "openclaw/plugin-sdk/session-catalog";
32
- import { decodeNodePtyResumeParams, resolveNodeHostExecutable, runNodePtyCommand } from "openclaw/plugin-sdk/node-host";
33
- import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
34
- import { createZstdDecompress } from "node:zlib";
36
+ import { decodeNodePtyResumeParams, decodeNodePtyStartParams, resolveNodeHostExecutable, runNodePtyCommand } from "openclaw/plugin-sdk/node-host";
35
37
  //#region extensions/codex/src/jsonl-lines.ts
36
- const JSONL_STREAM_THRESHOLD_BYTES = 4 * 1024 * 1024;
37
- const JSONL_READ_CHUNK_BYTES = 1024 * 1024;
38
+ const JSONL_STREAM_THRESHOLD_BYTES = 4194304;
39
+ const JSONL_READ_CHUNK_BYTES = 1048576;
38
40
  async function visitJsonlLines(file, visitor, chunkBytes = JSONL_READ_CHUNK_BYTES) {
39
41
  let size;
40
42
  try {
@@ -134,7 +136,7 @@ const CODEX_CLI_SESSIONS_LIST_COMMAND = "codex.cli.sessions.list";
134
136
  const CODEX_CLI_SESSION_RESUME_COMMAND = "codex.cli.session.resume";
135
137
  const DEFAULT_SESSION_LIMIT = 10;
136
138
  const MAX_SESSION_LIMIT = 50;
137
- const DEFAULT_RESUME_TIMEOUT_MS = 20 * 6e4;
139
+ const DEFAULT_RESUME_TIMEOUT_MS = 12e5;
138
140
  const SESSION_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
139
141
  const activeResumeSessions = /* @__PURE__ */ new Set();
140
142
  function createCodexCliSessionNodeHostCommands() {
@@ -525,7 +527,7 @@ function normalizeLimit$1(value) {
525
527
  return typeof value === "number" && Number.isFinite(value) ? Math.min(MAX_SESSION_LIMIT, Math.max(1, Math.floor(value))) : DEFAULT_SESSION_LIMIT;
526
528
  }
527
529
  function normalizeTimeoutMs(value) {
528
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(60 * 6e4, Math.floor(value)) : DEFAULT_RESUME_TIMEOUT_MS;
530
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(36e5, Math.floor(value)) : DEFAULT_RESUME_TIMEOUT_MS;
529
531
  }
530
532
  function truncateText(value, max) {
531
533
  if (value.length <= max) return value;
@@ -546,6 +548,198 @@ function formatNodeLabel(node) {
546
548
  ].filter(Boolean).join(" / ") || "node";
547
549
  }
548
550
  //#endregion
551
+ //#region extensions/codex/src/command-rpc.ts
552
+ async function prepareControlAuth(options, startOptions) {
553
+ if (!options.onResponse || !options.config || !options.sessionKey || options.authProfileId === null || startOptions.homeScope === "user") return {
554
+ authProfileId: options.authProfileId ?? void 0,
555
+ clientOptions: { authProfileId: options.authProfileId }
556
+ };
557
+ const config = options.config;
558
+ const { sessionAgentId } = resolveSessionAgentIdsStrict({
559
+ config,
560
+ sessionKey: options.sessionKey,
561
+ agentId: options.agentId
562
+ });
563
+ const agentDir = options.agentDir ?? resolveAgentDir$1(config, sessionAgentId);
564
+ const workspaceDir = resolveAgentWorkspaceDir(config, sessionAgentId);
565
+ const entry = getSessionEntry({
566
+ agentId: sessionAgentId,
567
+ storePath: resolveStorePath(config.session?.store, { agentId: sessionAgentId }),
568
+ sessionKey: options.sessionKey,
569
+ hydrateSkillPromptRefs: false,
570
+ readConsistency: "latest"
571
+ });
572
+ const model = resolveSessionModelRef(config, entry, sessionAgentId);
573
+ const store = resolveCodexAppServerAuthProfileStore({
574
+ agentDir,
575
+ config
576
+ });
577
+ const { plan, attempts } = prepareAgentRuntimeAuth({
578
+ provider: model.provider,
579
+ modelId: model.model,
580
+ config,
581
+ agentDir,
582
+ workspaceDir,
583
+ authProfileStore: store,
584
+ sessionAuthProfileId: entry?.authProfileOverride ?? options.authProfileId,
585
+ sessionAuthProfileSource: entry?.authProfileOverrideSource,
586
+ harnessId: "codex",
587
+ harnessAuthBootstrap: "harness"
588
+ });
589
+ const route = plan.modelRoute;
590
+ const resolvedAuth = route ? await resolveApiKeyForProvider({
591
+ provider: route.provider,
592
+ modelId: route.modelId,
593
+ modelApi: route.api,
594
+ cfg: config,
595
+ agentDir,
596
+ workspaceDir,
597
+ store,
598
+ profileId: attempts[0]?.profileId,
599
+ lockedProfile: plan.forwardedAuthProfileSource === "user",
600
+ allowAuthProfileFallback: attempts[0]?.allowAuthProfileFallback,
601
+ skipSetupProviderFallback: true
602
+ }) : void 0;
603
+ const handoff = await resolveCodexAppServerPreparedAuthHandoff({
604
+ authRequirement: route?.authRequirement,
605
+ resolvedApiKey: resolvedAuth?.apiKey,
606
+ authProfileId: route ? plan.forwardedAuthProfileId : resolveCodexAppServerAuthProfileId({
607
+ authProfileId: plan.forwardedAuthProfileId,
608
+ store,
609
+ config
610
+ }),
611
+ authProfileStore: store,
612
+ agentDir,
613
+ homeScope: startOptions.homeScope ?? "agent",
614
+ config,
615
+ subscriptionProfileRequiredError: "Prepared Codex subscription route requires a forwarded OpenAI OAuth or token profile.",
616
+ subscriptionProfileUnusableError: "Prepared Codex subscription auth profile is unusable."
617
+ });
618
+ const binding = handoff.authProfileId ? await prepareCodexAppServerAuthBinding({
619
+ authProfileId: handoff.authProfileId,
620
+ authProfileStore: store,
621
+ agentDir,
622
+ config
623
+ }) : void 0;
624
+ return {
625
+ authProfileId: handoff.authProfileId,
626
+ clientOptions: {
627
+ ...handoff.preparedAuth ? { preparedAuth: handoff.preparedAuth } : { authProfileId: handoff.authProfileId },
628
+ authRequirement: route?.authRequirement,
629
+ authProfileStore: binding?.authProfileStore ?? store,
630
+ authBindingFingerprint: binding?.fingerprint,
631
+ agentDir
632
+ }
633
+ };
634
+ }
635
+ function requestOptions(pluginConfig, limit, config, agentDir) {
636
+ const runtime = resolveCodexAppServerRuntimeOptions({ pluginConfig });
637
+ return {
638
+ limit,
639
+ timeoutMs: runtime.requestTimeoutMs,
640
+ startOptions: runtime.start,
641
+ config,
642
+ agentDir
643
+ };
644
+ }
645
+ async function codexControlRequest(pluginConfig, method, requestParams, options = {}) {
646
+ const runtime = options.startOptions ? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig }) : resolveCodexAppServerRuntimeOptions({ pluginConfig });
647
+ const startOptions = options.startOptions ?? runtime.start;
648
+ const auth = await prepareControlAuth(options, startOptions);
649
+ const controlRequestOptions = {
650
+ timeoutMs: options.timeoutMs ?? runtime.requestTimeoutMs,
651
+ assertCurrent: options.assertCurrent,
652
+ startOptions,
653
+ config: options.config,
654
+ sessionKey: options.sessionKey,
655
+ sessionId: options.sessionId,
656
+ agentDir: options.agentDir,
657
+ isolated: options.isolated,
658
+ ...auth.clientOptions
659
+ };
660
+ if (options.onResponse || options.beforeRequest) return await withCodexAppServerJsonClient(controlRequestOptions, async (request, client, scope) => {
661
+ await options.beforeRequest?.(request, client, scope);
662
+ scope.assertCurrent();
663
+ let response;
664
+ if (method === "thread/resume") {
665
+ if (!isJsonObject(requestParams) || typeof requestParams.threadId !== "string") throw new Error("Codex thread/resume requires a thread id.");
666
+ response = await resumeCodexAppServerThread({
667
+ client,
668
+ request: {
669
+ ...requestParams,
670
+ threadId: requestParams.threadId
671
+ },
672
+ requestResume: () => request({
673
+ method,
674
+ requestParams
675
+ }),
676
+ abandonClient: () => closeCodexStartupClientBestEffort(client)
677
+ });
678
+ } else response = await request({
679
+ method,
680
+ requestParams
681
+ });
682
+ await options.onResponse?.(response, client, {
683
+ authProfileId: auth.authProfileId,
684
+ assertCurrent: scope.assertCurrent
685
+ });
686
+ scope.assertCurrent();
687
+ return response;
688
+ });
689
+ return await requestCodexAppServerJson({
690
+ method,
691
+ requestParams,
692
+ ...controlRequestOptions
693
+ });
694
+ }
695
+ async function safeCodexControlRequest(pluginConfig, method, requestParams, options = {}) {
696
+ return await safeValue(async () => await codexControlRequest(pluginConfig, method, requestParams, options));
697
+ }
698
+ async function safeCodexModelList(pluginConfig, limit, config, agentDir) {
699
+ return await safeValue(async () => await listCodexAppServerModels(requestOptions(pluginConfig, limit, config, agentDir)));
700
+ }
701
+ async function readCodexStatusProbes(pluginConfig, config, agentDir) {
702
+ const [models, account, limits, mcps, skills] = await Promise.all([
703
+ safeCodexModelList(pluginConfig, 20, config, agentDir),
704
+ safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.account, { refreshToken: false }, {
705
+ config,
706
+ agentDir
707
+ }),
708
+ safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.rateLimits, void 0, {
709
+ config,
710
+ agentDir
711
+ }),
712
+ safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listMcpServers, { limit: 100 }, {
713
+ config,
714
+ agentDir
715
+ }),
716
+ safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listSkills, {}, {
717
+ config,
718
+ agentDir
719
+ })
720
+ ]);
721
+ return {
722
+ models,
723
+ account,
724
+ limits,
725
+ mcps,
726
+ skills
727
+ };
728
+ }
729
+ async function safeValue(read) {
730
+ try {
731
+ return {
732
+ ok: true,
733
+ value: await read()
734
+ };
735
+ } catch (error) {
736
+ return {
737
+ ok: false,
738
+ error: describeControlFailure(error)
739
+ };
740
+ }
741
+ }
742
+ //#endregion
549
743
  //#region extensions/codex/src/app-server/thread-archive-guard.ts
550
744
  const DESCENDANT_PAGE_LIMIT = 100;
551
745
  const MAX_DESCENDANT_PAGES = 100;
@@ -613,7 +807,7 @@ const MAX_SESSION_PREVIEW_LENGTH = 500;
613
807
  const MAX_SESSION_KEY_LENGTH = 1024;
614
808
  const MAX_METADATA_LENGTH = 500;
615
809
  const MAX_ACTIVE_FLAGS = 16;
616
- const MAX_TRANSCRIPT_PAGE_BYTES = 20 * 1024 * 1024;
810
+ const MAX_TRANSCRIPT_PAGE_BYTES = 20971520;
617
811
  var CatalogParamsError = class extends Error {};
618
812
  function readControlCursor(value, label) {
619
813
  if (value === void 0 || value === null) return;
@@ -997,7 +1191,7 @@ async function createOrReuseNodeAdoptedSession(params) {
997
1191
  key: nodeAdoptionSessionKey(params.hostId, params.record.threadId),
998
1192
  agentId: params.agentId,
999
1193
  recoverMatchingInitialEntry: true,
1000
- ...params.record.name?.trim() ? { label: params.record.name.trim() } : {},
1194
+ displayName: params.record.name ?? void 0,
1001
1195
  ...params.record.cwd?.trim() ? { spawnedCwd: params.record.cwd.trim() } : {},
1002
1196
  initialEntry: {
1003
1197
  agentHarnessId: "codex",
@@ -1040,182 +1234,6 @@ async function createOrReuseNodeAdoptedSession(params) {
1040
1234
  }
1041
1235
  }
1042
1236
  //#endregion
1043
- //#region extensions/codex/src/session-catalog-terminal.ts
1044
- const CODEX_TERMINAL_RESUME_COMMAND = "codex.terminal.resume.v1";
1045
- function resolveCodexCatalogTerminalHome(sources) {
1046
- const runtimeConfig = sources.getRuntimeConfig();
1047
- if (!runtimeConfig) throw new Error("OpenClaw runtime config is unavailable");
1048
- const agentDir = sources.source?.agentDir ?? (sources.agentId ? resolveAgentDir(runtimeConfig, sources.agentId) : resolveDefaultAgentDir(runtimeConfig));
1049
- return resolveCodexAppServerLocalHomeDir(sources.source?.appServer.start ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig: sources.getPluginConfig() }).start, agentDir);
1050
- }
1051
- function resolveLocalCodexTerminalExecutable(env = process.env) {
1052
- return resolveLocalCodexTerminalResolution(env)?.executable;
1053
- }
1054
- function resolveLocalCodexTerminalResolution(env = process.env) {
1055
- return resolveNodeHostExecutable("codex", {
1056
- env,
1057
- pathEnv: env.PATH ?? env.Path ?? "",
1058
- strategy: "fallback"
1059
- });
1060
- }
1061
- function codexNodeTerminalCapability(node) {
1062
- const commands = node.invocableCommands ?? node.commands;
1063
- return node.connected === true && commands?.includes("codex.terminal.resume.v1") === true ? { canOpenTerminalCodex: true } : {};
1064
- }
1065
- async function requireCatalogEligibleThread(control, threadId) {
1066
- const cached = await findCatalogEligibleThread(control, threadId, false);
1067
- if (cached) return cached;
1068
- const refreshed = await findCatalogEligibleThread(control, threadId, true);
1069
- if (refreshed) return refreshed;
1070
- throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
1071
- }
1072
- async function findCatalogEligibleThread(control, threadId, forceRefresh) {
1073
- let cursor;
1074
- const seenCursors = /* @__PURE__ */ new Set();
1075
- for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
1076
- const page = await control.listPage({
1077
- limit: 100,
1078
- ...cursor ? { cursor } : {},
1079
- ...forceRefresh ? { forceRefresh: true } : {}
1080
- });
1081
- const candidate = page.sessions.find((session) => session.threadId === threadId);
1082
- if (candidate) {
1083
- if (isInteractiveThreadSource(candidate.source)) return candidate;
1084
- throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
1085
- }
1086
- const nextCursor = page.nextCursor?.trim();
1087
- if (!nextCursor) return;
1088
- if (seenCursors.has(nextCursor)) throw new CatalogParamsError("Codex session eligibility could not be verified");
1089
- seenCursors.add(nextCursor);
1090
- cursor = nextCursor;
1091
- }
1092
- throw new CatalogParamsError("Codex session eligibility could not be verified");
1093
- }
1094
- function createCodexTerminalNodeHostCommand(bindRequest, configSources) {
1095
- return {
1096
- command: CODEX_TERMINAL_RESUME_COMMAND,
1097
- cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
1098
- dangerous: false,
1099
- duplex: true,
1100
- isAvailable: ({ env }) => Boolean(resolveNodeHostExecutable("codex", {
1101
- env,
1102
- pathEnv: env.PATH ?? env.Path ?? "",
1103
- strategy: "direct"
1104
- })),
1105
- handle: async (paramsJSON, io) => {
1106
- if (!io) throw new Error("Codex terminal command requires duplex transport");
1107
- const request = bindRequest(paramsJSON);
1108
- const resume = decodeNodePtyResumeParams(request.paramsJSON, (value) => {
1109
- if (typeof value !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(value)) throw new CatalogParamsError("threadId must be a UUID");
1110
- return value;
1111
- });
1112
- const record = await requireCatalogEligibleThread(request.control, resume.threadId);
1113
- const resolution = resolveNodeHostExecutable("codex", {
1114
- env: process.env,
1115
- pathEnv: process.env.PATH ?? process.env.Path ?? "",
1116
- strategy: "direct"
1117
- });
1118
- if (!resolution) throw new Error("Codex CLI is unavailable");
1119
- return JSON.stringify(await runNodePtyCommand({
1120
- file: resolution.executable,
1121
- args: ["resume", resume.threadId],
1122
- cwd: record.cwd,
1123
- env: { CODEX_HOME: resolveCodexCatalogTerminalHome({
1124
- ...configSources,
1125
- agentId: request.agentId
1126
- }) },
1127
- cols: resume.cols,
1128
- rows: resume.rows
1129
- }, io));
1130
- }
1131
- };
1132
- }
1133
- async function resolveNodeCatalogEligibleThread(params) {
1134
- let cursor;
1135
- const seenCursors = /* @__PURE__ */ new Set();
1136
- for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
1137
- const raw = await params.runtime.nodes.invoke({
1138
- nodeId: params.nodeId,
1139
- command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
1140
- params: {
1141
- agentId: params.agentId,
1142
- limit: 100,
1143
- ...cursor ? { cursor } : {}
1144
- },
1145
- timeoutMs: NODE_INVOKE_TIMEOUT_MS,
1146
- scopes: ["operator.write"]
1147
- });
1148
- const page = params.parseCatalogPage(unwrapNodeInvokePayload(raw));
1149
- const record = page.sessions.find((candidate) => candidate.threadId === params.threadId);
1150
- if (record) {
1151
- if (isInteractiveThreadSource(record.source)) return record;
1152
- break;
1153
- }
1154
- const nextCursor = page.nextCursor?.trim();
1155
- if (!nextCursor || seenCursors.has(nextCursor)) break;
1156
- seenCursors.add(nextCursor);
1157
- cursor = nextCursor;
1158
- }
1159
- throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
1160
- }
1161
- async function openCodexCatalogTerminal(params) {
1162
- const title = `codex resume ${params.threadId.slice(0, 8)}…`;
1163
- if (params.hostId === "gateway:local" || params.hostId.startsWith(`gateway:local:`)) {
1164
- const record = await requireCatalogEligibleThread(params.control, params.threadId);
1165
- const resolution = resolveLocalCodexTerminalResolution();
1166
- if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable");
1167
- return {
1168
- kind: "local",
1169
- argv: [
1170
- resolution.executable,
1171
- "resume",
1172
- params.threadId
1173
- ],
1174
- ...record.cwd ? { cwd: record.cwd } : {},
1175
- env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
1176
- ...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
1177
- title
1178
- };
1179
- }
1180
- if (!params.hostId.startsWith("node:")) throw new CatalogParamsError("hostId is invalid");
1181
- const nodeId = params.hostId.slice(5);
1182
- if (!(await params.api.runtime.nodes.list()).nodes.find((candidate) => {
1183
- const commands = candidate.invocableCommands ?? candidate.commands;
1184
- return candidate.nodeId === nodeId && candidate.connected === true && commands?.includes("codex.appServer.threads.list.v1") === true && commands.includes("codex.terminal.resume.v1");
1185
- })) throw new CatalogParamsError("paired-node Codex terminal is unavailable");
1186
- const record = await resolveNodeCatalogEligibleThread({
1187
- agentId: params.agentId,
1188
- runtime: params.api.runtime,
1189
- nodeId,
1190
- threadId: params.threadId,
1191
- parseCatalogPage: params.parseCatalogPage
1192
- });
1193
- return {
1194
- kind: "node",
1195
- nodeId,
1196
- command: CODEX_TERMINAL_RESUME_COMMAND,
1197
- paramsJSON: JSON.stringify({
1198
- agentId: params.agentId,
1199
- threadId: params.threadId
1200
- }),
1201
- ...record.cwd ? { cwd: record.cwd } : {},
1202
- title
1203
- };
1204
- }
1205
- async function startCodexCatalogTerminal(params) {
1206
- if (params.nodeId) throw new CatalogParamsError("Paired-node Codex terminal start is unavailable; omit hostId to start on the gateway host");
1207
- const resolution = resolveLocalCodexTerminalResolution();
1208
- if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable; install Codex or add codex to PATH, then try again");
1209
- return {
1210
- kind: "local",
1211
- argv: [resolution.executable, ...params.initialMessage !== void 0 ? ["--", params.initialMessage] : []],
1212
- cwd: params.cwd,
1213
- env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
1214
- ...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
1215
- title: "codex"
1216
- };
1217
- }
1218
- //#endregion
1219
1237
  //#region extensions/codex/src/session-catalog-adoption.ts
1220
1238
  const CODEX_SUPERVISION_SESSION_KEY_PREFIX = "harness:codex:supervision:";
1221
1239
  const boundCatalogSessionId = (value) => boundedCatalogString(value, 256);
@@ -1278,54 +1296,26 @@ async function findAdoptedSessionEntry(params) {
1278
1296
  const adopted = await listAdoptedSessionEntries(params);
1279
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);
1280
1298
  }
1281
- async function clearCreatedAdoptionBinding(params) {
1282
- let cleared = false;
1283
- let clearError;
1284
- try {
1285
- cleared = await params.bindingStore.mutate(params.identity, {
1286
- kind: "clear",
1287
- threadId: params.sourceThreadId,
1288
- expectedPendingSupervisionBranch: params.expectedPending
1289
- });
1290
- } catch (error) {
1291
- clearError = error;
1292
- }
1293
- if (cleared) return;
1294
- let current;
1295
- try {
1296
- current = await params.bindingStore.read(params.identity);
1297
- } catch (readError) {
1298
- throw new AggregateError([
1299
- params.cause,
1300
- ...clearError ? [clearError] : [],
1301
- readError
1302
- ], `OpenClaw session creation failed and the Codex binding could not be verified for ${params.sourceThreadId}`, { cause: readError });
1303
- }
1304
- if (!matchesPendingSupervisionOwner(current, params.expectedPending)) return;
1305
- 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 });
1306
- }
1307
1299
  function matchesPendingAdoptionBinding(binding, expected) {
1308
1300
  const historyCoveredThrough = binding?.historyCoveredThrough;
1309
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));
1310
1302
  }
1311
- function matchesPendingSupervisionOwner(binding, expected) {
1312
- const pending = binding?.pendingSupervisionBranch;
1313
- const cleanupThreadIds = pending?.cleanupThreadIds ?? [];
1314
- const expectedCleanupThreadIds = expected.cleanupThreadIds ?? [];
1315
- 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]);
1316
- }
1317
1303
  async function ensurePendingAdoptionBinding(params) {
1318
1304
  const pending = {
1319
1305
  sourceThreadId: params.sourceThreadId,
1320
1306
  connectionFingerprint: params.connectionFingerprint,
1321
1307
  ...params.lastTurnId ? { lastTurnId: params.lastTurnId } : {}
1322
1308
  };
1323
- if (!await reclaimCurrentCodexSessionGeneration({
1309
+ const ownsGeneration = await reclaimCurrentCodexSessionGeneration({
1310
+ assertCurrent: params.initialization.assertCurrent,
1324
1311
  bindingStore: params.bindingStore,
1325
1312
  identity: params.identity,
1326
1313
  config: params.config
1327
- })) 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}`);
1328
1317
  const existing = await params.bindingStore.read(params.identity);
1318
+ params.initialization.assertCurrent();
1329
1319
  if (existing) {
1330
1320
  if (matchesPendingAdoptionBinding(existing, params)) return;
1331
1321
  throw new Error(`OpenClaw session is already bound to Codex thread ${existing.threadId}`);
@@ -1340,19 +1330,7 @@ async function ensurePendingAdoptionBinding(params) {
1340
1330
  preserveNativeModel: true,
1341
1331
  pendingSupervisionBranch: pending
1342
1332
  };
1343
- let stored;
1344
- try {
1345
- stored = await params.bindingStore.mutate(params.identity, {
1346
- kind: "set",
1347
- if: { kind: "absent" },
1348
- binding
1349
- });
1350
- } catch (error) {
1351
- if (matchesPendingAdoptionBinding(await params.bindingStore.read(params.identity), params)) return;
1352
- throw error;
1353
- }
1354
- if (stored) return;
1355
- 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);
1356
1334
  }
1357
1335
  async function createOrReuseAdoptedSession(params) {
1358
1336
  const runtime = params.api.runtime;
@@ -1363,8 +1341,6 @@ async function createOrReuseAdoptedSession(params) {
1363
1341
  };
1364
1342
  const existing = await findAdoptedSessionEntry(lookup);
1365
1343
  if (existing) return existing;
1366
- let createdBindingIdentity;
1367
- let createdPendingBinding;
1368
1344
  try {
1369
1345
  const spawnedCwd = params.sourceThread.cwd?.trim() || void 0;
1370
1346
  const pendingLastTurnId = codexLastTerminalTurnId(params.sourceThread, boundCatalogSessionId);
@@ -1374,9 +1350,11 @@ async function createOrReuseAdoptedSession(params) {
1374
1350
  };
1375
1351
  const created = await createImportedCodexSession({
1376
1352
  runtime: params.api.runtime,
1353
+ bindingStore: params.bindingStore,
1377
1354
  config: params.config,
1378
1355
  key: adoptionSessionKey(params.sourceThread.id, params.sourceHomeId),
1379
1356
  agentId: params.agentId,
1357
+ displayName: params.sourceThread.name ?? void 0,
1380
1358
  thread: params.sourceThread,
1381
1359
  throughTurnId: pendingLastTurnId ?? null,
1382
1360
  recoverMatchingInitialEntry: true,
@@ -1389,21 +1367,17 @@ async function createOrReuseAdoptedSession(params) {
1389
1367
  modelLocked: true
1390
1368
  } } }
1391
1369
  },
1392
- afterImport: async (entry) => {
1393
- createdBindingIdentity = sessionBindingIdentity({
1370
+ afterImport: async (entry, initialization) => {
1371
+ const identity = sessionBindingIdentity({
1394
1372
  sessionId: entry.sessionId,
1395
1373
  sessionKey: entry.key,
1396
1374
  config: params.config
1397
1375
  });
1398
- createdPendingBinding = {
1399
- sourceThreadId: params.sourceThread.id,
1400
- connectionFingerprint: params.connectionFingerprint,
1401
- ...pendingLastTurnId ? { lastTurnId: pendingLastTurnId } : {}
1402
- };
1403
1376
  await ensurePendingAdoptionBinding({
1404
1377
  bindingStore: params.bindingStore,
1405
1378
  config: params.config,
1406
- identity: createdBindingIdentity,
1379
+ identity,
1380
+ initialization,
1407
1381
  sourceThreadId: params.sourceThread.id,
1408
1382
  connectionFingerprint: params.connectionFingerprint,
1409
1383
  cwd: spawnedCwd ?? "",
@@ -1422,24 +1396,13 @@ async function createOrReuseAdoptedSession(params) {
1422
1396
  boundThreadId: params.sourceThread.id
1423
1397
  };
1424
1398
  } catch (error) {
1425
- let raced = await findAdoptedSessionEntry(lookup);
1399
+ const raced = await findAdoptedSessionEntry(lookup);
1426
1400
  if (raced) return raced;
1427
- if (createdBindingIdentity && createdPendingBinding) {
1428
- await clearCreatedAdoptionBinding({
1429
- bindingStore: params.bindingStore,
1430
- identity: createdBindingIdentity,
1431
- sourceThreadId: params.sourceThread.id,
1432
- expectedPending: createdPendingBinding,
1433
- cause: error
1434
- });
1435
- raced = await findAdoptedSessionEntry(lookup);
1436
- if (raced) return raced;
1437
- }
1438
1401
  throw error;
1439
1402
  }
1440
1403
  }
1441
1404
  async function continueLocalCodexSessionInner(params) {
1442
- await requireCatalogEligibleThread(params.control, params.threadId);
1405
+ await params.control.requireEligibleThread(params.threadId);
1443
1406
  const existing = await findAdoptedSessionEntry({
1444
1407
  ...params,
1445
1408
  runtime: params.api.runtime
@@ -1532,8 +1495,8 @@ async function archiveLocalCodexSession(params) {
1532
1495
  return await runSessionActionExclusive(sessionCatalogAdoptedSourceKey(params.hostId ?? "gateway:local", params.threadId), async () => {
1533
1496
  return await params.bindingStore.withThreadArchiveFence(async () => {
1534
1497
  const run = async (control) => {
1535
- await requireCatalogEligibleThread(control, params.threadId);
1536
1498
  await assertNoPendingSupervisionBranch(params);
1499
+ await control.requireEligibleThread(params.threadId);
1537
1500
  const thread = await control.readThread(params.threadId, false);
1538
1501
  if (thread.id !== params.threadId) throw new Error("Codex app-server returned a different thread than requested");
1539
1502
  requireIdleThread(thread, "archive");
@@ -1607,7 +1570,8 @@ async function listPairedNode(params) {
1607
1570
  label: nodeLabel(params.node),
1608
1571
  kind: "node",
1609
1572
  nodeId: params.node.nodeId,
1610
- canContinueCodex: canContinueCodexOnNode(params.node)
1573
+ canContinueCodex: canContinueCodexOnNode(params.node),
1574
+ ...params.terminalCapabilities
1611
1575
  };
1612
1576
  if (params.node.connected !== true) {
1613
1577
  const host = {
@@ -1622,6 +1586,15 @@ async function listPairedNode(params) {
1622
1586
  params.onHost?.(host);
1623
1587
  return host;
1624
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
+ }
1625
1598
  const eventualHost = Promise.resolve().then(async () => {
1626
1599
  const page = filterCatalogPageByTitle(parseCatalogPage(unwrapNodeInvokePayload(await params.runtime.nodes.invoke({
1627
1600
  nodeId: params.node.nodeId,
@@ -1800,7 +1773,7 @@ async function continueNodeCodexSession(params) {
1800
1773
  if (params.clientScopes?.includes("operator.admin") !== true) throw new CatalogParamsError("continuing a paired-node Codex session requires operator.admin");
1801
1774
  const nodeId = params.hostId.slice(5).trim();
1802
1775
  if (!nodeId || params.hostId !== `node:${nodeId}`) throw new CatalogParamsError("Codex session catalog hostId is invalid");
1803
- const agentId = resolveSessionAgentIds({
1776
+ const agentId = resolveSessionAgentIdsStrict({
1804
1777
  config: params.config,
1805
1778
  agentId: params.agentId
1806
1779
  }).sessionAgentId;
@@ -1817,6 +1790,194 @@ async function continueNodeCodexSession(params) {
1817
1790
  });
1818
1791
  }
1819
1792
  //#endregion
1793
+ //#region extensions/codex/src/session-catalog-terminal.ts
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
+ }
1822
+ function resolveCodexCatalogTerminalHome(sources) {
1823
+ const runtimeConfig = sources.getRuntimeConfig();
1824
+ if (!runtimeConfig) throw new Error("OpenClaw runtime config is unavailable");
1825
+ const agentDir = sources.source?.agentDir ?? (sources.agentId ? resolveAgentDir$1(runtimeConfig, sources.agentId) : resolveDefaultAgentDir(runtimeConfig));
1826
+ const startOptions = sources.source?.appServer.start ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig: sources.getPluginConfig() }).start;
1827
+ return resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
1828
+ }
1829
+ function resolveLocalCodexTerminalExecutable(env = process.env) {
1830
+ return resolveLocalCodexTerminalResolution(env)?.executable;
1831
+ }
1832
+ function resolveLocalCodexTerminalResolution(env = process.env) {
1833
+ return resolveNodeHostExecutable("codex", {
1834
+ env,
1835
+ pathEnv: env.PATH ?? env.Path ?? "",
1836
+ strategy: "fallback"
1837
+ });
1838
+ }
1839
+ function codexNodeTerminalCapability(node) {
1840
+ const commands = node.invocableCommands ?? node.commands;
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
+ };
1845
+ }
1846
+ function createCodexTerminalNodeHostCommand(bindRequest, configSources) {
1847
+ return {
1848
+ command: CODEX_TERMINAL_RESUME_COMMAND,
1849
+ cap: CODEX_APP_SERVER_THREADS_CAPABILITY,
1850
+ dangerous: false,
1851
+ duplex: true,
1852
+ isAvailable: ({ env }) => Boolean(resolveNodeHostExecutable("codex", {
1853
+ env,
1854
+ pathEnv: env.PATH ?? env.Path ?? "",
1855
+ strategy: "direct"
1856
+ })),
1857
+ handle: async (paramsJSON, io) => {
1858
+ if (!io) throw new Error("Codex terminal command requires duplex transport");
1859
+ const request = bindRequest(paramsJSON);
1860
+ const resume = decodeNodePtyResumeParams(request.paramsJSON, (value) => {
1861
+ if (typeof value !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(value)) throw new CatalogParamsError("threadId must be a UUID");
1862
+ return value;
1863
+ });
1864
+ const record = await request.control.requireEligibleThread(resume.threadId);
1865
+ const resolution = resolveNodeHostExecutable("codex", {
1866
+ env: process.env,
1867
+ pathEnv: process.env.PATH ?? process.env.Path ?? "",
1868
+ strategy: "direct"
1869
+ });
1870
+ if (!resolution) throw new Error("Codex CLI is unavailable");
1871
+ return JSON.stringify(await runNodePtyCommand({
1872
+ file: resolution.executable,
1873
+ args: ["resume", resume.threadId],
1874
+ ...record.cwd ? { cwd: record.cwd } : {},
1875
+ env: { CODEX_HOME: resolveCodexCatalogTerminalHome({
1876
+ ...configSources,
1877
+ agentId: request.agentId
1878
+ }) },
1879
+ cols: resume.cols,
1880
+ rows: resume.rows
1881
+ }, io));
1882
+ }
1883
+ };
1884
+ }
1885
+ async function resolveNodeCatalogEligibleThread(params) {
1886
+ let cursor;
1887
+ const seenCursors = /* @__PURE__ */ new Set();
1888
+ for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
1889
+ const raw = await params.runtime.nodes.invoke({
1890
+ nodeId: params.nodeId,
1891
+ command: CODEX_APP_SERVER_THREADS_LIST_COMMAND,
1892
+ params: {
1893
+ agentId: params.agentId,
1894
+ limit: 100,
1895
+ ...cursor ? { cursor } : {}
1896
+ },
1897
+ timeoutMs: NODE_INVOKE_TIMEOUT_MS,
1898
+ scopes: ["operator.write"]
1899
+ });
1900
+ const page = params.parseCatalogPage(unwrapNodeInvokePayload(raw));
1901
+ const record = page.sessions.find((candidate) => candidate.threadId === params.threadId);
1902
+ if (record) {
1903
+ if (isInteractiveThreadSource(record.source)) return record;
1904
+ break;
1905
+ }
1906
+ const nextCursor = page.nextCursor?.trim();
1907
+ if (!nextCursor || seenCursors.has(nextCursor)) break;
1908
+ seenCursors.add(nextCursor);
1909
+ cursor = nextCursor;
1910
+ }
1911
+ throw new CatalogParamsError("Codex session is not a non-archived interactive Codex session");
1912
+ }
1913
+ async function openCodexCatalogTerminal(params) {
1914
+ const title = `codex resume ${params.threadId.slice(0, 8)}…`;
1915
+ if (params.hostId === "gateway:local" || params.hostId.startsWith(`gateway:local:`)) {
1916
+ const record = await params.control.requireEligibleThread(params.threadId);
1917
+ const resolution = resolveLocalCodexTerminalResolution();
1918
+ if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable");
1919
+ return {
1920
+ kind: "local",
1921
+ argv: [
1922
+ resolution.executable,
1923
+ "resume",
1924
+ params.threadId
1925
+ ],
1926
+ ...record.cwd ? { cwd: record.cwd } : {},
1927
+ env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
1928
+ ...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
1929
+ title
1930
+ };
1931
+ }
1932
+ if (!params.hostId.startsWith("node:")) throw new CatalogParamsError("hostId is invalid");
1933
+ const nodeId = params.hostId.slice(5);
1934
+ if (!(await params.api.runtime.nodes.list()).nodes.find((candidate) => {
1935
+ const commands = candidate.invocableCommands ?? candidate.commands;
1936
+ return candidate.nodeId === nodeId && candidate.connected === true && commands?.includes("codex.appServer.threads.list.v1") === true && commands.includes("codex.terminal.resume.v1");
1937
+ })) throw new CatalogParamsError("paired-node Codex terminal is unavailable");
1938
+ const record = await resolveNodeCatalogEligibleThread({
1939
+ agentId: params.agentId,
1940
+ runtime: params.api.runtime,
1941
+ nodeId,
1942
+ threadId: params.threadId,
1943
+ parseCatalogPage: params.parseCatalogPage
1944
+ });
1945
+ return {
1946
+ kind: "node",
1947
+ nodeId,
1948
+ command: CODEX_TERMINAL_RESUME_COMMAND,
1949
+ paramsJSON: JSON.stringify({
1950
+ agentId: params.agentId,
1951
+ threadId: params.threadId
1952
+ }),
1953
+ ...record.cwd ? { cwd: record.cwd } : {},
1954
+ title
1955
+ };
1956
+ }
1957
+ async function startCodexCatalogTerminal(params) {
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
+ };
1969
+ const resolution = resolveLocalCodexTerminalResolution();
1970
+ if (!resolution) throw new CatalogParamsError("Codex CLI is unavailable; install Codex or add codex to PATH, then try again");
1971
+ return {
1972
+ kind: "local",
1973
+ argv: [resolution.executable, ...params.initialMessage !== void 0 ? ["--", params.initialMessage] : []],
1974
+ cwd: params.cwd,
1975
+ env: { CODEX_HOME: resolveCodexCatalogTerminalHome(params) },
1976
+ ...resolution.pathEnv ? { pathEnv: resolution.pathEnv } : {},
1977
+ title: "codex"
1978
+ };
1979
+ }
1980
+ //#endregion
1820
1981
  //#region extensions/codex/src/session-catalog-listing.ts
1821
1982
  async function listVisiblePage(params) {
1822
1983
  const excluded = params.excludedThreadIds;
@@ -1904,7 +2065,7 @@ async function listGatewayHost(params) {
1904
2065
  }
1905
2066
  /** Lists Gateway-local and paired-node Codex sessions with per-host failures. */
1906
2067
  async function listCodexSessionCatalog(params) {
1907
- const agentId = resolveSessionAgentIds({
2068
+ const agentId = resolveSessionAgentIdsStrict({
1908
2069
  config: params.config ?? {},
1909
2070
  agentId: params.agentId
1910
2071
  }).sessionAgentId;
@@ -1939,13 +2100,14 @@ async function listCodexSessionCatalog(params) {
1939
2100
  if (!(!requestedHostIds || query.hostIds?.some((hostId) => hostId.startsWith("node:")))) return { hosts: await Promise.all(localHosts) };
1940
2101
  let nodes;
1941
2102
  try {
1942
- 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);
1943
2104
  } catch (error) {
1944
2105
  const registryHost = {
1945
2106
  hostId: "node:registry",
1946
2107
  label: "Paired nodes",
1947
2108
  kind: "node",
1948
2109
  connected: false,
2110
+ canStartTerminal: false,
1949
2111
  sessions: [],
1950
2112
  error: catalogError("NODE_LIST_FAILED", error)
1951
2113
  };
@@ -1958,17 +2120,15 @@ async function listCodexSessionCatalog(params) {
1958
2120
  runtime: params.runtime,
1959
2121
  sessionEntries: params.sessionEntries
1960
2122
  });
1961
- const nodeHosts = nodes.toSorted(compareNodeLabels).map(async (node) => {
1962
- const host = await listPairedNode({
1963
- agentId,
1964
- runtime: params.runtime,
1965
- node,
1966
- query,
1967
- adoptedSessions: adoptedNodeSessions,
1968
- ...params.onHost ? { onHost: params.onHost } : {}
1969
- });
1970
- return Object.assign(host, codexNodeTerminalCapability(node));
1971
- });
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
+ }));
1972
2132
  return { hosts: await Promise.all([...localHosts, ...nodeHosts]) };
1973
2133
  }
1974
2134
  /** Builds the node-local read-only Codex app-server catalog command. */
@@ -1978,7 +2138,7 @@ function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources
1978
2138
  if (!isRecord(parsed)) throw new CatalogParamsError("Codex session catalog parameters must be an object");
1979
2139
  const requestedAgentId = readBoundedOptionalString(parsed, "agentId", 256);
1980
2140
  const config = configSources.getRuntimeConfig() ?? {};
1981
- const agentId = resolveSessionAgentIds({
2141
+ const agentId = resolveSessionAgentIdsStrict({
1982
2142
  config,
1983
2143
  agentId: requestedAgentId
1984
2144
  }).sessionAgentId;
@@ -2035,7 +2195,7 @@ function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources
2035
2195
  const request = bindRequest(paramsJSON);
2036
2196
  const action = readNodeTranscriptParams(request.params);
2037
2197
  try {
2038
- await requireCatalogEligibleThread(request.control, action.threadId);
2198
+ await request.control.requireEligibleThread(action.threadId);
2039
2199
  const page = parseTranscriptPage(await request.control.listTurnPage({
2040
2200
  threadId: action.threadId,
2041
2201
  limit: action.limit,
@@ -2050,7 +2210,8 @@ function createCodexSessionCatalogNodeHostCommands(controlFactory, configSources
2050
2210
  }
2051
2211
  }
2052
2212
  },
2053
- createCodexTerminalNodeHostCommand(bindRequest, configSources)
2213
+ createCodexTerminalNodeHostCommand(bindRequest, configSources),
2214
+ createCodexTerminalStartNodeHostCommand()
2054
2215
  ];
2055
2216
  }
2056
2217
  function readNodeTranscriptParams(value) {
@@ -2080,7 +2241,7 @@ function flattenTranscriptPageDesc(page) {
2080
2241
  /** Reads the persisted transcript for a Gateway-local or paired-node Codex session. */
2081
2242
  async function readCodexSessionTranscript(params) {
2082
2243
  if (params.source || params.hostId === "gateway:local") {
2083
- await requireCatalogEligibleThread(params.control, params.threadId);
2244
+ await params.control.requireEligibleThread(params.threadId);
2084
2245
  const listParams = {
2085
2246
  threadId: params.threadId,
2086
2247
  limit: params.limit,
@@ -2146,7 +2307,7 @@ function toGenericTranscriptItem(item) {
2146
2307
  const fallback = item.title ?? item.name ?? item.tool ?? item.command ?? item.query ?? void 0;
2147
2308
  const resultText = item.aggregatedOutput || (item.result === void 0 ? void 0 : JSON.stringify(item.result, null, 2));
2148
2309
  const changesText = Array.isArray(item.changes) ? item.changes.map((change) => `${change.kind}: ${change.path}`).join("\n") || void 0 : void 0;
2149
- const text = item.text || resultText || changesText || fallback;
2310
+ const text = item.type === "userMessage" ? projectCodexUserItemText(item) : item.text || resultText || changesText || fallback;
2150
2311
  return {
2151
2312
  id: item.id,
2152
2313
  type,
@@ -2311,7 +2472,7 @@ function existingCatalogHomeCandidates(value, label) {
2311
2472
  /** Resolves every local Codex store the operator already owns, without path disclosure. */
2312
2473
  function resolveCodexCatalogHomes(params) {
2313
2474
  const { config, env, ownerAgentId, pluginConfig } = params;
2314
- const ownerAgentDir = resolveAgentDir(config, ownerAgentId, env);
2475
+ const ownerAgentDir = resolveAgentDir$1(config, ownerAgentId, env);
2315
2476
  const configuredHomes = readCodexPluginConfig(pluginConfig).sessionCatalog?.homes ?? [];
2316
2477
  const base = resolveCodexSupervisionAppServerRuntimeOptions({
2317
2478
  pluginConfig,
@@ -2334,7 +2495,7 @@ function resolveCodexCatalogHomes(params) {
2334
2495
  usesProcessHomeFallback: !processHomeConfigured
2335
2496
  });
2336
2497
  const agentIds = listAgentIds(config).toSorted((left, right) => left === ownerAgentId ? -1 : right === ownerAgentId ? 1 : left.localeCompare(right));
2337
- candidates.push(...agentIds.flatMap((agentId) => existingCatalogHomeCandidates(resolveCodexAppServerHomeDir(resolveAgentDir(config, agentId, env)), agentId)), ...configuredHomes.flatMap((entry) => {
2498
+ candidates.push(...agentIds.flatMap((agentId) => existingCatalogHomeCandidates(resolveCodexAppServerHomeDir(resolveAgentDir$1(config, agentId, env)), agentId)), ...configuredHomes.flatMap((entry) => {
2338
2499
  const { path: home, label } = typeof entry === "string" ? { path: entry } : entry;
2339
2500
  return existingCatalogHomeCandidates(home, label);
2340
2501
  }));
@@ -2400,96 +2561,6 @@ function createCodexCatalogHomeResolver(params) {
2400
2561
  } };
2401
2562
  }
2402
2563
  //#endregion
2403
- //#region extensions/codex/src/session-catalog-provenance.ts
2404
- const MAX_SESSION_META_BYTES = 1024 * 1024;
2405
- const SESSION_META_READ_CHUNK_BYTES = 64 * 1024;
2406
- const MAX_PROVENANCE_CACHE_ENTRIES = 2e4;
2407
- const provenanceByPath = /* @__PURE__ */ new Map();
2408
- function cacheProvenance(key, value) {
2409
- provenanceByPath.delete(key);
2410
- provenanceByPath.set(key, value);
2411
- while (provenanceByPath.size > MAX_PROVENANCE_CACHE_ENTRIES) {
2412
- const oldest = provenanceByPath.keys().next().value;
2413
- if (oldest === void 0) break;
2414
- provenanceByPath.delete(oldest);
2415
- }
2416
- }
2417
- /** Undefined means the metadata line is not durable enough to cache yet. */
2418
- async function readOpenClawOriginator(sessionsRoot, rolloutPath, threadId) {
2419
- let safeRoot;
2420
- try {
2421
- safeRoot = await root(sessionsRoot, {
2422
- hardlinks: "reject",
2423
- maxBytes: Number.MAX_SAFE_INTEGER,
2424
- symlinks: "reject"
2425
- });
2426
- } catch {
2427
- return;
2428
- }
2429
- const candidates = rolloutPath.endsWith(".zst") ? [rolloutPath, rolloutPath.slice(0, -4)] : [rolloutPath, `${rolloutPath}.zst`];
2430
- for (const candidate of candidates) {
2431
- let opened;
2432
- try {
2433
- opened = await safeRoot.open(path.relative(sessionsRoot, candidate));
2434
- } catch {
2435
- continue;
2436
- }
2437
- const input = opened.handle.createReadStream({
2438
- autoClose: false,
2439
- highWaterMark: SESSION_META_READ_CHUNK_BYTES
2440
- });
2441
- const reader = candidate.endsWith(".zst") ? input.pipe(createZstdDecompress()) : input;
2442
- try {
2443
- const chunks = [];
2444
- let bytesReadTotal = 0;
2445
- let line;
2446
- for await (const value of reader) {
2447
- const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
2448
- const remaining = MAX_SESSION_META_BYTES - bytesReadTotal;
2449
- if (remaining <= 0) break;
2450
- const bounded = chunk.subarray(0, remaining);
2451
- bytesReadTotal += bounded.length;
2452
- const newline = bounded.indexOf(10);
2453
- chunks.push(newline >= 0 ? bounded.subarray(0, newline) : bounded);
2454
- if (newline >= 0) {
2455
- line = Buffer.concat(chunks).toString("utf8");
2456
- break;
2457
- }
2458
- }
2459
- if (!line) continue;
2460
- let parsed;
2461
- try {
2462
- parsed = JSON.parse(line);
2463
- } catch {
2464
- continue;
2465
- }
2466
- if (!isRecord(parsed) || parsed.type !== "session_meta" || !isRecord(parsed.payload)) return false;
2467
- const payload = parsed.payload;
2468
- return (payload.id ?? payload.session_id) === threadId && payload.originator === "openclaw";
2469
- } catch {
2470
- continue;
2471
- } finally {
2472
- reader.destroy();
2473
- input.destroy();
2474
- await opened.handle.close().catch(() => void 0);
2475
- }
2476
- }
2477
- }
2478
- /**
2479
- * Codex 0.147 reports OpenClaw app-server rollouts as `vscode`, so the rollout's
2480
- * immutable session metadata is the authoritative historical provenance.
2481
- */
2482
- async function isOpenClawManagedCodexThread(thread, localSessionsRoot) {
2483
- const rolloutPath = typeof thread.path === "string" ? thread.path.trim() : "";
2484
- if (!localSessionsRoot || !rolloutPath) return false;
2485
- const cacheKey = `${localSessionsRoot}\0${rolloutPath}`;
2486
- const cached = provenanceByPath.get(cacheKey);
2487
- if (cached !== void 0) return cached;
2488
- const managed = await readOpenClawOriginator(localSessionsRoot, rolloutPath, thread.id);
2489
- if (managed !== void 0) cacheProvenance(cacheKey, managed);
2490
- return managed ?? false;
2491
- }
2492
- //#endregion
2493
2564
  //#region extensions/codex/src/session-catalog-control.ts
2494
2565
  const CODEX_SESSION_CATALOG_LIST_TTL_MS = 32e3;
2495
2566
  const CODEX_SESSION_CATALOG_LIST_CACHE_MAX_ENTRIES = 32;
@@ -2508,21 +2579,75 @@ function createCodexCatalogRequestSnapshot(requestTimeoutMs, request) {
2508
2579
  requestTimeoutMs,
2509
2580
  listThreads: (params, timeoutMs) => request(CODEX_CONTROL_METHODS.listThreads, params, timeoutMs),
2510
2581
  listThreadTurns: (params) => request(CODEX_CONTROL_METHODS.listThreadTurns, params),
2511
- forkThread: (params) => request(CODEX_CONTROL_METHODS.forkThread, assertCodexThreadForkParams(params)),
2512
- readThread: async (threadId, includeTurns) => (await request(CODEX_CONTROL_METHODS.readThread, {
2582
+ forkThread: (params, assertCurrent) => request(CODEX_CONTROL_METHODS.forkThread, assertCodexThreadForkParams(params), void 0, assertCurrent),
2583
+ readThread: async (threadId, includeTurns, timeoutMs) => (await request(CODEX_CONTROL_METHODS.readThread, {
2513
2584
  threadId,
2514
2585
  includeTurns
2515
- })).thread,
2516
- archiveThread: async (threadId) => {
2517
- await request(CODEX_CONTROL_METHODS.archiveThread, { threadId });
2586
+ }, timeoutMs)).thread,
2587
+ archiveThread: async (threadId, assertCurrent) => {
2588
+ await request(CODEX_CONTROL_METHODS.archiveThread, { threadId }, void 0, assertCurrent);
2518
2589
  }
2519
2590
  };
2520
2591
  }
2521
2592
  function createCodexSessionCatalogControlFromRequests(params) {
2522
2593
  return {
2594
+ forkContext: params.forkContext,
2523
2595
  ...params.clientId ? { clientId: params.clientId } : {},
2524
2596
  ...params.connectionFingerprint ? { connectionFingerprint: params.connectionFingerprint } : {},
2525
2597
  withPinnedConnection: params.withPinnedConnection,
2598
+ async requireEligibleThread(threadId) {
2599
+ const requests = params.createRequestSnapshot();
2600
+ const deadline = params.now() + requests.requestTimeoutMs;
2601
+ const unverified = () => new CatalogParamsError("Codex session eligibility could not be verified. Refresh the catalog and verify the session in its native Codex home before retrying.");
2602
+ const remaining = () => {
2603
+ const timeoutMs = Math.ceil(deadline - params.now());
2604
+ if (timeoutMs <= 0) throw unverified();
2605
+ return timeoutMs;
2606
+ };
2607
+ const verify = async () => {
2608
+ if (params.sourceHomeId && await params.managedThreads?.has(params.sourceHomeId, threadId)) throw unverified();
2609
+ const root = params.localSessionsRoot;
2610
+ const thread = root ? await requests.readThread(threadId, false, remaining()) : void 0;
2611
+ if (root && (!thread || thread.id !== threadId || !isInteractiveThreadSource(thread.source))) throw unverified();
2612
+ let cursor;
2613
+ const seenCursors = /* @__PURE__ */ new Set();
2614
+ for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
2615
+ const page = await requests.listThreads({
2616
+ archived: false,
2617
+ limit: 100,
2618
+ modelProviders: [],
2619
+ sortKey: root ? "recency_at" : "updated_at",
2620
+ sortDirection: "desc",
2621
+ ...root ? {
2622
+ useStateDbOnly: true,
2623
+ ...thread?.cwd ? { cwd: thread.cwd } : {}
2624
+ } : {},
2625
+ ...cursor ? { cursor } : {}
2626
+ }, remaining());
2627
+ remaining();
2628
+ const candidate = page.data.find((value) => value.id === threadId);
2629
+ if (candidate) {
2630
+ if (!isInteractiveThreadSource(candidate.source)) throw unverified();
2631
+ if (root && thread) {
2632
+ const rolloutPath = thread.path;
2633
+ if (!rolloutPath || !candidate.path || rolloutPath.replace(/\.zst$/u, "") !== candidate.path.replace(/\.zst$/u, "")) throw unverified();
2634
+ const metadata = await readCodexSessionMeta(root, rolloutPath, threadId);
2635
+ remaining();
2636
+ if (!metadata || !isInteractiveThreadSource(metadata.source) || metadata.originator === "openclaw") throw unverified();
2637
+ return thread;
2638
+ }
2639
+ return candidate;
2640
+ }
2641
+ const nextCursor = readControlCursor(page.nextCursor, "next response");
2642
+ if (!nextCursor || seenCursors.has(nextCursor)) throw unverified();
2643
+ seenCursors.add(nextCursor);
2644
+ cursor = nextCursor;
2645
+ }
2646
+ throw unverified();
2647
+ };
2648
+ return await withTimeout(verify(), requests.requestTimeoutMs, "Codex session eligibility could not be verified", unverified);
2649
+ },
2650
+ retireConnection: params.retireConnection,
2526
2651
  async listPage(pageParams) {
2527
2652
  const limit = normalizeLimit(pageParams.limit, "limit");
2528
2653
  const search = pageParams.searchTerm?.trim().toLocaleLowerCase() || void 0;
@@ -2584,11 +2709,11 @@ function createCodexSessionCatalogControlFromRequests(params) {
2584
2709
  async listTurnPage(listParams) {
2585
2710
  return await params.createRequestSnapshot().listThreadTurns(listParams);
2586
2711
  },
2587
- async forkThread(forkParams) {
2588
- return await params.createRequestSnapshot().forkThread(forkParams);
2712
+ async forkThread(forkParams, assertCurrent) {
2713
+ return await params.createRequestSnapshot().forkThread(forkParams, assertCurrent);
2589
2714
  },
2590
- async archiveThread(threadId) {
2591
- await params.createRequestSnapshot().archiveThread(threadId);
2715
+ async archiveThread(threadId, assertCurrent) {
2716
+ await params.createRequestSnapshot().archiveThread(threadId, assertCurrent);
2592
2717
  }
2593
2718
  };
2594
2719
  }
@@ -2606,7 +2731,7 @@ function createCodexSessionCatalogControl(params) {
2606
2731
  const catalogPagesByConfig = /* @__PURE__ */ new WeakMap();
2607
2732
  const resolveRequestOptions = (startOptions, agentId, source) => {
2608
2733
  const runtimeConfig = params.getRuntimeConfig();
2609
- const agentDir = source?.agentDir ?? resolveAgentDir(runtimeConfig ?? {}, agentId);
2734
+ const agentDir = source?.agentDir ?? resolveAgentDir$1(runtimeConfig ?? {}, agentId);
2610
2735
  const resolvedStartOptions = source?.appServer.start ?? startOptions;
2611
2736
  if (!runtimeConfig) return {
2612
2737
  agentDir,
@@ -2633,8 +2758,9 @@ function createCodexSessionCatalogControl(params) {
2633
2758
  const pluginConfig = getPluginConfig();
2634
2759
  const runtime = source?.appServer ?? resolveCodexSupervisionAppServerRuntimeOptions({ pluginConfig });
2635
2760
  const requestOptions = resolveRequestOptions(runtime.start, agentId, source);
2636
- 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, {
2637
2762
  ...requestOptions,
2763
+ assertCurrent,
2638
2764
  ...timeoutMs === void 0 ? {} : { timeoutMs }
2639
2765
  }));
2640
2766
  };
@@ -2650,18 +2776,31 @@ function createCodexSessionCatalogControl(params) {
2650
2776
  timeoutMs: runtime.requestTimeoutMs
2651
2777
  });
2652
2778
  try {
2653
- const requests = createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs) => await requestCodexAppServerClientJson({
2779
+ const requests = createCodexCatalogRequestSnapshot(runtime.requestTimeoutMs, async (method, requestParams, timeoutMs, assertCurrent) => await requestCodexAppServerClientJson({
2654
2780
  client,
2655
2781
  method,
2656
2782
  requestParams,
2657
2783
  config: runtimeConfig,
2658
- timeoutMs: timeoutMs ?? runtime.requestTimeoutMs
2784
+ timeoutMs: timeoutMs ?? runtime.requestTimeoutMs,
2785
+ assertCurrent
2659
2786
  }));
2660
2787
  const pinnedControl = createCodexSessionCatalogControlFromRequests({
2788
+ forkContext: {
2789
+ client,
2790
+ appServer: runtime,
2791
+ pluginConfig,
2792
+ agentDir,
2793
+ localSessionsRoot: source?.localSessionsRoot
2794
+ },
2661
2795
  clientId: resolveCodexAppServerClientInstanceId(client),
2796
+ retireConnection: () => {
2797
+ retireSharedCodexAppServerClientIfCurrent(client);
2798
+ },
2662
2799
  connectionFingerprint: buildCodexAppServerConnectionFingerprint(runtime, agentDir),
2663
2800
  createRequestSnapshot: () => requests,
2664
2801
  ...source?.localSessionsRoot ? { localSessionsRoot: source.localSessionsRoot } : {},
2802
+ sourceHomeId: source?.sourceHomeId,
2803
+ managedThreads: params.managedThreads,
2665
2804
  now,
2666
2805
  withPinnedConnection: async (nestedRun) => await nestedRun(pinnedControl)
2667
2806
  });
@@ -2678,6 +2817,7 @@ function createCodexSessionCatalogControl(params) {
2678
2817
  });
2679
2818
  return {
2680
2819
  ...control,
2820
+ requireEligibleThread: (threadId) => withPinnedConnection((pinned) => pinned.requireEligibleThread(threadId)),
2681
2821
  async listPage(pageParams) {
2682
2822
  const runtimeConfig = params.getRuntimeConfig();
2683
2823
  if (!runtimeConfig) return await control.listPage(pageParams);
@@ -2688,7 +2828,7 @@ function createCodexSessionCatalogControl(params) {
2688
2828
  }
2689
2829
  const key = codexCatalogPageCacheKey(pageParams, agentId, source);
2690
2830
  const cached = cache.get(key);
2691
- if (pageParams.forceRefresh !== true && cached) {
2831
+ if (cached) {
2692
2832
  cache.delete(key);
2693
2833
  cache.set(key, cached);
2694
2834
  if (cached.expiresAt > now()) return cached.value ?? await cached.page;
@@ -2719,7 +2859,7 @@ function createCodexSessionCatalogControl(params) {
2719
2859
  });
2720
2860
  else cache.delete(key);
2721
2861
  };
2722
- if (pageParams.forceRefresh !== true && staleValue) {
2862
+ if (staleValue) {
2723
2863
  page.then(settle, restore);
2724
2864
  return staleValue;
2725
2865
  }
@@ -2751,14 +2891,21 @@ function createCodexSessionCatalogNodeInvokePolicies() {
2751
2891
  commands: [
2752
2892
  CODEX_APP_SERVER_THREADS_LIST_COMMAND,
2753
2893
  CODEX_APP_SERVER_THREAD_TURNS_LIST_COMMAND,
2754
- CODEX_TERMINAL_RESUME_COMMAND
2894
+ CODEX_TERMINAL_RESUME_COMMAND,
2895
+ CODEX_TERMINAL_START_COMMAND
2755
2896
  ],
2756
2897
  defaultPlatforms: [
2757
2898
  "macos",
2758
2899
  "linux",
2759
2900
  "windows"
2760
2901
  ],
2761
- 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
+ }
2762
2909
  }];
2763
2910
  }
2764
2911
  function toGenericCatalogHost(host, localTerminalAvailable) {
@@ -2813,7 +2960,7 @@ function registerCodexSessionCatalog(params) {
2813
2960
  const homes = params.control.homesForAgent(agentId);
2814
2961
  return allowProcessHomeFallback === false ? homes.filter((home) => !home.usesProcessHomeFallback) : homes;
2815
2962
  };
2816
- const resolveRequestAgentId = (agentId) => resolveSessionAgentIds({
2963
+ const resolveRequestAgentId = (agentId) => resolveSessionAgentIdsStrict({
2817
2964
  config: params.getRuntimeConfig() ?? params.api.config,
2818
2965
  agentId
2819
2966
  }).sessionAgentId;
@@ -2848,8 +2995,11 @@ function registerCodexSessionCatalog(params) {
2848
2995
  const localTerminalAvailable = resolveLocalCodexTerminalExecutable() !== void 0;
2849
2996
  const { agentId: requestedAgentId, allowProcessHomeFallback, listNodes, onHost, sessionEntries, ...gatewayQuery } = query;
2850
2997
  const agentId = resolveRequestAgentId(requestedAgentId);
2851
- const mapHost = (host) => toGenericCatalogHost(host, localTerminalAvailable);
2852
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
+ });
2853
3003
  return (await listCodexSessionCatalog({
2854
3004
  agentId,
2855
3005
  bindingStore: params.bindingStore,
@@ -2894,7 +3044,7 @@ function registerCodexSessionCatalog(params) {
2894
3044
  if (!isLocalCodexCatalogHost(request.hostId)) throw new CatalogParamsError("Codex session catalog hostId is invalid");
2895
3045
  const { agentId, source, control } = bindLocalRequest(request);
2896
3046
  let upstreamBaseline;
2897
- return codexUpstreamContinueResult((await continueLocalCodexSession({
3047
+ const continued = await continueLocalCodexSession({
2898
3048
  agentId,
2899
3049
  api: params.api,
2900
3050
  bindingStore: params.bindingStore,
@@ -2907,7 +3057,8 @@ function registerCodexSessionCatalog(params) {
2907
3057
  onContinued: (baseline) => {
2908
3058
  upstreamBaseline = baseline;
2909
3059
  }
2910
- })).sessionKey, request.threadId, upstreamBaseline);
3060
+ });
3061
+ return codexUpstreamContinueResult(continued.sessionKey, request.threadId, upstreamBaseline);
2911
3062
  },
2912
3063
  checkUpstreamActivity: (probes, policy) => checkUpstreamActivity(probes.filter((probe) => !isLocalCodexCatalogHost(probe.hostId) || policy?.allowProcessHomeFallback !== false || catalogHomes(probe.agentId, false).some((home) => home.hostId === probe.hostId))),
2913
3064
  archive: async (request) => {
@@ -2943,11 +3094,16 @@ function registerCodexSessionCatalog(params) {
2943
3094
  });
2944
3095
  },
2945
3096
  startTerminalSession: async (request) => {
2946
- 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");
2947
3102
  return await startCodexCatalogTerminal({
2948
3103
  getPluginConfig: params.getPluginConfig,
2949
3104
  getRuntimeConfig: params.getRuntimeConfig,
2950
- ...request
3105
+ ...request,
3106
+ source
2951
3107
  });
2952
3108
  }
2953
3109
  });
@@ -2961,4 +3117,4 @@ const codexSessionCatalogRuntime = {
2961
3117
  archiveLocal: archiveLocalCodexSession
2962
3118
  };
2963
3119
  //#endregion
2964
- export { CODEX_LOCAL_SESSION_HOST_ID as a, createCodexCliSessionNodeInvokePolicies as c, resolveCodexCliSessionForBindingOnNode as d, resumeCodexCliSessionOnNode as f, createCodexSessionCatalogNodeHostCommands as i, formatCodexCliSessions as l, createCodexSessionCatalogNodeInvokePolicies as n, assertCodexArchiveDescendantsUnowned as o, createCodexSessionCatalogControl as r, createCodexCliSessionNodeHostCommands as s, codexSessionCatalogRuntime as t, listCodexCliSessionsOnNode as u };
3120
+ export { CODEX_LOCAL_SESSION_HOST_ID as a, readCodexStatusProbes as c, createCodexCliSessionNodeHostCommands as d, createCodexCliSessionNodeInvokePolicies as f, resumeCodexCliSessionOnNode as g, resolveCodexCliSessionForBindingOnNode as h, createCodexSessionCatalogNodeHostCommands as i, requestOptions as l, listCodexCliSessionsOnNode as m, createCodexSessionCatalogNodeInvokePolicies as n, assertCodexArchiveDescendantsUnowned as o, formatCodexCliSessions as p, createCodexSessionCatalogControl as r, codexControlRequest as s, codexSessionCatalogRuntime as t, safeCodexControlRequest as u };