@omnicross/daemon 0.1.6 → 0.1.7

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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/bootstrap.ts
2
- import { accessSync, constants as fsConstants, existsSync as existsSync17 } from "fs";
2
+ import { accessSync, constants as fsConstants, existsSync as existsSync19 } from "fs";
3
3
  import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
4
4
  import { DEFAULT_BILLING_CONFIG } from "@omnicross/contracts/billing-types";
5
5
  import { getGeminiCodeAssistProjectResolver } from "@omnicross/core/auth/GeminiCodeAssistProjectResolver";
@@ -114,7 +114,7 @@ async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
114
114
  const code = await deps.codexAwaitLoopback(state, void 0, signal);
115
115
  const result = await codexOAuth.exchangeCodeForTokens(
116
116
  { authorizationCode: code, codeVerifier, state },
117
- deps.oauthExchangeFetch
117
+ deps.oauthExchangeFetch("codex")
118
118
  );
119
119
  const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
120
120
  const block = {
@@ -634,6 +634,17 @@ function handleAuditQuery(req, res, reader) {
634
634
  res.writeHead(200, { "Content-Type": "application/json" });
635
635
  res.end(JSON.stringify({ records }));
636
636
  }
637
+ async function handleAuditStatsQuery(req, res, reader) {
638
+ const url = new URL(req.url ?? "/", "http://localhost");
639
+ const query2 = {};
640
+ const from = intParam(url.searchParams.get("from"));
641
+ if (from !== void 0) query2.from = from;
642
+ const to = intParam(url.searchParams.get("to"));
643
+ if (to !== void 0) query2.to = to;
644
+ const stats = reader ? await reader(query2) : { requestCount: 0, errorCount: 0, complete: true };
645
+ res.writeHead(200, { "Content-Type": "application/json" });
646
+ res.end(JSON.stringify(stats));
647
+ }
637
648
 
638
649
  // src/admin/billingStatusApi.ts
639
650
  function handleBillingStatus(res, reader) {
@@ -2145,7 +2156,13 @@ function listMappablePresets() {
2145
2156
  name: preset.name,
2146
2157
  apiFormat: resolved.format,
2147
2158
  baseUrl: preset.api_base_url,
2148
- models: Array.isArray(preset.models) ? preset.models : []
2159
+ models: Array.isArray(preset.models) ? preset.models : [],
2160
+ nameKey: preset.nameKey,
2161
+ icon: preset.icon,
2162
+ description: preset.description,
2163
+ features: preset.features,
2164
+ website: preset.website,
2165
+ modelsEndpoint: preset.modelsEndpoint
2149
2166
  });
2150
2167
  }
2151
2168
  return { mappable, excluded };
@@ -2536,7 +2553,7 @@ async function handleOAuthComplete(providerId, body, deps) {
2536
2553
  const rawCode = typeof body["code"] === "string" ? body["code"] : "";
2537
2554
  if (!sessionId) return err2(400, "oauth complete requires { sessionId }");
2538
2555
  if (!rawCode) return err2(400, "oauth complete requires { code }");
2539
- const session = deps.oauthSessions.take(sessionId);
2556
+ const session = deps.oauthSessions.peek(sessionId);
2540
2557
  if (!session) return err2(410, "oauth session is unknown, expired, or already used");
2541
2558
  if (session.providerId !== providerId) {
2542
2559
  return err2(400, `oauth session does not match provider '${providerId}'`);
@@ -2550,13 +2567,15 @@ async function handleOAuthComplete(providerId, body, deps) {
2550
2567
  }
2551
2568
  code = splitCode;
2552
2569
  }
2570
+ const exchangeFetch = deps.oauthExchangeFetch(providerId);
2553
2571
  let block;
2554
2572
  try {
2555
- block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, deps.oauthExchangeFetch) : await exchangeGemini(code, session.codeVerifier, deps.oauthExchangeFetch);
2573
+ block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
2556
2574
  } catch (exchangeError) {
2557
2575
  const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
2558
2576
  return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
2559
2577
  }
2578
+ deps.oauthSessions.consume(sessionId);
2560
2579
  const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
2561
2580
  await deps.subscriptionAccountAppender.appendProviderAccount(providerId, block, label);
2562
2581
  const status = await statusEntryFor(deps.subscriptionAccounts, providerId);
@@ -2806,8 +2825,8 @@ function validateAuditSegment(patch) {
2806
2825
  }
2807
2826
  }
2808
2827
  const maxBodyBytes = audit["maxBodyBytes"];
2809
- if (maxBodyBytes !== void 0 && (typeof maxBodyBytes !== "number" || !Number.isFinite(maxBodyBytes) || maxBodyBytes < 0)) {
2810
- errors.push("audit.maxBodyBytes must be a non-negative number");
2828
+ if (maxBodyBytes !== void 0 && (typeof maxBodyBytes !== "number" || !Number.isFinite(maxBodyBytes) || maxBodyBytes < -1)) {
2829
+ errors.push("audit.maxBodyBytes must be -1 or a non-negative number");
2811
2830
  }
2812
2831
  const retentionDays = audit["retentionDays"];
2813
2832
  if (retentionDays !== void 0 && (typeof retentionDays !== "number" || !Number.isFinite(retentionDays) || retentionDays < 0)) {
@@ -3252,18 +3271,16 @@ function preserveWebhookSecrets(incoming, current) {
3252
3271
  }
3253
3272
 
3254
3273
  // src/audit/auditRuntime.ts
3255
- import { join as join4 } from "path";
3256
3274
  import { setAuditCaptureConfig, setAuditSink } from "@omnicross/core/pipeline/auditSink";
3257
3275
  import { setUpstreamTracePath } from "@omnicross/core/pipeline/upstreamTrace";
3258
3276
  var writer = null;
3259
3277
  var sweeper = null;
3260
- var auditDir = "";
3261
- function setAuditRuntime(w, s, dir) {
3278
+ function setAuditRuntime(w, s) {
3262
3279
  writer = w;
3263
3280
  sweeper = s;
3264
- auditDir = dir;
3265
3281
  }
3266
3282
  function applyAuditConfig(config) {
3283
+ setUpstreamTracePath(null);
3267
3284
  const enabled = config?.enabled === true && writer !== null;
3268
3285
  if (enabled && config) {
3269
3286
  setAuditCaptureConfig(config);
@@ -3273,11 +3290,9 @@ function applyAuditConfig(config) {
3273
3290
  sweeper.configure(config);
3274
3291
  sweeper.start();
3275
3292
  }
3276
- setUpstreamTracePath(config.captureBodies ? join4(auditDir, "upstream-trace.jsonl") : null);
3277
3293
  } else {
3278
3294
  setAuditCaptureConfig(null);
3279
3295
  setAuditSink(null);
3280
- setUpstreamTracePath(null);
3281
3296
  if (sweeper) {
3282
3297
  if (config) sweeper.configure(config);
3283
3298
  sweeper.dispose();
@@ -3291,7 +3306,6 @@ function resetAuditRuntimeForTests() {
3291
3306
  if (sweeper) sweeper.dispose();
3292
3307
  writer = null;
3293
3308
  sweeper = null;
3294
- auditDir = "";
3295
3309
  }
3296
3310
 
3297
3311
  // src/billing/billingRuntime.ts
@@ -4136,6 +4150,11 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
4136
4150
  }
4137
4151
 
4138
4152
  // src/admin/adminApi.ts
4153
+ import {
4154
+ ACCOUNT_ROUTE_ACTIVITY_LIMIT,
4155
+ getSharedAccountRouteActivity
4156
+ } from "@omnicross/core/pipeline/AccountRouteActivity";
4157
+ import { getSharedOverloadCounter } from "@omnicross/core/pipeline/ServerOverloadCounter";
4139
4158
  function readBody(req) {
4140
4159
  return new Promise((resolve2, reject) => {
4141
4160
  const chunks = [];
@@ -4172,6 +4191,9 @@ function toKeyInfo(row) {
4172
4191
  id: row.id,
4173
4192
  name: row.name,
4174
4193
  keyPrefix: row.keyPrefix,
4194
+ // True only when a reversible `keySecret` envelope was persisted at creation
4195
+ // — gates the UI "view key" eye. Legacy hash-only rows read as absent.
4196
+ revealable: Boolean(row.keySecret),
4175
4197
  enabled: row.enabled,
4176
4198
  createdAt: row.createdAt,
4177
4199
  lastUsedAt: row.lastUsedAt,
@@ -4827,7 +4849,13 @@ function handlePresets(res, method) {
4827
4849
  name: p.name,
4828
4850
  apiFormat: p.apiFormat,
4829
4851
  baseUrl: p.baseUrl,
4830
- models: p.models
4852
+ models: p.models,
4853
+ nameKey: p.nameKey,
4854
+ icon: p.icon,
4855
+ description: p.description,
4856
+ features: p.features,
4857
+ website: p.website,
4858
+ modelsEndpoint: p.modelsEndpoint
4831
4859
  }));
4832
4860
  return writeJson3(res, 200, { presets, excluded });
4833
4861
  }
@@ -4861,12 +4889,27 @@ async function handleKeys(req, res, method, rest, deps) {
4861
4889
  plaintextOnce: created.plaintextOnce
4862
4890
  });
4863
4891
  }
4892
+ if (method === "GET" && rest.length === 2 && rest[1] === "reveal") {
4893
+ const revealed = await deps.keyDb.outboundApiKeysReveal(rest[0]);
4894
+ if (revealed !== null) return writeJson3(res, 200, { key: revealed });
4895
+ const exists = (await deps.keyDb.outboundApiKeysList()).some((r) => r.id === rest[0]);
4896
+ if (!exists) return writeJsonError(res, 404, `key '${rest[0]}' not found`);
4897
+ return writeJsonError(
4898
+ res,
4899
+ 409,
4900
+ `key '${rest[0]}' is not revealable (created before revealable key storage)`
4901
+ );
4902
+ }
4864
4903
  const id = rest[0];
4865
4904
  const action = rest[1];
4866
4905
  if (method === "POST" && id && action === "revoke") {
4867
4906
  const ok = await deps.keyDb.outboundApiKeysRevoke(id);
4868
4907
  return writeJson3(res, ok ? 200 : 404, { ok });
4869
4908
  }
4909
+ if (method === "DELETE" && id && !action) {
4910
+ const ok = await deps.keyDb.outboundApiKeysDelete(id);
4911
+ return writeJson3(res, ok ? 200 : 404, { ok });
4912
+ }
4870
4913
  if (method === "POST" && id && action === "enabled") {
4871
4914
  const body = await readJsonBody3(req);
4872
4915
  const enabled = body["enabled"] === true;
@@ -5043,6 +5086,40 @@ async function handleServer(req, res, method, deps) {
5043
5086
  return writeJsonError(res, 405, `method ${method} not allowed on server`);
5044
5087
  }
5045
5088
  async function handleAccounts(req, res, method, rest, deps) {
5089
+ if (rest[0] === "route-activity" && rest.length === 1) {
5090
+ if (method !== "GET") {
5091
+ return writeJsonError(res, 405, `method ${method} not allowed on account route activity`);
5092
+ }
5093
+ const query2 = requestQuery(req);
5094
+ const parsedLimit = Number(query2.get("limit") ?? "100");
5095
+ const records = getSharedAccountRouteActivity().list({
5096
+ providerId: query2.get("providerId") ?? void 0,
5097
+ accountId: query2.get("accountId") ?? void 0,
5098
+ sessionKey: query2.get("sessionKey") ?? void 0,
5099
+ limit: Number.isFinite(parsedLimit) ? parsedLimit : 100
5100
+ });
5101
+ return writeJson3(res, 200, {
5102
+ available: true,
5103
+ records,
5104
+ capacity: ACCOUNT_ROUTE_ACTIVITY_LIMIT,
5105
+ collectedAt: Date.now()
5106
+ });
5107
+ }
5108
+ if (rest[0] === "overload-counters" && rest.length === 1) {
5109
+ if (method !== "GET") {
5110
+ return writeJsonError(res, 405, `method ${method} not allowed on overload counters`);
5111
+ }
5112
+ const query2 = requestQuery(req);
5113
+ const entries = getSharedOverloadCounter().list({
5114
+ providerId: query2.get("providerId") ?? void 0,
5115
+ accountId: query2.get("accountId") ?? void 0
5116
+ });
5117
+ return writeJson3(res, 200, {
5118
+ available: true,
5119
+ entries,
5120
+ collectedAt: Date.now()
5121
+ });
5122
+ }
5046
5123
  if (rest[0] === "allowances") {
5047
5124
  return handleAccountAllowanceApi(
5048
5125
  req,
@@ -5189,8 +5266,13 @@ async function handleAccounts(req, res, method, rest, deps) {
5189
5266
  if (!(listed[providerId] ?? []).some((account) => account.id === accountId)) {
5190
5267
  return writeJsonError(res, 404, `account '${accountId}' not found`);
5191
5268
  }
5192
- const result = await deps.accountProbeService.probeAccount(providerId, accountId);
5193
- return writeJson3(res, 200, { ok: result.ok, marked: result.marked });
5269
+ const result = await deps.accountProbeService.testAccountConnection(providerId, accountId);
5270
+ return writeJson3(res, 200, {
5271
+ ok: result.ok,
5272
+ marked: result.marked,
5273
+ tier: result.tier,
5274
+ model: result.model
5275
+ });
5194
5276
  }
5195
5277
  if (method === "POST" && rest[2] === "label") {
5196
5278
  const accountId = rest[1];
@@ -5561,7 +5643,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
5561
5643
  }
5562
5644
 
5563
5645
  // src/admin/version.ts
5564
- var DAEMON_VERSION = true ? "0.1.6" : "0.0.0-dev";
5646
+ var DAEMON_VERSION = true ? "0.1.7" : "0.0.0-dev";
5565
5647
 
5566
5648
  // src/admin/AdminServer.ts
5567
5649
  var LOOPBACK_ADDR = "127.0.0.1";
@@ -5669,6 +5751,10 @@ var AdminServer = class {
5669
5751
  handleAuditQuery(req, res, this.deps.auditReader);
5670
5752
  return;
5671
5753
  }
5754
+ if (path2 === "/admin/api/audit/stats" && (req.method === "GET" || req.method === "HEAD")) {
5755
+ await handleAuditStatsQuery(req, res, this.deps.auditStatsReader);
5756
+ return;
5757
+ }
5672
5758
  if (path2 === "/admin/api/billing-status" && (req.method === "GET" || req.method === "HEAD")) {
5673
5759
  handleBillingStatus(res, this.deps.billingStatusReader);
5674
5760
  return;
@@ -5785,20 +5871,36 @@ var OAuthSessionStore = class {
5785
5871
  return sessionId;
5786
5872
  }
5787
5873
  /**
5788
- * SINGLE-USE consume: return + delete the session for `sessionId`, or `null`
5789
- * when it is unknown, already used, or past its TTL (in which case it is
5790
- * dropped). A `null` return means the completer must reject (no exchange, no
5791
- * write).
5874
+ * NON-DESTRUCTIVE lookup: return the session for `sessionId`, or `null` when
5875
+ * it is unknown, already consumed, or past its TTL (an expired entry is
5876
+ * dropped here). A `null` return means the completer must reject (no
5877
+ * exchange, no write).
5878
+ *
5879
+ * Deliberately NOT a consume: the completer peeks, runs the token exchange,
5880
+ * and only {@link consume}s once a token has actually been minted. Consuming
5881
+ * up-front burned the session on EVERY failed exchange (a mistyped/expired
5882
+ * pasted code, a proxy hiccup), so the user's natural retry hit
5883
+ * "session is unknown, expired, or already used" and the login became
5884
+ * unrecoverable without restarting the whole flow.
5792
5885
  */
5793
- take(sessionId) {
5886
+ peek(sessionId) {
5794
5887
  this.sweep();
5795
5888
  const session = this.sessions.get(sessionId);
5796
5889
  if (!session) return null;
5797
- this.sessions.delete(sessionId);
5798
- if (Date.now() - session.createdAt > this.ttlMs) return null;
5890
+ if (Date.now() - session.createdAt > this.ttlMs) {
5891
+ this.sessions.delete(sessionId);
5892
+ return null;
5893
+ }
5799
5894
  return session;
5800
5895
  }
5801
- /** Drop every session past its TTL. Called on each put/take. */
5896
+ /**
5897
+ * SINGLE-USE burn: drop the session so the same `sessionId` can never be
5898
+ * completed twice. Called ONLY after a successful token exchange.
5899
+ */
5900
+ consume(sessionId) {
5901
+ this.sessions.delete(sessionId);
5902
+ }
5903
+ /** Drop every session past its TTL. Called on each put/peek. */
5802
5904
  sweep() {
5803
5905
  const now = Date.now();
5804
5906
  for (const [id, session] of this.sessions) {
@@ -5813,6 +5915,10 @@ var LOOPBACK_HOST = "127.0.0.1";
5813
5915
  var LOOPBACK_PORT = 1455;
5814
5916
  var CALLBACK_PATH = "/auth/callback";
5815
5917
  var DEFAULT_TIMEOUT_MS = 5 * 6e4;
5918
+ var HTML_HEADERS = {
5919
+ "Content-Type": "text/html",
5920
+ Connection: "close"
5921
+ };
5816
5922
  function pageHtml(message) {
5817
5923
  return `<!doctype html><meta charset="utf-8"><title>omnicross login</title><body style="font-family:sans-serif;padding:2rem"><h2>${message}</h2><p>You can close this window and return to the terminal.</p></body>`;
5818
5924
  }
@@ -5823,30 +5929,31 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
5823
5929
  if (settled) return;
5824
5930
  settled = true;
5825
5931
  clearTimeout(timer);
5826
- server2.close(() => fn());
5932
+ fn();
5933
+ server2.close();
5827
5934
  };
5828
5935
  const server = createServer((req, res) => {
5829
5936
  const url = new URL(req.url ?? "", `http://${LOOPBACK_HOST}:${LOOPBACK_PORT}`);
5830
5937
  if (url.pathname !== CALLBACK_PATH) {
5831
- res.writeHead(404, { "Content-Type": "text/html" });
5938
+ res.writeHead(404, HTML_HEADERS);
5832
5939
  res.end(pageHtml("Not found"));
5833
5940
  return;
5834
5941
  }
5835
5942
  const code = url.searchParams.get("code");
5836
5943
  const state = url.searchParams.get("state");
5837
5944
  if (!code) {
5838
- res.writeHead(400, { "Content-Type": "text/html" });
5945
+ res.writeHead(400, HTML_HEADERS);
5839
5946
  res.end(pageHtml("Login failed: missing authorization code."));
5840
5947
  finish(server, () => reject(new Error("login: callback did not include an authorization code")));
5841
5948
  return;
5842
5949
  }
5843
5950
  if (state !== expectedState) {
5844
- res.writeHead(400, { "Content-Type": "text/html" });
5951
+ res.writeHead(400, HTML_HEADERS);
5845
5952
  res.end(pageHtml("Login failed: state mismatch."));
5846
5953
  finish(server, () => reject(new Error("login: callback state did not match (possible CSRF) \u2014 aborting")));
5847
5954
  return;
5848
5955
  }
5849
- res.writeHead(200, { "Content-Type": "text/html" });
5956
+ res.writeHead(200, HTML_HEADERS);
5850
5957
  res.end(pageHtml("Login complete."));
5851
5958
  finish(server, () => resolve2(code));
5852
5959
  });
@@ -5943,30 +6050,30 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
5943
6050
  }
5944
6051
 
5945
6052
  // src/commands/paths.ts
5946
- import { dirname as dirname5, join as join5 } from "path";
6053
+ import { dirname as dirname5, join as join4 } from "path";
5947
6054
  function defaultVouchersPath(configPath) {
5948
- return join5(dirname5(configPath), "vouchers.json");
6055
+ return join4(dirname5(configPath), "vouchers.json");
5949
6056
  }
5950
6057
  function defaultIntegrationsPath(configPath) {
5951
- return join5(dirname5(configPath), "integrations.json");
6058
+ return join4(dirname5(configPath), "integrations.json");
5952
6059
  }
5953
6060
  function defaultPricingPath(configPath) {
5954
- return join5(dirname5(configPath), "pricing.json");
6061
+ return join4(dirname5(configPath), "pricing.json");
5955
6062
  }
5956
6063
  function defaultPricingRefreshStatePath(configPath) {
5957
- return join5(dirname5(configPath), "pricing-refresh.json");
6064
+ return join4(dirname5(configPath), "pricing-refresh.json");
5958
6065
  }
5959
6066
  function defaultAccountAllowancePath(configPath) {
5960
- return join5(dirname5(configPath), "allowance-cache.json");
6067
+ return join4(dirname5(configPath), "allowance-cache.json");
5961
6068
  }
5962
6069
  function defaultUsageEventsPath(configPath) {
5963
- return join5(dirname5(configPath), "usage-events.jsonl");
6070
+ return join4(dirname5(configPath), "usage-events.jsonl");
5964
6071
  }
5965
6072
  function defaultAuditDir(configPath) {
5966
- return join5(dirname5(configPath), "audit");
6073
+ return join4(dirname5(configPath), "audit");
5967
6074
  }
5968
6075
  function defaultBillingDir(configPath) {
5969
- return join5(dirname5(configPath), "billing");
6076
+ return join4(dirname5(configPath), "billing");
5970
6077
  }
5971
6078
 
5972
6079
  // src/ports/ConfigFileProviderConfigSource.ts
@@ -6362,8 +6469,12 @@ var JsonlUsageEventStore = class {
6362
6469
  reasoningTokens: 0,
6363
6470
  costUsd: 0,
6364
6471
  costSavedByCacheUsd: 0,
6365
- eventCount: 0
6472
+ eventCount: 0,
6473
+ cacheEligibleEventCount: 0,
6474
+ coldCacheEventCount: 0,
6475
+ medianCacheHitRate: null
6366
6476
  };
6477
+ const perEventHitRates = [];
6367
6478
  for (const row of this.readRows(range)) {
6368
6479
  totals.inputTokens += row.inputTokens;
6369
6480
  totals.outputTokens += row.outputTokens;
@@ -6373,7 +6484,14 @@ var JsonlUsageEventStore = class {
6373
6484
  totals.costUsd += row.costUsd;
6374
6485
  totals.costSavedByCacheUsd += row.costSavedByCacheUsd;
6375
6486
  totals.eventCount += 1;
6487
+ const promptSideTokens = row.inputTokens + row.cacheReadTokens + row.cacheCreationTokens;
6488
+ if (promptSideTokens > 0) {
6489
+ totals.cacheEligibleEventCount += 1;
6490
+ if (row.cacheReadTokens === 0) totals.coldCacheEventCount += 1;
6491
+ perEventHitRates.push(row.cacheReadTokens / promptSideTokens);
6492
+ }
6376
6493
  }
6494
+ totals.medianCacheHitRate = median(perEventHitRates);
6377
6495
  return totals;
6378
6496
  }
6379
6497
  async getByModel(range) {
@@ -6607,6 +6725,15 @@ var NUMERIC_FIELDS = [
6607
6725
  ];
6608
6726
  var NULLABLE_STRING_FIELDS = ["messageId", "parentMessageId", "sessionId", "apiKeyId"];
6609
6727
  var isStringOrNull = (v) => v === null || typeof v === "string";
6728
+ var CACHE_KEY_SOURCES = /* @__PURE__ */ new Set([
6729
+ "client",
6730
+ "session-header",
6731
+ "thread-header",
6732
+ "body-session-id",
6733
+ "body-thread-id",
6734
+ "content-fingerprint",
6735
+ "none"
6736
+ ]);
6610
6737
  function isUsageEventRecord(parsed) {
6611
6738
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
6612
6739
  const r = parsed;
@@ -6614,6 +6741,10 @@ function isUsageEventRecord(parsed) {
6614
6741
  if (typeof r["providerId"] !== "string") return false;
6615
6742
  if (typeof r["model"] !== "string") return false;
6616
6743
  if (typeof r["engineOrigin"] !== "string") return false;
6744
+ if (r["cacheKeySource"] !== void 0 && (typeof r["cacheKeySource"] !== "string" || !CACHE_KEY_SOURCES.has(r["cacheKeySource"]))) return false;
6745
+ if (r["cacheKeyInjected"] !== void 0 && typeof r["cacheKeyInjected"] !== "boolean") {
6746
+ return false;
6747
+ }
6617
6748
  for (const f of NULLABLE_STRING_FIELDS) {
6618
6749
  if (!isStringOrNull(r[f])) return false;
6619
6750
  }
@@ -6623,14 +6754,30 @@ function isUsageEventRecord(parsed) {
6623
6754
  }
6624
6755
  return true;
6625
6756
  }
6757
+ function median(values) {
6758
+ if (values.length === 0) return null;
6759
+ values.sort((a, b) => a - b);
6760
+ const middle = Math.floor(values.length / 2);
6761
+ return values.length % 2 === 1 ? values[middle] : (values[middle - 1] + values[middle]) / 2;
6762
+ }
6626
6763
 
6627
6764
  // src/ports/JsonOutboundKeyDb.ts
6628
6765
  import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
6629
6766
  var JsonOutboundKeyDb = class {
6630
- constructor(keysPath) {
6767
+ /**
6768
+ * @param secretBox OPTIONAL reversible-secret codec. When present, a created
6769
+ * key's plaintext is persisted as a `keySecret` `enc:` envelope (enabling the
6770
+ * operator "view key" affordance via `outboundApiKeysReveal`). When absent the
6771
+ * store stays hash-only (byte-identical to the legacy behavior) and reveal
6772
+ * always returns `null`. Existing 1-arg call sites (tests, lightweight
6773
+ * embedders) keep working.
6774
+ */
6775
+ constructor(keysPath, secretBox3) {
6631
6776
  this.keysPath = keysPath;
6777
+ this.secretBox = secretBox3;
6632
6778
  }
6633
6779
  keysPath;
6780
+ secretBox;
6634
6781
  async outboundApiKeysList() {
6635
6782
  return this.readRows();
6636
6783
  }
@@ -6656,10 +6803,27 @@ var JsonOutboundKeyDb = class {
6656
6803
  allowedEndpoints: input.allowedEndpoints,
6657
6804
  loopbackOnly: input.loopbackOnly
6658
6805
  };
6806
+ if (input.plaintext && this.secretBox) {
6807
+ row.keySecret = this.secretBox.encrypt(input.plaintext);
6808
+ }
6659
6809
  rows.push(row);
6660
6810
  this.writeRows(rows);
6661
6811
  return row;
6662
6812
  }
6813
+ async outboundApiKeysReveal(id) {
6814
+ const rows = this.readRows();
6815
+ const row = rows.find((r) => r.id === id);
6816
+ if (!row || !row.keySecret || !this.secretBox) return null;
6817
+ return this.secretBox.decrypt(row.keySecret);
6818
+ }
6819
+ async outboundApiKeysDelete(id) {
6820
+ const rows = this.readRows();
6821
+ const idx = rows.findIndex((r) => r.id === id);
6822
+ if (idx < 0) return false;
6823
+ rows.splice(idx, 1);
6824
+ this.writeRows(rows);
6825
+ return true;
6826
+ }
6663
6827
  async outboundApiKeysRevoke(id) {
6664
6828
  return this.mutateRow(id, (row) => {
6665
6829
  if (row.revokedAt !== null) return false;
@@ -7163,9 +7327,9 @@ function findDuplicateCredentialIds(accounts) {
7163
7327
  // src/ports/external-cli-credentials.ts
7164
7328
  import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
7165
7329
  import { homedir as homedir3 } from "os";
7166
- import { join as join6 } from "path";
7330
+ import { join as join5 } from "path";
7167
7331
  function externalStorePath(provider, home = homedir3()) {
7168
- return provider === "claude" ? join6(home, ".claude", ".credentials.json") : join6(home, ".codex", "auth.json");
7332
+ return provider === "claude" ? join5(home, ".claude", ".credentials.json") : join5(home, ".codex", "auth.json");
7169
7333
  }
7170
7334
  function decodeJwtExpiryMs(token) {
7171
7335
  try {
@@ -7255,9 +7419,15 @@ var JsonSubscriptionCredentialStore = class {
7255
7419
  * TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
7256
7420
  * through {@link fetchUpstream} with the account's `{ providerId, accountId }`
7257
7421
  * ctx so the per-account/provider proxy applies. `@internal` also a test seam.
7422
+ *
7423
+ * `redactBodies` is REQUIRED here: this round-trip sends the refresh_token and
7424
+ * receives a fresh access/refresh token pair. Carrying a `providerId` opts the
7425
+ * call into the upstream trace (so a failing refresh is diagnosable), and the
7426
+ * trace captures bodies verbatim — without this flag every refresh would write
7427
+ * a plaintext token pair into `upstream-trace.jsonl`.
7258
7428
  */
7259
7429
  buildRefreshFetch(providerId, accountId) {
7260
- return this.fetchImpl ?? ((url, init) => fetchUpstream3(url, init, { providerId, accountId }));
7430
+ return this.fetchImpl ?? ((url, init) => fetchUpstream3(url, init, { providerId, accountId, redactBodies: true }));
7261
7431
  }
7262
7432
  /**
7263
7433
  * In-flight refresh coalescing. OAuth refresh tokens are
@@ -7817,6 +7987,127 @@ var JsonSubscriptionCredentialStore = class {
7817
7987
  // src/AccountHealthProbeScheduler.ts
7818
7988
  import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
7819
7989
 
7990
+ // src/probe/CodexGenerationProbe.ts
7991
+ import {
7992
+ DEFAULT_CODEX_CLI_HEADERS,
7993
+ codexAcceptHeader
7994
+ } from "@omnicross/core/provider-proxy/identity/codexCliHeaders";
7995
+ var CODEX_GENERATION_PROBE_MODEL = "gpt-5.6-luna";
7996
+ var CODEX_GENERATION_PROBE_URL = "https://chatgpt.com/backend-api/codex/responses";
7997
+ var MAX_STREAM_BYTES = 256 * 1024;
7998
+ var PROBE_INSTRUCTION = "Return exactly PONG and no other text.";
7999
+ function buildCodexGenerationProbeInit(token, signal) {
8000
+ return {
8001
+ method: "POST",
8002
+ signal,
8003
+ headers: {
8004
+ ...DEFAULT_CODEX_CLI_HEADERS,
8005
+ Authorization: `Bearer ${token}`,
8006
+ Accept: codexAcceptHeader(true),
8007
+ "Content-Type": "application/json"
8008
+ },
8009
+ body: JSON.stringify({
8010
+ model: CODEX_GENERATION_PROBE_MODEL,
8011
+ input: [
8012
+ {
8013
+ role: "developer",
8014
+ content: [{ type: "input_text", text: PROBE_INSTRUCTION }]
8015
+ },
8016
+ {
8017
+ role: "user",
8018
+ content: [{ type: "input_text", text: "Connection probe." }]
8019
+ }
8020
+ ],
8021
+ // GPT-5.6 otherwise defaults to medium reasoning. A connectivity probe
8022
+ // needs the lowest-cost path and no tool reasoning.
8023
+ reasoning: { effort: "none" },
8024
+ stream: true,
8025
+ store: false
8026
+ })
8027
+ };
8028
+ }
8029
+ async function readCodexGenerationProbeStream(response) {
8030
+ if (!response.body) return { completed: false, outputChars: 0 };
8031
+ const reader = response.body.getReader();
8032
+ const decoder = new TextDecoder();
8033
+ let buffer = "";
8034
+ let bytes = 0;
8035
+ let outputChars = 0;
8036
+ try {
8037
+ while (true) {
8038
+ const { done, value } = await reader.read();
8039
+ if (done) break;
8040
+ bytes += value.byteLength;
8041
+ if (bytes > MAX_STREAM_BYTES) {
8042
+ await reader.cancel();
8043
+ return { completed: false, outputChars };
8044
+ }
8045
+ buffer += decoder.decode(value, { stream: true });
8046
+ buffer = buffer.replace(/\r\n/g, "\n");
8047
+ let boundary = buffer.indexOf("\n\n");
8048
+ while (boundary >= 0) {
8049
+ const block = buffer.slice(0, boundary);
8050
+ buffer = buffer.slice(boundary + 2);
8051
+ const event = parseSseBlock(block);
8052
+ if (event) {
8053
+ const type = event["type"];
8054
+ if (type === "response.output_text.delta" && typeof event["delta"] === "string") {
8055
+ outputChars += event["delta"].length;
8056
+ } else if (type === "response.output_text.done" && typeof event["text"] === "string") {
8057
+ outputChars = Math.max(outputChars, event["text"].length);
8058
+ } else if (type === "response.failed" || type === "error") {
8059
+ await reader.cancel();
8060
+ return { completed: false, outputChars };
8061
+ } else if (type === "response.completed") {
8062
+ const completedResponse = asRecord(event["response"]);
8063
+ const status = completedResponse?.["status"];
8064
+ outputChars = Math.max(outputChars, countCompletedOutputChars(completedResponse));
8065
+ await reader.cancel();
8066
+ return {
8067
+ completed: (status === void 0 || status === "completed") && outputChars > 0,
8068
+ outputChars
8069
+ };
8070
+ }
8071
+ }
8072
+ boundary = buffer.indexOf("\n\n");
8073
+ }
8074
+ }
8075
+ } catch {
8076
+ return { completed: false, outputChars };
8077
+ } finally {
8078
+ reader.releaseLock();
8079
+ }
8080
+ return { completed: false, outputChars };
8081
+ }
8082
+ function parseSseBlock(block) {
8083
+ const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
8084
+ if (!data || data === "[DONE]") return null;
8085
+ try {
8086
+ return JSON.parse(data);
8087
+ } catch {
8088
+ return null;
8089
+ }
8090
+ }
8091
+ function asRecord(value) {
8092
+ return value !== null && typeof value === "object" ? value : void 0;
8093
+ }
8094
+ function countCompletedOutputChars(response) {
8095
+ const output = response?.["output"];
8096
+ if (!Array.isArray(output)) return 0;
8097
+ let chars = 0;
8098
+ for (const item of output) {
8099
+ const content = asRecord(item)?.["content"];
8100
+ if (!Array.isArray(content)) continue;
8101
+ for (const part of content) {
8102
+ const record = asRecord(part);
8103
+ if (record?.["type"] === "output_text" && typeof record["text"] === "string") {
8104
+ chars += record["text"].length;
8105
+ }
8106
+ }
8107
+ }
8108
+ return chars;
8109
+ }
8110
+
7820
8111
  // src/probe/ProbeStrategy.ts
7821
8112
  var PROVIDER_PROBE_PLANS = {
7822
8113
  claude: {
@@ -7944,17 +8235,17 @@ var AccountHealthProbeScheduler = class {
7944
8235
  }
7945
8236
  if (readThrew) {
7946
8237
  this.record(providerId, accountId, { ts: now, ok: false, status: null, tier: "local" });
7947
- return { ok: false, marked: false };
8238
+ return { ok: false, marked: false, tier: "local" };
7948
8239
  }
7949
8240
  if (!token) {
7950
8241
  this.health.recordUpstreamOutcome(providerId, accountId, { status: 401, now });
7951
8242
  this.record(providerId, accountId, { ts: now, ok: false, status: 401, tier: "local" });
7952
- return { ok: false, marked: true };
8243
+ return { ok: false, marked: true, tier: "local" };
7953
8244
  }
7954
8245
  const plan = this.planFor(providerId);
7955
8246
  if (plan.kind === "local") {
7956
8247
  this.record(providerId, accountId, { ts: now, ok: true, tier: "local" });
7957
- return { ok: true, marked: false };
8248
+ return { ok: true, marked: false, tier: "local" };
7958
8249
  }
7959
8250
  const start = this.now();
7960
8251
  let status = null;
@@ -7979,7 +8270,60 @@ var AccountHealthProbeScheduler = class {
7979
8270
  latencyMs,
7980
8271
  tier: "upstream"
7981
8272
  });
7982
- return { ok: status !== null && status < 400, marked };
8273
+ return { ok: status !== null && status < 400, marked, tier: "upstream" };
8274
+ }
8275
+ /**
8276
+ * Manual connection test. Codex performs a real, quota-consuming generation;
8277
+ * every other provider keeps its existing cheap probe. Scheduled sweeps never
8278
+ * call this method, so they remain non-billable.
8279
+ */
8280
+ async testAccountConnection(providerId, accountId) {
8281
+ if (providerId !== "codex") return this.probeAccount(providerId, accountId);
8282
+ const now = this.now();
8283
+ let token;
8284
+ try {
8285
+ token = await this.store.getAccessTokenForAccount(providerId, accountId);
8286
+ } catch {
8287
+ this.record(providerId, accountId, { ts: now, ok: false, status: null, tier: "local" });
8288
+ return { ok: false, marked: false, tier: "local", model: CODEX_GENERATION_PROBE_MODEL };
8289
+ }
8290
+ if (!token) {
8291
+ this.health.recordUpstreamOutcome(providerId, accountId, { status: 401, now });
8292
+ this.record(providerId, accountId, { ts: now, ok: false, status: 401, tier: "local" });
8293
+ return { ok: false, marked: true, tier: "local", model: CODEX_GENERATION_PROBE_MODEL };
8294
+ }
8295
+ const startedAt = this.now();
8296
+ let attempt = await this.runCodexGenerationAttempt(accountId, token);
8297
+ if (attempt.status === 401 && this.store.refreshAccountToken) {
8298
+ try {
8299
+ if (await this.store.refreshAccountToken(providerId, accountId)) {
8300
+ const refreshed = await this.store.getAccessTokenForAccount(providerId, accountId);
8301
+ if (refreshed) attempt = await this.runCodexGenerationAttempt(accountId, refreshed);
8302
+ }
8303
+ } catch {
8304
+ }
8305
+ }
8306
+ const latencyMs = this.now() - startedAt;
8307
+ const ok = attempt.status !== null && attempt.status >= 200 && attempt.status < 300 && attempt.completed;
8308
+ let marked = false;
8309
+ if (ok) {
8310
+ this.health.clearTransientMark(providerId, accountId);
8311
+ } else if (attempt.status === 401 || attempt.status === 403) {
8312
+ marked = this.applyOutcome(providerId, accountId, attempt.status, attempt.bodyText, now);
8313
+ }
8314
+ this.record(providerId, accountId, {
8315
+ ts: now,
8316
+ ok,
8317
+ status: attempt.status,
8318
+ latencyMs,
8319
+ tier: "generation"
8320
+ });
8321
+ return {
8322
+ ok,
8323
+ marked,
8324
+ tier: "generation",
8325
+ model: CODEX_GENERATION_PROBE_MODEL
8326
+ };
7983
8327
  }
7984
8328
  /** Per-account rolling history for the authed admin surface (design D5). */
7985
8329
  getAllHistory() {
@@ -8036,6 +8380,24 @@ var AccountHealthProbeScheduler = class {
8036
8380
  return "";
8037
8381
  }
8038
8382
  }
8383
+ async runCodexGenerationAttempt(accountId, token) {
8384
+ try {
8385
+ const timeoutMs = Math.max(this.config.timeoutMs, 15e3);
8386
+ const response = await this.fetchImpl(
8387
+ CODEX_GENERATION_PROBE_URL,
8388
+ buildCodexGenerationProbeInit(token, AbortSignal.timeout(timeoutMs)),
8389
+ { providerId: "codex", accountId, redactBodies: true }
8390
+ );
8391
+ if (response.status < 200 || response.status >= 300) {
8392
+ const bodyText = response.status === 403 ? await this.readBounded(response) : void 0;
8393
+ return { status: response.status, completed: false, bodyText };
8394
+ }
8395
+ const stream = await readCodexGenerationProbeStream(response);
8396
+ return { status: response.status, completed: stream.completed };
8397
+ } catch {
8398
+ return { status: null, completed: false };
8399
+ }
8400
+ }
8039
8401
  key(providerId, accountId) {
8040
8402
  return `${providerId}${KEY_SEP}${accountId}`;
8041
8403
  }
@@ -8131,7 +8493,7 @@ var AccountHealthSweeper = class {
8131
8493
  };
8132
8494
 
8133
8495
  // src/audit/AuditPruneSweeper.ts
8134
- import { existsSync as existsSync14, readdirSync, unlinkSync as unlinkSync3 } from "fs";
8496
+ import { existsSync as existsSync15, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
8135
8497
  import { join as join7 } from "path";
8136
8498
 
8137
8499
  // src/audit/auditFiles.ts
@@ -8154,12 +8516,213 @@ function auditFileDateMs(fileName) {
8154
8516
  return d.getTime();
8155
8517
  }
8156
8518
 
8519
+ // src/audit/auditStats.ts
8520
+ import {
8521
+ createReadStream,
8522
+ existsSync as existsSync14,
8523
+ readFileSync as readFileSync14,
8524
+ readdirSync,
8525
+ statSync as statSync3,
8526
+ writeFileSync as writeFileSync11
8527
+ } from "fs";
8528
+ import { basename, dirname as dirname7, join as join6 } from "path";
8529
+ var SIDECAR_VERSION = 1;
8530
+ var META_PREFIX_BYTES = 64 * 1024;
8531
+ var READ_CHUNK_BYTES = 4 * 1024 * 1024;
8532
+ function auditStatsFileName(auditFile) {
8533
+ return auditFile.replace(/\.jsonl$/, ".stats.json");
8534
+ }
8535
+ function readPersisted(path2) {
8536
+ if (!existsSync14(path2)) return null;
8537
+ try {
8538
+ const value = JSON.parse(readFileSync14(path2, "utf8"));
8539
+ if (value.version !== SIDECAR_VERSION || !Number.isSafeInteger(value.auditBytes) || (value.auditBytes ?? -1) < 0 || !Number.isSafeInteger(value.requestCount) || (value.requestCount ?? -1) < 0 || !Number.isSafeInteger(value.errorCount) || (value.errorCount ?? -1) < 0 || (value.errorCount ?? 0) > (value.requestCount ?? -1) || typeof value.complete !== "boolean" || value.minTs !== null && !Number.isFinite(value.minTs) || value.maxTs !== null && !Number.isFinite(value.maxTs)) {
8540
+ return null;
8541
+ }
8542
+ return value;
8543
+ } catch {
8544
+ return null;
8545
+ }
8546
+ }
8547
+ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfter, record) {
8548
+ const statsPath = join6(dirname7(auditPath), auditStatsFileName(basename(auditPath)));
8549
+ const previous = auditBytesBefore === 0 ? {
8550
+ version: SIDECAR_VERSION,
8551
+ auditBytes: 0,
8552
+ requestCount: 0,
8553
+ errorCount: 0,
8554
+ complete: true,
8555
+ minTs: null,
8556
+ maxTs: null
8557
+ } : readPersisted(statsPath);
8558
+ if (!previous || !previous.complete || previous.auditBytes !== auditBytesBefore) return;
8559
+ const next = {
8560
+ version: SIDECAR_VERSION,
8561
+ auditBytes: auditBytesAfter,
8562
+ requestCount: previous.requestCount + 1,
8563
+ errorCount: previous.errorCount + (record.status >= 400 || Boolean(record.error) ? 1 : 0),
8564
+ complete: true,
8565
+ minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
8566
+ maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
8567
+ };
8568
+ writeFileSync11(statsPath, JSON.stringify(next), "utf8");
8569
+ }
8570
+ function queryCovers(stats, from, to) {
8571
+ return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
8572
+ }
8573
+ function fileOverlaps(file, from, to) {
8574
+ const start = auditFileDateMs(file);
8575
+ if (start === null) return false;
8576
+ const date = new Date(start);
8577
+ const end = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1).getTime();
8578
+ return end > from && start <= to;
8579
+ }
8580
+ function parseMetadataPrefix(prefix, prefixTruncated) {
8581
+ const text = prefix.toString("utf8");
8582
+ const tsMatch = /(?:^|,)"ts":(-?\d+)/.exec(text);
8583
+ const statusMatch = /(?:^|,)"status":(-?\d+)/.exec(text);
8584
+ const errorMatch = /(?:^|,)"error":"((?:\\.|[^"\\])*)"/.exec(text);
8585
+ const bodyStarted = /,(?:"requestBody"|"responseBody"):/.test(text);
8586
+ return {
8587
+ ts: tsMatch ? Number(tsMatch[1]) : void 0,
8588
+ status: statusMatch ? Number(statusMatch[1]) : void 0,
8589
+ hasError: Boolean(errorMatch?.[1]),
8590
+ complete: Boolean(tsMatch && statusMatch && (!prefixTruncated || bodyStarted))
8591
+ };
8592
+ }
8593
+ async function scanAuditFile(auditPath, startByte, auditBytes, from, to) {
8594
+ let requestCount = 0;
8595
+ let errorCount = 0;
8596
+ let filteredRequestCount = 0;
8597
+ let filteredErrorCount = 0;
8598
+ let minTs = null;
8599
+ let maxTs = null;
8600
+ let complete = true;
8601
+ let prefixParts = [];
8602
+ let prefixBytes = 0;
8603
+ let prefixTruncated = false;
8604
+ const consumeLine = () => {
8605
+ if (prefixBytes === 0 && !prefixTruncated) return;
8606
+ const prefix = Buffer.concat(prefixParts, prefixBytes);
8607
+ const metadata = parseMetadataPrefix(prefix, prefixTruncated);
8608
+ if (!metadata.complete || metadata.ts === void 0 || metadata.status === void 0) {
8609
+ complete = false;
8610
+ } else {
8611
+ requestCount += 1;
8612
+ const isError = metadata.status >= 400 || metadata.hasError;
8613
+ if (isError) errorCount += 1;
8614
+ minTs = minTs === null ? metadata.ts : Math.min(minTs, metadata.ts);
8615
+ maxTs = maxTs === null ? metadata.ts : Math.max(maxTs, metadata.ts);
8616
+ if (metadata.ts >= from && metadata.ts <= to) {
8617
+ filteredRequestCount += 1;
8618
+ if (isError) filteredErrorCount += 1;
8619
+ }
8620
+ }
8621
+ prefixParts = [];
8622
+ prefixBytes = 0;
8623
+ prefixTruncated = false;
8624
+ };
8625
+ if (auditBytes > startByte) {
8626
+ const stream = createReadStream(auditPath, {
8627
+ start: startByte,
8628
+ end: auditBytes - 1,
8629
+ highWaterMark: READ_CHUNK_BYTES
8630
+ });
8631
+ for await (const value of stream) {
8632
+ const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
8633
+ let offset = 0;
8634
+ while (offset < chunk.length) {
8635
+ const newline = chunk.indexOf(10, offset);
8636
+ const end = newline === -1 ? chunk.length : newline;
8637
+ if (prefixBytes < META_PREFIX_BYTES) {
8638
+ const retained = Math.min(META_PREFIX_BYTES - prefixBytes, end - offset);
8639
+ if (retained > 0) {
8640
+ prefixParts.push(Buffer.from(chunk.subarray(offset, offset + retained)));
8641
+ prefixBytes += retained;
8642
+ }
8643
+ if (retained < end - offset) prefixTruncated = true;
8644
+ } else if (end > offset) {
8645
+ prefixTruncated = true;
8646
+ }
8647
+ if (newline === -1) break;
8648
+ consumeLine();
8649
+ offset = newline + 1;
8650
+ }
8651
+ }
8652
+ }
8653
+ if (prefixBytes > 0 || prefixTruncated) complete = false;
8654
+ return {
8655
+ all: {
8656
+ version: SIDECAR_VERSION,
8657
+ auditBytes,
8658
+ requestCount,
8659
+ errorCount,
8660
+ complete,
8661
+ minTs,
8662
+ maxTs
8663
+ },
8664
+ filtered: { requestCount: filteredRequestCount, errorCount: filteredErrorCount, complete }
8665
+ };
8666
+ }
8667
+ function mergePersistedStats(previous, appended) {
8668
+ return {
8669
+ version: SIDECAR_VERSION,
8670
+ auditBytes: appended.auditBytes,
8671
+ requestCount: previous.requestCount + appended.requestCount,
8672
+ errorCount: previous.errorCount + appended.errorCount,
8673
+ complete: previous.complete && appended.complete,
8674
+ minTs: previous.minTs === null ? appended.minTs : appended.minTs === null ? previous.minTs : Math.min(previous.minTs, appended.minTs),
8675
+ maxTs: previous.maxTs === null ? appended.maxTs : appended.maxTs === null ? previous.maxTs : Math.max(previous.maxTs, appended.maxTs)
8676
+ };
8677
+ }
8678
+ async function readAuditStats(auditDir, query2 = {}) {
8679
+ if (!existsSync14(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
8680
+ const from = typeof query2.from === "number" ? query2.from : -Infinity;
8681
+ const to = typeof query2.to === "number" ? query2.to : Infinity;
8682
+ let files;
8683
+ try {
8684
+ files = readdirSync(auditDir).filter((file) => AUDIT_FILE_RE.test(file) && fileOverlaps(file, from, to)).sort();
8685
+ } catch {
8686
+ return { requestCount: 0, errorCount: 0, complete: false };
8687
+ }
8688
+ const total = { requestCount: 0, errorCount: 0, complete: true };
8689
+ for (const file of files) {
8690
+ const auditPath = join6(auditDir, file);
8691
+ try {
8692
+ const auditBytes = statSync3(auditPath).size;
8693
+ const statsPath = join6(auditDir, auditStatsFileName(file));
8694
+ const persisted = readPersisted(statsPath);
8695
+ if (persisted && persisted.complete && persisted.auditBytes === auditBytes && queryCovers(persisted, from, to)) {
8696
+ total.requestCount += persisted.requestCount;
8697
+ total.errorCount += persisted.errorCount;
8698
+ continue;
8699
+ }
8700
+ const resumable = persisted && persisted.complete && persisted.auditBytes < auditBytes && queryCovers(persisted, from, to) ? persisted : null;
8701
+ const scanned = await scanAuditFile(
8702
+ auditPath,
8703
+ resumable?.auditBytes ?? 0,
8704
+ auditBytes,
8705
+ from,
8706
+ to
8707
+ );
8708
+ total.requestCount += scanned.filtered.requestCount + (resumable?.requestCount ?? 0);
8709
+ total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
8710
+ total.complete = total.complete && scanned.filtered.complete;
8711
+ const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
8712
+ if (current.complete) writeFileSync11(statsPath, JSON.stringify(current), "utf8");
8713
+ } catch {
8714
+ total.complete = false;
8715
+ }
8716
+ }
8717
+ return total;
8718
+ }
8719
+
8157
8720
  // src/audit/AuditPruneSweeper.ts
8158
8721
  var DAY_MS = 24 * 60 * 6e4;
8159
8722
  var SWEEP_INTERVAL_MS2 = 60 * 6e4;
8160
8723
  var AuditPruneSweeper = class {
8161
- constructor(auditDir2, logger, config, intervalMs = SWEEP_INTERVAL_MS2, now = Date.now) {
8162
- this.auditDir = auditDir2;
8724
+ constructor(auditDir, logger, config, intervalMs = SWEEP_INTERVAL_MS2, now = Date.now) {
8725
+ this.auditDir = auditDir;
8163
8726
  this.logger = logger;
8164
8727
  this.config = config;
8165
8728
  this.intervalMs = intervalMs;
@@ -8206,17 +8769,19 @@ var AuditPruneSweeper = class {
8206
8769
  if (!this.config.enabled || this.sweeping) return 0;
8207
8770
  this.sweeping = true;
8208
8771
  try {
8209
- if (!existsSync14(this.auditDir)) return 0;
8772
+ if (!existsSync15(this.auditDir)) return 0;
8210
8773
  const today = new Date(this.now());
8211
8774
  const todayMidnight = new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime();
8212
8775
  const cutoff = todayMidnight - (this.config.retentionDays - 1) * DAY_MS;
8213
8776
  let removed = 0;
8214
- for (const file of readdirSync(this.auditDir)) {
8777
+ for (const file of readdirSync2(this.auditDir)) {
8215
8778
  const dateMs = auditFileDateMs(file);
8216
8779
  if (dateMs === null || dateMs >= cutoff) continue;
8217
8780
  try {
8218
8781
  unlinkSync3(join7(this.auditDir, file));
8219
8782
  removed += 1;
8783
+ const statsPath = join7(this.auditDir, auditStatsFileName(file));
8784
+ if (existsSync15(statsPath)) unlinkSync3(statsPath);
8220
8785
  } catch (error) {
8221
8786
  this.logger.warn("[AuditPruneSweeper] failed to unlink expired audit file", {
8222
8787
  file,
@@ -8238,15 +8803,15 @@ var AuditPruneSweeper = class {
8238
8803
  };
8239
8804
 
8240
8805
  // src/audit/auditReader.ts
8241
- import { existsSync as existsSync15, readdirSync as readdirSync2, readFileSync as readFileSync14 } from "fs";
8806
+ import { existsSync as existsSync16, readdirSync as readdirSync3, readFileSync as readFileSync15 } from "fs";
8242
8807
  import { join as join8 } from "path";
8243
8808
  var DEFAULT_LIMIT = 200;
8244
8809
  var MAX_LIMIT = 2e3;
8245
- function readAuditRecords(auditDir2, query2 = {}) {
8246
- if (!existsSync15(auditDir2)) return [];
8810
+ function readAuditRecords(auditDir, query2 = {}) {
8811
+ if (!existsSync16(auditDir)) return [];
8247
8812
  let files;
8248
8813
  try {
8249
- files = readdirSync2(auditDir2).filter((f) => AUDIT_FILE_RE.test(f));
8814
+ files = readdirSync3(auditDir).filter((f) => AUDIT_FILE_RE.test(f));
8250
8815
  } catch {
8251
8816
  return [];
8252
8817
  }
@@ -8257,7 +8822,7 @@ function readAuditRecords(auditDir2, query2 = {}) {
8257
8822
  for (const file of files.sort().reverse()) {
8258
8823
  let raw;
8259
8824
  try {
8260
- raw = readFileSync14(join8(auditDir2, file), "utf8");
8825
+ raw = readFileSync15(join8(auditDir, file), "utf8");
8261
8826
  } catch {
8262
8827
  continue;
8263
8828
  }
@@ -8286,11 +8851,11 @@ function isAuditRecord(value) {
8286
8851
  }
8287
8852
 
8288
8853
  // src/audit/AuditWriter.ts
8289
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5 } from "fs";
8854
+ import { appendFileSync as appendFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync5, statSync as statSync4 } from "fs";
8290
8855
  import { join as join9 } from "path";
8291
8856
  var AuditWriter = class {
8292
- constructor(auditDir2, logger, defer = (fn) => setTimeout(fn, 0)) {
8293
- this.auditDir = auditDir2;
8857
+ constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
8858
+ this.auditDir = auditDir;
8294
8859
  this.logger = logger;
8295
8860
  this.defer = defer;
8296
8861
  }
@@ -8324,7 +8889,21 @@ var AuditWriter = class {
8324
8889
  this.dirEnsured = true;
8325
8890
  }
8326
8891
  const file = join9(this.auditDir, auditFileName(record.ts));
8327
- appendFileSync2(file, JSON.stringify(record) + "\n", "utf8");
8892
+ const line = JSON.stringify(record) + "\n";
8893
+ const auditBytesBefore = existsSync17(file) ? statSync4(file).size : 0;
8894
+ appendFileSync2(file, line, "utf8");
8895
+ try {
8896
+ updateAuditStatsAfterAppend(
8897
+ file,
8898
+ auditBytesBefore,
8899
+ auditBytesBefore + Buffer.byteLength(line, "utf8"),
8900
+ record
8901
+ );
8902
+ } catch (error) {
8903
+ this.logger.warn("[AuditWriter] failed to update audit stats", {
8904
+ error: error instanceof Error ? error.message : String(error)
8905
+ });
8906
+ }
8328
8907
  }
8329
8908
  };
8330
8909
 
@@ -8468,14 +9047,14 @@ var BillingPublisher = class {
8468
9047
  };
8469
9048
 
8470
9049
  // src/billing/billingReader.ts
8471
- import { existsSync as existsSync16, readdirSync as readdirSync3, readFileSync as readFileSync15 } from "fs";
9050
+ import { existsSync as existsSync18, readdirSync as readdirSync4, readFileSync as readFileSync16 } from "fs";
8472
9051
  import { join as join11 } from "path";
8473
9052
  function readBillingLedger(billingDir) {
8474
9053
  const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
8475
- if (!existsSync16(billingDir)) return view;
9054
+ if (!existsSync18(billingDir)) return view;
8476
9055
  let files;
8477
9056
  try {
8478
- files = readdirSync3(billingDir);
9057
+ files = readdirSync4(billingDir);
8479
9058
  } catch {
8480
9059
  return view;
8481
9060
  }
@@ -8506,7 +9085,7 @@ function readBillingStatus(billingDir) {
8506
9085
  function parseLines(dir, file) {
8507
9086
  let raw;
8508
9087
  try {
8509
- raw = readFileSync15(join11(dir, file), "utf8");
9088
+ raw = readFileSync16(join11(dir, file), "utf8");
8510
9089
  } catch {
8511
9090
  return [];
8512
9091
  }
@@ -8878,7 +9457,7 @@ function buildDaemon(config, paths) {
8878
9457
  normalizeServerConfig(decryptedConfig.server).allowanceScheduling
8879
9458
  );
8880
9459
  const llmConfig = new ConfigFileProviderConfigSource(decryptedConfig);
8881
- const keyDb = new JsonOutboundKeyDb(paths.keysPath);
9460
+ const keyDb = new JsonOutboundKeyDb(paths.keysPath, secretBox3);
8882
9461
  const voucherDb = new JsonVoucherDb(defaultVouchersPath(paths.configPath));
8883
9462
  const settingsStore = new JsonApiServerSettingsStore(paths.configPath, secretBox3);
8884
9463
  const integrationStateStore = new IntegrationStateStore(
@@ -8979,7 +9558,7 @@ function buildDaemon(config, paths) {
8979
9558
  // lines through the injected logger (honors level/format/file sink).
8980
9559
  logger
8981
9560
  });
8982
- const auditDir2 = defaultAuditDir(paths.configPath);
9561
+ const auditDir = defaultAuditDir(paths.configPath);
8983
9562
  const billingDir = defaultBillingDir(paths.configPath);
8984
9563
  const adminServer = new AdminServer({
8985
9564
  configPath: paths.configPath,
@@ -9013,10 +9592,16 @@ function buildDaemon(config, paths) {
9013
9592
  // (NOT widening the least-authority writer — no token-returning read reachable).
9014
9593
  oauthSessions: new OAuthSessionStore(),
9015
9594
  // Real global fetch by default; a test seam (`paths.oauthExchangeFetch`) can
9016
- // inject a mock so no real token endpoint is hit.
9017
- // upstream-proxy: default the OAuth token-exchange fetch to the proxy-aware
9018
- // helper so interactive login honors a configured proxy (global/env layers).
9019
- oauthExchangeFetch: paths.oauthExchangeFetch ?? ((url, init) => fetchUpstream7(url, init)),
9595
+ // inject a mock so no real token endpoint is hit (one FetchLike for every
9596
+ // provider the ctx below only matters on the real egress path).
9597
+ //
9598
+ // upstream-proxy: a PER-PROVIDER factory, so the exchange carries the same
9599
+ // `{ providerId }` ctx the CLI login and the token refresh already pass.
9600
+ // Without it the interactive login resolved only the global/env proxy layers
9601
+ // — `server.proxy.byProvider[...]` was silently skipped — and the call was
9602
+ // excluded from the upstream trace, so a failing login left no evidence.
9603
+ // `redactBodies` keeps the code/verifier + minted token out of that trace.
9604
+ oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream7(url, init, { providerId, redactBodies: true }),
9020
9605
  subscriptionAccountAppender: credentialStore,
9021
9606
  // Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
9022
9607
  // + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
@@ -9068,7 +9653,8 @@ function buildDaemon(config, paths) {
9068
9653
  // date-rotated audit store. Bound to the store dir here so the AdminServer
9069
9654
  // carries no path/store coupling. Records hold IP/UA/bodies → admin-only,
9070
9655
  // NEVER unauth, NEVER on `/health`. Routed in `AdminServer` (not `adminApi.ts`).
9071
- auditReader: (query2) => readAuditRecords(auditDir2, query2),
9656
+ auditReader: (query2) => readAuditRecords(auditDir, query2),
9657
+ auditStatsReader: (query2) => readAuditStats(auditDir, query2),
9072
9658
  // billing-event-stream: the AUTHED `GET /admin/api/billing-status` returns the
9073
9659
  // secret-free total/delivered/pending counts of the durable ledger.
9074
9660
  billingStatusReader: () => readBillingStatus(billingDir)
@@ -9078,9 +9664,9 @@ function buildDaemon(config, paths) {
9078
9664
  fetchImpl: (url, init) => fetchUpstream7(url, init)
9079
9665
  });
9080
9666
  setWebhookRuntime(webhookDispatcher, getSharedAccountHealth3());
9081
- const auditWriter = new AuditWriter(auditDir2, logger);
9082
- const auditPruneSweeper = new AuditPruneSweeper(auditDir2, logger, DEFAULT_AUDIT_CONFIG);
9083
- setAuditRuntime(auditWriter, auditPruneSweeper, auditDir2);
9667
+ const auditWriter = new AuditWriter(auditDir, logger);
9668
+ const auditPruneSweeper = new AuditPruneSweeper(auditDir, logger, DEFAULT_AUDIT_CONFIG);
9669
+ setAuditRuntime(auditWriter, auditPruneSweeper);
9084
9670
  const billingPublisher = new BillingPublisher(billingDir, logger);
9085
9671
  const billingRetrySweeper = new BillingRetrySweeper(
9086
9672
  billingDir,
@@ -9144,7 +9730,7 @@ function resetDaemonSingletonsForTests() {
9144
9730
  }
9145
9731
  function isTokensStoreReadable(tokensPath) {
9146
9732
  try {
9147
- if (!existsSync17(tokensPath)) return true;
9733
+ if (!existsSync19(tokensPath)) return true;
9148
9734
  accessSync(tokensPath, fsConstants.R_OK);
9149
9735
  return true;
9150
9736
  } catch {