@openclaw/codex 2026.6.11-beta.2 → 2026.7.1-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 (42) hide show
  1. package/dist/{app-server-policy-BPTiVNsW.js → app-server-policy-C968Kgin.js} +5 -5
  2. package/dist/{attempt-notifications-BYnndyFl.js → attempt-notifications-BGsEIIDI.js} +29 -40
  3. package/dist/{command-handlers-DEPTJYk0.js → command-handlers-Dv-pzAmg.js} +278 -237
  4. package/dist/command-rpc-eLM_mtC7.js +784 -0
  5. package/dist/compact-4mVBriKT.js +603 -0
  6. package/dist/{computer-use-ClrJwoFp.js → computer-use-Bmaz333N.js} +3 -3
  7. package/dist/{config-CszD0vP3.js → config-CYEDnLJ2.js} +44 -18
  8. package/dist/conversation-binding-data-BHRpYbX3.js +121 -0
  9. package/dist/doctor-contract-api.js +590 -22
  10. package/dist/harness.js +29 -10
  11. package/dist/index.js +1134 -35
  12. package/dist/{media-understanding-provider-ZUQHN7gT.js → media-understanding-provider-BHOu9-Gg.js} +13 -8
  13. package/dist/media-understanding-provider.js +1 -1
  14. package/dist/{models-CAmfBWRl.js → models-DDmO1zwd.js} +2 -2
  15. package/dist/notification-correlation-Bo7KB3ks.js +35 -0
  16. package/dist/{plugin-app-cache-key-C9ILt7bl.js → plugin-app-cache-key-BrhVdeEf.js} +13 -5
  17. package/dist/{protocol-dh-ETiNd.js → protocol-2POPqAY4.js} +2 -1
  18. package/dist/{protocol-validators-B19q5BIX.js → protocol-validators-dZQ-UTOa.js} +205 -56
  19. package/dist/provider-capabilities-DzI8fgEu.js +6172 -0
  20. package/dist/provider-catalog.js +4 -2
  21. package/dist/{provider-B-OHpbD3.js → provider-zjPfx5Fs.js} +94 -23
  22. package/dist/provider.js +2 -2
  23. package/dist/{request-Dcd452Nk.js → request-BcJyl8KL.js} +2 -2
  24. package/dist/{run-attempt-CXxtFC9d.js → run-attempt-DNSNk_G8.js} +1250 -2992
  25. package/dist/{sandbox-guard-3tnjhjFb.js → sandbox-guard-DA2TQfZW.js} +5 -3
  26. package/dist/session-binding-C1ZXdP-x.js +627 -0
  27. package/dist/session-binding-meta-B7aEMU7g.js +6 -0
  28. package/dist/shared-client-4ICy3U6d.js +1853 -0
  29. package/dist/{side-question-C-DhgJOd.js → side-question-CMOpEhvY.js} +134 -142
  30. package/dist/{thread-lifecycle-DtT3-ehU.js → thread-lifecycle-qWE88Dn2.js} +803 -481
  31. package/dist/{web-search-provider.runtime-CkwxrXQj.js → web-search-provider.runtime-BSlriav6.js} +3 -3
  32. package/npm-shrinkwrap.json +34 -34
  33. package/openclaw.plugin.json +23 -3
  34. package/package.json +9 -6
  35. package/dist/client-CLrtWgrD.js +0 -729
  36. package/dist/client-factory-Bm6HsGob.js +0 -18
  37. package/dist/compact-aQJ0ZKhe.js +0 -278
  38. package/dist/conversation-binding-t0XMLz-j.js +0 -1557
  39. package/dist/provider-capabilities-D7hnTO76.js +0 -3201
  40. package/dist/rate-limit-cache-C7qmZ0Jh.js +0 -26
  41. package/dist/session-binding-HOuPt-E0.js +0 -338
  42. package/dist/shared-client-Dtx5i7Ez.js +0 -1013
@@ -1,19 +1,21 @@
1
- import { n as isJsonObject } from "./protocol-dh-ETiNd.js";
2
- import { d as resolveCodexAppServerRuntimeOptions, o as isCodexFastServiceTier } from "./config-CszD0vP3.js";
3
- import { n as listCodexAppServerModels, t as listAllCodexAppServerModels } from "./models-CAmfBWRl.js";
4
- import { l as summarizeCodexAccountUsage } from "./provider-B-OHpbD3.js";
5
- import { l as writeCodexAppServerBinding, n as clearCodexAppServerBinding, o as readCodexAppServerBinding } from "./session-binding-HOuPt-E0.js";
6
- import { i as describeControlFailure, r as CODEX_CONTROL_METHODS } from "./plugin-app-cache-key-C9ILt7bl.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-BPTiVNsW.js";
8
- import { n as resolveCodexNativeExecutionBlock, r as resolveCodexNativeSandboxBlock } from "./sandbox-guard-3tnjhjFb.js";
9
- import { _ 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-t0XMLz-j.js";
10
- import { t as requestCodexAppServerJson } from "./request-Dcd452Nk.js";
11
- import { n as installCodexComputerUse, r as readCodexComputerUseStatus } from "./computer-use-ClrJwoFp.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-zjPfx5Fs.js";
6
+ import { y as resolveCodexAppServerAuthProfileIdForAgent } from "./shared-client-4ICy3U6d.js";
7
+ import { a as normalizeCodexAppServerBindingModelProvider, n as bindingStoreKey, o as reclaimCurrentCodexSessionGeneration, s as sessionBindingIdentity } from "./session-binding-C1ZXdP-x.js";
8
+ import { r as CODEX_CONTROL_METHODS } from "./plugin-app-cache-key-BrhVdeEf.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-C968Kgin.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-eLM_mtC7.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-Bmaz333N.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) {
@@ -349,7 +350,7 @@ async function handleCodexPluginsSubcommand(ctx, rest, io) {
349
350
  if (normalized === "enable" || normalized === "disable") {
350
351
  if (args.length === 0) return buildPluginNamePickerReply(normalized, await io.readConfig());
351
352
  if (!target || args.length > 1) return { text: `Usage: /codex plugins ${normalized} <name>` };
352
- if (!canMutateCodexPlugins(ctx)) return { text: `Only an owner or operator.admin gateway client can run /codex plugins ${normalized}.` };
353
+ if (!canMutateCodexHost(ctx)) return { text: `Only an owner or operator.admin gateway client can run /codex plugins ${normalized}.` };
353
354
  const wantEnabled = normalized === "enable";
354
355
  if (!((await io.readConfig()).plugins ?? {})[target]) return { text: `Codex sub-plugin '${formatCodexDisplayText(target)}' is not configured. Run '/codex plugins list' to see configured plugins.` };
355
356
  await io.mutate((block) => {
@@ -440,10 +441,6 @@ function buildPluginNamePickerReply(verb, current) {
440
441
  presentation: buildCodexCommandPickerPresentation("Codex sub-plugins", `Pick a Codex sub-plugin to ${verb}:`, buttons)
441
442
  };
442
443
  }
443
- function canMutateCodexPlugins(ctx) {
444
- if (ctx.senderIsOwner === true) return true;
445
- return ctx.gatewayClientScopes?.includes("operator.admin") === true;
446
- }
447
444
  function buildPluginsHelp() {
448
445
  return [
449
446
  "Codex sub-plugin management (writes only to ~/.openclaw/openclaw.json, never to ~/.codex/config.toml):",
@@ -479,81 +476,16 @@ function formatPluginList(plugins, options = {}) {
479
476
  ].join("\n");
480
477
  }
481
478
  //#endregion
482
- //#region extensions/codex/src/command-rpc.ts
483
- function requestOptions(pluginConfig, limit, config) {
484
- const runtime = resolveCodexAppServerRuntimeOptions({ pluginConfig });
485
- return {
486
- limit,
487
- timeoutMs: runtime.requestTimeoutMs,
488
- startOptions: runtime.start,
489
- config
490
- };
491
- }
492
- async function codexControlRequest(pluginConfig, method, requestParams, options = {}) {
493
- const runtime = resolveCodexAppServerRuntimeOptions({ pluginConfig });
494
- return await requestCodexAppServerJson({
495
- method,
496
- requestParams,
497
- timeoutMs: runtime.requestTimeoutMs,
498
- startOptions: runtime.start,
499
- config: options.config,
500
- sessionKey: options.sessionKey,
501
- sessionId: options.sessionId,
502
- authProfileId: options.authProfileId,
503
- agentDir: options.agentDir,
504
- isolated: options.isolated
505
- });
506
- }
507
- async function safeCodexControlRequest(pluginConfig, method, requestParams, options = {}) {
508
- return await safeValue(async () => await codexControlRequest(pluginConfig, method, requestParams, options));
509
- }
510
- async function safeCodexModelList(pluginConfig, limit, config) {
511
- return await safeValue(async () => await listCodexAppServerModels(requestOptions(pluginConfig, limit, config)));
512
- }
513
- async function readCodexStatusProbes(pluginConfig, config) {
514
- const [models, account, limits, mcps, skills] = await Promise.all([
515
- safeCodexModelList(pluginConfig, 20, config),
516
- safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.account, { refreshToken: false }, { config }),
517
- safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.rateLimits, void 0, { config }),
518
- safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listMcpServers, { limit: 100 }, { config }),
519
- safeCodexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listSkills, {}, { config })
520
- ]);
521
- return {
522
- models,
523
- account,
524
- limits,
525
- mcps,
526
- skills
527
- };
528
- }
529
- async function safeValue(read) {
530
- try {
531
- return {
532
- ok: true,
533
- value: await read()
534
- };
535
- } catch (error) {
536
- return {
537
- ok: false,
538
- error: describeControlFailure(error)
539
- };
540
- }
541
- }
542
- //#endregion
543
479
  //#region extensions/codex/src/command-handlers.ts
544
480
  const defaultCodexCommandDeps = {
545
481
  codexControlRequest,
546
482
  listCodexAppServerModels: listAllCodexAppServerModels,
547
483
  readCodexStatusProbes,
548
- readCodexAppServerBinding,
549
484
  requestOptions,
550
485
  safeCodexControlRequest,
551
- writeCodexAppServerBinding,
552
- clearCodexAppServerBinding,
553
486
  readCodexComputerUseStatus,
554
487
  installCodexComputerUse,
555
488
  resolveCodexDefaultWorkspaceDir,
556
- startCodexConversationThread,
557
489
  readCodexConversationActiveTurn,
558
490
  setCodexConversationFastMode,
559
491
  setCodexConversationModel,
@@ -578,7 +510,7 @@ const CODEX_DIAGNOSTICS_CONFIRMATION_MAX_REQUESTS_PER_SCOPE = 100;
578
510
  const CODEX_DIAGNOSTICS_CONFIRMATION_MAX_SCOPES = 100;
579
511
  const CODEX_DIAGNOSTICS_SCOPE_FIELD_MAX_CHARS = 128;
580
512
  const CODEX_RESUME_SAFE_THREAD_ID_PATTERN = /^[A-Za-z0-9._:-]+$/;
581
- const CODEX_NATIVE_EXECUTION_SUBCOMMANDS = new Set([
513
+ const CODEX_NATIVE_EXECUTION_SUBCOMMANDS = /* @__PURE__ */ new Set([
582
514
  "bind",
583
515
  "resume",
584
516
  "steer",
@@ -588,6 +520,12 @@ const CODEX_NATIVE_EXECUTION_SUBCOMMANDS = new Set([
588
520
  "compact",
589
521
  "review"
590
522
  ]);
523
+ const CODEX_NATIVE_CONTROL_SUBCOMMANDS = /* @__PURE__ */ new Set([
524
+ ...CODEX_NATIVE_EXECUTION_SUBCOMMANDS,
525
+ "detach",
526
+ "unbind",
527
+ "stop"
528
+ ]);
591
529
  const lastCodexDiagnosticsUploadByThread = /* @__PURE__ */ new Map();
592
530
  const lastCodexDiagnosticsUploadByScope = /* @__PURE__ */ new Map();
593
531
  const pendingCodexDiagnosticsConfirmations = /* @__PURE__ */ new Map();
@@ -725,6 +663,7 @@ async function handleCodexSubcommand(ctx, options) {
725
663
  const [subcommand = "status", ...rest] = args;
726
664
  const normalized = subcommand.toLowerCase();
727
665
  if (normalized === "help") return { text: buildHelp() };
666
+ if (CODEX_NATIVE_CONTROL_SUBCOMMANDS.has(normalized) && !returnsBeforeNativeCodexExecution(normalized, rest) && !canMutateCodexHost(ctx)) return { text: CODEX_NATIVE_EXECUTION_AUTH_ERROR };
728
667
  const sandboxBlock = resolveCodexNativeCommandSandboxBlock(ctx, normalized, rest);
729
668
  if (sandboxBlock) return { text: sandboxBlock };
730
669
  if (normalized === "plugins") {
@@ -739,7 +678,7 @@ async function handleCodexSubcommand(ctx, options) {
739
678
  if (rest.length > 0) return { text: "Usage: /codex models" };
740
679
  return { text: formatModels(await deps.listCodexAppServerModels(deps.requestOptions(options.pluginConfig, 100, ctx.config))) };
741
680
  }
742
- 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(" ")) };
743
682
  if (normalized === "sessions") return { text: await buildCodexCliSessions(deps, rest) };
744
683
  if (normalized === "resume") return { text: await resumeThread(deps, ctx, options.pluginConfig, rest) };
745
684
  if (normalized === "bind") return await bindConversation(deps, ctx, options.pluginConfig, rest);
@@ -759,31 +698,43 @@ async function handleCodexSubcommand(ctx, options) {
759
698
  if (normalized === "model") return { text: await setConversationModel(deps, ctx, options.pluginConfig, rest) };
760
699
  if (normalized === "fast") {
761
700
  if (isMenuVerb(rest)) return buildCodexFastMenuReply();
762
- return { text: await setConversationFastMode(deps, ctx, options.pluginConfig, rest) };
701
+ return { text: await setConversationFastMode(deps, ctx, rest) };
763
702
  }
764
703
  if (normalized === "permissions") {
765
704
  if (isMenuVerb(rest)) return buildCodexPermissionsMenuReply();
766
- return { text: await setConversationPermissions(deps, ctx, options.pluginConfig, rest) };
705
+ return { text: await setConversationPermissions(deps, ctx, rest) };
767
706
  }
768
- if (normalized === "compact") return { text: await startThreadAction(deps, ctx, options.pluginConfig, CODEX_CONTROL_METHODS.compact, "compaction", rest) };
769
- 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) };
770
709
  if (normalized === "diagnostics") return await handleCodexDiagnosticsFeedback(deps, ctx, options.pluginConfig, rest.join(" "), "/codex diagnostics");
771
710
  if (normalized === "computer-use" || normalized === "computeruse") {
772
711
  if (isMenuVerb(rest)) return buildCodexComputerUseMenuReply();
773
- return { text: await handleComputerUseCommand(deps, options.pluginConfig, rest) };
712
+ return { text: await handleComputerUseCommand(deps, ctx, options.pluginConfig, rest) };
774
713
  }
775
714
  if (normalized === "mcp") {
776
715
  if (rest.length > 0) return { text: "Usage: /codex mcp" };
777
- 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") };
778
721
  }
779
722
  if (normalized === "skills") {
780
723
  if (rest.length > 0) return { text: "Usage: /codex skills" };
781
- 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
+ })) };
782
729
  }
783
730
  if (normalized === "account") {
784
731
  if (rest.length > 0) return { text: "Usage: /codex account" };
785
- 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)]);
786
- 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)]);
787
738
  return { text: formatAccount(account, limits, await readCodexAccountAuthOverview({
788
739
  ctx,
789
740
  pluginConfig: options.pluginConfig,
@@ -805,6 +756,7 @@ function resolveCodexNativeCommandSandboxBlock(ctx, subcommand, args) {
805
756
  });
806
757
  return resolveCodexNativeExecutionBlock({
807
758
  config: ctx.config,
759
+ agentId: ctx.agentId,
808
760
  sessionKey: ctx.sessionKey,
809
761
  sessionId: ctx.sessionId,
810
762
  surface: `/${["codex", subcommand].join(" ")}`
@@ -820,6 +772,8 @@ function returnsBeforeNativeCodexExecution(subcommand, args) {
820
772
  case "permissions": return args.length === 0 || args.length > 1 || parseCodexPermissionsModeArg(args[0]) === void 0;
821
773
  case "compact":
822
774
  case "review":
775
+ case "detach":
776
+ case "unbind":
823
777
  case "stop": return args.length > 0;
824
778
  default: return false;
825
779
  }
@@ -836,9 +790,10 @@ function returnsBeforeNativeCodexResume(args) {
836
790
  if (parsed.host) return !normalizedThreadId || parsed.bindHere !== true;
837
791
  return !normalizedThreadId || args.length !== 1;
838
792
  }
839
- async function handleComputerUseCommand(deps, pluginConfig, args) {
793
+ async function handleComputerUseCommand(deps, ctx, pluginConfig, args) {
840
794
  const parsed = parseComputerUseArgs(args);
841
795
  if (parsed.help) return ["Usage: /codex computer-use [status|install] [--source <marketplace-source>] [--marketplace-path <path>] [--marketplace <name>]", "Checks or installs the configured Codex Computer Use plugin through app-server."].join("\n");
796
+ if (parsed.action === "install" && !canMutateCodexHost(ctx)) return "Only an owner or operator.admin gateway client can configure Codex Computer Use.";
842
797
  const params = {
843
798
  pluginConfig,
844
799
  forceEnable: parsed.action === "install" || parsed.hasOverrides,
@@ -850,53 +805,54 @@ async function handleComputerUseCommand(deps, pluginConfig, args) {
850
805
  async function bindConversation(deps, ctx, pluginConfig, args) {
851
806
  const parsed = parseBindArgs(args);
852
807
  if (parsed.help) return { text: "Usage: /codex bind [thread-id] [--cwd <path>] [--model <model>] [--provider <provider>]" };
853
- if (!ctx.sessionFile) return { text: "Cannot bind Codex because this command did not include an OpenClaw session file." };
854
808
  const scope = resolveCodexConversationControlScope(ctx);
855
809
  const workspaceDir = parsed.cwd ?? deps.resolveCodexDefaultWorkspaceDir(pluginConfig);
856
- const authProfileId = (await deps.readCodexAppServerBinding(ctx.sessionFile, {
857
- 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,
858
817
  config: ctx.config
859
- }))?.authProfileId;
860
- const startParams = {
861
- pluginConfig,
862
- config: ctx.config,
863
- 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,
864
830
  workspaceDir,
831
+ agentId: scope.agentId,
865
832
  agentDir: scope.agentDir,
866
- sessionKey: ctx.sessionKey,
867
- agentId: scope.sessionAgentId,
868
- threadId: parsed.threadId,
869
- model: parsed.model,
870
- modelProvider: parsed.provider
871
- };
872
- if (authProfileId) startParams.authProfileId = authProfileId;
873
- const data = await deps.startCodexConversationThread(startParams);
874
- const threadId = (await deps.readCodexAppServerBinding(ctx.sessionFile, {
875
- agentDir: scope.agentDir,
876
- config: ctx.config
877
- }))?.threadId ?? parsed.threadId ?? "new thread";
878
- const summary = `Codex app-server thread ${formatCodexDisplayText(threadId)} in ${formatCodexDisplayText(workspaceDir)}`;
879
- let request;
880
- try {
881
- request = await ctx.requestConversationBinding({
882
- summary,
883
- detachHint: "/codex detach",
884
- data
885
- });
886
- } catch (error) {
887
- await deps.clearCodexAppServerBinding(ctx.sessionFile);
888
- throw error;
889
- }
890
- 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
+ });
891
848
  if (request.status === "pending") return request.reply;
892
- await deps.clearCodexAppServerBinding(ctx.sessionFile);
893
- 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.` };
894
851
  }
895
852
  async function detachConversation(deps, ctx) {
896
853
  const data = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
897
854
  const detached = await ctx.detachConversationBinding();
898
- if (data?.kind === "codex-app-server-session") await deps.clearCodexAppServerBinding(data.sessionFile);
899
- 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" });
900
856
  return detached.removed ? "Detached this conversation from Codex." : "No Codex conversation binding was attached.";
901
857
  }
902
858
  async function describeConversationBinding(deps, ctx) {
@@ -911,11 +867,9 @@ async function describeConversationBinding(deps, ctx) {
911
867
  `- Workspace: ${formatCodexDisplayText(data.cwd ?? "unknown")}`,
912
868
  "- Active run: not tracked"
913
869
  ].join("\n");
914
- const threadBinding = await deps.readCodexAppServerBinding(data.sessionFile, {
915
- agentDir: data.agentDir,
916
- config: ctx.config
917
- });
918
- 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);
919
873
  return [
920
874
  "Codex conversation binding:",
921
875
  `- Thread: ${formatCodexDisplayText(threadBinding?.threadId ?? "unknown")}`,
@@ -924,13 +878,17 @@ async function describeConversationBinding(deps, ctx) {
924
878
  `- Fast: ${isCodexFastServiceTier(threadBinding?.serviceTier) ? "on" : "off"}`,
925
879
  `- Permissions: ${threadBinding ? formatPermissionsMode(threadBinding) : "default"}`,
926
880
  `- Active run: ${formatCodexDisplayText(active ? active.turnId : "none")}`,
927
- `- Session: ${formatCodexDisplayText(data.sessionFile)}`
881
+ `- Binding: ${formatCodexDisplayText(data.bindingId)}`
928
882
  ].join("\n");
929
883
  }
930
- async function buildThreads(deps, pluginConfig, filter) {
884
+ async function buildThreads(deps, ctx, pluginConfig, filter) {
885
+ const scope = await resolveCommandAppServerScope(deps, ctx);
931
886
  return formatThreads(await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.listThreads, {
932
887
  limit: 10,
933
888
  ...filter.trim() ? { searchTerm: filter.trim() } : {}
889
+ }, {
890
+ config: ctx.config,
891
+ ...scope
934
892
  }));
935
893
  }
936
894
  async function buildCodexCliSessions(deps, args) {
@@ -948,20 +906,58 @@ async function resumeThread(deps, ctx, pluginConfig, args) {
948
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>";
949
907
  if (parsed.host) return await bindCodexCliNodeSession(deps, ctx, parsed);
950
908
  if (!normalizedThreadId || args.length !== 1) return "Usage: /codex resume <thread-id>";
951
- if (!ctx.sessionFile) return "Cannot attach a Codex thread because this command did not include an OpenClaw session file.";
952
- const response = await deps.codexControlRequest(pluginConfig, CODEX_CONTROL_METHODS.resumeThread, {
953
- threadId: normalizedThreadId,
954
- 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
955
916
  });
956
- const thread = isJsonObject(response) && isJsonObject(response.thread) ? response.thread : {};
957
- const effectiveThreadId = readString$1(thread, "id") ?? normalizedThreadId;
958
- await deps.writeCodexAppServerBinding(ctx.sessionFile, {
959
- threadId: effectiveThreadId,
960
- cwd: readString$1(thread, "cwd") ?? "",
961
- model: isJsonObject(response) ? readString$1(response, "model") : void 0,
962
- 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)}.`;
963
960
  });
964
- return `Attached this OpenClaw session to Codex thread ${formatCodexDisplayText(effectiveThreadId)}.`;
965
961
  }
966
962
  async function bindCodexCliNodeSession(deps, ctx, parsed) {
967
963
  if (!parsed.threadId || !parsed.host || parsed.bindHere !== true) return "Usage: /codex resume <session-id> --host <node> --bind here";
@@ -972,9 +968,11 @@ async function bindCodexCliNodeSession(deps, ctx, parsed) {
972
968
  if (!resolved.session) return `No Codex CLI session ${formatCodexDisplayText(parsed.threadId)} was found on ${formatCodexDisplayText(parsed.host)}.`;
973
969
  const nodeId = resolved.node.nodeId;
974
970
  if (!nodeId) return "Cannot bind Codex CLI session because the selected node did not include a node id.";
971
+ const scope = resolveCodexConversationControlScope(ctx);
975
972
  const data = createCodexCliNodeConversationBindingData({
976
973
  nodeId,
977
974
  sessionId: parsed.threadId,
975
+ agentId: scope.agentId,
978
976
  cwd: resolved.session?.cwd
979
977
  });
980
978
  const summary = `Codex CLI session ${formatCodexDisplayText(parsed.threadId)} on ${formatCodexDisplayText(nodeId)}`;
@@ -989,9 +987,10 @@ async function bindCodexCliNodeSession(deps, ctx, parsed) {
989
987
  }
990
988
  async function stopConversationTurn(deps, ctx, pluginConfig) {
991
989
  const target = await resolveControlTarget(ctx);
992
- 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.";
993
991
  return (await deps.stopCodexConversationTurn({
994
- sessionFile: target.sessionFile,
992
+ identity: target.identity,
993
+ bindingStore: deps.bindingStore,
995
994
  pluginConfig,
996
995
  agentDir: target.agentDir,
997
996
  config: ctx.config
@@ -999,11 +998,12 @@ async function stopConversationTurn(deps, ctx, pluginConfig) {
999
998
  }
1000
999
  async function steerConversationTurn(deps, ctx, pluginConfig, message) {
1001
1000
  const target = await resolveControlTarget(ctx);
1002
- 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.";
1003
1002
  return (await deps.steerCodexConversationTurn({
1004
- sessionFile: target.sessionFile,
1005
- pluginConfig,
1003
+ identity: target.identity,
1004
+ bindingStore: deps.bindingStore,
1006
1005
  message,
1006
+ pluginConfig,
1007
1007
  agentDir: target.agentDir,
1008
1008
  config: ctx.config
1009
1009
  })).message;
@@ -1011,77 +1011,100 @@ async function steerConversationTurn(deps, ctx, pluginConfig, message) {
1011
1011
  async function setConversationModel(deps, ctx, pluginConfig, args) {
1012
1012
  if (args.length > 1) return "Usage: /codex model <model>";
1013
1013
  const target = await resolveControlTarget(ctx);
1014
- 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.";
1015
1015
  const [model = ""] = args;
1016
1016
  const normalized = model.trim();
1017
1017
  if (!normalized) {
1018
- const binding = await deps.readCodexAppServerBinding(target.sessionFile, {
1019
- agentDir: target.agentDir,
1020
- config: ctx.config
1021
- });
1018
+ const binding = await deps.bindingStore.read(target.identity);
1022
1019
  return binding?.model ? `Codex model: ${formatCodexDisplayText(binding.model)}` : "Usage: /codex model <model>";
1023
1020
  }
1024
1021
  return await deps.setCodexConversationModel({
1025
- sessionFile: target.sessionFile,
1022
+ identity: target.identity,
1023
+ bindingStore: deps.bindingStore,
1026
1024
  pluginConfig,
1027
1025
  model: normalized,
1028
1026
  agentDir: target.agentDir,
1029
1027
  config: ctx.config
1030
1028
  });
1031
1029
  }
1032
- async function setConversationFastMode(deps, ctx, pluginConfig, args) {
1030
+ async function setConversationFastMode(deps, ctx, args) {
1033
1031
  if (args.length > 1) return "Usage: /codex fast [on|off|status]";
1034
1032
  const target = await resolveControlTarget(ctx);
1035
- 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.";
1036
1034
  const value = args[0];
1037
1035
  const parsed = parseCodexFastModeArg(value);
1038
1036
  if (value && parsed == null && value.trim().toLowerCase() !== "status") return "Usage: /codex fast [on|off|status]";
1039
1037
  return await deps.setCodexConversationFastMode({
1040
- sessionFile: target.sessionFile,
1041
- pluginConfig,
1042
- enabled: parsed,
1043
- agentDir: target.agentDir,
1044
- config: ctx.config
1038
+ identity: target.identity,
1039
+ bindingStore: deps.bindingStore,
1040
+ enabled: parsed
1045
1041
  });
1046
1042
  }
1047
- async function setConversationPermissions(deps, ctx, pluginConfig, args) {
1043
+ async function setConversationPermissions(deps, ctx, args) {
1048
1044
  if (args.length > 1) return "Usage: /codex permissions [default|yolo|status]";
1049
1045
  const target = await resolveControlTarget(ctx);
1050
- if (!target) return "Cannot set Codex permissions because this command did not include an OpenClaw session file.";
1046
+ if (!target) return "Cannot set Codex permissions because this command did not include a stable binding identity.";
1051
1047
  const value = args[0];
1052
1048
  const parsed = parseCodexPermissionsModeArg(value);
1053
1049
  if (value && !parsed && value.trim().toLowerCase() !== "status") return "Usage: /codex permissions [default|yolo|status]";
1054
1050
  return await deps.setCodexConversationPermissions({
1055
- sessionFile: target.sessionFile,
1056
- pluginConfig,
1057
- mode: parsed,
1058
- agentDir: target.agentDir,
1059
- config: ctx.config
1051
+ identity: target.identity,
1052
+ bindingStore: deps.bindingStore,
1053
+ mode: parsed
1060
1054
  });
1061
1055
  }
1062
1056
  async function resolveControlTarget(ctx) {
1063
1057
  const data = readCodexConversationBindingData(await ctx.getCurrentConversationBinding());
1064
1058
  const scope = resolveCodexConversationControlScope(ctx);
1065
1059
  if (data?.kind === "codex-app-server-session") return {
1066
- sessionFile: data.sessionFile,
1067
- 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
1068
1064
  };
1069
- return ctx.sessionFile ? {
1070
- 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,
1071
1073
  agentDir: scope.agentDir
1072
1074
  } : void 0;
1073
1075
  }
1074
- async function resolveControlSessionFile(ctx) {
1075
- 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
+ };
1076
1098
  }
1077
1099
  function resolveCodexConversationControlScope(ctx) {
1078
1100
  const { sessionAgentId } = resolveSessionAgentIds({
1079
1101
  sessionKey: ctx.sessionKey,
1102
+ agentId: ctx.agentId,
1080
1103
  config: ctx.config
1081
1104
  });
1082
1105
  return {
1083
- agentDir: resolveAgentDir(ctx.config, sessionAgentId),
1084
- sessionAgentId
1106
+ agentId: sessionAgentId,
1107
+ agentDir: resolveAgentDir(ctx.config, sessionAgentId)
1085
1108
  };
1086
1109
  }
1087
1110
  async function handleCodexDiagnosticsFeedback(deps, ctx, pluginConfig, args, commandPrefix) {
@@ -1095,7 +1118,7 @@ async function handleCodexDiagnosticsFeedback(deps, ctx, pluginConfig, args, com
1095
1118
  return await requestCodexDiagnosticsFeedbackApproval(deps, ctx, parsed.note, commandPrefix);
1096
1119
  }
1097
1120
  async function requestCodexDiagnosticsFeedbackApproval(deps, ctx, note, commandPrefix) {
1098
- 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." };
1099
1122
  const targets = await resolveCodexDiagnosticsTargets(deps, ctx);
1100
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") };
1101
1124
  const now = Date.now();
@@ -1151,7 +1174,7 @@ async function requestCodexDiagnosticsFeedbackApproval(deps, ctx, note, commandP
1151
1174
  };
1152
1175
  }
1153
1176
  async function previewCodexDiagnosticsFeedbackApproval(deps, ctx, note) {
1154
- 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.";
1155
1178
  const targets = await resolveCodexDiagnosticsTargets(deps, ctx);
1156
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");
1157
1180
  const cooldownMessage = readCodexDiagnosticsTargetsCooldownMessage(targets, ctx, Date.now(), { includeThreadId: false });
@@ -1175,10 +1198,10 @@ async function confirmCodexDiagnosticsFeedback(deps, ctx, pluginConfig, token) {
1175
1198
  const scopeMismatch = readCodexDiagnosticsScopeMismatch(pending, ctx);
1176
1199
  if (scopeMismatch) return scopeMismatch.confirmMessage;
1177
1200
  deletePendingCodexDiagnosticsConfirmation(token);
1178
- if (!pending.privateRouted && !await hasAnyCodexDiagnosticsSessionFile(ctx)) return "Cannot send Codex diagnostics because this command did not include an OpenClaw session file.";
1179
- 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);
1180
1203
  if (!codexDiagnosticsTargetsMatch(pending.targets, currentTargets)) return "The Codex diagnostics sessions changed before confirmation. Run /diagnostics again for the current threads.";
1181
- return await sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, pending.note ?? "", pending.targets);
1204
+ return await sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, pending.note ?? "", currentTargets, { cooldownScope: pending.scopeKey });
1182
1205
  }
1183
1206
  function cancelCodexDiagnosticsFeedback(ctx, token) {
1184
1207
  const pending = readPendingCodexDiagnosticsConfirmation(token, Date.now());
@@ -1196,15 +1219,15 @@ function cancelCodexDiagnosticsFeedback(ctx, token) {
1196
1219
  ].join("\n");
1197
1220
  }
1198
1221
  async function sendCodexDiagnosticsFeedbackForContext(deps, ctx, pluginConfig, note) {
1199
- 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.";
1200
1223
  const targets = await resolveCodexDiagnosticsTargets(deps, ctx);
1201
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");
1202
1225
  return await sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, note, targets);
1203
1226
  }
1204
- async function sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, note, targets) {
1227
+ async function sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, note, targets, options = {}) {
1205
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");
1206
1229
  const now = Date.now();
1207
- const cooldownMessage = readCodexDiagnosticsTargetsCooldownMessage(targets, ctx, now);
1230
+ const cooldownMessage = readCodexDiagnosticsTargetsCooldownMessage(targets, ctx, now, { cooldownScope: options.cooldownScope });
1208
1231
  if (cooldownMessage) return cooldownMessage;
1209
1232
  const reason = normalizeDiagnosticsReason(note);
1210
1233
  const sent = [];
@@ -1216,6 +1239,12 @@ async function sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, n
1216
1239
  includeLogs: true,
1217
1240
  tags: buildDiagnosticsTags(ctx),
1218
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 } : {}
1219
1248
  });
1220
1249
  if (!response.ok) {
1221
1250
  failed.push({
@@ -1229,20 +1258,20 @@ async function sendCodexDiagnosticsFeedbackForTargets(deps, ctx, pluginConfig, n
1229
1258
  ...target,
1230
1259
  threadId: responseThreadId ?? target.threadId
1231
1260
  });
1232
- recordCodexDiagnosticsUpload(target.threadId, ctx, now);
1261
+ recordCodexDiagnosticsUpload(target.threadId, ctx, now, options.cooldownScope);
1233
1262
  }
1234
1263
  return formatCodexDiagnosticsUploadResult(sent, failed);
1235
1264
  }
1236
- async function hasAnyCodexDiagnosticsSessionFile(ctx) {
1237
- if (await resolveControlSessionFile(ctx)) return true;
1238
- 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));
1239
1268
  }
1240
1269
  async function resolveCodexDiagnosticsTargets(deps, ctx) {
1241
- const activeSessionFile = await resolveControlSessionFile(ctx);
1270
+ const activeTarget = await resolveControlTarget(ctx);
1242
1271
  const candidates = [];
1243
- if (activeSessionFile) candidates.push({
1244
- threadId: "",
1245
- sessionFile: activeSessionFile,
1272
+ if (activeTarget) candidates.push({
1273
+ identity: activeTarget.identity,
1274
+ agentDir: activeTarget.agentDir,
1246
1275
  sessionKey: ctx.sessionKey,
1247
1276
  sessionId: ctx.sessionId,
1248
1277
  channel: ctx.channel,
@@ -1252,10 +1281,17 @@ async function resolveCodexDiagnosticsTargets(deps, ctx) {
1252
1281
  threadParentId: ctx.threadParentId
1253
1282
  });
1254
1283
  for (const session of ctx.diagnosticsSessions ?? []) {
1255
- 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
+ });
1256
1292
  candidates.push({
1257
- threadId: "",
1258
- sessionFile: session.sessionFile,
1293
+ identity,
1294
+ agentDir: resolveAgentDir(ctx.config, identity.agentId),
1259
1295
  sessionKey: session.sessionKey,
1260
1296
  sessionId: session.sessionId,
1261
1297
  channel: session.channel,
@@ -1265,38 +1301,50 @@ async function resolveCodexDiagnosticsTargets(deps, ctx) {
1265
1301
  threadParentId: session.threadParentId
1266
1302
  });
1267
1303
  }
1268
- const seenSessionFiles = /* @__PURE__ */ new Set();
1304
+ const seenBindingKeys = /* @__PURE__ */ new Set();
1269
1305
  const seenThreadIds = /* @__PURE__ */ new Set();
1270
1306
  const targets = [];
1271
1307
  for (const candidate of candidates) {
1272
- if (seenSessionFiles.has(candidate.sessionFile)) continue;
1273
- seenSessionFiles.add(candidate.sessionFile);
1274
- 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);
1275
1312
  if (!binding?.threadId || seenThreadIds.has(binding.threadId)) continue;
1276
1313
  seenThreadIds.add(binding.threadId);
1277
- targets.push({
1278
- ...candidate,
1279
- threadId: binding.threadId
1280
- });
1314
+ targets.push(resolveCodexDiagnosticsTarget(candidate, binding, ctx.config));
1281
1315
  }
1282
1316
  return targets;
1283
1317
  }
1284
- async function resolvePendingCodexDiagnosticsTargets(deps, targets) {
1318
+ async function resolvePendingCodexDiagnosticsTargets(deps, targets, config) {
1285
1319
  const resolved = [];
1286
1320
  for (const target of targets) {
1287
- const binding = await deps.readCodexAppServerBinding(target.sessionFile);
1321
+ const binding = await deps.bindingStore.read(target.identity);
1288
1322
  if (!binding?.threadId) continue;
1289
- resolved.push({
1290
- ...target,
1291
- threadId: binding.threadId
1292
- });
1323
+ resolved.push(resolveCodexDiagnosticsTarget(target, binding, config));
1293
1324
  }
1294
1325
  return resolved;
1295
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
+ }
1296
1339
  function codexDiagnosticsTargetsMatch(expected, actual) {
1297
- const expectedThreadIds = expected.map((target) => target.threadId).toSorted();
1298
- const actualThreadIds = actual.map((target) => target.threadId).toSorted();
1299
- 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]);
1300
1348
  }
1301
1349
  function formatCodexDiagnosticsUploadResult(sent, failed) {
1302
1350
  const lines = [];
@@ -1498,7 +1546,7 @@ function readCodexDiagnosticsTargetsCooldownMessage(targets, ctx, now, options =
1498
1546
  return `Codex diagnostics were already sent for thread ${formatCodexThreadIdForDisplay(target.threadId)} recently. Try again in ${Math.ceil(cooldownMs / 1e3)}s.`;
1499
1547
  }
1500
1548
  }
1501
- const scopeCooldownMs = readCodexDiagnosticsScopeCooldownMs(readCodexDiagnosticsCooldownScope(ctx), now);
1549
+ const scopeCooldownMs = readCodexDiagnosticsScopeCooldownMs(options.cooldownScope ?? readCodexDiagnosticsCooldownScope(ctx), now);
1502
1550
  if (scopeCooldownMs > 0) return `Codex diagnostics were already sent for this account or channel recently. Try again in ${Math.ceil(scopeCooldownMs / 1e3)}s.`;
1503
1551
  }
1504
1552
  function readCodexDiagnosticsScopeCooldownMs(scope, now) {
@@ -1508,9 +1556,9 @@ function readCodexDiagnosticsScopeCooldownMs(scope, now) {
1508
1556
  if (remainingMs === 0) lastCodexDiagnosticsUploadByScope.delete(scope);
1509
1557
  return remainingMs;
1510
1558
  }
1511
- function recordCodexDiagnosticsUpload(threadId, ctx, now) {
1559
+ function recordCodexDiagnosticsUpload(threadId, ctx, now, cooldownScope) {
1512
1560
  pruneCodexDiagnosticsCooldowns(now);
1513
- recordBoundedCodexDiagnosticsCooldown(lastCodexDiagnosticsUploadByScope, readCodexDiagnosticsCooldownScope(ctx), CODEX_DIAGNOSTICS_COOLDOWN_MAX_SCOPES, now);
1561
+ recordBoundedCodexDiagnosticsCooldown(lastCodexDiagnosticsUploadByScope, cooldownScope ?? readCodexDiagnosticsCooldownScope(ctx), CODEX_DIAGNOSTICS_COOLDOWN_MAX_SCOPES, now);
1514
1562
  recordBoundedCodexDiagnosticsCooldown(lastCodexDiagnosticsUploadByThread, threadId, CODEX_DIAGNOSTICS_COOLDOWN_MAX_THREADS, now);
1515
1563
  }
1516
1564
  function recordBoundedCodexDiagnosticsCooldown(map, key, maxSize, now) {
@@ -1558,24 +1606,17 @@ function normalizeCodexDiagnosticsScopeField(value) {
1558
1606
  if (normalized.length <= CODEX_DIAGNOSTICS_SCOPE_FIELD_MAX_CHARS) return normalized;
1559
1607
  return `sha256:${crypto.createHash("sha256").update(normalized).digest("hex")}`;
1560
1608
  }
1561
- 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";
1562
1611
  if (args.length > 0) return `Usage: /codex ${label === "compaction" ? "compact" : label}`;
1563
1612
  const target = await resolveControlTarget(ctx);
1564
- if (!target) return `Cannot start Codex ${label} because this command did not include an OpenClaw session file.`;
1565
- const binding = await deps.readCodexAppServerBinding(target.sessionFile, {
1566
- agentDir: target.agentDir,
1567
- config: ctx.config
1568
- });
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);
1569
1615
  if (!binding?.threadId) return `No Codex thread is attached to this OpenClaw session yet.`;
1570
- 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" ? {
1571
1617
  threadId: binding.threadId,
1572
1618
  target: { type: "uncommittedChanges" }
1573
- }, {
1574
- agentDir: target.agentDir,
1575
- authProfileId: binding.authProfileId,
1576
- config: ctx.config
1577
- });
1578
- else await deps.codexControlRequest(pluginConfig, method, { threadId: binding.threadId }, {
1619
+ } : { threadId: binding.threadId }, {
1579
1620
  agentDir: target.agentDir,
1580
1621
  authProfileId: binding.authProfileId,
1581
1622
  config: ctx.config