@openclaw/codex 2026.7.1-beta.2 → 2026.7.1-beta.5

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 (39) hide show
  1. package/dist/{app-server-policy-B7hqD3Xj.js → app-server-policy-BUk0GLMy.js} +5 -5
  2. package/dist/{attempt-notifications-tqyQ_Tdn.js → attempt-notifications-BGsEIIDI.js} +5 -40
  3. package/dist/{command-handlers-C9K-KRU-.js → command-handlers-UVniG5Hl.js} +267 -232
  4. package/dist/command-rpc-BjgEPh19.js +784 -0
  5. package/dist/compact-CZzbcygx.js +603 -0
  6. package/dist/{computer-use-CpurU-G9.js → computer-use-DDeySrnb.js} +3 -3
  7. package/dist/{config-C_whBx3M.js → config-CYEDnLJ2.js} +36 -15
  8. package/dist/conversation-binding-data-BHRpYbX3.js +121 -0
  9. package/dist/doctor-contract-api.js +439 -22
  10. package/dist/harness.js +29 -10
  11. package/dist/index.js +1117 -19
  12. package/dist/{media-understanding-provider-BOeoBhEw.js → media-understanding-provider-DgqRvU2_.js} +13 -8
  13. package/dist/media-understanding-provider.js +1 -1
  14. package/dist/{models-DpzY1xpp.js → models-DDmO1zwd.js} +2 -2
  15. package/dist/notification-correlation-Bo7KB3ks.js +35 -0
  16. package/dist/{plugin-app-cache-key-BrZUG9jj.js → plugin-app-cache-key-o-AHbdaf.js} +8 -4
  17. package/dist/{protocol-dh-ETiNd.js → protocol-2POPqAY4.js} +2 -1
  18. package/dist/{protocol-validators-CJiWigAJ.js → protocol-validators-dZQ-UTOa.js} +116 -59
  19. package/dist/{provider-capabilities-wqedK49Z.js → provider-capabilities-gTCwjfmh.js} +2916 -114
  20. package/dist/{provider-nmtMDcGH.js → provider-cc_62eQE.js} +19 -10
  21. package/dist/provider.js +2 -2
  22. package/dist/{request-BQuSQF29.js → request-BcJyl8KL.js} +2 -2
  23. package/dist/{run-attempt-DikbXia_.js → run-attempt-8SPLiDlT.js} +907 -3002
  24. package/dist/{sandbox-guard-pTY2COoC.js → sandbox-guard-DA2TQfZW.js} +3 -3
  25. package/dist/session-binding-Dc03iwRF.js +595 -0
  26. package/dist/session-binding-meta-B7aEMU7g.js +6 -0
  27. package/dist/{shared-client-rR6TDJA3.js → shared-client-4ICy3U6d.js} +892 -103
  28. package/dist/{side-question-Ca9wqmd0.js → side-question-DkjXvYv7.js} +132 -140
  29. package/dist/{thread-lifecycle-D6R40plk.js → thread-lifecycle-BskXnNP-.js} +609 -444
  30. package/dist/{web-search-provider.runtime-B6eZEFmd.js → web-search-provider.runtime-B5F42P7x.js} +3 -3
  31. package/npm-shrinkwrap.json +34 -34
  32. package/openclaw.plugin.json +20 -0
  33. package/package.json +9 -6
  34. package/dist/client-IcTBKEqA.js +0 -732
  35. package/dist/client-factory-DfFu9JH_.js +0 -15
  36. package/dist/compact-CILptrPS.js +0 -278
  37. package/dist/conversation-binding-DOJtUxOy.js +0 -1570
  38. package/dist/rate-limit-cache-C7qmZ0Jh.js +0 -26
  39. package/dist/session-binding-Cm0Gg7Z1.js +0 -339
@@ -1,19 +1,21 @@
1
- import { n as isJsonObject } from "./protocol-dh-ETiNd.js";
2
- import { d as resolveCodexAppServerRuntimeOptions, o as isCodexFastServiceTier } from "./config-C_whBx3M.js";
3
- import { n as listCodexAppServerModels, t as listAllCodexAppServerModels } from "./models-DpzY1xpp.js";
4
- import { f as summarizeCodexAccountUsage } from "./provider-nmtMDcGH.js";
5
- import { l as writeCodexAppServerBinding, n as clearCodexAppServerBinding, o as readCodexAppServerBinding } from "./session-binding-Cm0Gg7Z1.js";
6
- import { i as describeControlFailure, r as CODEX_CONTROL_METHODS } from "./plugin-app-cache-key-BrZUG9jj.js";
7
- import { a as formatCodexDisplayText, c as formatList, d as formatThreads, f as readString$1, i as formatAccount, l as formatModels, o as formatCodexStatus, r as buildHelp, s as formatComputerUseStatus, u as formatSkills } from "./app-server-policy-B7hqD3Xj.js";
8
- import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-pTY2COoC.js";
9
- import { C as canMutateCodexHost, S as CODEX_NATIVE_EXECUTION_AUTH_ERROR, _ as steerCodexConversationTurn, b as readCodexConversationBindingData, d as parseCodexFastModeArg, f as parseCodexPermissionsModeArg, g as setCodexConversationPermissions, h as setCodexConversationModel, m as setCodexConversationFastMode, o as formatCodexCliSessions, p as readCodexConversationActiveTurn, r as startCodexConversationThread, u as formatPermissionsMode, v as stopCodexConversationTurn, x as resolveCodexDefaultWorkspaceDir, y as createCodexCliNodeConversationBindingData } from "./conversation-binding-DOJtUxOy.js";
10
- import { t as requestCodexAppServerJson } from "./request-BQuSQF29.js";
11
- import { n as installCodexComputerUse, r as readCodexComputerUseStatus } from "./computer-use-CpurU-G9.js";
12
- import { n as rememberCodexRateLimits } from "./rate-limit-cache-C7qmZ0Jh.js";
1
+ import { r as isJsonObject } from "./protocol-2POPqAY4.js";
2
+ import { o as isCodexFastServiceTier } from "./config-CYEDnLJ2.js";
3
+ import { n as assertCodexThreadResumeResponse } from "./protocol-validators-dZQ-UTOa.js";
4
+ import { t as listAllCodexAppServerModels } from "./models-DDmO1zwd.js";
5
+ import { p as summarizeCodexAccountUsage } from "./provider-cc_62eQE.js";
6
+ import { y as resolveCodexAppServerAuthProfileIdForAgent } from "./shared-client-4ICy3U6d.js";
7
+ import { a as reclaimCurrentCodexSessionGeneration, i as normalizeCodexAppServerBindingModelProvider, n as bindingStoreKey, o as sessionBindingIdentity } from "./session-binding-Dc03iwRF.js";
8
+ import { r as CODEX_CONTROL_METHODS } from "./plugin-app-cache-key-o-AHbdaf.js";
9
+ import { a as formatCodexDisplayText, c as formatList, d as formatThreads, f as readString$1, i as formatAccount, l as formatModels, o as formatCodexStatus, r as buildHelp, s as formatComputerUseStatus, u as formatSkills } from "./app-server-policy-BUk0GLMy.js";
10
+ import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-DA2TQfZW.js";
11
+ import { S as canMutateCodexHost, _ as setCodexConversationPermissions, d as formatPermissionsMode, f as parseCodexFastModeArg, g as setCodexConversationModel, h as setCodexConversationFastMode, i as safeCodexControlRequest, m as readCodexConversationActiveTurn, n as readCodexStatusProbes, p as parseCodexPermissionsModeArg, r as requestOptions, s as formatCodexCliSessions, t as codexControlRequest, v as steerCodexConversationTurn, x as CODEX_NATIVE_EXECUTION_AUTH_ERROR, y as stopCodexConversationTurn } from "./command-rpc-BjgEPh19.js";
12
+ import { i as readCodexConversationBindingData, n as createCodexCliNodeConversationBindingData, o as resolveCodexDefaultWorkspaceDir, r as createCodexConversationBindingData } from "./conversation-binding-data-BHRpYbX3.js";
13
+ import { n as installCodexComputerUse, r as readCodexComputerUseStatus } from "./computer-use-DDeySrnb.js";
13
14
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
14
- import { normalizeOptionalString, normalizeUniqueStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
15
15
  import crypto from "node:crypto";
16
16
  import { ensureAuthProfileStore, findNormalizedProviderValue, resolveAgentDir, resolveAuthProfileEligibility, resolveAuthProfileOrder, resolveDefaultAgentDir, resolveProfileUnusableUntilForDisplay, resolveSessionAgentIds } from "openclaw/plugin-sdk/agent-runtime";
17
+ import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
18
+ import { normalizeOptionalString, normalizeUniqueStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
17
19
  //#region extensions/codex/src/command-account.ts
18
20
  const OPENAI_PROVIDER_ID = "openai";
19
21
  const OPENAI_CODEX_PROVIDER_ID = OPENAI_PROVIDER_ID;
@@ -163,7 +165,6 @@ async function readSubscriptionUsage(params) {
163
165
  isolated: true
164
166
  });
165
167
  if (!limits.ok) return;
166
- rememberCodexRateLimits(limits.value);
167
168
  return summarizeCodexAccountUsage(limits.value, params.now);
168
169
  }
169
170
  function buildProfileRow(params) {
@@ -475,81 +476,16 @@ function formatPluginList(plugins, options = {}) {
475
476
  ].join("\n");
476
477
  }
477
478
  //#endregion
478
- //#region extensions/codex/src/command-rpc.ts
479
- function requestOptions(pluginConfig, limit, config) {
480
- const runtime = resolveCodexAppServerRuntimeOptions({ pluginConfig });
481
- return {
482
- limit,
483
- timeoutMs: runtime.requestTimeoutMs,
484
- startOptions: runtime.start,
485
- config
486
- };
487
- }
488
- async function codexControlRequest(pluginConfig, method, requestParams, options = {}) {
489
- const runtime = resolveCodexAppServerRuntimeOptions({ pluginConfig });
490
- return await requestCodexAppServerJson({
491
- method,
492
- requestParams,
493
- timeoutMs: runtime.requestTimeoutMs,
494
- startOptions: runtime.start,
495
- config: options.config,
496
- sessionKey: options.sessionKey,
497
- sessionId: options.sessionId,
498
- authProfileId: options.authProfileId,
499
- agentDir: options.agentDir,
500
- isolated: options.isolated
501
- });
502
- }
503
- async function safeCodexControlRequest(pluginConfig, method, requestParams, options = {}) {
504
- return await safeValue(async () => await codexControlRequest(pluginConfig, method, requestParams, options));
505
- }
506
- async function safeCodexModelList(pluginConfig, limit, config) {
507
- return await safeValue(async () => await listCodexAppServerModels(requestOptions(pluginConfig, limit, config)));
508
- }
509
- async function readCodexStatusProbes(pluginConfig, config) {
510
- const [models, account, limits, mcps, skills] = await Promise.all([
511
- safeCodexModelList(pluginConfig, 20, config),
512
- safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.account, { refreshToken: false }, { config }),
513
- safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.rateLimits, void 0, { config }),
514
- safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listMcpServers, { limit: 100 }, { config }),
515
- safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listSkills, {}, { config })
516
- ]);
517
- return {
518
- models,
519
- account,
520
- limits,
521
- mcps,
522
- skills
523
- };
524
- }
525
- async function safeValue(read) {
526
- try {
527
- return {
528
- ok: true,
529
- value: await read()
530
- };
531
- } catch (error) {
532
- return {
533
- ok: false,
534
- error: describeControlFailure(error)
535
- };
536
- }
537
- }
538
- //#endregion
539
479
  //#region extensions/codex/src/command-handlers.ts
540
480
  const defaultCodexCommandDeps = {
541
481
  codexControlRequest,
542
482
  listCodexAppServerModels: listAllCodexAppServerModels,
543
483
  readCodexStatusProbes,
544
- readCodexAppServerBinding,
545
484
  requestOptions,
546
485
  safeCodexControlRequest,
547
- writeCodexAppServerBinding,
548
- clearCodexAppServerBinding,
549
486
  readCodexComputerUseStatus,
550
487
  installCodexComputerUse,
551
488
  resolveCodexDefaultWorkspaceDir,
552
- startCodexConversationThread,
553
489
  readCodexConversationActiveTurn,
554
490
  setCodexConversationFastMode,
555
491
  setCodexConversationModel,
@@ -574,7 +510,7 @@ const CODEX_DIAGNOSTICS_CONFIRMATION_MAX_REQUESTS_PER_SCOPE = 100;
574
510
  const CODEX_DIAGNOSTICS_CONFIRMATION_MAX_SCOPES = 100;
575
511
  const CODEX_DIAGNOSTICS_SCOPE_FIELD_MAX_CHARS = 128;
576
512
  const CODEX_RESUME_SAFE_THREAD_ID_PATTERN = /^[A-Za-z0-9._:-]+$/;
577
- const CODEX_NATIVE_EXECUTION_SUBCOMMANDS = new Set([
513
+ const CODEX_NATIVE_EXECUTION_SUBCOMMANDS = /* @__PURE__ */ new Set([
578
514
  "bind",
579
515
  "resume",
580
516
  "steer",
@@ -584,7 +520,7 @@ const CODEX_NATIVE_EXECUTION_SUBCOMMANDS = new Set([
584
520
  "compact",
585
521
  "review"
586
522
  ]);
587
- const CODEX_NATIVE_CONTROL_SUBCOMMANDS = new Set([
523
+ const CODEX_NATIVE_CONTROL_SUBCOMMANDS = /* @__PURE__ */ new Set([
588
524
  ...CODEX_NATIVE_EXECUTION_SUBCOMMANDS,
589
525
  "detach",
590
526
  "unbind",
@@ -742,7 +678,7 @@ async function handleCodexSubcommand(ctx, options) {
742
678
  if (rest.length > 0) return { text: "Usage: /codex models" };
743
679
  return { text: formatModels(await deps.listCodexAppServerModels(deps.requestOptions(options.pluginConfig, 100, ctx.config))) };
744
680
  }
745
- if (normalized === "threads") return { text: await buildThreads(deps, options.pluginConfig, rest.join(" ")) };
681
+ if (normalized === "threads") return { text: await buildThreads(deps, ctx, options.pluginConfig, rest.join(" ")) };
746
682
  if (normalized === "sessions") return { text: await buildCodexCliSessions(deps, rest) };
747
683
  if (normalized === "resume") return { text: await resumeThread(deps, ctx, options.pluginConfig, rest) };
748
684
  if (normalized === "bind") return await bindConversation(deps, ctx, options.pluginConfig, rest);
@@ -762,14 +698,14 @@ async function handleCodexSubcommand(ctx, options) {
762
698
  if (normalized === "model") return { text: await setConversationModel(deps, ctx, options.pluginConfig, rest) };
763
699
  if (normalized === "fast") {
764
700
  if (isMenuVerb(rest)) return buildCodexFastMenuReply();
765
- return { text: await setConversationFastMode(deps, ctx, options.pluginConfig, rest) };
701
+ return { text: await setConversationFastMode(deps, ctx, rest) };
766
702
  }
767
703
  if (normalized === "permissions") {
768
704
  if (isMenuVerb(rest)) return buildCodexPermissionsMenuReply();
769
- return { text: await setConversationPermissions(deps, ctx, options.pluginConfig, rest) };
705
+ return { text: await setConversationPermissions(deps, ctx, rest) };
770
706
  }
771
- if (normalized === "compact") return { text: await startThreadAction(deps, ctx, options.pluginConfig, CODEX_CONTROL_METHODS.compact, "compaction", rest) };
772
- if (normalized === "review") return { text: await startThreadAction(deps, ctx, options.pluginConfig, CODEX_CONTROL_METHODS.review, "review", rest) };
707
+ if (normalized === "compact") return { text: await startThreadAction(deps, ctx, options.pluginConfig, "compact", rest) };
708
+ if (normalized === "review") return { text: await startThreadAction(deps, ctx, options.pluginConfig, "review", rest) };
773
709
  if (normalized === "diagnostics") return await handleCodexDiagnosticsFeedback(deps, ctx, options.pluginConfig, rest.join(" "), "/codex diagnostics");
774
710
  if (normalized === "computer-use" || normalized === "computeruse") {
775
711
  if (isMenuVerb(rest)) return buildCodexComputerUseMenuReply();
@@ -777,16 +713,28 @@ async function handleCodexSubcommand(ctx, options) {
777
713
  }
778
714
  if (normalized === "mcp") {
779
715
  if (rest.length > 0) return { text: "Usage: /codex mcp" };
780
- return { text: formatList(await deps.codexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.listMcpServers, { limit: 100 }), "MCP servers") };
716
+ const scope = await resolveCommandAppServerScope(deps, ctx);
717
+ return { text: formatList(await deps.codexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.listMcpServers, { limit: 100 }, {
718
+ config: ctx.config,
719
+ ...scope
720
+ }), "MCP servers") };
781
721
  }
782
722
  if (normalized === "skills") {
783
723
  if (rest.length > 0) return { text: "Usage: /codex skills" };
784
- return { text: formatSkills(await deps.codexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.listSkills, {})) };
724
+ const scope = await resolveCommandAppServerScope(deps, ctx);
725
+ return { text: formatSkills(await deps.codexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.listSkills, {}, {
726
+ config: ctx.config,
727
+ ...scope
728
+ })) };
785
729
  }
786
730
  if (normalized === "account") {
787
731
  if (rest.length > 0) return { text: "Usage: /codex account" };
788
- const [account, limits] = await Promise.all([deps.safeCodexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.account, { refreshToken: false }), deps.safeCodexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.rateLimits, void 0)]);
789
- if (limits.ok) rememberCodexRateLimits(limits.value);
732
+ const scope = await resolveCommandAppServerScope(deps, ctx);
733
+ const requestScope = {
734
+ config: ctx.config,
735
+ ...scope
736
+ };
737
+ const [account, limits] = await Promise.all([deps.safeCodexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.account, { refreshToken: false }, requestScope), deps.safeCodexControlRequest(options.pluginConfig, CODEX_CONTROL_METHODS.rateLimits, void 0, requestScope)]);
790
738
  return { text: formatAccount(account, limits, await readCodexAccountAuthOverview({
791
739
  ctx,
792
740
  pluginConfig: options.pluginConfig,
@@ -808,6 +756,7 @@ function resolveCodexNativeCommandSandboxBlock(ctx, subcommand, args) {
808
756
  });
809
757
  return resolveCodexNativeExecutionBlock({
810
758
  config: ctx.config,
759
+ agentId: ctx.agentId,
811
760
  sessionKey: ctx.sessionKey,
812
761
  sessionId: ctx.sessionId,
813
762
  surface: `/${["codex", subcommand].join(" ")}`
@@ -856,53 +805,54 @@ async function handleComputerUseCommand(deps, ctx, pluginConfig, args) {
856
805
  async function bindConversation(deps, ctx, pluginConfig, args) {
857
806
  const parsed = parseBindArgs(args);
858
807
  if (parsed.help) return { text: "Usage: /codex bind [thread-id] [--cwd <path>] [--model <model>] [--provider <provider>]" };
859
- if (!ctx.sessionFile) return { text: "Cannot bind Codex because this command did not include an OpenClaw session file." };
860
808
  const scope = resolveCodexConversationControlScope(ctx);
861
809
  const workspaceDir = parsed.cwd ?? deps.resolveCodexDefaultWorkspaceDir(pluginConfig);
862
- const authProfileId = (await deps.readCodexAppServerBinding(ctx.sessionFile, {
863
- agentDir: scope.agentDir,
810
+ const currentConversation = await ctx.getCurrentConversationBinding();
811
+ const currentConversationData = readCodexConversationBindingData(currentConversation);
812
+ const bindingId = currentConversationData?.kind === "codex-app-server-session" ? currentConversationData.bindingId : currentConversation ? `conversation-${currentConversation.bindingId}` : void 0;
813
+ const sessionOwner = ctx.sessionId ? sessionBindingIdentity({
814
+ sessionId: ctx.sessionId,
815
+ sessionKey: ctx.sessionKey,
816
+ agentId: scope.agentId,
864
817
  config: ctx.config
865
- }))?.authProfileId;
866
- const startParams = {
867
- pluginConfig,
868
- config: ctx.config,
869
- sessionFile: ctx.sessionFile,
818
+ }) : void 0;
819
+ const currentOwner = currentConversationData?.kind === "codex-app-server-session" ? conversationBindingIdentity(currentConversationData.bindingId) : sessionOwner;
820
+ const existingBinding = currentOwner ? await deps.bindingStore.read(currentOwner) : void 0;
821
+ const sessionSource = sessionOwner && existingBinding ? {
822
+ agentId: sessionOwner.agentId,
823
+ sessionId: sessionOwner.sessionId,
824
+ threadId: existingBinding.threadId,
825
+ ...sessionOwner.sessionKey ? { sessionKey: sessionOwner.sessionKey } : {}
826
+ } : void 0;
827
+ const authProfileId = existingBinding?.authProfileId;
828
+ const data = createCodexConversationBindingData({
829
+ bindingId,
870
830
  workspaceDir,
831
+ agentId: scope.agentId,
871
832
  agentDir: scope.agentDir,
872
- sessionKey: ctx.sessionKey,
873
- agentId: scope.sessionAgentId,
874
- threadId: parsed.threadId,
875
- model: parsed.model,
876
- modelProvider: parsed.provider
877
- };
878
- if (authProfileId) startParams.authProfileId = authProfileId;
879
- const data = await deps.startCodexConversationThread(startParams);
880
- const threadId = (await deps.readCodexAppServerBinding(ctx.sessionFile, {
881
- agentDir: scope.agentDir,
882
- config: ctx.config
883
- }))?.threadId ?? parsed.threadId ?? "new thread";
884
- const summary = `Codex app-server thread ${formatCodexDisplayText(threadId)} in ${formatCodexDisplayText(workspaceDir)}`;
885
- let request;
886
- try {
887
- request = await ctx.requestConversationBinding({
888
- summary,
889
- detachHint: "/codex detach",
890
- data
891
- });
892
- } catch (error) {
893
- await deps.clearCodexAppServerBinding(ctx.sessionFile);
894
- throw error;
895
- }
896
- if (request.status === "bound") return { text: `Bound this conversation to Codex thread ${formatCodexDisplayText(threadId)} in ${formatCodexDisplayText(workspaceDir)}.` };
833
+ source: currentConversationData?.kind === "codex-app-server-session" ? currentConversationData.source : sessionSource,
834
+ start: {
835
+ id: crypto.randomUUID(),
836
+ threadId: parsed.threadId,
837
+ model: parsed.model,
838
+ modelProvider: parsed.provider,
839
+ authProfileId
840
+ }
841
+ });
842
+ const threadLabel = parsed.threadId ?? "a new thread";
843
+ const request = await ctx.requestConversationBinding({
844
+ summary: `Codex app-server thread ${formatCodexDisplayText(threadLabel)} in ${formatCodexDisplayText(workspaceDir)}`,
845
+ detachHint: "/codex detach",
846
+ data
847
+ });
897
848
  if (request.status === "pending") return request.reply;
898
- await deps.clearCodexAppServerBinding(ctx.sessionFile);
899
- return { text: formatCodexDisplayText(request.message) };
849
+ if (request.status === "error") return { text: formatCodexDisplayText(request.message) };
850
+ return { text: `Bound this conversation to ${formatCodexDisplayText(threadLabel)} in ${formatCodexDisplayText(workspaceDir)}. The next message will initialize it.` };
900
851
  }
901
852
  async function detachConversation(deps, ctx) {
902
853
  const data = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
903
854
  const detached = await ctx.detachConversationBinding();
904
- if (data?.kind === "codex-app-server-session") await deps.clearCodexAppServerBinding(data.sessionFile);
905
- else if (ctx.sessionFile) await deps.clearCodexAppServerBinding(ctx.sessionFile);
855
+ if (data?.kind === "codex-app-server-session") await deps.bindingStore.mutate(conversationBindingIdentity(data.bindingId), { kind: "clear" });
906
856
  return detached.removed ? "Detached this conversation from Codex." : "No Codex conversation binding was attached.";
907
857
  }
908
858
  async function describeConversationBinding(deps, ctx) {
@@ -917,11 +867,9 @@ async function describeConversationBinding(deps, ctx) {
917
867
  `- Workspace: ${formatCodexDisplayText(data.cwd ?? "unknown")}`,
918
868
  "- Active run: not tracked"
919
869
  ].join("\n");
920
- const threadBinding = await deps.readCodexAppServerBinding(data.sessionFile, {
921
- agentDir: data.agentDir,
922
- config: ctx.config
923
- });
924
- const active = deps.readCodexConversationActiveTurn(data.sessionFile);
870
+ const identity = conversationBindingIdentity(data.bindingId);
871
+ const threadBinding = await deps.bindingStore.read(identity);
872
+ const active = deps.readCodexConversationActiveTurn(identity);
925
873
  return [
926
874
  "Codex conversation binding:",
927
875
  `- Thread: ${formatCodexDisplayText(threadBinding?.threadId ?? "unknown")}`,
@@ -930,13 +878,17 @@ async function describeConversationBinding(deps, ctx) {
930
878
  `- Fast: ${isCodexFastServiceTier(threadBinding?.serviceTier) ? "on" : "off"}`,
931
879
  `- Permissions: ${threadBinding ? formatPermissionsMode(threadBinding) : "default"}`,
932
880
  `- Active run: ${formatCodexDisplayText(active ? active.turnId : "none")}`,
933
- `- Session: ${formatCodexDisplayText(data.sessionFile)}`
881
+ `- Binding: ${formatCodexDisplayText(data.bindingId)}`
934
882
  ].join("\n");
935
883
  }
936
- async function buildThreads(deps, pluginConfig, filter) {
884
+ async function buildThreads(deps, ctx, pluginConfig, filter) {
885
+ const scope = await resolveCommandAppServerScope(deps, ctx);
937
886
  return formatThreads(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listThreads, {
938
887
  limit: 10,
939
888
  ...filter.trim() ? { searchTerm: filter.trim() } : {}
889
+ }, {
890
+ config: ctx.config,
891
+ ...scope
940
892
  }));
941
893
  }
942
894
  async function buildCodexCliSessions(deps, args) {
@@ -954,20 +906,58 @@ async function resumeThread(deps, ctx, pluginConfig, args) {
954
906
  if (parsed.help) return args.includes("--help") || args.includes("-h") || parsed.host ? "Usage: /codex resume <thread-id>\nUsage: /codex resume <session-id> --host <node> --bind here" : "Usage: /codex resume <thread-id>";
955
907
  if (parsed.host) return await bindCodexCliNodeSession(deps, ctx, parsed);
956
908
  if (!normalizedThreadId || args.length !== 1) return "Usage: /codex resume <thread-id>";
957
- if (!ctx.sessionFile) return "Cannot attach a Codex thread because this command did not include an OpenClaw session file.";
958
- const response = await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.resumeThread, {
959
- threadId: normalizedThreadId,
960
- persistExtendedHistory: true
909
+ if (!ctx.sessionId) return "Cannot attach a Codex thread because this command did not include an OpenClaw session id.";
910
+ const scope = resolveCodexConversationControlScope(ctx);
911
+ const identity = sessionBindingIdentity({
912
+ sessionId: ctx.sessionId,
913
+ sessionKey: ctx.sessionKey,
914
+ agentId: scope.agentId,
915
+ config: ctx.config
961
916
  });
962
- const thread = isJsonObject(response) && isJsonObject(response.thread) ? response.thread : {};
963
- const effectiveThreadId = readString$1(thread, "id") ?? normalizedThreadId;
964
- await deps.writeCodexAppServerBinding(ctx.sessionFile, {
965
- threadId: effectiveThreadId,
966
- cwd: readString$1(thread, "cwd") ?? "",
967
- model: isJsonObject(response) ? readString$1(response, "model") : void 0,
968
- modelProvider: isJsonObject(response) ? readString$1(response, "modelProvider") : void 0
917
+ return await deps.bindingStore.withLease(identity, async () => {
918
+ if (!await reclaimCurrentCodexSessionGeneration({
919
+ bindingStore: deps.bindingStore,
920
+ identity,
921
+ config: ctx.config
922
+ })) throw new Error(`Codex session generation is no longer current: ${identity.sessionId}`);
923
+ const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
924
+ authProfileId: (await deps.bindingStore.read(identity))?.authProfileId,
925
+ agentDir: scope.agentDir,
926
+ config: ctx.config
927
+ });
928
+ const response = assertCodexThreadResumeResponse(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.resumeThread, {
929
+ threadId: normalizedThreadId,
930
+ excludeTurns: true
931
+ }, {
932
+ config: ctx.config,
933
+ agentDir: scope.agentDir,
934
+ authProfileId,
935
+ sessionKey: ctx.sessionKey,
936
+ sessionId: ctx.sessionId
937
+ }));
938
+ const effectiveThreadId = response.thread.id;
939
+ if (effectiveThreadId !== normalizedThreadId) throw new Error(`Codex thread/resume returned ${effectiveThreadId} for ${normalizedThreadId}`);
940
+ const resumedCwd = response.thread.cwd;
941
+ if (typeof resumedCwd !== "string") throw new Error(`Codex thread/resume returned no cwd for ${normalizedThreadId}`);
942
+ const modelProvider = normalizeCodexAppServerBindingModelProvider({
943
+ authProfileId,
944
+ modelProvider: response.modelProvider ?? void 0,
945
+ agentDir: scope.agentDir,
946
+ config: ctx.config
947
+ });
948
+ if (!await deps.bindingStore.mutate(identity, {
949
+ kind: "set",
950
+ binding: {
951
+ threadId: effectiveThreadId,
952
+ cwd: resumedCwd,
953
+ authProfileId,
954
+ model: response.model,
955
+ modelProvider,
956
+ historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString()
957
+ }
958
+ })) throw new Error("Codex thread binding changed while attaching the resumed thread.");
959
+ return `Attached this OpenClaw session to Codex thread ${formatCodexDisplayText(effectiveThreadId)}.`;
969
960
  });
970
- return `Attached this OpenClaw session to Codex thread ${formatCodexDisplayText(effectiveThreadId)}.`;
971
961
  }
972
962
  async function bindCodexCliNodeSession(deps, ctx, parsed) {
973
963
  if (!parsed.threadId || !parsed.host || parsed.bindHere !== true) return "Usage: /codex resume <session-id> --host <node> --bind here";
@@ -978,9 +968,11 @@ async function bindCodexCliNodeSession(deps, ctx, parsed) {
978
968
  if (!resolved.session) return `No Codex CLI session ${formatCodexDisplayText(parsed.threadId)} was found on ${formatCodexDisplayText(parsed.host)}.`;
979
969
  const nodeId = resolved.node.nodeId;
980
970
  if (!nodeId) return "Cannot bind Codex CLI session because the selected node did not include a node id.";
971
+ const scope = resolveCodexConversationControlScope(ctx);
981
972
  const data = createCodexCliNodeConversationBindingData({
982
973
  nodeId,
983
974
  sessionId: parsed.threadId,
975
+ agentId: scope.agentId,
984
976
  cwd: resolved.session?.cwd
985
977
  });
986
978
  const summary = `Codex CLI session ${formatCodexDisplayText(parsed.threadId)} on ${formatCodexDisplayText(nodeId)}`;
@@ -995,9 +987,10 @@ async function bindCodexCliNodeSession(deps, ctx, parsed) {
995
987
  }
996
988
  async function stopConversationTurn(deps, ctx, pluginConfig) {
997
989
  const target = await resolveControlTarget(ctx);
998
- if (!target) return "Cannot stop Codex because this command did not include an OpenClaw session file.";
990
+ if (!target) return "Cannot stop Codex because this command did not include a stable binding identity.";
999
991
  return (await deps.stopCodexConversationTurn({
1000
- sessionFile: target.sessionFile,
992
+ identity: target.identity,
993
+ bindingStore: deps.bindingStore,
1001
994
  pluginConfig,
1002
995
  agentDir: target.agentDir,
1003
996
  config: ctx.config
@@ -1005,11 +998,12 @@ async function stopConversationTurn(deps, ctx, pluginConfig) {
1005
998
  }
1006
999
  async function steerConversationTurn(deps, ctx, pluginConfig, message) {
1007
1000
  const target = await resolveControlTarget(ctx);
1008
- if (!target) return "Cannot steer Codex because this command did not include an OpenClaw session file.";
1001
+ if (!target) return "Cannot steer Codex because this command did not include a stable binding identity.";
1009
1002
  return (await deps.steerCodexConversationTurn({
1010
- sessionFile: target.sessionFile,
1011
- pluginConfig,
1003
+ identity: target.identity,
1004
+ bindingStore: deps.bindingStore,
1012
1005
  message,
1006
+ pluginConfig,
1013
1007
  agentDir: target.agentDir,
1014
1008
  config: ctx.config
1015
1009
  })).message;
@@ -1017,77 +1011,100 @@ async function steerConversationTurn(deps, ctx, pluginConfig, message) {
1017
1011
  async function setConversationModel(deps, ctx, pluginConfig, args) {
1018
1012
  if (args.length > 1) return "Usage: /codex model <model>";
1019
1013
  const target = await resolveControlTarget(ctx);
1020
- if (!target) return "Cannot set Codex model because this command did not include an OpenClaw session file.";
1014
+ if (!target) return "Cannot set Codex model because this command did not include a stable binding identity.";
1021
1015
  const [model = ""] = args;
1022
1016
  const normalized = model.trim();
1023
1017
  if (!normalized) {
1024
- const binding = await deps.readCodexAppServerBinding(target.sessionFile, {
1025
- agentDir: target.agentDir,
1026
- config: ctx.config
1027
- });
1018
+ const binding = await deps.bindingStore.read(target.identity);
1028
1019
  return binding?.model ? `Codex model: ${formatCodexDisplayText(binding.model)}` : "Usage: /codex model <model>";
1029
1020
  }
1030
1021
  return await deps.setCodexConversationModel({
1031
- sessionFile: target.sessionFile,
1022
+ identity: target.identity,
1023
+ bindingStore: deps.bindingStore,
1032
1024
  pluginConfig,
1033
1025
  model: normalized,
1034
1026
  agentDir: target.agentDir,
1035
1027
  config: ctx.config
1036
1028
  });
1037
1029
  }
1038
- async function setConversationFastMode(deps, ctx, pluginConfig, args) {
1030
+ async function setConversationFastMode(deps, ctx, args) {
1039
1031
  if (args.length > 1) return "Usage: /codex fast [on|off|status]";
1040
1032
  const target = await resolveControlTarget(ctx);
1041
- if (!target) return "Cannot set Codex fast mode because this command did not include an OpenClaw session file.";
1033
+ if (!target) return "Cannot set Codex fast mode because this command did not include a stable binding identity.";
1042
1034
  const value = args[0];
1043
1035
  const parsed = parseCodexFastModeArg(value);
1044
1036
  if (value && parsed == null && value.trim().toLowerCase() !== "status") return "Usage: /codex fast [on|off|status]";
1045
1037
  return await deps.setCodexConversationFastMode({
1046
- sessionFile: target.sessionFile,
1047
- pluginConfig,
1048
- enabled: parsed,
1049
- agentDir: target.agentDir,
1050
- config: ctx.config
1038
+ identity: target.identity,
1039
+ bindingStore: deps.bindingStore,
1040
+ enabled: parsed
1051
1041
  });
1052
1042
  }
1053
- async function setConversationPermissions(deps, ctx, pluginConfig, args) {
1043
+ async function setConversationPermissions(deps, ctx, args) {
1054
1044
  if (args.length > 1) return "Usage: /codex permissions [default|yolo|status]";
1045
+ const target = await resolveControlTarget(ctx);
1046
+ if (!target) return "Cannot set Codex permissions because this command did not include a stable binding identity.";
1055
1047
  const value = args[0];
1056
1048
  const parsed = parseCodexPermissionsModeArg(value);
1057
1049
  if (value && !parsed && value.trim().toLowerCase() !== "status") return "Usage: /codex permissions [default|yolo|status]";
1058
- const target = await resolveControlTarget(ctx);
1059
- if (!target) return "Cannot set Codex permissions because this command did not include an OpenClaw session file.";
1060
1050
  return await deps.setCodexConversationPermissions({
1061
- sessionFile: target.sessionFile,
1062
- pluginConfig,
1063
- mode: parsed,
1064
- agentDir: target.agentDir,
1065
- config: ctx.config
1051
+ identity: target.identity,
1052
+ bindingStore: deps.bindingStore,
1053
+ mode: parsed
1066
1054
  });
1067
1055
  }
1068
1056
  async function resolveControlTarget(ctx) {
1069
1057
  const data = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
1070
1058
  const scope = resolveCodexConversationControlScope(ctx);
1071
1059
  if (data?.kind === "codex-app-server-session") return {
1072
- sessionFile: data.sessionFile,
1073
- agentDir: data.agentDir ?? scope.agentDir
1060
+ identity: conversationBindingIdentity(data.bindingId),
1061
+ agentId: data.agentId ?? scope.agentId,
1062
+ agentDir: data.agentDir ?? scope.agentDir,
1063
+ requestedAuthProfileId: data.start?.authProfileId
1074
1064
  };
1075
- return ctx.sessionFile ? {
1076
- sessionFile: ctx.sessionFile,
1065
+ return ctx.sessionId ? {
1066
+ identity: sessionBindingIdentity({
1067
+ sessionId: ctx.sessionId,
1068
+ sessionKey: ctx.sessionKey,
1069
+ agentId: scope.agentId,
1070
+ config: ctx.config
1071
+ }),
1072
+ agentId: scope.agentId,
1077
1073
  agentDir: scope.agentDir
1078
1074
  } : void 0;
1079
1075
  }
1080
- async function resolveControlSessionFile(ctx) {
1081
- return (await resolveControlTarget(ctx))?.sessionFile;
1076
+ async function resolveCommandAppServerScope(deps, ctx) {
1077
+ const target = await resolveControlTarget(ctx);
1078
+ const fallback = resolveCodexConversationControlScope(ctx);
1079
+ const agentDir = target?.agentDir ?? fallback.agentDir;
1080
+ const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
1081
+ authProfileId: (target ? await deps.bindingStore.read(target.identity) : void 0)?.authProfileId ?? target?.requestedAuthProfileId,
1082
+ agentDir,
1083
+ config: ctx.config
1084
+ });
1085
+ return {
1086
+ agentId: target?.agentId ?? fallback.agentId,
1087
+ agentDir,
1088
+ ...authProfileId ? { authProfileId } : {},
1089
+ ...ctx.sessionKey ? { sessionKey: ctx.sessionKey } : {},
1090
+ ...ctx.sessionId ? { sessionId: ctx.sessionId } : {}
1091
+ };
1092
+ }
1093
+ function conversationBindingIdentity(bindingId) {
1094
+ return {
1095
+ kind: "conversation",
1096
+ bindingId
1097
+ };
1082
1098
  }
1083
1099
  function resolveCodexConversationControlScope(ctx) {
1084
1100
  const { sessionAgentId } = resolveSessionAgentIds({
1085
1101
  sessionKey: ctx.sessionKey,
1102
+ agentId: ctx.agentId,
1086
1103
  config: ctx.config
1087
1104
  });
1088
1105
  return {
1089
- agentDir: resolveAgentDir(ctx.config, sessionAgentId),
1090
- sessionAgentId
1106
+ agentId: sessionAgentId,
1107
+ agentDir: resolveAgentDir(ctx.config, sessionAgentId)
1091
1108
  };
1092
1109
  }
1093
1110
  async function handleCodexDiagnosticsFeedback(deps, ctx, pluginConfig, args, commandPrefix) {
@@ -1101,7 +1118,7 @@ async function handleCodexDiagnosticsFeedback(deps, ctx, pluginConfig, args, com
1101
1118
  return await requestCodexDiagnosticsFeedbackApproval(deps, ctx, parsed.note, commandPrefix);
1102
1119
  }
1103
1120
  async function requestCodexDiagnosticsFeedbackApproval(deps, ctx, note, commandPrefix) {
1104
- if (!await hasAnyCodexDiagnosticsSessionFile(ctx)) return { text: "Cannot send Codex diagnostics because this command did not include an OpenClaw session file." };
1121
+ if (!await hasAnyCodexDiagnosticsIdentity(ctx)) return { text: "Cannot send Codex diagnostics because this command did not include a stable session identity." };
1105
1122
  const targets = await resolveCodexDiagnosticsTargets(deps, ctx);
1106
1123
  if (targets.length === 0) return { text: ["No Codex thread is attached to this OpenClaw session yet.", "Use /codex threads to find a thread, then /codex resume <thread-id> before sending diagnostics."].join("\n") };
1107
1124
  const now = Date.now();
@@ -1157,7 +1174,7 @@ async function requestCodexDiagnosticsFeedbackApproval(deps, ctx, note, commandP
1157
1174
  };
1158
1175
  }
1159
1176
  async function previewCodexDiagnosticsFeedbackApproval(deps, ctx, note) {
1160
- if (!await hasAnyCodexDiagnosticsSessionFile(ctx)) return "Cannot send Codex diagnostics because this command did not include an OpenClaw session file.";
1177
+ if (!await hasAnyCodexDiagnosticsIdentity(ctx)) return "Cannot send Codex diagnostics because this command did not include a stable session identity.";
1161
1178
  const targets = await resolveCodexDiagnosticsTargets(deps, ctx);
1162
1179
  if (targets.length === 0) return ["No Codex thread is attached to this OpenClaw session yet.", "Use /codex threads to find a thread, then /codex resume <thread-id> before sending diagnostics."].join("\n");
1163
1180
  const cooldownMessage = readCodexDiagnosticsTargetsCooldownMessage(targets, ctx, Date.now(), { includeThreadId: false });
@@ -1181,10 +1198,10 @@ async function confirmCodexDiagnosticsFeedback(deps, ctx, pluginConfig, token) {
1181
1198
  const scopeMismatch = readCodexDiagnosticsScopeMismatch(pending, ctx);
1182
1199
  if (scopeMismatch) return scopeMismatch.confirmMessage;
1183
1200
  deletePendingCodexDiagnosticsConfirmation(token);
1184
- if (!pending.privateRouted && !await hasAnyCodexDiagnosticsSessionFile(ctx)) return "Cannot send Codex diagnostics because this command did not include an OpenClaw session file.";
1185
- const currentTargets = pending.privateRouted ? await resolvePendingCodexDiagnosticsTargets(deps, pending.targets) : await resolveCodexDiagnosticsTargets(deps, ctx);
1201
+ if (!pending.privateRouted && !await hasAnyCodexDiagnosticsIdentity(ctx)) return "Cannot send Codex diagnostics because this command did not include a stable session identity.";
1202
+ const currentTargets = pending.privateRouted ? await resolvePendingCodexDiagnosticsTargets(deps, pending.targets, ctx.config) : await resolveCodexDiagnosticsTargets(deps, ctx);
1186
1203
  if (!codexDiagnosticsTargetsMatch(pending.targets, currentTargets)) return "The Codex diagnostics sessions changed before confirmation. Run /diagnostics again for the current threads.";
1187
- return await sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, pending.note ?? "", pending.targets);
1204
+ return await sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, pending.note ?? "", currentTargets, { cooldownScope: pending.scopeKey });
1188
1205
  }
1189
1206
  function cancelCodexDiagnosticsFeedback(ctx, token) {
1190
1207
  const pending = readPendingCodexDiagnosticsConfirmation(token, Date.now());
@@ -1202,15 +1219,15 @@ function cancelCodexDiagnosticsFeedback(ctx, token) {
1202
1219
  ].join("\n");
1203
1220
  }
1204
1221
  async function sendCodexDiagnosticsFeedbackForContext(deps, ctx, pluginConfig, note) {
1205
- if (!await hasAnyCodexDiagnosticsSessionFile(ctx)) return "Cannot send Codex diagnostics because this command did not include an OpenClaw session file.";
1222
+ if (!await hasAnyCodexDiagnosticsIdentity(ctx)) return "Cannot send Codex diagnostics because this command did not include a stable session identity.";
1206
1223
  const targets = await resolveCodexDiagnosticsTargets(deps, ctx);
1207
1224
  if (targets.length === 0) return ["No Codex thread is attached to this OpenClaw session yet.", "Use /codex threads to find a thread, then /codex resume <thread-id> before sending diagnostics."].join("\n");
1208
1225
  return await sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, note, targets);
1209
1226
  }
1210
- async function sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, note, targets) {
1227
+ async function sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, note, targets, options = {}) {
1211
1228
  if (targets.length === 0) return ["No Codex thread is attached to this OpenClaw session yet.", "Use /codex threads to find a thread, then /codex resume <thread-id> before sending diagnostics."].join("\n");
1212
1229
  const now = Date.now();
1213
- const cooldownMessage = readCodexDiagnosticsTargetsCooldownMessage(targets, ctx, now);
1230
+ const cooldownMessage = readCodexDiagnosticsTargetsCooldownMessage(targets, ctx, now, { cooldownScope: options.cooldownScope });
1214
1231
  if (cooldownMessage) return cooldownMessage;
1215
1232
  const reason = normalizeDiagnosticsReason(note);
1216
1233
  const sent = [];
@@ -1222,6 +1239,12 @@ async function sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, n
1222
1239
  includeLogs: true,
1223
1240
  tags: buildDiagnosticsTags(ctx),
1224
1241
  ...reason ? { reason } : {}
1242
+ }, {
1243
+ config: ctx.config,
1244
+ agentDir: target.agentDir,
1245
+ ...target.authProfileId ? { authProfileId: target.authProfileId } : {},
1246
+ ...target.sessionId ? { sessionId: target.sessionId } : {},
1247
+ ...target.sessionKey ? { sessionKey: target.sessionKey } : {}
1225
1248
  });
1226
1249
  if (!response.ok) {
1227
1250
  failed.push({
@@ -1235,20 +1258,20 @@ async function sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, n
1235
1258
  ...target,
1236
1259
  threadId: responseThreadId ?? target.threadId
1237
1260
  });
1238
- recordCodexDiagnosticsUpload(target.threadId, ctx, now);
1261
+ recordCodexDiagnosticsUpload(target.threadId, ctx, now, options.cooldownScope);
1239
1262
  }
1240
1263
  return formatCodexDiagnosticsUploadResult(sent, failed);
1241
1264
  }
1242
- async function hasAnyCodexDiagnosticsSessionFile(ctx) {
1243
- if (await resolveControlSessionFile(ctx)) return true;
1244
- return (ctx.diagnosticsSessions ?? []).some((session) => Boolean(session.sessionFile));
1265
+ async function hasAnyCodexDiagnosticsIdentity(ctx) {
1266
+ if (await resolveControlTarget(ctx)) return true;
1267
+ return (ctx.diagnosticsSessions ?? []).some((session) => Boolean(session.sessionId));
1245
1268
  }
1246
1269
  async function resolveCodexDiagnosticsTargets(deps, ctx) {
1247
- const activeSessionFile = await resolveControlSessionFile(ctx);
1270
+ const activeTarget = await resolveControlTarget(ctx);
1248
1271
  const candidates = [];
1249
- if (activeSessionFile) candidates.push({
1250
- threadId: "",
1251
- sessionFile: activeSessionFile,
1272
+ if (activeTarget) candidates.push({
1273
+ identity: activeTarget.identity,
1274
+ agentDir: activeTarget.agentDir,
1252
1275
  sessionKey: ctx.sessionKey,
1253
1276
  sessionId: ctx.sessionId,
1254
1277
  channel: ctx.channel,
@@ -1258,10 +1281,17 @@ async function resolveCodexDiagnosticsTargets(deps, ctx) {
1258
1281
  threadParentId: ctx.threadParentId
1259
1282
  });
1260
1283
  for (const session of ctx.diagnosticsSessions ?? []) {
1261
- if (!session.sessionFile) continue;
1284
+ if (!session.sessionId) continue;
1285
+ const inventoryAgentId = session.sessionKey ? parseAgentSessionKey(session.sessionKey)?.agentId : void 0;
1286
+ const identity = sessionBindingIdentity({
1287
+ sessionId: session.sessionId,
1288
+ sessionKey: session.sessionKey,
1289
+ agentId: inventoryAgentId ?? ctx.agentId,
1290
+ config: ctx.config
1291
+ });
1262
1292
  candidates.push({
1263
- threadId: "",
1264
- sessionFile: session.sessionFile,
1293
+ identity,
1294
+ agentDir: resolveAgentDir(ctx.config, identity.agentId),
1265
1295
  sessionKey: session.sessionKey,
1266
1296
  sessionId: session.sessionId,
1267
1297
  channel: session.channel,
@@ -1271,38 +1301,50 @@ async function resolveCodexDiagnosticsTargets(deps, ctx) {
1271
1301
  threadParentId: session.threadParentId
1272
1302
  });
1273
1303
  }
1274
- const seenSessionFiles = /* @__PURE__ */ new Set();
1304
+ const seenBindingKeys = /* @__PURE__ */ new Set();
1275
1305
  const seenThreadIds = /* @__PURE__ */ new Set();
1276
1306
  const targets = [];
1277
1307
  for (const candidate of candidates) {
1278
- if (seenSessionFiles.has(candidate.sessionFile)) continue;
1279
- seenSessionFiles.add(candidate.sessionFile);
1280
- const binding = await deps.readCodexAppServerBinding(candidate.sessionFile);
1308
+ const key = bindingStoreKey(candidate.identity);
1309
+ if (seenBindingKeys.has(key)) continue;
1310
+ seenBindingKeys.add(key);
1311
+ const binding = await deps.bindingStore.read(candidate.identity);
1281
1312
  if (!binding?.threadId || seenThreadIds.has(binding.threadId)) continue;
1282
1313
  seenThreadIds.add(binding.threadId);
1283
- targets.push({
1284
- ...candidate,
1285
- threadId: binding.threadId
1286
- });
1314
+ targets.push(resolveCodexDiagnosticsTarget(candidate, binding, ctx.config));
1287
1315
  }
1288
1316
  return targets;
1289
1317
  }
1290
- async function resolvePendingCodexDiagnosticsTargets(deps, targets) {
1318
+ async function resolvePendingCodexDiagnosticsTargets(deps, targets, config) {
1291
1319
  const resolved = [];
1292
1320
  for (const target of targets) {
1293
- const binding = await deps.readCodexAppServerBinding(target.sessionFile);
1321
+ const binding = await deps.bindingStore.read(target.identity);
1294
1322
  if (!binding?.threadId) continue;
1295
- resolved.push({
1296
- ...target,
1297
- threadId: binding.threadId
1298
- });
1323
+ resolved.push(resolveCodexDiagnosticsTarget(target, binding, config));
1299
1324
  }
1300
1325
  return resolved;
1301
1326
  }
1327
+ function resolveCodexDiagnosticsTarget(target, binding, config) {
1328
+ const authProfileId = resolveCodexAppServerAuthProfileIdForAgent({
1329
+ authProfileId: binding.authProfileId,
1330
+ agentDir: target.agentDir,
1331
+ config
1332
+ });
1333
+ return {
1334
+ ...target,
1335
+ threadId: binding.threadId,
1336
+ authProfileId
1337
+ };
1338
+ }
1302
1339
  function codexDiagnosticsTargetsMatch(expected, actual) {
1303
- const expectedThreadIds = expected.map((target) => target.threadId).toSorted();
1304
- const actualThreadIds = actual.map((target) => target.threadId).toSorted();
1305
- return expectedThreadIds.length === actualThreadIds.length && expectedThreadIds.every((threadId, index) => threadId === actualThreadIds[index]);
1340
+ const fingerprint = (target) => JSON.stringify([
1341
+ bindingStoreKey(target.identity),
1342
+ target.threadId,
1343
+ target.authProfileId ?? null
1344
+ ]);
1345
+ const expectedTargets = expected.map(fingerprint).toSorted();
1346
+ const actualTargets = actual.map(fingerprint).toSorted();
1347
+ return expectedTargets.length === actualTargets.length && expectedTargets.every((target, index) => target === actualTargets[index]);
1306
1348
  }
1307
1349
  function formatCodexDiagnosticsUploadResult(sent, failed) {
1308
1350
  const lines = [];
@@ -1504,7 +1546,7 @@ function readCodexDiagnosticsTargetsCooldownMessage(targets, ctx, now, options =
1504
1546
  return `Codex diagnostics were already sent for thread ${formatCodexThreadIdForDisplay(target.threadId)} recently. Try again in ${Math.ceil(cooldownMs / 1e3)}s.`;
1505
1547
  }
1506
1548
  }
1507
- const scopeCooldownMs = readCodexDiagnosticsScopeCooldownMs(readCodexDiagnosticsCooldownScope(ctx), now);
1549
+ const scopeCooldownMs = readCodexDiagnosticsScopeCooldownMs(options.cooldownScope ?? readCodexDiagnosticsCooldownScope(ctx), now);
1508
1550
  if (scopeCooldownMs > 0) return `Codex diagnostics were already sent for this account or channel recently. Try again in ${Math.ceil(scopeCooldownMs / 1e3)}s.`;
1509
1551
  }
1510
1552
  function readCodexDiagnosticsScopeCooldownMs(scope, now) {
@@ -1514,9 +1556,9 @@ function readCodexDiagnosticsScopeCooldownMs(scope, now) {
1514
1556
  if (remainingMs === 0) lastCodexDiagnosticsUploadByScope.delete(scope);
1515
1557
  return remainingMs;
1516
1558
  }
1517
- function recordCodexDiagnosticsUpload(threadId, ctx, now) {
1559
+ function recordCodexDiagnosticsUpload(threadId, ctx, now, cooldownScope) {
1518
1560
  pruneCodexDiagnosticsCooldowns(now);
1519
- recordBoundedCodexDiagnosticsCooldown(lastCodexDiagnosticsUploadByScope, readCodexDiagnosticsCooldownScope(ctx), CODEX_DIAGNOSTICS_COOLDOWN_MAX_SCOPES, now);
1561
+ recordBoundedCodexDiagnosticsCooldown(lastCodexDiagnosticsUploadByScope, cooldownScope ?? readCodexDiagnosticsCooldownScope(ctx), CODEX_DIAGNOSTICS_COOLDOWN_MAX_SCOPES, now);
1520
1562
  recordBoundedCodexDiagnosticsCooldown(lastCodexDiagnosticsUploadByThread, threadId, CODEX_DIAGNOSTICS_COOLDOWN_MAX_THREADS, now);
1521
1563
  }
1522
1564
  function recordBoundedCodexDiagnosticsCooldown(map, key, maxSize, now) {
@@ -1564,24 +1606,17 @@ function normalizeCodexDiagnosticsScopeField(value) {
1564
1606
  if (normalized.length <= CODEX_DIAGNOSTICS_SCOPE_FIELD_MAX_CHARS) return normalized;
1565
1607
  return `sha256:${crypto.createHash("sha256").update(normalized).digest("hex")}`;
1566
1608
  }
1567
- async function startThreadAction(deps, ctx, pluginConfig, method, label, args) {
1609
+ async function startThreadAction(deps, ctx, pluginConfig, kind, args) {
1610
+ const label = kind === "compact" ? "compaction" : "review";
1568
1611
  if (args.length > 0) return `Usage: /codex ${label === "compaction" ? "compact" : label}`;
1569
1612
  const target = await resolveControlTarget(ctx);
1570
- if (!target) return `Cannot start Codex ${label} because this command did not include an OpenClaw session file.`;
1571
- const binding = await deps.readCodexAppServerBinding(target.sessionFile, {
1572
- agentDir: target.agentDir,
1573
- config: ctx.config
1574
- });
1613
+ if (!target) return `Cannot start Codex ${label} because this command did not include a stable binding identity.`;
1614
+ const binding = await deps.bindingStore.read(target.identity);
1575
1615
  if (!binding?.threadId) return `No Codex thread is attached to this OpenClaw session yet.`;
1576
- if (method === CODEX_CONTROL_METHODS.review) await deps.codexControlRequest(pluginConfig, method, {
1616
+ await deps.codexControlRequest(pluginConfig, kind === "compact" ? CODEX_CONTROL_METHODS.compact : CODEX_CONTROL_METHODS.review, kind === "review" ? {
1577
1617
  threadId: binding.threadId,
1578
1618
  target: { type: "uncommittedChanges" }
1579
- }, {
1580
- agentDir: target.agentDir,
1581
- authProfileId: binding.authProfileId,
1582
- config: ctx.config
1583
- });
1584
- else await deps.codexControlRequest(pluginConfig, method, { threadId: binding.threadId }, {
1619
+ } : { threadId: binding.threadId }, {
1585
1620
  agentDir: target.agentDir,
1586
1621
  authProfileId: binding.authProfileId,
1587
1622
  config: ctx.config