@bitkyc08/opencodex 2.37.0 → 2.38.0

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 (66) hide show
  1. package/bin/ocx.mjs +69 -10
  2. package/gui/dist/assets/{index-CowztZdo.js → index-C14iCj_Q.js} +13 -13
  3. package/gui/dist/assets/index-D7PIz7_g.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/deepseek-harness.svg +3 -0
  7. package/gui/dist/provider-icons/oh-my-pi.svg +11 -0
  8. package/gui/dist/provider-icons/openclaw.svg +54 -0
  9. package/gui/dist/provider-icons/prime-agent.svg +21 -0
  10. package/gui/dist/provider-icons/zcode.svg +219 -0
  11. package/package.json +1 -1
  12. package/src/adapters/cursor/protobuf-request.ts +4 -1
  13. package/src/adapters/cursor/tool-definitions.ts +36 -4
  14. package/src/cli/capabilities.ts +14 -0
  15. package/src/cli/codex-cli-update.ts +96 -0
  16. package/src/cli/codex-shim-autorestore.ts +3 -0
  17. package/src/cli/export-command.ts +18 -17
  18. package/src/cli/help.ts +2 -2
  19. package/src/cli/index.ts +3 -2
  20. package/src/cli/launcher-context.ts +53 -2
  21. package/src/cli/opencode.ts +126 -33
  22. package/src/cli/registry.ts +16 -10
  23. package/src/cli/system-command.ts +6 -1
  24. package/src/clients/config-export.ts +293 -28
  25. package/src/codex/account-store.ts +10 -4
  26. package/src/codex/autostart-health.ts +3 -3
  27. package/src/codex/catalog/provider-fetch.ts +20 -1
  28. package/src/codex/catalog/sync.ts +4 -3
  29. package/src/codex/cli-install-provenance.ts +795 -0
  30. package/src/codex/convergence.ts +4 -3
  31. package/src/codex/credential-mutation-epoch.ts +11 -0
  32. package/src/codex/main-account.ts +2 -0
  33. package/src/codex/model-entitlements.ts +430 -27
  34. package/src/codex/native-profile-manager.ts +4 -0
  35. package/src/codex/reset-credit-operation-ledger.ts +1411 -0
  36. package/src/codex/reset-credit-recovery.ts +20 -2
  37. package/src/codex/shim.ts +204 -18
  38. package/src/codex/user-identity.ts +2 -1
  39. package/src/config/paths.ts +18 -3
  40. package/src/config.ts +23 -0
  41. package/src/generated/compatibility-version.json +81 -45
  42. package/src/integrations/registry.ts +112 -0
  43. package/src/integrations/state.ts +67 -5
  44. package/src/integrations/writer.ts +25 -9
  45. package/src/lib/bounded-subprocess.ts +36 -0
  46. package/src/lib/strict-semver.ts +47 -0
  47. package/src/lib/windows-elevation.ts +32 -1
  48. package/src/lib/windows-secret-acl.ts +47 -25
  49. package/src/lib/windows-service-mutation-lock.ts +133 -0
  50. package/src/lib/windows-user-principal.ts +15 -17
  51. package/src/responses/spill-store.ts +334 -29
  52. package/src/responses/state.ts +488 -7
  53. package/src/server/index.ts +4 -3
  54. package/src/server/lifecycle.ts +5 -1
  55. package/src/server/management/model-rows.ts +11 -2
  56. package/src/server/management/provider-routes.ts +4 -0
  57. package/src/server/management/system-restart.ts +5 -5
  58. package/src/server/management-api.ts +7 -2
  59. package/src/server/startup-action-control.ts +3 -2
  60. package/src/service.ts +594 -33
  61. package/src/sidecar/candidates.ts +1 -1
  62. package/src/update/codex-cli-update-launch-policy.d.mts +18 -0
  63. package/src/update/codex-cli-update-launch-policy.mjs +30 -0
  64. package/src/update/index.ts +3 -2
  65. package/src/update/job.ts +10 -11
  66. 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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.37.0",
3
+ "version": "2.38.0",
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",
@@ -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;
@@ -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
 
@@ -80,28 +80,24 @@ function hasContextLimit(model: ExportModel): boolean {
80
80
  *
81
81
  * `opencodeCatalogFromProxyRows` owns the visibility rules (drop `disabled`, drop dupes,
82
82
  * drop native under Codex Direct) — the export core does none of that, so a row filtered
83
- * here is the only thing keeping a disabled model out of a client's picker. Modalities are
84
- * re-joined by `namespaced` because the launcher's catalog type does not carry them.
83
+ * here is the only thing keeping a disabled model out of a client's picker. It also carries
84
+ * the effort ladder, so the ladder a client receives comes from the same filtered, deduped
85
+ * row as the model itself: a second lookup over the raw rows would let a hidden or disabled
86
+ * duplicate donate its ladder to the visible entry.
87
+ *
88
+ * Only modalities are re-joined by `namespaced`, because the catalog type does not carry them.
85
89
  */
86
90
  export function exportModelsFromProxyRows(
87
91
  rows: readonly ExportProxyModelRow[],
88
92
  config: OcxConfig,
89
93
  ): ExportModel[] {
90
- const metadata = new Map<string, Pick<ExportModel, "inputModalities" | "reasoningEfforts" | "defaultReasoningEffort">>();
94
+ const modalities = new Map<string, string[]>();
91
95
  for (const row of rows) {
92
96
  const namespaced = row.namespaced?.trim();
93
- if (!namespaced || metadata.has(namespaced)) continue;
94
- metadata.set(namespaced, {
95
- ...(Array.isArray(row.inputModalities) && row.inputModalities.length > 0
96
- ? { inputModalities: [...row.inputModalities] }
97
- : {}),
98
- ...(Array.isArray(row.reasoningEfforts) && row.reasoningEfforts.length > 0
99
- ? { reasoningEfforts: [...row.reasoningEfforts] }
100
- : {}),
101
- ...(typeof row.defaultReasoningEffort === "string" && row.defaultReasoningEffort.length > 0
102
- ? { defaultReasoningEffort: row.defaultReasoningEffort }
103
- : {}),
104
- });
97
+ if (!namespaced || modalities.has(namespaced)) continue;
98
+ if (Array.isArray(row.inputModalities) && row.inputModalities.length > 0) {
99
+ modalities.set(namespaced, [...row.inputModalities]);
100
+ }
105
101
  }
106
102
  return opencodeCatalogFromProxyRows(rows, config).map(entry => {
107
103
  const model: ExportModel = {
@@ -112,7 +108,12 @@ export function exportModelsFromProxyRows(
112
108
  if (entry.native) model.native = true;
113
109
  if (entry.displayName) model.displayName = entry.displayName;
114
110
  if (entry.contextWindow !== undefined) model.contextWindow = entry.contextWindow;
115
- Object.assign(model, metadata.get(entry.namespaced));
111
+ if (entry.reasoningEfforts && entry.reasoningEfforts.length > 0) {
112
+ model.reasoningEfforts = [...entry.reasoningEfforts];
113
+ }
114
+ if (entry.defaultReasoningEffort) model.defaultReasoningEffort = entry.defaultReasoningEffort;
115
+ const input = modalities.get(entry.namespaced);
116
+ if (input) model.inputModalities = [...input];
116
117
  return model;
117
118
  });
118
119
  }
@@ -203,7 +204,7 @@ export async function handleExportCommand(argv: string[], deps: ExportCommandDep
203
204
  "",
204
205
  ...(out !== undefined ? [`Wrote ${out}`] : []),
205
206
  `Destination: ${spec.destination(process.env)}`,
206
- "Merge this provider block into that file; do not replace it.",
207
+ "Merge this generated configuration into that file; do not replace it.",
207
208
  `Before launching: ${spec.exportHint}`,
208
209
  `${models.length} model${models.length === 1 ? "" : "s"}; ${degraded} omit context limits (the client applies its own defaults).`,
209
210
  ]);
package/src/cli/help.ts CHANGED
@@ -73,10 +73,10 @@ Usage:
73
73
  ocx memory [--json] Alias of ocx observe memory
74
74
  ocx api-key <sub> Alias of ocx access key
75
75
  ocx access <sub> External API keys and endpoint information
76
- ocx export --client <id> Print a client config wired to the running proxy (11 clients)
76
+ ocx export --client <id> Print a client config wired to the running proxy (12 clients)
77
77
  ocx integration client <sub> Enable, disable, inspect or roll back a client integration
78
78
  ocx grok <sub> Grok Build model selection and apply
79
- ocx system <sub> Runtime settings, startup, sync, and updates
79
+ ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
80
80
  ocx config <sub> Validated configuration show/get/set/import/export
81
81
  ocx lab <sub> Read-only Compatibility Lab projection inspection
82
82
  ocx claude [args...] Launch Claude Code wired to the proxy (model discovery on)
package/src/cli/index.ts CHANGED
@@ -363,11 +363,12 @@ async function handleStart(options: { block?: boolean } = {}) {
363
363
  shutdownStartedAt = now;
364
364
  console.log("\nšŸ›‘ Shutting down opencodex proxy...");
365
365
  void (async () => {
366
+ let shutdownSucceeded = false;
366
367
  try {
367
- await drainAndShutdown(server, config.shutdownTimeoutMs ?? 5000);
368
+ shutdownSucceeded = await drainAndShutdown(server, config.shutdownTimeoutMs ?? 5000);
368
369
  } finally {
369
370
  const restored = syncCleanup(); // idempotent (cleaned-guard); also re-run by process.on("exit")
370
- process.exit(restored ? 0 : 1);
371
+ process.exit(restored && shutdownSucceeded ? 0 : 1);
371
372
  }
372
373
  })();
373
374
  };
@@ -14,9 +14,19 @@ export const ANTHROPIC_PARENT_ENV_SLOTS = [
14
14
  ] as const;
15
15
 
16
16
  export type AnthropicParentEnvSlot = typeof ANTHROPIC_PARENT_ENV_SLOTS[number];
17
+ export type CodexCliVersionManagerRootEnvSlot = typeof CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS[number];
18
+
19
+ type CodexCliVersionManagerRoots = Readonly<Partial<Record<CodexCliVersionManagerRootEnvSlot, string>>>;
17
20
 
18
21
  export type TrustedNodeLaunchContext = {
19
22
  anthropicEnvSlots: readonly AnthropicParentEnvSlot[];
23
+ codexCliInspectionEnv: Readonly<{
24
+ codexCliPath: string | null;
25
+ path: string | null;
26
+ pathExt: string | null;
27
+ managerRoots: CodexCliVersionManagerRoots | null;
28
+ configDir: string;
29
+ }> | null;
20
30
  };
21
31
 
22
32
  let trustedContext: TrustedNodeLaunchContext | null = null;
@@ -25,6 +35,21 @@ function isLaunchProof(value: string): boolean {
25
35
  return /^[A-Za-z0-9_-]{43}$/.test(value);
26
36
  }
27
37
 
38
+ function parseVersionManagerRoots(value: unknown): CodexCliVersionManagerRoots | null | undefined {
39
+ // Missing means an older launcher. Preserve compatibility for unrelated
40
+ // commands, but updater inspection treats the incomplete snapshot as
41
+ // untrusted and fails closed.
42
+ if (value === undefined) return null;
43
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
44
+ const allowed = new Set<string>(CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS);
45
+ const entries = Object.entries(value);
46
+ if (entries.length > allowed.size || entries.some(([name, root]) =>
47
+ !allowed.has(name) || typeof root !== "string" || root.length === 0 || root.length > 32 * 1024)) {
48
+ return undefined;
49
+ }
50
+ return Object.freeze(Object.fromEntries(entries)) as CodexCliVersionManagerRoots;
51
+ }
52
+
28
53
  /** Consume the internal proof before normal CLI argument parsing. */
29
54
  export function initializeNodeLauncherContext(
30
55
  argv: string[] = process.argv,
@@ -45,7 +70,7 @@ export function initializeNodeLauncherContext(
45
70
  delete env.OCX_PRE_BUN_ANTHROPIC_ENV;
46
71
  trustedContext = null;
47
72
 
48
- if (proofArgs.length !== 1 || !raw || raw.length > 2048) return null;
73
+ if (proofArgs.length !== 1 || !raw || raw.length > 64 * 1024) return null;
49
74
  const proof = proofArgs[0]!;
50
75
  if (!isLaunchProof(proof)) return null;
51
76
 
@@ -54,6 +79,7 @@ export function initializeNodeLauncherContext(
54
79
  version?: unknown;
55
80
  proof?: unknown;
56
81
  anthropicEnvSlots?: unknown;
82
+ codexCliInspectionEnv?: unknown;
57
83
  };
58
84
  if (parsed.version !== 1 || parsed.proof !== proof || !Array.isArray(parsed.anthropicEnvSlots)) {
59
85
  return null;
@@ -65,7 +91,31 @@ export function initializeNodeLauncherContext(
65
91
  if (slots.length !== parsed.anthropicEnvSlots.length || new Set(slots).size !== slots.length) {
66
92
  return null;
67
93
  }
68
- trustedContext = { anthropicEnvSlots: slots };
94
+ const inspection = parsed.codexCliInspectionEnv;
95
+ const managerRoots = inspection && typeof inspection === "object" && !Array.isArray(inspection)
96
+ ? parseVersionManagerRoots((inspection as Record<string, unknown>).managerRoots)
97
+ : null;
98
+ const codexCliInspectionEnv = inspection === null || inspection === undefined
99
+ ? null
100
+ : inspection && typeof inspection === "object" && !Array.isArray(inspection)
101
+ && ["codexCliPath", "path", "pathExt"].every(key => {
102
+ const value = (inspection as Record<string, unknown>)[key];
103
+ return value === null || typeof value === "string";
104
+ })
105
+ && typeof (inspection as Record<string, unknown>).configDir === "string"
106
+ && (inspection as Record<string, string>).configDir.length > 0
107
+ && (inspection as Record<string, string>).configDir.length <= 32 * 1024
108
+ && managerRoots !== undefined
109
+ ? Object.freeze({
110
+ codexCliPath: (inspection as Record<string, string | null>).codexCliPath ?? null,
111
+ path: (inspection as Record<string, string | null>).path ?? null,
112
+ pathExt: (inspection as Record<string, string | null>).pathExt ?? null,
113
+ managerRoots,
114
+ configDir: (inspection as Record<string, string>).configDir,
115
+ })
116
+ : undefined;
117
+ if (codexCliInspectionEnv === undefined) return null;
118
+ trustedContext = { anthropicEnvSlots: slots, codexCliInspectionEnv };
69
119
  return trustedContext;
70
120
  } catch {
71
121
  return null;
@@ -75,3 +125,4 @@ export function initializeNodeLauncherContext(
75
125
  export function trustedNodeLauncherContext(): TrustedNodeLaunchContext | null {
76
126
  return trustedContext;
77
127
  }
128
+ import { CODEX_CLI_VERSION_MANAGER_ROOT_ENV_SLOTS } from "../update/codex-cli-update-launch-policy.mjs";