@tonyclaw/agent-inspector 3.1.20 → 3.1.22

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 (57) hide show
  1. package/.output/backend/nitro.json +1 -1
  2. package/.output/cli.js +137 -20
  3. package/.output/server/_ssr/index.mjs +2 -2
  4. package/.output/server/_ssr/{router-DpXchCXm.mjs → router-DKFaWvAr.mjs} +2371 -1171
  5. package/.output/server/_tanstack-start-manifest_v-ClTaMkEj.mjs +4 -0
  6. package/.output/server/index.mjs +1 -1
  7. package/.output/ui/assets/{CompareDrawer-4i1QHaQf.js → CompareDrawer-pPcsyvc6.js} +1 -1
  8. package/.output/ui/assets/{InspectorPet-Cw6VJLr7.js → InspectorPet-DR3JEqze.js} +1 -1
  9. package/.output/ui/assets/ProxyViewerContainer-DOyYRnb-.js +60 -0
  10. package/.output/ui/assets/{ReplayDialog-DTs575iW.js → ReplayDialog-Z-1-gUDm.js} +1 -1
  11. package/.output/ui/assets/{RequestAnatomy-Cl6Es3gv.js → RequestAnatomy-BsnZjE5G.js} +1 -1
  12. package/.output/ui/assets/{ResponseView-DNCCLkzG.js → ResponseView-HY64_ZY2.js} +1 -1
  13. package/.output/ui/assets/{StreamingChunkSequence-DKaxRL9U.js → StreamingChunkSequence-CfQUFbTA.js} +1 -1
  14. package/.output/ui/assets/{_sessionId-rkR3WgRq.js → _sessionId-Dq97lD86.js} +1 -1
  15. package/.output/ui/assets/{_sessionId-C-YjWz50.js → _sessionId-_F9wwa3a.js} +1 -1
  16. package/.output/ui/assets/index-B-Qod-aA.css +1 -0
  17. package/.output/ui/assets/{index-DxiecWHw.js → index-B2yLwOEu.js} +1 -1
  18. package/.output/ui/assets/{index-D0d-V67x.js → index-CN-gZDqV.js} +1 -1
  19. package/.output/ui/assets/{index-DYbzZTDM.js → index-DOAGzrq2.js} +1 -1
  20. package/.output/ui/assets/{index-B48RD1-5.js → index-DYnqmBBx.js} +2 -2
  21. package/.output/ui/assets/{json-viewer-CjAwZhHA.js → json-viewer-B2SQN0Lg.js} +1 -1
  22. package/.output/ui/assets/{jszip.min-DEGEN1HP.js → jszip.min-Dpks67u2.js} +1 -1
  23. package/.output/ui/index.html +2 -2
  24. package/.output/workers/logFinalizer.worker.js +258 -0
  25. package/.output/workers/sessionWorkerEntry.js +258 -0
  26. package/package.json +13 -6
  27. package/src/backend/routes/api/logs.$id.replay.ts +6 -1
  28. package/src/backend/routes/api/logs.stream.ts +7 -0
  29. package/src/backend/routes/api/providers.$providerId.model-metadata.ts +15 -4
  30. package/src/backend/routes/api/storage.ts +56 -0
  31. package/src/backend/routes/metrics.ts +14 -0
  32. package/src/cli/doctor.ts +135 -2
  33. package/src/cli.ts +7 -4
  34. package/src/components/ProxyViewerContainer.tsx +41 -24
  35. package/src/components/providers/SettingsDialog.tsx +261 -70
  36. package/src/components/proxy-viewer/LogEntry.tsx +19 -5
  37. package/src/components/proxy-viewer/bodyHydration.ts +67 -0
  38. package/src/components/proxy-viewer/proxyViewerContainerLogic.ts +78 -0
  39. package/src/knowledge/openclawClient.ts +11 -2
  40. package/src/knowledge/openclawGatewayClient.ts +6 -1
  41. package/src/lib/resourceLimits.ts +191 -0
  42. package/src/lib/safeFetch.ts +428 -20
  43. package/src/proxy/ecosystemTasks.ts +20 -0
  44. package/src/proxy/handler.ts +17 -4
  45. package/src/proxy/identityProxy.ts +36 -16
  46. package/src/proxy/logIndex.ts +38 -6
  47. package/src/proxy/logger.ts +4 -0
  48. package/src/proxy/rawStreamCapture.ts +16 -1
  49. package/src/proxy/runtimeHealth.ts +4 -2
  50. package/src/proxy/runtimeMetrics.ts +149 -0
  51. package/src/proxy/sessionRuntime.ts +38 -0
  52. package/src/proxy/sqliteLogIndex.ts +51 -0
  53. package/src/proxy/storageLifecycle.ts +432 -0
  54. package/src/proxy/store.ts +62 -12
  55. package/.output/server/_tanstack-start-manifest_v-DnbdNeun.mjs +0 -4
  56. package/.output/ui/assets/ProxyViewerContainer-BST0B7lY.js +0 -60
  57. package/.output/ui/assets/index-IBEDJoV_.css +0 -1
@@ -2,19 +2,21 @@ import { c as createRouter, a as createRootRoute, b as createFileRoute } from ".
2
2
  import { h as getRouterBasePath, r as runWithRequestPublicBasePath, g as getPublicBasePath, b as appendBasePathToOrigin, c as getPublicOrigin, w as withBasePath, s as stripBasePath, a as getConfiguredBasePath, n as normalizePublicBasePath, f as AGENT_INSPECTOR_BASE_PATH_ENV, d as getPublicPort, A as AGENT_INSPECTOR_PUBLIC_BASE_PATH_HEADER, e as AGENT_INSPECTOR_PUBLIC_PORT_HEADER } from "./publicBasePathContext-IH2ilOfu.mjs";
3
3
  import { randomUUID, timingSafeEqual, createHash, createDecipheriv, randomBytes, createCipheriv } from "node:crypto";
4
4
  import fs, { readdir, open, readFile, unlink, appendFile, writeFile, rename, stat, lstat, link, rm, statfs } from "node:fs/promises";
5
- import path, { basename, join, dirname, isAbsolute, resolve, win32, posix, parse, sep, relative } from "node:path";
6
- import { existsSync, readFileSync, writeFileSync, renameSync, createReadStream, mkdirSync, readdirSync, statSync, unlinkSync, copyFileSync, rmSync, constants, lstatSync, realpathSync, cpSync, openSync, fstatSync, fchmodSync, closeSync, writeSync, fsyncSync, createWriteStream } from "node:fs";
5
+ import path, { dirname, join, basename, relative, resolve, isAbsolute, win32, posix, parse, sep } from "node:path";
6
+ import { unlinkSync, existsSync, readFileSync, writeFileSync, renameSync, readdirSync, lstatSync, rmSync, cpSync, statSync, createReadStream, mkdirSync, copyFileSync, constants, realpathSync, openSync, fstatSync, fchmodSync, closeSync, writeSync, fsyncSync, createWriteStream } from "node:fs";
7
7
  import { spawn, execFileSync, execFile, spawnSync } from "node:child_process";
8
8
  import { Buffer } from "node:buffer";
9
9
  import { fileURLToPath, pathToFileURL } from "node:url";
10
10
  import { Worker } from "node:worker_threads";
11
11
  import { createInterface } from "node:readline";
12
+ import assert from "node:assert/strict";
12
13
  import { C as Conf } from "../_libs/conf.mjs";
13
14
  import { randomUUID as randomUUID$1 } from "crypto";
14
15
  import { promisify } from "node:util";
15
16
  import { lookup } from "node:dns/promises";
17
+ import { request as request$1 } from "node:http";
18
+ import { request } from "node:https";
16
19
  import { isIP, createServer } from "node:net";
17
- import assert from "node:assert/strict";
18
20
  import { M as McpServer, W as WebStandardStreamableHTTPServerTransport, R as ResourceTemplate } from "../_libs/modelcontextprotocol__server.mjs";
19
21
  import { J as JSZip } from "../_libs/jszip.mjs";
20
22
  import { homedir } from "node:os";
@@ -67,7 +69,7 @@ import "../_libs/lie.mjs";
67
69
  import "../_libs/immediate.mjs";
68
70
  import "../_libs/setimmediate.mjs";
69
71
  import "../_libs/pako.mjs";
70
- const Route$U = createRootRoute();
72
+ const Route$W = createRootRoute();
71
73
  const PRIVATE_DIRECTORY_MODE = 448;
72
74
  const PRIVATE_FILE_MODE = 384;
73
75
  const securedPathFingerprints = /* @__PURE__ */ new Map();
@@ -1300,8 +1302,8 @@ function canAppendCapturedLogFileOnExit(logDir, filePath) {
1300
1302
  return false;
1301
1303
  }
1302
1304
  }
1303
- const DAY_MS = 24 * 60 * 60 * 1e3;
1304
- const DEFAULT_RETENTION_DAYS = 7;
1305
+ const DAY_MS$1 = 24 * 60 * 60 * 1e3;
1306
+ const DEFAULT_RETENTION_DAYS$1 = 7;
1305
1307
  const DEFAULT_APP_LOG_MAX_BYTES = 10 * 1024 * 1024;
1306
1308
  const DEFAULT_APP_LOG_MAX_FILES = 5;
1307
1309
  function writeStderrFallback(message, error) {
@@ -1329,6 +1331,9 @@ function getInspectorLogPath() {
1329
1331
  }
1330
1332
  return path.join(getDataDir(), "logs", "inspector.log");
1331
1333
  }
1334
+ function resolveApplicationLogPath() {
1335
+ return getInspectorLogPath();
1336
+ }
1332
1337
  function readBoundedInteger(name2, fallback, minimum, maximum) {
1333
1338
  const raw = process.env[name2];
1334
1339
  if (raw === void 0 || raw === "") return fallback;
@@ -1337,7 +1342,7 @@ function readBoundedInteger(name2, fallback, minimum, maximum) {
1337
1342
  return parsed;
1338
1343
  }
1339
1344
  function getApplicationLogPolicy() {
1340
- const retentionDays = readBoundedInteger("LOG_RETENTION_DAYS", DEFAULT_RETENTION_DAYS, 1, 3650);
1345
+ const retentionDays = readBoundedInteger("LOG_RETENTION_DAYS", DEFAULT_RETENTION_DAYS$1, 1, 3650);
1341
1346
  return {
1342
1347
  maxBytes: readBoundedInteger(
1343
1348
  "AGENT_INSPECTOR_APP_LOG_MAX_BYTES",
@@ -1351,7 +1356,7 @@ function getApplicationLogPolicy() {
1351
1356
  1,
1352
1357
  100
1353
1358
  ),
1354
- retentionMs: retentionDays * DAY_MS
1359
+ retentionMs: retentionDays * DAY_MS$1
1355
1360
  };
1356
1361
  }
1357
1362
  function getRotatedGeneration(entry, baseName) {
@@ -3667,6 +3672,143 @@ function readNumber$1(row, name2) {
3667
3672
  const value = readProperty$2(row, name2);
3668
3673
  return typeof value === "number" && Number.isFinite(value) ? value : null;
3669
3674
  }
3675
+ function readString$2(row, name2) {
3676
+ const value = readProperty$2(row, name2);
3677
+ return typeof value === "string" ? value : null;
3678
+ }
3679
+ function readBoolean(row, name2) {
3680
+ return readNumber$1(row, name2) === 1;
3681
+ }
3682
+ function readApiFormat(row) {
3683
+ const value = readString$2(row, "api_format");
3684
+ switch (value) {
3685
+ case "anthropic":
3686
+ return "anthropic";
3687
+ case "openai":
3688
+ return "openai";
3689
+ case "unknown":
3690
+ case null:
3691
+ return "unknown";
3692
+ default:
3693
+ return "unknown";
3694
+ }
3695
+ }
3696
+ function readCaptureIncompleteReason(row) {
3697
+ const value = readString$2(row, "capture_incomplete_reason");
3698
+ switch (value) {
3699
+ case "capacity-exceeded":
3700
+ case "pipeline-closed":
3701
+ case "write-failed":
3702
+ case "finalize-failed":
3703
+ case "upstream-stream-error":
3704
+ case "shutdown":
3705
+ return value;
3706
+ case null:
3707
+ default:
3708
+ return null;
3709
+ }
3710
+ }
3711
+ function readStopReason(row) {
3712
+ const value = readString$2(row, "stop_reason");
3713
+ switch (value) {
3714
+ case "end_turn":
3715
+ case "tool_use":
3716
+ case "stop":
3717
+ case "length":
3718
+ return value;
3719
+ case null:
3720
+ default:
3721
+ return null;
3722
+ }
3723
+ }
3724
+ function readWarnings(row) {
3725
+ const value = readString$2(row, "warnings_json");
3726
+ if (value === null) return void 0;
3727
+ try {
3728
+ const parsed = JSON.parse(value);
3729
+ if (!Array.isArray(parsed)) return void 0;
3730
+ const warnings = [];
3731
+ for (const item of parsed) {
3732
+ if (typeof item !== "string") return void 0;
3733
+ warnings.push(item);
3734
+ }
3735
+ return warnings;
3736
+ } catch {
3737
+ return void 0;
3738
+ }
3739
+ }
3740
+ function readToolTraceEvents(row) {
3741
+ const value = readString$2(row, "tool_trace_events_json");
3742
+ if (value === null) return void 0;
3743
+ try {
3744
+ const parsed = JSON.parse(value);
3745
+ const result = ToolTraceEventSchema.array().safeParse(parsed);
3746
+ return result.success ? result.data : void 0;
3747
+ } catch {
3748
+ return void 0;
3749
+ }
3750
+ }
3751
+ function readRequiredNumber(row, name2) {
3752
+ const value = readNumber$1(row, name2);
3753
+ return value === null || !Number.isInteger(value) ? null : value;
3754
+ }
3755
+ function summaryFromRow(row, id) {
3756
+ return {
3757
+ id,
3758
+ timestamp: readString$2(row, "timestamp") ?? "",
3759
+ method: readString$2(row, "method") ?? "",
3760
+ path: readString$2(row, "path") ?? "",
3761
+ model: readString$2(row, "model"),
3762
+ sessionId: readString$2(row, "session_id"),
3763
+ responseStatus: readNumber$1(row, "response_status"),
3764
+ inputTokens: readNumber$1(row, "input_tokens"),
3765
+ outputTokens: readNumber$1(row, "output_tokens"),
3766
+ cacheCreationInputTokens: readNumber$1(row, "cache_creation_input_tokens"),
3767
+ cacheReadInputTokens: readNumber$1(row, "cache_read_input_tokens"),
3768
+ elapsedMs: readNumber$1(row, "elapsed_ms"),
3769
+ firstChunkMs: readNumber$1(row, "first_chunk_ms"),
3770
+ totalStreamMs: readNumber$1(row, "total_stream_ms"),
3771
+ tokensPerSecond: readNumber$1(row, "tokens_per_second"),
3772
+ streaming: readBoolean(row, "streaming"),
3773
+ userAgent: readString$2(row, "user_agent"),
3774
+ origin: readString$2(row, "origin"),
3775
+ apiFormat: readApiFormat(row),
3776
+ isTest: readBoolean(row, "is_test"),
3777
+ replayOfLogId: readNumber$1(row, "replay_of_log_id"),
3778
+ providerName: readString$2(row, "provider_name"),
3779
+ clientPort: readNumber$1(row, "client_port"),
3780
+ clientPid: readNumber$1(row, "client_pid"),
3781
+ clientCwd: readString$2(row, "client_cwd"),
3782
+ clientProjectFolder: readString$2(row, "client_project_folder"),
3783
+ streamingChunksPath: readString$2(row, "streaming_chunks_path"),
3784
+ rawRequestBodyBytes: readNumber$1(row, "raw_request_body_bytes"),
3785
+ responseTextBytes: readNumber$1(row, "response_text_bytes"),
3786
+ captureIncomplete: readBoolean(row, "capture_incomplete"),
3787
+ droppedChunks: readNumber$1(row, "dropped_chunks") ?? 0,
3788
+ droppedBytes: readNumber$1(row, "dropped_bytes") ?? 0,
3789
+ captureIncompleteReason: readCaptureIncompleteReason(row),
3790
+ warnings: readWarnings(row),
3791
+ stopReason: readStopReason(row),
3792
+ toolTraceEvents: readToolTraceEvents(row),
3793
+ error: readString$2(row, "error")
3794
+ };
3795
+ }
3796
+ function entryFromRow(row) {
3797
+ const id = readRequiredNumber(row, "id");
3798
+ const file = readString$2(row, "file");
3799
+ const byteOffset = readRequiredNumber(row, "byte_offset");
3800
+ const byteLength = readRequiredNumber(row, "byte_length");
3801
+ if (id === null || file === null || byteOffset === null || byteLength === null) return null;
3802
+ return {
3803
+ id,
3804
+ file,
3805
+ byteOffset,
3806
+ byteLength,
3807
+ sessionId: readString$2(row, "session_id"),
3808
+ model: readString$2(row, "model"),
3809
+ summary: summaryFromRow(row, id)
3810
+ };
3811
+ }
3670
3812
  async function getSqliteLogIndexMaxId() {
3671
3813
  const state = await getSqliteState();
3672
3814
  if (state.status !== "ready") return null;
@@ -3679,6 +3821,44 @@ async function upsertSqliteLogIndexEntry(entry) {
3679
3821
  if (state.status !== "ready") return false;
3680
3822
  return runPrepared$1(state.db, UPSERT_SQL$1, entryParams(entry));
3681
3823
  }
3824
+ function sqliteCursorWhereClause(filters) {
3825
+ const clauses = [];
3826
+ const params = [];
3827
+ if (filters.sessionId !== void 0) {
3828
+ clauses.push("session_id = ?");
3829
+ params.push(filters.sessionId);
3830
+ }
3831
+ if (filters.model !== void 0) {
3832
+ clauses.push("model = ?");
3833
+ params.push(filters.model);
3834
+ }
3835
+ if (filters.clientPid !== void 0) {
3836
+ clauses.push("client_pid = ?");
3837
+ params.push(filters.clientPid);
3838
+ }
3839
+ return {
3840
+ sql: clauses.length === 0 ? "" : ` WHERE ${clauses.join(" AND ")}`,
3841
+ params
3842
+ };
3843
+ }
3844
+ async function listSqliteLogCursorEntries(filters) {
3845
+ const state = await getSqliteState();
3846
+ if (state.status !== "ready") return null;
3847
+ const where = sqliteCursorWhereClause(filters);
3848
+ const rows = allPrepared$1(
3849
+ state.db,
3850
+ `SELECT * FROM log_index${where.sql} ORDER BY id ASC`,
3851
+ where.params
3852
+ );
3853
+ if (rows === null) return null;
3854
+ const entries = [];
3855
+ for (const row of rows) {
3856
+ const entry = entryFromRow(row);
3857
+ if (entry === null || entry.summary === void 0) return null;
3858
+ entries.push(entry);
3859
+ }
3860
+ return entries;
3861
+ }
3682
3862
  async function syncSqliteLogIndexEntries(entries) {
3683
3863
  const state = await getSqliteState();
3684
3864
  if (state.status !== "ready") return false;
@@ -4177,8 +4357,33 @@ function createEmptyIndex() {
4177
4357
  maxId: 0
4178
4358
  };
4179
4359
  }
4360
+ function createIndexFromEntries(entries) {
4361
+ const index = createEmptyIndex();
4362
+ for (const entry of entries) {
4363
+ index.entries[entry.id] = entry;
4364
+ index.maxId = Math.max(index.maxId, entry.id);
4365
+ }
4366
+ return index;
4367
+ }
4368
+ function sqliteProjectionReferencesExistingLedgers(entries) {
4369
+ const logDir = resolveLogDir();
4370
+ for (const entry of entries) {
4371
+ if (!entry.file.endsWith(".jsonl")) return false;
4372
+ if (!existsSync(join(logDir, entry.file))) return false;
4373
+ }
4374
+ return true;
4375
+ }
4180
4376
  async function loadIndex() {
4181
4377
  if (cachedIndex !== null) return cachedIndex;
4378
+ const sqliteEntries = await listSqliteLogCursorEntries({});
4379
+ if (sqliteEntries !== null) {
4380
+ if (sqliteProjectionReferencesExistingLedgers(sqliteEntries)) {
4381
+ cachedIndex = createIndexFromEntries(sqliteEntries);
4382
+ allocatedMaxId = Math.max(allocatedMaxId, cachedIndex.maxId);
4383
+ return cachedIndex;
4384
+ }
4385
+ logger.warn("[logIndex] SQLite projection references missing ledgers; rebuilding from JSONL");
4386
+ }
4182
4387
  return await rebuildIndex();
4183
4388
  }
4184
4389
  async function saveIndex(index) {
@@ -4215,8 +4420,10 @@ async function addToIndex(id, file, byteOffset, byteLength, metadata) {
4215
4420
  if (id > index.maxId) {
4216
4421
  index.maxId = id;
4217
4422
  }
4218
- void upsertSqliteLogIndexEntry(entry);
4219
- scheduleIndexFlush();
4423
+ const sqliteUpdated = await upsertSqliteLogIndexEntry(entry);
4424
+ if (!sqliteUpdated) {
4425
+ scheduleIndexFlush();
4426
+ }
4220
4427
  }
4221
4428
  async function updateLogIndexEntryMetadata(log) {
4222
4429
  const index = await loadIndex();
@@ -4227,14 +4434,15 @@ async function updateLogIndexEntryMetadata(log) {
4227
4434
  ...createLogIndexEntryMetadata(log)
4228
4435
  };
4229
4436
  index.entries[log.id] = updated;
4230
- await upsertSqliteLogIndexEntry(updated);
4231
- scheduleIndexFlush();
4437
+ const sqliteUpdated = await upsertSqliteLogIndexEntry(updated);
4438
+ if (!sqliteUpdated) scheduleIndexFlush();
4232
4439
  }
4233
4440
  let indexFlushScheduled = false;
4234
4441
  let indexFlushTimeout = null;
4235
4442
  async function flushIndexAsync() {
4236
4443
  indexFlushScheduled = false;
4237
4444
  indexFlushTimeout = null;
4445
+ if (await listSqliteLogCursorEntries({}) !== null) return;
4238
4446
  const index = await loadIndex();
4239
4447
  await saveIndex(index);
4240
4448
  }
@@ -4251,6 +4459,7 @@ async function flushIndex() {
4251
4459
  indexFlushTimeout = null;
4252
4460
  }
4253
4461
  indexFlushScheduled = false;
4462
+ if (await listSqliteLogCursorEntries({}) !== null) return;
4254
4463
  const index = await loadIndex();
4255
4464
  await saveIndex(index);
4256
4465
  }
@@ -5001,135 +5210,917 @@ function getSessionIds() {
5001
5210
  function getSessionSnapshots() {
5002
5211
  return [...sessions$1.values()].map((session) => ({ ...session }));
5003
5212
  }
5004
- const MAX_MEMORY_CACHE = 100;
5005
- const memoryCache = /* @__PURE__ */ new Map();
5006
- const MAX_CURSOR_PAGE_INDEXES = 16;
5007
- const logCursorPageIndexes = /* @__PURE__ */ new Map();
5008
- let sessionArchiveQueue = Promise.resolve();
5009
- const SESSION_ARCHIVE_BATCH_SIZE = 128;
5010
- const SESSION_ARCHIVE_FLUSH_DELAY_MS = 10;
5011
- let sessionArchiveBuffer = [];
5012
- let sessionArchiveFlushTimeout = null;
5013
- function drainSessionArchiveBuffer() {
5014
- const logs = sessionArchiveBuffer;
5015
- sessionArchiveBuffer = [];
5016
- return logs;
5017
- }
5018
- function queueSessionArchiveFlush() {
5019
- const logs = drainSessionArchiveBuffer();
5020
- if (logs.length === 0) return;
5021
- sessionArchiveQueue = sessionArchiveQueue.catch(() => void 0).then(async () => {
5022
- await archiveSessionLogs(logs);
5023
- }).catch((err) => {
5024
- logger.warn("[store] Failed to archive session logs:", String(err));
5025
- });
5026
- }
5027
- function cancelSessionArchiveFlushTimer() {
5028
- if (sessionArchiveFlushTimeout === null) return;
5029
- clearTimeout(sessionArchiveFlushTimeout);
5030
- sessionArchiveFlushTimeout = null;
5031
- }
5032
- function scheduleSessionArchiveFlush() {
5033
- if (sessionArchiveFlushTimeout !== null) return;
5034
- sessionArchiveFlushTimeout = setTimeout(() => {
5035
- sessionArchiveFlushTimeout = null;
5036
- queueSessionArchiveFlush();
5037
- }, SESSION_ARCHIVE_FLUSH_DELAY_MS);
5038
- }
5039
- function cursorPageIndexKey(options) {
5040
- return JSON.stringify({
5041
- sessionId: options.sessionId ?? null,
5042
- model: options.model ?? null,
5043
- clientPid: options.clientPid ?? null
5044
- });
5045
- }
5046
- function matchesCursorIndexFilters(log, index) {
5047
- if (index.sessionId !== void 0 && getLogSessionId(log) !== index.sessionId) return false;
5048
- if (index.model !== void 0 && log.model !== index.model) return false;
5049
- if (index.clientPid !== void 0 && log.clientPid !== index.clientPid) return false;
5050
- return true;
5051
- }
5052
- function insertSortedLogId(ids, id) {
5053
- if (ids.includes(id)) return;
5054
- const insertionIndex = ids.findIndex((candidate) => candidate > id);
5055
- if (insertionIndex === -1) {
5056
- ids.push(id);
5057
- return;
5058
- }
5059
- ids.splice(insertionIndex, 0, id);
5060
- }
5061
- function upsertLogCursorPageIndexes(log) {
5062
- for (const index of logCursorPageIndexes.values()) {
5063
- if (!matchesCursorIndexFilters(log, index)) {
5064
- if (index.logsById.delete(log.id)) {
5065
- index.ids = index.ids.filter((id) => id !== log.id);
5066
- }
5067
- index.entriesById.delete(log.id);
5068
- continue;
5069
- }
5070
- index.logsById.set(log.id, log);
5071
- insertSortedLogId(index.ids, log.id);
5213
+ const MAX_REST_LOG_PAGE_SIZE = 200;
5214
+ const MAX_SEARCH_QUERY_CHARS = 512;
5215
+ const MAX_SEARCH_PROJECT_CHARS = 256;
5216
+ const MAX_PROVIDER_MODELS = 64;
5217
+ const MAX_PROVIDER_MODEL_NAME_CHARS = 256;
5218
+ const MAX_PROVIDER_MODEL_METADATA = 64;
5219
+ const MAX_SSE_CLIENTS = 32;
5220
+ const MAX_ACTIVE_PROVIDER_TESTS = 2;
5221
+ const MAX_PROVIDER_TEST_CONCURRENCY = 4;
5222
+ const MAX_ACTIVE_PROXY_REQUESTS = 16;
5223
+ const positiveInteger = number().int().positive();
5224
+ const RuntimeBudgetPolicySchema = object({
5225
+ proxy: object({
5226
+ requestBytes: positiveInteger,
5227
+ responseBytes: positiveInteger,
5228
+ headerBytes: positiveInteger,
5229
+ deadlineMs: positiveInteger,
5230
+ idleTimeoutMs: positiveInteger,
5231
+ activeRequests: positiveInteger
5232
+ }),
5233
+ control: object({
5234
+ requestBytes: positiveInteger,
5235
+ responseBytes: positiveInteger,
5236
+ headerBytes: positiveInteger,
5237
+ deadlineMs: positiveInteger,
5238
+ idleTimeoutMs: positiveInteger,
5239
+ activeRequests: positiveInteger
5240
+ }),
5241
+ upstream: object({
5242
+ fetchResponseBytes: positiveInteger,
5243
+ fetchDeadlineMs: positiveInteger,
5244
+ replayResponseBytes: positiveInteger,
5245
+ replayDeadlineMs: positiveInteger,
5246
+ metadataResponseBytes: positiveInteger,
5247
+ metadataDeadlineMs: positiveInteger,
5248
+ connectTimeoutMs: positiveInteger,
5249
+ idleTimeoutMs: positiveInteger
5250
+ }),
5251
+ pagination: object({
5252
+ restPageSize: positiveInteger,
5253
+ searchQueryChars: positiveInteger,
5254
+ searchProjectChars: positiveInteger
5255
+ }),
5256
+ sse: object({
5257
+ clients: positiveInteger,
5258
+ initLogs: positiveInteger,
5259
+ heartbeatMs: positiveInteger
5260
+ }),
5261
+ imports: object({
5262
+ archiveBytes: positiveInteger,
5263
+ entries: positiveInteger,
5264
+ entryBytes: positiveInteger,
5265
+ uncompressedBytes: positiveInteger,
5266
+ expansionRatio: positiveInteger
5267
+ }),
5268
+ fanOut: object({
5269
+ providerModels: positiveInteger,
5270
+ providerModelMetadata: positiveInteger,
5271
+ providerTestConcurrency: positiveInteger,
5272
+ activeProviderTests: positiveInteger
5273
+ }),
5274
+ queues: object({
5275
+ proxyRequests: positiveInteger,
5276
+ finalizerJobs: positiveInteger,
5277
+ rawStreamMessages: positiveInteger,
5278
+ rawStreamBytes: positiveInteger,
5279
+ backgroundWorkers: positiveInteger
5280
+ })
5281
+ }).superRefine((policy, context) => {
5282
+ if (policy.control.requestBytes > policy.proxy.requestBytes) {
5283
+ context.addIssue({
5284
+ code: "custom",
5285
+ path: ["control", "requestBytes"],
5286
+ message: "control request budget must not exceed proxy request budget"
5287
+ });
5072
5288
  }
5073
- }
5074
- function removeLogsFromCursorPageIndexes(ids) {
5075
- if (ids.size === 0) return;
5076
- for (const index of logCursorPageIndexes.values()) {
5077
- for (const id of ids) {
5078
- index.logsById.delete(id);
5079
- index.entriesById.delete(id);
5080
- }
5081
- index.ids = index.ids.filter((id) => !ids.has(id));
5289
+ if (policy.control.responseBytes > policy.proxy.responseBytes) {
5290
+ context.addIssue({
5291
+ code: "custom",
5292
+ path: ["control", "responseBytes"],
5293
+ message: "control response budget must not exceed proxy response budget"
5294
+ });
5082
5295
  }
5083
- }
5084
- function clearLogCursorPageIndexes() {
5085
- logCursorPageIndexes.clear();
5086
- }
5087
- function evictOldestIfNeeded() {
5088
- while (memoryCache.size > MAX_MEMORY_CACHE) {
5089
- const oldestKey = memoryCache.keys().next().value;
5090
- if (oldestKey === void 0) break;
5091
- memoryCache.delete(oldestKey);
5296
+ if (policy.control.activeRequests > policy.proxy.activeRequests) {
5297
+ context.addIssue({
5298
+ code: "custom",
5299
+ path: ["control", "activeRequests"],
5300
+ message: "control active request budget must not exceed proxy active request budget"
5301
+ });
5092
5302
  }
5093
- }
5094
- function normalizeLog(log) {
5095
- const sessionId = getLogSessionId(log);
5096
- const pathFormat = apiFormatForPath(log.path);
5097
- const apiFormat = pathFormat === "unknown" ? log.apiFormat : pathFormat;
5098
- const captureIncomplete = log.captureIncomplete ?? false;
5099
- const droppedChunks = log.droppedChunks ?? 0;
5100
- const droppedBytes = log.droppedBytes ?? 0;
5101
- const captureIncompleteReason = log.captureIncompleteReason ?? null;
5102
- const stopReason = log.stopReason ?? extractStopReason(log);
5103
- if (sessionId === log.sessionId && apiFormat === log.apiFormat && log.captureIncomplete !== void 0 && log.droppedChunks !== void 0 && log.droppedBytes !== void 0 && log.captureIncompleteReason !== void 0 && log.stopReason !== void 0) {
5104
- return log;
5303
+ if (policy.upstream.fetchResponseBytes > policy.proxy.responseBytes) {
5304
+ context.addIssue({
5305
+ code: "custom",
5306
+ path: ["upstream", "fetchResponseBytes"],
5307
+ message: "upstream fetch response budget must not exceed proxy response budget"
5308
+ });
5105
5309
  }
5106
- return {
5107
- ...log,
5108
- sessionId,
5109
- apiFormat,
5110
- captureIncomplete,
5111
- droppedChunks,
5112
- droppedBytes,
5113
- captureIncompleteReason,
5114
- stopReason
5115
- };
5116
- }
5117
- function addToCache(log) {
5118
- const cachedLog = normalizeLog(log);
5119
- if (memoryCache.has(cachedLog.id)) {
5120
- memoryCache.delete(cachedLog.id);
5310
+ if (policy.upstream.replayResponseBytes > policy.proxy.responseBytes) {
5311
+ context.addIssue({
5312
+ code: "custom",
5313
+ path: ["upstream", "replayResponseBytes"],
5314
+ message: "upstream replay response budget must not exceed proxy response budget"
5315
+ });
5121
5316
  }
5122
- memoryCache.set(cachedLog.id, cachedLog);
5123
- upsertLogCursorPageIndexes(cachedLog);
5124
- evictOldestIfNeeded();
5125
- }
5126
- function archiveLogSnapshot(log) {
5127
- sessionArchiveBuffer.push(normalizeLog(log));
5128
- if (sessionArchiveBuffer.length >= SESSION_ARCHIVE_BATCH_SIZE) {
5129
- cancelSessionArchiveFlushTimer();
5130
- queueSessionArchiveFlush();
5131
- } else if (sessionArchiveBuffer.length === 1) {
5132
- scheduleSessionArchiveFlush();
5317
+ if (policy.upstream.metadataResponseBytes > policy.control.responseBytes) {
5318
+ context.addIssue({
5319
+ code: "custom",
5320
+ path: ["upstream", "metadataResponseBytes"],
5321
+ message: "upstream metadata response budget must not exceed control response budget"
5322
+ });
5323
+ }
5324
+ if (policy.upstream.metadataDeadlineMs > policy.control.deadlineMs) {
5325
+ context.addIssue({
5326
+ code: "custom",
5327
+ path: ["upstream", "metadataDeadlineMs"],
5328
+ message: "upstream metadata deadline must not exceed control deadline"
5329
+ });
5330
+ }
5331
+ if (policy.queues.proxyRequests < policy.proxy.activeRequests) {
5332
+ context.addIssue({
5333
+ code: "custom",
5334
+ path: ["queues", "proxyRequests"],
5335
+ message: "proxy request queue budget must cover active proxy requests"
5336
+ });
5337
+ }
5338
+ });
5339
+ const DEFAULT_RUNTIME_BUDGET_POLICY = RuntimeBudgetPolicySchema.parse({
5340
+ proxy: {
5341
+ requestBytes: 64 * 1024 * 1024,
5342
+ responseBytes: 256 * 1024 * 1024,
5343
+ headerBytes: 64 * 1024,
5344
+ deadlineMs: 12e4,
5345
+ idleTimeoutMs: 3e4,
5346
+ activeRequests: MAX_ACTIVE_PROXY_REQUESTS
5347
+ },
5348
+ control: {
5349
+ requestBytes: 8 * 1024 * 1024,
5350
+ responseBytes: 64 * 1024 * 1024,
5351
+ headerBytes: 32 * 1024,
5352
+ deadlineMs: 3e4,
5353
+ idleTimeoutMs: 1e4,
5354
+ activeRequests: 8
5355
+ },
5356
+ upstream: {
5357
+ fetchResponseBytes: 256 * 1024 * 1024,
5358
+ fetchDeadlineMs: 12e4,
5359
+ replayResponseBytes: 256 * 1024 * 1024,
5360
+ replayDeadlineMs: 12e4,
5361
+ metadataResponseBytes: 10 * 1024 * 1024,
5362
+ metadataDeadlineMs: 3e4,
5363
+ connectTimeoutMs: 1e4,
5364
+ idleTimeoutMs: 3e4
5365
+ },
5366
+ pagination: {
5367
+ restPageSize: MAX_REST_LOG_PAGE_SIZE,
5368
+ searchQueryChars: MAX_SEARCH_QUERY_CHARS,
5369
+ searchProjectChars: MAX_SEARCH_PROJECT_CHARS
5370
+ },
5371
+ sse: {
5372
+ clients: MAX_SSE_CLIENTS,
5373
+ initLogs: 500,
5374
+ heartbeatMs: 15e3
5375
+ },
5376
+ imports: {
5377
+ archiveBytes: 64 * 1024 * 1024,
5378
+ entries: 5e3,
5379
+ entryBytes: 32 * 1024 * 1024,
5380
+ uncompressedBytes: 256 * 1024 * 1024,
5381
+ expansionRatio: 20
5382
+ },
5383
+ fanOut: {
5384
+ providerModels: MAX_PROVIDER_MODELS,
5385
+ providerModelMetadata: MAX_PROVIDER_MODEL_METADATA,
5386
+ providerTestConcurrency: MAX_PROVIDER_TEST_CONCURRENCY,
5387
+ activeProviderTests: MAX_ACTIVE_PROVIDER_TESTS
5388
+ },
5389
+ queues: {
5390
+ proxyRequests: MAX_ACTIVE_PROXY_REQUESTS,
5391
+ finalizerJobs: 1e3,
5392
+ rawStreamMessages: 16384,
5393
+ rawStreamBytes: 64 * 1024 * 1024,
5394
+ backgroundWorkers: 4
5395
+ }
5396
+ });
5397
+ const ResourceLimitErrorCodeSchema = _enum([
5398
+ "invalid-page-request",
5399
+ "search-limit-exceeded",
5400
+ "provider-model-limit-exceeded",
5401
+ "sse-capacity-exceeded",
5402
+ "provider-test-capacity-exceeded",
5403
+ "proxy-capacity-exceeded"
5404
+ ]);
5405
+ const ResourceLimitStatusSchema = union([
5406
+ literal(400),
5407
+ literal(413),
5408
+ literal(429),
5409
+ literal(503)
5410
+ ]);
5411
+ const ResourceLimitErrorSchema = object({
5412
+ error: string().min(1).max(300),
5413
+ code: ResourceLimitErrorCodeSchema,
5414
+ status: ResourceLimitStatusSchema,
5415
+ resource: string().min(1).max(80),
5416
+ limit: number().int().positive(),
5417
+ retryable: boolean()
5418
+ });
5419
+ function createResourceLimitError(input) {
5420
+ return ResourceLimitErrorSchema.parse(input);
5421
+ }
5422
+ function resourceLimitResponse(input) {
5423
+ const body = createResourceLimitError(input);
5424
+ const headers = new Headers({ "cache-control": "no-store" });
5425
+ if (input.retryAfterSeconds !== void 0) {
5426
+ headers.set("retry-after", String(input.retryAfterSeconds));
5427
+ }
5428
+ return Response.json(body, { status: input.status, headers });
5429
+ }
5430
+ function parseBoundedIntegerParameter(input) {
5431
+ if (input.value === null) return { ok: true, value: input.fallback };
5432
+ const parsed = Number(input.value);
5433
+ if (!Number.isSafeInteger(parsed) || parsed < input.minimum || parsed > input.maximum) {
5434
+ return {
5435
+ ok: false,
5436
+ response: resourceLimitResponse({
5437
+ error: `Query parameter "${input.parameter}" must be an integer between ${String(input.minimum)} and ${String(input.maximum)}.`,
5438
+ code: "invalid-page-request",
5439
+ status: 400,
5440
+ resource: input.resource,
5441
+ limit: input.maximum,
5442
+ retryable: false
5443
+ })
5444
+ };
5445
+ }
5446
+ return { ok: true, value: parsed };
5447
+ }
5448
+ function createCapacityLimiter(maxActive) {
5449
+ const capacity = Number.isSafeInteger(maxActive) && maxActive > 0 ? maxActive : 1;
5450
+ let active = 0;
5451
+ return {
5452
+ capacity,
5453
+ activeCount: () => active,
5454
+ tryAcquire: () => {
5455
+ if (active >= capacity) return null;
5456
+ active += 1;
5457
+ let released = false;
5458
+ return {
5459
+ release: () => {
5460
+ if (released) return;
5461
+ released = true;
5462
+ active = Math.max(0, active - 1);
5463
+ }
5464
+ };
5465
+ }
5466
+ };
5467
+ }
5468
+ async function runWithConcurrencyLimit(tasks2, maxConcurrent) {
5469
+ if (tasks2.length === 0) return;
5470
+ const concurrency = Number.isSafeInteger(maxConcurrent) && maxConcurrent > 0 ? maxConcurrent : 1;
5471
+ const workerCount = Math.min(concurrency, tasks2.length);
5472
+ let nextIndex = 0;
5473
+ const runWorker = async () => {
5474
+ while (nextIndex < tasks2.length) {
5475
+ const index = nextIndex;
5476
+ nextIndex += 1;
5477
+ const task = tasks2[index];
5478
+ if (task !== void 0) await task();
5479
+ }
5480
+ };
5481
+ await Promise.all(Array.from({ length: workerCount }, runWorker));
5482
+ }
5483
+ const DEFAULT_MEMORY_LIMIT_BYTES = 1024 * 1024;
5484
+ const FILE_WRITE_BATCH_BYTES = 64 * 1024;
5485
+ const DEFAULT_QUEUE_MAX_MESSAGES = 256;
5486
+ const DEFAULT_QUEUE_MAX_BYTES = 8 * 1024 * 1024;
5487
+ const DEFAULT_GLOBAL_QUEUE_MAX_MESSAGES = 4096;
5488
+ const DEFAULT_GLOBAL_QUEUE_MAX_BYTES = 64 * 1024 * 1024;
5489
+ const activeCaptures = /* @__PURE__ */ new Set();
5490
+ function resolveMemoryLimitBytes() {
5491
+ const raw = process.env["AGENT_INSPECTOR_STREAM_MEMORY_LIMIT_BYTES"];
5492
+ if (raw === void 0 || raw === "") return DEFAULT_MEMORY_LIMIT_BYTES;
5493
+ const parsed = Number(raw);
5494
+ if (!Number.isInteger(parsed) || parsed < 0) return DEFAULT_MEMORY_LIMIT_BYTES;
5495
+ return parsed;
5496
+ }
5497
+ function resolvePositiveIntegerEnv(name2, fallback) {
5498
+ const raw = process.env[name2];
5499
+ if (raw === void 0 || raw === "") return fallback;
5500
+ const parsed = Number(raw);
5501
+ if (!Number.isSafeInteger(parsed) || parsed < 1) return fallback;
5502
+ return parsed;
5503
+ }
5504
+ function defaultQueueMaxMessages() {
5505
+ return resolvePositiveIntegerEnv(
5506
+ "AGENT_INSPECTOR_OBSERVATION_QUEUE_MESSAGES",
5507
+ DEFAULT_QUEUE_MAX_MESSAGES
5508
+ );
5509
+ }
5510
+ function defaultQueueMaxBytes() {
5511
+ return resolvePositiveIntegerEnv(
5512
+ "AGENT_INSPECTOR_OBSERVATION_QUEUE_BYTES",
5513
+ DEFAULT_QUEUE_MAX_BYTES
5514
+ );
5515
+ }
5516
+ class RawStreamObservationBudget {
5517
+ constructor(maxMessages, maxBytes) {
5518
+ this.maxMessages = maxMessages;
5519
+ this.maxBytes = maxBytes;
5520
+ }
5521
+ maxMessages;
5522
+ maxBytes;
5523
+ messages = 0;
5524
+ bytes = 0;
5525
+ tryAcquire(bytes) {
5526
+ if (this.messages + 1 > this.maxMessages || this.bytes + bytes > this.maxBytes) return null;
5527
+ this.messages += 1;
5528
+ this.bytes += bytes;
5529
+ let released = false;
5530
+ return () => {
5531
+ if (released) return;
5532
+ released = true;
5533
+ this.messages -= 1;
5534
+ this.bytes -= bytes;
5535
+ };
5536
+ }
5537
+ }
5538
+ const globalObservationBudget = new RawStreamObservationBudget(
5539
+ resolvePositiveIntegerEnv(
5540
+ "AGENT_INSPECTOR_OBSERVATION_GLOBAL_QUEUE_MESSAGES",
5541
+ DEFAULT_GLOBAL_QUEUE_MAX_MESSAGES
5542
+ ),
5543
+ resolvePositiveIntegerEnv(
5544
+ "AGENT_INSPECTOR_OBSERVATION_GLOBAL_QUEUE_BYTES",
5545
+ DEFAULT_GLOBAL_QUEUE_MAX_BYTES
5546
+ )
5547
+ );
5548
+ function getRawStreamDir() {
5549
+ return join(getDataDir(), "raw-streams");
5550
+ }
5551
+ function createRawStreamPath() {
5552
+ return join(getRawStreamDir(), `${Date.now()}-${randomUUID()}.stream.tmp`);
5553
+ }
5554
+ class RawStreamCapture {
5555
+ memoryLimitBytes;
5556
+ queueMaxMessages;
5557
+ queueMaxBytes;
5558
+ globalBudget;
5559
+ chunks = [];
5560
+ byteLength = 0;
5561
+ filePath = null;
5562
+ fileHandle = null;
5563
+ pendingFileChunks = [];
5564
+ pendingFileBytes = 0;
5565
+ operation = Promise.resolve();
5566
+ accepting = true;
5567
+ closed = false;
5568
+ sawChunk = false;
5569
+ queuedMessages = 0;
5570
+ queuedBytes = 0;
5571
+ incomplete = false;
5572
+ storageFailed = false;
5573
+ droppedChunks = 0;
5574
+ droppedBytes = 0;
5575
+ gapReason = null;
5576
+ constructor(options = {}) {
5577
+ this.memoryLimitBytes = options.memoryLimitBytes ?? resolveMemoryLimitBytes();
5578
+ this.queueMaxMessages = options.queueMaxMessages ?? defaultQueueMaxMessages();
5579
+ this.queueMaxBytes = options.queueMaxBytes ?? defaultQueueMaxBytes();
5580
+ this.globalBudget = options.globalBudget ?? globalObservationBudget;
5581
+ activeCaptures.add(this);
5582
+ }
5583
+ get hasChunks() {
5584
+ return this.sawChunk;
5585
+ }
5586
+ get status() {
5587
+ return {
5588
+ incomplete: this.incomplete,
5589
+ droppedChunks: this.droppedChunks,
5590
+ droppedBytes: this.droppedBytes,
5591
+ reason: this.gapReason
5592
+ };
5593
+ }
5594
+ recordDroppedObservation(reason, observedBytes) {
5595
+ const safeObservedBytes = Number.isSafeInteger(observedBytes) && observedBytes >= 0 ? observedBytes : 0;
5596
+ this.recordGap(reason, safeObservedBytes, safeObservedBytes > 0);
5597
+ }
5598
+ async append(text) {
5599
+ if (text === "") return;
5600
+ const next = this.operation.then(() => this.appendInternal(text));
5601
+ this.operation = next.catch(() => void 0);
5602
+ return next;
5603
+ }
5604
+ /**
5605
+ * Submit an observation without waiting for file I/O. Once capacity is exceeded, capture remains
5606
+ * incomplete and all later chunks are counted as dropped so the persisted value is an honest
5607
+ * prefix rather than a misleading stream with an unmarked hole.
5608
+ */
5609
+ tryAppend(text, observedBytes) {
5610
+ const safeObservedBytes = Number.isSafeInteger(observedBytes) && observedBytes >= 0 ? observedBytes : 0;
5611
+ const queuedBytes = Math.max(safeObservedBytes, Buffer.byteLength(text, "utf-8"));
5612
+ if (!this.accepting || this.closed) {
5613
+ this.recordGap("pipeline-closed", safeObservedBytes, safeObservedBytes > 0 || text !== "");
5614
+ return false;
5615
+ }
5616
+ if (this.incomplete) {
5617
+ this.recordGap("capacity-exceeded", safeObservedBytes, safeObservedBytes > 0 || text !== "");
5618
+ return false;
5619
+ }
5620
+ if (text === "") return true;
5621
+ if (this.queuedMessages + 1 > this.queueMaxMessages || this.queuedBytes + queuedBytes > this.queueMaxBytes) {
5622
+ this.recordGap("capacity-exceeded", safeObservedBytes, true);
5623
+ return false;
5624
+ }
5625
+ const releaseGlobal = this.globalBudget.tryAcquire(queuedBytes);
5626
+ if (releaseGlobal === null) {
5627
+ this.recordGap("capacity-exceeded", safeObservedBytes, true);
5628
+ return false;
5629
+ }
5630
+ this.queuedMessages += 1;
5631
+ this.queuedBytes += queuedBytes;
5632
+ this.operation = this.operation.then(async () => {
5633
+ if (this.storageFailed) {
5634
+ this.recordGap("write-failed", safeObservedBytes, true);
5635
+ return;
5636
+ }
5637
+ await this.appendInternal(text);
5638
+ }).catch(() => {
5639
+ this.storageFailed = true;
5640
+ this.recordGap("write-failed", safeObservedBytes, true);
5641
+ }).finally(() => {
5642
+ this.queuedMessages -= 1;
5643
+ this.queuedBytes -= queuedBytes;
5644
+ releaseGlobal();
5645
+ });
5646
+ return true;
5647
+ }
5648
+ async finalize() {
5649
+ this.accepting = false;
5650
+ const next = this.operation.then(() => this.finalizeInternal());
5651
+ this.operation = next.then(
5652
+ () => void 0,
5653
+ () => void 0
5654
+ );
5655
+ return next.finally(() => activeCaptures.delete(this));
5656
+ }
5657
+ async appendInternal(text) {
5658
+ if (this.closed) return;
5659
+ this.sawChunk = true;
5660
+ const nextBytes = Buffer.byteLength(text, "utf-8");
5661
+ if (this.filePath === null && this.byteLength + nextBytes <= this.memoryLimitBytes) {
5662
+ this.chunks.push(text);
5663
+ this.byteLength += nextBytes;
5664
+ return;
5665
+ }
5666
+ if (this.filePath === null) {
5667
+ await this.spillToFile();
5668
+ }
5669
+ if (this.filePath !== null) {
5670
+ this.pendingFileChunks.push(text);
5671
+ this.pendingFileBytes += nextBytes;
5672
+ this.byteLength += nextBytes;
5673
+ if (this.pendingFileBytes >= FILE_WRITE_BATCH_BYTES) await this.flushFileBuffer();
5674
+ }
5675
+ }
5676
+ recordGap(reason, observedBytes, countChunk) {
5677
+ this.incomplete = true;
5678
+ this.gapReason ??= reason;
5679
+ if (countChunk) this.droppedChunks += 1;
5680
+ this.droppedBytes += observedBytes;
5681
+ }
5682
+ async finalizeInternal() {
5683
+ if (!this.closed) {
5684
+ this.closed = true;
5685
+ await this.flushFileBuffer();
5686
+ if (this.fileHandle !== null) {
5687
+ const handle = this.fileHandle;
5688
+ this.fileHandle = null;
5689
+ try {
5690
+ await handle.sync();
5691
+ } finally {
5692
+ await handle.close();
5693
+ }
5694
+ }
5695
+ }
5696
+ if (this.filePath !== null) {
5697
+ return { type: "file", path: this.filePath };
5698
+ }
5699
+ return { type: "memory", rawStream: this.chunks.join("") };
5700
+ }
5701
+ async spillToFile() {
5702
+ const path2 = createRawStreamPath();
5703
+ assert(ensurePrivateDirectorySync(getRawStreamDir()), "Could not secure raw-stream directory");
5704
+ const handle = await open(path2, "wx", PRIVATE_FILE_MODE);
5705
+ let committed = false;
5706
+ try {
5707
+ const memoryText = this.chunks.join("");
5708
+ if (memoryText.length > 0) await handle.write(memoryText, void 0, "utf-8");
5709
+ assert(securePrivateFileSync(path2), "Could not secure raw-stream file");
5710
+ this.filePath = path2;
5711
+ this.fileHandle = handle;
5712
+ this.chunks = [];
5713
+ committed = true;
5714
+ } finally {
5715
+ if (!committed) {
5716
+ await handle.close().catch(() => void 0);
5717
+ await unlink(path2).catch(() => void 0);
5718
+ }
5719
+ }
5720
+ }
5721
+ async flushFileBuffer() {
5722
+ if (this.fileHandle === null || this.pendingFileChunks.length === 0) return;
5723
+ const text = this.pendingFileChunks.join("");
5724
+ this.pendingFileChunks = [];
5725
+ this.pendingFileBytes = 0;
5726
+ await this.fileHandle.write(text, void 0, "utf-8");
5727
+ }
5728
+ }
5729
+ async function flushAllRawStreamCaptures() {
5730
+ await Promise.all([...activeCaptures].map((capture) => capture.finalize()));
5731
+ }
5732
+ const MAX_INVENTORY_DEPTH = 8;
5733
+ const DAY_MS = 24 * 60 * 60 * 1e3;
5734
+ const DEFAULT_RETENTION_DAYS = 7;
5735
+ const DEFAULT_MAX_TOTAL_BYTES = 2 * 1024 * 1024 * 1024;
5736
+ let lastStorageLifecycleGcResult = null;
5737
+ function emptyStats() {
5738
+ return {
5739
+ fileCount: 0,
5740
+ bytes: 0,
5741
+ oldestModifiedMs: null,
5742
+ newestModifiedMs: null
5743
+ };
5744
+ }
5745
+ function recordFile(stats, bytes, modifiedMs) {
5746
+ stats.fileCount += 1;
5747
+ stats.bytes += bytes;
5748
+ stats.oldestModifiedMs = stats.oldestModifiedMs === null ? modifiedMs : Math.min(stats.oldestModifiedMs, modifiedMs);
5749
+ stats.newestModifiedMs = stats.newestModifiedMs === null ? modifiedMs : Math.max(stats.newestModifiedMs, modifiedMs);
5750
+ }
5751
+ function isoFromMs(value) {
5752
+ return value === null ? null : new Date(value).toISOString();
5753
+ }
5754
+ function isPathInsideRoot(root, path2) {
5755
+ const relativePath = relative(resolve(root), resolve(path2));
5756
+ return relativePath.length === 0 || !relativePath.startsWith("..") && !relativePath.startsWith("/") && !relativePath.startsWith("\\");
5757
+ }
5758
+ function collectRuleFiles(rule) {
5759
+ const files = [];
5760
+ if (!existsSync(rule.root)) return files;
5761
+ const visit = (currentDir, depth) => {
5762
+ if (depth > MAX_INVENTORY_DEPTH) return;
5763
+ let entries;
5764
+ try {
5765
+ entries = readdirSync(currentDir);
5766
+ } catch {
5767
+ return;
5768
+ }
5769
+ for (const entry of entries) {
5770
+ const path2 = join(currentDir, entry);
5771
+ if (!isPathInsideRoot(rule.root, path2)) continue;
5772
+ try {
5773
+ const fileStats = lstatSync(path2);
5774
+ if (fileStats.isDirectory()) {
5775
+ visit(path2, depth + 1);
5776
+ continue;
5777
+ }
5778
+ if (!fileStats.isFile() || !rule.shouldCount(entry, path2)) continue;
5779
+ files.push({ rule, path: path2, bytes: fileStats.size, modifiedMs: fileStats.mtimeMs });
5780
+ } catch {
5781
+ }
5782
+ }
5783
+ };
5784
+ visit(rule.root, 0);
5785
+ return files;
5786
+ }
5787
+ function collectRuleStats(rule) {
5788
+ const stats = emptyStats();
5789
+ for (const file of collectRuleFiles(rule)) {
5790
+ recordFile(stats, file.bytes, file.modifiedMs);
5791
+ }
5792
+ return stats;
5793
+ }
5794
+ function isAutoDeletableCleanupBehavior(behavior) {
5795
+ switch (behavior) {
5796
+ case "retention-gc":
5797
+ case "orphan-reclaim":
5798
+ return true;
5799
+ case "rotation":
5800
+ case "manual-owner-delete":
5801
+ return false;
5802
+ }
5803
+ }
5804
+ function summarizeRule(rule) {
5805
+ const stats = collectRuleStats(rule);
5806
+ return {
5807
+ kind: rule.kind,
5808
+ root: rule.root,
5809
+ sensitivity: rule.sensitivity,
5810
+ cleanupBehavior: rule.cleanupBehavior,
5811
+ fileCount: stats.fileCount,
5812
+ bytes: stats.bytes,
5813
+ oldestModifiedAt: isoFromMs(stats.oldestModifiedMs),
5814
+ newestModifiedAt: isoFromMs(stats.newestModifiedMs)
5815
+ };
5816
+ }
5817
+ function isSessionArchiveFile(fileName) {
5818
+ return fileName === "session.sqlite" || fileName.startsWith("session.sqlite-");
5819
+ }
5820
+ function isSqliteLogIndexFile(fileName) {
5821
+ return fileName === "inspector.sqlite" || fileName.startsWith("inspector.sqlite-");
5822
+ }
5823
+ function isBackupFile(fileName) {
5824
+ return fileName.endsWith(".bak") || fileName.endsWith(".backup");
5825
+ }
5826
+ function applicationLogMatcher(logPath) {
5827
+ const directory = dirname(logPath);
5828
+ const baseName = basename(logPath);
5829
+ return (fileName, path2) => {
5830
+ if (dirname(path2) !== directory) return false;
5831
+ if (fileName === baseName) return true;
5832
+ return fileName.startsWith(`${baseName}.`) && /^\d+$/.test(fileName.slice(baseName.length + 1));
5833
+ };
5834
+ }
5835
+ function parsePositiveIntegerEnv(name2, fallback) {
5836
+ const value = process.env[name2];
5837
+ if (value === void 0) return fallback;
5838
+ const parsed = Number.parseInt(value, 10);
5839
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
5840
+ return parsed;
5841
+ }
5842
+ function resolveStorageLifecycleQuotaPolicy() {
5843
+ const retentionDays = parsePositiveIntegerEnv(
5844
+ "AGENT_INSPECTOR_STORAGE_RETENTION_DAYS",
5845
+ parsePositiveIntegerEnv("LOG_RETENTION_DAYS", DEFAULT_RETENTION_DAYS)
5846
+ );
5847
+ return {
5848
+ maxAgeMs: retentionDays * DAY_MS,
5849
+ maxTotalBytes: parsePositiveIntegerEnv(
5850
+ "AGENT_INSPECTOR_STORAGE_MAX_BYTES",
5851
+ DEFAULT_MAX_TOTAL_BYTES
5852
+ )
5853
+ };
5854
+ }
5855
+ function getStorageLifecycleInventoryRules() {
5856
+ const applicationLogPath = resolveApplicationLogPath();
5857
+ return [
5858
+ {
5859
+ kind: "captured-log-jsonl",
5860
+ root: resolveLogDir(),
5861
+ sensitivity: "captured-data",
5862
+ cleanupBehavior: "retention-gc",
5863
+ shouldCount: (fileName) => fileName.endsWith(".jsonl")
5864
+ },
5865
+ {
5866
+ kind: "sqlite-log-index",
5867
+ root: resolveLogDir(),
5868
+ sensitivity: "captured-data",
5869
+ cleanupBehavior: "orphan-reclaim",
5870
+ shouldCount: isSqliteLogIndexFile
5871
+ },
5872
+ {
5873
+ kind: "session-archive",
5874
+ root: getSessionArchiveRoot(),
5875
+ sensitivity: "captured-data",
5876
+ cleanupBehavior: "retention-gc",
5877
+ shouldCount: isSessionArchiveFile
5878
+ },
5879
+ {
5880
+ kind: "streaming-chunk",
5881
+ root: getChunksDir(),
5882
+ sensitivity: "captured-data",
5883
+ cleanupBehavior: "retention-gc",
5884
+ shouldCount: (fileName) => fileName.endsWith(".json")
5885
+ },
5886
+ {
5887
+ kind: "raw-stream-temp",
5888
+ root: getRawStreamDir(),
5889
+ sensitivity: "captured-data",
5890
+ cleanupBehavior: "orphan-reclaim",
5891
+ shouldCount: () => true
5892
+ },
5893
+ {
5894
+ kind: "application-log",
5895
+ root: dirname(applicationLogPath),
5896
+ sensitivity: "operational",
5897
+ cleanupBehavior: "rotation",
5898
+ shouldCount: applicationLogMatcher(applicationLogPath)
5899
+ },
5900
+ {
5901
+ kind: "evidence",
5902
+ root: join(getDataDir(), "evidence"),
5903
+ sensitivity: "captured-data",
5904
+ cleanupBehavior: "manual-owner-delete",
5905
+ shouldCount: () => true
5906
+ },
5907
+ {
5908
+ kind: "backup",
5909
+ root: getDataDir(),
5910
+ sensitivity: "secret-adjacent",
5911
+ cleanupBehavior: "orphan-reclaim",
5912
+ shouldCount: isBackupFile
5913
+ }
5914
+ ];
5915
+ }
5916
+ function getStorageLifecycleInventory(rules = getStorageLifecycleInventoryRules()) {
5917
+ const artifacts = rules.map(summarizeRule);
5918
+ return {
5919
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
5920
+ totalFiles: artifacts.reduce((sum, artifact) => sum + artifact.fileCount, 0),
5921
+ totalBytes: artifacts.reduce((sum, artifact) => sum + artifact.bytes, 0),
5922
+ artifacts
5923
+ };
5924
+ }
5925
+ function collectLifecycleFiles(rules = getStorageLifecycleInventoryRules()) {
5926
+ return rules.flatMap((rule) => collectRuleFiles(rule));
5927
+ }
5928
+ function selectGcCandidates(files, policy, nowMs) {
5929
+ const autoDeletableFiles = files.filter(
5930
+ (file) => isAutoDeletableCleanupBehavior(file.rule.cleanupBehavior)
5931
+ );
5932
+ const expiredFiles = autoDeletableFiles.filter(
5933
+ (file) => nowMs - file.modifiedMs > policy.maxAgeMs
5934
+ );
5935
+ const expiredPaths = new Set(expiredFiles.map((file) => file.path));
5936
+ const candidates2 = [...expiredFiles];
5937
+ let totalBytes = files.reduce((sum, file) => sum + file.bytes, 0) - expiredFiles.reduce((sum, file) => sum + file.bytes, 0);
5938
+ if (totalBytes <= policy.maxTotalBytes) return candidates2;
5939
+ const quotaCandidates = autoDeletableFiles.filter((file) => !expiredPaths.has(file.path)).sort((a, b) => a.modifiedMs - b.modifiedMs);
5940
+ for (const file of quotaCandidates) {
5941
+ if (totalBytes <= policy.maxTotalBytes) break;
5942
+ candidates2.push(file);
5943
+ totalBytes -= file.bytes;
5944
+ }
5945
+ return candidates2;
5946
+ }
5947
+ function deleteLifecycleFile(file) {
5948
+ if (!isPathInsideRoot(file.rule.root, file.path)) {
5949
+ return { kind: file.rule.kind, path: file.path, message: "path outside lifecycle root" };
5950
+ }
5951
+ try {
5952
+ unlinkSync(file.path);
5953
+ return null;
5954
+ } catch (err) {
5955
+ if (err instanceof Error && err.message.includes("ENOENT")) return null;
5956
+ return { kind: file.rule.kind, path: file.path, message: String(err) };
5957
+ }
5958
+ }
5959
+ function runStorageLifecycleGc(policy = resolveStorageLifecycleQuotaPolicy(), rules = getStorageLifecycleInventoryRules(), nowMs = Date.now()) {
5960
+ const before = getStorageLifecycleInventory(rules);
5961
+ const files = collectLifecycleFiles(rules);
5962
+ const candidates2 = selectGcCandidates(files, policy, nowMs);
5963
+ const errors = [];
5964
+ let filesDeleted = 0;
5965
+ let bytesDeleted = 0;
5966
+ for (const file of candidates2) {
5967
+ const error = deleteLifecycleFile(file);
5968
+ if (error !== null) {
5969
+ errors.push(error);
5970
+ continue;
5971
+ }
5972
+ filesDeleted += 1;
5973
+ bytesDeleted += file.bytes;
5974
+ }
5975
+ const after = getStorageLifecycleInventory(rules);
5976
+ const autoDeletableBytes = after.artifacts.filter((artifact) => isAutoDeletableCleanupBehavior(artifact.cleanupBehavior)).reduce((sum, artifact) => sum + artifact.bytes, 0);
5977
+ const result = {
5978
+ checkedAt: new Date(nowMs).toISOString(),
5979
+ policy,
5980
+ before,
5981
+ after,
5982
+ filesScanned: files.length,
5983
+ bytesScanned: files.reduce((sum, file) => sum + file.bytes, 0),
5984
+ filesDeleted,
5985
+ bytesDeleted,
5986
+ reclaimableBytes: autoDeletableBytes,
5987
+ errors
5988
+ };
5989
+ lastStorageLifecycleGcResult = result;
5990
+ return result;
5991
+ }
5992
+ function getLastStorageLifecycleGcResult() {
5993
+ return lastStorageLifecycleGcResult;
5994
+ }
5995
+ const MAX_MEMORY_CACHE = 100;
5996
+ const memoryCache = /* @__PURE__ */ new Map();
5997
+ const MAX_CURSOR_PAGE_INDEXES = 16;
5998
+ const logCursorPageIndexes = /* @__PURE__ */ new Map();
5999
+ let sessionArchiveQueue = Promise.resolve();
6000
+ const SESSION_ARCHIVE_BATCH_SIZE = 128;
6001
+ const SESSION_ARCHIVE_FLUSH_DELAY_MS = 10;
6002
+ let sessionArchiveBuffer = [];
6003
+ let sessionArchiveFlushTimeout = null;
6004
+ function drainSessionArchiveBuffer() {
6005
+ const logs = sessionArchiveBuffer;
6006
+ sessionArchiveBuffer = [];
6007
+ return logs;
6008
+ }
6009
+ function queueSessionArchiveFlush() {
6010
+ const logs = drainSessionArchiveBuffer();
6011
+ if (logs.length === 0) return;
6012
+ sessionArchiveQueue = sessionArchiveQueue.catch(() => void 0).then(async () => {
6013
+ await archiveSessionLogs(logs);
6014
+ }).catch((err) => {
6015
+ logger.warn("[store] Failed to archive session logs:", String(err));
6016
+ });
6017
+ }
6018
+ function cancelSessionArchiveFlushTimer() {
6019
+ if (sessionArchiveFlushTimeout === null) return;
6020
+ clearTimeout(sessionArchiveFlushTimeout);
6021
+ sessionArchiveFlushTimeout = null;
6022
+ }
6023
+ function scheduleSessionArchiveFlush() {
6024
+ if (sessionArchiveFlushTimeout !== null) return;
6025
+ sessionArchiveFlushTimeout = setTimeout(() => {
6026
+ sessionArchiveFlushTimeout = null;
6027
+ queueSessionArchiveFlush();
6028
+ }, SESSION_ARCHIVE_FLUSH_DELAY_MS);
6029
+ }
6030
+ function cursorPageIndexKey(options) {
6031
+ return JSON.stringify({
6032
+ sessionId: options.sessionId ?? null,
6033
+ model: options.model ?? null,
6034
+ clientPid: options.clientPid ?? null
6035
+ });
6036
+ }
6037
+ function matchesCursorIndexFilters(log, index) {
6038
+ if (index.sessionId !== void 0 && getLogSessionId(log) !== index.sessionId) return false;
6039
+ if (index.model !== void 0 && log.model !== index.model) return false;
6040
+ if (index.clientPid !== void 0 && log.clientPid !== index.clientPid) return false;
6041
+ return true;
6042
+ }
6043
+ function insertSortedLogId(ids, id) {
6044
+ if (ids.includes(id)) return;
6045
+ const insertionIndex = ids.findIndex((candidate) => candidate > id);
6046
+ if (insertionIndex === -1) {
6047
+ ids.push(id);
6048
+ return;
6049
+ }
6050
+ ids.splice(insertionIndex, 0, id);
6051
+ }
6052
+ function upsertLogCursorPageIndexes(log) {
6053
+ for (const index of logCursorPageIndexes.values()) {
6054
+ if (!matchesCursorIndexFilters(log, index)) {
6055
+ if (index.logsById.delete(log.id)) {
6056
+ index.ids = index.ids.filter((id) => id !== log.id);
6057
+ }
6058
+ index.entriesById.delete(log.id);
6059
+ continue;
6060
+ }
6061
+ index.logsById.set(log.id, log);
6062
+ insertSortedLogId(index.ids, log.id);
6063
+ }
6064
+ }
6065
+ function removeLogsFromCursorPageIndexes(ids) {
6066
+ if (ids.size === 0) return;
6067
+ for (const index of logCursorPageIndexes.values()) {
6068
+ for (const id of ids) {
6069
+ index.logsById.delete(id);
6070
+ index.entriesById.delete(id);
6071
+ }
6072
+ index.ids = index.ids.filter((id) => !ids.has(id));
6073
+ }
6074
+ }
6075
+ function clearLogCursorPageIndexes() {
6076
+ logCursorPageIndexes.clear();
6077
+ }
6078
+ function evictOldestIfNeeded() {
6079
+ while (memoryCache.size > MAX_MEMORY_CACHE) {
6080
+ const oldestKey = memoryCache.keys().next().value;
6081
+ if (oldestKey === void 0) break;
6082
+ memoryCache.delete(oldestKey);
6083
+ }
6084
+ }
6085
+ function normalizeLog(log) {
6086
+ const sessionId = getLogSessionId(log);
6087
+ const pathFormat = apiFormatForPath(log.path);
6088
+ const apiFormat = pathFormat === "unknown" ? log.apiFormat : pathFormat;
6089
+ const captureIncomplete = log.captureIncomplete ?? false;
6090
+ const droppedChunks = log.droppedChunks ?? 0;
6091
+ const droppedBytes = log.droppedBytes ?? 0;
6092
+ const captureIncompleteReason = log.captureIncompleteReason ?? null;
6093
+ const stopReason = log.stopReason ?? extractStopReason(log);
6094
+ if (sessionId === log.sessionId && apiFormat === log.apiFormat && log.captureIncomplete !== void 0 && log.droppedChunks !== void 0 && log.droppedBytes !== void 0 && log.captureIncompleteReason !== void 0 && log.stopReason !== void 0) {
6095
+ return log;
6096
+ }
6097
+ return {
6098
+ ...log,
6099
+ sessionId,
6100
+ apiFormat,
6101
+ captureIncomplete,
6102
+ droppedChunks,
6103
+ droppedBytes,
6104
+ captureIncompleteReason,
6105
+ stopReason
6106
+ };
6107
+ }
6108
+ function addToCache(log) {
6109
+ const cachedLog = normalizeLog(log);
6110
+ if (memoryCache.has(cachedLog.id)) {
6111
+ memoryCache.delete(cachedLog.id);
6112
+ }
6113
+ memoryCache.set(cachedLog.id, cachedLog);
6114
+ upsertLogCursorPageIndexes(cachedLog);
6115
+ evictOldestIfNeeded();
6116
+ }
6117
+ function archiveLogSnapshot(log) {
6118
+ sessionArchiveBuffer.push(normalizeLog(log));
6119
+ if (sessionArchiveBuffer.length >= SESSION_ARCHIVE_BATCH_SIZE) {
6120
+ cancelSessionArchiveFlushTimer();
6121
+ queueSessionArchiveFlush();
6122
+ } else if (sessionArchiveBuffer.length === 1) {
6123
+ scheduleSessionArchiveFlush();
5133
6124
  }
5134
6125
  }
5135
6126
  async function flushSessionArchiveWrites() {
@@ -5559,17 +6550,42 @@ async function listLogsPage(options) {
5559
6550
  limit: options.limit
5560
6551
  };
5561
6552
  }
5562
- function matchesCursorWindow(id, options) {
5563
- if (options.beforeLogId !== void 0 && id >= options.beforeLogId) return false;
5564
- if (options.afterLogId !== void 0 && id <= options.afterLogId) return false;
5565
- return true;
6553
+ function lowerBoundLogId(ids, target) {
6554
+ let low = 0;
6555
+ let high = ids.length;
6556
+ while (low < high) {
6557
+ const middle = Math.floor((low + high) / 2);
6558
+ const id = ids[middle];
6559
+ if (id === void 0 || id >= target) {
6560
+ high = middle;
6561
+ continue;
6562
+ }
6563
+ low = middle + 1;
6564
+ }
6565
+ return low;
6566
+ }
6567
+ function upperBoundLogId(ids, target) {
6568
+ let low = 0;
6569
+ let high = ids.length;
6570
+ while (low < high) {
6571
+ const middle = Math.floor((low + high) / 2);
6572
+ const id = ids[middle];
6573
+ if (id === void 0 || id > target) {
6574
+ high = middle;
6575
+ continue;
6576
+ }
6577
+ low = middle + 1;
6578
+ }
6579
+ return low;
5566
6580
  }
5567
6581
  function selectCursorPageLogs(ids, options) {
5568
- const matchingWindow = ids.filter((id) => matchesCursorWindow(id, options));
6582
+ const windowStart = options.afterLogId === void 0 ? 0 : upperBoundLogId(ids, options.afterLogId);
6583
+ const windowEnd = options.beforeLogId === void 0 ? ids.length : lowerBoundLogId(ids, options.beforeLogId);
6584
+ if (windowStart >= windowEnd) return [];
5569
6585
  if (options.afterLogId !== void 0 || options.anchor === "oldest") {
5570
- return matchingWindow.slice(0, options.limit);
6586
+ return ids.slice(windowStart, Math.min(windowEnd, windowStart + options.limit));
5571
6587
  }
5572
- return matchingWindow.slice(Math.max(0, matchingWindow.length - options.limit));
6588
+ return ids.slice(Math.max(windowStart, windowEnd - options.limit), windowEnd);
5573
6589
  }
5574
6590
  function rememberLogCursorPageIndex(index) {
5575
6591
  if (logCursorPageIndexes.has(index.key)) {
@@ -5601,11 +6617,18 @@ function matchesLogIndexEntryFilters(entry, sessionId, model, clientPid) {
5601
6617
  return true;
5602
6618
  }
5603
6619
  async function buildLogCursorPageIndexFromLogIndex(options, allowRebuild) {
5604
- if (options.clientPid !== void 0) return null;
5605
- let entries = await listFilteredIndexEntries(options);
6620
+ let entries = await listSqliteLogCursorEntries(options);
6621
+ if (entries === null) {
6622
+ if (options.clientPid !== void 0) return null;
6623
+ entries = await listFilteredIndexEntries(options);
6624
+ }
5606
6625
  if (entries.length === 0 && allowRebuild) {
5607
6626
  await rebuildIndex();
5608
- entries = await listFilteredIndexEntries(options);
6627
+ entries = await listSqliteLogCursorEntries(options);
6628
+ if (entries === null) {
6629
+ if (options.clientPid !== void 0) return null;
6630
+ entries = await listFilteredIndexEntries(options);
6631
+ }
5609
6632
  }
5610
6633
  if (entries.some((entry) => !hasLogIndexFilterMetadata(entry))) {
5611
6634
  if (!allowRebuild) return null;
@@ -6420,9 +7443,9 @@ function validateValueAgainstSchema(schema, value, toolName, path2, warnings) {
6420
7443
  }
6421
7444
  }
6422
7445
  }
6423
- function extractAnthropicToolSchemas(request) {
7446
+ function extractAnthropicToolSchemas(request2) {
6424
7447
  const tools = /* @__PURE__ */ new Map();
6425
- for (const tool of readArray(request, "tools")) {
7448
+ for (const tool of readArray(request2, "tools")) {
6426
7449
  if (!isRecord$2(tool)) continue;
6427
7450
  const name2 = readString(tool, "name");
6428
7451
  if (name2 === null || name2 === "") continue;
@@ -6433,9 +7456,9 @@ function extractAnthropicToolSchemas(request) {
6433
7456
  }
6434
7457
  return tools;
6435
7458
  }
6436
- function extractOpenAIToolSchemas(request) {
7459
+ function extractOpenAIToolSchemas(request2) {
6437
7460
  const tools = /* @__PURE__ */ new Map();
6438
- for (const tool of readArray(request, "tools")) {
7461
+ for (const tool of readArray(request2, "tools")) {
6439
7462
  const functionDefinition = readObject(tool, "function");
6440
7463
  const name2 = functionDefinition === null ? readString(tool, "name") : readString(functionDefinition, "name") ?? readString(tool, "name");
6441
7464
  if (name2 === null || name2 === "") continue;
@@ -6454,16 +7477,16 @@ function mergeToolSchemas(primary, secondary) {
6454
7477
  }
6455
7478
  return merged;
6456
7479
  }
6457
- function extractToolSchemas(request, apiFormat) {
7480
+ function extractToolSchemas(request2, apiFormat) {
6458
7481
  switch (apiFormat) {
6459
7482
  case "anthropic":
6460
- return extractAnthropicToolSchemas(request);
7483
+ return extractAnthropicToolSchemas(request2);
6461
7484
  case "openai":
6462
- return extractOpenAIToolSchemas(request);
7485
+ return extractOpenAIToolSchemas(request2);
6463
7486
  case "unknown":
6464
7487
  return mergeToolSchemas(
6465
- extractOpenAIToolSchemas(request),
6466
- extractAnthropicToolSchemas(request)
7488
+ extractOpenAIToolSchemas(request2),
7489
+ extractAnthropicToolSchemas(request2)
6467
7490
  );
6468
7491
  }
6469
7492
  }
@@ -6570,10 +7593,10 @@ function uniqueWarnings(warnings) {
6570
7593
  return unique2;
6571
7594
  }
6572
7595
  function analyzeToolSchemaWarnings(input) {
6573
- const request = parseJson$1(input.rawRequestBody);
7596
+ const request2 = parseJson$1(input.rawRequestBody);
6574
7597
  const response = parseJson$1(input.responseText);
6575
- if (!request.ok || !response.ok) return [];
6576
- const toolSchemas = extractToolSchemas(request.value, input.apiFormat);
7598
+ if (!request2.ok || !response.ok) return [];
7599
+ const toolSchemas = extractToolSchemas(request2.value, input.apiFormat);
6577
7600
  const toolCalls = extractToolCalls(response.value, input.apiFormat);
6578
7601
  if (toolCalls.length === 0) return [];
6579
7602
  const warnings = [];
@@ -7180,6 +8203,84 @@ async function closeLogFinalizerWorkers() {
7180
8203
  function forceCloseLogFinalizerWorkers() {
7181
8204
  for (const worker of detachLogFinalizerWorkers()) void worker.terminate();
7182
8205
  }
8206
+ const UNASSIGNED_SESSION_QUEUE = "__unassigned__";
8207
+ const queues = /* @__PURE__ */ new Map();
8208
+ let queuedTaskCount = 0;
8209
+ class SessionTaskQueueRejectedError extends Error {
8210
+ constructor(limit) {
8211
+ super(`Session task queue is full; limit=${String(limit)}`);
8212
+ this.name = "SessionTaskQueueRejectedError";
8213
+ }
8214
+ }
8215
+ function queueKeyForSession(sessionId) {
8216
+ return sessionId ?? UNASSIGNED_SESSION_QUEUE;
8217
+ }
8218
+ function resolveSessionTaskQueueLimit() {
8219
+ const configured = Number(process.env["SESSION_TASK_QUEUE_LIMIT"]);
8220
+ if (Number.isSafeInteger(configured) && configured > 0) return configured;
8221
+ return DEFAULT_RUNTIME_BUDGET_POLICY.queues.finalizerJobs;
8222
+ }
8223
+ function errorMessage$1(err) {
8224
+ return err instanceof Error ? err.message : String(err);
8225
+ }
8226
+ async function runSessionTask(sessionId, taskName, task) {
8227
+ markSessionTaskStarted(sessionId);
8228
+ try {
8229
+ const result = await task();
8230
+ markSessionTaskFinished(sessionId, null);
8231
+ return result;
8232
+ } catch (err) {
8233
+ const message = errorMessage$1(err);
8234
+ logger.error(`[sessionRuntime] ${taskName} failed for session ${sessionId ?? "unassigned"}`);
8235
+ markSessionTaskFinished(sessionId, message);
8236
+ return Promise.reject(err);
8237
+ }
8238
+ }
8239
+ function enqueueSessionTask(sessionId, taskName, task) {
8240
+ const queueLimit = resolveSessionTaskQueueLimit();
8241
+ if (queuedTaskCount >= queueLimit) {
8242
+ logger.warn(
8243
+ `[sessionRuntime] Rejecting ${taskName} for session ${sessionId ?? "unassigned"}: queue full (${String(queuedTaskCount)}/${String(queueLimit)})`
8244
+ );
8245
+ return Promise.reject(new SessionTaskQueueRejectedError(queueLimit));
8246
+ }
8247
+ const queueKey = queueKeyForSession(sessionId);
8248
+ const previous = queues.get(queueKey) ?? Promise.resolve();
8249
+ queuedTaskCount += 1;
8250
+ markSessionTaskQueued(sessionId);
8251
+ const current = previous.catch(() => void 0).then(() => runSessionTask(sessionId, taskName, task));
8252
+ const settled = current.then(
8253
+ () => void 0,
8254
+ () => void 0
8255
+ );
8256
+ queues.set(queueKey, settled);
8257
+ void settled.then(() => {
8258
+ queuedTaskCount = Math.max(0, queuedTaskCount - 1);
8259
+ if (queues.get(queueKey) === settled) {
8260
+ queues.delete(queueKey);
8261
+ }
8262
+ });
8263
+ return current;
8264
+ }
8265
+ function enqueueFinalizeLogJob(job) {
8266
+ return enqueueSessionTask(
8267
+ getLogSessionId(job.log),
8268
+ "finalize-log",
8269
+ () => executeFinalizeLogJob(job)
8270
+ );
8271
+ }
8272
+ async function flushSessionTasks() {
8273
+ while (queues.size > 0) {
8274
+ await Promise.all([...queues.values()]);
8275
+ }
8276
+ }
8277
+ function getSessionRuntimeQueueHealth() {
8278
+ return {
8279
+ activeQueues: queues.size,
8280
+ queuedTasks: queuedTaskCount,
8281
+ queueLimit: resolveSessionTaskQueueLimit()
8282
+ };
8283
+ }
7183
8284
  const DEFAULT_CACHE_MS = 2e3;
7184
8285
  const DEFAULT_MIN_DISK_HEADROOM_BYTES = 256 * 1024 * 1024;
7185
8286
  const DEFAULT_MAX_FINALIZER_QUEUE = 256;
@@ -7218,6 +8319,7 @@ async function checkDiskHeadroom() {
7218
8319
  }
7219
8320
  function finalizerChecks() {
7220
8321
  const health = getFinalizerRuntimeHealth();
8322
+ const sessionQueue = getSessionRuntimeQueueHealth();
7221
8323
  const runtimeArtifactMissing = health.runtime !== "inline" && !health.artifactAvailable;
7222
8324
  const initializedWorkerMissing = health.runtime === "worker" && health.activeWorkers !== null && health.activeWorkers < health.configuredWorkers;
7223
8325
  const workers = runtimeArtifactMissing || initializedWorkerMissing ? "unavailable" : health.runtime === "inline" ? "degraded" : "ok";
@@ -7226,7 +8328,7 @@ function finalizerChecks() {
7226
8328
  DEFAULT_MAX_FINALIZER_QUEUE
7227
8329
  );
7228
8330
  const warningThreshold = Math.max(1, Math.floor(queueLimit * 0.8));
7229
- const queue = health.pendingJobs >= queueLimit ? "unavailable" : health.pendingJobs >= warningThreshold ? "degraded" : "ok";
8331
+ const queue = health.pendingJobs + sessionQueue.queuedTasks >= queueLimit ? "unavailable" : health.pendingJobs + sessionQueue.queuedTasks >= warningThreshold ? "degraded" : "ok";
7230
8332
  return { workers, queue };
7231
8333
  }
7232
8334
  function checkWorkers() {
@@ -7308,151 +8410,313 @@ function setRuntimeShuttingDown(shuttingDown) {
7308
8410
  runtimeShuttingDown = shuttingDown;
7309
8411
  readinessService.invalidate();
7310
8412
  }
7311
- async function readinessResponse() {
7312
- const readiness = await getRuntimeReadiness();
7313
- return Response.json(readiness, {
7314
- status: readiness.status === "unavailable" ? 503 : 200,
7315
- headers: { "cache-control": "no-store" }
8413
+ async function readinessResponse() {
8414
+ const readiness = await getRuntimeReadiness();
8415
+ return Response.json(readiness, {
8416
+ status: readiness.status === "unavailable" ? 503 : 200,
8417
+ headers: { "cache-control": "no-store" }
8418
+ });
8419
+ }
8420
+ const Route$V = createFileRoute("/readyz")({
8421
+ server: {
8422
+ handlers: {
8423
+ GET: readinessResponse
8424
+ }
8425
+ }
8426
+ });
8427
+ const INITIAL_STREAM_LOG_LIMIT = 100;
8428
+ const UPDATE_COALESCE_MS = 50;
8429
+ const sseClientLimiter = createCapacityLimiter(MAX_SSE_CLIENTS);
8430
+ function getLogsStreamMetrics() {
8431
+ return {
8432
+ activeClients: sseClientLimiter.activeCount(),
8433
+ capacity: sseClientLimiter.capacity
8434
+ };
8435
+ }
8436
+ function streamIncludesBodies(url2) {
8437
+ return url2.searchParams.get("compact") === "0";
8438
+ }
8439
+ function parseLastEventLogId(value) {
8440
+ if (value === null) return void 0;
8441
+ return parseLogId(value) ?? void 0;
8442
+ }
8443
+ function encodeSse(value, id) {
8444
+ const idLine = id === void 0 ? "" : `id: ${String(id)}
8445
+ `;
8446
+ return new TextEncoder().encode(`${idLine}data: ${JSON.stringify(value)}
8447
+
8448
+ `);
8449
+ }
8450
+ function createLogsStreamResponse(request2, clientLimiter = sseClientLimiter) {
8451
+ const lastEventIdHeader = request2.headers.get("last-event-id");
8452
+ const lastEventLogId = parseLastEventLogId(lastEventIdHeader);
8453
+ if (lastEventIdHeader !== null && lastEventLogId === void 0) {
8454
+ return Response.json({ error: "Invalid Last-Event-ID" }, { status: 400 });
8455
+ }
8456
+ const lease = clientLimiter.tryAcquire();
8457
+ if (lease === null) {
8458
+ return resourceLimitResponse({
8459
+ error: `SSE client capacity reached. At most ${String(clientLimiter.capacity)} live clients are allowed; retry after a connection closes.`,
8460
+ code: "sse-capacity-exceeded",
8461
+ status: 503,
8462
+ resource: "sse-clients",
8463
+ limit: clientLimiter.capacity,
8464
+ retryable: true,
8465
+ retryAfterSeconds: 1
8466
+ });
8467
+ }
8468
+ const url2 = new URL(request2.url);
8469
+ const sessionId = url2.searchParams.get("sessionId") ?? void 0;
8470
+ const model = url2.searchParams.get("model") ?? void 0;
8471
+ const includeBodies = streamIncludesBodies(url2);
8472
+ let controllerRef = null;
8473
+ let cleanedUp = false;
8474
+ let updateTimer = null;
8475
+ const pendingUpdates = /* @__PURE__ */ new Map();
8476
+ const flushPendingUpdates = () => {
8477
+ updateTimer = null;
8478
+ const controller = controllerRef;
8479
+ if (controller === null) return;
8480
+ if (controller.desiredSize !== null && controller.desiredSize <= -16) {
8481
+ controller.close();
8482
+ cleanup();
8483
+ return;
8484
+ }
8485
+ for (const log of pendingUpdates.values()) {
8486
+ if (controller.desiredSize !== null && controller.desiredSize <= -16) {
8487
+ controller.close();
8488
+ cleanup();
8489
+ return;
8490
+ }
8491
+ const payloadLog = includeBodies ? log : compactLogForList(log);
8492
+ controller.enqueue(encodeSse({ type: "update", log: payloadLog }, log.id));
8493
+ }
8494
+ pendingUpdates.clear();
8495
+ };
8496
+ const unsubscribe = onLogUpdate((log) => {
8497
+ if (controllerRef === null) return;
8498
+ if (sessionId !== void 0 && getLogSessionId(log) !== sessionId) return;
8499
+ if (model !== void 0 && log.model !== model) return;
8500
+ pendingUpdates.set(log.id, log);
8501
+ if (updateTimer === null) updateTimer = setTimeout(flushPendingUpdates, UPDATE_COALESCE_MS);
7316
8502
  });
7317
- }
7318
- const Route$T = createFileRoute("/readyz")({
7319
- server: {
7320
- handlers: {
7321
- GET: readinessResponse
8503
+ const cleanup = () => {
8504
+ if (cleanedUp) return;
8505
+ cleanedUp = true;
8506
+ clearInterval(heartbeat);
8507
+ if (updateTimer !== null) clearTimeout(updateTimer);
8508
+ updateTimer = null;
8509
+ pendingUpdates.clear();
8510
+ unsubscribe();
8511
+ lease.release();
8512
+ controllerRef = null;
8513
+ request2.signal.removeEventListener("abort", cleanup);
8514
+ };
8515
+ const heartbeat = setInterval(() => {
8516
+ if (controllerRef !== null) {
8517
+ try {
8518
+ controllerRef.enqueue(new TextEncoder().encode(": heartbeat\n\n"));
8519
+ } catch {
8520
+ cleanup();
8521
+ }
7322
8522
  }
7323
- }
7324
- });
7325
- function livenessResponse() {
7326
- return Response.json({ status: "ok" }, { headers: { "cache-control": "no-store" } });
7327
- }
7328
- const Route$S = createFileRoute("/livez")({
7329
- server: {
7330
- handlers: {
7331
- GET: livenessResponse
8523
+ }, 3e4);
8524
+ request2.signal.addEventListener("abort", cleanup, { once: true });
8525
+ const stream = new ReadableStream({
8526
+ async start(controller) {
8527
+ controllerRef = controller;
8528
+ try {
8529
+ const result = await listLogsCursorPage({
8530
+ sessionId,
8531
+ model,
8532
+ limit: INITIAL_STREAM_LOG_LIMIT,
8533
+ includeBodies,
8534
+ afterLogId: lastEventLogId,
8535
+ anchor: lastEventLogId === void 0 ? "newest" : void 0
8536
+ });
8537
+ if (cleanedUp) return;
8538
+ const logs = result.logs;
8539
+ const newestLogId = logs.at(-1)?.id;
8540
+ controller.enqueue(encodeSse({ type: "init", logs }, newestLogId));
8541
+ } catch (error) {
8542
+ cleanup();
8543
+ controller.error(error);
8544
+ }
8545
+ },
8546
+ cancel() {
8547
+ cleanup();
8548
+ }
8549
+ });
8550
+ return new Response(stream, {
8551
+ headers: {
8552
+ "Content-Type": "text/event-stream",
8553
+ "Cache-Control": "no-cache",
8554
+ Connection: "keep-alive",
8555
+ "X-Accel-Buffering": "no"
7332
8556
  }
7333
- }
7334
- });
7335
- const BACKEND_NOT_FOUND_BODY = {
7336
- error: "not_found",
7337
- message: "Agent Inspector backend does not serve the web UI."
7338
- };
7339
- function backendNotFoundResponse() {
7340
- return Response.json(BACKEND_NOT_FOUND_BODY, {
7341
- status: 404,
7342
- headers: { "cache-control": "no-store" }
7343
8557
  });
7344
8558
  }
7345
- const Route$R = createFileRoute("/$")({
8559
+ const Route$U = createFileRoute("/api/logs/stream")({
7346
8560
  server: {
7347
8561
  handlers: {
7348
- GET: backendNotFoundResponse,
7349
- POST: backendNotFoundResponse,
7350
- PUT: backendNotFoundResponse,
7351
- PATCH: backendNotFoundResponse,
7352
- DELETE: backendNotFoundResponse,
7353
- OPTIONS: backendNotFoundResponse
8562
+ GET: ({ request: request2 }) => createLogsStreamResponse(request2)
7354
8563
  }
7355
8564
  }
7356
8565
  });
7357
- function maskApiKey(apiKey) {
7358
- if (apiKey.length <= 8) return "••••••••";
7359
- return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
8566
+ let acceptingRequests = true;
8567
+ let activeRequests = 0;
8568
+ const drainWaiters = /* @__PURE__ */ new Set();
8569
+ function notifyDrained() {
8570
+ if (activeRequests !== 0) return;
8571
+ for (const resolve2 of drainWaiters) resolve2();
8572
+ drainWaiters.clear();
7360
8573
  }
7361
- const MAX_REST_LOG_PAGE_SIZE = 200;
7362
- const MAX_SEARCH_QUERY_CHARS = 512;
7363
- const MAX_SEARCH_PROJECT_CHARS = 256;
7364
- const MAX_PROVIDER_MODELS = 64;
7365
- const MAX_PROVIDER_MODEL_NAME_CHARS = 256;
7366
- const MAX_PROVIDER_MODEL_METADATA = 64;
7367
- const MAX_SSE_CLIENTS = 32;
7368
- const MAX_ACTIVE_PROVIDER_TESTS = 2;
7369
- const MAX_PROVIDER_TEST_CONCURRENCY = 4;
7370
- const MAX_ACTIVE_PROXY_REQUESTS = 16;
7371
- const ResourceLimitErrorCodeSchema = _enum([
7372
- "invalid-page-request",
7373
- "search-limit-exceeded",
7374
- "provider-model-limit-exceeded",
7375
- "sse-capacity-exceeded",
7376
- "provider-test-capacity-exceeded",
7377
- "proxy-capacity-exceeded"
7378
- ]);
7379
- const ResourceLimitStatusSchema = union([
7380
- literal(400),
7381
- literal(413),
7382
- literal(429),
7383
- literal(503)
7384
- ]);
7385
- const ResourceLimitErrorSchema = object({
7386
- error: string().min(1).max(300),
7387
- code: ResourceLimitErrorCodeSchema,
7388
- status: ResourceLimitStatusSchema,
7389
- resource: string().min(1).max(80),
7390
- limit: number().int().positive(),
7391
- retryable: boolean()
7392
- });
7393
- function createResourceLimitError(input) {
7394
- return ResourceLimitErrorSchema.parse(input);
8574
+ function acquireRuntimeRequest() {
8575
+ if (!acceptingRequests || activeRequests >= MAX_ACTIVE_PROXY_REQUESTS) return null;
8576
+ activeRequests += 1;
8577
+ let released = false;
8578
+ return {
8579
+ release() {
8580
+ if (released) return;
8581
+ released = true;
8582
+ activeRequests = Math.max(0, activeRequests - 1);
8583
+ notifyDrained();
8584
+ }
8585
+ };
7395
8586
  }
7396
- function resourceLimitResponse(input) {
7397
- const body = createResourceLimitError(input);
7398
- const headers = new Headers({ "cache-control": "no-store" });
7399
- if (input.retryAfterSeconds !== void 0) {
7400
- headers.set("retry-after", String(input.retryAfterSeconds));
7401
- }
7402
- return Response.json(body, { status: input.status, headers });
8587
+ function stopRuntimeAdmission() {
8588
+ acceptingRequests = false;
8589
+ notifyDrained();
7403
8590
  }
7404
- function parseBoundedIntegerParameter(input) {
7405
- if (input.value === null) return { ok: true, value: input.fallback };
7406
- const parsed = Number(input.value);
7407
- if (!Number.isSafeInteger(parsed) || parsed < input.minimum || parsed > input.maximum) {
8591
+ async function waitForRuntimeTrafficDrain() {
8592
+ if (activeRequests === 0) return;
8593
+ await new Promise((resolve2) => {
8594
+ drainWaiters.add(resolve2);
8595
+ });
8596
+ }
8597
+ function getRuntimeAdmissionState() {
8598
+ return { acceptingRequests, activeRequests, capacity: MAX_ACTIVE_PROXY_REQUESTS };
8599
+ }
8600
+ const PERFORMANCE_METRICS_SCHEMA = "agent-inspector.performance-metrics/v1";
8601
+ const OPERATIONAL_METRICS_SCHEMA = "agent-inspector.operational-metrics/v1";
8602
+ async function getTruncationMetrics() {
8603
+ const entries = await listSqliteLogCursorEntries({});
8604
+ if (entries === null) {
7408
8605
  return {
7409
- ok: false,
7410
- response: resourceLimitResponse({
7411
- error: `Query parameter "${input.parameter}" must be an integer between ${String(input.minimum)} and ${String(input.maximum)}.`,
7412
- code: "invalid-page-request",
7413
- status: 400,
7414
- resource: input.resource,
7415
- limit: input.maximum,
7416
- retryable: false
7417
- })
8606
+ captureIncompleteLogs: 0,
8607
+ droppedChunks: 0,
8608
+ droppedBytes: 0,
8609
+ source: "unavailable"
7418
8610
  };
7419
8611
  }
7420
- return { ok: true, value: parsed };
8612
+ return {
8613
+ captureIncompleteLogs: entries.filter((entry) => entry.summary?.captureIncomplete === true).length,
8614
+ droppedChunks: entries.reduce((sum, entry) => sum + (entry.summary?.droppedChunks ?? 0), 0),
8615
+ droppedBytes: entries.reduce((sum, entry) => sum + (entry.summary?.droppedBytes ?? 0), 0),
8616
+ source: "sqlite"
8617
+ };
7421
8618
  }
7422
- function createCapacityLimiter(maxActive) {
7423
- const capacity = Number.isSafeInteger(maxActive) && maxActive > 0 ? maxActive : 1;
7424
- let active = 0;
8619
+ async function getSafeOperationalMetrics(sse) {
8620
+ const [readiness, truncation] = await Promise.all([
8621
+ getRuntimeReadiness(),
8622
+ getTruncationMetrics()
8623
+ ]);
8624
+ const admission = getRuntimeAdmissionState();
8625
+ const workers = getFinalizerRuntimeHealth();
8626
+ const sessionQueue = getSessionRuntimeQueueHealth();
8627
+ const storageInventory = getStorageLifecycleInventory();
8628
+ const lastGc = getLastStorageLifecycleGcResult();
8629
+ const reclaimableBytes = storageInventory.artifacts.filter(
8630
+ (artifact) => artifact.cleanupBehavior === "retention-gc" || artifact.cleanupBehavior === "orphan-reclaim"
8631
+ ).reduce((sum, artifact) => sum + artifact.bytes, 0);
8632
+ const queueDepth = workers.pendingJobs + sessionQueue.queuedTasks;
7425
8633
  return {
7426
- capacity,
7427
- activeCount: () => active,
7428
- tryAcquire: () => {
7429
- if (active >= capacity) return null;
7430
- active += 1;
7431
- let released = false;
7432
- return {
7433
- release: () => {
7434
- if (released) return;
7435
- released = true;
7436
- active = Math.max(0, active - 1);
8634
+ schema: PERFORMANCE_METRICS_SCHEMA,
8635
+ activeUpstream: admission.activeRequests,
8636
+ queueDepth,
8637
+ queueBytes: 0,
8638
+ incompleteCaptures: truncation.captureIncompleteLogs,
8639
+ finalizerLagMs: 0,
8640
+ persistenceLagMs: 0,
8641
+ activeLeases: sse.activeClients,
8642
+ runtime: {
8643
+ schema: OPERATIONAL_METRICS_SCHEMA,
8644
+ collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
8645
+ readiness,
8646
+ requests: {
8647
+ activeProxyRequests: admission.activeRequests,
8648
+ proxyCapacity: admission.capacity,
8649
+ acceptingRequests: admission.acceptingRequests
8650
+ },
8651
+ limits: DEFAULT_RUNTIME_BUDGET_POLICY,
8652
+ queues: {
8653
+ finalizerPendingJobs: workers.pendingJobs,
8654
+ sessionQueuedTasks: sessionQueue.queuedTasks,
8655
+ sessionActiveQueues: sessionQueue.activeQueues,
8656
+ sessionQueueLimit: sessionQueue.queueLimit
8657
+ },
8658
+ workers,
8659
+ storage: {
8660
+ totalFiles: storageInventory.totalFiles,
8661
+ totalBytes: storageInventory.totalBytes,
8662
+ reclaimableBytes,
8663
+ lastGc: lastGc === null ? null : {
8664
+ checkedAt: lastGc.checkedAt,
8665
+ filesDeleted: lastGc.filesDeleted,
8666
+ bytesDeleted: lastGc.bytesDeleted,
8667
+ errors: lastGc.errors.length
7437
8668
  }
7438
- };
8669
+ },
8670
+ sse,
8671
+ truncation
8672
+ }
8673
+ };
8674
+ }
8675
+ const Route$T = createFileRoute("/metrics")({
8676
+ server: {
8677
+ handlers: {
8678
+ GET: async () => {
8679
+ const metrics = await getSafeOperationalMetrics(getLogsStreamMetrics());
8680
+ return Response.json(metrics, { headers: { "cache-control": "no-store" } });
8681
+ }
8682
+ }
8683
+ }
8684
+ });
8685
+ function livenessResponse() {
8686
+ return Response.json({ status: "ok" }, { headers: { "cache-control": "no-store" } });
8687
+ }
8688
+ const Route$S = createFileRoute("/livez")({
8689
+ server: {
8690
+ handlers: {
8691
+ GET: livenessResponse
7439
8692
  }
7440
- };
8693
+ }
8694
+ });
8695
+ const BACKEND_NOT_FOUND_BODY = {
8696
+ error: "not_found",
8697
+ message: "Agent Inspector backend does not serve the web UI."
8698
+ };
8699
+ function backendNotFoundResponse() {
8700
+ return Response.json(BACKEND_NOT_FOUND_BODY, {
8701
+ status: 404,
8702
+ headers: { "cache-control": "no-store" }
8703
+ });
7441
8704
  }
7442
- async function runWithConcurrencyLimit(tasks2, maxConcurrent) {
7443
- if (tasks2.length === 0) return;
7444
- const concurrency = Number.isSafeInteger(maxConcurrent) && maxConcurrent > 0 ? maxConcurrent : 1;
7445
- const workerCount = Math.min(concurrency, tasks2.length);
7446
- let nextIndex = 0;
7447
- const runWorker = async () => {
7448
- while (nextIndex < tasks2.length) {
7449
- const index = nextIndex;
7450
- nextIndex += 1;
7451
- const task = tasks2[index];
7452
- if (task !== void 0) await task();
8705
+ const Route$R = createFileRoute("/$")({
8706
+ server: {
8707
+ handlers: {
8708
+ GET: backendNotFoundResponse,
8709
+ POST: backendNotFoundResponse,
8710
+ PUT: backendNotFoundResponse,
8711
+ PATCH: backendNotFoundResponse,
8712
+ DELETE: backendNotFoundResponse,
8713
+ OPTIONS: backendNotFoundResponse
7453
8714
  }
7454
- };
7455
- await Promise.all(Array.from({ length: workerCount }, runWorker));
8715
+ }
8716
+ });
8717
+ function maskApiKey(apiKey) {
8718
+ if (apiKey.length <= 8) return "••••••••";
8719
+ return apiKey.slice(0, 4) + "••••••••" + apiKey.slice(-4);
7456
8720
  }
7457
8721
  function httpUrl() {
7458
8722
  return string().refine((val) => {
@@ -8796,16 +10060,16 @@ function setCache(port, info) {
8796
10060
  cache.set(port, { ...info, expiresAt: Date.now() + CACHE_TTL_MS });
8797
10061
  evictCacheIfNeeded();
8798
10062
  }
8799
- function extractRemotePort(request) {
10063
+ function extractRemotePort(request2) {
8800
10064
  const candidateSockets = [];
8801
- const requestAsUnknown = request;
10065
+ const requestAsUnknown = request2;
8802
10066
  const ctx = pick(requestAsUnknown, "context");
8803
10067
  const nitro = pick(ctx, "nitro");
8804
10068
  const node = pick(nitro, "node");
8805
10069
  const innerReq = pick(node, "req");
8806
10070
  const sock = pick(innerReq, "socket") ?? pick(innerReq, "connection");
8807
10071
  if (sock !== void 0) candidateSockets.push(sock);
8808
- const descriptor = Object.getOwnPropertyDescriptor(request, "socket");
10072
+ const descriptor = Object.getOwnPropertyDescriptor(request2, "socket");
8809
10073
  if (descriptor !== void 0 && descriptor.get === void 0) {
8810
10074
  candidateSockets.push(descriptor.value);
8811
10075
  }
@@ -9009,12 +10273,12 @@ function readIntHeader(headers, name2) {
9009
10273
  if (!Number.isInteger(parsed) || parsed < 0) return null;
9010
10274
  return parsed;
9011
10275
  }
9012
- function readClientHeaders(request) {
9013
- const pid = readIntHeader(request.headers, CLIENT_HEADERS.pid);
10276
+ function readClientHeaders(request2) {
10277
+ const pid = readIntHeader(request2.headers, CLIENT_HEADERS.pid);
9014
10278
  if (pid === null || pid === 0) return null;
9015
- const port = readIntHeader(request.headers, CLIENT_HEADERS.port);
9016
- const cwd = request.headers.get(CLIENT_HEADERS.cwd);
9017
- const projectFolder = request.headers.get(CLIENT_HEADERS.projectFolder);
10279
+ const port = readIntHeader(request2.headers, CLIENT_HEADERS.port);
10280
+ const cwd = request2.headers.get(CLIENT_HEADERS.cwd);
10281
+ const projectFolder = request2.headers.get(CLIENT_HEADERS.projectFolder);
9018
10282
  return {
9019
10283
  pid,
9020
10284
  port: port !== null && port > 0 ? port : null,
@@ -9022,10 +10286,10 @@ function readClientHeaders(request) {
9022
10286
  projectFolder: projectFolder !== null && projectFolder !== "" ? projectFolder : null
9023
10287
  };
9024
10288
  }
9025
- async function getClientInfo(request) {
9026
- const fromHeaders = readClientHeaders(request);
10289
+ async function getClientInfo(request2) {
10290
+ const fromHeaders = readClientHeaders(request2);
9027
10291
  if (fromHeaders !== null) return fromHeaders;
9028
- const remotePort = extractRemotePort(request);
10292
+ const remotePort = extractRemotePort(request2);
9029
10293
  if (remotePort !== null) {
9030
10294
  const cachedPort = getFromCache(remotePort);
9031
10295
  if (cachedPort !== null) return cachedPort;
@@ -9332,585 +10596,569 @@ function isLoopbackHost(hostname) {
9332
10596
  if (LOOPBACK_HOSTNAMES.has(lower2)) return true;
9333
10597
  if (lower2 === "::1") return true;
9334
10598
  if (lower2.startsWith("::ffff:127.")) return true;
9335
- if (lower2.startsWith("::ffff:7f00:")) return true;
9336
- return /^127\./.test(lower2);
9337
- }
9338
- function mappedIpv4Address(hostname) {
9339
- const lower2 = hostname.toLowerCase();
9340
- if (!lower2.startsWith("::ffff:")) return null;
9341
- const suffix = lower2.slice("::ffff:".length);
9342
- if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(suffix)) return suffix;
9343
- const match = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(suffix);
9344
- if (match === null) return null;
9345
- const highText = match[1];
9346
- const lowText = match[2];
9347
- if (highText === void 0 || lowText === void 0) return null;
9348
- const high = Number.parseInt(highText, 16);
9349
- const low = Number.parseInt(lowText, 16);
9350
- return `${String(high >> 8)}.${String(high & 255)}.${String(low >> 8)}.${String(low & 255)}`;
9351
- }
9352
- function isInternalHost(hostname) {
9353
- if (LOOPBACK_HOSTNAMES.has(hostname.toLowerCase())) return true;
9354
- const mappedIpv4 = mappedIpv4Address(hostname);
9355
- if (mappedIpv4 !== null) return isInternalHost(mappedIpv4);
9356
- const ipv4Match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
9357
- if (ipv4Match !== null) {
9358
- const [, a, b] = ipv4Match;
9359
- if (a === void 0 || b === void 0) return false;
9360
- const first = Number.parseInt(a, 10);
9361
- const second = Number.parseInt(b, 10);
9362
- if (first === 127) return true;
9363
- if (first === 10) return true;
9364
- if (first === 172 && second >= 16 && second <= 31) return true;
9365
- if (first === 192 && second === 168) return true;
9366
- if (first === 169 && second === 254) return true;
9367
- if (first === 0) return true;
9368
- if (first === 100 && second >= 64 && second <= 127) return true;
9369
- if (first >= 224) return true;
9370
- if (first === 198 && (second === 18 || second === 19)) return true;
9371
- return false;
9372
- }
9373
- const lower2 = hostname.toLowerCase();
9374
- if (lower2 === "::") return true;
9375
- if (lower2 === "::1") return true;
9376
- if (/^fe[89ab]/.test(lower2)) return true;
9377
- if (lower2.startsWith("fc") || lower2.startsWith("fd")) return true;
9378
- if (lower2.startsWith("ff")) return true;
9379
- return false;
9380
- }
9381
- function evaluateUpstreamUrl(rawUrl, options = {}) {
9382
- if (options.bypass === true) return { allow: true };
9383
- let parsed;
9384
- try {
9385
- parsed = new URL(rawUrl);
9386
- } catch {
9387
- return { allow: false, reason: "Not a valid URL" };
9388
- }
9389
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
9390
- return { allow: false, reason: `Unsupported scheme "${parsed.protocol}"` };
9391
- }
9392
- if (options.allowLoopback === true && parsed.hostname === "localhost") {
9393
- return { allow: true };
9394
- }
9395
- const hostname = parsed.hostname.replace(/^\[|]$/g, "");
9396
- if (isInternalHost(hostname)) {
9397
- if (options.allowLoopback === true) {
9398
- if (isLoopbackHost(hostname)) return { allow: true };
9399
- }
9400
- return {
9401
- allow: false,
9402
- reason: `Refusing to proxy to internal address "${hostname}"`
9403
- };
9404
- }
9405
- return { allow: true };
9406
- }
9407
- async function defaultResolver(hostname) {
9408
- return lookup(hostname, { all: true, verbatim: true });
9409
- }
9410
- function isLoopbackAddress(hostname) {
9411
- const lower2 = hostname.toLowerCase();
9412
- return lower2 === "::1" || lower2.startsWith("::ffff:127.") || lower2.startsWith("::ffff:7f00:") || /^127\./.test(lower2);
9413
- }
9414
- async function evaluateResolvedUpstreamUrl(rawUrl, options = {}) {
9415
- const initial = evaluateUpstreamUrl(rawUrl, options);
9416
- if (!initial.allow || options.bypass === true) return initial;
9417
- const parsed = new URL(rawUrl);
9418
- const hostname = parsed.hostname.replace(/^\[|]$/g, "");
9419
- if (isIP(hostname) !== 0 || hostname.toLowerCase() === "localhost") return initial;
9420
- let addresses;
9421
- try {
9422
- addresses = await (options.resolver ?? defaultResolver)(hostname);
9423
- } catch (error) {
9424
- return {
9425
- allow: false,
9426
- reason: `Could not safely resolve upstream host "${hostname}": ${String(error)}`
9427
- };
9428
- }
9429
- if (addresses.length === 0) {
9430
- return { allow: false, reason: `Upstream host "${hostname}" resolved to no addresses` };
9431
- }
9432
- for (const resolved of addresses) {
9433
- if (!isInternalHost(resolved.address)) continue;
9434
- if (options.allowLoopback === true && isLoopbackAddress(resolved.address)) continue;
9435
- return {
9436
- allow: false,
9437
- reason: `Refusing upstream host "${hostname}" because it resolves to internal address "${resolved.address}"`
9438
- };
9439
- }
9440
- return { allow: true };
9441
- }
9442
- function isRedirectStatus(status) {
9443
- return status >= 300 && status < 400;
9444
- }
9445
- async function safeFetch(rawUrl, init = {}, options = {}) {
9446
- const decision = await evaluateResolvedUpstreamUrl(rawUrl, options);
9447
- if (!decision.allow) {
9448
- return { ok: false, kind: "network-policy", reason: decision.reason };
9449
- }
9450
- const response = await fetch(rawUrl, { ...init, redirect: "manual" });
9451
- if (!isRedirectStatus(response.status)) return { ok: true, response };
9452
- if (response.body !== null) {
9453
- await response.body.cancel().catch(() => void 0);
9454
- }
9455
- const location = response.headers.get("location");
9456
- return {
9457
- ok: false,
9458
- kind: "redirect",
9459
- reason: location === null ? `Upstream returned blocked redirect HTTP ${String(response.status)}` : `Upstream redirect to "${location}" is blocked; configure the final API URL directly`
9460
- };
9461
- }
9462
- const BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
9463
- function isClaudeCodeBillingHeaderBlock(text) {
9464
- const trimmed = text.trimStart();
9465
- return trimmed.toLowerCase().startsWith(BILLING_HEADER_PREFIX);
9466
- }
9467
- function isObjectWithSystem(value) {
9468
- if (!isPlainRecord(value)) return false;
9469
- return Object.prototype.hasOwnProperty.call(value, "system");
9470
- }
9471
- function isBillingHeaderTextBlock(block) {
9472
- if (!isPlainRecord(block)) return false;
9473
- const typeVal = safeGetOwnProperty(block, "type");
9474
- const textVal = safeGetOwnProperty(block, "text");
9475
- if (typeof typeVal !== "string" || typeVal !== "text") return false;
9476
- if (typeof textVal !== "string") return false;
9477
- return isClaudeCodeBillingHeaderBlock(textVal);
9478
- }
9479
- function stripClaudeCodeBillingHeader(rawBody) {
9480
- let parsed;
9481
- try {
9482
- parsed = JSON.parse(rawBody);
9483
- } catch {
9484
- return { body: rawBody, removed: 0 };
9485
- }
9486
- if (!isObjectWithSystem(parsed)) {
9487
- return { body: rawBody, removed: 0 };
9488
- }
9489
- const system = parsed.system;
9490
- if (!Array.isArray(system) || system.length === 0) {
9491
- return { body: rawBody, removed: 0 };
9492
- }
9493
- const kept = [];
9494
- let removed = 0;
9495
- for (const block of system) {
9496
- if (isBillingHeaderTextBlock(block)) {
9497
- removed += 1;
9498
- continue;
9499
- }
9500
- kept.push(block);
9501
- }
9502
- if (removed === 0) {
9503
- return { body: rawBody, removed: 0 };
9504
- }
9505
- if (kept.length === 0) {
9506
- delete parsed.system;
9507
- } else {
9508
- parsed.system = kept;
9509
- }
9510
- return { body: JSON.stringify(parsed), removed };
9511
- }
9512
- const ROLE_ASSISTANT = "assistant";
9513
- const ROLE_TOOL = "tool";
9514
- function isObjectWithMessages(value) {
9515
- if (!isPlainRecord(value)) return false;
9516
- if (!Object.prototype.hasOwnProperty.call(value, "messages")) return false;
9517
- const messages = safeGetOwnProperty(value, "messages");
9518
- return Array.isArray(messages);
9519
- }
9520
- function collectAssistantToolCallIds(message, into) {
9521
- if (!isPlainRecord(message)) return;
9522
- const toolCalls = safeGetOwnProperty(message, "tool_calls");
9523
- if (!Array.isArray(toolCalls)) return;
9524
- for (const tc of toolCalls) {
9525
- if (!isPlainRecord(tc)) continue;
9526
- const id = safeGetOwnProperty(tc, "id");
9527
- if (typeof id === "string" && id.length > 0) {
9528
- into.add(id);
9529
- }
9530
- }
9531
- }
9532
- function findOrphanToolMessageIndices(messages) {
9533
- const knownToolCallIds = /* @__PURE__ */ new Set();
9534
- const indices = [];
9535
- const orphanIds = [];
9536
- for (let i = 0; i < messages.length; i++) {
9537
- const msg = messages[i];
9538
- if (!isPlainRecord(msg)) continue;
9539
- const role = safeGetOwnProperty(msg, "role");
9540
- if (role === ROLE_ASSISTANT) {
9541
- collectAssistantToolCallIds(msg, knownToolCallIds);
9542
- continue;
9543
- }
9544
- if (role === ROLE_TOOL) {
9545
- const id = safeGetOwnProperty(msg, "tool_call_id");
9546
- if (typeof id !== "string" || id.length === 0) {
9547
- indices.push(i);
9548
- orphanIds.push(null);
9549
- continue;
9550
- }
9551
- if (!knownToolCallIds.has(id)) {
9552
- indices.push(i);
9553
- orphanIds.push(id);
9554
- }
9555
- }
10599
+ if (lower2.startsWith("::ffff:7f00:")) return true;
10600
+ return /^127\./.test(lower2);
10601
+ }
10602
+ function mappedIpv4Address(hostname) {
10603
+ const lower2 = hostname.toLowerCase();
10604
+ if (!lower2.startsWith("::ffff:")) return null;
10605
+ const suffix = lower2.slice("::ffff:".length);
10606
+ if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(suffix)) return suffix;
10607
+ const match = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(suffix);
10608
+ if (match === null) return null;
10609
+ const highText = match[1];
10610
+ const lowText = match[2];
10611
+ if (highText === void 0 || lowText === void 0) return null;
10612
+ const high = Number.parseInt(highText, 16);
10613
+ const low = Number.parseInt(lowText, 16);
10614
+ return `${String(high >> 8)}.${String(high & 255)}.${String(low >> 8)}.${String(low & 255)}`;
10615
+ }
10616
+ function isInternalHost(hostname) {
10617
+ if (LOOPBACK_HOSTNAMES.has(hostname.toLowerCase())) return true;
10618
+ const mappedIpv4 = mappedIpv4Address(hostname);
10619
+ if (mappedIpv4 !== null) return isInternalHost(mappedIpv4);
10620
+ const ipv4Match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
10621
+ if (ipv4Match !== null) {
10622
+ const [, a, b] = ipv4Match;
10623
+ if (a === void 0 || b === void 0) return false;
10624
+ const first = Number.parseInt(a, 10);
10625
+ const second = Number.parseInt(b, 10);
10626
+ if (first === 127) return true;
10627
+ if (first === 10) return true;
10628
+ if (first === 172 && second >= 16 && second <= 31) return true;
10629
+ if (first === 192 && second === 168) return true;
10630
+ if (first === 169 && second === 254) return true;
10631
+ if (first === 0) return true;
10632
+ if (first === 100 && second >= 64 && second <= 127) return true;
10633
+ if (first >= 224) return true;
10634
+ if (first === 198 && (second === 18 || second === 19)) return true;
10635
+ return false;
9556
10636
  }
9557
- return { indices, orphanIds };
10637
+ const lower2 = hostname.toLowerCase();
10638
+ if (lower2 === "::") return true;
10639
+ if (lower2 === "::1") return true;
10640
+ if (/^fe[89ab]/.test(lower2)) return true;
10641
+ if (lower2.startsWith("fc") || lower2.startsWith("fd")) return true;
10642
+ if (lower2.startsWith("ff")) return true;
10643
+ return false;
9558
10644
  }
9559
- function stripOpenAIOrphanToolMessages(rawBody) {
10645
+ function evaluateUpstreamUrl(rawUrl, options = {}) {
10646
+ if (options.bypass === true) return { allow: true };
9560
10647
  let parsed;
9561
10648
  try {
9562
- parsed = JSON.parse(rawBody);
10649
+ parsed = new URL(rawUrl);
9563
10650
  } catch {
9564
- return { body: rawBody, removed: 0, orphanIds: [] };
10651
+ return { allow: false, reason: "Not a valid URL" };
9565
10652
  }
9566
- if (!isObjectWithMessages(parsed)) {
9567
- return { body: rawBody, removed: 0, orphanIds: [] };
10653
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
10654
+ return { allow: false, reason: `Unsupported scheme "${parsed.protocol}"` };
9568
10655
  }
9569
- const messages = parsed.messages;
9570
- const { indices, orphanIds } = findOrphanToolMessageIndices(messages);
9571
- if (indices.length === 0) {
9572
- return { body: rawBody, removed: 0, orphanIds: [] };
10656
+ if (options.allowLoopback === true && parsed.hostname === "localhost") {
10657
+ return { allow: true };
9573
10658
  }
9574
- const dropSet = new Set(indices);
9575
- const kept = [];
9576
- for (let i = 0; i < messages.length; i++) {
9577
- if (!dropSet.has(i)) kept.push(messages[i]);
10659
+ const hostname = parsed.hostname.replace(/^\[|]$/g, "");
10660
+ if (isInternalHost(hostname)) {
10661
+ if (options.allowLoopback === true) {
10662
+ if (isLoopbackHost(hostname)) return { allow: true };
10663
+ }
10664
+ return {
10665
+ allow: false,
10666
+ reason: `Refusing to proxy to internal address "${hostname}"`
10667
+ };
9578
10668
  }
9579
- parsed.messages = kept;
9580
- return { body: JSON.stringify(parsed), removed: indices.length, orphanIds };
10669
+ return { allow: true };
9581
10670
  }
9582
- const UNASSIGNED_SESSION_QUEUE = "__unassigned__";
9583
- const queues = /* @__PURE__ */ new Map();
9584
- function queueKeyForSession(sessionId) {
9585
- return sessionId ?? UNASSIGNED_SESSION_QUEUE;
10671
+ async function defaultResolver(hostname) {
10672
+ return lookup(hostname, { all: true, verbatim: true });
9586
10673
  }
9587
- function errorMessage$1(err) {
9588
- return err instanceof Error ? err.message : String(err);
10674
+ function isLoopbackAddress(hostname) {
10675
+ const lower2 = hostname.toLowerCase();
10676
+ return lower2 === "::1" || lower2.startsWith("::ffff:127.") || lower2.startsWith("::ffff:7f00:") || /^127\./.test(lower2);
9589
10677
  }
9590
- async function runSessionTask(sessionId, taskName, task) {
9591
- markSessionTaskStarted(sessionId);
10678
+ async function evaluateResolvedUpstreamUrlForFetch(rawUrl, options) {
10679
+ const initial = evaluateUpstreamUrl(rawUrl, options);
10680
+ if (!initial.allow) return initial;
10681
+ const parsed = new URL(rawUrl);
10682
+ const hostname = parsed.hostname.replace(/^\[|]$/g, "");
10683
+ if (options.bypass === true) return { allow: true, hostname, addresses: [] };
10684
+ const ipFamily = isIP(hostname);
10685
+ if (ipFamily !== 0)
10686
+ return { allow: true, hostname, addresses: [{ address: hostname, family: ipFamily }] };
10687
+ let addresses;
9592
10688
  try {
9593
- const result = await task();
9594
- markSessionTaskFinished(sessionId, null);
9595
- return result;
9596
- } catch (err) {
9597
- const message = errorMessage$1(err);
9598
- logger.error(`[sessionRuntime] ${taskName} failed for session ${sessionId ?? "unassigned"}`);
9599
- markSessionTaskFinished(sessionId, message);
9600
- return Promise.reject(err);
10689
+ addresses = await (options.resolver ?? defaultResolver)(hostname);
10690
+ } catch (error) {
10691
+ return {
10692
+ allow: false,
10693
+ reason: `Could not safely resolve upstream host "${hostname}": ${String(error)}`
10694
+ };
9601
10695
  }
10696
+ if (addresses.length === 0) {
10697
+ return { allow: false, reason: `Upstream host "${hostname}" resolved to no addresses` };
10698
+ }
10699
+ for (const resolved of addresses) {
10700
+ if (!isInternalHost(resolved.address)) continue;
10701
+ if (options.allowLoopback === true && isLoopbackAddress(resolved.address)) continue;
10702
+ return {
10703
+ allow: false,
10704
+ reason: `Refusing upstream host "${hostname}" because it resolves to internal address "${resolved.address}"`
10705
+ };
10706
+ }
10707
+ return { allow: true, hostname, addresses };
9602
10708
  }
9603
- function enqueueSessionTask(sessionId, taskName, task) {
9604
- const queueKey = queueKeyForSession(sessionId);
9605
- const previous = queues.get(queueKey) ?? Promise.resolve();
9606
- markSessionTaskQueued(sessionId);
9607
- const current = previous.catch(() => void 0).then(() => runSessionTask(sessionId, taskName, task));
9608
- const settled = current.then(
9609
- () => void 0,
9610
- () => void 0
9611
- );
9612
- queues.set(queueKey, settled);
9613
- void settled.then(() => {
9614
- if (queues.get(queueKey) === settled) {
9615
- queues.delete(queueKey);
9616
- }
9617
- });
9618
- return current;
9619
- }
9620
- function enqueueFinalizeLogJob(job) {
9621
- return enqueueSessionTask(
9622
- getLogSessionId(job.log),
9623
- "finalize-log",
9624
- () => executeFinalizeLogJob(job)
9625
- );
10709
+ function isRedirectStatus(status) {
10710
+ return status >= 300 && status < 400;
9626
10711
  }
9627
- async function flushSessionTasks() {
9628
- while (queues.size > 0) {
9629
- await Promise.all([...queues.values()]);
9630
- }
10712
+ function formatByteLimit(limit) {
10713
+ return `${String(limit)} bytes`;
9631
10714
  }
9632
- const DEFAULT_MEMORY_LIMIT_BYTES = 1024 * 1024;
9633
- const FILE_WRITE_BATCH_BYTES = 64 * 1024;
9634
- const DEFAULT_QUEUE_MAX_MESSAGES = 256;
9635
- const DEFAULT_QUEUE_MAX_BYTES = 8 * 1024 * 1024;
9636
- const DEFAULT_GLOBAL_QUEUE_MAX_MESSAGES = 4096;
9637
- const DEFAULT_GLOBAL_QUEUE_MAX_BYTES = 64 * 1024 * 1024;
9638
- const activeCaptures = /* @__PURE__ */ new Set();
9639
- function resolveMemoryLimitBytes() {
9640
- const raw = process.env["AGENT_INSPECTOR_STREAM_MEMORY_LIMIT_BYTES"];
9641
- if (raw === void 0 || raw === "") return DEFAULT_MEMORY_LIMIT_BYTES;
9642
- const parsed = Number(raw);
9643
- if (!Number.isInteger(parsed) || parsed < 0) return DEFAULT_MEMORY_LIMIT_BYTES;
10715
+ function parseContentLength(value) {
10716
+ if (value === null) return null;
10717
+ const parsed = Number(value);
10718
+ if (!Number.isSafeInteger(parsed) || parsed < 0) return null;
9644
10719
  return parsed;
9645
10720
  }
9646
- function resolvePositiveIntegerEnv(name2, fallback) {
9647
- const raw = process.env[name2];
9648
- if (raw === void 0 || raw === "") return fallback;
9649
- const parsed = Number(raw);
9650
- if (!Number.isSafeInteger(parsed) || parsed < 1) return fallback;
9651
- return parsed;
10721
+ function isPinnedDnsEnabled(options) {
10722
+ const disabledForTest = typeof process !== "undefined" && process.env["NODE_ENV"] === "test" && process.env["AGENT_INSPECTOR_DISABLE_DNS_PINNING_FOR_TESTS"] === "1";
10723
+ if (disabledForTest) return false;
10724
+ return options.pinDns !== false && options.bypass !== true;
9652
10725
  }
9653
- function defaultQueueMaxMessages() {
9654
- return resolvePositiveIntegerEnv(
9655
- "AGENT_INSPECTOR_OBSERVATION_QUEUE_MESSAGES",
9656
- DEFAULT_QUEUE_MAX_MESSAGES
9657
- );
10726
+ function choosePinnedAddress(addresses) {
10727
+ return addresses[0] ?? null;
9658
10728
  }
9659
- function defaultQueueMaxBytes() {
9660
- return resolvePositiveIntegerEnv(
9661
- "AGENT_INSPECTOR_OBSERVATION_QUEUE_BYTES",
9662
- DEFAULT_QUEUE_MAX_BYTES
9663
- );
10729
+ function hostHeaderValue(parsed) {
10730
+ return parsed.port.length === 0 ? parsed.host : `${parsed.hostname}:${parsed.port}`;
9664
10731
  }
9665
- class RawStreamObservationBudget {
9666
- constructor(maxMessages, maxBytes) {
9667
- this.maxMessages = maxMessages;
9668
- this.maxBytes = maxBytes;
9669
- }
9670
- maxMessages;
9671
- maxBytes;
9672
- messages = 0;
9673
- bytes = 0;
9674
- tryAcquire(bytes) {
9675
- if (this.messages + 1 > this.maxMessages || this.bytes + bytes > this.maxBytes) return null;
9676
- this.messages += 1;
9677
- this.bytes += bytes;
9678
- let released = false;
9679
- return () => {
9680
- if (released) return;
9681
- released = true;
9682
- this.messages -= 1;
9683
- this.bytes -= bytes;
9684
- };
10732
+ function headersToRecord$1(headers, parsed) {
10733
+ const normalized = new Headers(headers);
10734
+ if (!normalized.has("host")) {
10735
+ normalized.set("host", hostHeaderValue(parsed));
9685
10736
  }
10737
+ const result = {};
10738
+ normalized.forEach((value, key) => {
10739
+ result[key] = value;
10740
+ });
10741
+ return result;
9686
10742
  }
9687
- const globalObservationBudget = new RawStreamObservationBudget(
9688
- resolvePositiveIntegerEnv(
9689
- "AGENT_INSPECTOR_OBSERVATION_GLOBAL_QUEUE_MESSAGES",
9690
- DEFAULT_GLOBAL_QUEUE_MAX_MESSAGES
9691
- ),
9692
- resolvePositiveIntegerEnv(
9693
- "AGENT_INSPECTOR_OBSERVATION_GLOBAL_QUEUE_BYTES",
9694
- DEFAULT_GLOBAL_QUEUE_MAX_BYTES
9695
- )
9696
- );
9697
- function getRawStreamDir() {
9698
- return join(getDataDir(), "raw-streams");
9699
- }
9700
- function createRawStreamPath() {
9701
- return join(getRawStreamDir(), `${Date.now()}-${randomUUID()}.stream.tmp`);
9702
- }
9703
- class RawStreamCapture {
9704
- memoryLimitBytes;
9705
- queueMaxMessages;
9706
- queueMaxBytes;
9707
- globalBudget;
9708
- chunks = [];
9709
- byteLength = 0;
9710
- filePath = null;
9711
- fileHandle = null;
9712
- pendingFileChunks = [];
9713
- pendingFileBytes = 0;
9714
- operation = Promise.resolve();
9715
- accepting = true;
9716
- closed = false;
9717
- sawChunk = false;
9718
- queuedMessages = 0;
9719
- queuedBytes = 0;
9720
- incomplete = false;
9721
- storageFailed = false;
9722
- droppedChunks = 0;
9723
- droppedBytes = 0;
9724
- gapReason = null;
9725
- constructor(options = {}) {
9726
- this.memoryLimitBytes = options.memoryLimitBytes ?? resolveMemoryLimitBytes();
9727
- this.queueMaxMessages = options.queueMaxMessages ?? defaultQueueMaxMessages();
9728
- this.queueMaxBytes = options.queueMaxBytes ?? defaultQueueMaxBytes();
9729
- this.globalBudget = options.globalBudget ?? globalObservationBudget;
9730
- activeCaptures.add(this);
9731
- }
9732
- get hasChunks() {
9733
- return this.sawChunk;
10743
+ async function requestBodyToUint8Array(body) {
10744
+ if (body === void 0 || body === null) return { ok: true, body: null, contentType: null };
10745
+ if (typeof body === "string") {
10746
+ return { ok: true, body: new TextEncoder().encode(body), contentType: null };
9734
10747
  }
9735
- get status() {
10748
+ if (body instanceof URLSearchParams) {
9736
10749
  return {
9737
- incomplete: this.incomplete,
9738
- droppedChunks: this.droppedChunks,
9739
- droppedBytes: this.droppedBytes,
9740
- reason: this.gapReason
10750
+ ok: true,
10751
+ body: new TextEncoder().encode(body.toString()),
10752
+ contentType: "application/x-www-form-urlencoded;charset=UTF-8"
9741
10753
  };
9742
10754
  }
9743
- recordDroppedObservation(reason, observedBytes) {
9744
- const safeObservedBytes = Number.isSafeInteger(observedBytes) && observedBytes >= 0 ? observedBytes : 0;
9745
- this.recordGap(reason, safeObservedBytes, safeObservedBytes > 0);
10755
+ if (body instanceof Uint8Array) return { ok: true, body, contentType: null };
10756
+ if (body instanceof ArrayBuffer) {
10757
+ return { ok: true, body: new Uint8Array(body), contentType: null };
9746
10758
  }
9747
- async append(text) {
9748
- if (text === "") return;
9749
- const next = this.operation.then(() => this.appendInternal(text));
9750
- this.operation = next.catch(() => void 0);
9751
- return next;
10759
+ if (body instanceof Blob) {
10760
+ return { ok: true, body: new Uint8Array(await body.arrayBuffer()), contentType: body.type };
9752
10761
  }
9753
- /**
9754
- * Submit an observation without waiting for file I/O. Once capacity is exceeded, capture remains
9755
- * incomplete and all later chunks are counted as dropped so the persisted value is an honest
9756
- * prefix rather than a misleading stream with an unmarked hole.
9757
- */
9758
- tryAppend(text, observedBytes) {
9759
- const safeObservedBytes = Number.isSafeInteger(observedBytes) && observedBytes >= 0 ? observedBytes : 0;
9760
- const queuedBytes = Math.max(safeObservedBytes, Buffer.byteLength(text, "utf-8"));
9761
- if (!this.accepting || this.closed) {
9762
- this.recordGap("pipeline-closed", safeObservedBytes, safeObservedBytes > 0 || text !== "");
9763
- return false;
9764
- }
9765
- if (this.incomplete) {
9766
- this.recordGap("capacity-exceeded", safeObservedBytes, safeObservedBytes > 0 || text !== "");
9767
- return false;
9768
- }
9769
- if (text === "") return true;
9770
- if (this.queuedMessages + 1 > this.queueMaxMessages || this.queuedBytes + queuedBytes > this.queueMaxBytes) {
9771
- this.recordGap("capacity-exceeded", safeObservedBytes, true);
9772
- return false;
10762
+ return {
10763
+ ok: false,
10764
+ reason: "Pinned upstream fetch only supports buffered request bodies"
10765
+ };
10766
+ }
10767
+ function pinnedLookup(pinnedAddress) {
10768
+ return (_hostname, options, callback) => {
10769
+ if (options.all === true) {
10770
+ callback(null, [pinnedAddress]);
10771
+ return;
9773
10772
  }
9774
- const releaseGlobal = this.globalBudget.tryAcquire(queuedBytes);
9775
- if (releaseGlobal === null) {
9776
- this.recordGap("capacity-exceeded", safeObservedBytes, true);
9777
- return false;
10773
+ callback(null, pinnedAddress.address, pinnedAddress.family);
10774
+ };
10775
+ }
10776
+ function requestOptionsForPinnedFetch(parsed, init, pinnedAddress, contentType) {
10777
+ const headers = headersToRecord$1(init.headers, parsed);
10778
+ if (contentType !== null && !new Headers(headers).has("content-type")) {
10779
+ headers["content-type"] = contentType;
10780
+ }
10781
+ return {
10782
+ protocol: parsed.protocol,
10783
+ hostname: parsed.hostname,
10784
+ port: parsed.port,
10785
+ path: `${parsed.pathname}${parsed.search}`,
10786
+ method: init.method ?? "GET",
10787
+ headers,
10788
+ lookup: pinnedLookup(pinnedAddress)
10789
+ };
10790
+ }
10791
+ function streamFromIncomingMessage(incoming, maxBytes) {
10792
+ let seenBytes = 0;
10793
+ return new ReadableStream({
10794
+ start(controller) {
10795
+ incoming.on("data", (chunk) => {
10796
+ seenBytes += chunk.byteLength;
10797
+ if (maxBytes !== void 0 && seenBytes > maxBytes) {
10798
+ incoming.destroy(
10799
+ new Error(
10800
+ `Upstream response exceeded configured limit of ${formatByteLimit(maxBytes)}`
10801
+ )
10802
+ );
10803
+ controller.error(
10804
+ new Error(
10805
+ `Upstream response exceeded configured limit of ${formatByteLimit(maxBytes)}`
10806
+ )
10807
+ );
10808
+ return;
10809
+ }
10810
+ controller.enqueue(chunk);
10811
+ });
10812
+ incoming.on("end", () => {
10813
+ controller.close();
10814
+ });
10815
+ incoming.on("error", (error) => {
10816
+ controller.error(error);
10817
+ });
10818
+ },
10819
+ cancel() {
10820
+ incoming.destroy();
9778
10821
  }
9779
- this.queuedMessages += 1;
9780
- this.queuedBytes += queuedBytes;
9781
- this.operation = this.operation.then(async () => {
9782
- if (this.storageFailed) {
9783
- this.recordGap("write-failed", safeObservedBytes, true);
10822
+ });
10823
+ }
10824
+ async function cancelBody(response) {
10825
+ if (response.body === null) return;
10826
+ await response.body.cancel().catch(() => void 0);
10827
+ }
10828
+ function responseWithByteLimit(response, maxBytes) {
10829
+ if (response.body === null) return response;
10830
+ let seenBytes = 0;
10831
+ const reader = response.body.getReader();
10832
+ const body = new ReadableStream({
10833
+ async pull(controller) {
10834
+ const chunk = await reader.read();
10835
+ if (chunk.done) {
10836
+ controller.close();
9784
10837
  return;
9785
10838
  }
9786
- await this.appendInternal(text);
9787
- }).catch(() => {
9788
- this.storageFailed = true;
9789
- this.recordGap("write-failed", safeObservedBytes, true);
9790
- }).finally(() => {
9791
- this.queuedMessages -= 1;
9792
- this.queuedBytes -= queuedBytes;
9793
- releaseGlobal();
9794
- });
9795
- return true;
10839
+ seenBytes += chunk.value.byteLength;
10840
+ if (seenBytes > maxBytes) {
10841
+ void reader.cancel().catch(() => void 0);
10842
+ controller.error(
10843
+ new Error(`Upstream response exceeded configured limit of ${formatByteLimit(maxBytes)}`)
10844
+ );
10845
+ return;
10846
+ }
10847
+ controller.enqueue(chunk.value);
10848
+ },
10849
+ cancel(reason) {
10850
+ return reader.cancel(reason);
10851
+ }
10852
+ });
10853
+ return new Response(body, {
10854
+ status: response.status,
10855
+ statusText: response.statusText,
10856
+ headers: response.headers
10857
+ });
10858
+ }
10859
+ function shouldUseBudgetedSignal(init, deadlineMs) {
10860
+ return deadlineMs !== void 0 || init.signal !== void 0;
10861
+ }
10862
+ async function pinnedNodeFetch(rawUrl, init, options, decision) {
10863
+ const parsed = new URL(rawUrl);
10864
+ const pinnedAddress = choosePinnedAddress(decision.addresses);
10865
+ if (pinnedAddress === null) {
10866
+ return {
10867
+ ok: false,
10868
+ kind: "network-policy",
10869
+ reason: `Could not pin a resolved upstream address for "${decision.hostname}"`
10870
+ };
9796
10871
  }
9797
- async finalize() {
9798
- this.accepting = false;
9799
- const next = this.operation.then(() => this.finalizeInternal());
9800
- this.operation = next.then(
9801
- () => void 0,
9802
- () => void 0
10872
+ const encodedBody = await requestBodyToUint8Array(init.body);
10873
+ if (!encodedBody.ok) {
10874
+ return { ok: false, kind: "resource-limit", reason: encodedBody.reason };
10875
+ }
10876
+ return await new Promise((resolve2, reject) => {
10877
+ const linkedSignal = init.signal ?? void 0;
10878
+ const transport = parsed.protocol === "https:" ? request : request$1;
10879
+ const requestOptions = requestOptionsForPinnedFetch(
10880
+ parsed,
10881
+ init,
10882
+ pinnedAddress,
10883
+ encodedBody.contentType
9803
10884
  );
9804
- return next.finally(() => activeCaptures.delete(this));
9805
- }
9806
- async appendInternal(text) {
9807
- if (this.closed) return;
9808
- this.sawChunk = true;
9809
- const nextBytes = Buffer.byteLength(text, "utf-8");
9810
- if (this.filePath === null && this.byteLength + nextBytes <= this.memoryLimitBytes) {
9811
- this.chunks.push(text);
9812
- this.byteLength += nextBytes;
9813
- return;
10885
+ const request$2 = transport(requestOptions, (incoming) => {
10886
+ const status = incoming.statusCode ?? 0;
10887
+ if (isRedirectStatus(status)) {
10888
+ incoming.resume();
10889
+ const location = incoming.headers.location;
10890
+ resolve2({
10891
+ ok: false,
10892
+ kind: "redirect",
10893
+ reason: location === void 0 ? `Upstream returned blocked redirect HTTP ${String(status)}` : `Upstream redirect to "${location}" is blocked; configure the final API URL directly`
10894
+ });
10895
+ return;
10896
+ }
10897
+ const maxBytes = options.responseBytes;
10898
+ const headers = new Headers();
10899
+ for (const [key, value] of Object.entries(incoming.headers)) {
10900
+ if (Array.isArray(value)) {
10901
+ headers.set(key, value.join(", "));
10902
+ continue;
10903
+ }
10904
+ if (value !== void 0) headers.set(key, value);
10905
+ }
10906
+ if (maxBytes !== void 0) {
10907
+ const declaredBytes = parseContentLength(headers.get("content-length"));
10908
+ if (declaredBytes !== null && declaredBytes > maxBytes) {
10909
+ incoming.destroy();
10910
+ resolve2({
10911
+ ok: false,
10912
+ kind: "resource-limit",
10913
+ reason: `Upstream declared ${String(declaredBytes)} response bytes, exceeding configured limit of ${formatByteLimit(maxBytes)}`
10914
+ });
10915
+ return;
10916
+ }
10917
+ }
10918
+ resolve2({
10919
+ ok: true,
10920
+ response: new Response(streamFromIncomingMessage(incoming, maxBytes), {
10921
+ status,
10922
+ statusText: incoming.statusMessage,
10923
+ headers
10924
+ })
10925
+ });
10926
+ });
10927
+ let timedOut = false;
10928
+ let timeout;
10929
+ const finishAbort = () => {
10930
+ request$2.destroy(new DOMException("Safe fetch aborted", "AbortError"));
10931
+ };
10932
+ if (linkedSignal !== void 0) {
10933
+ if (linkedSignal.aborted) {
10934
+ finishAbort();
10935
+ } else {
10936
+ linkedSignal.addEventListener("abort", finishAbort, { once: true });
10937
+ }
9814
10938
  }
9815
- if (this.filePath === null) {
9816
- await this.spillToFile();
10939
+ if (options.deadlineMs !== void 0) {
10940
+ timeout = setTimeout(() => {
10941
+ timedOut = true;
10942
+ request$2.destroy(new DOMException("Safe fetch deadline exceeded", "TimeoutError"));
10943
+ }, options.deadlineMs);
9817
10944
  }
9818
- if (this.filePath !== null) {
9819
- this.pendingFileChunks.push(text);
9820
- this.pendingFileBytes += nextBytes;
9821
- this.byteLength += nextBytes;
9822
- if (this.pendingFileBytes >= FILE_WRITE_BATCH_BYTES) await this.flushFileBuffer();
10945
+ request$2.on("error", (error) => {
10946
+ if (timeout !== void 0) clearTimeout(timeout);
10947
+ if (linkedSignal !== void 0) {
10948
+ linkedSignal.removeEventListener("abort", finishAbort);
10949
+ }
10950
+ if (timedOut) {
10951
+ resolve2({
10952
+ ok: false,
10953
+ kind: "timeout",
10954
+ reason: `Upstream request exceeded configured deadline of ${String(options.deadlineMs)} ms`
10955
+ });
10956
+ return;
10957
+ }
10958
+ reject(error);
10959
+ });
10960
+ request$2.on("close", () => {
10961
+ if (timeout !== void 0) clearTimeout(timeout);
10962
+ if (linkedSignal !== void 0) {
10963
+ linkedSignal.removeEventListener("abort", finishAbort);
10964
+ }
10965
+ });
10966
+ if (encodedBody.body !== null) request$2.write(encodedBody.body);
10967
+ request$2.end();
10968
+ });
10969
+ }
10970
+ async function safeFetch(rawUrl, init = {}, options = {}) {
10971
+ const resolvedDecision = await evaluateResolvedUpstreamUrlForFetch(rawUrl, options);
10972
+ if (!resolvedDecision.allow) {
10973
+ return { ok: false, kind: "network-policy", reason: resolvedDecision.reason };
10974
+ }
10975
+ if (isPinnedDnsEnabled(options)) {
10976
+ return await pinnedNodeFetch(rawUrl, init, options, resolvedDecision);
10977
+ }
10978
+ const deadlineMs = options.deadlineMs;
10979
+ const useBudgetedSignal = shouldUseBudgetedSignal(init, deadlineMs);
10980
+ const controller = useBudgetedSignal ? new AbortController() : void 0;
10981
+ let timedOut = false;
10982
+ let timeout;
10983
+ const linkedSignal = init.signal ?? void 0;
10984
+ const forwardAbort = () => {
10985
+ controller?.abort(linkedSignal?.reason);
10986
+ };
10987
+ if (controller !== void 0 && linkedSignal !== void 0) {
10988
+ if (linkedSignal.aborted) {
10989
+ forwardAbort();
10990
+ } else {
10991
+ linkedSignal.addEventListener("abort", forwardAbort, { once: true });
9823
10992
  }
9824
10993
  }
9825
- recordGap(reason, observedBytes, countChunk) {
9826
- this.incomplete = true;
9827
- this.gapReason ??= reason;
9828
- if (countChunk) this.droppedChunks += 1;
9829
- this.droppedBytes += observedBytes;
10994
+ if (controller !== void 0 && deadlineMs !== void 0) {
10995
+ timeout = setTimeout(() => {
10996
+ timedOut = true;
10997
+ controller.abort(new DOMException("Safe fetch deadline exceeded", "TimeoutError"));
10998
+ }, deadlineMs);
9830
10999
  }
9831
- async finalizeInternal() {
9832
- if (!this.closed) {
9833
- this.closed = true;
9834
- await this.flushFileBuffer();
9835
- if (this.fileHandle !== null) {
9836
- const handle = this.fileHandle;
9837
- this.fileHandle = null;
9838
- try {
9839
- await handle.sync();
9840
- } finally {
9841
- await handle.close();
9842
- }
11000
+ try {
11001
+ const response = await fetch(rawUrl, {
11002
+ ...init,
11003
+ redirect: "manual",
11004
+ signal: controller?.signal ?? init.signal
11005
+ });
11006
+ if (!isRedirectStatus(response.status)) {
11007
+ const maxBytes = options.responseBytes;
11008
+ if (maxBytes === void 0) return { ok: true, response };
11009
+ const declaredBytes = parseContentLength(response.headers.get("content-length"));
11010
+ if (declaredBytes !== null && declaredBytes > maxBytes) {
11011
+ await cancelBody(response);
11012
+ return {
11013
+ ok: false,
11014
+ kind: "resource-limit",
11015
+ reason: `Upstream declared ${String(declaredBytes)} response bytes, exceeding configured limit of ${formatByteLimit(maxBytes)}`
11016
+ };
9843
11017
  }
11018
+ return { ok: true, response: responseWithByteLimit(response, maxBytes) };
9844
11019
  }
9845
- if (this.filePath !== null) {
9846
- return { type: "file", path: this.filePath };
11020
+ await cancelBody(response);
11021
+ const location = response.headers.get("location");
11022
+ return {
11023
+ ok: false,
11024
+ kind: "redirect",
11025
+ reason: location === null ? `Upstream returned blocked redirect HTTP ${String(response.status)}` : `Upstream redirect to "${location}" is blocked; configure the final API URL directly`
11026
+ };
11027
+ } catch (error) {
11028
+ if (timedOut) {
11029
+ return {
11030
+ ok: false,
11031
+ kind: "timeout",
11032
+ reason: deadlineMs === void 0 ? "Upstream request exceeded its configured deadline" : `Upstream request exceeded configured deadline of ${String(deadlineMs)} ms`
11033
+ };
11034
+ }
11035
+ return Promise.reject(error);
11036
+ } finally {
11037
+ if (timeout !== void 0) clearTimeout(timeout);
11038
+ if (controller !== void 0 && linkedSignal !== void 0) {
11039
+ linkedSignal.removeEventListener("abort", forwardAbort);
9847
11040
  }
9848
- return { type: "memory", rawStream: this.chunks.join("") };
9849
11041
  }
9850
- async spillToFile() {
9851
- const path2 = createRawStreamPath();
9852
- assert(ensurePrivateDirectorySync(getRawStreamDir()), "Could not secure raw-stream directory");
9853
- const handle = await open(path2, "wx", PRIVATE_FILE_MODE);
9854
- let committed = false;
9855
- try {
9856
- const memoryText = this.chunks.join("");
9857
- if (memoryText.length > 0) await handle.write(memoryText, void 0, "utf-8");
9858
- assert(securePrivateFileSync(path2), "Could not secure raw-stream file");
9859
- this.filePath = path2;
9860
- this.fileHandle = handle;
9861
- this.chunks = [];
9862
- committed = true;
9863
- } finally {
9864
- if (!committed) {
9865
- await handle.close().catch(() => void 0);
9866
- await unlink(path2).catch(() => void 0);
9867
- }
11042
+ }
11043
+ const BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
11044
+ function isClaudeCodeBillingHeaderBlock(text) {
11045
+ const trimmed = text.trimStart();
11046
+ return trimmed.toLowerCase().startsWith(BILLING_HEADER_PREFIX);
11047
+ }
11048
+ function isObjectWithSystem(value) {
11049
+ if (!isPlainRecord(value)) return false;
11050
+ return Object.prototype.hasOwnProperty.call(value, "system");
11051
+ }
11052
+ function isBillingHeaderTextBlock(block) {
11053
+ if (!isPlainRecord(block)) return false;
11054
+ const typeVal = safeGetOwnProperty(block, "type");
11055
+ const textVal = safeGetOwnProperty(block, "text");
11056
+ if (typeof typeVal !== "string" || typeVal !== "text") return false;
11057
+ if (typeof textVal !== "string") return false;
11058
+ return isClaudeCodeBillingHeaderBlock(textVal);
11059
+ }
11060
+ function stripClaudeCodeBillingHeader(rawBody) {
11061
+ let parsed;
11062
+ try {
11063
+ parsed = JSON.parse(rawBody);
11064
+ } catch {
11065
+ return { body: rawBody, removed: 0 };
11066
+ }
11067
+ if (!isObjectWithSystem(parsed)) {
11068
+ return { body: rawBody, removed: 0 };
11069
+ }
11070
+ const system = parsed.system;
11071
+ if (!Array.isArray(system) || system.length === 0) {
11072
+ return { body: rawBody, removed: 0 };
11073
+ }
11074
+ const kept = [];
11075
+ let removed = 0;
11076
+ for (const block of system) {
11077
+ if (isBillingHeaderTextBlock(block)) {
11078
+ removed += 1;
11079
+ continue;
9868
11080
  }
11081
+ kept.push(block);
9869
11082
  }
9870
- async flushFileBuffer() {
9871
- if (this.fileHandle === null || this.pendingFileChunks.length === 0) return;
9872
- const text = this.pendingFileChunks.join("");
9873
- this.pendingFileChunks = [];
9874
- this.pendingFileBytes = 0;
9875
- await this.fileHandle.write(text, void 0, "utf-8");
11083
+ if (removed === 0) {
11084
+ return { body: rawBody, removed: 0 };
9876
11085
  }
11086
+ if (kept.length === 0) {
11087
+ delete parsed.system;
11088
+ } else {
11089
+ parsed.system = kept;
11090
+ }
11091
+ return { body: JSON.stringify(parsed), removed };
9877
11092
  }
9878
- async function flushAllRawStreamCaptures() {
9879
- await Promise.all([...activeCaptures].map((capture) => capture.finalize()));
9880
- }
9881
- let acceptingRequests = true;
9882
- let activeRequests = 0;
9883
- const drainWaiters = /* @__PURE__ */ new Set();
9884
- function notifyDrained() {
9885
- if (activeRequests !== 0) return;
9886
- for (const resolve2 of drainWaiters) resolve2();
9887
- drainWaiters.clear();
11093
+ const ROLE_ASSISTANT = "assistant";
11094
+ const ROLE_TOOL = "tool";
11095
+ function isObjectWithMessages(value) {
11096
+ if (!isPlainRecord(value)) return false;
11097
+ if (!Object.prototype.hasOwnProperty.call(value, "messages")) return false;
11098
+ const messages = safeGetOwnProperty(value, "messages");
11099
+ return Array.isArray(messages);
9888
11100
  }
9889
- function acquireRuntimeRequest() {
9890
- if (!acceptingRequests || activeRequests >= MAX_ACTIVE_PROXY_REQUESTS) return null;
9891
- activeRequests += 1;
9892
- let released = false;
9893
- return {
9894
- release() {
9895
- if (released) return;
9896
- released = true;
9897
- activeRequests = Math.max(0, activeRequests - 1);
9898
- notifyDrained();
11101
+ function collectAssistantToolCallIds(message, into) {
11102
+ if (!isPlainRecord(message)) return;
11103
+ const toolCalls = safeGetOwnProperty(message, "tool_calls");
11104
+ if (!Array.isArray(toolCalls)) return;
11105
+ for (const tc of toolCalls) {
11106
+ if (!isPlainRecord(tc)) continue;
11107
+ const id = safeGetOwnProperty(tc, "id");
11108
+ if (typeof id === "string" && id.length > 0) {
11109
+ into.add(id);
9899
11110
  }
9900
- };
9901
- }
9902
- function stopRuntimeAdmission() {
9903
- acceptingRequests = false;
9904
- notifyDrained();
11111
+ }
9905
11112
  }
9906
- async function waitForRuntimeTrafficDrain() {
9907
- if (activeRequests === 0) return;
9908
- await new Promise((resolve2) => {
9909
- drainWaiters.add(resolve2);
9910
- });
11113
+ function findOrphanToolMessageIndices(messages) {
11114
+ const knownToolCallIds = /* @__PURE__ */ new Set();
11115
+ const indices = [];
11116
+ const orphanIds = [];
11117
+ for (let i = 0; i < messages.length; i++) {
11118
+ const msg = messages[i];
11119
+ if (!isPlainRecord(msg)) continue;
11120
+ const role = safeGetOwnProperty(msg, "role");
11121
+ if (role === ROLE_ASSISTANT) {
11122
+ collectAssistantToolCallIds(msg, knownToolCallIds);
11123
+ continue;
11124
+ }
11125
+ if (role === ROLE_TOOL) {
11126
+ const id = safeGetOwnProperty(msg, "tool_call_id");
11127
+ if (typeof id !== "string" || id.length === 0) {
11128
+ indices.push(i);
11129
+ orphanIds.push(null);
11130
+ continue;
11131
+ }
11132
+ if (!knownToolCallIds.has(id)) {
11133
+ indices.push(i);
11134
+ orphanIds.push(id);
11135
+ }
11136
+ }
11137
+ }
11138
+ return { indices, orphanIds };
9911
11139
  }
9912
- function getRuntimeAdmissionState() {
9913
- return { acceptingRequests, activeRequests, capacity: MAX_ACTIVE_PROXY_REQUESTS };
11140
+ function stripOpenAIOrphanToolMessages(rawBody) {
11141
+ let parsed;
11142
+ try {
11143
+ parsed = JSON.parse(rawBody);
11144
+ } catch {
11145
+ return { body: rawBody, removed: 0, orphanIds: [] };
11146
+ }
11147
+ if (!isObjectWithMessages(parsed)) {
11148
+ return { body: rawBody, removed: 0, orphanIds: [] };
11149
+ }
11150
+ const messages = parsed.messages;
11151
+ const { indices, orphanIds } = findOrphanToolMessageIndices(messages);
11152
+ if (indices.length === 0) {
11153
+ return { body: rawBody, removed: 0, orphanIds: [] };
11154
+ }
11155
+ const dropSet = new Set(indices);
11156
+ const kept = [];
11157
+ for (let i = 0; i < messages.length; i++) {
11158
+ if (!dropSet.has(i)) kept.push(messages[i]);
11159
+ }
11160
+ parsed.messages = kept;
11161
+ return { body: JSON.stringify(parsed), removed: indices.length, orphanIds };
9914
11162
  }
9915
11163
  const PATH_V1_CHAT_COMPLETIONS = "/v1/chat/completions";
9916
11164
  function buildUpstreamUrl(upstreamBase, normalizedPath) {
@@ -10400,10 +11648,14 @@ async function handleAdmittedProxy(req) {
10400
11648
  body: bodyToForward,
10401
11649
  signal: req.signal
10402
11650
  },
10403
- { allowLoopback: ALLOW_LOOPBACK$2 }
11651
+ {
11652
+ allowLoopback: ALLOW_LOOPBACK$2,
11653
+ deadlineMs: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.fetchDeadlineMs,
11654
+ responseBytes: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.fetchResponseBytes
11655
+ }
10404
11656
  );
10405
11657
  if (!fetchResult.ok) {
10406
- const status = fetchResult.kind === "network-policy" ? STATUS_FORBIDDEN : STATUS_BAD_GATEWAY;
11658
+ const status = fetchResult.kind === "network-policy" ? STATUS_FORBIDDEN : fetchResult.kind === "resource-limit" ? STATUS_PAYLOAD_TOO_LARGE : STATUS_BAD_GATEWAY;
10407
11659
  logger.warn(`[handler] Safe fetch blocked ${upstreamUrl}: ${fetchResult.reason}`);
10408
11660
  log.elapsedMs = Date.now() - startTime;
10409
11661
  log.responseStatus = status;
@@ -10411,7 +11663,7 @@ async function handleAdmittedProxy(req) {
10411
11663
  appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: fetchResult.reason });
10412
11664
  await persistFinalizedLogUpdate(log);
10413
11665
  return new Response(
10414
- fetchResult.kind === "network-policy" ? "Forbidden: upstream URL is not permitted" : "Proxy error: upstream redirects are not permitted",
11666
+ fetchResult.kind === "network-policy" ? "Forbidden: upstream URL is not permitted" : fetchResult.kind === "resource-limit" ? "Proxy error: upstream response exceeded configured limits" : fetchResult.kind === "timeout" ? "Proxy error: upstream request timed out" : "Proxy error: upstream redirects are not permitted",
10415
11667
  { status }
10416
11668
  );
10417
11669
  }
@@ -10513,23 +11765,62 @@ async function handleProxy(req) {
10513
11765
  const Route$Q = createFileRoute("/proxy/$")({
10514
11766
  server: {
10515
11767
  handlers: {
10516
- GET: ({ request }) => handleProxy(request),
10517
- POST: ({ request }) => handleProxy(request),
10518
- PUT: ({ request }) => handleProxy(request),
10519
- DELETE: ({ request }) => handleProxy(request),
10520
- PATCH: ({ request }) => handleProxy(request),
10521
- OPTIONS: ({ request }) => handleProxy(request)
11768
+ GET: ({ request: request2 }) => handleProxy(request2),
11769
+ POST: ({ request: request2 }) => handleProxy(request2),
11770
+ PUT: ({ request: request2 }) => handleProxy(request2),
11771
+ DELETE: ({ request: request2 }) => handleProxy(request2),
11772
+ PATCH: ({ request: request2 }) => handleProxy(request2),
11773
+ OPTIONS: ({ request: request2 }) => handleProxy(request2)
11774
+ }
11775
+ }
11776
+ });
11777
+ function summarizeGcResult(result) {
11778
+ if (result === null) return null;
11779
+ return {
11780
+ checkedAt: result.checkedAt,
11781
+ filesDeleted: result.filesDeleted,
11782
+ bytesDeleted: result.bytesDeleted,
11783
+ errors: result.errors.length,
11784
+ beforeTotalBytes: result.before.totalBytes,
11785
+ afterTotalBytes: result.after.totalBytes
11786
+ };
11787
+ }
11788
+ function getStorageResponse() {
11789
+ const logStats = getLogStorageStats();
11790
+ const inventory = getStorageLifecycleInventory();
11791
+ const policy = resolveStorageLifecycleQuotaPolicy();
11792
+ const reclaimableBytes = inventory.artifacts.filter(
11793
+ (artifact) => artifact.cleanupBehavior === "retention-gc" || artifact.cleanupBehavior === "orphan-reclaim"
11794
+ ).reduce((sum, artifact) => sum + artifact.bytes, 0);
11795
+ return {
11796
+ memoryCount: logStats.memoryCount,
11797
+ inventory,
11798
+ policy,
11799
+ reclaimableBytes,
11800
+ lastGc: summarizeGcResult(getLastStorageLifecycleGcResult())
11801
+ };
11802
+ }
11803
+ const Route$P = createFileRoute("/api/storage")({
11804
+ server: {
11805
+ handlers: {
11806
+ GET: () => {
11807
+ return Response.json(getStorageResponse(), { headers: { "cache-control": "no-store" } });
11808
+ },
11809
+ POST: () => {
11810
+ runStorageLifecycleGc();
11811
+ return Response.json(getStorageResponse(), { headers: { "cache-control": "no-store" } });
11812
+ }
10522
11813
  }
10523
11814
  }
10524
11815
  });
10525
11816
  function parseBooleanFlag(value) {
10526
11817
  return value === "1" || value === "true";
10527
11818
  }
10528
- const Route$P = createFileRoute("/api/sessions")({
11819
+ const Route$O = createFileRoute("/api/sessions")({
10529
11820
  server: {
10530
11821
  handlers: {
10531
- GET: async ({ request }) => {
10532
- const url2 = new URL(request.url);
11822
+ GET: async ({ request: request2 }) => {
11823
+ const url2 = new URL(request2.url);
10533
11824
  const sessionId = url2.searchParams.get("sessionId");
10534
11825
  if (sessionId !== null) {
10535
11826
  if (sessionId.length === 0) {
@@ -10658,7 +11949,7 @@ function runtimeDiscoveryResponse(env = process.env) {
10658
11949
  headers: { "cache-control": "no-store" }
10659
11950
  });
10660
11951
  }
10661
- const Route$O = createFileRoute("/api/runtime")({
11952
+ const Route$N = createFileRoute("/api/runtime")({
10662
11953
  server: {
10663
11954
  handlers: {
10664
11955
  GET: () => runtimeDiscoveryResponse()
@@ -12067,20 +13358,20 @@ function readInspectorRunEvidence(runId) {
12067
13358
  }
12068
13359
  return serviceOk({ run, evidence: run.evidence, markdown });
12069
13360
  }
12070
- async function readJsonBody$6(request) {
13361
+ async function readJsonBody$6(request2) {
12071
13362
  try {
12072
- const raw = await request.text();
13363
+ const raw = await request2.text();
12073
13364
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
12074
13365
  return { ok: true, value: JSON.parse(raw) };
12075
13366
  } catch {
12076
13367
  return { ok: false };
12077
13368
  }
12078
13369
  }
12079
- const Route$N = createFileRoute("/api/runs")({
13370
+ const Route$M = createFileRoute("/api/runs")({
12080
13371
  server: {
12081
13372
  handlers: {
12082
- GET: ({ request }) => {
12083
- const url2 = new URL(request.url);
13373
+ GET: ({ request: request2 }) => {
13374
+ const url2 = new URL(request2.url);
12084
13375
  if (url2.searchParams.get("failures") === "1") {
12085
13376
  const limitResult = parseBoundedIntegerParameter({
12086
13377
  value: url2.searchParams.get("limit"),
@@ -12095,8 +13386,8 @@ const Route$N = createFileRoute("/api/runs")({
12095
13386
  }
12096
13387
  return Response.json(listInspectorRuns());
12097
13388
  },
12098
- POST: async ({ request }) => {
12099
- const body = await readJsonBody$6(request);
13389
+ POST: async ({ request: request2 }) => {
13390
+ const body = await readJsonBody$6(request2);
12100
13391
  if (!body.ok) {
12101
13392
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
12102
13393
  }
@@ -12128,16 +13419,16 @@ const ProviderInputSchema = object({
12128
13419
  modelMetadata: ProviderModelMetadataListSchema.optional(),
12129
13420
  source: _enum(["company", "personal"]).optional()
12130
13421
  });
12131
- const Route$M = createFileRoute("/api/providers")({
13422
+ const Route$L = createFileRoute("/api/providers")({
12132
13423
  server: {
12133
13424
  handlers: {
12134
13425
  GET: () => {
12135
13426
  return Response.json(redactProviders(getProviders()));
12136
13427
  },
12137
- POST: async ({ request }) => {
13428
+ POST: async ({ request: request2 }) => {
12138
13429
  let body;
12139
13430
  try {
12140
- body = await request.json();
13431
+ body = await request2.json();
12141
13432
  } catch {
12142
13433
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
12143
13434
  }
@@ -13756,8 +15047,8 @@ function firstModel(provider) {
13756
15047
  }
13757
15048
  return void 0;
13758
15049
  }
13759
- function providerCandidates(request) {
13760
- if (request.providerId === void 0) {
15050
+ function providerCandidates(request2) {
15051
+ if (request2.providerId === void 0) {
13761
15052
  return getProviders().toSorted((left, right) => {
13762
15053
  const leftCompanyRank = left.source === "company" ? 0 : 1;
13763
15054
  const rightCompanyRank = right.source === "company" ? 0 : 1;
@@ -13765,12 +15056,12 @@ function providerCandidates(request) {
13765
15056
  return left.name.localeCompare(right.name);
13766
15057
  });
13767
15058
  }
13768
- const provider = getProvider(request.providerId);
15059
+ const provider = getProvider(request2.providerId);
13769
15060
  return provider === void 0 ? [] : [provider];
13770
15061
  }
13771
- function selectProvider(request) {
13772
- for (const provider of providerCandidates(request)) {
13773
- const modelName = request.model ?? firstModel(provider);
15062
+ function selectProvider(request2) {
15063
+ for (const provider of providerCandidates(request2)) {
15064
+ const modelName = request2.model ?? firstModel(provider);
13774
15065
  if (modelName === void 0) continue;
13775
15066
  const responsesBaseUrl = nonEmpty$1(provider.openaiResponsesBaseUrl);
13776
15067
  if (responsesBaseUrl !== void 0) {
@@ -13813,8 +15104,8 @@ function truncateContext(value, maxLength) {
13813
15104
  return `${value.slice(0, maxLength)}
13814
15105
  ...[truncated ${String(value.length - maxLength)} chars]`;
13815
15106
  }
13816
- function lastUserQuestion(request) {
13817
- const history = request.history ?? [];
15107
+ function lastUserQuestion(request2) {
15108
+ const history = request2.history ?? [];
13818
15109
  for (let index = history.length - 1; index >= 0; index -= 1) {
13819
15110
  const message = history[index];
13820
15111
  if (message !== void 0 && message.role === "user") return message.content;
@@ -13835,13 +15126,13 @@ function emptySummary() {
13835
15126
  sourceCounts: { log: 0, knowledge: 0, alert: 0, run: 0, group: 0, provider: 0 }
13836
15127
  };
13837
15128
  }
13838
- async function retrieveInspectorContext(request) {
15129
+ async function retrieveInspectorContext(request2) {
13839
15130
  try {
13840
15131
  return await retrievePiAgentEvidence({
13841
- question: request.question,
13842
- sessionId: request.sessionId,
13843
- recentUserQuestion: lastUserQuestion(request),
13844
- limits: request.logLimit === void 0 ? void 0 : { maxEvidence: request.logLimit }
15132
+ question: request2.question,
15133
+ sessionId: request2.sessionId,
15134
+ recentUserQuestion: lastUserQuestion(request2),
15135
+ limits: request2.logLimit === void 0 ? void 0 : { maxEvidence: request2.logLimit }
13845
15136
  });
13846
15137
  } catch (err) {
13847
15138
  return {
@@ -13991,11 +15282,11 @@ function lastAssistantMessage(messages) {
13991
15282
  }
13992
15283
  return null;
13993
15284
  }
13994
- function boundedHistory(request) {
13995
- const history = (request.history ?? []).slice(-12).map((message) => `${message.role}: ${message.content.slice(0, MAX_HISTORY_CHARS)}`);
15285
+ function boundedHistory(request2) {
15286
+ const history = (request2.history ?? []).slice(-12).map((message) => `${message.role}: ${message.content.slice(0, MAX_HISTORY_CHARS)}`);
13996
15287
  return history.length > 0 ? history.join("\n\n") : "No prior conversation in this request.";
13997
15288
  }
13998
- function buildContext(request, retrieval) {
15289
+ function buildContext(request2, retrieval) {
13999
15290
  return {
14000
15291
  systemPrompt: buildSystemPrompt(),
14001
15292
  messages: [
@@ -14003,16 +15294,16 @@ function buildContext(request, retrieval) {
14003
15294
  role: "user",
14004
15295
  content: [
14005
15296
  "Bounded conversation history:",
14006
- boundedHistory(request),
15297
+ boundedHistory(request2),
14007
15298
  "",
14008
- buildUserPrompt(request.question, retrieval)
15299
+ buildUserPrompt(request2.question, retrieval)
14009
15300
  ].join("\n"),
14010
15301
  timestamp: Date.now()
14011
15302
  }
14012
15303
  ]
14013
15304
  };
14014
15305
  }
14015
- async function askPi(selected, request, retrieval) {
15306
+ async function askPi(selected, request2, retrieval) {
14016
15307
  const { Agent } = await import(
14017
15308
  /* @vite-ignore */
14018
15309
  "../_libs/mariozechner__pi-agent-core.mjs"
@@ -14028,7 +15319,7 @@ async function askPi(selected, request, retrieval) {
14028
15319
  sessionId: selected.provider.id,
14029
15320
  maxRetryDelayMs: 1e4
14030
15321
  });
14031
- const context = buildContext(request, retrieval);
15322
+ const context = buildContext(request2, retrieval);
14032
15323
  await agent.prompt(context.messages);
14033
15324
  const assistantMessage = lastAssistantMessage(agent.state.messages);
14034
15325
  if (assistantMessage === null) {
@@ -14038,9 +15329,9 @@ async function askPi(selected, request, retrieval) {
14038
15329
  if (answer !== "") return stripReasoningTags(answer);
14039
15330
  return assistantMessage.errorMessage ?? "Inspector Assistant received an empty provider response.";
14040
15331
  }
14041
- async function runPiAgent(request) {
14042
- const retrieval = await retrieveInspectorContext(request);
14043
- const selected = selectProvider(request);
15332
+ async function runPiAgent(request2) {
15333
+ const retrieval = await retrieveInspectorContext(request2);
15334
+ const selected = selectProvider(request2);
14044
15335
  if (selected === null) {
14045
15336
  return {
14046
15337
  answer: "Inspector Assistant needs at least one Provider with OpenAI Responses, OpenAI Chat, or Anthropic Messages configured.",
@@ -14056,7 +15347,7 @@ async function runPiAgent(request) {
14056
15347
  }
14057
15348
  let answer;
14058
15349
  try {
14059
- answer = await askPi(selected, request, retrieval);
15350
+ answer = await askPi(selected, request2, retrieval);
14060
15351
  } catch (err) {
14061
15352
  answer = `Inspector Assistant provider request failed: ${err instanceof Error ? err.message : String(err)}`;
14062
15353
  }
@@ -14072,13 +15363,13 @@ async function runPiAgent(request) {
14072
15363
  queryStats: retrieval.queryStats
14073
15364
  };
14074
15365
  }
14075
- const Route$L = createFileRoute("/api/pi-agent")({
15366
+ const Route$K = createFileRoute("/api/pi-agent")({
14076
15367
  server: {
14077
15368
  handlers: {
14078
- POST: async ({ request }) => {
15369
+ POST: async ({ request: request2 }) => {
14079
15370
  let body;
14080
15371
  try {
14081
- body = await request.json();
15372
+ body = await request2.json();
14082
15373
  } catch {
14083
15374
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
14084
15375
  }
@@ -14095,7 +15386,7 @@ const Route$L = createFileRoute("/api/pi-agent")({
14095
15386
  }
14096
15387
  }
14097
15388
  });
14098
- const Route$K = createFileRoute("/api/models")({
15389
+ const Route$J = createFileRoute("/api/models")({
14099
15390
  server: {
14100
15391
  handlers: {
14101
15392
  GET: () => Response.json(getModels())
@@ -14103,7 +15394,7 @@ const Route$K = createFileRoute("/api/models")({
14103
15394
  }
14104
15395
  });
14105
15396
  const name = "@tonyclaw/agent-inspector";
14106
- const version = "3.1.20";
15397
+ const version = "3.1.22";
14107
15398
  const packageJson = {
14108
15399
  name,
14109
15400
  version
@@ -14440,8 +15731,8 @@ async function getInstanceConnectionImpl(callApi2, name2) {
14440
15731
  }
14441
15732
  async function startInstanceImpl(callApi2, input) {
14442
15733
  const path2 = instancePath(input.name, "/start");
14443
- const request = lifecycleRequest(input);
14444
- const response = await callApi2(path2, jsonPost(InstanceStartRequestSchema.parse(request)));
15734
+ const request2 = lifecycleRequest(input);
15735
+ const response = await callApi2(path2, jsonPost(InstanceStartRequestSchema.parse(request2)));
14445
15736
  return validatedInstanceResponse(response, InstanceOperationOutputSchema, `POST ${path2}`);
14446
15737
  }
14447
15738
  async function stopInstanceImpl(callApi2, name2) {
@@ -14451,8 +15742,8 @@ async function stopInstanceImpl(callApi2, name2) {
14451
15742
  }
14452
15743
  async function restartInstanceImpl(callApi2, input) {
14453
15744
  const path2 = instancePath(input.name, "/restart");
14454
- const request = lifecycleRequest(input);
14455
- const response = await callApi2(path2, jsonPost(InstanceStartRequestSchema.parse(request)));
15745
+ const request2 = lifecycleRequest(input);
15746
+ const response = await callApi2(path2, jsonPost(InstanceStartRequestSchema.parse(request2)));
14456
15747
  return validatedInstanceResponse(response, InstanceOperationOutputSchema, `POST ${path2}`);
14457
15748
  }
14458
15749
  function groupEvidenceDir(groupId) {
@@ -16111,10 +17402,10 @@ async function getServer() {
16111
17402
  }
16112
17403
  return initPromise;
16113
17404
  }
16114
- async function handleMcpRequest(request) {
16115
- return runWithRequestPublicBasePath(request, async () => {
17405
+ async function handleMcpRequest(request2) {
17406
+ return runWithRequestPublicBasePath(request2, async () => {
16116
17407
  const { transport } = await getServer();
16117
- return transport.handleRequest(request);
17408
+ return transport.handleRequest(request2);
16118
17409
  });
16119
17410
  }
16120
17411
  const TOOL_LIST_LOGS_DESC = `List recent captured agent/model proxy logs in reverse-chronological order with parsed previews. Useful for "what did I just send?" discovery.
@@ -17764,15 +19055,15 @@ const PROMPT_NAMES = [
17764
19055
  "inspector_generate_group_report",
17765
19056
  "inspector_extract_repro_steps"
17766
19057
  ];
17767
- const Route$J = createFileRoute("/api/mcp")({
19058
+ const Route$I = createFileRoute("/api/mcp")({
17768
19059
  server: {
17769
19060
  handlers: {
17770
19061
  // The SDK may issue either POST, GET, or DELETE. TanStack Start only
17771
19062
  // requires us to declare the methods we accept; routing by method is
17772
19063
  // handled inside the transport.
17773
- POST: ({ request }) => handleMcpRequest(request),
17774
- GET: ({ request }) => handleMcpRequest(request),
17775
- DELETE: ({ request }) => handleMcpRequest(request)
19064
+ POST: ({ request: request2 }) => handleMcpRequest(request2),
19065
+ GET: ({ request: request2 }) => handleMcpRequest(request2),
19066
+ DELETE: ({ request: request2 }) => handleMcpRequest(request2)
17776
19067
  }
17777
19068
  }
17778
19069
  });
@@ -17882,11 +19173,11 @@ function parseOptionalLogId(value) {
17882
19173
  const parsed = parseLogId(value);
17883
19174
  return parsed === null ? { ok: false } : { ok: true, value: parsed };
17884
19175
  }
17885
- const Route$I = createFileRoute("/api/logs")({
19176
+ const Route$H = createFileRoute("/api/logs")({
17886
19177
  server: {
17887
19178
  handlers: {
17888
- GET: async ({ request }) => {
17889
- const url2 = new URL(request.url);
19179
+ GET: async ({ request: request2 }) => {
19180
+ const url2 = new URL(request2.url);
17890
19181
  if (url2.searchParams.get("stats") === "1") {
17891
19182
  return Response.json(getLogStorageStats());
17892
19183
  }
@@ -17990,10 +19281,10 @@ const Route$I = createFileRoute("/api/logs")({
17990
19281
  await listLogsPage({ sessionId, model, clientPid, offset, limit, includeBodies })
17991
19282
  );
17992
19283
  },
17993
- DELETE: async ({ request }) => {
19284
+ DELETE: async ({ request: request2 }) => {
17994
19285
  let body = void 0;
17995
19286
  try {
17996
- const raw = await request.text();
19287
+ const raw = await request2.text();
17997
19288
  if (raw !== "") {
17998
19289
  const parsed = JSON.parse(raw);
17999
19290
  const result2 = DeleteLogsBodySchema.safeParse(parsed);
@@ -19800,10 +21091,10 @@ function instanceErrorResponse(error) {
19800
21091
  function instanceResultResponse(result, successStatus = 200) {
19801
21092
  return result.kind === "error" ? instanceErrorResponse(result.error) : Response.json(result.value, { status: successStatus, headers: NO_STORE_HEADERS$1 });
19802
21093
  }
19803
- async function parseInstanceBody(request, schema) {
21094
+ async function parseInstanceBody(request2, schema) {
19804
21095
  let value;
19805
21096
  try {
19806
- const text = await request.text();
21097
+ const text = await request2.text();
19807
21098
  value = text.trim().length === 0 ? {} : JSON.parse(text);
19808
21099
  } catch {
19809
21100
  return {
@@ -19842,7 +21133,7 @@ function instanceListResponse(instances) {
19842
21133
  { headers: NO_STORE_HEADERS$1 }
19843
21134
  );
19844
21135
  }
19845
- const Route$H = createFileRoute("/api/instances")({
21136
+ const Route$G = createFileRoute("/api/instances")({
19846
21137
  server: {
19847
21138
  handlers: {
19848
21139
  GET: async () => {
@@ -20152,6 +21443,11 @@ const tasks = /* @__PURE__ */ new Map();
20152
21443
  const activeTasks = /* @__PURE__ */ new Map();
20153
21444
  let acceptingTasks = true;
20154
21445
  let shutdownForceTimer = null;
21446
+ function activeTaskLimit() {
21447
+ const configured = Number(process.env["ECOSYSTEM_TASK_ACTIVE_LIMIT"]);
21448
+ if (Number.isSafeInteger(configured) && configured > 0) return configured;
21449
+ return DEFAULT_RUNTIME_BUDGET_POLICY.queues.backgroundWorkers;
21450
+ }
20155
21451
  function nowIso() {
20156
21452
  return (/* @__PURE__ */ new Date()).toISOString();
20157
21453
  }
@@ -20346,6 +21642,7 @@ function listEcosystemTasks() {
20346
21642
  }
20347
21643
  function startEcosystemTask(packageId, action) {
20348
21644
  if (!acceptingTasks) return null;
21645
+ if (activeTasks.size >= activeTaskLimit()) return null;
20349
21646
  if (action === "recipe") return null;
20350
21647
  const definition = action === "runner-presets" ? findEcosystemPackage("eval-harness") : findEcosystemPackage(packageId);
20351
21648
  if (definition === null) return null;
@@ -20372,6 +21669,7 @@ function startEcosystemTask(packageId, action) {
20372
21669
  }
20373
21670
  function startEcosystemRecipeTask(recipeId) {
20374
21671
  if (!acceptingTasks) return null;
21672
+ if (activeTasks.size >= activeTaskLimit()) return null;
20375
21673
  const recipe = findEcosystemRecipe(recipeId);
20376
21674
  if (recipe === null) return null;
20377
21675
  if (!recipe.runnable) return null;
@@ -20534,7 +21832,7 @@ function installRuntimeShutdownSignalHandlers() {
20534
21832
  }
20535
21833
  installRuntimeShutdownSignalHandlers();
20536
21834
  logger.debug("Health endpoint loaded");
20537
- const Route$G = createFileRoute("/api/health")({
21835
+ const Route$F = createFileRoute("/api/health")({
20538
21836
  server: {
20539
21837
  handlers: {
20540
21838
  GET: async () => {
@@ -20547,18 +21845,18 @@ const Route$G = createFileRoute("/api/health")({
20547
21845
  }
20548
21846
  }
20549
21847
  });
20550
- async function readJsonBody$5(request) {
21848
+ async function readJsonBody$5(request2) {
20551
21849
  try {
20552
- const raw = await request.text();
21850
+ const raw = await request2.text();
20553
21851
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
20554
21852
  return { ok: true, value: JSON.parse(raw) };
20555
21853
  } catch {
20556
21854
  return { ok: false };
20557
21855
  }
20558
21856
  }
20559
- function readDeleteEvidenceFlag$1(request) {
21857
+ function readDeleteEvidenceFlag$1(request2) {
20560
21858
  try {
20561
- const value = new URL(request.url).searchParams.get("deleteEvidence");
21859
+ const value = new URL(request2.url).searchParams.get("deleteEvidence");
20562
21860
  switch (value) {
20563
21861
  case null:
20564
21862
  return { ok: true, value: void 0 };
@@ -20575,12 +21873,12 @@ function readDeleteEvidenceFlag$1(request) {
20575
21873
  return { ok: false };
20576
21874
  }
20577
21875
  }
20578
- const Route$F = createFileRoute("/api/groups")({
21876
+ const Route$E = createFileRoute("/api/groups")({
20579
21877
  server: {
20580
21878
  handlers: {
20581
21879
  GET: () => Response.json(listInspectorGroups()),
20582
- POST: async ({ request }) => {
20583
- const body = await readJsonBody$5(request);
21880
+ POST: async ({ request: request2 }) => {
21881
+ const body = await readJsonBody$5(request2);
20584
21882
  if (!body.ok) {
20585
21883
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
20586
21884
  }
@@ -20593,8 +21891,8 @@ const Route$F = createFileRoute("/api/groups")({
20593
21891
  }
20594
21892
  return Response.json(createInspectorGroup(parsed.data), { status: 201 });
20595
21893
  },
20596
- DELETE: ({ request }) => {
20597
- const deleteEvidence = readDeleteEvidenceFlag$1(request);
21894
+ DELETE: ({ request: request2 }) => {
21895
+ const deleteEvidence = readDeleteEvidenceFlag$1(request2);
20598
21896
  if (!deleteEvidence.ok) {
20599
21897
  return Response.json(
20600
21898
  { error: "deleteEvidence must be one of true, false, 1, or 0" },
@@ -20619,16 +21917,16 @@ const RuntimeConfigPatchSchema = object({
20619
21917
  }).strict().refine((v) => Object.keys(v).length > 0, {
20620
21918
  message: "At least one field must be provided"
20621
21919
  });
20622
- const Route$E = createFileRoute("/api/config")({
21920
+ const Route$D = createFileRoute("/api/config")({
20623
21921
  server: {
20624
21922
  handlers: {
20625
21923
  GET: () => {
20626
21924
  return Response.json(getConfig());
20627
21925
  },
20628
- PATCH: async ({ request }) => {
21926
+ PATCH: async ({ request: request2 }) => {
20629
21927
  let body;
20630
21928
  try {
20631
- body = await request.json();
21929
+ body = await request2.json();
20632
21930
  } catch {
20633
21931
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
20634
21932
  }
@@ -20733,11 +22031,11 @@ function parseFilters(url2) {
20733
22031
  source: sourceResult.success ? sourceResult.data : void 0
20734
22032
  };
20735
22033
  }
20736
- const Route$D = createFileRoute("/api/alerts")({
22034
+ const Route$C = createFileRoute("/api/alerts")({
20737
22035
  server: {
20738
22036
  handlers: {
20739
- GET: async ({ request }) => {
20740
- const url2 = new URL(request.url);
22037
+ GET: async ({ request: request2 }) => {
22038
+ const url2 = new URL(request2.url);
20741
22039
  const limitResult = parseBoundedIntegerParameter({
20742
22040
  value: url2.searchParams.get("limit"),
20743
22041
  parameter: "limit",
@@ -20767,16 +22065,16 @@ const Route$D = createFileRoute("/api/alerts")({
20767
22065
  }
20768
22066
  }
20769
22067
  });
20770
- async function readJsonBody$4(request) {
22068
+ async function readJsonBody$4(request2) {
20771
22069
  try {
20772
- const raw = await request.text();
22070
+ const raw = await request2.text();
20773
22071
  if (raw.trim().length === 0) return { ok: true, value: {} };
20774
22072
  return { ok: true, value: JSON.parse(raw) };
20775
22073
  } catch {
20776
22074
  return { ok: false };
20777
22075
  }
20778
22076
  }
20779
- const Route$C = createFileRoute("/api/runs/$runId")({
22077
+ const Route$B = createFileRoute("/api/runs/$runId")({
20780
22078
  server: {
20781
22079
  handlers: {
20782
22080
  GET: ({ params }) => {
@@ -20786,8 +22084,8 @@ const Route$C = createFileRoute("/api/runs/$runId")({
20786
22084
  }
20787
22085
  return Response.json(run.value);
20788
22086
  },
20789
- PATCH: async ({ params, request }) => {
20790
- const body = await readJsonBody$4(request);
22087
+ PATCH: async ({ params, request: request2 }) => {
22088
+ const body = await readJsonBody$4(request2);
20791
22089
  if (!body.ok) {
20792
22090
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
20793
22091
  }
@@ -21309,7 +22607,7 @@ function consumeProviderScan(scanId, selectionIds, now = Date.now()) {
21309
22607
  }
21310
22608
  return selected;
21311
22609
  }
21312
- const Route$B = createFileRoute("/api/providers/scan")({
22610
+ const Route$A = createFileRoute("/api/providers/scan")({
21313
22611
  server: {
21314
22612
  handlers: {
21315
22613
  POST: () => {
@@ -21355,12 +22653,12 @@ const ProviderScanImportRequestSchema = object({
21355
22653
  selectionIds: array(string().uuid()).min(1).max(100)
21356
22654
  });
21357
22655
  const ProviderFileImportSchema = union([string(), object({ providers: unknown() })]);
21358
- const Route$A = createFileRoute("/api/providers/import")({
22656
+ const Route$z = createFileRoute("/api/providers/import")({
21359
22657
  server: {
21360
22658
  handlers: {
21361
- POST: async ({ request }) => {
22659
+ POST: async ({ request: request2 }) => {
21362
22660
  try {
21363
- const rawBody = await request.text();
22661
+ const rawBody = await request2.text();
21364
22662
  let jsonContent;
21365
22663
  try {
21366
22664
  const parsedBody = JSON.parse(rawBody);
@@ -21427,7 +22725,7 @@ function exportResponse(json, filename) {
21427
22725
  }
21428
22726
  });
21429
22727
  }
21430
- const Route$z = createFileRoute("/api/providers/export")({
22728
+ const Route$y = createFileRoute("/api/providers/export")({
21431
22729
  server: {
21432
22730
  handlers: {
21433
22731
  GET: () => {
@@ -21436,8 +22734,8 @@ const Route$z = createFileRoute("/api/providers/export")({
21436
22734
  `agent-inspector-providers-safe-${Date.now()}.json`
21437
22735
  );
21438
22736
  },
21439
- POST: async ({ request }) => {
21440
- const body = await request.json().catch(() => null);
22737
+ POST: async ({ request: request2 }) => {
22738
+ const body = await request2.json().catch(() => null);
21441
22739
  const parsed = SecretExportRequestSchema.safeParse(body);
21442
22740
  if (!parsed.success) {
21443
22741
  return Response.json(
@@ -21470,7 +22768,7 @@ const ProviderUpdateSchema = object({
21470
22768
  modelMetadata: ProviderModelMetadataListSchema.optional(),
21471
22769
  source: _enum(["company", "personal"]).optional()
21472
22770
  });
21473
- const Route$y = createFileRoute("/api/providers/$providerId")({
22771
+ const Route$x = createFileRoute("/api/providers/$providerId")({
21474
22772
  server: {
21475
22773
  handlers: {
21476
22774
  GET: ({ params }) => {
@@ -21481,10 +22779,10 @@ const Route$y = createFileRoute("/api/providers/$providerId")({
21481
22779
  }
21482
22780
  return Response.json(redactProvider(provider));
21483
22781
  },
21484
- PUT: async ({ params, request }) => {
22782
+ PUT: async ({ params, request: request2 }) => {
21485
22783
  let body;
21486
22784
  try {
21487
- body = await request.json();
22785
+ body = await request2.json();
21488
22786
  } catch {
21489
22787
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
21490
22788
  }
@@ -21535,139 +22833,6 @@ const Route$y = createFileRoute("/api/providers/$providerId")({
21535
22833
  }
21536
22834
  }
21537
22835
  });
21538
- const INITIAL_STREAM_LOG_LIMIT = 100;
21539
- const UPDATE_COALESCE_MS = 50;
21540
- const sseClientLimiter = createCapacityLimiter(MAX_SSE_CLIENTS);
21541
- function streamIncludesBodies(url2) {
21542
- return url2.searchParams.get("compact") === "0";
21543
- }
21544
- function parseLastEventLogId(value) {
21545
- if (value === null) return void 0;
21546
- return parseLogId(value) ?? void 0;
21547
- }
21548
- function encodeSse(value, id) {
21549
- const idLine = id === void 0 ? "" : `id: ${String(id)}
21550
- `;
21551
- return new TextEncoder().encode(`${idLine}data: ${JSON.stringify(value)}
21552
-
21553
- `);
21554
- }
21555
- function createLogsStreamResponse(request, clientLimiter = sseClientLimiter) {
21556
- const lastEventIdHeader = request.headers.get("last-event-id");
21557
- const lastEventLogId = parseLastEventLogId(lastEventIdHeader);
21558
- if (lastEventIdHeader !== null && lastEventLogId === void 0) {
21559
- return Response.json({ error: "Invalid Last-Event-ID" }, { status: 400 });
21560
- }
21561
- const lease = clientLimiter.tryAcquire();
21562
- if (lease === null) {
21563
- return resourceLimitResponse({
21564
- error: `SSE client capacity reached. At most ${String(clientLimiter.capacity)} live clients are allowed; retry after a connection closes.`,
21565
- code: "sse-capacity-exceeded",
21566
- status: 503,
21567
- resource: "sse-clients",
21568
- limit: clientLimiter.capacity,
21569
- retryable: true,
21570
- retryAfterSeconds: 1
21571
- });
21572
- }
21573
- const url2 = new URL(request.url);
21574
- const sessionId = url2.searchParams.get("sessionId") ?? void 0;
21575
- const model = url2.searchParams.get("model") ?? void 0;
21576
- const includeBodies = streamIncludesBodies(url2);
21577
- let controllerRef = null;
21578
- let cleanedUp = false;
21579
- let updateTimer = null;
21580
- const pendingUpdates = /* @__PURE__ */ new Map();
21581
- const flushPendingUpdates = () => {
21582
- updateTimer = null;
21583
- const controller = controllerRef;
21584
- if (controller === null) return;
21585
- if (controller.desiredSize !== null && controller.desiredSize <= -16) {
21586
- controller.close();
21587
- cleanup();
21588
- return;
21589
- }
21590
- for (const log of pendingUpdates.values()) {
21591
- if (controller.desiredSize !== null && controller.desiredSize <= -16) {
21592
- controller.close();
21593
- cleanup();
21594
- return;
21595
- }
21596
- const payloadLog = includeBodies ? log : compactLogForList(log);
21597
- controller.enqueue(encodeSse({ type: "update", log: payloadLog }, log.id));
21598
- }
21599
- pendingUpdates.clear();
21600
- };
21601
- const unsubscribe = onLogUpdate((log) => {
21602
- if (controllerRef === null) return;
21603
- if (sessionId !== void 0 && getLogSessionId(log) !== sessionId) return;
21604
- if (model !== void 0 && log.model !== model) return;
21605
- pendingUpdates.set(log.id, log);
21606
- if (updateTimer === null) updateTimer = setTimeout(flushPendingUpdates, UPDATE_COALESCE_MS);
21607
- });
21608
- const cleanup = () => {
21609
- if (cleanedUp) return;
21610
- cleanedUp = true;
21611
- clearInterval(heartbeat);
21612
- if (updateTimer !== null) clearTimeout(updateTimer);
21613
- updateTimer = null;
21614
- pendingUpdates.clear();
21615
- unsubscribe();
21616
- lease.release();
21617
- controllerRef = null;
21618
- request.signal.removeEventListener("abort", cleanup);
21619
- };
21620
- const heartbeat = setInterval(() => {
21621
- if (controllerRef !== null) {
21622
- try {
21623
- controllerRef.enqueue(new TextEncoder().encode(": heartbeat\n\n"));
21624
- } catch {
21625
- cleanup();
21626
- }
21627
- }
21628
- }, 3e4);
21629
- request.signal.addEventListener("abort", cleanup, { once: true });
21630
- const stream = new ReadableStream({
21631
- async start(controller) {
21632
- controllerRef = controller;
21633
- try {
21634
- const result = await listLogsCursorPage({
21635
- sessionId,
21636
- model,
21637
- limit: INITIAL_STREAM_LOG_LIMIT,
21638
- includeBodies,
21639
- afterLogId: lastEventLogId,
21640
- anchor: lastEventLogId === void 0 ? "newest" : void 0
21641
- });
21642
- if (cleanedUp) return;
21643
- const logs = result.logs;
21644
- const newestLogId = logs.at(-1)?.id;
21645
- controller.enqueue(encodeSse({ type: "init", logs }, newestLogId));
21646
- } catch (error) {
21647
- cleanup();
21648
- controller.error(error);
21649
- }
21650
- },
21651
- cancel() {
21652
- cleanup();
21653
- }
21654
- });
21655
- return new Response(stream, {
21656
- headers: {
21657
- "Content-Type": "text/event-stream",
21658
- "Cache-Control": "no-cache",
21659
- Connection: "keep-alive",
21660
- "X-Accel-Buffering": "no"
21661
- }
21662
- });
21663
- }
21664
- const Route$x = createFileRoute("/api/logs/stream")({
21665
- server: {
21666
- handlers: {
21667
- GET: ({ request }) => createLogsStreamResponse(request)
21668
- }
21669
- }
21670
- });
21671
22836
  const DEFAULT_LOG_IMPORT_LIMITS = {
21672
22837
  maxCompressedBytes: 200 * 1024 * 1024,
21673
22838
  maxEntries: 1e4,
@@ -22269,8 +23434,8 @@ function declaredContentLength(value) {
22269
23434
  const parsed = Number.parseInt(value, 10);
22270
23435
  return Number.isSafeInteger(parsed) ? parsed : "invalid";
22271
23436
  }
22272
- async function readBoundedBody(request, maxBytes) {
22273
- const declaredBytes = declaredContentLength(request.headers.get("content-length"));
23437
+ async function readBoundedBody(request2, maxBytes) {
23438
+ const declaredBytes = declaredContentLength(request2.headers.get("content-length"));
22274
23439
  if (declaredBytes === "invalid") {
22275
23440
  return { ok: false, status: 400, message: "Invalid Content-Length header" };
22276
23441
  }
@@ -22281,7 +23446,7 @@ async function readBoundedBody(request, maxBytes) {
22281
23446
  message: `Import request exceeds maximum size of ${String(maxBytes)} bytes.`
22282
23447
  };
22283
23448
  }
22284
- const body = request.body;
23449
+ const body = request2.body;
22285
23450
  if (body === null) {
22286
23451
  return { ok: false, status: 400, message: "Expected multipart form data" };
22287
23452
  }
@@ -22317,14 +23482,14 @@ async function readBoundedBody(request, maxBytes) {
22317
23482
  function isUploadedFile(value) {
22318
23483
  return value instanceof File;
22319
23484
  }
22320
- async function readBoundedLogImportUpload(request, options = {}) {
22321
- const contentType = request.headers.get("content-type");
23485
+ async function readBoundedLogImportUpload(request2, options = {}) {
23486
+ const contentType = request2.headers.get("content-type");
22322
23487
  if (contentType === null || !contentType.toLowerCase().startsWith("multipart/form-data")) {
22323
23488
  return { ok: false, status: 400, message: "Expected multipart form data" };
22324
23489
  }
22325
23490
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_LOG_IMPORT_LIMITS.maxCompressedBytes;
22326
23491
  const maxRequestBytes = options.maxRequestBytes ?? MAX_LOG_IMPORT_REQUEST_BYTES;
22327
- const body = await readBoundedBody(request, maxRequestBytes);
23492
+ const body = await readBoundedBody(request2, maxRequestBytes);
22328
23493
  if (!body.ok) return body;
22329
23494
  let formData;
22330
23495
  try {
@@ -22354,8 +23519,8 @@ async function readBoundedLogImportUpload(request, options = {}) {
22354
23519
  const Route$w = createFileRoute("/api/logs/import")({
22355
23520
  server: {
22356
23521
  handlers: {
22357
- POST: async ({ request }) => {
22358
- const upload = await readBoundedLogImportUpload(request);
23522
+ POST: async ({ request: request2 }) => {
23523
+ const upload = await readBoundedLogImportUpload(request2);
22359
23524
  if (!upload.ok) {
22360
23525
  return Response.json({ error: upload.message }, { status: upload.status });
22361
23526
  }
@@ -22620,7 +23785,11 @@ async function searchOpenClawGateway(query, project) {
22620
23785
  sessionKey: getOpenClawSessionKey()
22621
23786
  })
22622
23787
  },
22623
- { bypass: true }
23788
+ {
23789
+ bypass: true,
23790
+ deadlineMs: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataDeadlineMs,
23791
+ responseBytes: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataResponseBytes
23792
+ }
22624
23793
  );
22625
23794
  if (!fetchResult.ok) return { results: [], warning: fetchResult.reason };
22626
23795
  const response = fetchResult.response;
@@ -22823,7 +23992,11 @@ async function promoteToOpenClaw(candidate) {
22823
23992
  headers: authHeaders(),
22824
23993
  body: JSON.stringify(buildOpenClawPayload(candidate))
22825
23994
  },
22826
- { bypass: true }
23995
+ {
23996
+ bypass: true,
23997
+ deadlineMs: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataDeadlineMs,
23998
+ responseBytes: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataResponseBytes
23999
+ }
22827
24000
  );
22828
24001
  if (!fetchResult.ok) return { success: false, error: fetchResult.reason };
22829
24002
  const response = fetchResult.response;
@@ -22855,7 +24028,11 @@ async function searchOpenClaw(query, project) {
22855
24028
  method: "GET",
22856
24029
  headers: authHeaders()
22857
24030
  },
22858
- { bypass: true }
24031
+ {
24032
+ bypass: true,
24033
+ deadlineMs: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataDeadlineMs,
24034
+ responseBytes: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataResponseBytes
24035
+ }
22859
24036
  );
22860
24037
  if (!fetchResult.ok) return { results: [], warning: fetchResult.reason };
22861
24038
  const response = fetchResult.response;
@@ -22895,8 +24072,8 @@ function bodySearchQuery(value) {
22895
24072
  const Route$u = createFileRoute("/api/knowledge/search")({
22896
24073
  server: {
22897
24074
  handlers: {
22898
- GET: async ({ request }) => {
22899
- const url2 = new URL(request.url);
24075
+ GET: async ({ request: request2 }) => {
24076
+ const url2 = new URL(request2.url);
22900
24077
  const query = url2.searchParams.get("q") ?? "";
22901
24078
  const project = url2.searchParams.get("project") ?? void 0;
22902
24079
  if (query === "") {
@@ -22905,10 +24082,10 @@ const Route$u = createFileRoute("/api/knowledge/search")({
22905
24082
  if (query.length > MAX_SEARCH_QUERY_CHARS) return searchLimitResponse();
22906
24083
  return Response.json(await searchOpenClaw(query, project));
22907
24084
  },
22908
- POST: async ({ request }) => {
24085
+ POST: async ({ request: request2 }) => {
22909
24086
  let body;
22910
24087
  try {
22911
- body = await request.json();
24088
+ body = await request2.json();
22912
24089
  } catch {
22913
24090
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
22914
24091
  }
@@ -22928,8 +24105,8 @@ const Route$u = createFileRoute("/api/knowledge/search")({
22928
24105
  const Route$t = createFileRoute("/api/knowledge/project-context")({
22929
24106
  server: {
22930
24107
  handlers: {
22931
- GET: async ({ request }) => {
22932
- const url2 = new URL(request.url);
24108
+ GET: async ({ request: request2 }) => {
24109
+ const url2 = new URL(request2.url);
22933
24110
  const project = url2.searchParams.get("project") ?? "";
22934
24111
  if (project === "") {
22935
24112
  return Response.json({ error: "Missing project search parameter" }, { status: 400 });
@@ -22964,18 +24141,18 @@ const Route$r = createFileRoute("/api/instances/$name")({
22964
24141
  }
22965
24142
  }
22966
24143
  });
22967
- async function readJsonBody$3(request) {
24144
+ async function readJsonBody$3(request2) {
22968
24145
  try {
22969
- const raw = await request.text();
24146
+ const raw = await request2.text();
22970
24147
  if (raw.trim().length === 0) return { ok: true, value: {} };
22971
24148
  return { ok: true, value: JSON.parse(raw) };
22972
24149
  } catch {
22973
24150
  return { ok: false };
22974
24151
  }
22975
24152
  }
22976
- function readDeleteEvidenceFlag(request) {
24153
+ function readDeleteEvidenceFlag(request2) {
22977
24154
  try {
22978
- const value = new URL(request.url).searchParams.get("deleteEvidence");
24155
+ const value = new URL(request2.url).searchParams.get("deleteEvidence");
22979
24156
  switch (value) {
22980
24157
  case null:
22981
24158
  return { ok: true, value: void 0 };
@@ -23002,8 +24179,8 @@ const Route$q = createFileRoute("/api/groups/$groupId")({
23002
24179
  }
23003
24180
  return Response.json(group.value);
23004
24181
  },
23005
- PATCH: async ({ params, request }) => {
23006
- const body = await readJsonBody$3(request);
24182
+ PATCH: async ({ params, request: request2 }) => {
24183
+ const body = await readJsonBody$3(request2);
23007
24184
  if (!body.ok) {
23008
24185
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
23009
24186
  }
@@ -23020,8 +24197,8 @@ const Route$q = createFileRoute("/api/groups/$groupId")({
23020
24197
  }
23021
24198
  return Response.json(updated.value);
23022
24199
  },
23023
- DELETE: ({ params, request }) => {
23024
- const deleteEvidence = readDeleteEvidenceFlag(request);
24200
+ DELETE: ({ params, request: request2 }) => {
24201
+ const deleteEvidence = readDeleteEvidenceFlag(request2);
23025
24202
  if (!deleteEvidence.ok) {
23026
24203
  return Response.json(
23027
24204
  { error: "deleteEvidence must be one of true, false, 1, or 0" },
@@ -23076,8 +24253,8 @@ const Route$n = createFileRoute("/api/config/paths")({
23076
24253
  const Route$m = createFileRoute("/api/alerts/summary")({
23077
24254
  server: {
23078
24255
  handlers: {
23079
- GET: async ({ request }) => {
23080
- const url2 = new URL(request.url);
24256
+ GET: async ({ request: request2 }) => {
24257
+ const url2 = new URL(request2.url);
23081
24258
  const scanLimitResult = parseBoundedIntegerParameter({
23082
24259
  value: url2.searchParams.get("scanLimit"),
23083
24260
  parameter: "scanLimit",
@@ -23096,9 +24273,9 @@ const Route$m = createFileRoute("/api/alerts/summary")({
23096
24273
  }
23097
24274
  }
23098
24275
  });
23099
- async function readJsonBody$2(request) {
24276
+ async function readJsonBody$2(request2) {
23100
24277
  try {
23101
- const raw = await request.text();
24278
+ const raw = await request2.text();
23102
24279
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
23103
24280
  return { ok: true, value: JSON.parse(raw) };
23104
24281
  } catch {
@@ -23115,8 +24292,8 @@ const Route$l = createFileRoute("/api/runs/$runId/evidence")({
23115
24292
  }
23116
24293
  return Response.json(evidence.value);
23117
24294
  },
23118
- POST: async ({ params, request }) => {
23119
- const body = await readJsonBody$2(request);
24295
+ POST: async ({ params, request: request2 }) => {
24296
+ const body = await readJsonBody$2(request2);
23120
24297
  if (!body.ok) {
23121
24298
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
23122
24299
  }
@@ -23130,7 +24307,7 @@ const Route$l = createFileRoute("/api/runs/$runId/evidence")({
23130
24307
  const exported = await exportInspectorRunEvidence(
23131
24308
  params.runId,
23132
24309
  parsed.data,
23133
- new URL(request.url).origin
24310
+ new URL(request2.url).origin
23134
24311
  );
23135
24312
  if (!exported.ok) {
23136
24313
  return Response.json({ error: exported.message }, { status: exported.status });
@@ -24038,12 +25215,12 @@ async function readRegistryJson(response) {
24038
25215
  const Route$j = createFileRoute("/api/providers/$providerId/model-metadata")({
24039
25216
  server: {
24040
25217
  handlers: {
24041
- POST: async ({ params, request }) => {
25218
+ POST: async ({ params, request: request2 }) => {
24042
25219
  const provider = getProvider(params.providerId);
24043
25220
  if (provider === void 0) {
24044
25221
  return Response.json({ error: "Provider not found" }, { status: 404 });
24045
25222
  }
24046
- const parsed = RefreshModelMetadataInputSchema.safeParse(await request.json());
25223
+ const parsed = RefreshModelMetadataInputSchema.safeParse(await request2.json());
24047
25224
  if (!parsed.success) {
24048
25225
  return Response.json({ error: parsed.error.message }, { status: 400 });
24049
25226
  }
@@ -24077,9 +25254,16 @@ const Route$j = createFileRoute("/api/providers/$providerId/model-metadata")({
24077
25254
  }
24078
25255
  let response;
24079
25256
  try {
24080
- const fetchResult = await safeFetch(sourceUrl, {
24081
- headers: { Accept: "application/json" }
24082
- });
25257
+ const fetchResult = await safeFetch(
25258
+ sourceUrl,
25259
+ {
25260
+ headers: { Accept: "application/json" }
25261
+ },
25262
+ {
25263
+ deadlineMs: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataDeadlineMs,
25264
+ responseBytes: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataResponseBytes
25265
+ }
25266
+ );
24083
25267
  if (!fetchResult.ok) {
24084
25268
  return Response.json(
24085
25269
  { error: `Model metadata URL blocked: ${fetchResult.reason}` },
@@ -24153,14 +25337,14 @@ const ReplayRequestSchema = object({
24153
25337
  const Route$i = createFileRoute("/api/logs/$id/replay")({
24154
25338
  server: {
24155
25339
  handlers: {
24156
- POST: async ({ params, request }) => {
25340
+ POST: async ({ params, request: request2 }) => {
24157
25341
  const id = parseLogId(params.id);
24158
25342
  if (id === null) {
24159
25343
  return Response.json({ error: "Invalid log ID" }, { status: 400 });
24160
25344
  }
24161
25345
  let replayRequest;
24162
25346
  try {
24163
- const raw = await request.json();
25347
+ const raw = await request2.json();
24164
25348
  const parsed = ReplayRequestSchema.safeParse(raw);
24165
25349
  if (!parsed.success) {
24166
25350
  return Response.json({ error: "Invalid request body" }, { status: 400 });
@@ -24218,7 +25402,11 @@ const Route$i = createFileRoute("/api/logs/$id/replay")({
24218
25402
  headers,
24219
25403
  body: modifiedBody
24220
25404
  },
24221
- { allowLoopback: process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1" }
25405
+ {
25406
+ allowLoopback: process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1",
25407
+ deadlineMs: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.replayDeadlineMs,
25408
+ responseBytes: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.replayResponseBytes
25409
+ }
24222
25410
  );
24223
25411
  if (!fetchResult.ok) {
24224
25412
  return Response.json({
@@ -24472,12 +25660,12 @@ function parseIntegerParam(value, fallback) {
24472
25660
  const Route$g = createFileRoute("/api/logs/$id/body")({
24473
25661
  server: {
24474
25662
  handlers: {
24475
- GET: async ({ params, request }) => {
25663
+ GET: async ({ params, request: request2 }) => {
24476
25664
  const id = parseLogId(params.id);
24477
25665
  if (id === null) {
24478
25666
  return Response.json({ error: "Invalid log ID" }, { status: 400 });
24479
25667
  }
24480
- const url2 = new URL(request.url);
25668
+ const url2 = new URL(request2.url);
24481
25669
  const partResult = LogBodyPartSchema.safeParse(url2.searchParams.get("part"));
24482
25670
  if (!partResult.success) {
24483
25671
  return Response.json({ error: "Invalid body part" }, { status: 400 });
@@ -24519,7 +25707,7 @@ object({
24519
25707
  const Route$f = createFileRoute("/api/knowledge/candidates/$candidateId")({
24520
25708
  server: {
24521
25709
  handlers: {
24522
- PATCH: async ({ params, request }) => {
25710
+ PATCH: async ({ params, request: request2 }) => {
24523
25711
  const existing = getCandidate(params.candidateId);
24524
25712
  if (existing === null) {
24525
25713
  return Response.json({ error: "Knowledge candidate not found" }, { status: 404 });
@@ -24530,7 +25718,7 @@ const Route$f = createFileRoute("/api/knowledge/candidates/$candidateId")({
24530
25718
  { status: 409 }
24531
25719
  );
24532
25720
  }
24533
- const raw = await request.json().catch(() => null);
25721
+ const raw = await request2.json().catch(() => null);
24534
25722
  const parsed = CandidateUpdateSchema.safeParse(raw);
24535
25723
  if (!parsed.success) {
24536
25724
  return Response.json({ error: parsed.error.message }, { status: 400 });
@@ -24547,8 +25735,8 @@ const Route$f = createFileRoute("/api/knowledge/candidates/$candidateId")({
24547
25735
  const Route$e = createFileRoute("/api/instances/$name/stop")({
24548
25736
  server: {
24549
25737
  handlers: {
24550
- POST: async ({ params, request }) => {
24551
- const body = await parseInstanceBody(request, InstanceEmptyRequestSchema);
25738
+ POST: async ({ params, request: request2 }) => {
25739
+ const body = await parseInstanceBody(request2, InstanceEmptyRequestSchema);
24552
25740
  if (!body.ok) return body.response;
24553
25741
  const selfError = selfLifecycleError(params.name);
24554
25742
  if (selfError !== null) return instanceErrorResponse(selfError);
@@ -24560,8 +25748,8 @@ const Route$e = createFileRoute("/api/instances/$name/stop")({
24560
25748
  const Route$d = createFileRoute("/api/instances/$name/start")({
24561
25749
  server: {
24562
25750
  handlers: {
24563
- POST: async ({ params, request }) => {
24564
- const body = await parseInstanceBody(request, InstanceStartRequestSchema);
25751
+ POST: async ({ params, request: request2 }) => {
25752
+ const body = await parseInstanceBody(request2, InstanceStartRequestSchema);
24565
25753
  if (!body.ok) return body.response;
24566
25754
  const result = await startNamedInstance(params.name, body.value);
24567
25755
  const status = result.kind === "success" && result.value.outcome === "created" ? 201 : 200;
@@ -24573,8 +25761,8 @@ const Route$d = createFileRoute("/api/instances/$name/start")({
24573
25761
  const Route$c = createFileRoute("/api/instances/$name/restart")({
24574
25762
  server: {
24575
25763
  handlers: {
24576
- POST: async ({ params, request }) => {
24577
- const body = await parseInstanceBody(request, InstanceStartRequestSchema);
25764
+ POST: async ({ params, request: request2 }) => {
25765
+ const body = await parseInstanceBody(request2, InstanceStartRequestSchema);
24578
25766
  if (!body.ok) return body.response;
24579
25767
  const selfError = selfLifecycleError(params.name);
24580
25768
  if (selfError !== null) return instanceErrorResponse(selfError);
@@ -24590,9 +25778,9 @@ const Route$b = createFileRoute("/api/instances/$name/connection")({
24590
25778
  }
24591
25779
  }
24592
25780
  });
24593
- async function readJsonBody$1(request) {
25781
+ async function readJsonBody$1(request2) {
24594
25782
  try {
24595
- const raw = await request.text();
25783
+ const raw = await request2.text();
24596
25784
  if (raw.trim().length === 0) return { ok: true, value: {} };
24597
25785
  return { ok: true, value: JSON.parse(raw) };
24598
25786
  } catch {
@@ -24609,8 +25797,8 @@ const Route$a = createFileRoute("/api/groups/$groupId/sessions")({
24609
25797
  }
24610
25798
  return Response.json(sessions2.value);
24611
25799
  },
24612
- POST: async ({ params, request }) => {
24613
- const body = await readJsonBody$1(request);
25800
+ POST: async ({ params, request: request2 }) => {
25801
+ const body = await readJsonBody$1(request2);
24614
25802
  if (!body.ok) {
24615
25803
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
24616
25804
  }
@@ -24630,9 +25818,9 @@ const Route$a = createFileRoute("/api/groups/$groupId/sessions")({
24630
25818
  }
24631
25819
  }
24632
25820
  });
24633
- async function readJsonBody(request) {
25821
+ async function readJsonBody(request2) {
24634
25822
  try {
24635
- const raw = await request.text();
25823
+ const raw = await request2.text();
24636
25824
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
24637
25825
  return { ok: true, value: JSON.parse(raw) };
24638
25826
  } catch {
@@ -24649,8 +25837,8 @@ const Route$9 = createFileRoute("/api/groups/$groupId/evidence")({
24649
25837
  }
24650
25838
  return Response.json(evidence.value);
24651
25839
  },
24652
- POST: async ({ params, request }) => {
24653
- const body = await readJsonBody(request);
25840
+ POST: async ({ params, request: request2 }) => {
25841
+ const body = await readJsonBody(request2);
24654
25842
  if (!body.ok) {
24655
25843
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
24656
25844
  }
@@ -24664,7 +25852,7 @@ const Route$9 = createFileRoute("/api/groups/$groupId/evidence")({
24664
25852
  const exported = await exportInspectorGroupEvidence(
24665
25853
  params.groupId,
24666
25854
  parsed.data,
24667
- new URL(request.url).origin
25855
+ new URL(request2.url).origin
24668
25856
  );
24669
25857
  if (!exported.ok) {
24670
25858
  return Response.json({ error: exported.message }, { status: exported.status });
@@ -25004,14 +26192,14 @@ async function logModelResults(displayName, providerName, providerSessionId, ant
25004
26192
  const Route$7 = createFileRoute("/api/providers/$providerId/test/log")({
25005
26193
  server: {
25006
26194
  handlers: {
25007
- POST: async ({ params, request }) => {
26195
+ POST: async ({ params, request: request2 }) => {
25008
26196
  const provider = getProvider(params.providerId);
25009
26197
  if (!provider) {
25010
26198
  return Response.json({ error: "Provider not found" }, { status: 404 });
25011
26199
  }
25012
26200
  let body;
25013
26201
  try {
25014
- body = await request.json();
26202
+ body = await request2.json();
25015
26203
  } catch {
25016
26204
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
25017
26205
  }
@@ -25329,12 +26517,12 @@ const NO_STORE_HEADERS = { "Cache-Control": "no-store" };
25329
26517
  function noStoreJson(body, status = 200) {
25330
26518
  return Response.json(body, { status, headers: NO_STORE_HEADERS });
25331
26519
  }
25332
- async function readExecutionRequest(request) {
25333
- const body = await request.json().catch(() => null);
26520
+ async function readExecutionRequest(request2) {
26521
+ const body = await request2.json().catch(() => null);
25334
26522
  return EcosystemExecutionRequestSchema.safeParse(body);
25335
26523
  }
25336
- async function handleEcosystemPackageExecution(request, packageId, action, startTask = startEcosystemTask) {
25337
- const parsed = await readExecutionRequest(request);
26524
+ async function handleEcosystemPackageExecution(request2, packageId, action, startTask = startEcosystemTask) {
26525
+ const parsed = await readExecutionRequest(request2);
25338
26526
  if (!parsed.success) {
25339
26527
  auditEcosystemExecution("confirmation_rejected", action, null, "invalid_request");
25340
26528
  return noStoreJson({ error: "Explicit local command confirmation is required" }, 400);
@@ -25366,9 +26554,9 @@ async function handleEcosystemPackageExecution(request, packageId, action, start
25366
26554
  auditEcosystemExecution("execution_started", action, plan.targetId, "none");
25367
26555
  return noStoreJson({ task });
25368
26556
  }
25369
- async function handleEcosystemRecipeExecution(request, recipeId, startTask = startEcosystemRecipeTask) {
26557
+ async function handleEcosystemRecipeExecution(request2, recipeId, startTask = startEcosystemRecipeTask) {
25370
26558
  const action = "recipe";
25371
- const parsed = await readExecutionRequest(request);
26559
+ const parsed = await readExecutionRequest(request2);
25372
26560
  if (!parsed.success) {
25373
26561
  auditEcosystemExecution("confirmation_rejected", action, null, "invalid_request");
25374
26562
  return noStoreJson({ error: "Explicit local command confirmation is required" }, 400);
@@ -25403,149 +26591,159 @@ async function handleEcosystemRecipeExecution(request, recipeId, startTask = sta
25403
26591
  const Route$4 = createFileRoute("/api/ecosystem/recipes/$recipeId/run")({
25404
26592
  server: {
25405
26593
  handlers: {
25406
- POST: ({ params, request }) => handleEcosystemRecipeExecution(request, params.recipeId)
26594
+ POST: ({ params, request: request2 }) => handleEcosystemRecipeExecution(request2, params.recipeId)
25407
26595
  }
25408
26596
  }
25409
26597
  });
25410
26598
  const Route$3 = createFileRoute("/api/ecosystem/packages/$packageId/upgrade")({
25411
26599
  server: {
25412
26600
  handlers: {
25413
- POST: ({ params, request }) => handleEcosystemPackageExecution(request, params.packageId, "upgrade")
26601
+ POST: ({ params, request: request2 }) => handleEcosystemPackageExecution(request2, params.packageId, "upgrade")
25414
26602
  }
25415
26603
  }
25416
26604
  });
25417
26605
  const Route$2 = createFileRoute("/api/ecosystem/packages/$packageId/runner-presets")({
25418
26606
  server: {
25419
26607
  handlers: {
25420
- POST: ({ params, request }) => handleEcosystemPackageExecution(request, params.packageId, "runner-presets")
26608
+ POST: ({ params, request: request2 }) => handleEcosystemPackageExecution(request2, params.packageId, "runner-presets")
25421
26609
  }
25422
26610
  }
25423
26611
  });
25424
26612
  const Route$1 = createFileRoute("/api/ecosystem/packages/$packageId/install")({
25425
26613
  server: {
25426
26614
  handlers: {
25427
- POST: ({ params, request }) => handleEcosystemPackageExecution(request, params.packageId, "install")
26615
+ POST: ({ params, request: request2 }) => handleEcosystemPackageExecution(request2, params.packageId, "install")
25428
26616
  }
25429
26617
  }
25430
26618
  });
25431
26619
  const Route = createFileRoute("/api/ecosystem/packages/$packageId/help")({
25432
26620
  server: {
25433
26621
  handlers: {
25434
- POST: ({ params, request }) => handleEcosystemPackageExecution(request, params.packageId, "help")
26622
+ POST: ({ params, request: request2 }) => handleEcosystemPackageExecution(request2, params.packageId, "help")
25435
26623
  }
25436
26624
  }
25437
26625
  });
25438
- const ReadyzRoute = Route$T.update({
26626
+ const ReadyzRoute = Route$V.update({
25439
26627
  id: "/readyz",
25440
26628
  path: "/readyz",
25441
- getParentRoute: () => Route$U
26629
+ getParentRoute: () => Route$W
26630
+ });
26631
+ const MetricsRoute = Route$T.update({
26632
+ id: "/metrics",
26633
+ path: "/metrics",
26634
+ getParentRoute: () => Route$W
25442
26635
  });
25443
26636
  const LivezRoute = Route$S.update({
25444
26637
  id: "/livez",
25445
26638
  path: "/livez",
25446
- getParentRoute: () => Route$U
26639
+ getParentRoute: () => Route$W
25447
26640
  });
25448
26641
  const SplatRoute = Route$R.update({
25449
26642
  id: "/$",
25450
26643
  path: "/$",
25451
- getParentRoute: () => Route$U
26644
+ getParentRoute: () => Route$W
25452
26645
  });
25453
26646
  const ProxySplatRoute = Route$Q.update({
25454
26647
  id: "/proxy/$",
25455
26648
  path: "/proxy/$",
25456
- getParentRoute: () => Route$U
26649
+ getParentRoute: () => Route$W
26650
+ });
26651
+ const ApiStorageRoute = Route$P.update({
26652
+ id: "/api/storage",
26653
+ path: "/api/storage",
26654
+ getParentRoute: () => Route$W
25457
26655
  });
25458
- const ApiSessionsRoute = Route$P.update({
26656
+ const ApiSessionsRoute = Route$O.update({
25459
26657
  id: "/api/sessions",
25460
26658
  path: "/api/sessions",
25461
- getParentRoute: () => Route$U
26659
+ getParentRoute: () => Route$W
25462
26660
  });
25463
- const ApiRuntimeRoute = Route$O.update({
26661
+ const ApiRuntimeRoute = Route$N.update({
25464
26662
  id: "/api/runtime",
25465
26663
  path: "/api/runtime",
25466
- getParentRoute: () => Route$U
26664
+ getParentRoute: () => Route$W
25467
26665
  });
25468
- const ApiRunsRoute = Route$N.update({
26666
+ const ApiRunsRoute = Route$M.update({
25469
26667
  id: "/api/runs",
25470
26668
  path: "/api/runs",
25471
- getParentRoute: () => Route$U
26669
+ getParentRoute: () => Route$W
25472
26670
  });
25473
- const ApiProvidersRoute = Route$M.update({
26671
+ const ApiProvidersRoute = Route$L.update({
25474
26672
  id: "/api/providers",
25475
26673
  path: "/api/providers",
25476
- getParentRoute: () => Route$U
26674
+ getParentRoute: () => Route$W
25477
26675
  });
25478
- const ApiPiAgentRoute = Route$L.update({
26676
+ const ApiPiAgentRoute = Route$K.update({
25479
26677
  id: "/api/pi-agent",
25480
26678
  path: "/api/pi-agent",
25481
- getParentRoute: () => Route$U
26679
+ getParentRoute: () => Route$W
25482
26680
  });
25483
- const ApiModelsRoute = Route$K.update({
26681
+ const ApiModelsRoute = Route$J.update({
25484
26682
  id: "/api/models",
25485
26683
  path: "/api/models",
25486
- getParentRoute: () => Route$U
26684
+ getParentRoute: () => Route$W
25487
26685
  });
25488
- const ApiMcpRoute = Route$J.update({
26686
+ const ApiMcpRoute = Route$I.update({
25489
26687
  id: "/api/mcp",
25490
26688
  path: "/api/mcp",
25491
- getParentRoute: () => Route$U
26689
+ getParentRoute: () => Route$W
25492
26690
  });
25493
- const ApiLogsRoute = Route$I.update({
26691
+ const ApiLogsRoute = Route$H.update({
25494
26692
  id: "/api/logs",
25495
26693
  path: "/api/logs",
25496
- getParentRoute: () => Route$U
26694
+ getParentRoute: () => Route$W
25497
26695
  });
25498
- const ApiInstancesRoute = Route$H.update({
26696
+ const ApiInstancesRoute = Route$G.update({
25499
26697
  id: "/api/instances",
25500
26698
  path: "/api/instances",
25501
- getParentRoute: () => Route$U
26699
+ getParentRoute: () => Route$W
25502
26700
  });
25503
- const ApiHealthRoute = Route$G.update({
26701
+ const ApiHealthRoute = Route$F.update({
25504
26702
  id: "/api/health",
25505
26703
  path: "/api/health",
25506
- getParentRoute: () => Route$U
26704
+ getParentRoute: () => Route$W
25507
26705
  });
25508
- const ApiGroupsRoute = Route$F.update({
26706
+ const ApiGroupsRoute = Route$E.update({
25509
26707
  id: "/api/groups",
25510
26708
  path: "/api/groups",
25511
- getParentRoute: () => Route$U
26709
+ getParentRoute: () => Route$W
25512
26710
  });
25513
- const ApiConfigRoute = Route$E.update({
26711
+ const ApiConfigRoute = Route$D.update({
25514
26712
  id: "/api/config",
25515
26713
  path: "/api/config",
25516
- getParentRoute: () => Route$U
26714
+ getParentRoute: () => Route$W
25517
26715
  });
25518
- const ApiAlertsRoute = Route$D.update({
26716
+ const ApiAlertsRoute = Route$C.update({
25519
26717
  id: "/api/alerts",
25520
26718
  path: "/api/alerts",
25521
- getParentRoute: () => Route$U
26719
+ getParentRoute: () => Route$W
25522
26720
  });
25523
- const ApiRunsRunIdRoute = Route$C.update({
26721
+ const ApiRunsRunIdRoute = Route$B.update({
25524
26722
  id: "/$runId",
25525
26723
  path: "/$runId",
25526
26724
  getParentRoute: () => ApiRunsRoute
25527
26725
  });
25528
- const ApiProvidersScanRoute = Route$B.update({
26726
+ const ApiProvidersScanRoute = Route$A.update({
25529
26727
  id: "/scan",
25530
26728
  path: "/scan",
25531
26729
  getParentRoute: () => ApiProvidersRoute
25532
26730
  });
25533
- const ApiProvidersImportRoute = Route$A.update({
26731
+ const ApiProvidersImportRoute = Route$z.update({
25534
26732
  id: "/import",
25535
26733
  path: "/import",
25536
26734
  getParentRoute: () => ApiProvidersRoute
25537
26735
  });
25538
- const ApiProvidersExportRoute = Route$z.update({
26736
+ const ApiProvidersExportRoute = Route$y.update({
25539
26737
  id: "/export",
25540
26738
  path: "/export",
25541
26739
  getParentRoute: () => ApiProvidersRoute
25542
26740
  });
25543
- const ApiProvidersProviderIdRoute = Route$y.update({
26741
+ const ApiProvidersProviderIdRoute = Route$x.update({
25544
26742
  id: "/$providerId",
25545
26743
  path: "/$providerId",
25546
26744
  getParentRoute: () => ApiProvidersRoute
25547
26745
  });
25548
- const ApiLogsStreamRoute = Route$x.update({
26746
+ const ApiLogsStreamRoute = Route$U.update({
25549
26747
  id: "/stream",
25550
26748
  path: "/stream",
25551
26749
  getParentRoute: () => ApiLogsRoute
@@ -25563,17 +26761,17 @@ const ApiLogsIdRoute = Route$v.update({
25563
26761
  const ApiKnowledgeSearchRoute = Route$u.update({
25564
26762
  id: "/api/knowledge/search",
25565
26763
  path: "/api/knowledge/search",
25566
- getParentRoute: () => Route$U
26764
+ getParentRoute: () => Route$W
25567
26765
  });
25568
26766
  const ApiKnowledgeProjectContextRoute = Route$t.update({
25569
26767
  id: "/api/knowledge/project-context",
25570
26768
  path: "/api/knowledge/project-context",
25571
- getParentRoute: () => Route$U
26769
+ getParentRoute: () => Route$W
25572
26770
  });
25573
26771
  const ApiKnowledgeCandidatesRoute = Route$s.update({
25574
26772
  id: "/api/knowledge/candidates",
25575
26773
  path: "/api/knowledge/candidates",
25576
- getParentRoute: () => Route$U
26774
+ getParentRoute: () => Route$W
25577
26775
  });
25578
26776
  const ApiInstancesNameRoute = Route$r.update({
25579
26777
  id: "/$name",
@@ -25588,12 +26786,12 @@ const ApiGroupsGroupIdRoute = Route$q.update({
25588
26786
  const ApiEcosystemTasksRoute = Route$p.update({
25589
26787
  id: "/api/ecosystem/tasks",
25590
26788
  path: "/api/ecosystem/tasks",
25591
- getParentRoute: () => Route$U
26789
+ getParentRoute: () => Route$W
25592
26790
  });
25593
26791
  const ApiEcosystemPackagesRoute = Route$o.update({
25594
26792
  id: "/api/ecosystem/packages",
25595
26793
  path: "/api/ecosystem/packages",
25596
- getParentRoute: () => Route$U
26794
+ getParentRoute: () => Route$W
25597
26795
  });
25598
26796
  const ApiConfigPathsRoute = Route$n.update({
25599
26797
  id: "/paths",
@@ -25683,7 +26881,7 @@ const ApiProvidersProviderIdTestLogRoute = Route$7.update({
25683
26881
  const ApiKnowledgeSessionsSessionIdCandidatesRoute = Route$6.update({
25684
26882
  id: "/api/knowledge/sessions/$sessionId/candidates",
25685
26883
  path: "/api/knowledge/sessions/$sessionId/candidates",
25686
- getParentRoute: () => Route$U
26884
+ getParentRoute: () => Route$W
25687
26885
  });
25688
26886
  const ApiKnowledgeCandidatesCandidateIdPromoteRoute = Route$5.update({
25689
26887
  id: "/promote",
@@ -25693,7 +26891,7 @@ const ApiKnowledgeCandidatesCandidateIdPromoteRoute = Route$5.update({
25693
26891
  const ApiEcosystemRecipesRecipeIdRunRoute = Route$4.update({
25694
26892
  id: "/api/ecosystem/recipes/$recipeId/run",
25695
26893
  path: "/api/ecosystem/recipes/$recipeId/run",
25696
- getParentRoute: () => Route$U
26894
+ getParentRoute: () => Route$W
25697
26895
  });
25698
26896
  const ApiEcosystemPackagesPackageIdUpgradeRoute = Route$3.update({
25699
26897
  id: "/$packageId/upgrade",
@@ -25823,6 +27021,7 @@ const ApiKnowledgeCandidatesRouteWithChildren = ApiKnowledgeCandidatesRoute._add
25823
27021
  const rootRouteChildren = {
25824
27022
  SplatRoute,
25825
27023
  LivezRoute,
27024
+ MetricsRoute,
25826
27025
  ReadyzRoute,
25827
27026
  ApiAlertsRoute: ApiAlertsRouteWithChildren,
25828
27027
  ApiConfigRoute: ApiConfigRouteWithChildren,
@@ -25837,6 +27036,7 @@ const rootRouteChildren = {
25837
27036
  ApiRunsRoute: ApiRunsRouteWithChildren,
25838
27037
  ApiRuntimeRoute,
25839
27038
  ApiSessionsRoute,
27039
+ ApiStorageRoute,
25840
27040
  ProxySplatRoute,
25841
27041
  ApiEcosystemPackagesRoute: ApiEcosystemPackagesRouteWithChildren,
25842
27042
  ApiEcosystemTasksRoute: ApiEcosystemTasksRouteWithChildren,
@@ -25846,7 +27046,7 @@ const rootRouteChildren = {
25846
27046
  ApiEcosystemRecipesRecipeIdRunRoute,
25847
27047
  ApiKnowledgeSessionsSessionIdCandidatesRoute
25848
27048
  };
25849
- const routeTree = Route$U._addFileChildren(rootRouteChildren)._addFileTypes();
27049
+ const routeTree = Route$W._addFileChildren(rootRouteChildren)._addFileTypes();
25850
27050
  function defaultRouterBasePath() {
25851
27051
  return getRouterBasePath(typeof process === "undefined" ? void 0 : process.env);
25852
27052
  }