@bitkyc08/opencodex 2.34.0 → 2.35.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 (71) hide show
  1. package/gui/dist/assets/{index-C4TMRloX.js → index-DNdRKXK9.js} +11 -11
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +3 -1
  4. package/src/adapters/base.ts +26 -0
  5. package/src/adapters/cursor/catalog.ts +541 -0
  6. package/src/adapters/cursor/cursor-errors.ts +15 -0
  7. package/src/adapters/cursor/discovery.ts +34 -41
  8. package/src/adapters/cursor/envelope-echo.ts +128 -0
  9. package/src/adapters/cursor/request-builder.ts +19 -12
  10. package/src/adapters/cursor/tool-definitions.ts +2 -1
  11. package/src/adapters/cursor/tool-result-normalize.ts +23 -31
  12. package/src/adapters/cursor.ts +21 -2
  13. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  14. package/src/adapters/google-antigravity-replay.ts +71 -2
  15. package/src/adapters/google-antigravity-wire.ts +5 -0
  16. package/src/adapters/google.ts +15 -1
  17. package/src/adapters/kiro-constants.ts +12 -0
  18. package/src/adapters/kiro.ts +128 -11
  19. package/src/adapters/openai-chat.ts +16 -2
  20. package/src/adapters/openai-responses.ts +15 -2
  21. package/src/adapters/run-turn-queue.ts +36 -1
  22. package/src/adapters/tool-catalog-nudge.ts +2 -1
  23. package/src/adapters/xai-web-search.ts +10 -14
  24. package/src/claude/outbound.ts +14 -3
  25. package/src/cli/access.ts +46 -3
  26. package/src/cli/account-api.ts +84 -15
  27. package/src/cli/account-extended.ts +261 -28
  28. package/src/cli/account-main.ts +12 -12
  29. package/src/cli/account.ts +40 -10
  30. package/src/cli/agent.ts +8 -1
  31. package/src/cli/capabilities-command.ts +94 -0
  32. package/src/cli/capabilities.ts +496 -0
  33. package/src/cli/claude-desktop.ts +31 -11
  34. package/src/cli/dispatch.ts +195 -27
  35. package/src/cli/doctor.ts +100 -1
  36. package/src/cli/help.ts +11 -2
  37. package/src/cli/index.ts +19 -3
  38. package/src/cli/inspect.ts +230 -0
  39. package/src/cli/observe.ts +11 -3
  40. package/src/cli/registry.ts +34 -2
  41. package/src/cli/runtime-api.ts +51 -7
  42. package/src/cli/status.ts +16 -0
  43. package/src/cli/storage.ts +234 -0
  44. package/src/cli/system-command.ts +16 -0
  45. package/src/cli/usage-report.ts +52 -2
  46. package/src/cli/version-skew.ts +46 -0
  47. package/src/codex/account-label.ts +21 -0
  48. package/src/codex/catalog/provider-fetch.ts +4 -0
  49. package/src/codex/transition-state.ts +12 -3
  50. package/src/compatibility/openai-responses.ts +9 -1
  51. package/src/generated/compatibility-version.json +95 -59
  52. package/src/integrations/ownership-policy.ts +24 -5
  53. package/src/integrations/ownership.ts +36 -2
  54. package/src/integrations/state.ts +40 -7
  55. package/src/integrations/writer.ts +21 -3
  56. package/src/lib/admin-secrets.ts +24 -0
  57. package/src/lib/errors.ts +25 -1
  58. package/src/lib/service-secrets.ts +15 -0
  59. package/src/oauth/store.ts +14 -5
  60. package/src/providers/label.ts +34 -1
  61. package/src/responses/turn-termination.ts +107 -0
  62. package/src/server/management/logs-usage-routes.ts +0 -16
  63. package/src/server/management/route-registry.ts +311 -0
  64. package/src/server/proxy-liveness.ts +27 -4
  65. package/src/server/request-log.ts +29 -1
  66. package/src/server/responses/core.ts +80 -0
  67. package/src/service.ts +34 -0
  68. package/src/storage/policy-job.ts +14 -4
  69. package/src/storage/policy.ts +88 -23
  70. package/src/usage/log.ts +44 -4
  71. package/src/usage/summary.ts +10 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Declared inventory of every reachable management route.
3
+ *
4
+ * DECLARED, not harvested. A grep cannot see this surface: 18 routes are registered
5
+ * through a regex, an `endsWith`, a `pathname.slice`, a prefix decode, a path constant, or a
6
+ * negated `pathname !== "…"` guard, and two of those are live routes whose only textual
7
+ * trace is the negated form. For `GET /api/storage` an equality scan finds solely the dead
8
+ * shadowed copy in `logs-usage-routes.ts` and never the live one.
9
+ *
10
+ * This module is pure DATA and must stay that way. It is imported by
11
+ * `src/server/management-api.ts`, which `tests/core-lab-boundary.test.ts` protects: a user
12
+ * with one provider and no Lab must execute no Lab code. Route paths are strings, so
13
+ * declaring `/api/lab/status` here creates no module edge. Never import a handler, and
14
+ * never import anything from `src/lab/`. The `module` field names the owning file as text
15
+ * for exactly this reason.
16
+ *
17
+ * Reconciliation lives in `tests/management-route-registry.test.ts`, which resolves
18
+ * `(method, path)` pairs from source and fails loudly on a route whose method it cannot
19
+ * determine. Adding a route without declaring it here fails that test.
20
+ */
21
+
22
+ export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD";
23
+
24
+ /**
25
+ * Why a route has no CLI verb. Every value is a claim about the route that a reviewer
26
+ * can check, not a way to quiet the parity test.
27
+ */
28
+ export type ExemptionReason =
29
+ /** Requires a dashboard browser session. Includes the user-consent star boundary. */
30
+ | "session-only"
31
+ /** Deliberately returns 405; there is nothing to drive. */
32
+ | "disabled"
33
+ /** Gated on a process-scoped capability principal, not an operator action. */
34
+ | "capability-principal"
35
+ /** A test seam, not an operator capability. */
36
+ | "test-seam"
37
+ /** The CLI reaches the same data through a local transport instead of HTTP. */
38
+ | "local-transport"
39
+ /** Unreachable in the live dispatch order; delete rather than expose. */
40
+ | "dead"
41
+ /**
42
+ * A verb is owed but belongs to a later work-phase. BOUNDED: requires `owner` and
43
+ * `ownerDoc`, and the parity test asserts that tracked doc exists and names the route.
44
+ * The doc is deliberately a repository file rather than the goalplan, which is
45
+ * gitignored -- a test reading machine-local state passes here and finds nothing in CI.
46
+ */
47
+ | "deferred-verb";
48
+
49
+ export interface RouteExemption {
50
+ readonly reason: ExemptionReason;
51
+ /** Free text; required, because an exemption nobody justified is how a gate erodes. */
52
+ readonly why: string;
53
+ /** Work-phase that owes the verb. Required for `deferred-verb`. */
54
+ readonly owner?: string;
55
+ /** Tracked doc naming the route. Required for `deferred-verb`. */
56
+ readonly ownerDoc?: string;
57
+ }
58
+
59
+ /** How a route is registered, for routes an equality scan cannot see. */
60
+ export type NonLiteralMechanism =
61
+ | "negated-guard"
62
+ | "path-constant"
63
+ | "prefix-decode"
64
+ | "slice"
65
+ | "ends-with"
66
+ | "regex";
67
+
68
+ export interface ManagementRoute {
69
+ readonly method: HttpMethod;
70
+ readonly path: string;
71
+ /** Owning source file, repo-relative without the `src/` prefix or `.ts` suffix. */
72
+ readonly module: string;
73
+ readonly mutates: boolean;
74
+ /** Set when the route is not recoverable from an equality scan of its own file. */
75
+ readonly mechanism?: NonLiteralMechanism;
76
+ readonly exempt?: RouteExemption;
77
+ }
78
+
79
+
80
+ /** Every reachable management route. */
81
+ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
82
+ // server/management-api
83
+ { method: "POST", path: "/api/stop", module: "server/management-api", mutates: true },
84
+ // codex/auth-api
85
+ { method: "DELETE", path: "/api/codex-auth/accounts", module: "codex/auth-api", mutates: true },
86
+ { method: "GET", path: "/api/codex-auth/accounts", module: "codex/auth-api", mutates: false },
87
+ { method: "GET", path: "/api/codex-auth/active", module: "codex/auth-api", mutates: false },
88
+ { method: "GET", path: "/api/codex-auth/login-status", module: "codex/auth-api", mutates: false },
89
+ { method: "GET", path: "/api/codex-auth/quota", module: "codex/auth-api", mutates: false },
90
+ { method: "GET", path: "/api/codex-auth/reset-credits", module: "codex/auth-api", mutates: false },
91
+ { method: "PATCH", path: "/api/codex-auth/pool-strategy", module: "codex/auth-api", mutates: true },
92
+ { method: "POST", path: "/api/codex-auth/accounts", module: "codex/auth-api", mutates: true },
93
+ { method: "POST", path: "/api/codex-auth/accounts/clear-cooldown", module: "codex/auth-api", mutates: true },
94
+ { method: "POST", path: "/api/codex-auth/login", module: "codex/auth-api", mutates: true },
95
+ { method: "POST", path: "/api/codex-auth/login/cancel", module: "codex/auth-api", mutates: true },
96
+ { method: "POST", path: "/api/codex-auth/login/code", module: "codex/auth-api", mutates: true },
97
+ { method: "POST", path: "/api/codex-auth/reset-credits/consume", module: "codex/auth-api", mutates: true },
98
+ { method: "PUT", path: "/api/codex-auth/accounts/alias", module: "codex/auth-api", mutates: true },
99
+ { method: "PUT", path: "/api/codex-auth/accounts/pause", module: "codex/auth-api", mutates: true },
100
+ { method: "PUT", path: "/api/codex-auth/accounts/pause-exhausted", module: "codex/auth-api", mutates: true },
101
+ { method: "PUT", path: "/api/codex-auth/accounts/priority", module: "codex/auth-api", mutates: true },
102
+ { method: "PUT", path: "/api/codex-auth/active", module: "codex/auth-api", mutates: true },
103
+ { method: "PUT", path: "/api/codex-auth/auto-switch", module: "codex/auth-api", mutates: true },
104
+ { method: "PUT", path: "/api/codex-auth/failover", module: "codex/auth-api", mutates: true },
105
+ { method: "PUT", path: "/api/codex-auth/pool-strategy", module: "codex/auth-api", mutates: true },
106
+ // codex/native-profile-api
107
+ { method: "GET", path: "/api/native-main-profiles", module: "codex/native-profile-api", mutates: false },
108
+ { method: "GET", path: "/api/native-main-profiles/doctor", module: "codex/native-profile-api", mutates: false },
109
+ { method: "POST", path: "/api/native-main-profiles/recover", module: "codex/native-profile-api", mutates: true },
110
+ { method: "POST", path: "/api/native-main-profiles/register", module: "codex/native-profile-api", mutates: true },
111
+ { method: "POST", path: "/api/native-main-profiles/stage", module: "codex/native-profile-api", mutates: true },
112
+ { method: "POST", path: "/api/native-main-profiles/stage/cancel", module: "codex/native-profile-api", mutates: true },
113
+ { method: "POST", path: "/api/native-main-profiles/stage/finish", module: "codex/native-profile-api", mutates: true },
114
+ { method: "POST", path: "/api/native-main-profiles/stage/heartbeat", module: "codex/native-profile-api", mutates: true },
115
+ { method: "POST", path: "/api/native-main-profiles/switch", module: "codex/native-profile-api", mutates: true },
116
+ // server/management/agent-settings-routes
117
+ { method: "GET", path: "/api/claude-code", module: "server/management/agent-settings-routes", mutates: false },
118
+ { method: "GET", path: "/api/claude-desktop", module: "server/management/agent-settings-routes", mutates: false },
119
+ { method: "GET", path: "/api/claude-desktop/status", module: "server/management/agent-settings-routes", mutates: false },
120
+ { method: "GET", path: "/api/codex-auth/features/default-mode-request-user-input", module: "server/management/agent-settings-routes", mutates: false },
121
+ { method: "GET", path: "/api/effort-caps", module: "server/management/agent-settings-routes", mutates: false },
122
+ { method: "GET", path: "/api/grok", module: "server/management/agent-settings-routes", mutates: false },
123
+ { method: "GET", path: "/api/injection-model", module: "server/management/agent-settings-routes", mutates: false },
124
+ { method: "GET", path: "/api/subagent-model-fallback", module: "server/management/agent-settings-routes", mutates: false },
125
+ { method: "GET", path: "/api/subagent-models", module: "server/management/agent-settings-routes", mutates: false },
126
+ { method: "GET", path: "/api/v2", module: "server/management/agent-settings-routes", mutates: false },
127
+ { method: "POST", path: "/api/claude-desktop/apply", module: "server/management/agent-settings-routes", mutates: true },
128
+ { method: "POST", path: "/api/grok/apply", module: "server/management/agent-settings-routes", mutates: true },
129
+ { method: "PUT", path: "/api/claude-code", module: "server/management/agent-settings-routes", mutates: true },
130
+ { method: "PUT", path: "/api/claude-desktop", module: "server/management/agent-settings-routes", mutates: true },
131
+ { method: "PUT", path: "/api/codex-auth/features/default-mode-request-user-input", module: "server/management/agent-settings-routes", mutates: true },
132
+ { method: "PUT", path: "/api/effort-caps", module: "server/management/agent-settings-routes", mutates: true },
133
+ { method: "PUT", path: "/api/grok/selection", module: "server/management/agent-settings-routes", mutates: true },
134
+ { method: "PUT", path: "/api/injection-model", module: "server/management/agent-settings-routes", mutates: true },
135
+ { method: "PUT", path: "/api/subagent-model-fallback", module: "server/management/agent-settings-routes", mutates: true },
136
+ { method: "PUT", path: "/api/subagent-models", module: "server/management/agent-settings-routes", mutates: true },
137
+ { method: "PUT", path: "/api/v2", module: "server/management/agent-settings-routes", mutates: true },
138
+ // server/management/codex-prompt-routes
139
+ { method: "GET", path: "/api/codex-prompt", module: "server/management/codex-prompt-routes", mutates: false },
140
+ { method: "GET", path: "/api/codex-prompt/text", module: "server/management/codex-prompt-routes", mutates: false },
141
+ { method: "POST", path: "/api/codex-prompt/adopt", module: "server/management/codex-prompt-routes", mutates: true, exempt: { reason: "session-only", why: "Prompt adoption requires the gui-session principal (codex-prompt-routes.ts:298)." } },
142
+ { method: "POST", path: "/api/codex-prompt/repair", module: "server/management/codex-prompt-routes", mutates: true, exempt: { reason: "session-only", why: "Prompt repair requires the gui-session principal (codex-prompt-routes.ts:298)." } },
143
+ { method: "PUT", path: "/api/codex-prompt/base", module: "server/management/codex-prompt-routes", mutates: true, exempt: { reason: "session-only", why: "Base prompt write requires the gui-session principal (codex-prompt-routes.ts:298)." } },
144
+ { method: "PUT", path: "/api/codex-prompt/base/select", module: "server/management/codex-prompt-routes", mutates: true, exempt: { reason: "session-only", why: "Base prompt selection requires the gui-session principal (codex-prompt-routes.ts:298)." } },
145
+ { method: "PUT", path: "/api/codex-prompt/custom", module: "server/management/codex-prompt-routes", mutates: true, exempt: { reason: "session-only", why: "Custom prompt write requires the gui-session principal (codex-prompt-routes.ts:298)." } },
146
+ { method: "PUT", path: "/api/codex-prompt/toggle", module: "server/management/codex-prompt-routes", mutates: true, exempt: { reason: "session-only", why: "Prompt toggle requires the gui-session principal (codex-prompt-routes.ts:298)." } },
147
+ // server/management/combo-routes
148
+ { method: "DELETE", path: "/api/combos", module: "server/management/combo-routes", mutates: true },
149
+ { method: "GET", path: "/api/combos", module: "server/management/combo-routes", mutates: false },
150
+ { method: "PUT", path: "/api/combos", module: "server/management/combo-routes", mutates: true },
151
+ // server/management/config-routes
152
+ { method: "GET", path: "/api/config", module: "server/management/config-routes", mutates: false },
153
+ { method: "GET", path: "/api/diagnostics/project-config", module: "server/management/config-routes", mutates: false },
154
+ { method: "GET", path: "/api/settings", module: "server/management/config-routes", mutates: false },
155
+ { method: "GET", path: "/api/shadow-call-settings", module: "server/management/config-routes", mutates: false },
156
+ { method: "GET", path: "/api/sidecar-settings", module: "server/management/config-routes", mutates: false },
157
+ { method: "GET", path: "/api/startup-health", module: "server/management/config-routes", mutates: false },
158
+ { method: "GET", path: "/api/update/check", module: "server/management/config-routes", mutates: false },
159
+ { method: "GET", path: "/api/update/status", module: "server/management/config-routes", mutates: false },
160
+ { method: "GET", path: "/api/windows-tray", module: "server/management/config-routes", mutates: false },
161
+ { method: "POST", path: "/api/startup-action", module: "server/management/config-routes", mutates: true },
162
+ { method: "POST", path: "/api/sync", module: "server/management/config-routes", mutates: true },
163
+ { method: "POST", path: "/api/update/run", module: "server/management/config-routes", mutates: true },
164
+ { method: "POST", path: "/api/windows-tray", module: "server/management/config-routes", mutates: true },
165
+ { method: "PUT", path: "/api/config", module: "server/management/config-routes", mutates: true, exempt: { reason: "disabled", why: "Returns 405 by design; provider changes go through POST /api/providers." } },
166
+ { method: "PUT", path: "/api/settings", module: "server/management/config-routes", mutates: true },
167
+ { method: "PUT", path: "/api/shadow-call-settings", module: "server/management/config-routes", mutates: true },
168
+ { method: "PUT", path: "/api/sidecar-settings", module: "server/management/config-routes", mutates: true },
169
+ // server/management/integration-routes
170
+ { method: "GET", path: "/api/client-integrations", module: "server/management/integration-routes", mutates: false },
171
+ { method: "GET", path: "/api/client-integrations/journal", module: "server/management/integration-routes", mutates: false },
172
+ { method: "POST", path: "/api/client-integrations/restore", module: "server/management/integration-routes", mutates: true },
173
+ // server/management/lab-automation-routes
174
+ { method: "GET", path: "/api/lab/automation", module: "server/management/lab-automation-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
175
+ { method: "GET", path: "/api/lab/automation/runs", module: "server/management/lab-automation-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
176
+ { method: "POST", path: "/api/lab/automation/run", module: "server/management/lab-automation-routes", mutates: true, exempt: { reason: "deferred-verb", why: "Lab automation run has no CLI verb yet. A local SQLite read cannot drive it, so local-transport does not apply.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
177
+ { method: "PUT", path: "/api/lab/automation", module: "server/management/lab-automation-routes", mutates: true, exempt: { reason: "deferred-verb", why: "Lab automation config update has no CLI verb yet. A local SQLite read cannot drive it, so local-transport does not apply.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
178
+ // server/management/lab-routes
179
+ { method: "GET", path: "/api/lab/artifacts", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
180
+ { method: "GET", path: "/api/lab/catalog", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
181
+ { method: "GET", path: "/api/lab/events", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
182
+ { method: "GET", path: "/api/lab/observations", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
183
+ { method: "GET", path: "/api/lab/production-signals", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
184
+ { method: "GET", path: "/api/lab/public/community", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
185
+ { method: "GET", path: "/api/lab/status", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
186
+ { method: "GET", path: "/api/lab/subjects", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
187
+ { method: "GET", path: "/api/lab/verdicts", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
188
+ { method: "POST", path: "/api/lab/public/community/import", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "Community evidence import has no CLI verb yet. A local SQLite read cannot drive it, so local-transport does not apply.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
189
+ { method: "POST", path: "/api/lab/public/export", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "Public evidence export has no CLI verb yet. A local SQLite read cannot drive it, so local-transport does not apply.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
190
+ { method: "POST", path: "/api/lab/public/preview", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "Public evidence preview has no CLI verb yet. A local SQLite read cannot drive it, so local-transport does not apply.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
191
+ { method: "POST", path: "/api/lab/public/verify", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "Public evidence verification has no CLI verb yet. A local SQLite read cannot drive it, so local-transport does not apply.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
192
+ // server/management/logs-usage-routes
193
+ { method: "GET", path: "/api/claude/inbound-debug", module: "server/management/logs-usage-routes", mutates: false },
194
+ { method: "GET", path: "/api/debug", module: "server/management/logs-usage-routes", mutates: false },
195
+ { method: "GET", path: "/api/debug/injection-logs", module: "server/management/logs-usage-routes", mutates: false },
196
+ { method: "GET", path: "/api/debug/logs", module: "server/management/logs-usage-routes", mutates: false },
197
+ { method: "GET", path: "/api/debug/usage-logs", module: "server/management/logs-usage-routes", mutates: false },
198
+ { method: "GET", path: "/api/logs", module: "server/management/logs-usage-routes", mutates: false },
199
+ { method: "GET", path: "/api/storage/cleanup-policy", module: "server/management/logs-usage-routes", mutates: false },
200
+ { method: "GET", path: "/api/storage/cleanup-policy/test-stream", module: "server/management/logs-usage-routes", mutates: false, exempt: { reason: "test-seam", why: "Opt-in streaming seam declared at src/storage/policy-job.ts:71." } },
201
+ { method: "GET", path: "/api/storage/trash", module: "server/management/logs-usage-routes", mutates: false },
202
+ { method: "GET", path: "/api/storage/trash/restore/test-stream", module: "server/management/logs-usage-routes", mutates: false, exempt: { reason: "test-seam", why: "Opt-in streaming seam declared at src/storage/restore-job.ts:34." } },
203
+ { method: "GET", path: "/api/usage", module: "server/management/logs-usage-routes", mutates: false },
204
+ { method: "POST", path: "/api/storage/cleanup", module: "server/management/logs-usage-routes", mutates: true },
205
+ { method: "POST", path: "/api/storage/cleanup-policy/run", module: "server/management/logs-usage-routes", mutates: true },
206
+ { method: "POST", path: "/api/storage/cleanup/preview", module: "server/management/logs-usage-routes", mutates: true },
207
+ { method: "POST", path: "/api/storage/trash/restore", module: "server/management/logs-usage-routes", mutates: true },
208
+ { method: "PUT", path: "/api/debug", module: "server/management/logs-usage-routes", mutates: true },
209
+ { method: "PUT", path: "/api/storage/cleanup-policy", module: "server/management/logs-usage-routes", mutates: true },
210
+ // server/management/model-routes
211
+ { method: "GET", path: "/api/aliases", module: "server/management/model-routes", mutates: false },
212
+ { method: "GET", path: "/api/catalog", module: "server/management/model-routes", mutates: false },
213
+ { method: "GET", path: "/api/client-config", module: "server/management/model-routes", mutates: false },
214
+ { method: "GET", path: "/api/custom-models", module: "server/management/model-routes", mutates: false },
215
+ { method: "GET", path: "/api/model-discovery", module: "server/management/model-routes", mutates: false },
216
+ { method: "GET", path: "/api/model-presets", module: "server/management/model-routes", mutates: false },
217
+ { method: "GET", path: "/api/models", module: "server/management/model-routes", mutates: false },
218
+ { method: "GET", path: "/api/selected-models", module: "server/management/model-routes", mutates: false },
219
+ { method: "POST", path: "/api/custom-models", module: "server/management/model-routes", mutates: true },
220
+ { method: "POST", path: "/api/model-discovery/acknowledge", module: "server/management/model-routes", mutates: true },
221
+ { method: "PUT", path: "/api/default-aliases", module: "server/management/model-routes", mutates: true },
222
+ { method: "PUT", path: "/api/disabled-models", module: "server/management/model-routes", mutates: true },
223
+ { method: "PUT", path: "/api/model-discovery", module: "server/management/model-routes", mutates: true },
224
+ { method: "PUT", path: "/api/model-presets", module: "server/management/model-routes", mutates: true },
225
+ { method: "PUT", path: "/api/model-visibility", module: "server/management/model-routes", mutates: true },
226
+ { method: "PUT", path: "/api/selected-models", module: "server/management/model-routes", mutates: true },
227
+ // server/management/native-integration-routes
228
+ { method: "GET", path: "/api/native-integrations", module: "server/management/native-integration-routes", mutates: false },
229
+ { method: "PUT", path: "/api/native-integrations/claude", module: "server/management/native-integration-routes", mutates: true },
230
+ { method: "PUT", path: "/api/native-integrations/claude-desktop", module: "server/management/native-integration-routes", mutates: true },
231
+ { method: "PUT", path: "/api/native-integrations/codex", module: "server/management/native-integration-routes", mutates: true },
232
+ { method: "PUT", path: "/api/native-integrations/grok", module: "server/management/native-integration-routes", mutates: true },
233
+ // server/management/oauth-account-routes
234
+ { method: "DELETE", path: "/api/keys", module: "server/management/oauth-account-routes", mutates: true },
235
+ { method: "DELETE", path: "/api/oauth/accounts", module: "server/management/oauth-account-routes", mutates: true },
236
+ { method: "DELETE", path: "/api/providers/keys", module: "server/management/oauth-account-routes", mutates: true },
237
+ { method: "GET", path: "/api/key-providers", module: "server/management/oauth-account-routes", mutates: false },
238
+ { method: "GET", path: "/api/keys", module: "server/management/oauth-account-routes", mutates: false },
239
+ { method: "GET", path: "/api/oauth/accounts", module: "server/management/oauth-account-routes", mutates: false },
240
+ { method: "GET", path: "/api/oauth/accounts/pool", module: "server/management/oauth-account-routes", mutates: false },
241
+ { method: "GET", path: "/api/oauth/providers", module: "server/management/oauth-account-routes", mutates: false },
242
+ { method: "GET", path: "/api/oauth/status", module: "server/management/oauth-account-routes", mutates: false },
243
+ { method: "GET", path: "/api/providers/keys", module: "server/management/oauth-account-routes", mutates: false },
244
+ { method: "PATCH", path: "/api/keys", module: "server/management/oauth-account-routes", mutates: true },
245
+ { method: "PATCH", path: "/api/oauth/accounts/pool", module: "server/management/oauth-account-routes", mutates: true },
246
+ { method: "POST", path: "/api/keys", module: "server/management/oauth-account-routes", mutates: true },
247
+ { method: "POST", path: "/api/oauth/accounts/clear-cooldown", module: "server/management/oauth-account-routes", mutates: true },
248
+ { method: "POST", path: "/api/oauth/accounts/import", module: "server/management/oauth-account-routes", mutates: true },
249
+ { method: "POST", path: "/api/oauth/login", module: "server/management/oauth-account-routes", mutates: true },
250
+ { method: "POST", path: "/api/oauth/login/cancel", module: "server/management/oauth-account-routes", mutates: true },
251
+ { method: "POST", path: "/api/oauth/login/code", module: "server/management/oauth-account-routes", mutates: true },
252
+ { method: "POST", path: "/api/oauth/logout", module: "server/management/oauth-account-routes", mutates: true },
253
+ { method: "POST", path: "/api/providers/keys", module: "server/management/oauth-account-routes", mutates: true },
254
+ { method: "PUT", path: "/api/oauth/accounts/active", module: "server/management/oauth-account-routes", mutates: true },
255
+ { method: "PUT", path: "/api/oauth/accounts/alias", module: "server/management/oauth-account-routes", mutates: true },
256
+ { method: "PUT", path: "/api/oauth/accounts/pool", module: "server/management/oauth-account-routes", mutates: true },
257
+ { method: "PUT", path: "/api/providers/keys/active", module: "server/management/oauth-account-routes", mutates: true },
258
+ { method: "PUT", path: "/api/providers/keys/alias", module: "server/management/oauth-account-routes", mutates: true },
259
+ // server/management/provider-routes
260
+ { method: "DELETE", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
261
+ { method: "GET", path: "/api/provider-context-caps", module: "server/management/provider-routes", mutates: false },
262
+ { method: "GET", path: "/api/provider-presets", module: "server/management/provider-routes", mutates: false },
263
+ { method: "GET", path: "/api/provider-quotas", module: "server/management/provider-routes", mutates: false },
264
+ { method: "GET", path: "/api/provider-request-pacing", module: "server/management/provider-routes", mutates: false },
265
+ { method: "GET", path: "/api/providers", module: "server/management/provider-routes", mutates: false },
266
+ { method: "PATCH", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
267
+ { method: "POST", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
268
+ { method: "POST", path: "/api/providers/test", module: "server/management/provider-routes", mutates: true },
269
+ { method: "PUT", path: "/api/provider-context-caps", module: "server/management/provider-routes", mutates: true },
270
+ // server/management/request-history-routes
271
+ { method: "GET", path: "/api/request-history", module: "server/management/request-history-routes", mutates: false },
272
+ // server/management/routing-analytics-routes
273
+ // server/management/routing-profile-routes
274
+ { method: "DELETE", path: "/api/routing-profiles", module: "server/management/routing-profile-routes", mutates: true },
275
+ { method: "GET", path: "/api/routing-profiles", module: "server/management/routing-profile-routes", mutates: false },
276
+ { method: "POST", path: "/api/routing-profiles/dry-run", module: "server/management/routing-profile-routes", mutates: true },
277
+ { method: "PUT", path: "/api/routing-profiles", module: "server/management/routing-profile-routes", mutates: true },
278
+ // server/management/sidebar-routes
279
+ { method: "GET", path: "/api/github/star", module: "server/management/sidebar-routes", mutates: false },
280
+ { method: "GET", path: "/api/update/badge", module: "server/management/sidebar-routes", mutates: false },
281
+ { method: "POST", path: "/api/github/star", module: "server/management/sidebar-routes", mutates: true, exempt: { reason: "session-only", why: "User-consent boundary in AGENTS_INSTALL.md: starring spends the user's identity. Must never gain a CLI verb." } },
282
+ // server/management/storage-log-guard-routes
283
+ { method: "GET", path: "/api/storage/codex-logs", module: "server/management/storage-log-guard-routes", mutates: false },
284
+ { method: "POST", path: "/api/storage/codex-logs/compact", module: "server/management/storage-log-guard-routes", mutates: true },
285
+ { method: "POST", path: "/api/storage/codex-logs/protect", module: "server/management/storage-log-guard-routes", mutates: true },
286
+ { method: "POST", path: "/api/storage/codex-logs/repair", module: "server/management/storage-log-guard-routes", mutates: true },
287
+ { method: "POST", path: "/api/storage/codex-logs/unprotect", module: "server/management/storage-log-guard-routes", mutates: true },
288
+ // server/management/system-routes
289
+ { method: "GET", path: "/api/system/memory", module: "server/management/system-routes", mutates: false },
290
+ { method: "GET", path: "/api/system/windows-replace-retries", module: "server/management/system-routes", mutates: false },
291
+ { method: "POST", path: "/api/system/restart", module: "server/management/system-routes", mutates: true },
292
+ // --- Routes an equality scan of their own file cannot see (18). ---
293
+ // Each carries `mechanism`; the reconciliation test counts these separately.
294
+ { method: "GET", path: "/api/storage", module: "server/management/storage-log-guard-routes", mutates: false, mechanism: "negated-guard" },
295
+ { method: "GET", path: "/api/routing-analytics", module: "server/management/routing-analytics-routes", mutates: false, mechanism: "negated-guard" },
296
+ { method: "GET", path: "/api/system/codex-app-server", module: "server/management/system-routes", mutates: false, mechanism: "path-constant" },
297
+ { method: "POST", path: "/api/system/codex-restart", module: "server/management/system-routes", mutates: true, mechanism: "path-constant" },
298
+ { method: "POST", path: "/api/providers/reload", module: "server/management/provider-routes", mutates: true, mechanism: "path-constant", exempt: { reason: "capability-principal", why: "Gated on the local-provider-reload-capability principal (provider-routes.ts:467), not an operator action." } },
299
+ { method: "GET", path: "/api/client-integrations/{clientId}", module: "server/management/integration-routes", mutates: false, mechanism: "prefix-decode" },
300
+ { method: "PUT", path: "/api/client-integrations/{clientId}", module: "server/management/integration-routes", mutates: true, mechanism: "prefix-decode" },
301
+ { method: "GET", path: "/api/request-history/{id}", module: "server/management/request-history-routes", mutates: false, mechanism: "slice" },
302
+ { method: "GET", path: "/api/request-history/{id}/route-decision", module: "server/management/request-history-routes", mutates: false, mechanism: "ends-with" },
303
+ { method: "PUT", path: "/api/providers/{provider}/alias", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
304
+ { method: "PUT", path: "/api/providers/{provider}/model-aliases", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
305
+ { method: "PUT", path: "/api/custom-models/{id}", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
306
+ { method: "DELETE", path: "/api/custom-models/{id}", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
307
+ { method: "GET", path: "/api/lab/subjects/{id}", module: "server/management/lab-routes", mutates: false, mechanism: "regex", exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
308
+ { method: "GET", path: "/api/lab/events/{id}", module: "server/management/lab-routes", mutates: false, mechanism: "regex", exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
309
+ { method: "GET", path: "/api/lab/artifacts/{digest}", module: "server/management/lab-routes", mutates: false, mechanism: "regex", exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
310
+ { method: "POST", path: "/api/lab/automation/runs/{id}/cancel", module: "server/management/lab-automation-routes", mutates: true, mechanism: "regex", exempt: { reason: "deferred-verb", why: "Lab automation run cancellation has no CLI verb yet. A local SQLite read cannot drive it, so local-transport does not apply.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
311
+ ];
@@ -68,6 +68,14 @@ export interface LiveProxy {
68
68
  hostname?: string;
69
69
  /** Whether the successful probe used runtime-port metadata or the configured listen port. */
70
70
  source: "runtime" | "config";
71
+ /**
72
+ * Version the live proxy reported on `/healthz`, when it reported one.
73
+ *
74
+ * Carried so a stale `ocx` on PATH can be detected without a second request: the
75
+ * identity probe already parsed and validated this body. Absent for a legacy proxy whose
76
+ * healthz body predates the field.
77
+ */
78
+ version?: string;
71
79
  }
72
80
 
73
81
  /**
@@ -99,7 +107,7 @@ export async function proxyIdentityAt(
99
107
  port: number,
100
108
  opts: { hostname?: string; expectedPid?: number } = {},
101
109
  io: LivenessIo = {},
102
- ): Promise<{ pid: number | null } | null> {
110
+ ): Promise<{ pid: number | null; version?: string } | null> {
103
111
  const fetchFn = io.fetchFn ?? directLocalHttpFetch;
104
112
  const sleepFn = io.sleepFn ?? ((ms: number) => new Promise<void>(r => setTimeout(r, ms)));
105
113
  const nowFn = io.nowFn ?? Date.now;
@@ -122,7 +130,9 @@ export async function proxyIdentityAt(
122
130
  if (!isOpencodexHealthz(body)) return null;
123
131
  const pid = typeof body?.pid === "number" ? body.pid : null;
124
132
  if (opts.expectedPid !== undefined && pid !== null && pid !== opts.expectedPid) return null;
125
- return { pid };
133
+ // Guarded the same way `pid` is: a non-string version is absent, not coerced.
134
+ const version = typeof body?.version === "string" ? body.version : undefined;
135
+ return version === undefined ? { pid } : { pid, version };
126
136
  } catch {
127
137
  // Transport failure (timeout / refused) — retry while budget remains; a proxy that
128
138
  // has only just begun listening can miss a single short probe (#764).
@@ -180,7 +190,13 @@ export async function findLiveProxy(io: LivenessIo = {}): Promise<LiveProxy | nu
180
190
  // healthz confirmed the pid itself → trusted; a pidless legacy body did not,
181
191
  // so the cheap pid must pass full identity verification before it is returned.
182
192
  const trusted = identity.pid === pid ? pid : killablePid(pid);
183
- return { pid: trusted, port: runtime.port, hostname: runtime.hostname, source: "runtime" };
193
+ return {
194
+ pid: trusted,
195
+ port: runtime.port,
196
+ hostname: runtime.hostname,
197
+ source: "runtime",
198
+ ...(identity.version === undefined ? {} : { version: identity.version }),
199
+ };
184
200
  }
185
201
  }
186
202
  }
@@ -197,7 +213,13 @@ export async function findLiveProxy(io: LivenessIo = {}): Promise<LiveProxy | nu
197
213
  // (its process dead, the port reused by a pidless legacy proxy) — synthesizing it
198
214
  // would hand destructive callers (stopProxy → kill fallback) a reusable pid.
199
215
  if (identity) {
200
- return { pid: verifiedReportedPid(identity.pid), port: record.port, hostname: record.hostname, source: "runtime" };
216
+ return {
217
+ pid: verifiedReportedPid(identity.pid),
218
+ port: record.port,
219
+ hostname: record.hostname,
220
+ source: "runtime",
221
+ ...(identity.version === undefined ? {} : { version: identity.version }),
222
+ };
201
223
  }
202
224
  }
203
225
 
@@ -211,6 +233,7 @@ export async function findLiveProxy(io: LivenessIo = {}): Promise<LiveProxy | nu
211
233
  port,
212
234
  hostname: config.hostname,
213
235
  source: "config",
236
+ ...(identity.version === undefined ? {} : { version: identity.version }),
214
237
  };
215
238
  }
216
239
  return null;
@@ -63,6 +63,13 @@ export interface RequestLogContext {
63
63
  * product: widening that enum would merge Responses and Chat Completions,
64
64
  * since both leave it undefined. */
65
65
  inboundProtocol?: "responses" | "chat" | "messages";
66
+ /**
67
+ * Set when an adapter answered the turn locally and no upstream request was made
68
+ * (`ProviderAdapter.localTerminal`). A fixed identifier naming the code path, never
69
+ * conversation-derived: it exists so a request log showing zero sends is explainable
70
+ * rather than looking like a lost request.
71
+ */
72
+ localTerminalReason?: string;
66
73
  /** Stable non-PII Codex Pool account identity for durable usage attribution. */
67
74
  accountLogLabel?: string;
68
75
  requestedModel?: string;
@@ -133,6 +140,12 @@ export interface RequestLogEntry {
133
140
  /** TTFT: ms from request start to the first non-empty model output delta; unset for non-streaming/tool-only. */
134
141
  firstOutputMs?: number;
135
142
  surface?: "claude" | "claude-desktop" | "grok";
143
+ /**
144
+ * Set when the proxy answered this turn locally and sent nothing upstream. Without it a zero-send
145
+ * row is indistinguishable from a request that vanished. A fixed adapter-supplied identifier,
146
+ * never conversation-derived.
147
+ */
148
+ localTerminalReason?: string;
136
149
  /** The matched configured key's id. Set ONLY for admissionKind "configured" —
137
150
  * never a sentinel, so a hand-edited entry whose id happens to be "loopback"
138
151
  * cannot absorb unrelated traffic. */
@@ -942,6 +955,7 @@ export function addFinalRequestLog(
942
955
  logCtx.usage,
943
956
  logCtx.usageLogInputTokens,
944
957
  contextWindowForModel(logCtx.providerAdapter ?? logCtx.provider, logCtx.model),
958
+ logCtx.localTerminalReason !== undefined,
945
959
  );
946
960
  const attempts = logCtx.attempts?.map(attempt => ({
947
961
  ...attempt,
@@ -969,6 +983,9 @@ export function addFinalRequestLog(
969
983
  ...(logCtx.apiKeyId ? { apiKeyId: logCtx.apiKeyId } : {}),
970
984
  ...(logCtx.admissionKind ? { admissionKind: logCtx.admissionKind } : {}),
971
985
  ...(logCtx.inboundProtocol ? { inboundProtocol: logCtx.inboundProtocol } : {}),
986
+ ...(logCtx.localTerminalReason
987
+ ? { localTerminalReason: sanitizeLogMetadataString(logCtx.localTerminalReason) }
988
+ : {}),
972
989
  ...(isCodexUsageAccountLogLabel(logCtx.accountLogLabel)
973
990
  ? { accountLogLabel: logCtx.accountLogLabel }
974
991
  : {}),
@@ -1033,6 +1050,15 @@ export function filterRequestLogs(logs: RequestLogEntry[], params: URLSearchPara
1033
1050
  if (conversationId) {
1034
1051
  filtered = filtered.filter(entry => matchesLogConversationId(entry.conversationId, conversationId));
1035
1052
  }
1053
+ // #2704: there was no `model` clause at all, so `?model=x` was ACCEPTED and silently
1054
+ // ignored -- worse than an error, because it yields wrong conclusions from output that
1055
+ // looks correct. Attempts are matched for the same reason `provider` matches them: a
1056
+ // request that failed over should be findable by the model that actually served it.
1057
+ const model = params.get("model")?.trim();
1058
+ if (model) {
1059
+ filtered = filtered.filter(entry => entry.model === model
1060
+ || entry.attempts?.some(attempt => attempt.model === model));
1061
+ }
1036
1062
  const status = params.get("status")?.trim().toLowerCase();
1037
1063
  if (status) {
1038
1064
  filtered = /^[1-5]xx$/.test(status)
@@ -1102,6 +1128,7 @@ function finalizedUsage(
1102
1128
  usage: OcxUsage | undefined,
1103
1129
  inputTokenEstimate: number | undefined,
1104
1130
  contextWindow: number | undefined,
1131
+ locallyAnswered = false,
1105
1132
  ): FinalizedUsageResult {
1106
1133
  // The ESTIMATE itself is capped at the model's context window (codex-router PR #140). The
1107
1134
  // combined value below keeps its max(inputTokens, estimate) behavior — a provider-reported
@@ -1111,7 +1138,7 @@ function finalizedUsage(
1111
1138
  && inputTokenEstimate >= 0
1112
1139
  ? capEstimateAtContextWindow(inputTokenEstimate, contextWindow)
1113
1140
  : undefined;
1114
- const finalUsage = usageForFinalLog(adapter, usage);
1141
+ const finalUsage = usageForFinalLog(adapter, usage, locallyAnswered);
1115
1142
  const usageFallback = !finalUsage && estimate !== undefined
1116
1143
  ? { inputTokens: estimate, outputTokens: 0, estimated: true }
1117
1144
  : undefined;
@@ -1211,6 +1238,7 @@ export function finishRequestAttempt(
1211
1238
  usage ?? attempt.usage,
1212
1239
  attempt.inputTokenEstimate,
1213
1240
  contextWindowForModel(attempt.adapter, attempt.model),
1241
+ attempt.locallyAnswered === true,
1214
1242
  );
1215
1243
  attempt.status = status;
1216
1244
  attempt.durationMs = Math.max(0, durationMs);