oh-my-codex 0.18.11 → 0.18.13

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 (196) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +9 -1
  4. package/dist/autopilot/__tests__/ralplan-gate.test.js +668 -0
  5. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  6. package/dist/autopilot/completion-gate.d.ts +10 -0
  7. package/dist/autopilot/completion-gate.d.ts.map +1 -0
  8. package/dist/autopilot/completion-gate.js +154 -0
  9. package/dist/autopilot/completion-gate.js.map +1 -0
  10. package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
  11. package/dist/autopilot/ralplan-gate.js +42 -21
  12. package/dist/autopilot/ralplan-gate.js.map +1 -1
  13. package/dist/cli/__tests__/codex-plugin-layout.test.js +46 -3
  14. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  15. package/dist/cli/__tests__/doctor-invalid-config.test.js +35 -0
  16. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  17. package/dist/cli/__tests__/doctor-warning-copy.test.js +317 -0
  18. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  19. package/dist/cli/__tests__/index.test.js +120 -2
  20. package/dist/cli/__tests__/index.test.js.map +1 -1
  21. package/dist/cli/__tests__/launch-fallback.test.js +1 -1
  22. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  23. package/dist/cli/__tests__/resume.test.js +217 -1
  24. package/dist/cli/__tests__/resume.test.js.map +1 -1
  25. package/dist/cli/__tests__/session-scoped-runtime.test.js +101 -0
  26. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  27. package/dist/cli/__tests__/session-search-help.test.js +3 -2
  28. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  29. package/dist/cli/__tests__/session-search.test.js +64 -2
  30. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  31. package/dist/cli/__tests__/setup-agents-overwrite.test.js +289 -1
  32. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  33. package/dist/cli/__tests__/setup-install-mode.test.js +290 -17
  34. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  35. package/dist/cli/__tests__/setup-prompts-overwrite.test.js +74 -0
  36. package/dist/cli/__tests__/setup-prompts-overwrite.test.js.map +1 -1
  37. package/dist/cli/__tests__/setup-scope.test.js +45 -0
  38. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  39. package/dist/cli/__tests__/state.test.js +93 -0
  40. package/dist/cli/__tests__/state.test.js.map +1 -1
  41. package/dist/cli/__tests__/update.test.js +157 -3
  42. package/dist/cli/__tests__/update.test.js.map +1 -1
  43. package/dist/cli/__tests__/version-sync-contract.test.js +2 -0
  44. package/dist/cli/__tests__/version-sync-contract.test.js.map +1 -1
  45. package/dist/cli/doctor.d.ts.map +1 -1
  46. package/dist/cli/doctor.js +90 -12
  47. package/dist/cli/doctor.js.map +1 -1
  48. package/dist/cli/index.d.ts +13 -4
  49. package/dist/cli/index.d.ts.map +1 -1
  50. package/dist/cli/index.js +439 -46
  51. package/dist/cli/index.js.map +1 -1
  52. package/dist/cli/project-runtime-codex-homes.d.ts +6 -0
  53. package/dist/cli/project-runtime-codex-homes.d.ts.map +1 -0
  54. package/dist/cli/project-runtime-codex-homes.js +27 -0
  55. package/dist/cli/project-runtime-codex-homes.js.map +1 -0
  56. package/dist/cli/session-search.d.ts.map +1 -1
  57. package/dist/cli/session-search.js +8 -1
  58. package/dist/cli/session-search.js.map +1 -1
  59. package/dist/cli/setup.d.ts +2 -2
  60. package/dist/cli/setup.d.ts.map +1 -1
  61. package/dist/cli/setup.js +482 -126
  62. package/dist/cli/setup.js.map +1 -1
  63. package/dist/cli/state.d.ts.map +1 -1
  64. package/dist/cli/state.js +79 -8
  65. package/dist/cli/state.js.map +1 -1
  66. package/dist/cli/update.d.ts +1 -0
  67. package/dist/cli/update.d.ts.map +1 -1
  68. package/dist/cli/update.js +42 -10
  69. package/dist/cli/update.js.map +1 -1
  70. package/dist/config/__tests__/codex-hooks.test.js +73 -29
  71. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  72. package/dist/config/codex-hooks.d.ts +14 -0
  73. package/dist/config/codex-hooks.d.ts.map +1 -1
  74. package/dist/config/codex-hooks.js +54 -51
  75. package/dist/config/codex-hooks.js.map +1 -1
  76. package/dist/config/generator.d.ts +1 -1
  77. package/dist/config/generator.d.ts.map +1 -1
  78. package/dist/config/generator.js +1 -1
  79. package/dist/config/generator.js.map +1 -1
  80. package/dist/hooks/__tests__/best-practice-research-skill.test.js +12 -0
  81. package/dist/hooks/__tests__/best-practice-research-skill.test.js.map +1 -1
  82. package/dist/hud/__tests__/authority.test.js +45 -12
  83. package/dist/hud/__tests__/authority.test.js.map +1 -1
  84. package/dist/hud/__tests__/reconcile.test.js +95 -0
  85. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  86. package/dist/hud/__tests__/render.test.js +6 -6
  87. package/dist/hud/__tests__/render.test.js.map +1 -1
  88. package/dist/hud/__tests__/tmux.test.js +2 -2
  89. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  90. package/dist/hud/authority.d.ts.map +1 -1
  91. package/dist/hud/authority.js +17 -2
  92. package/dist/hud/authority.js.map +1 -1
  93. package/dist/hud/index.js +1 -4
  94. package/dist/hud/index.js.map +1 -1
  95. package/dist/hud/reconcile.d.ts.map +1 -1
  96. package/dist/hud/reconcile.js +42 -0
  97. package/dist/hud/reconcile.js.map +1 -1
  98. package/dist/hud/render.d.ts.map +1 -1
  99. package/dist/hud/render.js +6 -0
  100. package/dist/hud/render.js.map +1 -1
  101. package/dist/hud/tmux.d.ts.map +1 -1
  102. package/dist/hud/tmux.js +5 -4
  103. package/dist/hud/tmux.js.map +1 -1
  104. package/dist/mcp/__tests__/bootstrap.test.js +31 -1
  105. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  106. package/dist/mcp/bootstrap.d.ts +1 -0
  107. package/dist/mcp/bootstrap.d.ts.map +1 -1
  108. package/dist/mcp/bootstrap.js +32 -0
  109. package/dist/mcp/bootstrap.js.map +1 -1
  110. package/dist/modes/__tests__/base-autopilot-gates.test.d.ts +2 -0
  111. package/dist/modes/__tests__/base-autopilot-gates.test.d.ts.map +1 -0
  112. package/dist/modes/__tests__/base-autopilot-gates.test.js +154 -0
  113. package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -0
  114. package/dist/modes/base.d.ts +4 -1
  115. package/dist/modes/base.d.ts.map +1 -1
  116. package/dist/modes/base.js +71 -1
  117. package/dist/modes/base.js.map +1 -1
  118. package/dist/pipeline/__tests__/orchestrator.test.js +144 -3
  119. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  120. package/dist/pipeline/__tests__/stages.test.js +109 -0
  121. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  122. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  123. package/dist/pipeline/orchestrator.js +11 -4
  124. package/dist/pipeline/orchestrator.js.map +1 -1
  125. package/dist/pipeline/stages/code-review.d.ts +2 -0
  126. package/dist/pipeline/stages/code-review.d.ts.map +1 -1
  127. package/dist/pipeline/stages/code-review.js +2 -0
  128. package/dist/pipeline/stages/code-review.js.map +1 -1
  129. package/dist/pipeline/stages/ultraqa.d.ts +3 -0
  130. package/dist/pipeline/stages/ultraqa.d.ts.map +1 -1
  131. package/dist/pipeline/stages/ultraqa.js +3 -0
  132. package/dist/pipeline/stages/ultraqa.js.map +1 -1
  133. package/dist/ralplan/__tests__/consensus-gate.test.d.ts +2 -0
  134. package/dist/ralplan/__tests__/consensus-gate.test.d.ts.map +1 -0
  135. package/dist/ralplan/__tests__/consensus-gate.test.js +631 -0
  136. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -0
  137. package/dist/ralplan/consensus-gate.d.ts +9 -1
  138. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  139. package/dist/ralplan/consensus-gate.js +287 -65
  140. package/dist/ralplan/consensus-gate.js.map +1 -1
  141. package/dist/scripts/__tests__/codex-native-hook.test.js +481 -0
  142. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  143. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  144. package/dist/scripts/codex-native-hook.js +145 -25
  145. package/dist/scripts/codex-native-hook.js.map +1 -1
  146. package/dist/scripts/codex-native-pre-post.d.ts +1 -0
  147. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  148. package/dist/scripts/codex-native-pre-post.js +130 -0
  149. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  150. package/dist/session-history/__tests__/search.test.js +166 -0
  151. package/dist/session-history/__tests__/search.test.js.map +1 -1
  152. package/dist/session-history/search.d.ts +7 -0
  153. package/dist/session-history/search.d.ts.map +1 -1
  154. package/dist/session-history/search.js +83 -24
  155. package/dist/session-history/search.js.map +1 -1
  156. package/dist/sidecar/__tests__/collector.test.js +60 -0
  157. package/dist/sidecar/__tests__/collector.test.js.map +1 -1
  158. package/dist/sidecar/collector.d.ts.map +1 -1
  159. package/dist/sidecar/collector.js +3 -6
  160. package/dist/sidecar/collector.js.map +1 -1
  161. package/dist/state/__tests__/operations.test.js +622 -0
  162. package/dist/state/__tests__/operations.test.js.map +1 -1
  163. package/dist/state/__tests__/skill-active.test.js +82 -0
  164. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  165. package/dist/state/operations.d.ts.map +1 -1
  166. package/dist/state/operations.js +31 -9
  167. package/dist/state/operations.js.map +1 -1
  168. package/dist/state/skill-active.d.ts.map +1 -1
  169. package/dist/state/skill-active.js +41 -1
  170. package/dist/state/skill-active.js.map +1 -1
  171. package/dist/team/__tests__/runtime.test.js +81 -57
  172. package/dist/team/__tests__/runtime.test.js.map +1 -1
  173. package/dist/team/runtime.js +4 -4
  174. package/dist/team/runtime.js.map +1 -1
  175. package/dist/utils/__tests__/paths.test.js +23 -0
  176. package/dist/utils/__tests__/paths.test.js.map +1 -1
  177. package/dist/utils/__tests__/version.test.js +27 -0
  178. package/dist/utils/__tests__/version.test.js.map +1 -1
  179. package/dist/utils/paths.d.ts.map +1 -1
  180. package/dist/utils/paths.js +4 -2
  181. package/dist/utils/paths.js.map +1 -1
  182. package/dist/utils/version.d.ts.map +1 -1
  183. package/dist/utils/version.js +7 -2
  184. package/dist/utils/version.js.map +1 -1
  185. package/dist/verification/__tests__/ci-rust-gates.test.js +4 -2
  186. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  187. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +71 -3
  188. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  189. package/package.json +1 -1
  190. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  191. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +53 -2
  192. package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +6 -1
  193. package/skills/best-practice-research/SKILL.md +6 -1
  194. package/src/scripts/__tests__/codex-native-hook.test.ts +615 -0
  195. package/src/scripts/codex-native-hook.ts +162 -32
  196. package/src/scripts/codex-native-pre-post.ts +137 -0
package/dist/cli/setup.js CHANGED
@@ -8,9 +8,11 @@ import { existsSync } from "fs";
8
8
  import { spawnSync } from "child_process";
9
9
  import { createInterface } from "readline/promises";
10
10
  import { homedir } from "os";
11
+ import TOML from "@iarna/toml";
12
+ import { createHash } from "crypto";
11
13
  import { codexHome, codexConfigPath, codexPromptsDir, codexAgentsDir, userSkillsDir, omxStateDir, detectLegacySkillRootOverlap, omxPlansDir, omxLogsDir, } from "../utils/paths.js";
12
- import { buildMergedConfig, getRootModelName, getRootTomlArray, hasLegacyOmxTeamRunTable, isOmxManagedNotifyCommand, sanitizePreviousNotifyCommand, stripExistingOmxBlocks, stripExistingSharedMcpRegistryBlock, mergeSharedMcpRegistryBlock, stripOmxEnvSettings, stripOmxFeatureFlags, stripOmxSeededBehavioralDefaults, upsertPluginModeRuntimeFeatureFlags, upsertManagedCodexHookTrustState, stripManagedCodexHookTrustState, OMX_PLUGIN_DEVELOPER_INSTRUCTIONS, hasFirstPartyOmxMcpRegistrations, extractFirstPartyOmxMcpSections, stripFirstPartyOmxMcpSections, } from "../config/generator.js";
13
- import { buildManagedCodexHookTrustState, buildManagedCodexNativeHookWindowsShimContent, buildManagedCodexNativeHookWindowsShimPath, mergeManagedCodexHooksConfig, removeManagedCodexHooks, } from "../config/codex-hooks.js";
14
+ import { buildMergedConfig, getRootModelName, getRootTomlArray, hasLegacyOmxTeamRunTable, isOmxManagedNotifyCommand, sanitizePreviousNotifyCommand, stripExistingOmxBlocks, stripExistingSharedMcpRegistryBlock, mergeSharedMcpRegistryBlock, stripOmxEnvSettings, stripOmxFeatureFlags, stripOmxSeededBehavioralDefaults, upsertPluginModeRuntimeFeatureFlags, upsertManagedCodexHookTrustState, stripManagedCodexHookTrustState, OMX_DEVELOPER_INSTRUCTIONS, OMX_PLUGIN_DEVELOPER_INSTRUCTIONS, hasFirstPartyOmxMcpRegistrations, extractFirstPartyOmxMcpSections, stripFirstPartyOmxMcpSections, } from "../config/generator.js";
15
+ import { buildManagedCodexHookTrustState, buildManagedCodexNativeHookWindowsShimContent, buildManagedCodexNativeHookWindowsShimPath, mergeManagedCodexHooksConfig, extractCodexHooksJsonTrustState, removeManagedCodexHooks, } from "../config/codex-hooks.js";
14
16
  import { getLegacyUnifiedMcpRegistryCandidate, getUnifiedMcpRegistryCandidates, loadUnifiedMcpRegistry, planClaudeCodeMcpSettingsSync, } from "../config/mcp-registry.js";
15
17
  import { generateAgentToml } from "../agents/native-config.js";
16
18
  import { AGENT_DEFINITIONS } from "../agents/definitions.js";
@@ -62,6 +64,7 @@ const PROJECT_GITIGNORE_ENTRIES = [
62
64
  const LEGACY_PROJECT_GITIGNORE_ENTRIES = [".codex/"];
63
65
  const SETUP_ONLY_INSTALLABLE_SKILLS = new Set(["wiki"]);
64
66
  const DEFAULT_SETUP_MCP_MODE = "none";
67
+ const SKIP_NATIVE_AGENT_REFRESH_ENV = "OMX_SKIP_NATIVE_AGENT_REFRESH";
65
68
  const HARD_DEPRECATED_SKILL_NAMES = new Set(["web-clone"]);
66
69
  const TEAM_MODE_SKILL_NAMES = new Set(["team", "worker"]);
67
70
  const TEAM_MODE_PROMPT_NAMES = new Set(["team-executor"]);
@@ -192,6 +195,68 @@ function getBackupContext(scope, projectRoot) {
192
195
  baseRoot: homedir(),
193
196
  };
194
197
  }
198
+ function escapeTomlBasicString(value) {
199
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
200
+ }
201
+ function renderHooksJsonTrustStateToml(content) {
202
+ const trustState = extractCodexHooksJsonTrustState(content);
203
+ return Object.entries(trustState)
204
+ .sort(([left], [right]) => left.localeCompare(right))
205
+ .flatMap(([key, state]) => [
206
+ `[hooks.state."${escapeTomlBasicString(key)}"]`,
207
+ `trusted_hash = "${escapeTomlBasicString(state.trusted_hash)}"`,
208
+ ...(typeof state.enabled === "boolean" ? [`enabled = ${state.enabled}`] : []),
209
+ "",
210
+ ])
211
+ .join("\n")
212
+ .trimEnd();
213
+ }
214
+ function existingHooksStateKeys(config) {
215
+ try {
216
+ const parsed = TOML.parse(config);
217
+ return new Set(Object.keys(parsed.hooks?.state ?? {}));
218
+ }
219
+ catch {
220
+ return new Set();
221
+ }
222
+ }
223
+ function appendHooksJsonTrustStateToConfig(config, hooksContent) {
224
+ const existingKeys = existingHooksStateKeys(config);
225
+ const trustState = extractCodexHooksJsonTrustState(hooksContent);
226
+ const migratableContent = JSON.stringify({
227
+ state: Object.fromEntries(Object.entries(trustState).filter(([key]) => !existingKeys.has(key))),
228
+ });
229
+ const trustToml = renderHooksJsonTrustStateToml(migratableContent);
230
+ if (!trustToml)
231
+ return config;
232
+ const base = config.trimEnd();
233
+ return [
234
+ base,
235
+ base ? "" : null,
236
+ "# Migrated from legacy hooks.json state; kept in Codex config.toml because Codex 0.140 rejects top-level hooks.json state.",
237
+ trustToml,
238
+ "",
239
+ ].filter((line) => line !== null).join("\n");
240
+ }
241
+ async function migrateLegacyHooksJsonTrustStateToConfig(configPath, hooksContent, backupContext, summary, options) {
242
+ const existingConfig = existsSync(configPath)
243
+ ? await readFile(configPath, "utf-8")
244
+ : "";
245
+ const nextConfig = appendHooksJsonTrustStateToConfig(existingConfig, hooksContent);
246
+ if (nextConfig === existingConfig)
247
+ return;
248
+ if (await ensureBackup(configPath, existsSync(configPath), backupContext, options)) {
249
+ summary.backedUp += 1;
250
+ }
251
+ if (!options.dryRun) {
252
+ await mkdir(dirname(configPath), { recursive: true });
253
+ await writeFile(configPath, nextConfig);
254
+ }
255
+ summary.updated += 1;
256
+ if (options.verbose) {
257
+ console.log(` ${options.dryRun ? "would migrate" : "migrated"} legacy hooks.json trust state to ${configPath}`);
258
+ }
259
+ }
195
260
  async function ensureBackup(destinationPath, contentChanged, backupContext, options) {
196
261
  if (!contentChanged || !existsSync(destinationPath))
197
262
  return false;
@@ -517,41 +582,55 @@ async function promptForAgentsOverwrite(destinationPath) {
517
582
  }
518
583
  async function promptForPluginAgentsMdDefault(destinationPath) {
519
584
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
520
- return false;
585
+ return !existsSync(destinationPath);
521
586
  }
522
587
  const rl = createInterface({
523
588
  input: process.stdin,
524
589
  output: process.stdout,
525
590
  });
526
591
  try {
527
- const answer = (await rl.question(`Plugin mode: install OMX AGENTS.md defaults at "${destinationPath}"? [y/N]: `))
592
+ const answer = (await rl.question(`Plugin mode: install/update OMX AGENTS.md defaults at "${destinationPath}"? [Y/n]: `))
528
593
  .trim()
529
594
  .toLowerCase();
530
- return answer === "y" || answer === "yes";
595
+ return answer === "" || answer === "y" || answer === "yes";
531
596
  }
532
597
  finally {
533
598
  rl.close();
534
599
  }
535
600
  }
536
- async function promptForPluginDeveloperInstructionsDefault(configPath) {
537
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
538
- return false;
601
+ const LEGACY_PLUGIN_DEVELOPER_INSTRUCTIONS = "You have oh-my-codex installed through Codex plugin mode. AGENTS.md is the orchestration brain and main control surface. Follow AGENTS.md for skill/keyword routing and $name workflow invocation. When spawning native subagents, set `agent_type` to an installed role and never omit it for OMX work. Registered Codex plugin marketplace surfaces supply OMX workflows and plugin-scoped companion resources when the plugin is installed; native agent roles are installed as setup-owned Codex agent TOML files in plugin mode so agent_type routing works. User-installed skills may still live under ~/.codex/skills. Use outcome-first, concise progress updates: state the target result, constraints, validation evidence, and stop condition before adding process detail.";
602
+ function normalizeDeveloperInstructionsText(value) {
603
+ return value.replace(/\r\n/g, "\n").trim();
604
+ }
605
+ function classifyPluginDeveloperInstructions(value) {
606
+ if (typeof value !== "string")
607
+ return "custom";
608
+ const normalized = normalizeDeveloperInstructionsText(value);
609
+ if (normalized ===
610
+ normalizeDeveloperInstructionsText(OMX_PLUGIN_DEVELOPER_INSTRUCTIONS)) {
611
+ return "current";
539
612
  }
540
- const rl = createInterface({
541
- input: process.stdin,
542
- output: process.stdout,
543
- });
613
+ if (normalized ===
614
+ normalizeDeveloperInstructionsText(LEGACY_PLUGIN_DEVELOPER_INSTRUCTIONS)) {
615
+ return "current";
616
+ }
617
+ if (normalized === normalizeDeveloperInstructionsText(OMX_DEVELOPER_INSTRUCTIONS)) {
618
+ return "historical";
619
+ }
620
+ return "custom";
621
+ }
622
+ function readRootDeveloperInstructions(config) {
623
+ if (!rootHasTomlKey(config, "developer_instructions"))
624
+ return undefined;
544
625
  try {
545
- const answer = (await rl.question(`Plugin mode: add OMX developer_instructions defaults to "${configPath}"? [y/N]: `))
546
- .trim()
547
- .toLowerCase();
548
- return answer === "y" || answer === "yes";
626
+ const parsed = TOML.parse(config);
627
+ return parsed.developer_instructions;
549
628
  }
550
- finally {
551
- rl.close();
629
+ catch {
630
+ return Symbol.for("omx.invalid-developer-instructions");
552
631
  }
553
632
  }
554
- async function promptForPluginDeveloperInstructionsOverwrite(configPath) {
633
+ async function askYesNoDefaultYes(question) {
555
634
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
556
635
  return false;
557
636
  }
@@ -560,15 +639,99 @@ async function promptForPluginDeveloperInstructionsOverwrite(configPath) {
560
639
  output: process.stdout,
561
640
  });
562
641
  try {
563
- const answer = (await rl.question(`Plugin mode: overwrite existing developer_instructions in "${configPath}" with OMX defaults? [y/N]: `))
564
- .trim()
565
- .toLowerCase();
566
- return answer === "y" || answer === "yes";
642
+ const answer = (await rl.question(question)).trim().toLowerCase();
643
+ return answer === "" || answer === "y" || answer === "yes";
567
644
  }
568
645
  finally {
569
646
  rl.close();
570
647
  }
571
648
  }
649
+ function legacyPluginDeveloperInstructionsDecision(choice, state = "missing") {
650
+ if (choice === "refresh" || (choice === true && state === "historical")) {
651
+ return {
652
+ action: "update",
653
+ state: "historical",
654
+ reason: choice === "refresh"
655
+ ? "legacy explicit refresh policy"
656
+ : "legacy boolean approval refreshed historical developer_instructions",
657
+ };
658
+ }
659
+ if (choice === true || choice === "preserve-or-add") {
660
+ return {
661
+ action: "add",
662
+ state: "missing",
663
+ reason: "legacy explicit add-if-missing policy",
664
+ };
665
+ }
666
+ return {
667
+ action: "preserve",
668
+ state,
669
+ reason: "legacy explicit skip policy",
670
+ };
671
+ }
672
+ async function resolvePluginDeveloperInstructionsDecision(configPath, options) {
673
+ const existing = existsSync(configPath)
674
+ ? await readFile(configPath, "utf-8")
675
+ : "";
676
+ const value = readRootDeveloperInstructions(existing);
677
+ if (value === undefined) {
678
+ if (options.pluginDeveloperInstructionsPrompt) {
679
+ return legacyPluginDeveloperInstructionsDecision(await options.pluginDeveloperInstructionsPrompt(configPath), "missing");
680
+ }
681
+ const install = await askYesNoDefaultYes(`Plugin mode: add OMX developer_instructions bootstrap to "${configPath}"? [Y/n]: `);
682
+ return install
683
+ ? {
684
+ action: "add",
685
+ state: "missing",
686
+ reason: "missing developer_instructions",
687
+ }
688
+ : {
689
+ action: "preserve",
690
+ state: "missing",
691
+ reason: "missing developer_instructions skipped",
692
+ };
693
+ }
694
+ const state = classifyPluginDeveloperInstructions(value);
695
+ if (state === "current") {
696
+ return {
697
+ action: "preserve",
698
+ state,
699
+ reason: "current OMX developer_instructions already installed",
700
+ };
701
+ }
702
+ if (state === "historical") {
703
+ const updateDecision = options.pluginDeveloperInstructionsPrompt
704
+ ? legacyPluginDeveloperInstructionsDecision(await options.pluginDeveloperInstructionsPrompt(configPath), state)
705
+ : await askYesNoDefaultYes(`Plugin mode: update OMX developer_instructions bootstrap at "${configPath}"? [Y/n]: `)
706
+ ? {
707
+ action: "update",
708
+ state,
709
+ reason: "recognized historical OMX developer_instructions",
710
+ }
711
+ : {
712
+ action: "preserve",
713
+ state,
714
+ reason: "historical OMX developer_instructions preserved",
715
+ };
716
+ const update = updateDecision.action === "update";
717
+ return update
718
+ ? {
719
+ action: "update",
720
+ state,
721
+ reason: "recognized historical OMX developer_instructions",
722
+ }
723
+ : {
724
+ action: "preserve",
725
+ state,
726
+ reason: "historical OMX developer_instructions preserved",
727
+ };
728
+ }
729
+ return {
730
+ action: "preserve",
731
+ state: "custom",
732
+ reason: "custom or unknown developer_instructions preserved",
733
+ };
734
+ }
572
735
  async function resolveSetupScope(projectRoot, requestedScope, persistedReviewDecision = "keep", persistedPreferences, setupScopePrompt) {
573
736
  if (requestedScope) {
574
737
  return { scope: requestedScope, source: "cli" };
@@ -898,7 +1061,15 @@ async function cleanupPluginModeLegacyPrompts(srcDir, dstDir, backupContext, opt
898
1061
  await removeEmptyDirectoryIfPresent(dstDir, options);
899
1062
  return summary;
900
1063
  }
901
- function stripPluginModeLegacyRootDefaults(config) {
1064
+ function removeRootTomlKey(config, key) {
1065
+ const range = findRootTomlKeyRange(config, key);
1066
+ if (!range)
1067
+ return config;
1068
+ const before = config.slice(0, range.start);
1069
+ const after = config.slice(range.end).replace(/^\r?\n?/, "\n");
1070
+ return `${before}${after}`;
1071
+ }
1072
+ function stripPluginModeLegacyRootDefaults(config, developerInstructionsDecision) {
902
1073
  const lines = config.split(/\r?\n/);
903
1074
  const firstTableIndex = lines.findIndex((line) => /^\s*\[/.test(line));
904
1075
  const boundary = firstTableIndex >= 0 ? firstTableIndex : lines.length;
@@ -918,14 +1089,15 @@ function stripPluginModeLegacyRootDefaults(config) {
918
1089
  /^\s*model_reasoning_effort\s*=\s*"medium"\s*$/.test(line)) {
919
1090
  continue;
920
1091
  }
921
- if (index < boundary &&
922
- /^\s*developer_instructions\s*=/.test(line) &&
923
- line.includes("You have oh-my-codex installed.")) {
924
- continue;
925
- }
926
1092
  result.push(line);
927
1093
  }
928
- return result.join("\n").replace(/\n{3,}/g, "\n\n");
1094
+ let nextConfig = result.join("\n").replace(/\n{3,}/g, "\n\n");
1095
+ if (developerInstructionsDecision.action === "update" &&
1096
+ developerInstructionsDecision.state === "historical" &&
1097
+ classifyPluginDeveloperInstructions(readRootDeveloperInstructions(nextConfig)) === "historical") {
1098
+ nextConfig = removeRootTomlKey(nextConfig, "developer_instructions");
1099
+ }
1100
+ return nextConfig;
929
1101
  }
930
1102
  function rootHasTomlKey(config, key) {
931
1103
  const lines = config.split(/\r?\n/);
@@ -936,18 +1108,62 @@ function rootHasTomlKey(config, key) {
936
1108
  return lines.slice(0, boundary).some((line) => pattern.test(line));
937
1109
  }
938
1110
  function replaceRootTomlKey(config, key, line) {
939
- const lines = config.trimEnd().split(/\r?\n/);
940
- const firstTableIndex = lines.findIndex((entry) => /^\s*\[/.test(entry));
941
- const boundary = firstTableIndex < 0 ? lines.length : firstTableIndex;
1111
+ const range = findRootTomlKeyRange(config, key);
1112
+ if (!range)
1113
+ return insertRootTomlKey(config, line);
1114
+ const before = config.slice(0, range.start);
1115
+ const after = config.slice(range.end).replace(/^\r?\n?/, "\n");
1116
+ return `${before}${line}${after}`.replace(/\n?$/, "\n");
1117
+ }
1118
+ function findRootTomlKeyRange(config, key) {
942
1119
  const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
943
- const pattern = new RegExp(`^\\s*${escapedKey}\\s*=`);
944
- for (let i = 0; i < boundary; i++) {
945
- if (pattern.test(lines[i])) {
946
- lines[i] = line;
947
- return lines.join("\n") + "\n";
1120
+ const keyPattern = new RegExp(`^\\s*${escapedKey}\\s*=`);
1121
+ const nextRootKeyPattern = /^\s*[A-Za-z0-9_-]+\s*=/;
1122
+ const tablePattern = /^\s*\[/;
1123
+ const linePattern = /.*(?:\r?\n|$)/g;
1124
+ let match;
1125
+ let found = null;
1126
+ let inMultiline = false;
1127
+ let multilineDelimiter = null;
1128
+ while ((match = linePattern.exec(config)) && match[0] !== "") {
1129
+ const line = match[0];
1130
+ const lineStart = match.index;
1131
+ const lineEnd = lineStart + line.length;
1132
+ const trimmedLine = line.replace(/\r?\n$/, "");
1133
+ if (!found) {
1134
+ if (tablePattern.test(trimmedLine))
1135
+ return null;
1136
+ if (keyPattern.test(trimmedLine)) {
1137
+ found = { start: lineStart, end: lineEnd };
1138
+ const valuePart = trimmedLine.slice(trimmedLine.indexOf("=") + 1);
1139
+ const delimiter = valuePart.includes('"""')
1140
+ ? '"""'
1141
+ : valuePart.includes("'''")
1142
+ ? "'''"
1143
+ : null;
1144
+ if (delimiter && valuePart.split(delimiter).length - 1 === 1) {
1145
+ inMultiline = true;
1146
+ multilineDelimiter = delimiter;
1147
+ }
1148
+ else {
1149
+ return found;
1150
+ }
1151
+ }
1152
+ continue;
1153
+ }
1154
+ found.end = lineEnd;
1155
+ if (inMultiline && multilineDelimiter) {
1156
+ if (trimmedLine.includes(multilineDelimiter)) {
1157
+ return found;
1158
+ }
1159
+ continue;
1160
+ }
1161
+ if (tablePattern.test(trimmedLine) || nextRootKeyPattern.test(trimmedLine)) {
1162
+ found.end = lineStart;
1163
+ return found;
948
1164
  }
949
1165
  }
950
- return insertRootTomlKey(config, line);
1166
+ return found;
951
1167
  }
952
1168
  function insertRootTomlKey(config, line) {
953
1169
  const lines = config.trimEnd().split(/\r?\n/);
@@ -1041,6 +1257,7 @@ async function applyPluginModeHooksConfig(configPath, hooksPath, pkgRoot, codexH
1041
1257
  const existingHooksContent = existsSync(hooksPath)
1042
1258
  ? await readFile(hooksPath, "utf-8")
1043
1259
  : null;
1260
+ await migrateLegacyHooksJsonTrustStateToConfig(configPath, existingHooksContent, backupContext, summary, options);
1044
1261
  if (options.pluginScopedHooks) {
1045
1262
  await cleanupPluginModeManagedHooksJson(existingHooksContent, hooksPath, backupContext, summary, options);
1046
1263
  }
@@ -1060,19 +1277,21 @@ async function applyPluginDeveloperInstructionsDefault(configPath, backupContext
1060
1277
  const existing = existsSync(configPath)
1061
1278
  ? await readFile(configPath, "utf-8")
1062
1279
  : "";
1280
+ if (options.decision.action === "preserve") {
1281
+ summary.skipped += 1;
1282
+ if (options.verbose) {
1283
+ console.log(` preserved plugin developer_instructions default: ${options.decision.reason}`);
1284
+ }
1285
+ return options.decision.state === "missing" ? "skipped" : "exists";
1286
+ }
1063
1287
  const line = `developer_instructions = ${JSON.stringify(OMX_PLUGIN_DEVELOPER_INSTRUCTIONS)}`;
1064
1288
  const hasExistingDeveloperInstructions = rootHasTomlKey(existing, "developer_instructions");
1065
- if (hasExistingDeveloperInstructions) {
1066
- const overwrite = options.pluginDeveloperInstructionsOverwritePrompt
1067
- ? await options.pluginDeveloperInstructionsOverwritePrompt(configPath)
1068
- : await promptForPluginDeveloperInstructionsOverwrite(configPath);
1069
- if (!overwrite) {
1070
- summary.skipped += 1;
1071
- if (options.verbose) {
1072
- console.log(" skipped plugin developer_instructions default: root developer_instructions already exists");
1073
- }
1074
- return "exists";
1289
+ if (hasExistingDeveloperInstructions && options.decision.action === "add") {
1290
+ summary.skipped += 1;
1291
+ if (options.verbose) {
1292
+ console.log(" skipped plugin developer_instructions default: root developer_instructions already exists");
1075
1293
  }
1294
+ return "exists";
1076
1295
  }
1077
1296
  const nextConfig = hasExistingDeveloperInstructions
1078
1297
  ? replaceRootTomlKey(existing, "developer_instructions", line)
@@ -1102,7 +1321,7 @@ async function cleanupPluginModeLegacyConfig(configPath, backupContext, options)
1102
1321
  config = stripFirstPartyOmxMcpSections(config);
1103
1322
  config = stripExistingOmxBlocks(config).cleaned;
1104
1323
  config = stripExistingSharedMcpRegistryBlock(config).cleaned;
1105
- config = stripPluginModeLegacyRootDefaults(config);
1324
+ config = stripPluginModeLegacyRootDefaults(config, options.developerInstructionsDecision);
1106
1325
  config = stripOmxSeededBehavioralDefaults(config);
1107
1326
  config = stripOmxFeatureFlags(config);
1108
1327
  config = stripManagedCodexHookTrustState(config);
@@ -1130,32 +1349,8 @@ async function cleanupPluginModeLegacyConfig(configPath, backupContext, options)
1130
1349
  }
1131
1350
  return true;
1132
1351
  }
1133
- async function cleanupPluginModeLegacyAgentsMd(agentsMdPath, backupContext, options) {
1134
- if (!existsSync(agentsMdPath))
1135
- return false;
1136
- const fileInfo = await lstat(agentsMdPath);
1137
- if (fileInfo.isSymbolicLink()) {
1138
- if (options.verbose) {
1139
- console.log(` preserved symlinked AGENTS.md at ${agentsMdPath}; plugin mode only removes direct legacy OMX-generated files`);
1140
- }
1141
- return false;
1142
- }
1143
- const content = await readFile(agentsMdPath, "utf-8");
1144
- if (!isOmxGeneratedAgentsMd(content))
1145
- return false;
1146
- if (await ensureBackup(agentsMdPath, true, backupContext, options)) {
1147
- // backup created for pre-existing AGENTS.md
1148
- }
1149
- if (!options.dryRun) {
1150
- await rm(agentsMdPath, { force: true });
1151
- }
1152
- if (options.verbose) {
1153
- console.log(` ${options.dryRun ? "would remove" : "removed"} legacy OMX-generated AGENTS.md`);
1154
- }
1155
- return true;
1156
- }
1157
1352
  export async function setup(options = {}) {
1158
- const { force = false, dryRun = false, installMode: requestedInstallMode, mcpMode: requestedMcpMode, teamMode: requestedTeamMode, scope: requestedScope, verbose = false, setupScopePrompt, persistedSetupReviewPrompt, installModePrompt, modelUpgradePrompt, pluginAgentsMdPrompt, pluginDeveloperInstructionsPrompt, pluginDeveloperInstructionsOverwritePrompt, firstPartyMcpRemovalPrompt, } = options;
1353
+ const { force = false, dryRun = false, installMode: requestedInstallMode, mcpMode: requestedMcpMode, teamMode: requestedTeamMode, scope: requestedScope, verbose = false, skipNativeAgentRefresh: requestedSkipNativeAgentRefresh = false, setupScopePrompt, persistedSetupReviewPrompt, installModePrompt, modelUpgradePrompt, pluginAgentsMdPrompt, pluginDeveloperInstructionsPrompt, firstPartyMcpRemovalPrompt, } = options;
1159
1354
  const pkgRoot = getPackageRoot();
1160
1355
  const projectRoot = process.cwd();
1161
1356
  const persistedPreferences = await readPersistedSetupPreferences(projectRoot, { warnOnLegacyScope: true });
@@ -1194,6 +1389,8 @@ export async function setup(options = {}) {
1194
1389
  : undefined)
1195
1390
  ?? "enabled";
1196
1391
  const isTeamModeEnabled = teamModeEnabled(resolvedTeamMode);
1392
+ const skipNativeAgentRefresh = requestedSkipNativeAgentRefresh ||
1393
+ process.env[SKIP_NATIVE_AGENT_REFRESH_ENV] === "1";
1197
1394
  const scopeDirs = resolveScopeDirectories(resolvedScope.scope, projectRoot);
1198
1395
  const existingConfigForMcpMigration = existsSync(scopeDirs.codexConfigFile)
1199
1396
  ? await readFile(scopeDirs.codexConfigFile, "utf-8")
@@ -1224,15 +1421,32 @@ export async function setup(options = {}) {
1224
1421
  const pluginAgentsMdDst = resolvedScope.scope === "project"
1225
1422
  ? join(projectRoot, "AGENTS.md")
1226
1423
  : join(scopeDirs.codexHomeDir, "AGENTS.md");
1227
- const usePluginDeveloperInstructionsDefault = isPluginInstallMode
1228
- ? pluginDeveloperInstructionsPrompt
1229
- ? await pluginDeveloperInstructionsPrompt(scopeDirs.codexConfigFile)
1230
- : await promptForPluginDeveloperInstructionsDefault(scopeDirs.codexConfigFile)
1231
- : false;
1424
+ const pluginDeveloperInstructionsDecision = isPluginInstallMode
1425
+ ? await resolvePluginDeveloperInstructionsDecision(scopeDirs.codexConfigFile, { pluginDeveloperInstructionsPrompt })
1426
+ : {
1427
+ action: "preserve",
1428
+ state: "custom",
1429
+ reason: "non-plugin setup mode",
1430
+ };
1431
+ let pluginAgentsMdPathExists = false;
1432
+ let pluginAgentsMdIsSymlink = false;
1433
+ try {
1434
+ const pluginAgentsMdStat = await lstat(pluginAgentsMdDst);
1435
+ pluginAgentsMdPathExists = true;
1436
+ pluginAgentsMdIsSymlink = pluginAgentsMdStat.isSymbolicLink();
1437
+ }
1438
+ catch {
1439
+ pluginAgentsMdPathExists = false;
1440
+ pluginAgentsMdIsSymlink = false;
1441
+ }
1232
1442
  const usePluginAgentsMdDefault = isPluginInstallMode
1233
- ? pluginAgentsMdPrompt
1234
- ? await pluginAgentsMdPrompt(pluginAgentsMdDst)
1235
- : await promptForPluginAgentsMdDefault(pluginAgentsMdDst)
1443
+ ? options.mergeAgents || pluginAgentsMdIsSymlink
1444
+ ? false
1445
+ : force
1446
+ ? true
1447
+ : pluginAgentsMdPrompt
1448
+ ? await pluginAgentsMdPrompt(pluginAgentsMdDst)
1449
+ : await promptForPluginAgentsMdDefault(pluginAgentsMdDst)
1236
1450
  : false;
1237
1451
  console.log("oh-my-codex setup");
1238
1452
  console.log("=================\n");
@@ -1382,7 +1596,11 @@ export async function setup(options = {}) {
1382
1596
  }
1383
1597
  // Step 4: Install native agent configs
1384
1598
  console.log("[4/8] Installing native agent configs...");
1385
- if (isPluginInstallMode) {
1599
+ if (skipNativeAgentRefresh) {
1600
+ summary.nativeAgents = createEmptyCategorySummary();
1601
+ console.log(" Native agent refresh skipped for background update-check setup refresh.\n");
1602
+ }
1603
+ else if (isPluginInstallMode) {
1386
1604
  summary.nativeAgents = await refreshNativeAgentConfigs(pkgRoot, scopeDirs.nativeAgentsDir, backupContext, {
1387
1605
  force,
1388
1606
  dryRun,
@@ -1443,6 +1661,7 @@ export async function setup(options = {}) {
1443
1661
  verbose,
1444
1662
  preserveFirstPartyMcp: shouldOfferFirstPartyMcpRemoval &&
1445
1663
  !removeFirstPartyMcpRegistrations,
1664
+ developerInstructionsDecision: pluginDeveloperInstructionsDecision,
1446
1665
  });
1447
1666
  if (configCleaned)
1448
1667
  summary.config.removed += 1;
@@ -1495,11 +1714,11 @@ export async function setup(options = {}) {
1495
1714
  console.log(pluginScopedHooksSupported
1496
1715
  ? " Plugin-scoped Codex hooks and runtime feature flags refresh complete (plugin_hooks, goals).\n"
1497
1716
  : ` Native Codex hooks fallback and runtime feature flags refresh complete (${scopeDirs.codexHooksFile}; hooks, goals).\n`);
1498
- if (usePluginDeveloperInstructionsDefault) {
1717
+ if (pluginDeveloperInstructionsDecision.action !== "preserve") {
1499
1718
  const developerInstructionsResult = await applyPluginDeveloperInstructionsDefault(scopeDirs.codexConfigFile, backupContext, summary.config, {
1500
1719
  dryRun,
1501
1720
  verbose,
1502
- pluginDeveloperInstructionsOverwritePrompt,
1721
+ decision: pluginDeveloperInstructionsDecision,
1503
1722
  });
1504
1723
  if (developerInstructionsResult === "updated") {
1505
1724
  resolvedConfig = existsSync(scopeDirs.codexConfigFile)
@@ -1512,7 +1731,7 @@ export async function setup(options = {}) {
1512
1731
  }
1513
1732
  }
1514
1733
  else {
1515
- console.log(" Plugin-mode developer_instructions default not selected.\n");
1734
+ console.log(` Plugin-mode developer_instructions default preserved (${pluginDeveloperInstructionsDecision.reason}).\n`);
1516
1735
  }
1517
1736
  }
1518
1737
  else {
@@ -1537,6 +1756,7 @@ export async function setup(options = {}) {
1537
1756
  const existingHooksContent = existsSync(scopeDirs.codexHooksFile)
1538
1757
  ? await readFile(scopeDirs.codexHooksFile, "utf-8")
1539
1758
  : null;
1759
+ await migrateLegacyHooksJsonTrustStateToConfig(scopeDirs.codexConfigFile, existingHooksContent, backupContext, summary.config, { dryRun, verbose });
1540
1760
  const hooksConfig = mergeManagedCodexHooksConfig(existingHooksContent, pkgRoot, scopeDirs.codexHooksFile, { platform: process.platform, codexHomeDir: scopeDirs.codexHomeDir });
1541
1761
  await syncManagedContent(hooksConfig, scopeDirs.codexHooksFile, summary.config, backupContext, { dryRun, verbose }, `native hooks ${scopeDirs.codexHooksFile}`);
1542
1762
  await syncManagedWindowsNativeHookShim(scopeDirs.codexHomeDir, pkgRoot, summary.config, backupContext, { dryRun, verbose });
@@ -1560,51 +1780,99 @@ export async function setup(options = {}) {
1560
1780
  console.log();
1561
1781
  // Step 6: Generate AGENTS.md
1562
1782
  console.log("[6/8] Generating AGENTS.md...");
1783
+ const activeSession = resolvedScope.scope === "project"
1784
+ ? await readSessionState(projectRoot)
1785
+ : null;
1786
+ const sessionIsActive = activeSession && !isSessionStale(activeSession);
1563
1787
  if (isPluginInstallMode) {
1564
- const agentsMdRemoved = await cleanupPluginModeLegacyAgentsMd(pluginAgentsMdDst, backupContext, { dryRun, verbose });
1565
- if (agentsMdRemoved) {
1566
- summary.agentsMd.removed += 1;
1567
- console.log(` ${dryRun ? "Would remove" : "Removed"} legacy OMX-generated AGENTS.md for plugin mode.\n`);
1568
- }
1569
- if (usePluginAgentsMdDefault) {
1570
- const agentsMdSrc = join(pkgRoot, "templates", "AGENTS.md");
1571
- if (existsSync(agentsMdSrc)) {
1572
- const content = await readFile(agentsMdSrc, "utf-8");
1573
- const modelTableContext = resolveAgentsModelTableContext(resolvedConfig, {
1574
- codexHomeOverride: scopeDirs.codexHomeDir,
1575
- });
1576
- const modelTableDefinitions = getAgentsModelTableDefinitionsForTeamMode(resolvedTeamMode);
1577
- const rewritten = upsertAgentsModelTable(addGeneratedAgentsMarker(applyTeamModeToAgentsTemplate(applyPluginModeWordingToAgentsTemplate(content, resolvedScope.scope), resolvedTeamMode)), modelTableContext, modelTableDefinitions);
1578
- const result = await syncManagedAgentsContent(rewritten, pluginAgentsMdDst, summary.agentsMd, backupContext, {
1579
- agentsOverwritePrompt: options.agentsOverwritePrompt,
1580
- dryRun,
1581
- force,
1582
- verbose,
1583
- });
1584
- if (result === "updated") {
1585
- console.log(resolvedScope.scope === "project"
1586
- ? " Generated plugin-mode AGENTS.md defaults in project root."
1587
- : ` Generated plugin-mode AGENTS.md defaults in ${scopeDirs.codexHomeDir}.`);
1788
+ const agentsMdSrc = join(pkgRoot, "templates", "AGENTS.md");
1789
+ const pluginAgentsMdExists = pluginAgentsMdPathExists;
1790
+ if (existsSync(agentsMdSrc)) {
1791
+ const content = await readFile(agentsMdSrc, "utf-8");
1792
+ const modelTableContext = resolveAgentsModelTableContext(resolvedConfig, {
1793
+ codexHomeOverride: scopeDirs.codexHomeDir,
1794
+ });
1795
+ const modelTableDefinitions = getAgentsModelTableDefinitionsForTeamMode(resolvedTeamMode);
1796
+ const rewritten = upsertAgentsModelTable(addGeneratedAgentsMarker(applyTeamModeToAgentsTemplate(applyPluginModeWordingToAgentsTemplate(content, resolvedScope.scope), resolvedTeamMode)), modelTableContext, modelTableDefinitions);
1797
+ if (options.mergeAgents && pluginAgentsMdExists) {
1798
+ if (pluginAgentsMdIsSymlink) {
1799
+ summary.agentsMd.skipped += 1;
1800
+ console.log(` Skipped plugin-mode AGENTS.md merge for symlinked ${pluginAgentsMdDst}; existing AGENTS.md left untouched.`);
1588
1801
  }
1589
- else if (result === "unchanged") {
1590
- console.log(resolvedScope.scope === "project"
1591
- ? " Plugin-mode AGENTS.md defaults already up to date in project root."
1592
- : ` Plugin-mode AGENTS.md defaults already up to date in ${scopeDirs.codexHomeDir}.`);
1802
+ else {
1803
+ const existing = await readFile(pluginAgentsMdDst, "utf-8");
1804
+ const mergedAgentsContent = upsertManagedAgentsBlock(existing, rewritten);
1805
+ const canApplyManagedAgentsMerge = mergedAgentsContent !== existing;
1806
+ if (resolvedScope.scope === "project" &&
1807
+ sessionIsActive &&
1808
+ canApplyManagedAgentsMerge) {
1809
+ summary.agentsMd.skipped += 1;
1810
+ console.log(" WARNING: Active omx session detected (pid " +
1811
+ activeSession?.pid +
1812
+ ").");
1813
+ console.log(" Skipping AGENTS.md overwrite to avoid corrupting runtime overlay.");
1814
+ console.log(" Stop the active session first, then re-run setup.");
1815
+ }
1816
+ else if (!canApplyManagedAgentsMerge) {
1817
+ summary.agentsMd.unchanged += 1;
1818
+ console.log(resolvedScope.scope === "project"
1819
+ ? " Plugin-mode AGENTS.md already up to date in project root."
1820
+ : ` Plugin-mode AGENTS.md already up to date in ${scopeDirs.codexHomeDir}.`);
1821
+ }
1822
+ else {
1823
+ await syncManagedContent(mergedAgentsContent, pluginAgentsMdDst, summary.agentsMd, backupContext, { dryRun, verbose }, `plugin AGENTS merge ${pluginAgentsMdDst}`);
1824
+ console.log(resolvedScope.scope === "project"
1825
+ ? " Merged plugin-mode OMX-managed AGENTS.md sections into project root."
1826
+ : ` Merged plugin-mode OMX-managed AGENTS.md sections into ${scopeDirs.codexHomeDir}.`);
1827
+ }
1828
+ }
1829
+ }
1830
+ else if (usePluginAgentsMdDefault) {
1831
+ const defaultWouldChange = pluginAgentsMdExists
1832
+ ? (await readFile(pluginAgentsMdDst, "utf-8")) !== rewritten
1833
+ : true;
1834
+ if (resolvedScope.scope === "project" &&
1835
+ sessionIsActive &&
1836
+ defaultWouldChange) {
1837
+ summary.agentsMd.skipped += 1;
1838
+ console.log(" WARNING: Active omx session detected (pid " +
1839
+ activeSession?.pid +
1840
+ ").");
1841
+ console.log(" Skipping AGENTS.md overwrite to avoid corrupting runtime overlay.");
1842
+ console.log(" Stop the active session first, then re-run setup.");
1593
1843
  }
1594
1844
  else {
1595
- console.log(` Skipped plugin-mode AGENTS.md defaults for ${pluginAgentsMdDst}.`);
1845
+ const result = await syncManagedAgentsContent(rewritten, pluginAgentsMdDst, summary.agentsMd, backupContext, {
1846
+ agentsOverwritePrompt: options.agentsOverwritePrompt,
1847
+ dryRun,
1848
+ force,
1849
+ verbose,
1850
+ });
1851
+ if (result === "updated") {
1852
+ console.log(resolvedScope.scope === "project"
1853
+ ? " Generated plugin-mode AGENTS.md defaults in project root."
1854
+ : ` Generated plugin-mode AGENTS.md defaults in ${scopeDirs.codexHomeDir}.`);
1855
+ }
1856
+ else if (result === "unchanged") {
1857
+ console.log(resolvedScope.scope === "project"
1858
+ ? " Plugin-mode AGENTS.md defaults already up to date in project root."
1859
+ : ` Plugin-mode AGENTS.md defaults already up to date in ${scopeDirs.codexHomeDir}.`);
1860
+ }
1861
+ else {
1862
+ console.log(` Skipped plugin-mode AGENTS.md defaults for ${pluginAgentsMdDst}.`);
1863
+ }
1596
1864
  }
1597
1865
  }
1598
1866
  else {
1599
1867
  summary.agentsMd.skipped += 1;
1600
- console.log(" AGENTS.md template not found, skipping.");
1868
+ console.log(pluginAgentsMdExists
1869
+ ? " Plugin-mode AGENTS.md defaults not selected; existing AGENTS.md left untouched.\n"
1870
+ : " Plugin-mode AGENTS.md defaults not selected; no AGENTS.md was generated.\n");
1601
1871
  }
1602
1872
  }
1603
1873
  else {
1604
1874
  summary.agentsMd.skipped += 1;
1605
- console.log(agentsMdRemoved
1606
- ? " Plugin-mode AGENTS.md defaults not selected.\n"
1607
- : " AGENTS.md generation skipped; no legacy OMX-generated AGENTS.md found and defaults not selected.\n");
1875
+ console.log(" AGENTS.md template not found, skipping.");
1608
1876
  }
1609
1877
  }
1610
1878
  else {
@@ -1614,10 +1882,6 @@ export async function setup(options = {}) {
1614
1882
  : join(scopeDirs.codexHomeDir, "AGENTS.md");
1615
1883
  const agentsMdExists = existsSync(agentsMdDst);
1616
1884
  // Guard: refuse to overwrite project-root AGENTS.md during active session
1617
- const activeSession = resolvedScope.scope === "project"
1618
- ? await readSessionState(projectRoot)
1619
- : null;
1620
- const sessionIsActive = activeSession && !isSessionStale(activeSession);
1621
1885
  if (existsSync(agentsMdSrc)) {
1622
1886
  const content = await readFile(agentsMdSrc, "utf-8");
1623
1887
  const modelTableContext = resolveAgentsModelTableContext(resolvedConfig, {
@@ -1772,7 +2036,7 @@ export async function setup(options = {}) {
1772
2036
  if (isPluginInstallMode) {
1773
2037
  console.log(` 2. Registered Codex marketplace ${OMX_LOCAL_MARKETPLACE_NAME} supplies OMX skills and workflow surfaces`);
1774
2038
  console.log(" 3. Browse plugin-provided skills with /skills");
1775
- console.log(" 4. Optional AGENTS.md and developer_instructions defaults are only installed when selected during plugin-mode setup");
2039
+ console.log(" 4. Plugin-mode AGENTS.md defaults provide persistent orchestration guidance; developer_instructions is an optional bootstrap");
1776
2040
  console.log(" 5. Native agent role TOML files written to .codex/agents/ for agent_type routing");
1777
2041
  }
1778
2042
  else {
@@ -1875,6 +2139,93 @@ async function syncManagedContent(content, dstPath, summary, backupContext, opti
1875
2139
  console.log(` ${options.dryRun ? "would update" : "updated"} ${verboseLabel}`);
1876
2140
  }
1877
2141
  }
2142
+ function hashContent(content) {
2143
+ return createHash("sha256").update(content).digest("hex");
2144
+ }
2145
+ function nativeAgentInstallManifestPath(agentsDir) {
2146
+ return join(agentsDir, "..", ".omx", "native-agents.json");
2147
+ }
2148
+ async function readNativeAgentInstallManifest(agentsDir) {
2149
+ const manifestPath = nativeAgentInstallManifestPath(agentsDir);
2150
+ if (!existsSync(manifestPath))
2151
+ return { version: 1, files: {} };
2152
+ try {
2153
+ const parsed = JSON.parse(await readFile(manifestPath, "utf-8"));
2154
+ if (parsed.version !== 1 ||
2155
+ !parsed.files ||
2156
+ typeof parsed.files !== "object") {
2157
+ return { version: 1, files: {} };
2158
+ }
2159
+ const files = {};
2160
+ for (const [fileName, entry] of Object.entries(parsed.files)) {
2161
+ if (!fileName.endsWith(".toml"))
2162
+ continue;
2163
+ if (!entry || typeof entry !== "object")
2164
+ continue;
2165
+ const sha256 = entry.sha256;
2166
+ if (typeof sha256 === "string" && /^[0-9a-f]{64}$/i.test(sha256)) {
2167
+ files[fileName] = { sha256: sha256.toLowerCase() };
2168
+ }
2169
+ }
2170
+ return { version: 1, files };
2171
+ }
2172
+ catch {
2173
+ return { version: 1, files: {} };
2174
+ }
2175
+ }
2176
+ async function writeNativeAgentInstallManifest(agentsDir, manifest) {
2177
+ const manifestPath = nativeAgentInstallManifestPath(agentsDir);
2178
+ await mkdir(dirname(manifestPath), { recursive: true });
2179
+ const sortedFiles = Object.fromEntries(Object.entries(manifest.files).sort(([left], [right]) => left.localeCompare(right)));
2180
+ await writeFile(manifestPath, JSON.stringify({ version: 1, files: sortedFiles }, null, 2) + "\n");
2181
+ }
2182
+ async function syncNativeAgentToml(content, dstPath, summary, backupContext, options, verboseLabel, manifest) {
2183
+ const fileName = basename(dstPath);
2184
+ const nextHash = hashContent(content);
2185
+ const destinationExists = existsSync(dstPath);
2186
+ if (!destinationExists) {
2187
+ if (!options.dryRun) {
2188
+ await mkdir(dirname(dstPath), { recursive: true });
2189
+ await writeFile(dstPath, content);
2190
+ manifest.files[fileName] = { sha256: nextHash };
2191
+ }
2192
+ summary.updated += 1;
2193
+ if (options.verbose) {
2194
+ console.log(` ${options.dryRun ? "would update" : "updated"} ${verboseLabel}`);
2195
+ }
2196
+ return;
2197
+ }
2198
+ const existing = await readFile(dstPath, "utf-8");
2199
+ const existingHash = hashContent(existing);
2200
+ if (existing === content) {
2201
+ if (!options.dryRun) {
2202
+ manifest.files[fileName] = { sha256: nextHash };
2203
+ }
2204
+ summary.unchanged += 1;
2205
+ return;
2206
+ }
2207
+ const priorHash = manifest.files[fileName]?.sha256;
2208
+ const safeToOverwrite = options.force || priorHash === existingHash;
2209
+ if (!safeToOverwrite) {
2210
+ summary.skipped += 1;
2211
+ if (options.verbose) {
2212
+ console.log(` skipped ${verboseLabel} (local modifications preserved; use --force to overwrite)`);
2213
+ }
2214
+ return;
2215
+ }
2216
+ if (await ensureBackup(dstPath, true, backupContext, options)) {
2217
+ summary.backedUp += 1;
2218
+ }
2219
+ if (!options.dryRun) {
2220
+ await mkdir(dirname(dstPath), { recursive: true });
2221
+ await writeFile(dstPath, content);
2222
+ manifest.files[fileName] = { sha256: nextHash };
2223
+ }
2224
+ summary.updated += 1;
2225
+ if (options.verbose) {
2226
+ console.log(` ${options.dryRun ? "would update" : "updated"} ${verboseLabel}`);
2227
+ }
2228
+ }
1878
2229
  async function syncManagedWindowsNativeHookShim(codexHomeDir, pkgRoot, summary, backupContext, options) {
1879
2230
  if (process.platform !== "win32")
1880
2231
  return;
@@ -2059,6 +2410,7 @@ async function refreshNativeAgentConfigs(pkgRoot, agentsDir, backupContext, opti
2059
2410
  if (!options.dryRun) {
2060
2411
  await mkdir(agentsDir, { recursive: true });
2061
2412
  }
2413
+ const nativeAgentManifest = await readNativeAgentInstallManifest(agentsDir);
2062
2414
  const manifest = tryReadCatalogManifest();
2063
2415
  const agentStatusByName = manifest
2064
2416
  ? getCatalogAgentStatusByName(manifest)
@@ -2093,7 +2445,7 @@ async function refreshNativeAgentConfigs(pkgRoot, agentsDir, backupContext, opti
2093
2445
  codexHomeOverride: join(agentsDir, ".."),
2094
2446
  });
2095
2447
  const dst = join(agentsDir, `${name}.toml`);
2096
- await syncManagedContent(toml, dst, summary, backupContext, options, `native agent ${name}.toml`);
2448
+ await syncNativeAgentToml(toml, dst, summary, backupContext, options, `native agent ${name}.toml`, nativeAgentManifest);
2097
2449
  }
2098
2450
  summary.removed += await cleanupObsoleteNativeAgents(agentsDir, backupContext, options);
2099
2451
  if (manifest) {
@@ -2124,6 +2476,7 @@ async function refreshNativeAgentConfigs(pkgRoot, agentsDir, backupContext, opti
2124
2476
  }
2125
2477
  if (!options.dryRun) {
2126
2478
  await rm(staleAgentPath, { force: true });
2479
+ delete nativeAgentManifest.files[file];
2127
2480
  }
2128
2481
  summary.removed += 1;
2129
2482
  if (options.verbose) {
@@ -2136,6 +2489,9 @@ async function refreshNativeAgentConfigs(pkgRoot, agentsDir, backupContext, opti
2136
2489
  }
2137
2490
  }
2138
2491
  }
2492
+ if (!options.dryRun) {
2493
+ await writeNativeAgentInstallManifest(agentsDir, nativeAgentManifest);
2494
+ }
2139
2495
  return summary;
2140
2496
  }
2141
2497
  async function cleanupObsoleteNativeAgents(agentsDir, backupContext, options) {