@tonyclaw/agent-inspector 2.0.17 → 2.0.19

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 (73) hide show
  1. package/.output/cli.js +149 -40
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-DDg7Cs4N.js → CompareDrawer-D-90PIPw.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-CxRDWacg.js +114 -0
  5. package/.output/public/assets/ReplayDialog-DzlLYoTO.js +1 -0
  6. package/.output/public/assets/{RequestAnatomy-DR8OICOJ.js → RequestAnatomy-0hbyf6oa.js} +1 -1
  7. package/.output/public/assets/ResponseView-CKe0tNqr.js +1 -0
  8. package/.output/public/assets/StreamingChunkSequence-enhfdeNh.js +1 -0
  9. package/.output/public/assets/_sessionId-CJ-SusES.js +1 -0
  10. package/.output/public/assets/index-BbFsmQRC.js +1 -0
  11. package/.output/public/assets/index-BfBgrkgU.css +1 -0
  12. package/.output/public/assets/{main-bwZlEXw2.js → main-o9-hg8g1.js} +2 -2
  13. package/.output/server/_libs/lucide-react.mjs +141 -118
  14. package/.output/server/_libs/tanstack__react-virtual.mjs +13 -2
  15. package/.output/server/_libs/tanstack__virtual-core.mjs +24 -1
  16. package/.output/server/{_sessionId-BEuJJhqT.mjs → _sessionId-F8lm6PIb.mjs} +3 -2
  17. package/.output/server/_ssr/{CompareDrawer-yTO93GMz.mjs → CompareDrawer-Cf1yfwfd.mjs} +4 -3
  18. package/.output/server/_ssr/{ProxyViewerContainer-C8qCkHGB.mjs → ProxyViewerContainer-Bmu9867L.mjs} +349 -74
  19. package/.output/server/_ssr/{ReplayDialog-DRF9PG7Z.mjs → ReplayDialog-DxZfLBOe.mjs} +199 -17
  20. package/.output/server/_ssr/{RequestAnatomy-DBcuV_jV.mjs → RequestAnatomy-CbdERGZd.mjs} +4 -3
  21. package/.output/server/_ssr/{ResponseView-JJm78HZT.mjs → ResponseView-CvLd6qxI.mjs} +4 -3
  22. package/.output/server/_ssr/{StreamingChunkSequence-DyGKjBYx.mjs → StreamingChunkSequence-B_lkRB6o.mjs} +4 -3
  23. package/.output/server/_ssr/{index-T7JG28t6.mjs → index-ejrSItfz.mjs} +3 -2
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/{router-BJob8RN3.mjs → router-SbCnbDc2.mjs} +896 -240
  26. package/.output/server/{_tanstack-start-manifest_v-CuiFi3XC.mjs → _tanstack-start-manifest_v-yx8ftqWz.mjs} +1 -1
  27. package/.output/server/index.mjs +60 -60
  28. package/README.md +26 -109
  29. package/package.json +2 -1
  30. package/src/cli/doctor.ts +160 -8
  31. package/src/cli/templates/skill-onboard.ts +2 -20
  32. package/src/components/OnboardingBanner.tsx +13 -13
  33. package/src/components/ProxyViewer.tsx +55 -38
  34. package/src/components/ProxyViewerContainer.tsx +5 -0
  35. package/src/components/providers/SettingsDialog.tsx +113 -2
  36. package/src/components/proxy-viewer/ConversationGroup.tsx +20 -3
  37. package/src/components/proxy-viewer/ConversationGroupList.tsx +144 -0
  38. package/src/components/proxy-viewer/ConversationHeader.tsx +0 -2
  39. package/src/components/proxy-viewer/ReplayDialog.tsx +200 -13
  40. package/src/components/proxy-viewer/index.ts +1 -1
  41. package/src/knowledge/openclawClient.ts +0 -8
  42. package/src/knowledge/types.ts +0 -2
  43. package/src/lib/export-logs.ts +72 -5
  44. package/src/lib/providerTestContract.ts +0 -1
  45. package/src/lib/useProviders.ts +1 -1
  46. package/src/mcp/server.ts +14 -6
  47. package/src/mcp/toolHandlers.ts +21 -4
  48. package/src/proxy/chunkStorage.ts +2 -4
  49. package/src/proxy/dataDir.ts +37 -15
  50. package/src/proxy/formats/anthropic/index.ts +0 -1
  51. package/src/proxy/formats/anthropic/schemas.ts +0 -2
  52. package/src/proxy/handler.ts +41 -29
  53. package/src/proxy/logFinalizer.ts +93 -11
  54. package/src/proxy/logger.ts +35 -10
  55. package/src/proxy/providers.ts +12 -5
  56. package/src/proxy/rawStreamCapture.ts +85 -0
  57. package/src/proxy/schemas.ts +1 -51
  58. package/src/proxy/sessionRuntime.ts +0 -9
  59. package/src/proxy/socketTracker.ts +15 -8
  60. package/src/proxy/store.ts +414 -13
  61. package/src/routes/api/logs.$id.replay.ts +66 -1
  62. package/src/routes/api/logs.stream.ts +11 -3
  63. package/src/routes/api/logs.ts +51 -17
  64. package/src/routes/api/providers.$providerId.ts +10 -3
  65. package/src/routes/api/providers.ts +6 -3
  66. package/.output/public/assets/ProxyViewerContainer-DbbK1y7N.js +0 -114
  67. package/.output/public/assets/ReplayDialog-C1zWNkoz.js +0 -1
  68. package/.output/public/assets/ResponseView-Dh9iSj6h.js +0 -1
  69. package/.output/public/assets/StreamingChunkSequence-B_xElyH3.js +0 -1
  70. package/.output/public/assets/_sessionId-gv1a_NNu.js +0 -1
  71. package/.output/public/assets/index-D_nZj9Vt.css +0 -1
  72. package/.output/public/assets/index-u-LwDaeH.js +0 -1
  73. package/src/components/ui/json-expansion-button.tsx +0 -56
@@ -1,8 +1,9 @@
1
1
  import { c as createRouter, a as createRootRoute, b as createFileRoute, l as lazyRouteComponent, O as Outlet, H as HeadContent, S as Scripts } from "../_libs/tanstack__react-router.mjs";
2
2
  import { j as jsxRuntimeExports } from "../_libs/react.mjs";
3
3
  import { S as SWRConfig } from "../_libs/swr.mjs";
4
- import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync } from "node:fs";
5
- import fs, { mkdir, appendFile, readFile, open, readdir, stat, unlink, writeFile } from "node:fs/promises";
4
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync, readdirSync, statSync, createReadStream, cpSync, rmSync } from "node:fs";
5
+ import fs, { mkdir, appendFile, readFile, writeFile, readdir, open, stat, unlink } from "node:fs/promises";
6
+ import { createInterface } from "node:readline";
6
7
  import { Buffer } from "node:buffer";
7
8
  import path, { join, isAbsolute, dirname } from "node:path";
8
9
  import { execFile, exec, spawn } from "node:child_process";
@@ -11,6 +12,7 @@ import { C as Conf } from "../_libs/conf.mjs";
11
12
  import { randomUUID } from "crypto";
12
13
  import { promisify } from "node:util";
13
14
  import { Worker } from "node:worker_threads";
15
+ import { randomUUID as randomUUID$1 } from "node:crypto";
14
16
  import { M as McpServer, W as WebStandardStreamableHTTPServerTransport } from "../_libs/modelcontextprotocol__server.mjs";
15
17
  import { homedir } from "node:os";
16
18
  import { d as object, b as string, a as array, _ as _enum, u as union, n as number, c as boolean, e as unknown, l as literal, r as record, g as discriminatedUnion, h as _null, k as lazy } from "../_libs/zod.mjs";
@@ -31,7 +33,6 @@ import "../_libs/isbot.mjs";
31
33
  import "../_libs/use-sync-external-store.mjs";
32
34
  import "../_libs/dequal.mjs";
33
35
  import "node:process";
34
- import "node:crypto";
35
36
  import "node:assert";
36
37
  import "../_libs/dot-prop.mjs";
37
38
  import "../_libs/env-paths.mjs";
@@ -49,7 +50,7 @@ import "../_libs/mimic-function.mjs";
49
50
  import "../_libs/semver.mjs";
50
51
  import "../_libs/uint8array-extras.mjs";
51
52
  const faviconSvg = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2064%2064'%20role='img'%20aria-label='Agent%20Inspector'%3e%3crect%20width='64'%20height='64'%20rx='14'%20fill='%23111827'%20/%3e%3cg%20fill='none'%20stroke='%23f59e0b'%20stroke-width='4.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3e%3cpath%20fill='%23f59e0b'%20d='M15%2036c0-11%207-18%2017-18s17%207%2017%2018c0%208-7%2013-17%2013s-17-5-17-13z'%20/%3e%3cpath%20d='M16%2031c-6-5-12-3-12%204%200%205%206%206%2011%202'%20/%3e%3cpath%20d='M48%2031c6-5%2012-3%2012%204%200%205-6%206-11%202'%20/%3e%3cpath%20d='M27%2019l-3-7'%20/%3e%3cpath%20d='M37%2019l3-7'%20/%3e%3cpath%20d='M19%2045l-6%209'%20/%3e%3cpath%20d='M27%2048l-3%209'%20/%3e%3cpath%20d='M37%2048l3%209'%20/%3e%3cpath%20d='M45%2045l6%209'%20/%3e%3c/g%3e%3cpath%20d='M14%2047l9-8%208%209c-5%203.5-12%203-17-1z'%20fill='%232f6b3f'%20opacity='.95'%20/%3e%3cpath%20d='M18%2046l5-5%205%206c-3%201.6-7%201.4-10-1z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M23%2041v10'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='1.6'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='31'%20cy='48'%20r='1.8'%20fill='%23c2412d'%20/%3e%3cpath%20d='M24%2044l13-11%2014%2013c-8%205.8-18%205.3-27-2z'%20fill='%232f6b3f'%20opacity='.97'%20/%3e%3cpath%20d='M30%2043l7-7%209%209c-5%202.7-11%202.5-16-2z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M37%2036v14'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='2'%20stroke-linecap='round'%20/%3e%3cpath%20d='M30%2043c5-2%2011-1.5%2016%202'%20fill='none'%20stroke='%239fca78'%20stroke-width='1.8'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='51'%20cy='46'%20r='2'%20fill='%23c2412d'%20/%3e%3cpath%20d='M40%2050l8-8%207%208c-4.2%203.2-10%203-15%200z'%20fill='%232f6b3f'%20opacity='.95'%20/%3e%3cpath%20d='M43%2049l5-5%204.5%205.5c-3%201.5-6%201.2-9.5-.5z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M48%2044v9'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='1.5'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='55'%20cy='50'%20r='1.7'%20fill='%23c2412d'%20/%3e%3ccircle%20cx='24'%20cy='11'%20r='3.2'%20fill='%23f59e0b'%20/%3e%3ccircle%20cx='40'%20cy='11'%20r='3.2'%20fill='%23f59e0b'%20/%3e%3ccircle%20cx='25'%20cy='34'%20r='2.1'%20fill='%23111827'%20/%3e%3ccircle%20cx='39'%20cy='34'%20r='2.1'%20fill='%23111827'%20/%3e%3c/svg%3e";
52
- const appCss = "/assets/index-D_nZj9Vt.css";
53
+ const appCss = "/assets/index-BfBgrkgU.css";
53
54
  const Route$s = createRootRoute({
54
55
  head: () => ({
55
56
  meta: [
@@ -92,7 +93,7 @@ function RootDocument({ children }) {
92
93
  ] })
93
94
  ] });
94
95
  }
95
- const $$splitComponentImporter$1 = () => import("./index-T7JG28t6.mjs");
96
+ const $$splitComponentImporter$1 = () => import("./index-ejrSItfz.mjs");
96
97
  const Route$r = createFileRoute("/")({
97
98
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
98
99
  });
@@ -135,7 +136,7 @@ function decodeSessionIdFromPath(encoded) {
135
136
  function getSessionPath(sessionId) {
136
137
  return `/session/${encodeSessionIdForPath(sessionId)}`;
137
138
  }
138
- const $$splitComponentImporter = () => import("../_sessionId-BEuJJhqT.mjs");
139
+ const $$splitComponentImporter = () => import("../_sessionId-F8lm6PIb.mjs");
139
140
  const Route$q = createFileRoute("/session/$sessionId")({
140
141
  component: lazyRouteComponent($$splitComponentImporter, "component"),
141
142
  parseParams: (params) => ({
@@ -146,16 +147,19 @@ const Route$q = createFileRoute("/session/$sessionId")({
146
147
  })
147
148
  });
148
149
  const CURRENT_DATA_DIR_NAME = ".agent-inspector";
149
- const LEGACY_DATA_DIR_NAME = ".llm-inspector";
150
+ const LEGACY_DATA_DIR_NAMES = [".llm-inspector", ".llm-agent"];
150
151
  function getDataDir() {
151
- return resolveDataDir(existsSync);
152
+ migrateLegacyDataDirs();
153
+ return resolveDataDir();
152
154
  }
153
155
  function hasDataDirState(pathExists, dir) {
154
156
  return pathExists(join(dir, "providers.json")) || pathExists(join(dir, "config.json")) || pathExists(join(dir, "logs")) || pathExists(join(dir, "chunks"));
155
157
  }
156
- function resolveDataDir(pathExists, env = process.env, platform = process.platform) {
157
- const isWindows = platform === "win32";
158
- const base = isWindows ? env["USERPROFILE"] ?? env["APPDATA"] ?? "C:\\" : env["HOME"] ?? "/tmp";
158
+ function defaultBaseDir(env = process.env, platform = process.platform) {
159
+ return platform === "win32" ? env["USERPROFILE"] ?? env["APPDATA"] ?? "C:\\" : env["HOME"] ?? "/tmp";
160
+ }
161
+ function resolveDataDir(_pathExists, env = process.env, platform = process.platform) {
162
+ const base = defaultBaseDir(env, platform);
159
163
  const dirEnv = env["AGENT_INSPECTOR_DATA_DIR"];
160
164
  if (dirEnv !== void 0 && dirEnv !== "") {
161
165
  return isAbsolute(dirEnv) ? dirEnv : join(base, dirEnv);
@@ -164,14 +168,31 @@ function resolveDataDir(pathExists, env = process.env, platform = process.platfo
164
168
  if (legacyDirEnv !== void 0 && legacyDirEnv !== "") {
165
169
  return isAbsolute(legacyDirEnv) ? legacyDirEnv : join(base, legacyDirEnv);
166
170
  }
167
- const currentDir = join(base, CURRENT_DATA_DIR_NAME);
168
- const legacyDir = join(base, LEGACY_DATA_DIR_NAME);
169
- return !hasDataDirState(pathExists, currentDir) && hasDataDirState(pathExists, legacyDir) ? legacyDir : currentDir;
171
+ return join(base, CURRENT_DATA_DIR_NAME);
170
172
  }
171
173
  function hasExplicitDataDir() {
172
174
  return process.env["AGENT_INSPECTOR_DATA_DIR"] !== void 0 && process.env["AGENT_INSPECTOR_DATA_DIR"] !== "" || process.env["AGENT_INSPECTOR_CONFIG_DIR"] !== void 0 && process.env["AGENT_INSPECTOR_CONFIG_DIR"] !== "";
173
175
  }
176
+ function migrateLegacyDataDirs() {
177
+ if (hasExplicitDataDir()) return;
178
+ const base = defaultBaseDir();
179
+ const currentDir = join(base, CURRENT_DATA_DIR_NAME);
180
+ if (hasDataDirState(existsSync, currentDir)) return;
181
+ for (const legacyName of LEGACY_DATA_DIR_NAMES) {
182
+ const legacyDir = join(base, legacyName);
183
+ if (!existsSync(legacyDir)) continue;
184
+ try {
185
+ cpSync(legacyDir, currentDir, { recursive: true, force: true });
186
+ rmSync(legacyDir, { recursive: true, force: true });
187
+ } catch {
188
+ }
189
+ }
190
+ }
174
191
  const RETENTION_DAYS = Number(process.env["LOG_RETENTION_DAYS"] ?? "7");
192
+ function writeStderrFallback(message, error) {
193
+ process.stderr.write(`${message} ${String(error)}
194
+ `);
195
+ }
175
196
  function resolveLogDir() {
176
197
  const logDirEnv = process.env["LOG_DIR"];
177
198
  if (logDirEnv !== void 0 && logDirEnv !== "") {
@@ -212,7 +233,7 @@ async function initLogger() {
212
233
  }
213
234
  }
214
235
  } catch (err) {
215
- console.error("[logger] Failed to initialize log directory:", err);
236
+ writeStderrFallback("[logger] Failed to initialize log directory:", err);
216
237
  }
217
238
  }
218
239
  let loggerInitialized = false;
@@ -229,7 +250,7 @@ async function writeAppLog(message) {
229
250
  await appendFile(logPath, `[${timestamp}] ${message}
230
251
  `, "utf-8");
231
252
  } catch (err) {
232
- console.error(`[logger] Failed to write to ${logPath}:`, err);
253
+ writeStderrFallback(`[logger] Failed to write to ${logPath}:`, err);
233
254
  }
234
255
  }
235
256
  const logger = {
@@ -253,12 +274,27 @@ const logger = {
253
274
  const MAX_BUFFER_SIZE = 1e3;
254
275
  let writeBuffer = [];
255
276
  let writeQueue = Promise.resolve();
277
+ let logWriteLock = Promise.resolve();
278
+ async function runWithLogWriteLock(fn) {
279
+ const previousLock = logWriteLock;
280
+ let releaseLock2 = () => {
281
+ };
282
+ logWriteLock = new Promise((resolve) => {
283
+ releaseLock2 = resolve;
284
+ });
285
+ await previousLock;
286
+ try {
287
+ return await fn();
288
+ } finally {
289
+ releaseLock2();
290
+ }
291
+ }
256
292
  function drainBuffer() {
257
293
  const toWrite = writeBuffer.join("");
258
294
  writeBuffer = [];
259
295
  return toWrite;
260
296
  }
261
- async function flushWriteBuffer() {
297
+ async function flushWriteBufferUnlocked() {
262
298
  let toWrite;
263
299
  {
264
300
  toWrite = drainBuffer();
@@ -270,24 +306,31 @@ async function flushWriteBuffer() {
270
306
  await appendFile(filePath, toWrite, "utf-8");
271
307
  } catch (err) {
272
308
  writeBuffer.unshift(toWrite);
273
- console.error("[logger] Failed to flush write buffer, re-queued:", err);
309
+ writeStderrFallback("[logger] Failed to flush write buffer, re-queued:", err);
274
310
  }
275
311
  }
276
312
  function appendLogEntry(entry) {
277
313
  const line = JSON.stringify(entry) + "\n";
278
314
  writeBuffer.push(line);
279
315
  if (writeBuffer.length >= MAX_BUFFER_SIZE) {
280
- writeQueue = writeQueue.then(() => flushWriteBuffer());
316
+ writeQueue = writeQueue.then(() => runWithLogWriteLock(flushWriteBufferUnlocked));
281
317
  } else if (writeBuffer.length === 1) {
282
- writeQueue = writeQueue.then(() => flushWriteBuffer());
318
+ writeQueue = writeQueue.then(() => runWithLogWriteLock(flushWriteBufferUnlocked));
283
319
  }
284
320
  }
285
321
  async function flushLogBuffer() {
286
322
  await writeQueue;
287
323
  if (writeBuffer.length > 0) {
288
- await flushWriteBuffer();
324
+ await runWithLogWriteLock(flushWriteBufferUnlocked);
289
325
  }
290
326
  }
327
+ async function runWithFlushedLogWriteLock(fn) {
328
+ await flushLogBuffer();
329
+ return await runWithLogWriteLock(async () => {
330
+ await flushWriteBufferUnlocked();
331
+ return await fn();
332
+ });
333
+ }
291
334
  process.on("exit", () => {
292
335
  if (writeBuffer.length > 0) {
293
336
  const toWrite = drainBuffer();
@@ -304,127 +347,6 @@ for (const signal of ["SIGINT", "SIGTERM"]) {
304
347
  void flushLogBuffer().then(() => process.exit(0));
305
348
  });
306
349
  }
307
- const INDEX_VERSION = 1;
308
- const INDEX_FILE = "logs.idx";
309
- function getIndexPath() {
310
- return join(resolveLogDir(), INDEX_FILE);
311
- }
312
- let cachedIndex = null;
313
- function createEmptyIndex() {
314
- return {
315
- version: INDEX_VERSION,
316
- entries: {},
317
- maxId: 0
318
- };
319
- }
320
- function isLogIndex(obj) {
321
- if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return false;
322
- const versionDesc = Object.getOwnPropertyDescriptor(obj, "version");
323
- const entriesDesc = Object.getOwnPropertyDescriptor(obj, "entries");
324
- const maxIdDesc = Object.getOwnPropertyDescriptor(obj, "maxId");
325
- return versionDesc !== void 0 && typeof versionDesc.value === "number" && entriesDesc !== void 0 && typeof entriesDesc.value === "object" && entriesDesc.value !== null && maxIdDesc !== void 0 && typeof maxIdDesc.value === "number";
326
- }
327
- async function loadIndex() {
328
- if (cachedIndex !== null) return cachedIndex;
329
- const indexPath = getIndexPath();
330
- if (!existsSync(indexPath)) {
331
- cachedIndex = createEmptyIndex();
332
- return cachedIndex;
333
- }
334
- try {
335
- const content = await readFile(indexPath, "utf-8");
336
- const parsed = JSON.parse(content);
337
- if (isLogIndex(parsed)) {
338
- cachedIndex = parsed;
339
- } else {
340
- cachedIndex = createEmptyIndex();
341
- }
342
- return cachedIndex;
343
- } catch (err) {
344
- logger.error("[logIndex] Failed to load index:", String(err));
345
- cachedIndex = createEmptyIndex();
346
- return cachedIndex;
347
- }
348
- }
349
- async function saveIndex(index) {
350
- const indexPath = getIndexPath();
351
- const dir = dirname(indexPath);
352
- try {
353
- await mkdir(dir, { recursive: true });
354
- } catch {
355
- }
356
- try {
357
- await writeFile(indexPath, JSON.stringify(index), "utf-8");
358
- } catch (err) {
359
- logger.error("[logIndex] Failed to save index:", String(err));
360
- }
361
- }
362
- async function addToIndex(id, file, byteOffset, byteLength) {
363
- const index = await loadIndex();
364
- index.entries[id] = { id, file, byteOffset, byteLength };
365
- if (id > index.maxId) {
366
- index.maxId = id;
367
- }
368
- scheduleIndexFlush();
369
- }
370
- let indexFlushScheduled = false;
371
- async function flushIndexAsync() {
372
- indexFlushScheduled = false;
373
- const index = await loadIndex();
374
- await saveIndex(index);
375
- }
376
- function scheduleIndexFlush() {
377
- if (indexFlushScheduled) return;
378
- indexFlushScheduled = true;
379
- setTimeout(() => {
380
- void flushIndexAsync();
381
- }, 1e3);
382
- }
383
- async function findInIndex(id) {
384
- const index = await loadIndex();
385
- return index.entries[id] ?? null;
386
- }
387
- let idGenerationPromise = null;
388
- let releaseLock = null;
389
- async function acquireLock() {
390
- if (releaseLock === null) {
391
- idGenerationPromise = new Promise((resolve) => {
392
- releaseLock = resolve;
393
- });
394
- } else {
395
- await idGenerationPromise;
396
- idGenerationPromise = new Promise((resolve) => {
397
- releaseLock = resolve;
398
- });
399
- }
400
- }
401
- function releaseLockFn() {
402
- if (releaseLock) {
403
- const resolve = releaseLock;
404
- releaseLock = null;
405
- idGenerationPromise = null;
406
- resolve();
407
- }
408
- }
409
- async function getNextLogId() {
410
- await acquireLock();
411
- try {
412
- const index = await loadIndex();
413
- const nextId = index.maxId + 1;
414
- index.maxId = nextId;
415
- cachedIndex = index;
416
- return nextId;
417
- } finally {
418
- releaseLockFn();
419
- }
420
- }
421
- function getCurrentLogFile() {
422
- const now = /* @__PURE__ */ new Date();
423
- const yyyy = now.getUTCFullYear();
424
- const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
425
- const dd = String(now.getUTCDate()).padStart(2, "0");
426
- return `${yyyy}-${mm}-${dd}.jsonl`;
427
- }
428
350
  const JsonValueSchema = lazy(
429
351
  () => union([
430
352
  string(),
@@ -438,14 +360,6 @@ const JsonValueSchema = lazy(
438
360
  function trustAsJsonValue(value) {
439
361
  return value;
440
362
  }
441
- function isPlainRecord(val) {
442
- return typeof val === "object" && val !== null && !Array.isArray(val);
443
- }
444
- function safeGetOwnProperty(obj, key) {
445
- if (obj === null || typeof obj !== "object" || Array.isArray(obj)) return void 0;
446
- const desc = Object.getOwnPropertyDescriptor(obj, key);
447
- return desc?.value;
448
- }
449
363
  const CacheControl = object({
450
364
  type: string(),
451
365
  ttl: string().optional(),
@@ -807,6 +721,7 @@ const CapturedLogSchema = object({
807
721
  headers: record(string(), string()).optional(),
808
722
  apiFormat: _enum(["anthropic", "openai", "unknown"]).default("unknown"),
809
723
  isTest: boolean().optional().default(false),
724
+ replayOfLogId: number().nullable().optional(),
810
725
  providerName: string().nullable().optional(),
811
726
  clientPort: number().nullable().optional(),
812
727
  clientPid: number().nullable().optional(),
@@ -897,7 +812,7 @@ function writeChunks(logId, chunks, truncated) {
897
812
  try {
898
813
  renameSync(tempPath, targetPath);
899
814
  } catch (err) {
900
- console.warn("[chunkStorage] Rename failed, falling back to copy+delete:", err);
815
+ logger.warn("[chunkStorage] Rename failed, falling back to copy+delete:", String(err));
901
816
  try {
902
817
  copyFileSync(tempPath, targetPath);
903
818
  unlinkSync(tempPath);
@@ -921,6 +836,193 @@ function readChunks(path2) {
921
836
  return null;
922
837
  }
923
838
  }
839
+ const INDEX_VERSION = 1;
840
+ const INDEX_FILE = "logs.idx";
841
+ function getIndexPath() {
842
+ return join(resolveLogDir(), INDEX_FILE);
843
+ }
844
+ let cachedIndex = null;
845
+ function createEmptyIndex() {
846
+ return {
847
+ version: INDEX_VERSION,
848
+ entries: {},
849
+ maxId: 0
850
+ };
851
+ }
852
+ function isLogIndex(obj) {
853
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) return false;
854
+ const versionDesc = Object.getOwnPropertyDescriptor(obj, "version");
855
+ const entriesDesc = Object.getOwnPropertyDescriptor(obj, "entries");
856
+ const maxIdDesc = Object.getOwnPropertyDescriptor(obj, "maxId");
857
+ return versionDesc !== void 0 && typeof versionDesc.value === "number" && entriesDesc !== void 0 && typeof entriesDesc.value === "object" && entriesDesc.value !== null && maxIdDesc !== void 0 && typeof maxIdDesc.value === "number";
858
+ }
859
+ async function loadIndex() {
860
+ if (cachedIndex !== null) return cachedIndex;
861
+ const indexPath = getIndexPath();
862
+ if (!existsSync(indexPath)) {
863
+ cachedIndex = createEmptyIndex();
864
+ return cachedIndex;
865
+ }
866
+ try {
867
+ const content = await readFile(indexPath, "utf-8");
868
+ const parsed = JSON.parse(content);
869
+ if (isLogIndex(parsed)) {
870
+ cachedIndex = parsed;
871
+ } else {
872
+ cachedIndex = createEmptyIndex();
873
+ }
874
+ return cachedIndex;
875
+ } catch (err) {
876
+ logger.error("[logIndex] Failed to load index:", String(err));
877
+ cachedIndex = createEmptyIndex();
878
+ return cachedIndex;
879
+ }
880
+ }
881
+ async function saveIndex(index) {
882
+ const indexPath = getIndexPath();
883
+ const dir = dirname(indexPath);
884
+ try {
885
+ await mkdir(dir, { recursive: true });
886
+ } catch {
887
+ }
888
+ try {
889
+ await writeFile(indexPath, JSON.stringify(index), "utf-8");
890
+ } catch (err) {
891
+ logger.error("[logIndex] Failed to save index:", String(err));
892
+ }
893
+ }
894
+ async function addToIndex(id, file, byteOffset, byteLength) {
895
+ const index = await loadIndex();
896
+ index.entries[id] = { id, file, byteOffset, byteLength };
897
+ if (id > index.maxId) {
898
+ index.maxId = id;
899
+ }
900
+ scheduleIndexFlush();
901
+ }
902
+ let indexFlushScheduled = false;
903
+ let indexFlushTimeout = null;
904
+ async function flushIndexAsync() {
905
+ indexFlushScheduled = false;
906
+ indexFlushTimeout = null;
907
+ const index = await loadIndex();
908
+ await saveIndex(index);
909
+ }
910
+ function scheduleIndexFlush() {
911
+ if (indexFlushScheduled) return;
912
+ indexFlushScheduled = true;
913
+ indexFlushTimeout = setTimeout(() => {
914
+ void flushIndexAsync();
915
+ }, 1e3);
916
+ }
917
+ async function flushIndex() {
918
+ if (indexFlushTimeout !== null) {
919
+ clearTimeout(indexFlushTimeout);
920
+ indexFlushTimeout = null;
921
+ }
922
+ indexFlushScheduled = false;
923
+ const index = await loadIndex();
924
+ await saveIndex(index);
925
+ }
926
+ async function findInIndex(id) {
927
+ const index = await loadIndex();
928
+ return index.entries[id] ?? null;
929
+ }
930
+ async function indexFile(filePath, file) {
931
+ const entries = {};
932
+ let maxId = 0;
933
+ let byteOffset = 0;
934
+ const fileStream = createInterface({
935
+ input: createReadStream(filePath),
936
+ crlfDelay: Infinity
937
+ });
938
+ for await (const line of fileStream) {
939
+ const lineBytes = Buffer.byteLength(line, "utf-8") + 1;
940
+ if (line.trim() !== "") {
941
+ try {
942
+ const entry = JSON.parse(line);
943
+ if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
944
+ const idDesc = Object.getOwnPropertyDescriptor(entry, "id");
945
+ if (idDesc !== void 0 && typeof idDesc.value === "number") {
946
+ const entryId = idDesc.value;
947
+ maxId = Math.max(maxId, entryId);
948
+ entries[entryId] = {
949
+ id: entryId,
950
+ file,
951
+ byteOffset,
952
+ byteLength: lineBytes
953
+ };
954
+ }
955
+ }
956
+ } catch {
957
+ }
958
+ }
959
+ byteOffset += lineBytes;
960
+ }
961
+ return { entries, maxId };
962
+ }
963
+ async function rebuildIndex() {
964
+ const logDir = resolveLogDir();
965
+ const newIndex = createEmptyIndex();
966
+ if (!existsSync(logDir)) {
967
+ cachedIndex = newIndex;
968
+ await saveIndex(newIndex);
969
+ return newIndex;
970
+ }
971
+ const files = (await readdir(logDir)).filter((f) => f.endsWith(".jsonl")).sort();
972
+ const CONCURRENCY = 4;
973
+ for (let i = 0; i < files.length; i += CONCURRENCY) {
974
+ const batch = files.slice(i, i + CONCURRENCY);
975
+ const results = await Promise.all(batch.map((file) => indexFile(join(logDir, file), file)));
976
+ for (const result of results) {
977
+ Object.assign(newIndex.entries, result.entries);
978
+ newIndex.maxId = Math.max(newIndex.maxId, result.maxId);
979
+ }
980
+ }
981
+ cachedIndex = newIndex;
982
+ await saveIndex(newIndex);
983
+ return newIndex;
984
+ }
985
+ let idGenerationPromise = null;
986
+ let releaseLock = null;
987
+ async function acquireLock() {
988
+ if (releaseLock === null) {
989
+ idGenerationPromise = new Promise((resolve) => {
990
+ releaseLock = resolve;
991
+ });
992
+ } else {
993
+ await idGenerationPromise;
994
+ idGenerationPromise = new Promise((resolve) => {
995
+ releaseLock = resolve;
996
+ });
997
+ }
998
+ }
999
+ function releaseLockFn() {
1000
+ if (releaseLock) {
1001
+ const resolve = releaseLock;
1002
+ releaseLock = null;
1003
+ idGenerationPromise = null;
1004
+ resolve();
1005
+ }
1006
+ }
1007
+ async function getNextLogId() {
1008
+ await acquireLock();
1009
+ try {
1010
+ const index = await loadIndex();
1011
+ const nextId = index.maxId + 1;
1012
+ index.maxId = nextId;
1013
+ cachedIndex = index;
1014
+ return nextId;
1015
+ } finally {
1016
+ releaseLockFn();
1017
+ }
1018
+ }
1019
+ function getCurrentLogFile() {
1020
+ const now = /* @__PURE__ */ new Date();
1021
+ const yyyy = now.getUTCFullYear();
1022
+ const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
1023
+ const dd = String(now.getUTCDate()).padStart(2, "0");
1024
+ return `${yyyy}-${mm}-${dd}.jsonl`;
1025
+ }
924
1026
  const IDLE_TIMEOUT_MS = Number(process.env["SESSION_PROCESS_IDLE_MS"]) || 5 * 60 * 1e3;
925
1027
  const MAX_RESTARTS = 3;
926
1028
  const _processes = /* @__PURE__ */ new Map();
@@ -1259,9 +1361,11 @@ async function addTestLogEntry(entry) {
1259
1361
  sessionId: session.id,
1260
1362
  streamingChunksPath
1261
1363
  };
1262
- const logFile = getCurrentLogFile();
1263
- appendLogEntry(log);
1264
- await addToIndex(id, logFile, -1, -1);
1364
+ await runWithLogWriteLock(async () => {
1365
+ const logFile = getCurrentLogFile();
1366
+ appendLogEntry(log);
1367
+ await addToIndex(id, logFile, -1, -1);
1368
+ });
1265
1369
  addToCache(log);
1266
1370
  observeSessionLog(log, session.source);
1267
1371
  emitLogUpdate(log);
@@ -1304,9 +1408,11 @@ async function createLog(method, path2, requestBody, headers, clientInfo, rawHea
1304
1408
  clientProjectFolder: clientInfo?.projectFolder ?? null,
1305
1409
  streamingChunksPath: null
1306
1410
  };
1307
- const logFile = getCurrentLogFile();
1308
- appendLogEntry(log);
1309
- await addToIndex(id, logFile, -1, -1);
1411
+ await runWithLogWriteLock(async () => {
1412
+ const logFile = getCurrentLogFile();
1413
+ appendLogEntry(log);
1414
+ await addToIndex(id, logFile, -1, -1);
1415
+ });
1310
1416
  addToCache(log);
1311
1417
  markSessionStarted(log, session.source);
1312
1418
  emitLogUpdate(log);
@@ -1388,11 +1494,81 @@ async function getLogById(id) {
1388
1494
  }
1389
1495
  function getFilteredLogs(sessionId, model) {
1390
1496
  return [...memoryCache.values()].filter((l) => {
1391
- if (sessionId !== void 0 && getLogSessionId(l) !== sessionId) return false;
1392
- if (model !== void 0 && l.model !== model) return false;
1393
- return true;
1497
+ return matchesLogFilters(l, sessionId, model);
1394
1498
  });
1395
1499
  }
1500
+ function matchesLogFilters(log, sessionId, model) {
1501
+ if (sessionId !== void 0 && getLogSessionId(log) !== sessionId) return false;
1502
+ if (model !== void 0 && log.model !== model) return false;
1503
+ return true;
1504
+ }
1505
+ function requestedWindowFitsMemory(filteredMemoryCount, offset, limit) {
1506
+ return offset + limit <= filteredMemoryCount;
1507
+ }
1508
+ function paginateLogs(logs, offset, limit) {
1509
+ return logs.slice(offset, offset + limit);
1510
+ }
1511
+ async function readPersistedLogsById() {
1512
+ const byId = /* @__PURE__ */ new Map();
1513
+ const logDir = resolveLogDir();
1514
+ if (!existsSync(logDir)) return byId;
1515
+ try {
1516
+ const entries = await readdir(logDir);
1517
+ const files = entries.filter((file) => file.endsWith(".jsonl")).sort();
1518
+ for (const file of files) {
1519
+ await readPersistedLogFile(join(logDir, file), byId);
1520
+ }
1521
+ } catch (err) {
1522
+ logger.error("[store] Failed to list persisted logs:", String(err));
1523
+ }
1524
+ for (const log of memoryCache.values()) {
1525
+ byId.set(log.id, log);
1526
+ }
1527
+ return byId;
1528
+ }
1529
+ async function readPersistedLogFile(filePath, byId) {
1530
+ if (!existsSync(filePath)) return;
1531
+ try {
1532
+ const fileStream = createInterface({
1533
+ input: createReadStream(filePath),
1534
+ crlfDelay: Infinity
1535
+ });
1536
+ for await (const line of fileStream) {
1537
+ if (line.trim() === "") continue;
1538
+ try {
1539
+ const parsed = JSON.parse(line);
1540
+ const result = CapturedLogSchema.safeParse(parsed);
1541
+ if (result.success) {
1542
+ const log = normalizeLogSession(result.data);
1543
+ byId.set(log.id, log);
1544
+ observeSessionLog(log);
1545
+ }
1546
+ } catch {
1547
+ }
1548
+ }
1549
+ } catch (err) {
1550
+ logger.error("[store] Failed to read persisted log file:", filePath, String(err));
1551
+ }
1552
+ }
1553
+ async function listLogsPage(options) {
1554
+ const memoryLogs = getFilteredLogs(options.sessionId, options.model);
1555
+ if (requestedWindowFitsMemory(memoryLogs.length, options.offset, options.limit)) {
1556
+ return {
1557
+ logs: paginateLogs(memoryLogs, options.offset, options.limit),
1558
+ total: memoryLogs.length,
1559
+ offset: options.offset,
1560
+ limit: options.limit
1561
+ };
1562
+ }
1563
+ const persistedById = await readPersistedLogsById();
1564
+ const persistedLogs = [...persistedById.values()].filter((log) => matchesLogFilters(log, options.sessionId, options.model)).sort((left, right) => left.id - right.id);
1565
+ return {
1566
+ logs: paginateLogs(persistedLogs, options.offset, options.limit),
1567
+ total: persistedLogs.length,
1568
+ offset: options.offset,
1569
+ limit: options.limit
1570
+ };
1571
+ }
1396
1572
  function getSessions() {
1397
1573
  return getSessionIds();
1398
1574
  }
@@ -1403,12 +1579,216 @@ function getModels() {
1403
1579
  }
1404
1580
  return [...set];
1405
1581
  }
1582
+ function getReplayLogsForSource(sourceLogId) {
1583
+ return [...memoryCache.values()].filter((log) => log.replayOfLogId === sourceLogId);
1584
+ }
1585
+ function collectDirectoryStats(dir, shouldCount) {
1586
+ if (!existsSync(dir)) return { fileCount: 0, bytes: 0 };
1587
+ let fileCount = 0;
1588
+ let bytes = 0;
1589
+ const visit = (currentDir, depth) => {
1590
+ if (depth > 8) return;
1591
+ let entries;
1592
+ try {
1593
+ entries = readdirSync(currentDir);
1594
+ } catch {
1595
+ return;
1596
+ }
1597
+ for (const entry of entries) {
1598
+ const path2 = join(currentDir, entry);
1599
+ try {
1600
+ const stats = statSync(path2);
1601
+ if (stats.isDirectory()) {
1602
+ visit(path2, depth + 1);
1603
+ } else if (shouldCount(entry)) {
1604
+ fileCount += 1;
1605
+ bytes += stats.size;
1606
+ }
1607
+ } catch {
1608
+ }
1609
+ }
1610
+ };
1611
+ visit(dir, 0);
1612
+ return { fileCount, bytes };
1613
+ }
1614
+ function getLogStorageStats() {
1615
+ const logDir = resolveLogDir();
1616
+ const chunkDir = getChunksDir();
1617
+ const logStats = collectDirectoryStats(logDir, (fileName) => fileName.endsWith(".jsonl"));
1618
+ const chunkStats = collectDirectoryStats(chunkDir, (fileName) => fileName.endsWith(".json"));
1619
+ return {
1620
+ memoryCount: memoryCache.size,
1621
+ logDir,
1622
+ logFileCount: logStats.fileCount,
1623
+ logBytes: logStats.bytes,
1624
+ chunkDir,
1625
+ chunkFileCount: chunkStats.fileCount,
1626
+ chunkBytes: chunkStats.bytes
1627
+ };
1628
+ }
1406
1629
  function clearAllLogs() {
1407
1630
  const count = memoryCache.size;
1408
1631
  memoryCache.clear();
1409
1632
  clearSessionRegistry();
1410
1633
  return { cleared: count };
1411
1634
  }
1635
+ async function deleteMatchingFiles(dir, shouldDelete) {
1636
+ if (!existsSync(dir)) return 0;
1637
+ let deleted = 0;
1638
+ let entries;
1639
+ try {
1640
+ entries = await readdir(dir);
1641
+ } catch (err) {
1642
+ logger.warn("[store] Failed to list log storage directory:", String(err));
1643
+ return deleted;
1644
+ }
1645
+ for (const entry of entries) {
1646
+ if (!shouldDelete(entry)) continue;
1647
+ try {
1648
+ await unlink(join(dir, entry));
1649
+ deleted += 1;
1650
+ } catch (err) {
1651
+ logger.warn("[store] Failed to delete log storage file:", entry, String(err));
1652
+ }
1653
+ }
1654
+ return deleted;
1655
+ }
1656
+ async function clearPersistedLogStorage() {
1657
+ return await runWithFlushedLogWriteLock(async () => {
1658
+ await flushIndex();
1659
+ const result = clearAllLogs();
1660
+ const [logFilesDeleted, chunkFilesDeleted] = await Promise.all([
1661
+ deleteMatchingFiles(
1662
+ resolveLogDir(),
1663
+ (fileName) => fileName.endsWith(".jsonl") || fileName === "logs.idx"
1664
+ ),
1665
+ deleteMatchingFiles(
1666
+ getChunksDir(),
1667
+ (fileName) => fileName.endsWith(".json") || fileName.startsWith(".") && fileName.endsWith(".tmp")
1668
+ )
1669
+ ]);
1670
+ await rebuildIndex();
1671
+ return {
1672
+ cleared: result.cleared,
1673
+ logFilesDeleted,
1674
+ chunkFilesDeleted
1675
+ };
1676
+ });
1677
+ }
1678
+ function readLogIdFromLine(line) {
1679
+ if (line.trim() === "") return null;
1680
+ try {
1681
+ const parsed = JSON.parse(line);
1682
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
1683
+ const idDesc = Object.getOwnPropertyDescriptor(parsed, "id");
1684
+ if (idDesc === void 0 || typeof idDesc.value !== "number") return null;
1685
+ return idDesc.value;
1686
+ } catch {
1687
+ return null;
1688
+ }
1689
+ }
1690
+ async function rewriteLogFileWithoutIds(filePath, ids) {
1691
+ let content;
1692
+ try {
1693
+ content = await readFile(filePath, "utf-8");
1694
+ } catch (err) {
1695
+ logger.warn("[store] Failed to read log file for deletion:", filePath, String(err));
1696
+ return { rewritten: false, removedIds: /* @__PURE__ */ new Set() };
1697
+ }
1698
+ const lines = content.split("\n");
1699
+ const keptLines = [];
1700
+ const removedIds = /* @__PURE__ */ new Set();
1701
+ for (const line of lines) {
1702
+ if (line === "") continue;
1703
+ const id = readLogIdFromLine(line);
1704
+ if (id !== null && ids.has(id)) {
1705
+ removedIds.add(id);
1706
+ } else {
1707
+ keptLines.push(line);
1708
+ }
1709
+ }
1710
+ if (removedIds.size === 0) return { rewritten: false, removedIds };
1711
+ try {
1712
+ if (keptLines.length === 0) {
1713
+ await unlink(filePath);
1714
+ } else {
1715
+ await writeFile(filePath, `${keptLines.join("\n")}
1716
+ `, "utf-8");
1717
+ }
1718
+ } catch (err) {
1719
+ logger.warn("[store] Failed to rewrite log file after deletion:", filePath, String(err));
1720
+ return { rewritten: false, removedIds: /* @__PURE__ */ new Set() };
1721
+ }
1722
+ return { rewritten: true, removedIds };
1723
+ }
1724
+ async function rewriteLogFilesWithoutIds(ids) {
1725
+ const logDir = resolveLogDir();
1726
+ if (!existsSync(logDir)) return { logFilesRewritten: 0, removedIds: /* @__PURE__ */ new Set() };
1727
+ let entries;
1728
+ try {
1729
+ entries = await readdir(logDir);
1730
+ } catch (err) {
1731
+ logger.warn("[store] Failed to list log directory for deletion:", String(err));
1732
+ return { logFilesRewritten: 0, removedIds: /* @__PURE__ */ new Set() };
1733
+ }
1734
+ let logFilesRewritten = 0;
1735
+ const removedIds = /* @__PURE__ */ new Set();
1736
+ for (const entry of entries) {
1737
+ if (!entry.endsWith(".jsonl")) continue;
1738
+ const result = await rewriteLogFileWithoutIds(join(logDir, entry), ids);
1739
+ if (result.rewritten) logFilesRewritten += 1;
1740
+ for (const removedId of result.removedIds) {
1741
+ removedIds.add(removedId);
1742
+ }
1743
+ }
1744
+ return { logFilesRewritten, removedIds };
1745
+ }
1746
+ async function deleteChunkFilesByIds(ids) {
1747
+ const chunkDir = getChunksDir();
1748
+ if (!existsSync(chunkDir)) return 0;
1749
+ let deleted = 0;
1750
+ for (const id of ids) {
1751
+ const candidates2 = [`${String(id)}.json`, `.${String(id)}.tmp`];
1752
+ for (const candidate of candidates2) {
1753
+ try {
1754
+ await unlink(join(chunkDir, candidate));
1755
+ deleted += 1;
1756
+ } catch {
1757
+ }
1758
+ }
1759
+ }
1760
+ return deleted;
1761
+ }
1762
+ async function clearPersistedLogsByIds(ids) {
1763
+ const uniqueIds = new Set(ids);
1764
+ if (uniqueIds.size === 0) {
1765
+ return { cleared: 0, logFilesRewritten: 0, chunkFilesDeleted: 0 };
1766
+ }
1767
+ return await runWithFlushedLogWriteLock(async () => {
1768
+ await flushIndex();
1769
+ const memoryRemovedIds = /* @__PURE__ */ new Set();
1770
+ for (const id of uniqueIds) {
1771
+ if (memoryCache.has(id)) memoryRemovedIds.add(id);
1772
+ }
1773
+ const result = clearLogsByIds([...uniqueIds]);
1774
+ const [rewriteResult, chunkFilesDeleted] = await Promise.all([
1775
+ rewriteLogFilesWithoutIds(uniqueIds),
1776
+ deleteChunkFilesByIds(uniqueIds)
1777
+ ]);
1778
+ await rebuildIndex();
1779
+ const clearedIds = new Set(rewriteResult.removedIds);
1780
+ if (result.cleared > 0) {
1781
+ for (const id of memoryRemovedIds) {
1782
+ clearedIds.add(id);
1783
+ }
1784
+ }
1785
+ return {
1786
+ cleared: clearedIds.size,
1787
+ logFilesRewritten: rewriteResult.logFilesRewritten,
1788
+ chunkFilesDeleted
1789
+ };
1790
+ });
1791
+ }
1412
1792
  function clearLogsByIds(ids) {
1413
1793
  const unique2 = /* @__PURE__ */ new Set();
1414
1794
  for (const id of ids) {
@@ -2047,6 +2427,10 @@ const alibabaProvider = {
2047
2427
  }
2048
2428
  };
2049
2429
  registry.register(alibabaProvider);
2430
+ function maskApiKey(apiKey) {
2431
+ if (apiKey.length <= 8) return "••••••••";
2432
+ return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
2433
+ }
2050
2434
  const ProviderModelMetadataSchema = object({
2051
2435
  model: string().min(1),
2052
2436
  contextWindow: number().int().positive().optional(),
@@ -2613,6 +2997,15 @@ function getProvider(id) {
2613
2997
  const providers = getProviders();
2614
2998
  return providers.find((p) => p.id === id);
2615
2999
  }
3000
+ function redactProvider(provider) {
3001
+ return {
3002
+ ...provider,
3003
+ apiKey: maskApiKey(provider.apiKey)
3004
+ };
3005
+ }
3006
+ function redactProviders(providers) {
3007
+ return providers.map(redactProvider);
3008
+ }
2616
3009
  function normalizeApiKey(apiKey) {
2617
3010
  return apiKey.replace(/^Bearer\s+/i, "").trim();
2618
3011
  }
@@ -2779,10 +3172,6 @@ function importProviders(json) {
2779
3172
  }
2780
3173
  return { imported, errors };
2781
3174
  }
2782
- function maskApiKey(apiKey) {
2783
- if (apiKey.length <= 8) return "••••••••";
2784
- return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
2785
- }
2786
3175
  function getModelUsageName(model, providerName) {
2787
3176
  if (providerName !== void 0 && providerName !== "" && providerName.toLowerCase().includes("minimax")) {
2788
3177
  return model.replace(/ /g, "-");
@@ -2824,6 +3213,10 @@ const execFileAsync = promisify(execFile);
2824
3213
  const cache = /* @__PURE__ */ new Map();
2825
3214
  const CACHE_TTL_MS = 5 * 60 * 1e3;
2826
3215
  const MAX_CACHE_SIZE = 200;
3216
+ const RemotePortSchema = number().int().min(1).max(65535);
3217
+ const SocketSchema = object({
3218
+ remotePort: RemotePortSchema.nullish()
3219
+ }).passthrough();
2827
3220
  const inflight = /* @__PURE__ */ new Map();
2828
3221
  function evictCacheIfNeeded() {
2829
3222
  while (cache.size > MAX_CACHE_SIZE) {
@@ -2845,10 +3238,13 @@ function setCache(port, info) {
2845
3238
  evictCacheIfNeeded();
2846
3239
  }
2847
3240
  function extractRemotePort(request) {
2848
- const socket = request.socket;
2849
- const remotePort = socket?.remotePort;
2850
- if (remotePort !== void 0 && remotePort !== null) return remotePort;
2851
- return null;
3241
+ const descriptor = Object.getOwnPropertyDescriptor(request, "socket");
3242
+ if (descriptor === void 0 || descriptor.get !== void 0) return null;
3243
+ const parsed = SocketSchema.safeParse(descriptor.value);
3244
+ if (!parsed.success) return null;
3245
+ const remotePort = parsed.data.remotePort;
3246
+ if (remotePort === void 0 || remotePort === null) return null;
3247
+ return remotePort;
2852
3248
  }
2853
3249
  async function lookupClientInfo(port) {
2854
3250
  const platform = process.platform;
@@ -3123,6 +3519,14 @@ function toPersistedConfig(value) {
3123
3519
  timeDisplayFormat: value.timeDisplayFormat
3124
3520
  };
3125
3521
  }
3522
+ function isPlainRecord(val) {
3523
+ return typeof val === "object" && val !== null && !Array.isArray(val);
3524
+ }
3525
+ function safeGetOwnProperty(obj, key) {
3526
+ if (obj === null || typeof obj !== "object" || Array.isArray(obj)) return void 0;
3527
+ const desc = Object.getOwnPropertyDescriptor(obj, key);
3528
+ return desc?.value;
3529
+ }
3126
3530
  const BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
3127
3531
  function isClaudeCodeBillingHeaderBlock(text) {
3128
3532
  const trimmed = text.trimStart();
@@ -3288,14 +3692,14 @@ function persistStreamingChunks(log) {
3288
3692
  log.streamingChunksPath = chunkPath;
3289
3693
  }
3290
3694
  }
3291
- function finalizeWithError(job, log, fallbackStatus, fallbackResponseText, err) {
3695
+ function finalizeWithError(job, log, fallbackStatus, fallbackResponseText, err, cleanupRawStreamPath = null) {
3292
3696
  const message = errorMessage$2(err);
3293
3697
  logger.error(`[logFinalizer] Failed to finalize log #${log.id}:`, message);
3294
3698
  log.responseStatus = log.responseStatus ?? fallbackStatus;
3295
3699
  log.responseText = log.responseText ?? fallbackResponseText;
3296
3700
  log.elapsedMs = job.elapsedMs;
3297
3701
  log.error = message;
3298
- return { log, upstreamUrl: job.upstreamUrl, error: message };
3702
+ return { log, upstreamUrl: job.upstreamUrl, error: message, cleanupRawStreamPath };
3299
3703
  }
3300
3704
  function finalizeNonStreaming(job, log) {
3301
3705
  const formatHandler = formatForPath(log.path);
@@ -3316,36 +3720,90 @@ function finalizeNonStreaming(job, log) {
3316
3720
  return finalizeWithError(job, log, job.responseStatus, job.responseBody, err);
3317
3721
  }
3318
3722
  }
3723
+ function cleanupPathForRawStream(rawStream) {
3724
+ if (typeof rawStream === "string") return null;
3725
+ switch (rawStream.type) {
3726
+ case "memory":
3727
+ return null;
3728
+ case "file":
3729
+ return rawStream.path;
3730
+ }
3731
+ }
3732
+ function readRawStream(rawStream) {
3733
+ if (typeof rawStream === "string") {
3734
+ return { rawStreamText: rawStream, cleanupRawStreamPath: null };
3735
+ }
3736
+ switch (rawStream.type) {
3737
+ case "memory":
3738
+ return { rawStreamText: rawStream.rawStream, cleanupRawStreamPath: null };
3739
+ case "file":
3740
+ return {
3741
+ rawStreamText: readFileSync(rawStream.path, "utf-8"),
3742
+ cleanupRawStreamPath: rawStream.path
3743
+ };
3744
+ }
3745
+ }
3319
3746
  function finalizeStreaming(job, log) {
3320
3747
  const formatHandler = formatForPath(log.path);
3321
3748
  if (formatHandler === null) {
3322
- return finalizeWithError(job, log, job.responseStatus, job.rawStream, "Unsupported format");
3749
+ return finalizeWithError(
3750
+ job,
3751
+ log,
3752
+ job.responseStatus,
3753
+ "Streaming response unavailable",
3754
+ "Unsupported format",
3755
+ cleanupPathForRawStream(job.rawStream)
3756
+ );
3323
3757
  }
3758
+ let rawStreamText = "";
3759
+ let cleanupRawStreamPath = null;
3324
3760
  try {
3761
+ const raw = readRawStream(job.rawStream);
3762
+ rawStreamText = raw.rawStreamText;
3763
+ cleanupRawStreamPath = raw.cleanupRawStreamPath;
3325
3764
  log.elapsedMs = job.elapsedMs;
3326
3765
  log.responseStatus = job.responseStatus;
3327
3766
  log.responseText = formatHandler.extractStream(
3328
- job.rawStream,
3767
+ rawStreamText,
3329
3768
  log,
3330
3769
  log.model ?? void 0,
3331
3770
  job.collectStreamingChunks
3332
3771
  );
3333
3772
  persistStreamingChunks(log);
3334
- return { log, upstreamUrl: job.upstreamUrl, error: null };
3773
+ return { log, upstreamUrl: job.upstreamUrl, error: null, cleanupRawStreamPath };
3335
3774
  } catch (err) {
3336
- return finalizeWithError(job, log, job.responseStatus, job.rawStream, err);
3775
+ return finalizeWithError(
3776
+ job,
3777
+ log,
3778
+ job.responseStatus,
3779
+ rawStreamText === "" ? "Streaming response unavailable" : rawStreamText,
3780
+ err,
3781
+ cleanupRawStreamPath ?? cleanupPathForRawStream(job.rawStream)
3782
+ );
3337
3783
  }
3338
3784
  }
3339
3785
  function finalizeStreamAbort(job, log) {
3340
3786
  const formatHandler = formatForPath(log.path);
3341
3787
  if (formatHandler === null && job.hasChunks) {
3342
- return finalizeWithError(job, log, 499, "Client aborted", "Unsupported format");
3788
+ return finalizeWithError(
3789
+ job,
3790
+ log,
3791
+ 499,
3792
+ "Client aborted",
3793
+ "Unsupported format",
3794
+ cleanupPathForRawStream(job.rawStream)
3795
+ );
3343
3796
  }
3797
+ let rawStreamText = "";
3798
+ let cleanupRawStreamPath = null;
3344
3799
  try {
3345
3800
  log.elapsedMs = job.elapsedMs;
3346
3801
  if (job.hasChunks && formatHandler !== null) {
3802
+ const raw = readRawStream(job.rawStream);
3803
+ rawStreamText = raw.rawStreamText;
3804
+ cleanupRawStreamPath = raw.cleanupRawStreamPath;
3347
3805
  log.responseText = formatHandler.extractStream(
3348
- job.rawStream,
3806
+ rawStreamText,
3349
3807
  log,
3350
3808
  log.model ?? void 0,
3351
3809
  job.collectStreamingChunks
@@ -3354,9 +3812,16 @@ function finalizeStreamAbort(job, log) {
3354
3812
  } else {
3355
3813
  log.responseText = "Client aborted";
3356
3814
  }
3357
- return { log, upstreamUrl: job.upstreamUrl, error: "Client aborted" };
3815
+ return { log, upstreamUrl: job.upstreamUrl, error: "Client aborted", cleanupRawStreamPath };
3358
3816
  } catch (err) {
3359
- return finalizeWithError(job, log, 499, "Client aborted", err);
3817
+ return finalizeWithError(
3818
+ job,
3819
+ log,
3820
+ 499,
3821
+ rawStreamText === "" ? "Client aborted" : rawStreamText,
3822
+ err,
3823
+ cleanupRawStreamPath ?? cleanupPathForRawStream(job.rawStream)
3824
+ );
3360
3825
  }
3361
3826
  }
3362
3827
  function buildFinalizeLogResult(job) {
@@ -3373,6 +3838,17 @@ function buildFinalizeLogResult(job) {
3373
3838
  function commitFinalizeLogResult(result) {
3374
3839
  appendLogEntry({ ...buildFileLogEntry(result.log, result.upstreamUrl), error: result.error });
3375
3840
  finalizeLogUpdate(result.log);
3841
+ if (result.cleanupRawStreamPath !== void 0 && result.cleanupRawStreamPath !== null) {
3842
+ try {
3843
+ rmSync(result.cleanupRawStreamPath, { force: true });
3844
+ } catch (err) {
3845
+ logger.warn(
3846
+ "[logFinalizer] Failed to remove raw stream temp file:",
3847
+ result.cleanupRawStreamPath,
3848
+ errorMessage$2(err)
3849
+ );
3850
+ }
3851
+ }
3376
3852
  }
3377
3853
  const RUNTIME = (() => {
3378
3854
  if (process.env["FINALIZER_USE_WORKER"] === "0") return "inline";
@@ -3495,6 +3971,63 @@ function enqueueFinalizeLogJob(job) {
3495
3971
  () => executeFinalizeLogJob(job)
3496
3972
  );
3497
3973
  }
3974
+ const DEFAULT_MEMORY_LIMIT_BYTES = 1024 * 1024;
3975
+ function resolveMemoryLimitBytes() {
3976
+ const raw = process.env["AGENT_INSPECTOR_STREAM_MEMORY_LIMIT_BYTES"];
3977
+ if (raw === void 0 || raw === "") return DEFAULT_MEMORY_LIMIT_BYTES;
3978
+ const parsed = Number(raw);
3979
+ if (!Number.isInteger(parsed) || parsed < 0) return DEFAULT_MEMORY_LIMIT_BYTES;
3980
+ return parsed;
3981
+ }
3982
+ function getRawStreamDir() {
3983
+ return join(getDataDir(), "raw-streams");
3984
+ }
3985
+ function createRawStreamPath() {
3986
+ return join(getRawStreamDir(), `${Date.now()}-${randomUUID$1()}.stream.tmp`);
3987
+ }
3988
+ class RawStreamCapture {
3989
+ memoryLimitBytes;
3990
+ chunks = [];
3991
+ byteLength = 0;
3992
+ filePath = null;
3993
+ sawChunk = false;
3994
+ constructor(memoryLimitBytes = resolveMemoryLimitBytes()) {
3995
+ this.memoryLimitBytes = memoryLimitBytes;
3996
+ }
3997
+ get hasChunks() {
3998
+ return this.sawChunk;
3999
+ }
4000
+ async append(text) {
4001
+ if (text === "") return;
4002
+ this.sawChunk = true;
4003
+ const nextBytes = Buffer.byteLength(text, "utf-8");
4004
+ if (this.filePath === null && this.byteLength + nextBytes <= this.memoryLimitBytes) {
4005
+ this.chunks.push(text);
4006
+ this.byteLength += nextBytes;
4007
+ return;
4008
+ }
4009
+ if (this.filePath === null) {
4010
+ await this.spillToFile();
4011
+ }
4012
+ if (this.filePath !== null) {
4013
+ await appendFile(this.filePath, text, "utf-8");
4014
+ this.byteLength += nextBytes;
4015
+ }
4016
+ }
4017
+ snapshot() {
4018
+ if (this.filePath !== null) {
4019
+ return { type: "file", path: this.filePath };
4020
+ }
4021
+ return { type: "memory", rawStream: this.chunks.join("") };
4022
+ }
4023
+ async spillToFile() {
4024
+ const path2 = createRawStreamPath();
4025
+ await mkdir(getRawStreamDir(), { recursive: true });
4026
+ await writeFile(path2, this.chunks.join(""), "utf-8");
4027
+ this.filePath = path2;
4028
+ this.chunks = [];
4029
+ }
4030
+ }
3498
4031
  function describeApiRoute(apiPath) {
3499
4032
  const endpointPath = apiPath.split("?")[0] ?? "";
3500
4033
  const isChatCompletions = endpointPath === PATH_CHAT_COMPLETIONS || endpointPath === PATH_V1_CHAT_COMPLETIONS;
@@ -3603,25 +4136,47 @@ function handleNonStreamingResponse(upstreamRes, responseBody, startTime, upstre
3603
4136
  function handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log, collectStreamingChunks) {
3604
4137
  log.streaming = true;
3605
4138
  log.responseStatus = upstreamRes.status;
3606
- const chunks = [];
4139
+ const capture = new RawStreamCapture();
3607
4140
  const decoder = new TextDecoder();
4141
+ let finalizationScheduled = false;
4142
+ const scheduleStreamingFinalization = (type) => {
4143
+ if (finalizationScheduled) return;
4144
+ finalizationScheduled = true;
4145
+ const elapsedMs = Date.now() - startTime;
4146
+ const rawStream = capture.snapshot();
4147
+ switch (type) {
4148
+ case "streaming":
4149
+ scheduleLogFinalization({
4150
+ type: "streaming",
4151
+ log,
4152
+ upstreamUrl,
4153
+ elapsedMs,
4154
+ responseStatus: upstreamRes.status,
4155
+ rawStream,
4156
+ collectStreamingChunks
4157
+ });
4158
+ return;
4159
+ case "stream-abort":
4160
+ scheduleLogFinalization({
4161
+ type: "stream-abort",
4162
+ log,
4163
+ upstreamUrl,
4164
+ elapsedMs,
4165
+ rawStream,
4166
+ hasChunks: capture.hasChunks,
4167
+ collectStreamingChunks
4168
+ });
4169
+ return;
4170
+ }
4171
+ };
3608
4172
  const transform = new TransformStream({
3609
- transform(chunk, controller) {
4173
+ async transform(chunk, controller) {
3610
4174
  controller.enqueue(chunk);
3611
- chunks.push(decoder.decode(chunk, { stream: true }));
4175
+ await capture.append(decoder.decode(chunk, { stream: true }));
3612
4176
  },
3613
- flush() {
3614
- const full = chunks.join("");
3615
- const elapsedMs = Date.now() - startTime;
3616
- scheduleLogFinalization({
3617
- type: "streaming",
3618
- log,
3619
- upstreamUrl,
3620
- elapsedMs,
3621
- responseStatus: upstreamRes.status,
3622
- rawStream: full,
3623
- collectStreamingChunks
3624
- });
4177
+ async flush() {
4178
+ await capture.append(decoder.decode());
4179
+ scheduleStreamingFinalization("streaming");
3625
4180
  }
3626
4181
  });
3627
4182
  if (upstreamRes.body === null) {
@@ -3631,18 +4186,7 @@ function handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log,
3631
4186
  req.signal?.addEventListener("abort", () => {
3632
4187
  if (log.responseText === null) {
3633
4188
  logger.info(`[handler] Streaming client aborted: ${log.method} ${log.path}`);
3634
- const elapsedMs = Date.now() - startTime;
3635
- const full = chunks.join("");
3636
- const hasChunks = chunks.length > 0;
3637
- scheduleLogFinalization({
3638
- type: "stream-abort",
3639
- log,
3640
- upstreamUrl,
3641
- elapsedMs,
3642
- rawStream: full,
3643
- hasChunks,
3644
- collectStreamingChunks
3645
- });
4189
+ scheduleStreamingFinalization("stream-abort");
3646
4190
  }
3647
4191
  });
3648
4192
  const responseHeaders = new Headers(upstreamRes.headers);
@@ -3797,8 +4341,11 @@ const ProviderInputSchema = object({
3797
4341
  const Route$n = createFileRoute("/api/providers")({
3798
4342
  server: {
3799
4343
  handlers: {
3800
- GET: () => {
3801
- return Response.json(getProviders());
4344
+ GET: ({ request }) => {
4345
+ const url = new URL(request.url);
4346
+ const includeSecrets = url.searchParams.get("includeSecrets") === "1";
4347
+ const providers = getProviders();
4348
+ return Response.json(includeSecrets ? providers : redactProviders(providers));
3802
4349
  },
3803
4350
  POST: async ({ request }) => {
3804
4351
  const parsed = ProviderInputSchema.safeParse(await request.json());
@@ -4073,13 +4620,18 @@ async function listModelsImpl(callApi2) {
4073
4620
  if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
4074
4621
  return textJson(await res.json());
4075
4622
  }
4076
- async function listProvidersImpl(callApi2) {
4077
- const res = await callApi2("/api/providers");
4623
+ function includeSecretsQuery(includeSecrets) {
4624
+ return includeSecrets === true ? "?includeSecrets=1" : "";
4625
+ }
4626
+ async function listProvidersImpl(callApi2, args = {}) {
4627
+ const res = await callApi2(`/api/providers${includeSecretsQuery(args.includeSecrets)}`);
4078
4628
  if (!res.ok) return toolError(`GET /api/providers returned ${res.status}`);
4079
4629
  return textJson(await res.json());
4080
4630
  }
4081
- async function getProviderImpl(callApi2, id) {
4082
- const res = await callApi2(`/api/providers/${encodeURIComponent(id)}`);
4631
+ async function getProviderImpl(callApi2, id, args = {}) {
4632
+ const res = await callApi2(
4633
+ `/api/providers/${encodeURIComponent(id)}${includeSecretsQuery(args.includeSecrets)}`
4634
+ );
4083
4635
  if (!res.ok) return toolError(`GET /api/providers/${id} returned ${res.status}`);
4084
4636
  return textJson(await res.json());
4085
4637
  }
@@ -4302,21 +4854,24 @@ function registerTools(server) {
4302
4854
  "inspector_list_providers",
4303
4855
  {
4304
4856
  title: "List configured LLM providers",
4305
- description: "Returns the full ProviderConfig array, including apiKey in PLAINTEXT. MCP is localhost-only any process that can call /api/mcp can also read <dataDir>/providers.json directly, so the apiKey is not redacted. Do not expose this MCP server to non-trusted local processes.",
4306
- inputSchema: object({})
4857
+ description: "Returns the configured ProviderConfig array with apiKey redacted by default. Set includeSecrets=true only when the user explicitly needs plaintext local provider keys; this exposes secrets from <dataDir>/providers.json.",
4858
+ inputSchema: object({
4859
+ includeSecrets: boolean().optional().describe("Return plaintext provider API keys. Exposes local secrets when true.")
4860
+ })
4307
4861
  },
4308
- () => safeCall(() => listProvidersImpl(callApi))
4862
+ (args) => safeCall(() => listProvidersImpl(callApi, args))
4309
4863
  );
4310
4864
  server.registerTool(
4311
4865
  "inspector_get_provider",
4312
4866
  {
4313
4867
  title: "Get a single provider by id",
4314
- description: "Returns the full ProviderConfig for the given id, including apiKey in PLAINTEXT (same posture as inspector_list_providers).",
4868
+ description: "Returns one ProviderConfig with apiKey redacted by default. Set includeSecrets=true only when the user explicitly needs the plaintext local provider key.",
4315
4869
  inputSchema: object({
4316
- id: string().describe("The provider id.")
4870
+ id: string().describe("The provider id."),
4871
+ includeSecrets: boolean().optional().describe("Return the plaintext provider API key. Exposes a local secret when true.")
4317
4872
  })
4318
4873
  },
4319
- ({ id }) => safeCall(() => getProviderImpl(callApi, id))
4874
+ ({ id, includeSecrets }) => safeCall(() => getProviderImpl(callApi, id, { includeSecrets }))
4320
4875
  );
4321
4876
  server.registerTool(
4322
4877
  "inspector_replay_log",
@@ -4452,23 +5007,40 @@ const Route$l = createFileRoute("/api/mcp")({
4452
5007
  const DeleteBodySchema = object({
4453
5008
  ids: array(number().int().positive()).optional()
4454
5009
  }).optional();
5010
+ function parseNonNegativeInt(value, fallback) {
5011
+ if (value === null) return fallback;
5012
+ const parsed = Number(value);
5013
+ if (!Number.isInteger(parsed) || parsed < 0) return fallback;
5014
+ return parsed;
5015
+ }
5016
+ function parsePositiveInt(value, fallback) {
5017
+ if (value === null) return fallback;
5018
+ const parsed = Number(value);
5019
+ if (!Number.isInteger(parsed) || parsed < 1) return fallback;
5020
+ return parsed;
5021
+ }
4455
5022
  const Route$k = createFileRoute("/api/logs")({
4456
5023
  server: {
4457
5024
  handlers: {
4458
- GET: ({ request }) => {
5025
+ GET: async ({ request }) => {
4459
5026
  const url = new URL(request.url);
5027
+ if (url.searchParams.get("stats") === "1") {
5028
+ return Response.json(getLogStorageStats());
5029
+ }
5030
+ const replayOf = url.searchParams.get("replayOf");
5031
+ if (replayOf !== null) {
5032
+ const id = Number(replayOf);
5033
+ if (!Number.isInteger(id) || id <= 0) {
5034
+ return Response.json({ error: "Invalid replayOf log ID" }, { status: 400 });
5035
+ }
5036
+ const logs = getReplayLogsForSource(id);
5037
+ return Response.json({ logs, total: logs.length, offset: 0, limit: logs.length });
5038
+ }
4460
5039
  const sessionId = url.searchParams.get("sessionId") ?? void 0;
4461
5040
  const model = url.searchParams.get("model") ?? void 0;
4462
- const offset = Number(url.searchParams.get("offset") ?? 0);
4463
- const limit = Number(url.searchParams.get("limit") ?? 50);
4464
- const allLogs = getFilteredLogs(sessionId, model);
4465
- const paginatedLogs = allLogs.slice(offset, offset + limit);
4466
- return Response.json({
4467
- logs: paginatedLogs,
4468
- total: allLogs.length,
4469
- offset,
4470
- limit
4471
- });
5041
+ const offset = parseNonNegativeInt(url.searchParams.get("offset"), 0);
5042
+ const limit = parsePositiveInt(url.searchParams.get("limit"), 50);
5043
+ return Response.json(await listLogsPage({ sessionId, model, offset, limit }));
4472
5044
  },
4473
5045
  DELETE: async ({ request }) => {
4474
5046
  let body = void 0;
@@ -4489,11 +5061,21 @@ const Route$k = createFileRoute("/api/logs")({
4489
5061
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
4490
5062
  }
4491
5063
  if (body?.ids !== void 0 && body.ids.length > 0) {
4492
- const result2 = clearLogsByIds(body.ids);
4493
- return Response.json({ success: true, cleared: result2.cleared });
5064
+ const result2 = await clearPersistedLogsByIds(body.ids);
5065
+ return Response.json({
5066
+ success: true,
5067
+ cleared: result2.cleared,
5068
+ logFilesRewritten: result2.logFilesRewritten,
5069
+ chunkFilesDeleted: result2.chunkFilesDeleted
5070
+ });
4494
5071
  }
4495
- const result = clearAllLogs();
4496
- return Response.json({ success: true, cleared: result.cleared });
5072
+ const result = await clearPersistedLogStorage();
5073
+ return Response.json({
5074
+ success: true,
5075
+ cleared: result.cleared,
5076
+ logFilesDeleted: result.logFilesDeleted,
5077
+ chunkFilesDeleted: result.chunkFilesDeleted
5078
+ });
4497
5079
  }
4498
5080
  }
4499
5081
  }
@@ -5015,13 +5597,15 @@ const ProviderUpdateSchema = object({
5015
5597
  const Route$e = createFileRoute("/api/providers/$providerId")({
5016
5598
  server: {
5017
5599
  handlers: {
5018
- GET: ({ params }) => {
5600
+ GET: ({ params, request }) => {
5601
+ const url = new URL(request.url);
5602
+ const includeSecrets = url.searchParams.get("includeSecrets") === "1";
5019
5603
  const providers = getProviders();
5020
5604
  const provider = providers.find((p) => p.id === params.providerId);
5021
5605
  if (!provider) {
5022
5606
  return Response.json({ error: "Provider not found" }, { status: 404 });
5023
5607
  }
5024
- return Response.json(provider);
5608
+ return Response.json(includeSecrets ? provider : redactProvider(provider));
5025
5609
  },
5026
5610
  PUT: async ({ params, request }) => {
5027
5611
  const parsed = ProviderUpdateSchema.safeParse(await request.json());
@@ -5044,6 +5628,7 @@ const Route$e = createFileRoute("/api/providers/$providerId")({
5044
5628
  }
5045
5629
  }
5046
5630
  });
5631
+ const INITIAL_STREAM_LOG_LIMIT = 500;
5047
5632
  const Route$d = createFileRoute("/api/logs/stream")({
5048
5633
  server: {
5049
5634
  handlers: {
@@ -5085,9 +5670,15 @@ const Route$d = createFileRoute("/api/logs/stream")({
5085
5670
  }, 3e4);
5086
5671
  request.signal.addEventListener("abort", cleanup, { once: true });
5087
5672
  const stream = new ReadableStream({
5088
- start(controller) {
5673
+ async start(controller) {
5089
5674
  controllerRef = controller;
5090
- const logs = getFilteredLogs(sessionId, model);
5675
+ const result = await listLogsPage({
5676
+ sessionId,
5677
+ model,
5678
+ offset: 0,
5679
+ limit: INITIAL_STREAM_LOG_LIMIT
5680
+ });
5681
+ const logs = result.logs;
5091
5682
  const initData = `data: ${JSON.stringify({ type: "init", logs })}
5092
5683
 
5093
5684
  `;
@@ -6501,6 +7092,37 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
6501
7092
  const mockLog = { ...log };
6502
7093
  const responseText = formatHandler.extractStream(fullResponse, mockLog, model, false);
6503
7094
  const tokens = formatHandler.extractTokens(responseText);
7095
+ const savedReplayLog = await addTestLogEntry({
7096
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7097
+ method: log.method,
7098
+ path: log.path,
7099
+ model,
7100
+ sessionId: log.sessionId,
7101
+ rawRequestBody: modifiedBody,
7102
+ responseStatus: upstreamRes.status,
7103
+ responseText,
7104
+ inputTokens: tokens.inputTokens ?? null,
7105
+ outputTokens: tokens.outputTokens ?? null,
7106
+ cacheCreationInputTokens: tokens.cacheCreationInputTokens ?? null,
7107
+ cacheReadInputTokens: tokens.cacheReadInputTokens ?? null,
7108
+ elapsedMs,
7109
+ streaming: true,
7110
+ userAgent: log.userAgent,
7111
+ origin: log.origin,
7112
+ rawHeaders: log.rawHeaders,
7113
+ headers: log.headers,
7114
+ apiFormat: log.apiFormat,
7115
+ isTest: true,
7116
+ replayOfLogId: log.id,
7117
+ providerName: log.providerName,
7118
+ clientPort: log.clientPort,
7119
+ clientPid: log.clientPid,
7120
+ clientCwd: log.clientCwd,
7121
+ clientProjectFolder: log.clientProjectFolder,
7122
+ streamingChunks: void 0,
7123
+ streamingChunksPath: null,
7124
+ error: null
7125
+ });
6504
7126
  return Response.json({
6505
7127
  success: true,
6506
7128
  responseStatus: upstreamRes.status,
@@ -6508,11 +7130,43 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
6508
7130
  inputTokens: tokens.inputTokens ?? void 0,
6509
7131
  outputTokens: tokens.outputTokens ?? void 0,
6510
7132
  elapsedMs,
6511
- streaming: true
7133
+ streaming: true,
7134
+ replayLogId: savedReplayLog.id
6512
7135
  });
6513
7136
  } else {
6514
7137
  const responseText = await upstreamRes.text();
6515
7138
  const tokens = formatHandler.extractTokens(responseText);
7139
+ const savedReplayLog = await addTestLogEntry({
7140
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7141
+ method: log.method,
7142
+ path: log.path,
7143
+ model,
7144
+ sessionId: log.sessionId,
7145
+ rawRequestBody: modifiedBody,
7146
+ responseStatus: upstreamRes.status,
7147
+ responseText,
7148
+ inputTokens: tokens.inputTokens ?? null,
7149
+ outputTokens: tokens.outputTokens ?? null,
7150
+ cacheCreationInputTokens: tokens.cacheCreationInputTokens ?? null,
7151
+ cacheReadInputTokens: tokens.cacheReadInputTokens ?? null,
7152
+ elapsedMs,
7153
+ streaming: false,
7154
+ userAgent: log.userAgent,
7155
+ origin: log.origin,
7156
+ rawHeaders: log.rawHeaders,
7157
+ headers: log.headers,
7158
+ apiFormat: log.apiFormat,
7159
+ isTest: true,
7160
+ replayOfLogId: log.id,
7161
+ providerName: log.providerName,
7162
+ clientPort: log.clientPort,
7163
+ clientPid: log.clientPid,
7164
+ clientCwd: log.clientCwd,
7165
+ clientProjectFolder: log.clientProjectFolder,
7166
+ streamingChunks: void 0,
7167
+ streamingChunksPath: null,
7168
+ error: null
7169
+ });
6516
7170
  return Response.json({
6517
7171
  success: true,
6518
7172
  responseStatus: upstreamRes.status,
@@ -6520,7 +7174,8 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
6520
7174
  inputTokens: tokens.inputTokens ?? void 0,
6521
7175
  outputTokens: tokens.outputTokens ?? void 0,
6522
7176
  elapsedMs,
6523
- streaming: false
7177
+ streaming: false,
7178
+ replayLogId: savedReplayLog.id
6524
7179
  });
6525
7180
  }
6526
7181
  }
@@ -7204,8 +7859,9 @@ export {
7204
7859
  ProviderTestResultsSchema as j,
7205
7860
  createFailedProviderTestResults as k,
7206
7861
  resolveProviderContextWindow as l,
7207
- isPlainRecord as m,
7208
- router as n,
7862
+ maskApiKey as m,
7863
+ isPlainRecord as n,
7864
+ router as o,
7209
7865
  parseOpenAIResponse as p,
7210
7866
  requestFormatForPath as r,
7211
7867
  safeGetOwnProperty as s