oh-my-codex 0.21.17 → 0.21.19

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 (34) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/dist/agents/__tests__/native-config.test.js +468 -2
  4. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  5. package/dist/agents/native-config.d.ts +5 -0
  6. package/dist/agents/native-config.d.ts.map +1 -1
  7. package/dist/agents/native-config.js +63 -7
  8. package/dist/agents/native-config.js.map +1 -1
  9. package/dist/cli/__tests__/doctor-invalid-config.test.js +57 -0
  10. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  11. package/dist/cli/__tests__/doctor-spark-routing.test.js +23 -0
  12. package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
  13. package/dist/cli/__tests__/plugin-cache-live-pin-3790.test.js +1 -1
  14. package/dist/cli/__tests__/setup-refresh.test.js +54 -0
  15. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  16. package/dist/cli/doctor.d.ts.map +1 -1
  17. package/dist/cli/doctor.js +9 -2
  18. package/dist/cli/doctor.js.map +1 -1
  19. package/dist/config/__tests__/generator-idempotent.test.js +64 -1
  20. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  21. package/dist/config/generator.d.ts.map +1 -1
  22. package/dist/config/generator.js +66 -21
  23. package/dist/config/generator.js.map +1 -1
  24. package/dist/config/models.d.ts +37 -0
  25. package/dist/config/models.d.ts.map +1 -1
  26. package/dist/config/models.js +98 -0
  27. package/dist/config/models.js.map +1 -1
  28. package/dist/team/__tests__/tmux-session.test.js +18 -0
  29. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  30. package/dist/team/tmux-session.d.ts.map +1 -1
  31. package/dist/team/tmux-session.js +34 -5
  32. package/dist/team/tmux-session.js.map +1 -1
  33. package/package.json +1 -1
  34. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
package/Cargo.lock CHANGED
@@ -32,7 +32,7 @@ checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
32
32
 
33
33
  [[package]]
34
34
  name = "omx-api"
35
- version = "0.21.17"
35
+ version = "0.21.19"
36
36
  dependencies = [
37
37
  "serde",
38
38
  "serde_json",
@@ -40,14 +40,14 @@ dependencies = [
40
40
 
41
41
  [[package]]
42
42
  name = "omx-explore-harness"
43
- version = "0.21.17"
43
+ version = "0.21.19"
44
44
  dependencies = [
45
45
  "libc",
46
46
  ]
47
47
 
48
48
  [[package]]
49
49
  name = "omx-mux"
50
- version = "0.21.17"
50
+ version = "0.21.19"
51
51
  dependencies = [
52
52
  "serde",
53
53
  "serde_json",
@@ -55,7 +55,7 @@ dependencies = [
55
55
 
56
56
  [[package]]
57
57
  name = "omx-runtime"
58
- version = "0.21.17"
58
+ version = "0.21.19"
59
59
  dependencies = [
60
60
  "fs2",
61
61
  "libc",
@@ -67,7 +67,7 @@ dependencies = [
67
67
 
68
68
  [[package]]
69
69
  name = "omx-runtime-core"
70
- version = "0.21.17"
70
+ version = "0.21.19"
71
71
  dependencies = [
72
72
  "fs2",
73
73
  "serde",
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "omx-sparkshell"
79
- version = "0.21.17"
79
+ version = "0.21.19"
80
80
  dependencies = [
81
81
  "omx-mux",
82
82
  ]
package/Cargo.toml CHANGED
@@ -11,7 +11,7 @@ resolver = "2"
11
11
 
12
12
  [workspace.package]
13
13
 
14
- version = "0.21.17"
14
+ version = "0.21.19"
15
15
 
16
16
  edition = "2021"
17
17
  rust-version = "1.73"
@@ -306,6 +306,39 @@ describe("agents/native-config", () => {
306
306
  assert.doesNotMatch(toml, /resolved_model: gpt-5\.6-terra/, `${role} must not inherit exact-Terra pins`);
307
307
  }
308
308
  });
309
+ it("does not bake exactModel for planner/architect/researcher when custom provider is configured (fixes #3823)", async () => {
310
+ const codexHome = join(tmpdir(), `omx-custom-provider-test-${process.pid}`);
311
+ try {
312
+ // Create a config.toml with a custom provider
313
+ await mkdir(codexHome, { recursive: true });
314
+ await writeFile(join(codexHome, "config.toml"), [
315
+ 'model = "glm-5.3-flash"',
316
+ 'model_provider = "custom-provider"',
317
+ ].join("\n"));
318
+ // With custom provider, planner should NOT bake gpt-6-astra, should inherit from config
319
+ const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
320
+ codexHomeOverride: codexHome,
321
+ });
322
+ assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model field with custom provider");
323
+ // When model is not baked, agents inherit from config.toml at runtime
324
+ assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not receive exactModel guidance with custom provider");
325
+ // With custom provider, architect should NOT bake gpt-6-astra, should inherit from config
326
+ const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
327
+ codexHomeOverride: codexHome,
328
+ });
329
+ assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model field with custom provider");
330
+ assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not receive exactModel guidance with custom provider");
331
+ // With custom provider, researcher should NOT bake gpt-6-astra, should inherit from config
332
+ const researcherToml = generateAgentToml(AGENT_DEFINITIONS.researcher, "researcher prompt", {
333
+ codexHomeOverride: codexHome,
334
+ });
335
+ assert.doesNotMatch(researcherToml, /^model\s*=/m, "researcher should not bake model field with custom provider");
336
+ assert.doesNotMatch(researcherToml, /exact gpt-6-astra model/, "researcher should not receive exactModel guidance with custom provider");
337
+ }
338
+ finally {
339
+ await rm(codexHome, { recursive: true, force: true });
340
+ }
341
+ });
309
342
  it("applies exact-model Terra guidance only for resolved gpt-5.6-terra standard roles", () => {
310
343
  const agent = {
311
344
  name: "debugger",
@@ -487,9 +520,10 @@ describe("agents/native-config", () => {
487
520
  OMX_DEFAULT_SPARK_MODEL: "gpt-5.6-luna",
488
521
  },
489
522
  }));
523
+ // Use lowercase "openai" (built-in) instead of "OpenAI" (custom) to match expected behavior
490
524
  await writeFile(join(codexHome, "config.toml"), [
491
525
  'model = "gpt-5.6-sol"',
492
- 'model_provider = "OpenAI"',
526
+ 'model_provider = "openai"', // Built-in provider
493
527
  '',
494
528
  ].join('\n'));
495
529
  await writeFile(join(promptsDir, "explore.md"), "explore prompt");
@@ -498,7 +532,7 @@ describe("agents/native-config", () => {
498
532
  catalogManifest: manifestWithAgents(["explore"]),
499
533
  });
500
534
  const exploreToml = await readFile(join(outDir, "explore.toml"), "utf8");
501
- // Model resolved from env config, but without explicit per-agent override, not written to TOML
535
+ // Model resolved from env default for Spark-lane agents (gpt-5.6-luna), not from root config
502
536
  assert.match(exploreToml, /resolved_model: gpt-5\.6-luna/);
503
537
  assert.doesNotMatch(exploreToml, /model_provider = /);
504
538
  }
@@ -641,5 +675,437 @@ describe("agents/native-config", () => {
641
675
  await rm(root, { recursive: true, force: true });
642
676
  }
643
677
  });
678
+ it("does not bake exactModel pins for planner/architect/researcher when using custom provider (fixes #3823)", async () => {
679
+ const root = await mkdtemp(join(tmpdir(), "omx-issue-3823-custom-provider-"));
680
+ const codexHome = join(root, ".codex");
681
+ const promptsDir = join(root, "prompts");
682
+ const outDir = join(codexHome, "agents");
683
+ const previousCodexHome = process.env.CODEX_HOME;
684
+ const previousStandardModel = process.env.OMX_DEFAULT_STANDARD_MODEL;
685
+ try {
686
+ delete process.env.OMX_DEFAULT_STANDARD_MODEL; // Isolate test from env defaults
687
+ process.env.CODEX_HOME = codexHome;
688
+ await mkdir(promptsDir, { recursive: true });
689
+ await mkdir(codexHome, { recursive: true });
690
+ // Test 1: With custom provider, exactModel pins should NOT be baked
691
+ await writeFile(join(codexHome, "config.toml"), [
692
+ 'model = "glm-5.3-flash"',
693
+ 'model_provider = "custom"',
694
+ '',
695
+ '[model_providers.custom]',
696
+ 'name = "Custom Third-Party"',
697
+ 'base_url = "https://custom-provider.com/v1"',
698
+ 'wire_api = "responses"',
699
+ '',
700
+ ].join("\n"));
701
+ await writeFile(join(promptsDir, "planner.md"), "planner prompt");
702
+ await writeFile(join(promptsDir, "architect.md"), "architect prompt");
703
+ await writeFile(join(promptsDir, "researcher.md"), "researcher prompt");
704
+ await installNativeAgentConfigs(root, {
705
+ agentsDir: outDir,
706
+ catalogManifest: manifestWithAgents(["planner", "architect", "researcher"]),
707
+ });
708
+ // Planner should NOT have model baked, inherits from config.toml
709
+ let plannerToml = await readFile(join(outDir, "planner.toml"), "utf8");
710
+ assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model when using custom provider");
711
+ assert.match(plannerToml, /resolved_model: glm-5\.3-flash/, "planner should resolve to custom provider model from config");
712
+ // Architect should NOT have model baked, inherits from config.toml
713
+ let architectToml = await readFile(join(outDir, "architect.toml"), "utf8");
714
+ assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model when using custom provider");
715
+ assert.match(architectToml, /resolved_model: glm-5\.3-flash/, "architect should resolve to custom provider model from config");
716
+ // Researcher should NOT have model baked, inherits from config.toml
717
+ let researcherToml = await readFile(join(outDir, "researcher.toml"), "utf8");
718
+ assert.doesNotMatch(researcherToml, /^model\s*=/m, "researcher should not bake model when using custom provider");
719
+ assert.match(researcherToml, /resolved_model: glm-5\.3-flash/, "researcher should resolve to custom provider model from config");
720
+ // Test 2: With default provider, exactModel pins SHOULD be baked
721
+ await rm(outDir, { recursive: true, force: true });
722
+ await writeFile(join(codexHome, "config.toml"), 'model = "gpt-5.6-sol"\n');
723
+ await installNativeAgentConfigs(root, {
724
+ agentsDir: outDir,
725
+ catalogManifest: manifestWithAgents(["planner", "architect", "researcher"]),
726
+ });
727
+ // Planner SHOULD have exactModel baked with default provider
728
+ plannerToml = await readFile(join(outDir, "planner.toml"), "utf8");
729
+ assert.match(plannerToml, /model = "gpt-6-astra"/, "planner should bake exactModel when using default provider");
730
+ assert.match(plannerToml, /resolved_model: gpt-6-astra/, "planner should resolve to gpt-6-astra with default provider");
731
+ // Architect SHOULD have exactModel baked with default provider
732
+ architectToml = await readFile(join(outDir, "architect.toml"), "utf8");
733
+ assert.match(architectToml, /model = "gpt-6-astra"/, "architect should bake exactModel when using default provider");
734
+ // Researcher SHOULD have exactModel baked with default provider
735
+ researcherToml = await readFile(join(outDir, "researcher.toml"), "utf8");
736
+ assert.match(researcherToml, /model = "gpt-6-astra"/, "researcher should bake exactModel when using default provider");
737
+ }
738
+ finally {
739
+ if (typeof previousCodexHome === "string")
740
+ process.env.CODEX_HOME = previousCodexHome;
741
+ else
742
+ delete process.env.CODEX_HOME;
743
+ if (typeof previousStandardModel === "string")
744
+ process.env.OMX_DEFAULT_STANDARD_MODEL = previousStandardModel;
745
+ else
746
+ delete process.env.OMX_DEFAULT_STANDARD_MODEL;
747
+ await rm(root, { recursive: true, force: true });
748
+ }
749
+ });
750
+ it("resolves profile-selected custom provider and skips exactModel pins (P1 fix for #3824)", async () => {
751
+ const codexHome = join(tmpdir(), `omx-profile-provider-test-${process.pid}`);
752
+ try {
753
+ // Create a config.toml with a profile-selected custom provider
754
+ await mkdir(codexHome, { recursive: true });
755
+ await writeFile(join(codexHome, "config.toml"), [
756
+ 'profile = "custom-lane"',
757
+ '',
758
+ '[profiles.custom-lane]',
759
+ 'model_provider = "profile-custom-provider"',
760
+ 'model = "llm-5.0-custom"',
761
+ ].join("\n"));
762
+ // With profile-selected custom provider, planner should NOT bake exactModel
763
+ const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
764
+ codexHomeOverride: codexHome,
765
+ });
766
+ assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model when custom provider is in active profile");
767
+ assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not receive astra guidance when custom provider is in active profile");
768
+ // With profile-selected custom provider, architect should NOT bake exactModel
769
+ const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
770
+ codexHomeOverride: codexHome,
771
+ });
772
+ assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model when custom provider is in active profile");
773
+ assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not receive astra guidance when custom provider is in active profile");
774
+ }
775
+ finally {
776
+ await rm(codexHome, { recursive: true, force: true });
777
+ }
778
+ });
779
+ it("preserves exactModel pins for built-in openai-chatgpt provider (P2.1 fix for #3824)", async () => {
780
+ const codexHome = join(tmpdir(), `omx-builtin-provider-test-${process.pid}`);
781
+ const origCodexHome = process.env.CODEX_HOME;
782
+ try {
783
+ // Create config with built-in openai-chatgpt provider
784
+ // This would normally be set by omx auth add
785
+ await mkdir(codexHome, { recursive: true });
786
+ await writeFile(join(codexHome, "config.toml"), [
787
+ 'model = "gpt-6-astra"',
788
+ 'model_provider = "openai-chatgpt"',
789
+ ].join("\n"));
790
+ // With built-in openai-chatgpt provider, planner SHOULD bake exactModel
791
+ const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
792
+ codexHomeOverride: codexHome,
793
+ });
794
+ assert.match(plannerToml, /model = "gpt-6-astra"/, "planner should bake exactModel with openai-chatgpt provider");
795
+ assert.match(plannerToml, /exact gpt-6-astra model/, "planner should receive astra guidance with openai-chatgpt provider");
796
+ // With built-in openai-chatgpt provider, architect SHOULD bake exactModel
797
+ const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
798
+ codexHomeOverride: codexHome,
799
+ });
800
+ assert.match(architectToml, /model = "gpt-6-astra"/, "architect should bake exactModel with openai-chatgpt provider");
801
+ assert.match(architectToml, /exact gpt-6-astra model/, "architect should receive astra guidance with openai-chatgpt provider");
802
+ }
803
+ finally {
804
+ if (typeof origCodexHome === "string") {
805
+ process.env.CODEX_HOME = origCodexHome;
806
+ }
807
+ else {
808
+ delete process.env.CODEX_HOME;
809
+ }
810
+ await rm(codexHome, { recursive: true, force: true });
811
+ }
812
+ });
813
+ it("suppresses astra guidance for custom provider without root model (P2.2 fix for #3824)", async () => {
814
+ const codexHome = join(tmpdir(), `omx-custom-no-model-test-${process.pid}`);
815
+ try {
816
+ // Create a config.toml with a custom provider but no root model
817
+ // This is the configuration shape from tmux-session.test.ts:3683-3692
818
+ await mkdir(codexHome, { recursive: true });
819
+ await writeFile(join(codexHome, "config.toml"), [
820
+ 'model_provider = "custom_provider"',
821
+ '',
822
+ '[model_providers.custom_provider]',
823
+ 'name = "custom_provider"',
824
+ 'base_url = "http://localhost:3000/v1"',
825
+ 'wire_api = "responses"',
826
+ 'requires_openai_auth = true',
827
+ 'env_key = "CUSTOM_PROVIDER_API_KEY"',
828
+ ].join("\n"));
829
+ // With custom provider and no root model, planner should NOT emit astra guidance
830
+ const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
831
+ codexHomeOverride: codexHome,
832
+ });
833
+ assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model field with custom provider and no root model");
834
+ assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not emit astra guidance with custom provider and no root model");
835
+ // With custom provider and no root model, architect should NOT emit astra guidance
836
+ const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
837
+ codexHomeOverride: codexHome,
838
+ });
839
+ assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model field with custom provider and no root model");
840
+ assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not emit astra guidance with custom provider and no root model");
841
+ }
842
+ finally {
843
+ await rm(codexHome, { recursive: true, force: true });
844
+ }
845
+ });
846
+ it("preserves exactModel pins for profile-scoped built-in provider model (Codex finding 4309255932)", async () => {
847
+ const codexHome = join(tmpdir(), `omx-profile-builtin-model-test-${process.pid}`);
848
+ try {
849
+ // Create a config.toml with profile-scoped built-in provider and model, but no root model
850
+ // This reproduces the scenario from finding 4309255932
851
+ await mkdir(codexHome, { recursive: true });
852
+ await writeFile(join(codexHome, "config.toml"), [
853
+ 'profile = "work-lane"',
854
+ '',
855
+ '[profiles.work-lane]',
856
+ 'model_provider = "openai-chatgpt"',
857
+ 'model = "gpt-6-luna"',
858
+ ].join("\n"));
859
+ // With profile-scoped built-in provider and model (no root model),
860
+ // planner SHOULD bake exactModel because:
861
+ // - It has exactModel (gpt-6-astra)
862
+ // - The effective provider is built-in (openai-chatgpt from profile)
863
+ // - The effective model is configured (gpt-6-luna in profile)
864
+ const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
865
+ codexHomeOverride: codexHome,
866
+ });
867
+ assert.match(plannerToml, /model = "gpt-6-astra"/, "planner should bake exactModel with profile-scoped built-in provider and model");
868
+ assert.match(plannerToml, /exact gpt-6-astra model/, "planner should receive astra guidance with profile-scoped built-in provider and model");
869
+ // Same for architect
870
+ const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
871
+ codexHomeOverride: codexHome,
872
+ });
873
+ assert.match(architectToml, /model = "gpt-6-astra"/, "architect should bake exactModel with profile-scoped built-in provider and model");
874
+ assert.match(architectToml, /exact gpt-6-astra model/, "architect should receive astra guidance with profile-scoped built-in provider and model");
875
+ }
876
+ finally {
877
+ await rm(codexHome, { recursive: true, force: true });
878
+ }
879
+ });
880
+ it("preserves exact guidance for custom provider with per-agent override (Codex finding 4309255938)", async () => {
881
+ const codexHome = join(tmpdir(), `omx-custom-override-test-${process.pid}`);
882
+ try {
883
+ // Create a config.toml with custom provider, no root model, but explicit agentModels override
884
+ // This reproduces the scenario from finding 4309255938
885
+ await mkdir(codexHome, { recursive: true });
886
+ await writeFile(join(codexHome, "config.toml"), [
887
+ 'model_provider = "custom_provider"',
888
+ '',
889
+ '[model_providers.custom_provider]',
890
+ 'name = "custom_provider"',
891
+ 'base_url = "http://localhost:3000/v1"',
892
+ 'wire_api = "responses"',
893
+ 'requires_openai_auth = true',
894
+ 'env_key = "CUSTOM_PROVIDER_API_KEY"',
895
+ ].join("\n"));
896
+ await writeFile(join(codexHome, ".omx-config.json"), JSON.stringify({
897
+ agentModels: { planner: "gpt-5.6-terra" }
898
+ }));
899
+ // With custom provider, no root model, but explicit planner override to gpt-5.6-terra,
900
+ // planner SHOULD bake the override model AND receive terra guidance because:
901
+ // - There IS an explicit per-agent override (planner: "gpt-5.6-terra")
902
+ // - The overlay should NOT be suppressed when there's an override
903
+ const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
904
+ codexHomeOverride: codexHome,
905
+ });
906
+ assert.match(plannerToml, /model = "gpt-5\.6-terra"/, "planner should bake override model with per-agent override");
907
+ assert.match(plannerToml, /exact gpt-5\.6-terra model/, "planner should receive terra guidance with per-agent override");
908
+ // Architect should NOT have override or guidance (no override specified)
909
+ const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
910
+ codexHomeOverride: codexHome,
911
+ });
912
+ assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model without override");
913
+ assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not emit astra guidance without override");
914
+ }
915
+ finally {
916
+ await rm(codexHome, { recursive: true, force: true });
917
+ }
918
+ });
919
+ it("resolves profile model before falling back to env vars (Codex P2 finding)", async () => {
920
+ const codexHome = join(tmpdir(), `omx-profile-env-priority-test-${process.pid}`);
921
+ const origFrontierModel = process.env.OMX_DEFAULT_FRONTIER_MODEL;
922
+ try {
923
+ // Create a config.toml with profile-scoped built-in provider and model, but no root model
924
+ // Also set OMX_DEFAULT_FRONTIER_MODEL env var to a different model
925
+ // The profile model should take precedence and planner should return exactModel
926
+ await mkdir(codexHome, { recursive: true });
927
+ await writeFile(join(codexHome, "config.toml"), [
928
+ 'profile = "work-lane"',
929
+ '',
930
+ '[profiles.work-lane]',
931
+ 'model_provider = "openai-chatgpt"',
932
+ 'model = "gpt-6-luna"',
933
+ ].join("\n"));
934
+ // Set env var to a different model
935
+ process.env.OMX_DEFAULT_FRONTIER_MODEL = "gpt-5.6-terra";
936
+ // With profile-scoped built-in provider and model + env var set,
937
+ // planner should return exactModel (gpt-6-astra), not the env var default
938
+ const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
939
+ codexHomeOverride: codexHome,
940
+ });
941
+ assert.match(plannerToml, /model = "gpt-6-astra"/, "planner should bake exactModel, not env var default, when profile model is set");
942
+ assert.match(plannerToml, /exact gpt-6-astra model/, "planner should receive astra guidance, not fall back to env var");
943
+ // Same for architect
944
+ const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
945
+ codexHomeOverride: codexHome,
946
+ });
947
+ assert.match(architectToml, /model = "gpt-6-astra"/, "architect should bake exactModel, not env var default");
948
+ assert.match(architectToml, /exact gpt-6-astra model/, "architect should receive astra guidance");
949
+ }
950
+ finally {
951
+ if (typeof origFrontierModel === "string") {
952
+ process.env.OMX_DEFAULT_FRONTIER_MODEL = origFrontierModel;
953
+ }
954
+ else {
955
+ delete process.env.OMX_DEFAULT_FRONTIER_MODEL;
956
+ }
957
+ await rm(codexHome, { recursive: true, force: true });
958
+ }
959
+ });
960
+ it("resolves profile-scoped model before root fallback (Codex finding 4309857268)", async () => {
961
+ const codexHome = join(tmpdir(), `omx-profile-model-priority-test-${process.pid}`);
962
+ try {
963
+ // Create a config.toml with:
964
+ // - Root model: astra
965
+ // - Profile-scoped custom provider + model: custom_glm
966
+ // When the profile is active, metadata should reflect the profile model, not root
967
+ await mkdir(codexHome, { recursive: true });
968
+ await writeFile(join(codexHome, "config.toml"), [
969
+ 'profile = "custom-lane"',
970
+ 'model = "gpt-6-astra"',
971
+ '',
972
+ '[profiles.custom-lane]',
973
+ 'model_provider = "custom-glm-provider"',
974
+ 'model = "custom-glm-4.0"',
975
+ ].join("\n"));
976
+ // With profile-scoped custom provider + model + root astra,
977
+ // planner should use profile model for metadata, not root astra
978
+ const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
979
+ codexHomeOverride: codexHome,
980
+ });
981
+ // Should NOT emit astra guidance (the root model)
982
+ assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not resolve to root astra when profile has custom provider with model");
983
+ // Should NOT have astra in resolved_model if custom provider is active
984
+ assert.doesNotMatch(plannerToml, /resolved_model: gpt-6-astra/, "planner metadata should not report root model when profile model is configured");
985
+ }
986
+ finally {
987
+ await rm(codexHome, { recursive: true, force: true });
988
+ }
989
+ });
990
+ it("keeps exact pins when built-in provider omits model field (Codex finding 4309857276)", async () => {
991
+ const codexHome = join(tmpdir(), `omx-builtin-no-model-test-${process.pid}`);
992
+ try {
993
+ // Create a config.toml with explicit built-in provider (openai-chatgpt) but NO model field
994
+ // This should behave like the implicit default provider and keep exact pins
995
+ await mkdir(codexHome, { recursive: true });
996
+ await writeFile(join(codexHome, "config.toml"), [
997
+ 'model_provider = "openai-chatgpt"',
998
+ // Intentionally NO model field
999
+ ].join("\n"));
1000
+ // With explicit built-in provider but no root model,
1001
+ // planner should STILL bake exactModel (like implicit default)
1002
+ const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
1003
+ codexHomeOverride: codexHome,
1004
+ });
1005
+ assert.match(plannerToml, /model = "gpt-6-astra"/, "planner should bake exactModel when built-in provider is explicit even without root model");
1006
+ assert.match(plannerToml, /exact gpt-6-astra model/, "planner should receive exact guidance when built-in provider is explicit without root model");
1007
+ // Same for architect
1008
+ const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
1009
+ codexHomeOverride: codexHome,
1010
+ });
1011
+ assert.match(architectToml, /model = "gpt-6-astra"/, "architect should bake exactModel when built-in provider is explicit without root model");
1012
+ assert.match(architectToml, /exact gpt-6-astra model/, "architect should receive exact guidance when built-in provider is explicit without root model");
1013
+ }
1014
+ finally {
1015
+ await rm(codexHome, { recursive: true, force: true });
1016
+ }
1017
+ });
1018
+ it("omits fallback metadata for custom provider without root or profile model (Codex finding 4309974425)", async () => {
1019
+ const codexHome = join(tmpdir(), `omx-custom-provider-no-model-test-${process.pid}`);
1020
+ try {
1021
+ // Create a config.toml with custom provider but NO root model and NO profile model
1022
+ // The agent will inherit the custom provider's default model at runtime
1023
+ // So we should NOT emit resolved_model metadata claiming astra
1024
+ await mkdir(codexHome, { recursive: true });
1025
+ await writeFile(join(codexHome, "config.toml"), [
1026
+ 'model_provider = "custom-provider-x"',
1027
+ '',
1028
+ '[model_providers.custom-provider-x]',
1029
+ 'name = "custom-provider-x"',
1030
+ 'base_url = "http://localhost:8000/v1"',
1031
+ 'wire_api = "openai"',
1032
+ 'requires_openai_auth = false',
1033
+ 'env_key = "CUSTOM_PROVIDER_X_API_KEY"',
1034
+ ].join("\n"));
1035
+ // With custom provider and no root/profile model,
1036
+ // planner should NOT emit resolved_model metadata
1037
+ const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
1038
+ codexHomeOverride: codexHome,
1039
+ });
1040
+ assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model field with custom provider and no root model");
1041
+ assert.doesNotMatch(plannerToml, /resolved_model:/, "planner should not emit resolved_model metadata with custom provider and no root model");
1042
+ assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not emit astra guidance with custom provider and no root model");
1043
+ // Same for architect
1044
+ const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
1045
+ codexHomeOverride: codexHome,
1046
+ });
1047
+ assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model field with custom provider and no root model");
1048
+ assert.doesNotMatch(architectToml, /resolved_model:/, "architect should not emit resolved_model metadata with custom provider and no root model");
1049
+ assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not emit astra guidance with custom provider and no root model");
1050
+ }
1051
+ finally {
1052
+ await rm(codexHome, { recursive: true, force: true });
1053
+ }
1054
+ });
1055
+ it("treats mixed-case provider IDs as custom providers (Codex finding new)", async () => {
1056
+ const codexHome = join(tmpdir(), `omx-mixed-case-provider-test-${process.pid}`);
1057
+ try {
1058
+ // Create a config.toml with mixed-case provider ID ("OpenAI" instead of lowercase "openai")
1059
+ // This should be treated as a custom provider, NOT the built-in "openai" provider,
1060
+ // to preserve intentional custom provider configurations.
1061
+ await mkdir(codexHome, { recursive: true });
1062
+ await writeFile(join(codexHome, "config.toml"), [
1063
+ 'model_provider = "OpenAI"', // Custom provider (not built-in)
1064
+ 'model = "gpt-6-astra"',
1065
+ ].join("\n"));
1066
+ // With a custom provider, planner should NOT bake exactModel
1067
+ const plannerToml = generateAgentToml(AGENT_DEFINITIONS.planner, "planner prompt", {
1068
+ codexHomeOverride: codexHome,
1069
+ });
1070
+ assert.doesNotMatch(plannerToml, /^model\s*=/m, "planner should not bake model field with custom provider");
1071
+ assert.doesNotMatch(plannerToml, /exact gpt-6-astra model/, "planner should not receive exact guidance with custom provider");
1072
+ // Same for architect
1073
+ const architectToml = generateAgentToml(AGENT_DEFINITIONS.architect, "architect prompt", {
1074
+ codexHomeOverride: codexHome,
1075
+ });
1076
+ assert.doesNotMatch(architectToml, /^model\s*=/m, "architect should not bake model field with custom provider");
1077
+ assert.doesNotMatch(architectToml, /exact gpt-6-astra model/, "architect should not receive exact guidance with custom provider");
1078
+ }
1079
+ finally {
1080
+ await rm(codexHome, { recursive: true, force: true });
1081
+ }
1082
+ });
1083
+ it("applies profile models before built-in fallbacks (Codex finding 4310223529)", async () => {
1084
+ // This test verifies that when a built-in provider has an explicit model configured
1085
+ // (from profile or root), roles without exactModel get that model instead of
1086
+ // falling back to env/class-based defaults.
1087
+ const codexHome = join(tmpdir(), `omx-profile-model-test-${process.pid}`);
1088
+ try {
1089
+ // Create a config with built-in provider and explicit model
1090
+ // For roles without exactModel (like debugger), this model should be used
1091
+ // instead of falling back to resolveStandardModel/resolveFrontierModel
1092
+ await mkdir(codexHome, { recursive: true });
1093
+ await writeFile(join(codexHome, "config.toml"), [
1094
+ 'model = "gpt-5.6-terra"',
1095
+ 'model_provider = "openai"', // Built-in provider
1096
+ ].join("\n"));
1097
+ // Debugger has no exactModel, so with this fix it should:
1098
+ // 1. See built-in provider (openai)
1099
+ // 2. See configured model (gpt-5.6-terra)
1100
+ // 3. Return the configured model instead of falling back to modelClass defaults
1101
+ const debuggerToml = generateAgentToml(AGENT_DEFINITIONS.debugger, "debugger prompt", {
1102
+ codexHomeOverride: codexHome,
1103
+ });
1104
+ assert.match(debuggerToml, /resolved_model: gpt-5\.6-terra/, "debugger should resolve to configured model with built-in provider");
1105
+ }
1106
+ finally {
1107
+ await rm(codexHome, { recursive: true, force: true });
1108
+ }
1109
+ });
644
1110
  });
645
1111
  //# sourceMappingURL=native-config.test.js.map