oh-my-codex 0.18.14 → 0.18.16

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 (156) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +10 -7
  4. package/dist/agents/__tests__/native-config.test.js +37 -0
  5. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  6. package/dist/agents/native-config.d.ts.map +1 -1
  7. package/dist/agents/native-config.js +10 -1
  8. package/dist/agents/native-config.js.map +1 -1
  9. package/dist/cli/__tests__/autoresearch-guided.test.js +3 -0
  10. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  11. package/dist/cli/__tests__/doctor-artifact-ownership.test.js +63 -0
  12. package/dist/cli/__tests__/doctor-artifact-ownership.test.js.map +1 -1
  13. package/dist/cli/__tests__/doctor-explore-deprecated.test.d.ts +2 -0
  14. package/dist/cli/__tests__/doctor-explore-deprecated.test.d.ts.map +1 -0
  15. package/dist/cli/__tests__/doctor-explore-deprecated.test.js +33 -0
  16. package/dist/cli/__tests__/doctor-explore-deprecated.test.js.map +1 -0
  17. package/dist/cli/__tests__/doctor-warning-copy.test.js +131 -2
  18. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  19. package/dist/cli/__tests__/mcp-parity.test.js +40 -0
  20. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  21. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.d.ts +2 -0
  22. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.d.ts.map +1 -0
  23. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +64 -0
  24. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -0
  25. package/dist/cli/__tests__/session-search-help.test.js +3 -1
  26. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  27. package/dist/cli/__tests__/session-search.test.js +55 -1
  28. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  29. package/dist/cli/__tests__/setup-agents-overwrite.test.js +35 -0
  30. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  31. package/dist/cli/__tests__/update.test.js +80 -2
  32. package/dist/cli/__tests__/update.test.js.map +1 -1
  33. package/dist/cli/__tests__/url.test.d.ts +2 -0
  34. package/dist/cli/__tests__/url.test.d.ts.map +1 -0
  35. package/dist/cli/__tests__/url.test.js +82 -0
  36. package/dist/cli/__tests__/url.test.js.map +1 -0
  37. package/dist/cli/autoresearch-intake.d.ts.map +1 -1
  38. package/dist/cli/autoresearch-intake.js +2 -0
  39. package/dist/cli/autoresearch-intake.js.map +1 -1
  40. package/dist/cli/doctor.d.ts +17 -1
  41. package/dist/cli/doctor.d.ts.map +1 -1
  42. package/dist/cli/doctor.js +203 -38
  43. package/dist/cli/doctor.js.map +1 -1
  44. package/dist/cli/index.d.ts +2 -2
  45. package/dist/cli/index.d.ts.map +1 -1
  46. package/dist/cli/index.js +7 -1
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  49. package/dist/cli/plugin-marketplace.js +21 -62
  50. package/dist/cli/plugin-marketplace.js.map +1 -1
  51. package/dist/cli/session-search.d.ts +6 -0
  52. package/dist/cli/session-search.d.ts.map +1 -1
  53. package/dist/cli/session-search.js +104 -4
  54. package/dist/cli/session-search.js.map +1 -1
  55. package/dist/cli/update.d.ts.map +1 -1
  56. package/dist/cli/update.js +20 -4
  57. package/dist/cli/update.js.map +1 -1
  58. package/dist/cli/url.d.ts +8 -0
  59. package/dist/cli/url.d.ts.map +1 -0
  60. package/dist/cli/url.js +69 -0
  61. package/dist/cli/url.js.map +1 -0
  62. package/dist/config/__tests__/codex-hooks.test.js +20 -4
  63. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  64. package/dist/config/codex-hooks.d.ts.map +1 -1
  65. package/dist/config/codex-hooks.js +1 -4
  66. package/dist/config/codex-hooks.js.map +1 -1
  67. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +363 -0
  68. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  69. package/dist/hud/__tests__/state.test.js +121 -0
  70. package/dist/hud/__tests__/state.test.js.map +1 -1
  71. package/dist/hud/state.d.ts.map +1 -1
  72. package/dist/hud/state.js +33 -2
  73. package/dist/hud/state.js.map +1 -1
  74. package/dist/hud/types.d.ts +1 -1
  75. package/dist/hud/types.d.ts.map +1 -1
  76. package/dist/index.d.ts +1 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +1 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/mcp/__tests__/bootstrap.test.js +24 -1
  81. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  82. package/dist/mcp/__tests__/state-paths.test.js +53 -0
  83. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  84. package/dist/mcp/bootstrap.d.ts +2 -0
  85. package/dist/mcp/bootstrap.d.ts.map +1 -1
  86. package/dist/mcp/bootstrap.js +32 -6
  87. package/dist/mcp/bootstrap.js.map +1 -1
  88. package/dist/mcp/state-paths.d.ts.map +1 -1
  89. package/dist/mcp/state-paths.js +11 -5
  90. package/dist/mcp/state-paths.js.map +1 -1
  91. package/dist/notifications/__tests__/http-client.test.js +9 -0
  92. package/dist/notifications/__tests__/http-client.test.js.map +1 -1
  93. package/dist/notifications/http-client.js +1 -1
  94. package/dist/notifications/http-client.js.map +1 -1
  95. package/dist/scripts/__tests__/codex-native-hook.test.js +600 -7
  96. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  97. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  98. package/dist/scripts/codex-native-hook.js +278 -8
  99. package/dist/scripts/codex-native-hook.js.map +1 -1
  100. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +1 -0
  101. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  102. package/dist/scripts/notify-hook/team-leader-nudge.js +279 -86
  103. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  104. package/dist/session-history/__tests__/friction.test.d.ts +2 -0
  105. package/dist/session-history/__tests__/friction.test.d.ts.map +1 -0
  106. package/dist/session-history/__tests__/friction.test.js +107 -0
  107. package/dist/session-history/__tests__/friction.test.js.map +1 -0
  108. package/dist/session-history/friction.d.ts +68 -0
  109. package/dist/session-history/friction.d.ts.map +1 -0
  110. package/dist/session-history/friction.js +365 -0
  111. package/dist/session-history/friction.js.map +1 -0
  112. package/dist/sidecar/__tests__/args.test.d.ts +2 -0
  113. package/dist/sidecar/__tests__/args.test.d.ts.map +1 -0
  114. package/dist/sidecar/__tests__/args.test.js +18 -0
  115. package/dist/sidecar/__tests__/args.test.js.map +1 -0
  116. package/dist/sidecar/index.d.ts.map +1 -1
  117. package/dist/sidecar/index.js +12 -4
  118. package/dist/sidecar/index.js.map +1 -1
  119. package/dist/ultragoal/__tests__/artifacts.test.js +150 -0
  120. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  121. package/dist/ultragoal/artifacts.d.ts +8 -0
  122. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  123. package/dist/ultragoal/artifacts.js +108 -8
  124. package/dist/ultragoal/artifacts.js.map +1 -1
  125. package/dist/url-reader/__tests__/url-reader.test.d.ts +2 -0
  126. package/dist/url-reader/__tests__/url-reader.test.d.ts.map +1 -0
  127. package/dist/url-reader/__tests__/url-reader.test.js +433 -0
  128. package/dist/url-reader/__tests__/url-reader.test.js.map +1 -0
  129. package/dist/url-reader/index.d.ts +4 -0
  130. package/dist/url-reader/index.d.ts.map +1 -0
  131. package/dist/url-reader/index.js +522 -0
  132. package/dist/url-reader/index.js.map +1 -0
  133. package/dist/url-reader/types.d.ts +41 -0
  134. package/dist/url-reader/types.d.ts.map +1 -0
  135. package/dist/url-reader/types.js +2 -0
  136. package/dist/url-reader/types.js.map +1 -0
  137. package/dist/utils/agents-md.d.ts.map +1 -1
  138. package/dist/utils/agents-md.js +3 -0
  139. package/dist/utils/agents-md.js.map +1 -1
  140. package/dist/utils/platform-command.d.ts +7 -1
  141. package/dist/utils/platform-command.d.ts.map +1 -1
  142. package/dist/utils/platform-command.js +12 -1
  143. package/dist/utils/platform-command.js.map +1 -1
  144. package/dist/vscode/__tests__/index.test.d.ts +2 -0
  145. package/dist/vscode/__tests__/index.test.d.ts.map +1 -0
  146. package/dist/vscode/__tests__/index.test.js +236 -0
  147. package/dist/vscode/__tests__/index.test.js.map +1 -0
  148. package/dist/vscode/index.d.ts +76 -0
  149. package/dist/vscode/index.d.ts.map +1 -0
  150. package/dist/vscode/index.js +196 -0
  151. package/dist/vscode/index.js.map +1 -0
  152. package/package.json +2 -2
  153. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  154. package/src/scripts/__tests__/codex-native-hook.test.ts +692 -24
  155. package/src/scripts/codex-native-hook.ts +313 -7
  156. package/src/scripts/notify-hook/team-leader-nudge.ts +298 -91
@@ -13,7 +13,7 @@ import { parse as parseToml } from "@iarna/toml";
13
13
  import { getBuiltinExploreHarnessUnsupportedReason, resolvePackagedExploreHarnessCommand, EXPLORE_BIN_ENV, } from "./explore.js";
14
14
  import { getPackageRoot } from "../utils/package.js";
15
15
  import { hasLegacyOmxTeamRunTable, getModelContextRecommendation, } from "../config/generator.js";
16
- import { MANAGED_HOOK_EVENTS, buildManagedCodexNativeHookCommand, discoverCodexHookConfigPaths, getManagedCodexHookCommandsForEvent, getMissingManagedCodexHookEvents, hasCodexHooksJsonTopLevelState, } from "../config/codex-hooks.js";
16
+ import { MANAGED_HOOK_EVENTS, buildManagedCodexNativeHookCommand, discoverCodexHookConfigPaths, resolveWindowsPowerShellPath, getManagedCodexHookCommandsForEvent, getMissingManagedCodexHookEvents, hasCodexHooksJsonTopLevelState, } from "../config/codex-hooks.js";
17
17
  import { OMX_FIRST_PARTY_MCP_SERVER_NAMES } from "../config/omx-first-party-mcp.js";
18
18
  import { getDefaultBridge, isBridgeEnabled } from "../runtime/bridge.js";
19
19
  import { OMX_EXPLORE_CMD_ENV, isExploreCommandRoutingEnabled, } from "../hooks/explore-routing.js";
@@ -163,7 +163,10 @@ export async function doctor(options = {}) {
163
163
  // Check 2: Node.js version
164
164
  checks.push(checkNodeVersion());
165
165
  // Check 2.5: Explore harness readiness
166
- checks.push(checkExploreHarness());
166
+ const exploreRoutingState = await resolveExploreRoutingState(paths.configPath);
167
+ checks.push(checkExploreHarness(process.platform, process.env, {
168
+ exploreRoutingEnabled: exploreRoutingState.enabled,
169
+ }));
167
170
  // Check 3: Codex home directory
168
171
  checks.push(checkDirectory("Codex home", paths.codexHomeDir));
169
172
  // Check 4: Config file
@@ -187,9 +190,13 @@ export async function doctor(options = {}) {
187
190
  if (runtimeMirrorCheck)
188
191
  checks.push(runtimeMirrorCheck);
189
192
  // Check 4.5: Explore routing default
190
- checks.push(await checkExploreRouting(paths.configPath));
193
+ checks.push(checkExploreRoutingFromState(exploreRoutingState));
191
194
  // Check 4.6: Lore commit guard default
192
195
  checks.push(await checkLoreCommitGuard(paths.configPath));
196
+ // Check 4.7: External process guards
197
+ const externalProcessGuardCheck = await checkExternalCodexProcessGuards();
198
+ if (externalProcessGuardCheck)
199
+ checks.push(externalProcessGuardCheck);
193
200
  // Check 5: Prompts installed
194
201
  checks.push(await checkPrompts(paths.promptsDir, scopeResolution.installMode));
195
202
  // Check 6: Skills installed
@@ -582,7 +589,16 @@ function checkNodeVersion() {
582
589
  message: `v${process.versions.node} (need >= 20)`,
583
590
  };
584
591
  }
585
- export function checkExploreHarness(platform = process.platform, env = process.env) {
592
+ export function checkExploreHarness(platform = process.platform, env = process.env, options = {}) {
593
+ const override = env[EXPLORE_BIN_ENV]?.trim();
594
+ const exploreRoutingEnabled = options.exploreRoutingEnabled ?? isExploreCommandRoutingEnabled(env);
595
+ if (!override && !exploreRoutingEnabled) {
596
+ return {
597
+ name: "Explore Harness",
598
+ status: "pass",
599
+ message: "skipped: omx explore is hard-deprecated and explore routing is disabled by default; use omx sparkshell for shell-native read-only evidence",
600
+ };
601
+ }
586
602
  const packageRoot = getPackageRoot();
587
603
  const manifestPath = join(packageRoot, "crates", "omx-explore", "Cargo.toml");
588
604
  if (!existsSync(manifestPath)) {
@@ -592,7 +608,6 @@ export function checkExploreHarness(platform = process.platform, env = process.e
592
608
  message: "Rust harness sources not found in this install (omx explore unavailable until packaged or OMX_EXPLORE_BIN is set)",
593
609
  };
594
610
  }
595
- const override = env[EXPLORE_BIN_ENV]?.trim();
596
611
  if (override) {
597
612
  const resolved = join(packageRoot, override);
598
613
  if (existsSync(override) || existsSync(resolved)) {
@@ -683,6 +698,8 @@ function formatArtifactIssue(repoRoot, issue) {
683
698
  return `${formatArtifactPath(repoRoot, issue.path)} (${issue.reason}${owner})`;
684
699
  }
685
700
  function shouldReportOwnerMismatch(uid, currentUid) {
701
+ if (currentUid === 0)
702
+ return false;
686
703
  if (typeof uid !== "number")
687
704
  return false;
688
705
  if (uid === 0)
@@ -717,8 +734,10 @@ async function collectRepoArtifactOwnershipIssues(repoRoot, options = {}) {
717
734
  const uid = typeof info.uid === "number" ? info.uid : undefined;
718
735
  const gid = typeof info.gid === "number" ? info.gid : undefined;
719
736
  let reason = null;
720
- if (uid === 0)
721
- reason = "root-owned";
737
+ if (uid === 0) {
738
+ if (currentUid !== 0)
739
+ reason = "root-owned";
740
+ }
722
741
  else if (shouldReportOwnerMismatch(uid, currentUid))
723
742
  reason = "owner-mismatch";
724
743
  else {
@@ -921,10 +940,36 @@ async function checkModelContextRecommendation(configPath) {
921
940
  return null;
922
941
  }
923
942
  }
924
- async function checkExploreRouting(configPath) {
925
- const envValue = process.env[OMX_EXPLORE_CMD_ENV];
943
+ async function resolveExploreRoutingState(configPath, env = process.env) {
944
+ const envValue = env[OMX_EXPLORE_CMD_ENV];
926
945
  if (typeof envValue === "string") {
927
- if (isExploreCommandRoutingEnabled(process.env)) {
946
+ return { source: "env", enabled: isExploreCommandRoutingEnabled(env) };
947
+ }
948
+ if (!existsSync(configPath)) {
949
+ return { source: "default", enabled: false, reason: "missing-config" };
950
+ }
951
+ try {
952
+ const content = await readFile(configPath, "utf-8");
953
+ const parsed = parseToml(content);
954
+ const configuredValue = parsed?.shell_environment_policy?.set?.USE_OMX_EXPLORE_CMD ??
955
+ parsed?.env?.USE_OMX_EXPLORE_CMD;
956
+ if (typeof configuredValue === "string") {
957
+ return {
958
+ source: "config",
959
+ enabled: isExploreCommandRoutingEnabled({
960
+ USE_OMX_EXPLORE_CMD: configuredValue,
961
+ }),
962
+ };
963
+ }
964
+ return { source: "default", enabled: false, reason: "unset" };
965
+ }
966
+ catch {
967
+ return { source: "unreadable", enabled: false };
968
+ }
969
+ }
970
+ function checkExploreRoutingFromState(state) {
971
+ if (state.source === "env") {
972
+ if (state.enabled) {
928
973
  return {
929
974
  name: "Explore routing",
930
975
  status: "warn",
@@ -937,47 +982,34 @@ async function checkExploreRouting(configPath) {
937
982
  message: "deprecated compatibility routing disabled by environment override (recommended)",
938
983
  };
939
984
  }
940
- if (!existsSync(configPath)) {
941
- return {
942
- name: "Explore routing",
943
- status: "pass",
944
- message: "deprecated by default (config.toml not found yet)",
945
- };
946
- }
947
- try {
948
- const content = await readFile(configPath, "utf-8");
949
- const parsed = parseToml(content);
950
- const configuredValue = parsed?.shell_environment_policy?.set?.USE_OMX_EXPLORE_CMD ??
951
- parsed?.env?.USE_OMX_EXPLORE_CMD;
952
- if (typeof configuredValue === "string") {
953
- if (isExploreCommandRoutingEnabled({
954
- USE_OMX_EXPLORE_CMD: configuredValue,
955
- })) {
956
- return {
957
- name: "Explore routing",
958
- status: "warn",
959
- message: 'deprecated compatibility routing enabled in config.toml; set USE_OMX_EXPLORE_CMD = "0" under [shell_environment_policy.set] and use normal Codex repo inspection or omx sparkshell instead',
960
- };
961
- }
985
+ if (state.source === "config") {
986
+ if (state.enabled) {
962
987
  return {
963
988
  name: "Explore routing",
964
- status: "pass",
965
- message: "deprecated compatibility routing disabled in config.toml (recommended)",
989
+ status: "warn",
990
+ message: 'deprecated compatibility routing enabled in config.toml; set USE_OMX_EXPLORE_CMD = "0" under [shell_environment_policy.set] and use normal Codex repo inspection or omx sparkshell instead',
966
991
  };
967
992
  }
968
993
  return {
969
994
  name: "Explore routing",
970
995
  status: "pass",
971
- message: "deprecated by default",
996
+ message: "deprecated compatibility routing disabled in config.toml (recommended)",
972
997
  };
973
998
  }
974
- catch {
999
+ if (state.source === "unreadable") {
975
1000
  return {
976
1001
  name: "Explore routing",
977
1002
  status: "fail",
978
1003
  message: "cannot read config.toml for explore routing check",
979
1004
  };
980
1005
  }
1006
+ return {
1007
+ name: "Explore routing",
1008
+ status: "pass",
1009
+ message: state.reason === "missing-config"
1010
+ ? "deprecated by default (config.toml not found yet)"
1011
+ : "deprecated by default",
1012
+ };
981
1013
  }
982
1014
  const LORE_COMMIT_GUARD_EXPLICIT_OPT_OUT_VALUES = new Set([
983
1015
  "0",
@@ -1060,6 +1092,117 @@ async function checkLoreCommitGuard(configPath) {
1060
1092
  function isExplicitLoreCommitGuardOptOut(value) {
1061
1093
  return LORE_COMMIT_GUARD_EXPLICIT_OPT_OUT_VALUES.has(value.trim().toLowerCase());
1062
1094
  }
1095
+ function decodeBasicXmlEntities(value) {
1096
+ return value
1097
+ .replaceAll("&", "&")
1098
+ .replaceAll("&lt;", "<")
1099
+ .replaceAll("&gt;", ">")
1100
+ .replaceAll("&quot;", '"')
1101
+ .replaceAll("&apos;", "'");
1102
+ }
1103
+ function extractPlistStrings(content) {
1104
+ return [...content.matchAll(/<string>([^<]+)<\/string>/g)].map((match) => decodeBasicXmlEntities(match[1]?.trim() ?? ""));
1105
+ }
1106
+ function extractPlistLabel(content, fallback) {
1107
+ const labelMatch = content.match(/<key>Label<\/key>\s*<string>([^<]+)<\/string>/);
1108
+ return decodeBasicXmlEntities(labelMatch?.[1]?.trim() || fallback);
1109
+ }
1110
+ async function readExistingTextFile(path) {
1111
+ try {
1112
+ return await readFile(path, "utf-8");
1113
+ }
1114
+ catch {
1115
+ return "";
1116
+ }
1117
+ }
1118
+ function expandLaunchAgentPath(value, homeDir) {
1119
+ if (value === "~")
1120
+ return homeDir;
1121
+ if (value.startsWith("~/"))
1122
+ return join(homeDir, value.slice(2));
1123
+ if (value === "$HOME")
1124
+ return homeDir;
1125
+ if (value.startsWith("$HOME/"))
1126
+ return join(homeDir, value.slice(6));
1127
+ if (value === "${HOME}")
1128
+ return homeDir;
1129
+ if (value.startsWith("${HOME}/"))
1130
+ return join(homeDir, value.slice(8));
1131
+ return value;
1132
+ }
1133
+ function classifyExternalCodexProcessGuard(plistContent, combinedContent) {
1134
+ const lowerPlist = plistContent.toLowerCase();
1135
+ const lower = combinedContent.toLowerCase();
1136
+ if (lower.includes("codex-mcp-child-guard") ||
1137
+ lower.includes("codex_mcp_child_guard") ||
1138
+ combinedContent.includes("CODEX_MCP_GUARD_DEDUPE_APP_CHILDREN") ||
1139
+ (lower.includes("codex app-server") &&
1140
+ lower.includes("pgrep -p") &&
1141
+ lower.includes("kill"))) {
1142
+ return "Codex app-server MCP child dedupe";
1143
+ }
1144
+ if ((lowerPlist.includes("codex-xcode-mcp-guard") ||
1145
+ lowerPlist.includes("codex_xcode_mcp_guard")) &&
1146
+ lower.includes("xcodebuildmcp") &&
1147
+ lower.includes("kill")) {
1148
+ return "XcodeBuildMCP cleanup";
1149
+ }
1150
+ return null;
1151
+ }
1152
+ export async function checkExternalCodexProcessGuards(options = {}) {
1153
+ const platform = options.platform ?? process.platform;
1154
+ if (platform !== "darwin")
1155
+ return null;
1156
+ const homeDir = options.homeDir ?? process.env.HOME;
1157
+ if (!homeDir)
1158
+ return null;
1159
+ const launchAgentsDir = join(homeDir, "Library", "LaunchAgents");
1160
+ if (!existsSync(launchAgentsDir))
1161
+ return null;
1162
+ let entries;
1163
+ try {
1164
+ entries = await readdir(launchAgentsDir);
1165
+ }
1166
+ catch {
1167
+ return null;
1168
+ }
1169
+ const findings = [];
1170
+ for (const entry of entries) {
1171
+ if (!entry.endsWith(".plist"))
1172
+ continue;
1173
+ const plistPath = join(launchAgentsDir, entry);
1174
+ const plistContent = await readExistingTextFile(plistPath);
1175
+ if (plistContent.trim() === "")
1176
+ continue;
1177
+ const loweredPlist = plistContent.toLowerCase();
1178
+ if (!loweredPlist.includes("codex") &&
1179
+ !loweredPlist.includes("mcp") &&
1180
+ !loweredPlist.includes("omx")) {
1181
+ continue;
1182
+ }
1183
+ let combinedContent = plistContent;
1184
+ for (const value of extractPlistStrings(plistContent)) {
1185
+ const expandedValue = expandLaunchAgentPath(value, homeDir);
1186
+ if (!expandedValue.startsWith("/"))
1187
+ continue;
1188
+ if (!existsSync(expandedValue))
1189
+ continue;
1190
+ combinedContent += `\n${await readExistingTextFile(expandedValue)}`;
1191
+ }
1192
+ const reason = classifyExternalCodexProcessGuard(plistContent, combinedContent);
1193
+ if (!reason)
1194
+ continue;
1195
+ const label = extractPlistLabel(plistContent, entry);
1196
+ findings.push(`${label} (${reason})`);
1197
+ }
1198
+ if (findings.length === 0)
1199
+ return null;
1200
+ return {
1201
+ name: "External process guards",
1202
+ status: "warn",
1203
+ message: `external LaunchAgent(s) may terminate Codex/MCP helper processes outside OMX setup ownership: ${findings.join(", ")}; unload/remove them before attributing SIGTERM or transport resets to standard OMX MCP configuration`,
1204
+ };
1205
+ }
1063
1206
  function isEnabledTomlValue(value) {
1064
1207
  return value === true || (typeof value === "string" && ["1", "true", "yes", "on"].includes(value.trim().toLowerCase()));
1065
1208
  }
@@ -1405,6 +1548,27 @@ export function classifyPostCompactHookStdout(stdout) {
1405
1548
  };
1406
1549
  }
1407
1550
  }
1551
+ export function buildPostCompactSmokeSpawnInvocation(expectedCommand, options = {}) {
1552
+ const platform = options.platform ?? process.platform;
1553
+ if (platform === "win32") {
1554
+ return {
1555
+ command: resolveWindowsPowerShellPath(options.env),
1556
+ args: [
1557
+ "-NoProfile",
1558
+ "-ExecutionPolicy",
1559
+ "Bypass",
1560
+ "-Command",
1561
+ expectedCommand,
1562
+ ],
1563
+ shell: false,
1564
+ };
1565
+ }
1566
+ return {
1567
+ command: expectedCommand,
1568
+ args: [],
1569
+ shell: true,
1570
+ };
1571
+ }
1408
1572
  async function checkNativePostCompactHookRuntime(hooksPath, cwd, codexHomeDir) {
1409
1573
  if (!existsSync(hooksPath))
1410
1574
  return null;
@@ -1437,7 +1601,8 @@ async function checkNativePostCompactHookRuntime(hooksPath, cwd, codexHomeDir) {
1437
1601
  cwd: smokeCwd,
1438
1602
  session_id: "omx-doctor-postcompact-smoke",
1439
1603
  });
1440
- const result = spawnSync(expectedCommand, {
1604
+ const smokeInvocation = buildPostCompactSmokeSpawnInvocation(expectedCommand);
1605
+ const result = spawnSync(smokeInvocation.command, smokeInvocation.args, {
1441
1606
  cwd,
1442
1607
  encoding: "utf-8",
1443
1608
  env: {
@@ -1445,7 +1610,7 @@ async function checkNativePostCompactHookRuntime(hooksPath, cwd, codexHomeDir) {
1445
1610
  OMX_NATIVE_HOOK_DOCTOR_SMOKE: "1",
1446
1611
  },
1447
1612
  input: payload,
1448
- shell: true,
1613
+ shell: smokeInvocation.shell,
1449
1614
  timeout: 5_000,
1450
1615
  });
1451
1616
  if (result.error) {