@yansigit/opencodex 2.36.1-dev.20260829.48 → 2.36.1

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 (94) hide show
  1. package/gui/dist/assets/{ApiKeys-LOOFiZfv.js → ApiKeys-CsmNyf4I.js} +1 -1
  2. package/gui/dist/assets/{Claude-BwKzpXe3.js → Claude-B4sHsGTD.js} +1 -1
  3. package/gui/dist/assets/{CodexSet-B5qi9KeE.js → CodexSet-BuAQ4YQn.js} +1 -1
  4. package/gui/dist/assets/{FileIntegrationPage-B0sUef6W.js → FileIntegrationPage-BC65sEaS.js} +1 -1
  5. package/gui/dist/assets/{Grok-bsb4n-f4.js → Grok-DNJWlISf.js} +1 -1
  6. package/gui/dist/assets/{Integrations-CSMwFDvM.js → Integrations-vGJshFZK.js} +2 -2
  7. package/gui/dist/assets/{IntegrationsOverview-CTORdKJA.js → IntegrationsOverview-DYMkDFHE.js} +1 -1
  8. package/gui/dist/assets/{Logs-BWYbfELf.js → Logs-Coc7_RPP.js} +1 -1
  9. package/gui/dist/assets/{Models-CexEtdT1.js → Models-CKVT8Meb.js} +1 -1
  10. package/gui/dist/assets/{NumberStepper-BapSFQnW.js → NumberStepper-Dug4wR8n.js} +1 -1
  11. package/gui/dist/assets/{Providers-BwUFeAgA.js → Providers-DyaCHRWL.js} +1 -1
  12. package/gui/dist/assets/{RestoreDialog-CD8piq90.js → RestoreDialog-BpuvVU2w.js} +1 -1
  13. package/gui/dist/assets/{Startup-BgX731C2.js → Startup-DAUQqNEN.js} +1 -1
  14. package/gui/dist/assets/{Storage-Bgq7HphP.js → Storage-DfP95SlJ.js} +1 -1
  15. package/gui/dist/assets/{Subagents-ChxItbeF.js → Subagents-CMNsZLfk.js} +1 -1
  16. package/gui/dist/assets/{Usage-BkylzP50.js → Usage-ESFRfBMO.js} +1 -1
  17. package/gui/dist/assets/{codex-stale-banner-D4gvSWnO.js → codex-stale-banner-W4FhikN7.js} +1 -1
  18. package/gui/dist/assets/{data-surface-BtO3lwam.js → data-surface-B8PX8rjm.js} +1 -1
  19. package/gui/dist/assets/{data-surface-pCXChiBf.js → data-surface-CETFXqiA.js} +1 -1
  20. package/gui/dist/assets/{index-CNopOid3.js → index-DHQHBRhb.js} +3 -3
  21. package/gui/dist/assets/{model-display-CiUpg8T9.js → model-display-OBTJhQwT.js} +1 -1
  22. package/gui/dist/assets/{provider-payload-b2jlS-On.js → provider-payload-CJRJUn7w.js} +1 -1
  23. package/gui/dist/assets/{section-tabs-BP7gEPK6.js → section-tabs-B8sLMiz6.js} +1 -1
  24. package/gui/dist/assets/shared-DI18-uF7.js +69 -0
  25. package/gui/dist/index.html +2 -2
  26. package/package.json +3 -1
  27. package/src/adapters/base.ts +26 -0
  28. package/src/adapters/cursor/catalog.ts +541 -0
  29. package/src/adapters/cursor/cursor-errors.ts +15 -0
  30. package/src/adapters/cursor/discovery.ts +34 -41
  31. package/src/adapters/cursor/envelope-echo.ts +128 -0
  32. package/src/adapters/cursor/request-builder.ts +19 -12
  33. package/src/adapters/cursor/tool-definitions.ts +2 -1
  34. package/src/adapters/cursor/tool-result-normalize.ts +23 -31
  35. package/src/adapters/cursor.ts +21 -2
  36. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  37. package/src/adapters/google-antigravity-replay.ts +71 -2
  38. package/src/adapters/google.ts +19 -4
  39. package/src/adapters/kiro-constants.ts +12 -0
  40. package/src/adapters/kiro.ts +128 -11
  41. package/src/adapters/openai-chat.ts +16 -2
  42. package/src/adapters/openai-responses.ts +15 -2
  43. package/src/adapters/tool-catalog-nudge.ts +2 -1
  44. package/src/adapters/xai-web-search.ts +10 -14
  45. package/src/claude/outbound.ts +14 -3
  46. package/src/cli/access.ts +46 -3
  47. package/src/cli/account-api.ts +93 -16
  48. package/src/cli/account-extended.ts +262 -36
  49. package/src/cli/account-main.ts +12 -12
  50. package/src/cli/account.ts +40 -10
  51. package/src/cli/agent.ts +8 -1
  52. package/src/cli/capabilities-command.ts +94 -0
  53. package/src/cli/capabilities.ts +535 -0
  54. package/src/cli/claude-desktop.ts +31 -11
  55. package/src/cli/dispatch.ts +195 -27
  56. package/src/cli/doctor.ts +100 -1
  57. package/src/cli/help.ts +11 -2
  58. package/src/cli/index.ts +19 -3
  59. package/src/cli/inspect.ts +230 -0
  60. package/src/cli/observe.ts +11 -3
  61. package/src/cli/registry.ts +34 -2
  62. package/src/cli/runtime-api.ts +51 -7
  63. package/src/cli/status.ts +16 -0
  64. package/src/cli/storage.ts +234 -0
  65. package/src/cli/system-command.ts +16 -0
  66. package/src/cli/usage-report.ts +52 -2
  67. package/src/cli/version-skew.ts +46 -0
  68. package/src/codex/account-label.ts +21 -0
  69. package/src/codex/catalog/provider-fetch.ts +4 -0
  70. package/src/codex/transition-state.ts +12 -3
  71. package/src/compatibility/openai-responses.ts +9 -1
  72. package/src/generated/compatibility-version.json +94 -58
  73. package/src/integrations/ownership-policy.ts +24 -5
  74. package/src/integrations/ownership.ts +36 -2
  75. package/src/integrations/state.ts +40 -7
  76. package/src/integrations/writer.ts +21 -3
  77. package/src/lib/admin-secrets.ts +24 -0
  78. package/src/lib/errors.ts +25 -1
  79. package/src/lib/service-secrets.ts +15 -0
  80. package/src/oauth/store.ts +16 -6
  81. package/src/providers/label.ts +34 -1
  82. package/src/responses/turn-termination.ts +107 -0
  83. package/src/server/management/logs-usage-routes.ts +5 -27
  84. package/src/server/management/route-registry.ts +317 -0
  85. package/src/server/proxy-liveness.ts +27 -4
  86. package/src/server/request-log.ts +37 -17
  87. package/src/server/responses/core.ts +80 -4
  88. package/src/server/responses/policy-fallback.ts +1 -1
  89. package/src/service.ts +34 -0
  90. package/src/storage/policy-job.ts +14 -4
  91. package/src/storage/policy.ts +79 -33
  92. package/src/usage/log.ts +32 -6
  93. package/src/usage/summary.ts +22 -34
  94. package/gui/dist/assets/shared-BdYGV-yJ.js +0 -69
@@ -0,0 +1,317 @@
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/providers/test", module: "server/management-api", mutates: true },
84
+ { method: "POST", path: "/api/stop", module: "server/management-api", mutates: true },
85
+ // codex/auth-api
86
+ { method: "DELETE", path: "/api/codex-auth/accounts", module: "codex/auth-api", mutates: true },
87
+ { method: "GET", path: "/api/codex-auth/accounts", module: "codex/auth-api", mutates: false },
88
+ { method: "GET", path: "/api/codex-auth/active", module: "codex/auth-api", mutates: false },
89
+ { method: "GET", path: "/api/codex-auth/login-status", module: "codex/auth-api", mutates: false },
90
+ { method: "GET", path: "/api/codex-auth/quota", module: "codex/auth-api", mutates: false },
91
+ { method: "GET", path: "/api/codex-auth/reset-credits", module: "codex/auth-api", mutates: false },
92
+ { method: "PATCH", path: "/api/codex-auth/pool-strategy", module: "codex/auth-api", mutates: true },
93
+ { method: "POST", path: "/api/codex-auth/accounts", module: "codex/auth-api", mutates: true },
94
+ { method: "POST", path: "/api/codex-auth/accounts/clear-cooldown", module: "codex/auth-api", mutates: true },
95
+ { method: "POST", path: "/api/codex-auth/login", module: "codex/auth-api", mutates: true },
96
+ { method: "POST", path: "/api/codex-auth/login/cancel", module: "codex/auth-api", mutates: true },
97
+ { method: "POST", path: "/api/codex-auth/login/code", module: "codex/auth-api", mutates: true },
98
+ { method: "POST", path: "/api/codex-auth/reset-credits/consume", module: "codex/auth-api", mutates: true },
99
+ { method: "PUT", path: "/api/codex-auth/accounts/alias", module: "codex/auth-api", mutates: true },
100
+ { method: "PUT", path: "/api/codex-auth/accounts/pause", module: "codex/auth-api", mutates: true },
101
+ { method: "PUT", path: "/api/codex-auth/accounts/pause-exhausted", module: "codex/auth-api", mutates: true },
102
+ { method: "PUT", path: "/api/codex-auth/accounts/priority", module: "codex/auth-api", mutates: true },
103
+ { method: "PUT", path: "/api/codex-auth/active", module: "codex/auth-api", mutates: true },
104
+ { method: "PUT", path: "/api/codex-auth/auto-switch", module: "codex/auth-api", mutates: true },
105
+ { method: "PUT", path: "/api/codex-auth/failover", module: "codex/auth-api", mutates: true },
106
+ { method: "PUT", path: "/api/codex-auth/pool-strategy", module: "codex/auth-api", mutates: true },
107
+ // codex/native-profile-api
108
+ { method: "GET", path: "/api/native-main-profiles", module: "codex/native-profile-api", mutates: false },
109
+ { method: "GET", path: "/api/native-main-profiles/doctor", module: "codex/native-profile-api", mutates: false },
110
+ { method: "POST", path: "/api/native-main-profiles/recover", module: "codex/native-profile-api", mutates: true },
111
+ { method: "POST", path: "/api/native-main-profiles/register", module: "codex/native-profile-api", mutates: true },
112
+ { method: "POST", path: "/api/native-main-profiles/stage", module: "codex/native-profile-api", mutates: true },
113
+ { method: "POST", path: "/api/native-main-profiles/stage/cancel", module: "codex/native-profile-api", mutates: true },
114
+ { method: "POST", path: "/api/native-main-profiles/stage/finish", module: "codex/native-profile-api", mutates: true },
115
+ { method: "POST", path: "/api/native-main-profiles/stage/heartbeat", module: "codex/native-profile-api", mutates: true },
116
+ { method: "POST", path: "/api/native-main-profiles/switch", module: "codex/native-profile-api", mutates: true },
117
+ // server/management/agent-settings-routes
118
+ { method: "GET", path: "/api/claude-code", module: "server/management/agent-settings-routes", mutates: false },
119
+ { method: "GET", path: "/api/claude-desktop", module: "server/management/agent-settings-routes", mutates: false },
120
+ { method: "GET", path: "/api/claude-desktop/status", module: "server/management/agent-settings-routes", mutates: false },
121
+ { method: "GET", path: "/api/codex-auth/features/default-mode-request-user-input", module: "server/management/agent-settings-routes", mutates: false },
122
+ { method: "GET", path: "/api/effort-caps", module: "server/management/agent-settings-routes", mutates: false },
123
+ { method: "GET", path: "/api/grok", module: "server/management/agent-settings-routes", mutates: false },
124
+ { method: "GET", path: "/api/injection-model", module: "server/management/agent-settings-routes", mutates: false },
125
+ { method: "GET", path: "/api/subagent-model-fallback", module: "server/management/agent-settings-routes", mutates: false },
126
+ { method: "GET", path: "/api/subagent-models", module: "server/management/agent-settings-routes", mutates: false },
127
+ { method: "GET", path: "/api/subagent-roles", module: "server/management/agent-settings-routes", mutates: false },
128
+ { method: "GET", path: "/api/v2", module: "server/management/agent-settings-routes", mutates: false },
129
+ { method: "POST", path: "/api/claude-desktop/apply", module: "server/management/agent-settings-routes", mutates: true },
130
+ { method: "POST", path: "/api/grok/apply", module: "server/management/agent-settings-routes", mutates: true },
131
+ { method: "POST", path: "/api/subagent-model-authority", module: "server/management/agent-settings-routes", mutates: false },
132
+ { method: "PUT", path: "/api/claude-code", module: "server/management/agent-settings-routes", mutates: true },
133
+ { method: "PUT", path: "/api/claude-desktop", module: "server/management/agent-settings-routes", mutates: true },
134
+ { method: "PUT", path: "/api/codex-auth/features/default-mode-request-user-input", module: "server/management/agent-settings-routes", mutates: true },
135
+ { method: "PUT", path: "/api/effort-caps", module: "server/management/agent-settings-routes", mutates: true },
136
+ { method: "PUT", path: "/api/grok/selection", module: "server/management/agent-settings-routes", mutates: true },
137
+ { method: "PUT", path: "/api/injection-model", module: "server/management/agent-settings-routes", mutates: true },
138
+ { method: "PUT", path: "/api/subagent-model-fallback", module: "server/management/agent-settings-routes", mutates: true },
139
+ { method: "PUT", path: "/api/subagent-models", module: "server/management/agent-settings-routes", mutates: true },
140
+ { method: "PUT", path: "/api/subagent-roles", module: "server/management/agent-settings-routes", mutates: true },
141
+ { method: "PUT", path: "/api/v2", module: "server/management/agent-settings-routes", mutates: true },
142
+ // server/management/codex-prompt-routes
143
+ { method: "GET", path: "/api/codex-prompt", module: "server/management/codex-prompt-routes", mutates: false },
144
+ { method: "GET", path: "/api/codex-prompt/text", module: "server/management/codex-prompt-routes", mutates: false },
145
+ { 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)." } },
146
+ { 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)." } },
147
+ { 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)." } },
148
+ { 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)." } },
149
+ { 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)." } },
150
+ { 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)." } },
151
+ // server/management/combo-routes
152
+ { method: "DELETE", path: "/api/combos", module: "server/management/combo-routes", mutates: true },
153
+ { method: "GET", path: "/api/combos", module: "server/management/combo-routes", mutates: false },
154
+ { method: "PUT", path: "/api/combos", module: "server/management/combo-routes", mutates: true },
155
+ // server/management/config-routes
156
+ { method: "GET", path: "/api/config", module: "server/management/config-routes", mutates: false },
157
+ { method: "GET", path: "/api/diagnostics/project-config", module: "server/management/config-routes", mutates: false },
158
+ { method: "GET", path: "/api/settings", module: "server/management/config-routes", mutates: false },
159
+ { method: "GET", path: "/api/shadow-call-settings", module: "server/management/config-routes", mutates: false },
160
+ { method: "GET", path: "/api/sidecar-settings", module: "server/management/config-routes", mutates: false },
161
+ { method: "GET", path: "/api/startup-health", module: "server/management/config-routes", mutates: false },
162
+ { method: "GET", path: "/api/update/check", module: "server/management/config-routes", mutates: false },
163
+ { method: "GET", path: "/api/update/status", module: "server/management/config-routes", mutates: false },
164
+ { method: "GET", path: "/api/windows-tray", module: "server/management/config-routes", mutates: false },
165
+ { method: "POST", path: "/api/startup-action", module: "server/management/config-routes", mutates: true },
166
+ { method: "POST", path: "/api/sync", module: "server/management/config-routes", mutates: true },
167
+ { method: "POST", path: "/api/update/run", module: "server/management/config-routes", mutates: true },
168
+ { method: "POST", path: "/api/windows-tray", module: "server/management/config-routes", mutates: true },
169
+ { 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." } },
170
+ { method: "PUT", path: "/api/settings", module: "server/management/config-routes", mutates: true },
171
+ { method: "PUT", path: "/api/shadow-call-settings", module: "server/management/config-routes", mutates: true },
172
+ { method: "PUT", path: "/api/sidecar-settings", module: "server/management/config-routes", mutates: true },
173
+ // server/management/integration-routes
174
+ { method: "GET", path: "/api/client-integrations", module: "server/management/integration-routes", mutates: false },
175
+ { method: "GET", path: "/api/client-integrations/journal", module: "server/management/integration-routes", mutates: false },
176
+ { method: "POST", path: "/api/client-integrations/restore", module: "server/management/integration-routes", mutates: true },
177
+ // server/management/lab-automation-routes
178
+ { 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." } },
179
+ { 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." } },
180
+ { 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" } },
181
+ { 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" } },
182
+ // server/management/lab-routes
183
+ { 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." } },
184
+ { 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." } },
185
+ { 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." } },
186
+ { 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." } },
187
+ { 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." } },
188
+ { 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." } },
189
+ { 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." } },
190
+ { 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." } },
191
+ { 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." } },
192
+ { 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" } },
193
+ { 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" } },
194
+ { 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" } },
195
+ { 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" } },
196
+ // server/management/logs-usage-routes
197
+ { method: "GET", path: "/api/claude/inbound-debug", module: "server/management/logs-usage-routes", mutates: false },
198
+ { method: "GET", path: "/api/debug", module: "server/management/logs-usage-routes", mutates: false },
199
+ { method: "GET", path: "/api/debug/injection-logs", module: "server/management/logs-usage-routes", mutates: false },
200
+ { method: "GET", path: "/api/debug/logs", module: "server/management/logs-usage-routes", mutates: false },
201
+ { method: "GET", path: "/api/debug/usage-logs", module: "server/management/logs-usage-routes", mutates: false },
202
+ { method: "GET", path: "/api/logs", module: "server/management/logs-usage-routes", mutates: false },
203
+ { method: "GET", path: "/api/storage/cleanup-policy", module: "server/management/logs-usage-routes", mutates: false },
204
+ { 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." } },
205
+ { method: "GET", path: "/api/storage/trash", module: "server/management/logs-usage-routes", mutates: false },
206
+ { 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." } },
207
+ { method: "GET", path: "/api/usage", module: "server/management/logs-usage-routes", mutates: false },
208
+ { method: "POST", path: "/api/storage/cleanup", module: "server/management/logs-usage-routes", mutates: true },
209
+ { method: "POST", path: "/api/storage/cleanup-policy/run", module: "server/management/logs-usage-routes", mutates: true },
210
+ { method: "POST", path: "/api/storage/cleanup/preview", module: "server/management/logs-usage-routes", mutates: true },
211
+ { method: "POST", path: "/api/storage/trash/restore", module: "server/management/logs-usage-routes", mutates: true },
212
+ { method: "PUT", path: "/api/debug", module: "server/management/logs-usage-routes", mutates: true },
213
+ { method: "PUT", path: "/api/storage/cleanup-policy", module: "server/management/logs-usage-routes", mutates: true },
214
+ // server/management/model-routes
215
+ { method: "GET", path: "/api/aliases", module: "server/management/model-routes", mutates: false },
216
+ { method: "GET", path: "/api/catalog", module: "server/management/model-routes", mutates: false },
217
+ { method: "GET", path: "/api/client-config", module: "server/management/model-routes", mutates: false },
218
+ { method: "GET", path: "/api/custom-models", module: "server/management/model-routes", mutates: false },
219
+ { method: "GET", path: "/api/model-discovery", module: "server/management/model-routes", mutates: false },
220
+ { method: "GET", path: "/api/model-presets", module: "server/management/model-routes", mutates: false },
221
+ { method: "GET", path: "/api/models", module: "server/management/model-routes", mutates: false },
222
+ { method: "GET", path: "/api/selected-models", module: "server/management/model-routes", mutates: false },
223
+ { method: "POST", path: "/api/custom-models", module: "server/management/model-routes", mutates: true },
224
+ { method: "POST", path: "/api/model-discovery/acknowledge", module: "server/management/model-routes", mutates: true },
225
+ { method: "PUT", path: "/api/default-aliases", module: "server/management/model-routes", mutates: true },
226
+ { method: "PUT", path: "/api/disabled-models", module: "server/management/model-routes", mutates: true },
227
+ { method: "PUT", path: "/api/model-discovery", module: "server/management/model-routes", mutates: true },
228
+ { method: "PUT", path: "/api/model-presets", module: "server/management/model-routes", mutates: true },
229
+ { method: "PUT", path: "/api/model-visibility", module: "server/management/model-routes", mutates: true },
230
+ { method: "PUT", path: "/api/selected-models", module: "server/management/model-routes", mutates: true },
231
+ // server/management/native-integration-routes
232
+ { method: "GET", path: "/api/native-integrations", module: "server/management/native-integration-routes", mutates: false },
233
+ { method: "PUT", path: "/api/native-integrations/claude", module: "server/management/native-integration-routes", mutates: true },
234
+ { method: "PUT", path: "/api/native-integrations/claude-desktop", module: "server/management/native-integration-routes", mutates: true },
235
+ { method: "PUT", path: "/api/native-integrations/codex", module: "server/management/native-integration-routes", mutates: true },
236
+ { method: "PUT", path: "/api/native-integrations/grok", module: "server/management/native-integration-routes", mutates: true },
237
+ // server/management/oauth-account-routes
238
+ { method: "DELETE", path: "/api/keys", module: "server/management/oauth-account-routes", mutates: true },
239
+ { method: "DELETE", path: "/api/oauth/accounts", module: "server/management/oauth-account-routes", mutates: true },
240
+ { method: "DELETE", path: "/api/providers/keys", module: "server/management/oauth-account-routes", mutates: true },
241
+ { method: "GET", path: "/api/key-providers", module: "server/management/oauth-account-routes", mutates: false },
242
+ { method: "GET", path: "/api/keys", module: "server/management/oauth-account-routes", mutates: false },
243
+ { method: "GET", path: "/api/oauth/accounts", module: "server/management/oauth-account-routes", mutates: false },
244
+ { method: "GET", path: "/api/oauth/accounts/pool", module: "server/management/oauth-account-routes", mutates: false },
245
+ { method: "GET", path: "/api/oauth/providers", module: "server/management/oauth-account-routes", mutates: false },
246
+ { method: "GET", path: "/api/oauth/status", module: "server/management/oauth-account-routes", mutates: false },
247
+ { method: "GET", path: "/api/providers/keys", module: "server/management/oauth-account-routes", mutates: false },
248
+ { method: "PATCH", path: "/api/keys", module: "server/management/oauth-account-routes", mutates: true },
249
+ { method: "PATCH", path: "/api/oauth/accounts/pool", module: "server/management/oauth-account-routes", mutates: true },
250
+ { method: "POST", path: "/api/keys", module: "server/management/oauth-account-routes", mutates: true },
251
+ { method: "POST", path: "/api/oauth/accounts/clear-cooldown", module: "server/management/oauth-account-routes", mutates: true },
252
+ { method: "POST", path: "/api/oauth/accounts/import", module: "server/management/oauth-account-routes", mutates: true },
253
+ { method: "POST", path: "/api/oauth/login", module: "server/management/oauth-account-routes", mutates: true },
254
+ { method: "POST", path: "/api/oauth/login/cancel", module: "server/management/oauth-account-routes", mutates: true },
255
+ { method: "POST", path: "/api/oauth/login/code", module: "server/management/oauth-account-routes", mutates: true },
256
+ { method: "POST", path: "/api/oauth/logout", module: "server/management/oauth-account-routes", mutates: true },
257
+ { method: "POST", path: "/api/providers/keys", module: "server/management/oauth-account-routes", mutates: true },
258
+ { method: "PUT", path: "/api/oauth/accounts/active", module: "server/management/oauth-account-routes", mutates: true },
259
+ { method: "PUT", path: "/api/oauth/accounts/alias", module: "server/management/oauth-account-routes", mutates: true },
260
+ { method: "PUT", path: "/api/oauth/accounts/pool", module: "server/management/oauth-account-routes", mutates: true },
261
+ { method: "PUT", path: "/api/providers/keys/active", module: "server/management/oauth-account-routes", mutates: true },
262
+ { method: "PUT", path: "/api/providers/keys/alias", module: "server/management/oauth-account-routes", mutates: true },
263
+ // server/management/provider-routes
264
+ { method: "DELETE", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
265
+ { method: "GET", path: "/api/provider-context-caps", module: "server/management/provider-routes", mutates: false },
266
+ { method: "GET", path: "/api/provider-presets", module: "server/management/provider-routes", mutates: false },
267
+ { method: "GET", path: "/api/provider-quotas", module: "server/management/provider-routes", mutates: false },
268
+ { method: "GET", path: "/api/provider-request-pacing", module: "server/management/provider-routes", mutates: false },
269
+ { method: "GET", path: "/api/providers", module: "server/management/provider-routes", mutates: false },
270
+ { method: "PATCH", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
271
+ { method: "POST", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
272
+ { method: "POST", path: "/api/providers/test", module: "server/management/provider-routes", mutates: true },
273
+ { method: "PUT", path: "/api/provider-context-caps", module: "server/management/provider-routes", mutates: true },
274
+ // server/management/replit-provider-routes
275
+ { method: "POST", path: "/api/providers/replit-pair", module: "server/management/replit-provider-routes", mutates: true },
276
+ // server/management/request-history-routes
277
+ { method: "GET", path: "/api/request-history", module: "server/management/request-history-routes", mutates: false },
278
+ // server/management/routing-analytics-routes
279
+ // server/management/routing-profile-routes
280
+ { method: "DELETE", path: "/api/routing-profiles", module: "server/management/routing-profile-routes", mutates: true },
281
+ { method: "GET", path: "/api/routing-profiles", module: "server/management/routing-profile-routes", mutates: false },
282
+ { method: "POST", path: "/api/routing-profiles/dry-run", module: "server/management/routing-profile-routes", mutates: true },
283
+ { method: "PUT", path: "/api/routing-profiles", module: "server/management/routing-profile-routes", mutates: true },
284
+ // server/management/sidebar-routes
285
+ { method: "GET", path: "/api/github/star", module: "server/management/sidebar-routes", mutates: false },
286
+ { method: "GET", path: "/api/update/badge", module: "server/management/sidebar-routes", mutates: false },
287
+ { 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." } },
288
+ // server/management/storage-log-guard-routes
289
+ { method: "GET", path: "/api/storage/codex-logs", module: "server/management/storage-log-guard-routes", mutates: false },
290
+ { method: "POST", path: "/api/storage/codex-logs/compact", module: "server/management/storage-log-guard-routes", mutates: true },
291
+ { method: "POST", path: "/api/storage/codex-logs/protect", module: "server/management/storage-log-guard-routes", mutates: true },
292
+ { method: "POST", path: "/api/storage/codex-logs/repair", module: "server/management/storage-log-guard-routes", mutates: true },
293
+ { method: "POST", path: "/api/storage/codex-logs/unprotect", module: "server/management/storage-log-guard-routes", mutates: true },
294
+ // server/management/system-routes
295
+ { method: "GET", path: "/api/system/memory", module: "server/management/system-routes", mutates: false },
296
+ { method: "GET", path: "/api/system/windows-replace-retries", module: "server/management/system-routes", mutates: false },
297
+ { method: "POST", path: "/api/system/restart", module: "server/management/system-routes", mutates: true },
298
+ // --- Routes an equality scan of their own file cannot see (18). ---
299
+ // Each carries `mechanism`; the reconciliation test counts these separately.
300
+ { method: "GET", path: "/api/storage", module: "server/management/storage-log-guard-routes", mutates: false, mechanism: "negated-guard" },
301
+ { method: "GET", path: "/api/routing-analytics", module: "server/management/routing-analytics-routes", mutates: false, mechanism: "negated-guard" },
302
+ { method: "GET", path: "/api/system/codex-app-server", module: "server/management/system-routes", mutates: false, mechanism: "path-constant" },
303
+ { method: "POST", path: "/api/system/codex-restart", module: "server/management/system-routes", mutates: true, mechanism: "path-constant" },
304
+ { 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." } },
305
+ { method: "GET", path: "/api/client-integrations/{clientId}", module: "server/management/integration-routes", mutates: false, mechanism: "prefix-decode" },
306
+ { method: "PUT", path: "/api/client-integrations/{clientId}", module: "server/management/integration-routes", mutates: true, mechanism: "prefix-decode" },
307
+ { method: "GET", path: "/api/request-history/{id}", module: "server/management/request-history-routes", mutates: false, mechanism: "slice" },
308
+ { method: "GET", path: "/api/request-history/{id}/route-decision", module: "server/management/request-history-routes", mutates: false, mechanism: "ends-with" },
309
+ { method: "PUT", path: "/api/providers/{provider}/alias", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
310
+ { method: "PUT", path: "/api/providers/{provider}/model-aliases", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
311
+ { method: "PUT", path: "/api/custom-models/{id}", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
312
+ { method: "DELETE", path: "/api/custom-models/{id}", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
313
+ { 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." } },
314
+ { 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." } },
315
+ { 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." } },
316
+ { 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" } },
317
+ ];
@@ -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;
@@ -21,6 +21,7 @@ import {
21
21
  isKnownAdmissionKind,
22
22
  isKnownInboundProtocol,
23
23
  isKnownUsageSurface,
24
+ isCodexUsageAccountLogLabel,
24
25
  isPersistableAccountLogLabel,
25
26
  isValidReasoningWireValue,
26
27
  readRecentUsageEntries,
@@ -65,6 +66,13 @@ export interface RequestLogContext {
65
66
  * product: widening that enum would merge Responses and Chat Completions,
66
67
  * since both leave it undefined. */
67
68
  inboundProtocol?: "responses" | "chat" | "messages";
69
+ /**
70
+ * Set when an adapter answered the turn locally and no upstream request was made
71
+ * (`ProviderAdapter.localTerminal`). A fixed identifier naming the code path, never
72
+ * conversation-derived: it exists so a request log showing zero sends is explainable
73
+ * rather than looking like a lost request.
74
+ */
75
+ localTerminalReason?: string;
68
76
  agentKind?: AgentKind;
69
77
  /** Stable non-PII Codex Pool account identity for durable usage attribution. */
70
78
  accountLogLabel?: string;
@@ -136,6 +144,12 @@ export interface RequestLogEntry {
136
144
  /** TTFT: ms from request start to the first non-empty model output delta; unset for non-streaming/tool-only. */
137
145
  firstOutputMs?: number;
138
146
  surface?: "claude" | "claude-desktop" | "grok";
147
+ /**
148
+ * Set when the proxy answered this turn locally and sent nothing upstream. Without it a zero-send
149
+ * row is indistinguishable from a request that vanished. A fixed adapter-supplied identifier,
150
+ * never conversation-derived.
151
+ */
152
+ localTerminalReason?: string;
139
153
  /** The matched configured key's id. Set ONLY for admissionKind "configured" —
140
154
  * never a sentinel, so a hand-edited entry whose id happens to be "loopback"
141
155
  * cannot absorb unrelated traffic. */
@@ -264,7 +278,7 @@ export function requestLogEntryFromPersistedUsage(entry: PersistedUsageEntry): R
264
278
  ...(entry.firstOutputMs !== undefined ? { firstOutputMs: entry.firstOutputMs } : {}),
265
279
  ...(isKnownUsageSurface(entry.surface) ? { surface: entry.surface } : {}),
266
280
  ...(entry.conversationId ? { conversationId: entry.conversationId } : {}),
267
- ...(isPersistableAccountLogLabel(entry.accountLogLabel)
281
+ ...(isCodexUsageAccountLogLabel(entry.accountLogLabel)
268
282
  ? { accountLogLabel: entry.accountLogLabel }
269
283
  : {}),
270
284
  ...(entry.requestedModel ? { requestedModel: entry.requestedModel } : {}),
@@ -949,6 +963,7 @@ export function addFinalRequestLog(
949
963
  logCtx.usage,
950
964
  logCtx.usageLogInputTokens,
951
965
  contextWindowForModel(logCtx.providerAdapter ?? logCtx.provider, logCtx.model),
966
+ logCtx.localTerminalReason !== undefined,
952
967
  );
953
968
  const attempts = logCtx.attempts?.map(attempt => ({
954
969
  ...attempt,
@@ -976,6 +991,9 @@ export function addFinalRequestLog(
976
991
  ...(logCtx.apiKeyId ? { apiKeyId: logCtx.apiKeyId } : {}),
977
992
  ...(logCtx.admissionKind ? { admissionKind: logCtx.admissionKind } : {}),
978
993
  ...(logCtx.inboundProtocol ? { inboundProtocol: logCtx.inboundProtocol } : {}),
994
+ ...(logCtx.localTerminalReason
995
+ ? { localTerminalReason: sanitizeLogMetadataString(logCtx.localTerminalReason) }
996
+ : {}),
979
997
  ...(logCtx.agentKind ? { agentKind: logCtx.agentKind } : {}),
980
998
  ...(isPersistableAccountLogLabel(logCtx.accountLogLabel)
981
999
  ? { accountLogLabel: logCtx.accountLogLabel }
@@ -1037,29 +1055,29 @@ export function filterRequestLogs(logs: RequestLogEntry[], params: URLSearchPara
1037
1055
  filtered = filtered.filter(entry => entry.provider === provider
1038
1056
  || entry.attempts?.some(attempt => attempt.provider === provider));
1039
1057
  }
1040
- const model = params.get("model")?.trim().toLowerCase();
1058
+ const conversationId = params.get("conversationId")?.trim() || params.get("conversation")?.trim();
1059
+ if (conversationId) {
1060
+ filtered = filtered.filter(entry => matchesLogConversationId(entry.conversationId, conversationId));
1061
+ }
1062
+ // #2704: there was no `model` clause at all, so `?model=x` was ACCEPTED and silently
1063
+ // ignored -- worse than an error, because it yields wrong conclusions from output that
1064
+ // looks correct. Attempts are matched for the same reason `provider` matches them: a
1065
+ // request that failed over should be findable by the model that actually served it.
1066
+ const model = params.get("model")?.trim();
1041
1067
  if (model) {
1042
- filtered = filtered.filter(entry => entry.model.toLowerCase() === model
1043
- || entry.resolvedModel?.toLowerCase() === model
1044
- || entry.attempts?.some(attempt => attempt.model.toLowerCase() === model));
1068
+ filtered = filtered.filter(entry => entry.model === model
1069
+ || entry.resolvedModel === model
1070
+ || entry.attempts?.some(attempt => attempt.model === model));
1045
1071
  }
1046
1072
  const sinceRaw = params.get("since")?.trim();
1047
1073
  if (sinceRaw) {
1048
1074
  const since = Number(sinceRaw);
1049
- if (Number.isFinite(since)) {
1050
- filtered = filtered.filter(entry => entry.timestamp >= since);
1051
- }
1075
+ if (Number.isFinite(since)) filtered = filtered.filter(entry => entry.timestamp >= since);
1052
1076
  }
1053
1077
  const untilRaw = params.get("until")?.trim();
1054
1078
  if (untilRaw) {
1055
1079
  const until = Number(untilRaw);
1056
- if (Number.isFinite(until)) {
1057
- filtered = filtered.filter(entry => entry.timestamp <= until);
1058
- }
1059
- }
1060
- const conversationId = params.get("conversationId")?.trim() || params.get("conversation")?.trim();
1061
- if (conversationId) {
1062
- filtered = filtered.filter(entry => matchesLogConversationId(entry.conversationId, conversationId));
1080
+ if (Number.isFinite(until)) filtered = filtered.filter(entry => entry.timestamp <= until);
1063
1081
  }
1064
1082
  const status = params.get("status")?.trim().toLowerCase();
1065
1083
  if (status) {
@@ -1130,6 +1148,7 @@ function finalizedUsage(
1130
1148
  usage: OcxUsage | undefined,
1131
1149
  inputTokenEstimate: number | undefined,
1132
1150
  contextWindow: number | undefined,
1151
+ locallyAnswered = false,
1133
1152
  ): FinalizedUsageResult {
1134
1153
  // The ESTIMATE itself is capped at the model's context window (codex-router PR #140). The
1135
1154
  // combined value below keeps its max(inputTokens, estimate) behavior — a provider-reported
@@ -1139,7 +1158,7 @@ function finalizedUsage(
1139
1158
  && inputTokenEstimate >= 0
1140
1159
  ? capEstimateAtContextWindow(inputTokenEstimate, contextWindow)
1141
1160
  : undefined;
1142
- const finalUsage = usageForFinalLog(adapter, usage);
1161
+ const finalUsage = usageForFinalLog(adapter, usage, locallyAnswered);
1143
1162
  const usageFallback = !finalUsage && estimate !== undefined
1144
1163
  ? { inputTokens: estimate, outputTokens: 0, estimated: true }
1145
1164
  : undefined;
@@ -1203,7 +1222,7 @@ export function sealRequestAttemptIdentity(
1203
1222
  if (!attempt) return;
1204
1223
  attempt.provider = provider;
1205
1224
  attempt.adapter = adapter;
1206
- if (isPersistableAccountLogLabel(accountLogLabel)) attempt.accountLogLabel = accountLogLabel;
1225
+ if (isCodexUsageAccountLogLabel(accountLogLabel)) attempt.accountLogLabel = accountLogLabel;
1207
1226
  }
1208
1227
 
1209
1228
  export function noteAttemptSend(
@@ -1240,6 +1259,7 @@ export function finishRequestAttempt(
1240
1259
  usage ?? attempt.usage,
1241
1260
  attempt.inputTokenEstimate,
1242
1261
  contextWindowForModel(attempt.adapter, attempt.model),
1262
+ attempt.locallyAnswered === true,
1243
1263
  );
1244
1264
  attempt.status = status;
1245
1265
  attempt.durationMs = Math.max(0, durationMs);