@bitkyc08/opencodex 2.37.0 → 2.39.0-preview.20260901

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 (131) hide show
  1. package/bin/ocx.mjs +127 -16
  2. package/gui/dist/assets/index-6HZDkIpX.js +112 -0
  3. package/gui/dist/assets/index-uvENYLin.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/gui/dist/provider-icons/aside.svg +3 -0
  6. package/gui/dist/provider-icons/baseten.svg +13 -0
  7. package/gui/dist/provider-icons/bizrouter.svg +41 -0
  8. package/gui/dist/provider-icons/cerebras.svg +26 -0
  9. package/gui/dist/provider-icons/deepinfra.svg +75 -0
  10. package/gui/dist/provider-icons/deepseek-harness.svg +3 -0
  11. package/gui/dist/provider-icons/digitalocean.svg +10 -0
  12. package/gui/dist/provider-icons/featherless.svg +4 -0
  13. package/gui/dist/provider-icons/gajae-code.svg +410 -0
  14. package/gui/dist/provider-icons/hermes-agent.svg +207 -0
  15. package/gui/dist/provider-icons/hyperbolic.svg +18 -0
  16. package/gui/dist/provider-icons/kilo.svg +13 -0
  17. package/gui/dist/provider-icons/litellm.svg +1 -0
  18. package/gui/dist/provider-icons/minimax.svg +1 -0
  19. package/gui/dist/provider-icons/nanogpt.svg +74 -0
  20. package/gui/dist/provider-icons/nebius.svg +1 -0
  21. package/gui/dist/provider-icons/neuralwatt.svg +27 -0
  22. package/gui/dist/provider-icons/nous.svg +149 -0
  23. package/gui/dist/provider-icons/novita.svg +32 -0
  24. package/gui/dist/provider-icons/oh-my-pi.svg +11 -0
  25. package/gui/dist/provider-icons/openclaw.svg +54 -0
  26. package/gui/dist/provider-icons/orcarouter.svg +175 -0
  27. package/gui/dist/provider-icons/parallel.svg +13 -0
  28. package/gui/dist/provider-icons/prime-agent.svg +21 -0
  29. package/gui/dist/provider-icons/sambanova.svg +276 -0
  30. package/gui/dist/provider-icons/scaleway.svg +11 -0
  31. package/gui/dist/provider-icons/siliconflow.svg +18 -0
  32. package/gui/dist/provider-icons/synthetic.svg +12 -0
  33. package/gui/dist/provider-icons/together.svg +18 -0
  34. package/gui/dist/provider-icons/umans.svg +30 -0
  35. package/gui/dist/provider-icons/venice.svg +165 -0
  36. package/gui/dist/provider-icons/vultr.svg +15 -0
  37. package/gui/dist/provider-icons/zai.svg +218 -0
  38. package/gui/dist/provider-icons/zcode.svg +219 -0
  39. package/gui/dist/provider-icons/zenmux.svg +1 -0
  40. package/package.json +1 -1
  41. package/src/adapters/cursor/live-models.ts +1 -0
  42. package/src/adapters/cursor/protobuf-request.ts +4 -1
  43. package/src/adapters/cursor/tool-definitions.ts +36 -4
  44. package/src/adapters/openai-responses.ts +72 -10
  45. package/src/bridge.ts +15 -15
  46. package/src/cli/capabilities.ts +14 -0
  47. package/src/cli/codex-cli-update.ts +96 -0
  48. package/src/cli/codex-shim-autorestore.ts +3 -0
  49. package/src/cli/dispatch.ts +54 -18
  50. package/src/cli/export-command.ts +18 -17
  51. package/src/cli/help.ts +2 -2
  52. package/src/cli/index.ts +423 -32
  53. package/src/cli/integrations.ts +24 -2
  54. package/src/cli/launcher-context.ts +53 -2
  55. package/src/cli/opencode.ts +126 -33
  56. package/src/cli/registry.ts +16 -10
  57. package/src/cli/system-command.ts +6 -1
  58. package/src/cli/uninstall-plan.ts +86 -0
  59. package/src/clients/config-export.ts +293 -28
  60. package/src/codex/account-store.ts +131 -12
  61. package/src/codex/auth-api.ts +202 -33
  62. package/src/codex/autostart-health.ts +3 -3
  63. package/src/codex/catalog/provider-fetch.ts +96 -10
  64. package/src/codex/catalog/sync.ts +4 -3
  65. package/src/codex/cli-install-provenance.ts +795 -0
  66. package/src/codex/convergence.ts +4 -3
  67. package/src/codex/credential-mutation-epoch.ts +11 -0
  68. package/src/codex/history-job.ts +10 -0
  69. package/src/codex/history-manifest.ts +35 -2
  70. package/src/codex/history-provider.ts +196 -19
  71. package/src/codex/history-worker.ts +3 -0
  72. package/src/codex/main-account.ts +2 -0
  73. package/src/codex/model-entitlements.ts +430 -27
  74. package/src/codex/native-profile-manager.ts +4 -0
  75. package/src/codex/quota-401-recovery.ts +190 -0
  76. package/src/codex/quota-recovery-timing.ts +28 -0
  77. package/src/codex/quota.ts +6 -0
  78. package/src/codex/reset-credit-operation-ledger.ts +1411 -0
  79. package/src/codex/reset-credit-recovery.ts +20 -2
  80. package/src/codex/routing.ts +68 -16
  81. package/src/codex/shim.ts +204 -18
  82. package/src/codex/subagent-model-fallback.ts +4 -1
  83. package/src/codex/user-identity.ts +2 -1
  84. package/src/config/paths.ts +18 -3
  85. package/src/config/pending-teardown-names.d.mts +8 -0
  86. package/src/config/pending-teardown-names.mjs +69 -0
  87. package/src/config/pending-teardown.ts +286 -0
  88. package/src/config.ts +23 -0
  89. package/src/generated/compatibility-version.json +155 -67
  90. package/src/integrations/journal.ts +12 -1
  91. package/src/integrations/registry.ts +112 -0
  92. package/src/integrations/state.ts +67 -5
  93. package/src/integrations/writer.ts +107 -16
  94. package/src/lib/bounded-subprocess.ts +36 -0
  95. package/src/lib/process-control.ts +30 -5
  96. package/src/lib/state-store-registrations.ts +8 -0
  97. package/src/lib/strict-semver.ts +47 -0
  98. package/src/lib/windows-elevation.ts +32 -1
  99. package/src/lib/windows-secret-acl.ts +47 -25
  100. package/src/lib/windows-service-mutation-lock.ts +133 -0
  101. package/src/lib/windows-user-principal.ts +15 -17
  102. package/src/oauth/index.ts +220 -13
  103. package/src/oauth/store.ts +220 -18
  104. package/src/responses/spill-store.ts +354 -29
  105. package/src/responses/state.ts +775 -8
  106. package/src/server/index.ts +4 -3
  107. package/src/server/lifecycle.ts +5 -1
  108. package/src/server/management/integration-routes.ts +36 -5
  109. package/src/server/management/model-rows.ts +11 -2
  110. package/src/server/management/provider-routes.ts +4 -0
  111. package/src/server/management/system-restart.ts +5 -5
  112. package/src/server/management-api.ts +72 -13
  113. package/src/server/responses/encrypted-payload.ts +18 -1
  114. package/src/server/startup-action-control.ts +3 -2
  115. package/src/server/stop-teardown.ts +84 -0
  116. package/src/service.ts +749 -53
  117. package/src/sidecar/candidates.ts +1 -1
  118. package/src/update/codex-cli-update-launch-policy.d.mts +18 -0
  119. package/src/update/codex-cli-update-launch-policy.mjs +30 -0
  120. package/src/update/index.ts +39 -6
  121. package/src/update/job.ts +10 -11
  122. package/src/update/proxy-liveness-probe.d.mts +6 -0
  123. package/src/update/proxy-liveness-probe.mjs +84 -0
  124. package/src/update/stop-contract.d.mts +2 -0
  125. package/src/update/stop-contract.mjs +15 -0
  126. package/src/update/stop-decision.d.mts +10 -0
  127. package/src/update/stop-decision.mjs +34 -0
  128. package/src/vision/eligibility.ts +19 -1
  129. package/src/vision/index.ts +4 -18
  130. package/gui/dist/assets/index-CowztZdo.js +0 -112
  131. package/gui/dist/assets/index-jqE_VOKI.css +0 -1
@@ -0,0 +1,219 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
5
+ <style type="text/css">
6
+ .st0{opacity:0.3;fill:#E2E4E7;}
7
+ .st1{opacity:0.8;fill:#E2E4E7;stroke:#FFFFFF;stroke-width:5;stroke-miterlimit:10;}
8
+ .st2{fill:url(#SVGID_1_);}
9
+ .st3{fill:none;stroke:#E0E4E9;stroke-width:0.25;stroke-miterlimit:10;}
10
+ .st4{fill:none;}
11
+ .st5{fill:#9DA1A5;}
12
+ .st6{fill-rule:evenodd;clip-rule:evenodd;fill:none;}
13
+ .st7{fill-rule:evenodd;clip-rule:evenodd;fill:#DFE2E7;}
14
+ .st8{fill-rule:evenodd;clip-rule:evenodd;fill:#CDD4DA;}
15
+ .st9{fill-rule:evenodd;clip-rule:evenodd;fill:#B3BCC7;}
16
+ .st10{fill-rule:evenodd;clip-rule:evenodd;fill:#9DAAB7;}
17
+ .st11{fill-rule:evenodd;clip-rule:evenodd;fill:#8698A8;}
18
+ .st12{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_2_);}
19
+ .st13{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_3_);}
20
+ .st14{fill:#1F63EC;}
21
+ .st15{fill:#2D2D2D;}
22
+ .st16{fill:none;stroke:#E0E4E9;stroke-width:0.5;stroke-miterlimit:10;}
23
+ .st17{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_4_);}
24
+ .st18{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_5_);}
25
+ .st19{fill:none;stroke:#677380;stroke-width:0.5;stroke-miterlimit:10;}
26
+ .st20{fill:none;stroke:url(#SVGID_6_);stroke-width:2;stroke-miterlimit:10;}
27
+ .st21{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_7_);}
28
+ .st22{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_8_);}
29
+ .st23{fill:#FFFFFF;}
30
+ .st24{fill-rule:evenodd;clip-rule:evenodd;fill:#2D2D2D;}
31
+ .st25{clip-path:url(#SVGID_10_);}
32
+ .st26{clip-path:url(#SVGID_12_);}
33
+ .st27{fill:url(#SVGID_13_);}
34
+ .st28{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_14_);}
35
+ .st29{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_15_);}
36
+ .st30{clip-path:url(#SVGID_17_);}
37
+ .st31{clip-path:url(#SVGID_19_);}
38
+ .st32{fill:url(#SVGID_20_);}
39
+ .st33{fill:none;stroke:url(#SVGID_21_);stroke-width:2;stroke-miterlimit:10;}
40
+ .st34{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_22_);}
41
+ .st35{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_23_);}
42
+ .st36{clip-path:url(#SVGID_25_);}
43
+ .st37{clip-path:url(#SVGID_27_);}
44
+ .st38{fill:url(#SVGID_28_);}
45
+ .st39{clip-path:url(#SVGID_30_);}
46
+ .st40{clip-path:url(#SVGID_32_);}
47
+ .st41{fill:url(#SVGID_33_);}
48
+ .st42{fill-rule:evenodd;clip-rule:evenodd;fill:#126EF6;}
49
+ .st43{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
50
+ .st44{clip-path:url(#SVGID_35_);}
51
+ .st45{clip-path:url(#SVGID_37_);}
52
+ .st46{fill:url(#SVGID_38_);}
53
+ .st47{fill-rule:evenodd;clip-rule:evenodd;fill:#9DA1A5;}
54
+ .st48{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_39_);}
55
+ .st49{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_40_);}
56
+ .st50{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_41_);}
57
+ .st51{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_42_);}
58
+ .st52{fill:none;stroke:url(#SVGID_43_);stroke-width:2;stroke-miterlimit:10;}
59
+ .st53{fill-rule:evenodd;clip-rule:evenodd;fill:none;stroke:#E0E4E9;stroke-width:0.5;stroke-miterlimit:10;}
60
+ .st54{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_44_);}
61
+ .st55{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_45_);}
62
+ .st56{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_46_);}
63
+ .st57{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_47_);}
64
+ .st58{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_48_);}
65
+ .st59{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_49_);}
66
+ .st60{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_50_);}
67
+ .st61{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_51_);}
68
+ .st62{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_52_);}
69
+ .st63{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_53_);}
70
+ .st64{clip-path:url(#SVGID_55_);}
71
+ .st65{clip-path:url(#SVGID_57_);}
72
+ .st66{fill:url(#SVGID_58_);}
73
+ .st67{clip-path:url(#SVGID_60_);}
74
+ .st68{clip-path:url(#SVGID_62_);}
75
+ .st69{fill:url(#SVGID_63_);}
76
+ .st70{fill:none;stroke:url(#SVGID_64_);stroke-width:2;stroke-miterlimit:10;}
77
+ .st71{clip-path:url(#SVGID_66_);}
78
+ .st72{clip-path:url(#SVGID_68_);}
79
+ .st73{fill:url(#SVGID_69_);}
80
+ .st74{clip-path:url(#SVGID_71_);}
81
+ .st75{clip-path:url(#SVGID_73_);}
82
+ .st76{fill:url(#SVGID_74_);}
83
+ .st77{clip-path:url(#SVGID_76_);}
84
+ .st78{clip-path:url(#SVGID_78_);}
85
+ .st79{fill:url(#SVGID_79_);}
86
+ .st80{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_80_);}
87
+ .st81{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_81_);}
88
+ .st82{clip-path:url(#SVGID_83_);}
89
+ .st83{clip-path:url(#SVGID_85_);}
90
+ .st84{fill:url(#SVGID_86_);}
91
+ .st85{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_87_);}
92
+ .st86{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_88_);}
93
+ .st87{clip-path:url(#SVGID_90_);}
94
+ .st88{clip-path:url(#SVGID_92_);}
95
+ .st89{fill:url(#SVGID_93_);}
96
+ .st90{fill:none;stroke:url(#SVGID_94_);stroke-width:2;stroke-miterlimit:10;}
97
+ .st91{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_95_);}
98
+ .st92{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_96_);}
99
+ .st93{clip-path:url(#SVGID_98_);}
100
+ .st94{clip-path:url(#SVGID_100_);}
101
+ .st95{fill:url(#SVGID_101_);}
102
+ .st96{clip-path:url(#SVGID_103_);}
103
+ .st97{clip-path:url(#SVGID_105_);}
104
+ .st98{fill:url(#SVGID_106_);}
105
+ .st99{clip-path:url(#SVGID_108_);}
106
+ .st100{clip-path:url(#SVGID_110_);}
107
+ .st101{fill:url(#SVGID_111_);}
108
+ .st102{fill:#FFFFFF;stroke:#B3BCC7;stroke-width:0.275;stroke-miterlimit:10;}
109
+ .st103{clip-path:url(#SVGID_113_);}
110
+ .st104{fill:#FDD138;}
111
+ .st105{fill:#FCA62F;}
112
+ .st106{fill:#FB7927;}
113
+ .st107{fill:#F44B22;}
114
+ .st108{fill:#D81915;}
115
+ .st109{fill:#2D2D2D;stroke:#FFFFFF;stroke-width:0.3354;stroke-miterlimit:10;}
116
+ .st110{fill:none;stroke:#65727F;stroke-width:2;stroke-miterlimit:10;}
117
+ .st111{fill:none;stroke:#65727F;stroke-width:0.75;stroke-miterlimit:10;}
118
+ .st112{fill:url(#SVGID_114_);}
119
+ .st113{fill:#D06C50;}
120
+ .st114{fill:#2D2D2D;stroke:#B3BCC7;stroke-width:0.275;stroke-miterlimit:10;}
121
+ .st115{opacity:0.2;}
122
+ .st116{fill:none;stroke:#677380;stroke-width:0.3564;stroke-miterlimit:10;}
123
+ .st117{fill:none;stroke:#677380;stroke-width:0.3564;stroke-miterlimit:10;stroke-dasharray:1.0212,1.0212;}
124
+ .st118{fill:none;stroke:#677380;stroke-width:0.3564;stroke-miterlimit:10;stroke-dasharray:1.0205,1.0205;}
125
+ .st119{opacity:0.2;fill:none;}
126
+ .st120{fill:none;stroke:#677380;stroke-width:0.3689;stroke-miterlimit:10;}
127
+ .st121{fill:none;stroke:#677380;stroke-width:0.3689;stroke-miterlimit:10;stroke-dasharray:1.0509,1.0509;}
128
+ .st122{opacity:0.3;fill:#1F63EC;}
129
+ .st123{fill:#2D2D2D;stroke:#FFFFFF;stroke-width:0.3162;stroke-miterlimit:10;}
130
+ .st124{fill:#FFFFFF;stroke:#B3BCC7;stroke-width:0.3162;stroke-miterlimit:10;}
131
+ .st125{clip-path:url(#SVGID_118_);}
132
+ .st126{fill:url(#SVGID_119_);}
133
+ .st127{fill:none;stroke:#DFE2E7;stroke-width:0.75;stroke-miterlimit:10;}
134
+ .st128{fill:#9DA1A5;stroke:#FFFFFF;stroke-miterlimit:10;}
135
+ .st129{fill:url(#SVGID_120_);}
136
+ .st130{fill:none;stroke:#677380;stroke-width:0.75;stroke-miterlimit:10;}
137
+ .st131{opacity:0.4;}
138
+ .st132{clip-path:url(#SVGID_122_);}
139
+ .st133{clip-path:url(#SVGID_124_);}
140
+ .st134{fill:url(#SVGID_125_);}
141
+ .st135{fill:none;stroke:#8392A3;stroke-width:0.35;stroke-miterlimit:10;}
142
+ .st136{fill:none;stroke:#8392A3;stroke-width:0.35;stroke-miterlimit:10;stroke-dasharray:0.9951,0.9951;}
143
+ .st137{fill:none;stroke:#8392A3;stroke-width:0.35;stroke-miterlimit:10;stroke-dasharray:1.004,1.004;}
144
+ .st138{fill:none;stroke:url(#SVGID_126_);stroke-width:1.5;stroke-miterlimit:10;}
145
+ .st139{fill:url(#SVGID_127_);}
146
+ .st140{fill:none;stroke:#DDE0E4;stroke-width:0.35;stroke-miterlimit:10;}
147
+ .st141{fill:#2D2D2D;stroke:#A9B3BE;stroke-width:0.275;stroke-miterlimit:10;}
148
+ .st142{fill-rule:evenodd;clip-rule:evenodd;fill:#126EF4;}
149
+ .st143{fill:#FFFFFF;stroke:#B1BAC4;stroke-width:0.275;stroke-miterlimit:10;}
150
+ .st144{fill:#CE6C50;}
151
+ .st145{fill:#5B5B5B;}
152
+ .st146{fill:#8392A3;}
153
+ .st147{fill:none;stroke:url(#SVGID_128_);stroke-width:1.5;stroke-miterlimit:10;}
154
+ .st148{fill:url(#SVGID_129_);}
155
+ .st149{fill:none;stroke:#B5BDC4;stroke-width:0.7;stroke-miterlimit:10;}
156
+ .st150{opacity:0.6;fill:none;stroke:#78838E;stroke-width:0.35;stroke-miterlimit:10;}
157
+ .st151{opacity:0.2;fill:none;stroke:#8392A3;stroke-width:0.35;stroke-miterlimit:10;stroke-dasharray:1,1;}
158
+ .st152{fill:none;stroke:#DDE0E4;stroke-width:0.75;stroke-miterlimit:10;}
159
+ .st153{fill:none;stroke:#8392A3;stroke-width:0.5;stroke-miterlimit:10;}
160
+ .st154{opacity:0.2;fill:none;stroke:#677380;stroke-width:0.3564;stroke-miterlimit:10;stroke-dasharray:1.0182,1.0182;}
161
+ .st155{fill:none;stroke:#DDE0E4;stroke-width:0.765;stroke-miterlimit:10;}
162
+ .st156{fill:url(#SVGID_130_);}
163
+ .st157{fill:url(#SVGID_131_);}
164
+ .st158{fill:#B1BAC4;}
165
+ .st159{fill:#CBD1D8;}
166
+ .st160{fill:#0B1B2B;}
167
+ .st161{fill:#91D119;}
168
+ .st162{opacity:0.7;}
169
+ .st163{fill:#FFFFFF;stroke:#000000;stroke-width:0.4418;stroke-miterlimit:10;}
170
+ .st164{fill:none;stroke:#939CAA;stroke-width:0.2209;stroke-miterlimit:10;}
171
+ .st165{fill:none;stroke:#FFFFFF;stroke-width:3.0924;stroke-miterlimit:10;}
172
+ .st166{fill:url(#SVGID_132_);}
173
+ .st167{fill:none;stroke:url(#SVGID_133_);stroke-width:1.714;stroke-miterlimit:10;}
174
+ .st168{fill:url(#SVGID_134_);}
175
+ .st169{fill:url(#SVGID_135_);}
176
+ .st170{fill:url(#SVGID_136_);}
177
+ .st171{fill:url(#SVGID_137_);}
178
+ .st172{fill:url(#SVGID_138_);}
179
+ .st173{fill:url(#SVGID_139_);}
180
+ .st174{fill:url(#SVGID_140_);}
181
+ .st175{fill:url(#SVGID_141_);}
182
+ .st176{fill:url(#SVGID_142_);}
183
+ .st177{fill:url(#SVGID_143_);}
184
+ .st178{fill:url(#SVGID_144_);}
185
+ .st179{fill:none;stroke:#1F63EC;stroke-width:4;stroke-miterlimit:10;}
186
+ .st180{fill:none;stroke:#0B1B2B;stroke-width:4;stroke-miterlimit:10;}
187
+ .st181{fill:none;stroke:#677380;stroke-width:0.3989;stroke-miterlimit:10;}
188
+ .st182{fill:none;stroke:#677380;stroke-width:0.3989;stroke-miterlimit:10;stroke-dasharray:1.14,1.14;}
189
+ .st183{fill:#257AF1;}
190
+ .st184{opacity:0.3;fill:#FFFFFF;}
191
+ .st185{fill:none;stroke:#98A5B2;stroke-width:4;stroke-miterlimit:10;}
192
+ .st186{fill:none;stroke:#65727F;stroke-width:0.3989;stroke-miterlimit:10;}
193
+ .st187{fill:none;stroke:#65727F;stroke-width:0.3989;stroke-miterlimit:10;stroke-dasharray:1.14,1.14;}
194
+ .st188{fill:none;stroke:#DDDFE4;stroke-width:0.75;stroke-miterlimit:10;}
195
+ .st189{fill:#9A9EA2;}
196
+ .st190{fill-rule:evenodd;clip-rule:evenodd;fill:#3267AC;}
197
+ .st191{fill:#FFFFFF;stroke:#AFB8C3;stroke-width:0.275;stroke-miterlimit:10;}
198
+ .st192{fill:#C5694E;}
199
+ .st193{fill:#8192A2;}
200
+ .st194{fill:#2D2D2D;stroke:#FFFFFF;stroke-width:0.6317;stroke-miterlimit:10;}
201
+ </style>
202
+ <g id="图层_2">
203
+ </g>
204
+ <g id="图层_1">
205
+ <path class="st194" d="M24.51,28.51H5.49c-2.21,0-4-1.79-4-4V5.49c0-2.21,1.79-4,4-4h19.03c2.21,0,4,1.79,4,4v19.03
206
+ C28.51,26.72,26.72,28.51,24.51,28.51z"/>
207
+ <g>
208
+ <g>
209
+ <g>
210
+ <g>
211
+ <path class="st23" d="M15.47,7.1l-1.3,1.85c-0.2,0.29-0.54,0.47-0.9,0.47h-7.1V7.09C6.16,7.1,15.47,7.1,15.47,7.1z"/>
212
+ <polygon class="st23" points="24.3,7.1 13.14,22.91 5.7,22.91 16.86,7.1 "/>
213
+ <path class="st23" d="M14.53,22.91l1.31-1.86c0.2-0.29,0.54-0.47,0.9-0.47h7.09v2.33H14.53z"/>
214
+ </g>
215
+ </g>
216
+ </g>
217
+ </g>
218
+ </g>
219
+ </svg>
@@ -0,0 +1 @@
1
+ <svg width="160" height="160" xmlns="http://www.w3.org/2000/svg" fill="none"><path stroke="null" fill="#000" d="M77.776.421c8.25-.317 24.947.775 38.304 12.768 10.957 9.839 14.733 28.078 12.18 42.221-2.341 12.965-7.858 23.572-5.696 33.394 2.16 9.821 7.658 14.663 12.334 16.696 4.519 1.965 8.886 1.375 8.055 5.108-.832 3.732-6.482 5.107-10.608 5.107-2.262 0-9.103-.59-12.139-2.554-3.587-2.322-4.386-4.402-5.752-6.074-.231-.284-.64-.087-.586.276.622 4.113 2.338 11.094 5.12 15.423 2.252 3.503 3.183 6.058 8.341 10.38 5.16 4.32 7.57 8.249 2.553 11.031-4.456 2.471-14.142-2.585-19.839-8.839-5.127-5.629-8.697-12.572-10.108-17.09-.755-2.415-1.511-6.57-2.266-9.969-.1-.446-.711-.374-.725.084-.25 8.649-1.61 16.909-3.598 25.764-1.43 6.375-4.91 14.143-8.053 18.857-3.833 5.749-9.522 7.543-13.75 5.971-3.382-1.257-.983-5.531 0-9.232.982-3.7 2.947-11.439 4.27-19.918 1.197-7.674 1.514-15.271.981-22.47-.033-.439-.708-.506-.844-.087-1.102 3.376-3.18 8.972-4.696 12.376-3.162 7.099-5.501 10.214-11.983 17.677-5.83 6.714-22.597 16.207-24.75 6.876-.59-2.553 2.498-3.671 5.345-5.893 4.28-3.339 7.857-9.264 11.248-16.5 3.045-6.497 4.592-12.782 4.83-16.794.022-.387-.464-.533-.682-.213-1.41 2.067-3.485 4.865-5.771 7.185-3.819 3.877-8.054 7.867-12.966 9.822-7.897 3.143-15.95.982-15.361-4.714.492-4.758 5.804-3.773 12.416-10.987 6.482-7.072 8.643-12.573 9.035-18.662.314-4.872-4.451-16.434-6.874-21.607-3.863-8.25-6.718-31.665 4.91-46.75C55.188.226 72.67.617 77.777.42zM55.101 33.397c-5.408 0-8.112 4.151-8.112 9.272 0 5.12 3.004 9.271 8.112 9.271 5.108 0 8.113-4.15 8.113-9.271 0-5.12-2.705-9.272-8.113-9.272zm44.04 0c-5.408 0-8.112 4.151-8.113 9.272 0 5.12 3.005 9.271 8.113 9.271s8.113-4.15 8.113-9.271c0-5.12-2.705-9.272-8.113-9.272z" clip-rule="evenodd" fill-rule="evenodd"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.37.0",
3
+ "version": "2.39.0-preview.20260901",
4
4
  "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
@@ -262,6 +262,7 @@ async function fetchCursorUsableModelsHttp2Once(opts: CursorUsableModelsOptions)
262
262
  req.on("error", () => close({ ok: false, error: "transport", detail: "HTTP/2 request failed" }));
263
263
  req.on("end", () => {
264
264
  if (bodyRejected) return;
265
+ if (status === 0) return close({ ok: false, error: "transport", detail: "HTTP/2 response ended before headers" });
265
266
  if (status === 401 || status === 403) {
266
267
  return close({ ok: false, error: "auth", detail: `HTTP ${status}` });
267
268
  }
@@ -56,6 +56,7 @@ import {
56
56
  cursorToolsForActivePrompt,
57
57
  buildCursorToolGuidanceSystemNote,
58
58
  buildCursorToolDefinitions,
59
+ cursorToolWireName,
59
60
  cursorRequestHasShellAlias,
60
61
  CURSOR_SHELL_ALIAS_SYSTEM_NOTE,
61
62
  OCX_RESPONSES_TOOL_PROVIDER,
@@ -1091,7 +1092,9 @@ function toolCallStep(
1091
1092
  ): Uint8Array {
1092
1093
  const args: Record<string, Uint8Array> = {};
1093
1094
  for (const [key, value] of Object.entries(part.arguments ?? {})) args[key] = argBytes(value);
1094
- const toolName = namespacedToolName(part.namespace, part.name);
1095
+ // Replay the same provider-isolated identity advertised in this request. Returned calls are
1096
+ // restored to the client name, so transcript parts carry the client name again on the next turn.
1097
+ const toolName = cursorToolWireName(part);
1095
1098
  const decodedResult = result ? decodeResultParts(result) : undefined;
1096
1099
  const serialize = (maxImages: number): Uint8Array => toBinary(ConversationStepSchema, create(ConversationStepSchema, {
1097
1100
  message: {
@@ -12,6 +12,7 @@ export const CODEX_SHELL_COMMAND_TOOL = "shell_command";
12
12
  export const CODEX_UNIFIED_EXEC_TOOL = "exec";
13
13
  export const CODEX_WAIT_TOOL = "wait";
14
14
  export const CODEX_APPLY_PATCH_TOOL = "apply_patch";
15
+ export const CODEX_TOOL_SEARCH_TOOL = "tool_search";
15
16
  export const CURSOR_EDIT_FILE_TOOL = "edit_file";
16
17
  export const CURSOR_MULTI_EDIT_TOOL = "multi_edit";
17
18
  export const CURSOR_STRUCTURED_EDIT_TOOLS = [CURSOR_EDIT_FILE_TOOL, CURSOR_MULTI_EDIT_TOOL] as const;
@@ -169,8 +170,10 @@ function cursorToolChoiceMatches(
169
170
  }
170
171
  return tool.name === choiceName || cursorToolWireName(tool) === choiceName;
171
172
  }
172
- if (tool.name === choiceName || cursorToolWireName(tool) === choiceName) return true;
173
- return cursorToolChoiceAliases(tool).includes(choiceName);
173
+ if (tool.name === choiceName) return true;
174
+ if (cursorToolChoiceAliases(tool).includes(choiceName)) return true;
175
+ return cursorToolWireName(tool) === choiceName
176
+ && !catalog.some(candidate => candidate.name === choiceName);
174
177
  }
175
178
 
176
179
  export function isBareCodexShellBridgeTool(tool: Pick<OcxTool, "namespace" | "name">): boolean {
@@ -320,10 +323,39 @@ export function cursorRequestAdvertisesStructuredEdits(
320
323
  return cursorStructuredEditTools(tools, toolChoice).length > 0;
321
324
  }
322
325
 
326
+ const CURSOR_CLIENT_TOOL_WIRE_PREFIX = "ocx_client_";
327
+ const CURSOR_PROXY_OWNED_BARE_TOOL_NAMES = new Set([
328
+ CODEX_UNIFIED_EXEC_TOOL,
329
+ CODEX_WAIT_TOOL,
330
+ CODEX_EXEC_COMMAND_TOOL,
331
+ CODEX_SHELL_COMMAND_TOOL,
332
+ CODEX_APPLY_PATCH_TOOL,
333
+ CURSOR_EDIT_FILE_TOOL,
334
+ CURSOR_MULTI_EDIT_TOOL,
335
+ CODEX_TOOL_SEARCH_TOOL,
336
+ ]);
337
+
338
+ /** Avoid collisions with Cursor's private bare-tool namespace. */
339
+ function isCursorBareClientToolWireAliased(
340
+ tool: Pick<OcxTool, "namespace" | "name">,
341
+ ): boolean {
342
+ return !tool.namespace
343
+ && !CURSOR_PROXY_OWNED_BARE_TOOL_NAMES.has(tool.name);
344
+ }
345
+
323
346
  export function cursorToolWireName(tool: Pick<OcxTool, "namespace" | "name">): string {
347
+ if (isCursorBareClientToolWireAliased(tool)) {
348
+ return `${CURSOR_CLIENT_TOOL_WIRE_PREFIX}${tool.name}`;
349
+ }
324
350
  return namespacedToolName(tool.namespace, tool.name);
325
351
  }
326
352
 
353
+ function clientSemanticToolNameFromCursorWire(name: string): string {
354
+ return name.startsWith(CURSOR_CLIENT_TOOL_WIRE_PREFIX)
355
+ ? name.slice(CURSOR_CLIENT_TOOL_WIRE_PREFIX.length)
356
+ : name;
357
+ }
358
+
327
359
  /**
328
360
  * Cursor's harness shows MCP tools to the model as `mcp_<providerIdentifier>_<toolName>`; models
329
361
  * sometimes call that display name verbatim instead of the advertised short name (live 20:41/21:00
@@ -591,7 +623,7 @@ function quotedNames(names: readonly string[]): string {
591
623
  }
592
624
 
593
625
  function advertisedCoversNeighbor(wireNames: readonly string[], neighbor: (typeof NEIGHBOR_AGENT_TOOL_NAMES)[number]): boolean {
594
- const advertised = new Set(wireNames.map(name => name.toLowerCase()));
626
+ const advertised = new Set(wireNames.map(name => clientSemanticToolNameFromCursorWire(name).toLowerCase()));
595
627
  if (advertised.has(neighbor.toLowerCase())) return true;
596
628
  return NEIGHBOR_AGENT_TOOL_ALIASES[neighbor].some(alias => advertised.has(alias.toLowerCase()));
597
629
  }
@@ -602,7 +634,7 @@ function unavailableNeighborAgentToolNames(wireNames: readonly string[]): string
602
634
 
603
635
  function discoveryToolLabel(wireNames: readonly string[]): string | undefined {
604
636
  const labels: string[] = [];
605
- if (wireNames.includes("tool_search")) labels.push("`tool_search`");
637
+ if (wireNames.includes(CODEX_TOOL_SEARCH_TOOL)) labels.push(`\`${CODEX_TOOL_SEARCH_TOOL}\``);
606
638
  if (wireNames.some(name => name.startsWith("mcp__"))) labels.push("MCP");
607
639
  if (wireNames.some(name => /resource/i.test(name))) labels.push("resource discovery");
608
640
  return labels.length > 0 ? labels.join(", ") : undefined;
@@ -236,6 +236,30 @@ function stripCanonicalOnlyToolFields(body: unknown, includeCapabilityGated: boo
236
236
  return input ? { ...rewrittenBody, input } : rewrittenBody;
237
237
  }
238
238
 
239
+ /**
240
+ * Codex keeps this ChatGPT-internal item metadata when its configured provider name is `openai`.
241
+ * Loopback OpenCodex injection intentionally retains that provider identity for history continuity,
242
+ * even when the proxy ultimately routes the request to a public Responses destination. Those
243
+ * destinations reject the private field as an unknown `input[*]` parameter, so remove it at the
244
+ * noncanonical boundary without mutating the caller-owned raw body.
245
+ */
246
+ function stripInternalChatMessageMetadataPassthrough(body: unknown): unknown {
247
+ if (!isPlainObject(body) || !Array.isArray(body.input)) return body;
248
+
249
+ let changed = false;
250
+ const input = body.input.map(item => {
251
+ if (!isPlainObject(item) || !Object.hasOwn(item, "internal_chat_message_metadata_passthrough")) {
252
+ return item;
253
+ }
254
+ changed = true;
255
+ const next = { ...item };
256
+ delete next.internal_chat_message_metadata_passthrough;
257
+ return next;
258
+ });
259
+
260
+ return changed ? { ...body, input } : body;
261
+ }
262
+
239
263
  /**
240
264
  * When `store` is false, the upstream API does not persist response items. Any item ID
241
265
  * forwarded in `input` is then interpreted as a reference to a stored item that does not
@@ -901,13 +925,23 @@ function annotateEmptyResponsesToolOutputs(body: unknown, enabled: boolean): unk
901
925
  * Runs on every forward request; with intact pairs it returns the original reference.
902
926
  */
903
927
  /**
904
- * Backfill `queries` on a replayed single-query `web_search_call`.
928
+ * Repair a replayed `web_search_call` action that is missing either key.
905
929
  *
906
930
  * `webSearchAction()` in the bridge now emits both keys, but that only helps items
907
931
  * created after the fix. A conversation that already recorded
908
- * `{type:"search", query:"..."}` replays that stored item on every subsequent turn, and
909
- * DeepSeek's native Responses parser requires `queries` — so upgrading alone leaves
910
- * those threads permanently 400ing with `missing field 'queries'` (#930).
932
+ * `{type:"search", query:"..."}` or `{type:"search", queries:[...]}` replays that stored
933
+ * item on every subsequent turn. DeepSeek's native Responses parser requires `queries`
934
+ * (#930) and Console Go's validator requires `query` (#3071), so upgrading alone leaves
935
+ * those threads permanently 400ing in one direction or the other. The repair runs both
936
+ * ways.
937
+ *
938
+ * Input items carry a loose schema, so a stored `queries` is not necessarily an array of
939
+ * strings. A partly- or wholly-malformed array is left alone rather than used as a source
940
+ * for the singular field: writing `query: 123` would satisfy the presence check and still
941
+ * fail the validator this repair exists to satisfy, and deriving `query` from
942
+ * `["a", 42]` would satisfy Console Go while leaving DeepSeek to reject the same replay.
943
+ * An empty `queries: []` canonicalizes to the shape the bridge emits for an empty search,
944
+ * keeping an existing `query` when the item has one.
911
945
  *
912
946
  * Runs on every Responses request, on both `input` items and the `action` nested inside
913
947
  * them. Returns the original reference when nothing needs repair, so the common path
@@ -920,9 +954,35 @@ function backfillWebSearchQueries(body: unknown): unknown {
920
954
  if (!isPlainObject(item) || item.type !== "web_search_call") return item;
921
955
  const action = item.action;
922
956
  if (!isPlainObject(action) || action.type !== "search") return item;
923
- if (typeof action.query !== "string" || Array.isArray(action.queries)) return item;
924
- changed = true;
925
- return { ...item, action: { ...action, queries: [action.query] } };
957
+ // Repair whichever side is missing so both strict parsers pass:
958
+ // DeepSeek native Responses requires `queries`; Console Go requires `query`.
959
+ const rep: Record<string, unknown> = { ...action };
960
+ let itemChanged = false;
961
+ const hasQuery = typeof action.query === "string";
962
+ const queries = Array.isArray(action.queries) ? action.queries : undefined;
963
+ if (queries !== undefined && queries.length === 0) {
964
+ // An empty array satisfies neither validator. Canonicalize to the empty-search
965
+ // shape the bridge emits, keeping an existing query rather than discarding it.
966
+ const query = hasQuery ? action.query as string : "";
967
+ rep.query = query;
968
+ rep.queries = [query];
969
+ itemChanged = true;
970
+ } else if (!hasQuery && queries !== undefined) {
971
+ // A plural array is only a usable source for the singular field when EVERY member
972
+ // is a string: deriving `query` from a partly-malformed array would satisfy Console
973
+ // Go while leaving DeepSeek to reject the same replay. Wholly malformed arrays are
974
+ // left untouched — coercing or dropping members would invent semantics the stored
975
+ // item never had.
976
+ if (queries.every(entry => typeof entry === "string")) {
977
+ rep.query = queries[0]; // multi-query item recorded before the fix
978
+ itemChanged = true;
979
+ }
980
+ } else if (hasQuery && queries === undefined) {
981
+ rep.queries = [action.query]; // single-query item recorded before the fix
982
+ itemChanged = true;
983
+ }
984
+ if (itemChanged) changed = true;
985
+ return itemChanged ? { ...item, action: rep } : item;
926
986
  });
927
987
  return changed ? { ...body, input } : body;
928
988
  }
@@ -2097,11 +2157,13 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
2097
2157
  outBody = repairOversizedReplayCallIds(outBody);
2098
2158
  }
2099
2159
  outBody = stripUnsupportedReasoningSummaryDelivery(outBody, parsed.modelId);
2100
- // Repair stored history from before the bridge emitted both keys: a conversation
2101
- // that already recorded a single-query web_search_call replays it every turn, and
2102
- // a strict parser rejects the whole request over it (#930).
2160
+ // Repair stored history from before the bridge emitted both keys, in either
2161
+ // direction: a conversation that already recorded a web_search_call replays it
2162
+ // every turn, and a strict parser rejects the whole request over the missing key —
2163
+ // `queries` for DeepSeek (#930), `query` for Console Go (#3071).
2103
2164
  outBody = backfillWebSearchQueries(outBody);
2104
2165
  if (!isCanonicalOpenAiForwardProvider(provider)) {
2166
+ outBody = stripInternalChatMessageMetadataPassthrough(outBody);
2105
2167
  outBody = promoteClientLoadedTools(outBody);
2106
2168
  }
2107
2169
  if (!isCanonicalOpenAiForwardProvider(provider)) {
package/src/bridge.ts CHANGED
@@ -146,27 +146,27 @@ export { adapterFailureFromMessage } from "./lib/errors";
146
146
  /**
147
147
  * Build the native `WebSearchAction::Search` payload from the queries that ran.
148
148
  *
149
- * Single query `{ query, queries: [query] }`. Batch `{ queries }` with NO singular
150
- * `query`. Empty → `{ query: "", queries: [""] }`.
149
+ * Every action carries BOTH keys: `{ query, queries }`, where `query` is the first
150
+ * member. Empty → `{ query: "", queries: [""] }`.
151
151
  *
152
- * The asymmetry is load-bearing in both directions. codex-rs prefers a non-empty `query`
153
- * for the cell label and renders "<first> ..." only when `query` is ABSENT and
154
- * `queries.len() > 1`, so adding `query` to a batch would collapse the plural ellipsis.
155
- * Meanwhile DeepSeek's native Responses parser makes `queries` a required field, so a
156
- * replayed one-term `web_search_call` carried in the history of every subsequent turn
157
- * fails deserialization with `missing field 'queries'` and 400s the rest of the
158
- * conversation (#930). Carrying both keys in the single case satisfies the strict parser
159
- * without changing what codex-rs displays.
152
+ * Carrying both is load-bearing in both directions. DeepSeek's native Responses parser
153
+ * makes `queries` a required field, and Console Go's upstream validator makes `query` a
154
+ * required field so a replayed `web_search_call` carried in the history of every
155
+ * subsequent turn fails deserialization with `missing field 'queries'` (#930) or 400s
156
+ * with `missing required field 'query'` unless both keys are present. Carrying both keys
157
+ * in every case satisfies both strict parsers; the trade-off is that a multi-query batch
158
+ * loses the "<first> ..." ellipsis in codex-rs and shows the first query as the label.
159
+ *
160
+ * That trade is deliberate: a cosmetic label against a conversation that 400s on every
161
+ * subsequent turn. Do not restore the old batch-omits-`query` shape to win the ellipsis
162
+ * back — it reopens #3071.
160
163
  *
161
164
  * This fixes items created from here on. History recorded before it is repaired at the
162
165
  * replay boundary by `backfillWebSearchQueries()` in the Responses adapter.
163
166
  */
164
167
  function webSearchAction(queries: string[]): Record<string, unknown> {
165
- if (queries.length <= 1) {
166
- const query = queries[0] ?? "";
167
- return { type: "search", query, queries: [query] };
168
- }
169
- return { type: "search", queries };
168
+ const first = queries[0] ?? "";
169
+ return { type: "search", query: first, queries: queries.length > 0 ? queries : [first] };
170
170
  }
171
171
 
172
172
  interface OutputItem {
@@ -418,6 +418,20 @@ export const CAPABILITIES: readonly Capability[] = [
418
418
  "The GUI reads this state directly; without a verb an agent could not tell whether the Codex app-server was reachable at all.",
419
419
  ],
420
420
  },
421
+ {
422
+ command: ["system", "codex-cli-update", "check"],
423
+ summary: "Inspect a configured Codex CLI candidate and its ownership provenance.",
424
+ routes: [],
425
+ flags: [{ name: "--json", value: "boolean", summary: "Emit the redacted provenance report as JSON." }],
426
+ mutates: false,
427
+ json: "envelope",
428
+ details: [
429
+ "Proof-bound published-launcher context authenticates the configured candidate snapshot, not successful Codex execution; this check does not attest or admit a selected runtime.",
430
+ "On Windows this first slice performs no candidate or configuration filesystem I/O: only a proof-captured absolute environment candidate can receive lexical app-bundle or version-manager labels; every other Windows candidate fails closed.",
431
+ "Makes no package-registry request.",
432
+ "Does not execute Codex or npm, install or repair software, control a process, or write configuration or cache state.",
433
+ ],
434
+ },
421
435
  {
422
436
  command: ["system", "codex-restart"],
423
437
  summary: "Restart the Codex app-server.",
@@ -0,0 +1,96 @@
1
+ import {
2
+ inspectCodexCliInstall,
3
+ type CodexCliInstallProvenanceDeps,
4
+ type CodexCliInstallReport,
5
+ } from "../codex/cli-install-provenance";
6
+ import { CliUsageError, isJsonOption, printData, runCliAction } from "./runtime-api";
7
+ import { trustedNodeLauncherContext } from "./launcher-context";
8
+
9
+ export const CODEX_CLI_UPDATE_USAGE = `Usage:
10
+ ocx system codex-cli-update check [--json]`;
11
+
12
+ export type ParsedCodexCliUpdateArgs = Readonly<{
13
+ json: boolean;
14
+ }>;
15
+
16
+ export interface CodexCliUpdateCommandDeps {
17
+ readonly inspectInstall?: (deps: CodexCliInstallProvenanceDeps) => Promise<CodexCliInstallReport>;
18
+ }
19
+
20
+ function installSummary(report: CodexCliInstallReport): string[] {
21
+ return [
22
+ `candidate: ${report.candidateAvailable ? "yes" : "no"}`,
23
+ `candidate-source: ${report.candidateSource ?? "unavailable"}`,
24
+ `selection-attested: ${report.selectionAttested ? "yes" : "no"}`,
25
+ `provenance: ${report.provenance}`,
26
+ `managed: ${report.managed ? "yes" : "no"}`,
27
+ `reason: ${report.reason}`,
28
+ `candidate-version: ${report.candidateVersion ?? "unavailable"}`,
29
+ `package-version: ${report.packageVersion ?? "unavailable"}`,
30
+ `version-evidence: ${report.versionEvidence.kind}`,
31
+ `location: ${report.location ?? "unavailable"}`,
32
+ `shim: ${report.shim.status}${report.shim.backingKind ? `/${report.shim.backingKind}` : ""}`,
33
+ ];
34
+ }
35
+
36
+ export function parseCodexCliUpdateArgs(argv: readonly string[]): ParsedCodexCliUpdateArgs {
37
+ // `--json` is accepted in any argv position CLI-wide, so remove it before positional
38
+ // validation. Requiring `check` at index 0 first would reject `--json check`, which
39
+ // automation that puts output flags ahead of the subcommand legitimately produces.
40
+ let json = false;
41
+ const positional: string[] = [];
42
+ for (const token of argv) {
43
+ if (isJsonOption(token)) {
44
+ if (json) throw new CliUsageError("--json may be specified only once", CODEX_CLI_UPDATE_USAGE);
45
+ json = true;
46
+ continue;
47
+ }
48
+ positional.push(token);
49
+ }
50
+ if (positional[0] !== "check") {
51
+ throw new CliUsageError("codex-cli-update action must be check", CODEX_CLI_UPDATE_USAGE);
52
+ }
53
+ if (positional.length > 1) {
54
+ throw new CliUsageError("unsupported codex-cli-update argument", CODEX_CLI_UPDATE_USAGE);
55
+ }
56
+ return Object.freeze({ json });
57
+ }
58
+
59
+ export async function handleCodexCliUpdateCommand(
60
+ argv: readonly string[],
61
+ deps: CodexCliUpdateCommandDeps = {},
62
+ ): Promise<number> {
63
+ let parsed: ParsedCodexCliUpdateArgs;
64
+ try {
65
+ parsed = parseCodexCliUpdateArgs(argv);
66
+ } catch (error) {
67
+ if (error instanceof CliUsageError) {
68
+ console.error(`Error: ${error.message}`);
69
+ console.error(error.usage ?? CODEX_CLI_UPDATE_USAGE);
70
+ return 2;
71
+ }
72
+ throw error;
73
+ }
74
+ return runCliAction(async () => {
75
+ const trustedInspectionEnv = trustedNodeLauncherContext()?.codexCliInspectionEnv;
76
+ const inspectionDeps: CodexCliInstallProvenanceDeps = trustedInspectionEnv
77
+ && trustedInspectionEnv.managerRoots !== null ? {
78
+ env: {
79
+ ...trustedInspectionEnv.managerRoots,
80
+ CODEX_CLI_PATH: trustedInspectionEnv.codexCliPath ?? undefined,
81
+ PATH: trustedInspectionEnv.path ?? undefined,
82
+ PATHEXT: trustedInspectionEnv.pathExt ?? undefined,
83
+ },
84
+ configDir: trustedInspectionEnv.configDir,
85
+ // This is a fresh one-shot CLI process. Its proof-bound launcher snapshot
86
+ // supplies configured candidate evidence, not selected-runtime admission.
87
+ } : {
88
+ // Direct Bun/source launches have no pre-dotenv proof. Do not inspect
89
+ // ambient or persisted candidate state at all.
90
+ env: { PATH: "" },
91
+ configDir: ".",
92
+ };
93
+ const report = await (deps.inspectInstall ?? inspectCodexCliInstall)(inspectionDeps);
94
+ printData(report, parsed.json, installSummary(report));
95
+ });
96
+ }
@@ -19,6 +19,9 @@ export function skipsCodexShimAutoRestore(command: string | undefined, args: str
19
19
  if (command === "uninstall" || command === "remove") return true;
20
20
  // `lab` is read-only inspection; it must not trigger shim side effects.
21
21
  if (command === "lab") return true;
22
+ // The entire updater-inspection namespace is zero-effect, including malformed
23
+ // or future actions. A later `apply` implementation must own its preflight.
24
+ if (command === "system" && args[1] === "codex-cli-update") return true;
22
25
  return command === "codex-shim" && ["install", "uninstall", "remove"].includes(args[1] ?? "");
23
26
  }
24
27