@tonyclaw/agent-inspector 3.1.19 → 3.1.21

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 (61) hide show
  1. package/.output/backend/nitro.json +1 -1
  2. package/.output/cli.js +140 -20
  3. package/.output/server/_ssr/index.mjs +2 -2
  4. package/.output/server/_ssr/{router-0qlvqhVh.mjs → router-Be6BLuut.mjs} +2454 -1135
  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-BOk9hd-Z.js → CompareDrawer-CGk2HFI1.js} +1 -1
  8. package/.output/ui/assets/{InspectorPet-DhHDBpk7.js → InspectorPet-CDM7SdOT.js} +1 -1
  9. package/.output/ui/assets/ProxyViewerContainer-CaWD78tw.js +60 -0
  10. package/.output/ui/assets/{ReplayDialog-DJhKtuNB.js → ReplayDialog-DqaeV1He.js} +1 -1
  11. package/.output/ui/assets/{RequestAnatomy-hP0tsqDC.js → RequestAnatomy-BtHl9P0k.js} +1 -1
  12. package/.output/ui/assets/{ResponseView-BSiUkwCb.js → ResponseView-C6t1vpxs.js} +1 -1
  13. package/.output/ui/assets/{StreamingChunkSequence-CzQqwv4X.js → StreamingChunkSequence-R4mgd3GQ.js} +1 -1
  14. package/.output/ui/assets/{_sessionId-BU-i6h0V.js → _sessionId-CL3hdLlQ.js} +1 -1
  15. package/.output/ui/assets/{_sessionId-C2Pq4Jb0.js → _sessionId-HS666S72.js} +1 -1
  16. package/.output/ui/assets/index-B-Qod-aA.css +1 -0
  17. package/.output/ui/assets/{index-D8C-t-2U.js → index-Bd9jPL4n.js} +1 -1
  18. package/.output/ui/assets/{index-zHpVF0jk.js → index-C4NHrclw.js} +1 -1
  19. package/.output/ui/assets/{index-UUnRwbOJ.js → index-UF1PJEcq.js} +1 -1
  20. package/.output/ui/assets/{index-B7NeIBDf.js → index-tX38BNV2.js} +2 -2
  21. package/.output/ui/assets/{json-viewer-DOzS5rUT.js → json-viewer-KSgzN_Ov.js} +1 -1
  22. package/.output/ui/assets/{jszip.min-CCCY7qNk.js → jszip.min-BiMDxghN.js} +1 -1
  23. package/.output/ui/index.html +2 -2
  24. package/.output/workers/logFinalizer.worker.js +261 -0
  25. package/.output/workers/sessionWorkerEntry.js +261 -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/contracts/index.ts +2 -0
  40. package/src/contracts/log.ts +5 -0
  41. package/src/knowledge/openclawClient.ts +11 -2
  42. package/src/knowledge/openclawGatewayClient.ts +6 -1
  43. package/src/lib/resourceLimits.ts +191 -0
  44. package/src/lib/safeFetch.ts +428 -20
  45. package/src/lib/stopReason.ts +66 -39
  46. package/src/proxy/ecosystemTasks.ts +20 -0
  47. package/src/proxy/handler.ts +17 -4
  48. package/src/proxy/identityProxy.ts +36 -16
  49. package/src/proxy/logIndex.ts +57 -6
  50. package/src/proxy/logger.ts +4 -0
  51. package/src/proxy/rawStreamCapture.ts +16 -1
  52. package/src/proxy/runtimeHealth.ts +4 -2
  53. package/src/proxy/runtimeMetrics.ts +149 -0
  54. package/src/proxy/schemas.ts +2 -0
  55. package/src/proxy/sessionRuntime.ts +38 -0
  56. package/src/proxy/sqliteLogIndex.ts +78 -2
  57. package/src/proxy/storageLifecycle.ts +432 -0
  58. package/src/proxy/store.ts +69 -13
  59. package/.output/server/_tanstack-start-manifest_v-DnbdNeun.mjs +0 -4
  60. package/.output/ui/assets/ProxyViewerContainer-BRYf987G.js +0 -59
  61. 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();
@@ -449,6 +451,8 @@ const ToolTraceEventSchema = object({
449
451
  argumentsText: string().nullable(),
450
452
  argumentsPreview: string().nullable()
451
453
  });
454
+ const StopReasonValueSchema = _enum(["end_turn", "tool_use", "stop", "length"]);
455
+ const StopReasonSchema = StopReasonValueSchema.nullable();
452
456
  const StreamingChunksArraySchema = object({
453
457
  chunks: array(StreamingChunkSchema$1),
454
458
  truncated: boolean().optional().default(false)
@@ -509,6 +513,7 @@ const CapturedLogSchema = object({
509
513
  captureIncompleteReason: CaptureIncompleteReasonSchema.nullable().optional(),
510
514
  warnings: array(string()).optional(),
511
515
  toolTraceEvents: array(ToolTraceEventSchema).optional(),
516
+ stopReason: StopReasonSchema.optional(),
512
517
  /** Error message from streaming response (e.g., SSE error event) */
513
518
  error: string().nullable().optional()
514
519
  });
@@ -1297,8 +1302,8 @@ function canAppendCapturedLogFileOnExit(logDir, filePath) {
1297
1302
  return false;
1298
1303
  }
1299
1304
  }
1300
- const DAY_MS = 24 * 60 * 60 * 1e3;
1301
- const DEFAULT_RETENTION_DAYS = 7;
1305
+ const DAY_MS$1 = 24 * 60 * 60 * 1e3;
1306
+ const DEFAULT_RETENTION_DAYS$1 = 7;
1302
1307
  const DEFAULT_APP_LOG_MAX_BYTES = 10 * 1024 * 1024;
1303
1308
  const DEFAULT_APP_LOG_MAX_FILES = 5;
1304
1309
  function writeStderrFallback(message, error) {
@@ -1326,6 +1331,9 @@ function getInspectorLogPath() {
1326
1331
  }
1327
1332
  return path.join(getDataDir(), "logs", "inspector.log");
1328
1333
  }
1334
+ function resolveApplicationLogPath() {
1335
+ return getInspectorLogPath();
1336
+ }
1329
1337
  function readBoundedInteger(name2, fallback, minimum, maximum) {
1330
1338
  const raw = process.env[name2];
1331
1339
  if (raw === void 0 || raw === "") return fallback;
@@ -1334,7 +1342,7 @@ function readBoundedInteger(name2, fallback, minimum, maximum) {
1334
1342
  return parsed;
1335
1343
  }
1336
1344
  function getApplicationLogPolicy() {
1337
- const retentionDays = readBoundedInteger("LOG_RETENTION_DAYS", DEFAULT_RETENTION_DAYS, 1, 3650);
1345
+ const retentionDays = readBoundedInteger("LOG_RETENTION_DAYS", DEFAULT_RETENTION_DAYS$1, 1, 3650);
1338
1346
  return {
1339
1347
  maxBytes: readBoundedInteger(
1340
1348
  "AGENT_INSPECTOR_APP_LOG_MAX_BYTES",
@@ -1348,7 +1356,7 @@ function getApplicationLogPolicy() {
1348
1356
  1,
1349
1357
  100
1350
1358
  ),
1351
- retentionMs: retentionDays * DAY_MS
1359
+ retentionMs: retentionDays * DAY_MS$1
1352
1360
  };
1353
1361
  }
1354
1362
  function getRotatedGeneration(entry, baseName) {
@@ -3167,6 +3175,98 @@ function extractToolTraceEvents(log) {
3167
3175
  return [];
3168
3176
  }
3169
3177
  }
3178
+ function normalizeAnthropicStopReason(value) {
3179
+ switch (value) {
3180
+ case "end_turn":
3181
+ case "tool_use":
3182
+ return value;
3183
+ case "max_tokens":
3184
+ return "length";
3185
+ default:
3186
+ return null;
3187
+ }
3188
+ }
3189
+ function extractStopReasonFromJsonValue(value) {
3190
+ if (!isRecord$2(value)) return null;
3191
+ if (typeof value.stop_reason === "string") {
3192
+ return normalizeAnthropicStopReason(value.stop_reason);
3193
+ }
3194
+ if (isRecord$2(value.delta) && typeof value.delta.stop_reason === "string") {
3195
+ return normalizeAnthropicStopReason(value.delta.stop_reason);
3196
+ }
3197
+ if (Array.isArray(value.choices)) {
3198
+ for (const choice of value.choices) {
3199
+ if (!isRecord$2(choice) || typeof choice.finish_reason !== "string") continue;
3200
+ const reason = normalizeOpenAIFinishReason(choice.finish_reason);
3201
+ if (reason !== null) return reason;
3202
+ }
3203
+ }
3204
+ if (Array.isArray(value.output)) {
3205
+ for (const item of value.output) {
3206
+ if (isRecord$2(item) && item.type === "function_call") {
3207
+ return "tool_use";
3208
+ }
3209
+ }
3210
+ if (value.status === "completed") {
3211
+ return "stop";
3212
+ }
3213
+ }
3214
+ if (value.status === "incomplete" && isRecord$2(value.incomplete_details)) {
3215
+ const reason = value.incomplete_details.reason;
3216
+ if (typeof reason === "string" && isResponsesLengthLimitReason(reason)) {
3217
+ return "length";
3218
+ }
3219
+ }
3220
+ return null;
3221
+ }
3222
+ function extractStopReasonFromSseText(text) {
3223
+ let stopReason = null;
3224
+ for (const rawLine of text.split("\n")) {
3225
+ const line = rawLine.trim();
3226
+ if (!line.startsWith("data: ")) continue;
3227
+ const data = line.slice(6).trim();
3228
+ if (data === "[DONE]") break;
3229
+ try {
3230
+ const reason = extractStopReasonFromJsonValue(JSON.parse(data));
3231
+ if (reason !== null) stopReason = reason;
3232
+ } catch {
3233
+ }
3234
+ }
3235
+ return stopReason;
3236
+ }
3237
+ function normalizeOpenAIFinishReason(value) {
3238
+ switch (value) {
3239
+ case "stop":
3240
+ case "length":
3241
+ return value;
3242
+ case "max_tokens":
3243
+ return "length";
3244
+ default:
3245
+ return null;
3246
+ }
3247
+ }
3248
+ function isResponsesLengthLimitReason(value) {
3249
+ switch (value) {
3250
+ case "max_output_tokens":
3251
+ case "max_tokens":
3252
+ return true;
3253
+ default:
3254
+ return false;
3255
+ }
3256
+ }
3257
+ function extractStopReason(log) {
3258
+ if (log.stopReason !== void 0) return log.stopReason;
3259
+ if (log.responseText === null) return null;
3260
+ try {
3261
+ let json = JSON.parse(log.responseText);
3262
+ if (typeof json === "string") {
3263
+ json = JSON.parse(json);
3264
+ }
3265
+ return extractStopReasonFromJsonValue(json);
3266
+ } catch {
3267
+ return extractStopReasonFromSseText(log.responseText);
3268
+ }
3269
+ }
3170
3270
  const SQLITE_INDEX_FILE = "inspector.sqlite";
3171
3271
  const SQLITE_PACKAGE_NAME$1 = "better-sqlite3";
3172
3272
  const BUN_SQLITE_MODULE_NAME$1 = "bun:sqlite";
@@ -3209,6 +3309,7 @@ CREATE TABLE IF NOT EXISTS log_index (
3209
3309
  dropped_bytes INTEGER NOT NULL DEFAULT 0,
3210
3310
  capture_incomplete_reason TEXT,
3211
3311
  warnings_json TEXT,
3312
+ stop_reason TEXT,
3212
3313
  tool_trace_events_json TEXT,
3213
3314
  error TEXT
3214
3315
  );
@@ -3255,10 +3356,11 @@ INSERT INTO log_index (
3255
3356
  dropped_bytes,
3256
3357
  capture_incomplete_reason,
3257
3358
  warnings_json,
3359
+ stop_reason,
3258
3360
  tool_trace_events_json,
3259
3361
  error
3260
3362
  ) VALUES (
3261
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
3363
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
3262
3364
  )
3263
3365
  ON CONFLICT(id) DO UPDATE SET
3264
3366
  file = excluded.file,
@@ -3297,6 +3399,7 @@ ON CONFLICT(id) DO UPDATE SET
3297
3399
  dropped_bytes = excluded.dropped_bytes,
3298
3400
  capture_incomplete_reason = excluded.capture_incomplete_reason,
3299
3401
  warnings_json = excluded.warnings_json,
3402
+ stop_reason = excluded.stop_reason,
3300
3403
  tool_trace_events_json = excluded.tool_trace_events_json,
3301
3404
  error = excluded.error
3302
3405
  `;
@@ -3378,6 +3481,7 @@ const REQUIRED_SCHEMA_COLUMNS = [
3378
3481
  { name: "dropped_chunks", definition: "INTEGER NOT NULL DEFAULT 0" },
3379
3482
  { name: "dropped_bytes", definition: "INTEGER NOT NULL DEFAULT 0" },
3380
3483
  { name: "capture_incomplete_reason", definition: "TEXT" },
3484
+ { name: "stop_reason", definition: "TEXT" },
3381
3485
  { name: "tool_trace_events_json", definition: "TEXT" }
3382
3486
  ];
3383
3487
  function ensureSchemaColumns(db) {
@@ -3559,6 +3663,7 @@ function entryParams(entry) {
3559
3663
  summary?.droppedBytes ?? 0,
3560
3664
  summary?.captureIncompleteReason ?? null,
3561
3665
  warningJson(summary?.warnings),
3666
+ summary?.stopReason ?? null,
3562
3667
  toolTraceEventsJson(summary),
3563
3668
  summary?.error ?? null
3564
3669
  ];
@@ -3567,6 +3672,143 @@ function readNumber$1(row, name2) {
3567
3672
  const value = readProperty$2(row, name2);
3568
3673
  return typeof value === "number" && Number.isFinite(value) ? value : null;
3569
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
+ }
3570
3812
  async function getSqliteLogIndexMaxId() {
3571
3813
  const state = await getSqliteState();
3572
3814
  if (state.status !== "ready") return null;
@@ -3579,6 +3821,44 @@ async function upsertSqliteLogIndexEntry(entry) {
3579
3821
  if (state.status !== "ready") return false;
3580
3822
  return runPrepared$1(state.db, UPSERT_SQL$1, entryParams(entry));
3581
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
+ }
3582
3862
  async function syncSqliteLogIndexEntries(entries) {
3583
3863
  const state = await getSqliteState();
3584
3864
  if (state.status !== "ready") return false;
@@ -4064,6 +4344,7 @@ function createLogIndexEntryMetadata(log) {
4064
4344
  droppedBytes: log.droppedBytes ?? 0,
4065
4345
  captureIncompleteReason: log.captureIncompleteReason ?? null,
4066
4346
  warnings: log.warnings,
4347
+ stopReason: log.stopReason ?? extractStopReason(log),
4067
4348
  toolTraceEvents: extractToolTraceEvents(log),
4068
4349
  error: log.error ?? null
4069
4350
  }
@@ -4076,8 +4357,33 @@ function createEmptyIndex() {
4076
4357
  maxId: 0
4077
4358
  };
4078
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
+ }
4079
4376
  async function loadIndex() {
4080
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
+ }
4081
4387
  return await rebuildIndex();
4082
4388
  }
4083
4389
  async function saveIndex(index) {
@@ -4114,8 +4420,10 @@ async function addToIndex(id, file, byteOffset, byteLength, metadata) {
4114
4420
  if (id > index.maxId) {
4115
4421
  index.maxId = id;
4116
4422
  }
4117
- void upsertSqliteLogIndexEntry(entry);
4118
- scheduleIndexFlush();
4423
+ const sqliteUpdated = await upsertSqliteLogIndexEntry(entry);
4424
+ if (!sqliteUpdated) {
4425
+ scheduleIndexFlush();
4426
+ }
4119
4427
  }
4120
4428
  async function updateLogIndexEntryMetadata(log) {
4121
4429
  const index = await loadIndex();
@@ -4126,14 +4434,15 @@ async function updateLogIndexEntryMetadata(log) {
4126
4434
  ...createLogIndexEntryMetadata(log)
4127
4435
  };
4128
4436
  index.entries[log.id] = updated;
4129
- await upsertSqliteLogIndexEntry(updated);
4130
- scheduleIndexFlush();
4437
+ const sqliteUpdated = await upsertSqliteLogIndexEntry(updated);
4438
+ if (!sqliteUpdated) scheduleIndexFlush();
4131
4439
  }
4132
4440
  let indexFlushScheduled = false;
4133
4441
  let indexFlushTimeout = null;
4134
4442
  async function flushIndexAsync() {
4135
4443
  indexFlushScheduled = false;
4136
4444
  indexFlushTimeout = null;
4445
+ if (await listSqliteLogCursorEntries({}) !== null) return;
4137
4446
  const index = await loadIndex();
4138
4447
  await saveIndex(index);
4139
4448
  }
@@ -4150,6 +4459,7 @@ async function flushIndex() {
4150
4459
  indexFlushTimeout = null;
4151
4460
  }
4152
4461
  indexFlushScheduled = false;
4462
+ if (await listSqliteLogCursorEntries({}) !== null) return;
4153
4463
  const index = await loadIndex();
4154
4464
  await saveIndex(index);
4155
4465
  }
@@ -4236,6 +4546,19 @@ function readCaptureIncompleteReasonField(entry) {
4236
4546
  return null;
4237
4547
  }
4238
4548
  }
4549
+ function readStopReasonField(entry) {
4550
+ const value = readStringOrNullField(entry, "stopReason");
4551
+ switch (value) {
4552
+ case "end_turn":
4553
+ case "tool_use":
4554
+ case "stop":
4555
+ case "length":
4556
+ return value;
4557
+ case null:
4558
+ default:
4559
+ return null;
4560
+ }
4561
+ }
4239
4562
  function readStringArrayField(entry, field) {
4240
4563
  const desc = Object.getOwnPropertyDescriptor(entry, field);
4241
4564
  if (desc === void 0 || !Array.isArray(desc.value)) return void 0;
@@ -4287,6 +4610,7 @@ function readLogIndexSummary(entry, id) {
4287
4610
  droppedBytes: readNumberOrNullField(entry, "droppedBytes") ?? 0,
4288
4611
  captureIncompleteReason: readCaptureIncompleteReasonField(entry),
4289
4612
  warnings: readStringArrayField(entry, "warnings"),
4613
+ stopReason: readStopReasonField(entry),
4290
4614
  error: readStringOrNullField(entry, "error")
4291
4615
  };
4292
4616
  }
@@ -4886,94 +5210,876 @@ function getSessionIds() {
4886
5210
  function getSessionSnapshots() {
4887
5211
  return [...sessions$1.values()].map((session) => ({ ...session }));
4888
5212
  }
4889
- const MAX_MEMORY_CACHE = 100;
4890
- const memoryCache = /* @__PURE__ */ new Map();
4891
- const MAX_CURSOR_PAGE_INDEXES = 16;
4892
- const logCursorPageIndexes = /* @__PURE__ */ new Map();
4893
- let sessionArchiveQueue = Promise.resolve();
4894
- const SESSION_ARCHIVE_BATCH_SIZE = 128;
4895
- const SESSION_ARCHIVE_FLUSH_DELAY_MS = 10;
4896
- let sessionArchiveBuffer = [];
4897
- let sessionArchiveFlushTimeout = null;
4898
- function drainSessionArchiveBuffer() {
4899
- const logs = sessionArchiveBuffer;
4900
- sessionArchiveBuffer = [];
4901
- return logs;
4902
- }
4903
- function queueSessionArchiveFlush() {
4904
- const logs = drainSessionArchiveBuffer();
4905
- if (logs.length === 0) return;
4906
- sessionArchiveQueue = sessionArchiveQueue.catch(() => void 0).then(async () => {
4907
- await archiveSessionLogs(logs);
4908
- }).catch((err) => {
4909
- logger.warn("[store] Failed to archive session logs:", String(err));
4910
- });
4911
- }
4912
- function cancelSessionArchiveFlushTimer() {
4913
- if (sessionArchiveFlushTimeout === null) return;
4914
- clearTimeout(sessionArchiveFlushTimeout);
4915
- sessionArchiveFlushTimeout = null;
4916
- }
4917
- function scheduleSessionArchiveFlush() {
4918
- if (sessionArchiveFlushTimeout !== null) return;
4919
- sessionArchiveFlushTimeout = setTimeout(() => {
4920
- sessionArchiveFlushTimeout = null;
4921
- queueSessionArchiveFlush();
4922
- }, SESSION_ARCHIVE_FLUSH_DELAY_MS);
4923
- }
4924
- function cursorPageIndexKey(options) {
4925
- return JSON.stringify({
4926
- sessionId: options.sessionId ?? null,
4927
- model: options.model ?? null,
4928
- clientPid: options.clientPid ?? null
4929
- });
4930
- }
4931
- function matchesCursorIndexFilters(log, index) {
4932
- if (index.sessionId !== void 0 && getLogSessionId(log) !== index.sessionId) return false;
4933
- if (index.model !== void 0 && log.model !== index.model) return false;
4934
- if (index.clientPid !== void 0 && log.clientPid !== index.clientPid) return false;
4935
- return true;
4936
- }
4937
- function insertSortedLogId(ids, id) {
4938
- if (ids.includes(id)) return;
4939
- const insertionIndex = ids.findIndex((candidate) => candidate > id);
4940
- if (insertionIndex === -1) {
4941
- ids.push(id);
4942
- return;
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
+ });
4943
5288
  }
4944
- ids.splice(insertionIndex, 0, id);
4945
- }
4946
- function upsertLogCursorPageIndexes(log) {
4947
- for (const index of logCursorPageIndexes.values()) {
4948
- if (!matchesCursorIndexFilters(log, index)) {
4949
- if (index.logsById.delete(log.id)) {
4950
- index.ids = index.ids.filter((id) => id !== log.id);
4951
- }
4952
- index.entriesById.delete(log.id);
4953
- continue;
4954
- }
4955
- index.logsById.set(log.id, log);
4956
- insertSortedLogId(index.ids, log.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
+ });
4957
5295
  }
4958
- }
4959
- function removeLogsFromCursorPageIndexes(ids) {
4960
- if (ids.size === 0) return;
4961
- for (const index of logCursorPageIndexes.values()) {
4962
- for (const id of ids) {
4963
- index.logsById.delete(id);
4964
- index.entriesById.delete(id);
4965
- }
4966
- index.ids = index.ids.filter((id) => !ids.has(id));
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
+ });
4967
5302
  }
4968
- }
4969
- function clearLogCursorPageIndexes() {
4970
- logCursorPageIndexes.clear();
4971
- }
4972
- function evictOldestIfNeeded() {
4973
- while (memoryCache.size > MAX_MEMORY_CACHE) {
4974
- const oldestKey = memoryCache.keys().next().value;
4975
- if (oldestKey === void 0) break;
4976
- memoryCache.delete(oldestKey);
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
+ });
5309
+ }
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
+ });
5316
+ }
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);
4977
6083
  }
4978
6084
  }
4979
6085
  function normalizeLog(log) {
@@ -4984,7 +6090,8 @@ function normalizeLog(log) {
4984
6090
  const droppedChunks = log.droppedChunks ?? 0;
4985
6091
  const droppedBytes = log.droppedBytes ?? 0;
4986
6092
  const captureIncompleteReason = log.captureIncompleteReason ?? null;
4987
- if (sessionId === log.sessionId && apiFormat === log.apiFormat && log.captureIncomplete !== void 0 && log.droppedChunks !== void 0 && log.droppedBytes !== void 0 && log.captureIncompleteReason !== void 0) {
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) {
4988
6095
  return log;
4989
6096
  }
4990
6097
  return {
@@ -4994,7 +6101,8 @@ function normalizeLog(log) {
4994
6101
  captureIncomplete,
4995
6102
  droppedChunks,
4996
6103
  droppedBytes,
4997
- captureIncompleteReason
6104
+ captureIncompleteReason,
6105
+ stopReason
4998
6106
  };
4999
6107
  }
5000
6108
  function addToCache(log) {
@@ -5033,6 +6141,7 @@ function compactLogForList(log) {
5033
6141
  rawHeaders: void 0,
5034
6142
  headers: void 0,
5035
6143
  streamingChunks: void 0,
6144
+ stopReason: log.stopReason ?? extractStopReason(log),
5036
6145
  toolTraceEvents: log.toolTraceEvents ?? extractToolTraceEvents(log),
5037
6146
  rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength(log.rawRequestBody),
5038
6147
  responseTextBytes: log.responseTextBytes ?? textByteLength(log.responseText),
@@ -5078,6 +6187,7 @@ function compactLogFromIndexSummary(summary) {
5078
6187
  droppedBytes: summary.droppedBytes,
5079
6188
  captureIncompleteReason: summary.captureIncompleteReason,
5080
6189
  warnings: summary.warnings,
6190
+ stopReason: summary.stopReason,
5081
6191
  toolTraceEvents: summary.toolTraceEvents,
5082
6192
  error: summary.error
5083
6193
  });
@@ -5440,17 +6550,42 @@ async function listLogsPage(options) {
5440
6550
  limit: options.limit
5441
6551
  };
5442
6552
  }
5443
- function matchesCursorWindow(id, options) {
5444
- if (options.beforeLogId !== void 0 && id >= options.beforeLogId) return false;
5445
- if (options.afterLogId !== void 0 && id <= options.afterLogId) return false;
5446
- 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;
5447
6580
  }
5448
6581
  function selectCursorPageLogs(ids, options) {
5449
- 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 [];
5450
6585
  if (options.afterLogId !== void 0 || options.anchor === "oldest") {
5451
- return matchingWindow.slice(0, options.limit);
6586
+ return ids.slice(windowStart, Math.min(windowEnd, windowStart + options.limit));
5452
6587
  }
5453
- return matchingWindow.slice(Math.max(0, matchingWindow.length - options.limit));
6588
+ return ids.slice(Math.max(windowStart, windowEnd - options.limit), windowEnd);
5454
6589
  }
5455
6590
  function rememberLogCursorPageIndex(index) {
5456
6591
  if (logCursorPageIndexes.has(index.key)) {
@@ -5482,11 +6617,18 @@ function matchesLogIndexEntryFilters(entry, sessionId, model, clientPid) {
5482
6617
  return true;
5483
6618
  }
5484
6619
  async function buildLogCursorPageIndexFromLogIndex(options, allowRebuild) {
5485
- if (options.clientPid !== void 0) return null;
5486
- 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
+ }
5487
6625
  if (entries.length === 0 && allowRebuild) {
5488
6626
  await rebuildIndex();
5489
- 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
+ }
5490
6632
  }
5491
6633
  if (entries.some((entry) => !hasLogIndexFilterMetadata(entry))) {
5492
6634
  if (!allowRebuild) return null;
@@ -6301,9 +7443,9 @@ function validateValueAgainstSchema(schema, value, toolName, path2, warnings) {
6301
7443
  }
6302
7444
  }
6303
7445
  }
6304
- function extractAnthropicToolSchemas(request) {
7446
+ function extractAnthropicToolSchemas(request2) {
6305
7447
  const tools = /* @__PURE__ */ new Map();
6306
- for (const tool of readArray(request, "tools")) {
7448
+ for (const tool of readArray(request2, "tools")) {
6307
7449
  if (!isRecord$2(tool)) continue;
6308
7450
  const name2 = readString(tool, "name");
6309
7451
  if (name2 === null || name2 === "") continue;
@@ -6314,9 +7456,9 @@ function extractAnthropicToolSchemas(request) {
6314
7456
  }
6315
7457
  return tools;
6316
7458
  }
6317
- function extractOpenAIToolSchemas(request) {
7459
+ function extractOpenAIToolSchemas(request2) {
6318
7460
  const tools = /* @__PURE__ */ new Map();
6319
- for (const tool of readArray(request, "tools")) {
7461
+ for (const tool of readArray(request2, "tools")) {
6320
7462
  const functionDefinition = readObject(tool, "function");
6321
7463
  const name2 = functionDefinition === null ? readString(tool, "name") : readString(functionDefinition, "name") ?? readString(tool, "name");
6322
7464
  if (name2 === null || name2 === "") continue;
@@ -6335,16 +7477,16 @@ function mergeToolSchemas(primary, secondary) {
6335
7477
  }
6336
7478
  return merged;
6337
7479
  }
6338
- function extractToolSchemas(request, apiFormat) {
7480
+ function extractToolSchemas(request2, apiFormat) {
6339
7481
  switch (apiFormat) {
6340
7482
  case "anthropic":
6341
- return extractAnthropicToolSchemas(request);
7483
+ return extractAnthropicToolSchemas(request2);
6342
7484
  case "openai":
6343
- return extractOpenAIToolSchemas(request);
7485
+ return extractOpenAIToolSchemas(request2);
6344
7486
  case "unknown":
6345
7487
  return mergeToolSchemas(
6346
- extractOpenAIToolSchemas(request),
6347
- extractAnthropicToolSchemas(request)
7488
+ extractOpenAIToolSchemas(request2),
7489
+ extractAnthropicToolSchemas(request2)
6348
7490
  );
6349
7491
  }
6350
7492
  }
@@ -6451,10 +7593,10 @@ function uniqueWarnings(warnings) {
6451
7593
  return unique2;
6452
7594
  }
6453
7595
  function analyzeToolSchemaWarnings(input) {
6454
- const request = parseJson$1(input.rawRequestBody);
7596
+ const request2 = parseJson$1(input.rawRequestBody);
6455
7597
  const response = parseJson$1(input.responseText);
6456
- if (!request.ok || !response.ok) return [];
6457
- const toolSchemas = extractToolSchemas(request.value, input.apiFormat);
7598
+ if (!request2.ok || !response.ok) return [];
7599
+ const toolSchemas = extractToolSchemas(request2.value, input.apiFormat);
6458
7600
  const toolCalls = extractToolCalls(response.value, input.apiFormat);
6459
7601
  if (toolCalls.length === 0) return [];
6460
7602
  const warnings = [];
@@ -7061,6 +8203,84 @@ async function closeLogFinalizerWorkers() {
7061
8203
  function forceCloseLogFinalizerWorkers() {
7062
8204
  for (const worker of detachLogFinalizerWorkers()) void worker.terminate();
7063
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
+ }
7064
8284
  const DEFAULT_CACHE_MS = 2e3;
7065
8285
  const DEFAULT_MIN_DISK_HEADROOM_BYTES = 256 * 1024 * 1024;
7066
8286
  const DEFAULT_MAX_FINALIZER_QUEUE = 256;
@@ -7099,6 +8319,7 @@ async function checkDiskHeadroom() {
7099
8319
  }
7100
8320
  function finalizerChecks() {
7101
8321
  const health = getFinalizerRuntimeHealth();
8322
+ const sessionQueue = getSessionRuntimeQueueHealth();
7102
8323
  const runtimeArtifactMissing = health.runtime !== "inline" && !health.artifactAvailable;
7103
8324
  const initializedWorkerMissing = health.runtime === "worker" && health.activeWorkers !== null && health.activeWorkers < health.configuredWorkers;
7104
8325
  const workers = runtimeArtifactMissing || initializedWorkerMissing ? "unavailable" : health.runtime === "inline" ? "degraded" : "ok";
@@ -7107,7 +8328,7 @@ function finalizerChecks() {
7107
8328
  DEFAULT_MAX_FINALIZER_QUEUE
7108
8329
  );
7109
8330
  const warningThreshold = Math.max(1, Math.floor(queueLimit * 0.8));
7110
- 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";
7111
8332
  return { workers, queue };
7112
8333
  }
7113
8334
  function checkWorkers() {
@@ -7189,151 +8410,313 @@ function setRuntimeShuttingDown(shuttingDown) {
7189
8410
  runtimeShuttingDown = shuttingDown;
7190
8411
  readinessService.invalidate();
7191
8412
  }
7192
- async function readinessResponse() {
7193
- const readiness = await getRuntimeReadiness();
7194
- return Response.json(readiness, {
7195
- status: readiness.status === "unavailable" ? 503 : 200,
7196
- 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);
7197
8502
  });
7198
- }
7199
- const Route$T = createFileRoute("/readyz")({
7200
- server: {
7201
- handlers: {
7202
- 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
+ }
7203
8522
  }
7204
- }
7205
- });
7206
- function livenessResponse() {
7207
- return Response.json({ status: "ok" }, { headers: { "cache-control": "no-store" } });
7208
- }
7209
- const Route$S = createFileRoute("/livez")({
7210
- server: {
7211
- handlers: {
7212
- 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"
7213
8556
  }
7214
- }
7215
- });
7216
- const BACKEND_NOT_FOUND_BODY = {
7217
- error: "not_found",
7218
- message: "Agent Inspector backend does not serve the web UI."
7219
- };
7220
- function backendNotFoundResponse() {
7221
- return Response.json(BACKEND_NOT_FOUND_BODY, {
7222
- status: 404,
7223
- headers: { "cache-control": "no-store" }
7224
8557
  });
7225
8558
  }
7226
- const Route$R = createFileRoute("/$")({
8559
+ const Route$U = createFileRoute("/api/logs/stream")({
7227
8560
  server: {
7228
8561
  handlers: {
7229
- GET: backendNotFoundResponse,
7230
- POST: backendNotFoundResponse,
7231
- PUT: backendNotFoundResponse,
7232
- PATCH: backendNotFoundResponse,
7233
- DELETE: backendNotFoundResponse,
7234
- OPTIONS: backendNotFoundResponse
8562
+ GET: ({ request: request2 }) => createLogsStreamResponse(request2)
7235
8563
  }
7236
8564
  }
7237
8565
  });
7238
- function maskApiKey(apiKey) {
7239
- if (apiKey.length <= 8) return "••••••••";
7240
- 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();
7241
8573
  }
7242
- const MAX_REST_LOG_PAGE_SIZE = 200;
7243
- const MAX_SEARCH_QUERY_CHARS = 512;
7244
- const MAX_SEARCH_PROJECT_CHARS = 256;
7245
- const MAX_PROVIDER_MODELS = 64;
7246
- const MAX_PROVIDER_MODEL_NAME_CHARS = 256;
7247
- const MAX_PROVIDER_MODEL_METADATA = 64;
7248
- const MAX_SSE_CLIENTS = 32;
7249
- const MAX_ACTIVE_PROVIDER_TESTS = 2;
7250
- const MAX_PROVIDER_TEST_CONCURRENCY = 4;
7251
- const MAX_ACTIVE_PROXY_REQUESTS = 16;
7252
- const ResourceLimitErrorCodeSchema = _enum([
7253
- "invalid-page-request",
7254
- "search-limit-exceeded",
7255
- "provider-model-limit-exceeded",
7256
- "sse-capacity-exceeded",
7257
- "provider-test-capacity-exceeded",
7258
- "proxy-capacity-exceeded"
7259
- ]);
7260
- const ResourceLimitStatusSchema = union([
7261
- literal(400),
7262
- literal(413),
7263
- literal(429),
7264
- literal(503)
7265
- ]);
7266
- const ResourceLimitErrorSchema = object({
7267
- error: string().min(1).max(300),
7268
- code: ResourceLimitErrorCodeSchema,
7269
- status: ResourceLimitStatusSchema,
7270
- resource: string().min(1).max(80),
7271
- limit: number().int().positive(),
7272
- retryable: boolean()
7273
- });
7274
- function createResourceLimitError(input) {
7275
- 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
+ };
7276
8586
  }
7277
- function resourceLimitResponse(input) {
7278
- const body = createResourceLimitError(input);
7279
- const headers = new Headers({ "cache-control": "no-store" });
7280
- if (input.retryAfterSeconds !== void 0) {
7281
- headers.set("retry-after", String(input.retryAfterSeconds));
7282
- }
7283
- return Response.json(body, { status: input.status, headers });
8587
+ function stopRuntimeAdmission() {
8588
+ acceptingRequests = false;
8589
+ notifyDrained();
7284
8590
  }
7285
- function parseBoundedIntegerParameter(input) {
7286
- if (input.value === null) return { ok: true, value: input.fallback };
7287
- const parsed = Number(input.value);
7288
- 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) {
7289
8605
  return {
7290
- ok: false,
7291
- response: resourceLimitResponse({
7292
- error: `Query parameter "${input.parameter}" must be an integer between ${String(input.minimum)} and ${String(input.maximum)}.`,
7293
- code: "invalid-page-request",
7294
- status: 400,
7295
- resource: input.resource,
7296
- limit: input.maximum,
7297
- retryable: false
7298
- })
8606
+ captureIncompleteLogs: 0,
8607
+ droppedChunks: 0,
8608
+ droppedBytes: 0,
8609
+ source: "unavailable"
7299
8610
  };
7300
8611
  }
7301
- 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
+ };
7302
8618
  }
7303
- function createCapacityLimiter(maxActive) {
7304
- const capacity = Number.isSafeInteger(maxActive) && maxActive > 0 ? maxActive : 1;
7305
- 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;
7306
8633
  return {
7307
- capacity,
7308
- activeCount: () => active,
7309
- tryAcquire: () => {
7310
- if (active >= capacity) return null;
7311
- active += 1;
7312
- let released = false;
7313
- return {
7314
- release: () => {
7315
- if (released) return;
7316
- released = true;
7317
- 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
7318
8668
  }
7319
- };
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
7320
8692
  }
7321
- };
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
+ });
7322
8704
  }
7323
- async function runWithConcurrencyLimit(tasks2, maxConcurrent) {
7324
- if (tasks2.length === 0) return;
7325
- const concurrency = Number.isSafeInteger(maxConcurrent) && maxConcurrent > 0 ? maxConcurrent : 1;
7326
- const workerCount = Math.min(concurrency, tasks2.length);
7327
- let nextIndex = 0;
7328
- const runWorker = async () => {
7329
- while (nextIndex < tasks2.length) {
7330
- const index = nextIndex;
7331
- nextIndex += 1;
7332
- const task = tasks2[index];
7333
- 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
7334
8714
  }
7335
- };
7336
- 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);
7337
8720
  }
7338
8721
  function httpUrl() {
7339
8722
  return string().refine((val) => {
@@ -8677,16 +10060,16 @@ function setCache(port, info) {
8677
10060
  cache.set(port, { ...info, expiresAt: Date.now() + CACHE_TTL_MS });
8678
10061
  evictCacheIfNeeded();
8679
10062
  }
8680
- function extractRemotePort(request) {
10063
+ function extractRemotePort(request2) {
8681
10064
  const candidateSockets = [];
8682
- const requestAsUnknown = request;
10065
+ const requestAsUnknown = request2;
8683
10066
  const ctx = pick(requestAsUnknown, "context");
8684
10067
  const nitro = pick(ctx, "nitro");
8685
10068
  const node = pick(nitro, "node");
8686
10069
  const innerReq = pick(node, "req");
8687
10070
  const sock = pick(innerReq, "socket") ?? pick(innerReq, "connection");
8688
10071
  if (sock !== void 0) candidateSockets.push(sock);
8689
- const descriptor = Object.getOwnPropertyDescriptor(request, "socket");
10072
+ const descriptor = Object.getOwnPropertyDescriptor(request2, "socket");
8690
10073
  if (descriptor !== void 0 && descriptor.get === void 0) {
8691
10074
  candidateSockets.push(descriptor.value);
8692
10075
  }
@@ -8890,12 +10273,12 @@ function readIntHeader(headers, name2) {
8890
10273
  if (!Number.isInteger(parsed) || parsed < 0) return null;
8891
10274
  return parsed;
8892
10275
  }
8893
- function readClientHeaders(request) {
8894
- const pid = readIntHeader(request.headers, CLIENT_HEADERS.pid);
10276
+ function readClientHeaders(request2) {
10277
+ const pid = readIntHeader(request2.headers, CLIENT_HEADERS.pid);
8895
10278
  if (pid === null || pid === 0) return null;
8896
- const port = readIntHeader(request.headers, CLIENT_HEADERS.port);
8897
- const cwd = request.headers.get(CLIENT_HEADERS.cwd);
8898
- 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);
8899
10282
  return {
8900
10283
  pid,
8901
10284
  port: port !== null && port > 0 ? port : null,
@@ -8903,10 +10286,10 @@ function readClientHeaders(request) {
8903
10286
  projectFolder: projectFolder !== null && projectFolder !== "" ? projectFolder : null
8904
10287
  };
8905
10288
  }
8906
- async function getClientInfo(request) {
8907
- const fromHeaders = readClientHeaders(request);
10289
+ async function getClientInfo(request2) {
10290
+ const fromHeaders = readClientHeaders(request2);
8908
10291
  if (fromHeaders !== null) return fromHeaders;
8909
- const remotePort = extractRemotePort(request);
10292
+ const remotePort = extractRemotePort(request2);
8910
10293
  if (remotePort !== null) {
8911
10294
  const cachedPort = getFromCache(remotePort);
8912
10295
  if (cachedPort !== null) return cachedPort;
@@ -9213,585 +10596,569 @@ function isLoopbackHost(hostname) {
9213
10596
  if (LOOPBACK_HOSTNAMES.has(lower2)) return true;
9214
10597
  if (lower2 === "::1") return true;
9215
10598
  if (lower2.startsWith("::ffff:127.")) return true;
9216
- if (lower2.startsWith("::ffff:7f00:")) return true;
9217
- return /^127\./.test(lower2);
9218
- }
9219
- function mappedIpv4Address(hostname) {
9220
- const lower2 = hostname.toLowerCase();
9221
- if (!lower2.startsWith("::ffff:")) return null;
9222
- const suffix = lower2.slice("::ffff:".length);
9223
- if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(suffix)) return suffix;
9224
- const match = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(suffix);
9225
- if (match === null) return null;
9226
- const highText = match[1];
9227
- const lowText = match[2];
9228
- if (highText === void 0 || lowText === void 0) return null;
9229
- const high = Number.parseInt(highText, 16);
9230
- const low = Number.parseInt(lowText, 16);
9231
- return `${String(high >> 8)}.${String(high & 255)}.${String(low >> 8)}.${String(low & 255)}`;
9232
- }
9233
- function isInternalHost(hostname) {
9234
- if (LOOPBACK_HOSTNAMES.has(hostname.toLowerCase())) return true;
9235
- const mappedIpv4 = mappedIpv4Address(hostname);
9236
- if (mappedIpv4 !== null) return isInternalHost(mappedIpv4);
9237
- const ipv4Match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
9238
- if (ipv4Match !== null) {
9239
- const [, a, b] = ipv4Match;
9240
- if (a === void 0 || b === void 0) return false;
9241
- const first = Number.parseInt(a, 10);
9242
- const second = Number.parseInt(b, 10);
9243
- if (first === 127) return true;
9244
- if (first === 10) return true;
9245
- if (first === 172 && second >= 16 && second <= 31) return true;
9246
- if (first === 192 && second === 168) return true;
9247
- if (first === 169 && second === 254) return true;
9248
- if (first === 0) return true;
9249
- if (first === 100 && second >= 64 && second <= 127) return true;
9250
- if (first >= 224) return true;
9251
- if (first === 198 && (second === 18 || second === 19)) return true;
9252
- return false;
9253
- }
9254
- const lower2 = hostname.toLowerCase();
9255
- if (lower2 === "::") return true;
9256
- if (lower2 === "::1") return true;
9257
- if (/^fe[89ab]/.test(lower2)) return true;
9258
- if (lower2.startsWith("fc") || lower2.startsWith("fd")) return true;
9259
- if (lower2.startsWith("ff")) return true;
9260
- return false;
9261
- }
9262
- function evaluateUpstreamUrl(rawUrl, options = {}) {
9263
- if (options.bypass === true) return { allow: true };
9264
- let parsed;
9265
- try {
9266
- parsed = new URL(rawUrl);
9267
- } catch {
9268
- return { allow: false, reason: "Not a valid URL" };
9269
- }
9270
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
9271
- return { allow: false, reason: `Unsupported scheme "${parsed.protocol}"` };
9272
- }
9273
- if (options.allowLoopback === true && parsed.hostname === "localhost") {
9274
- return { allow: true };
9275
- }
9276
- const hostname = parsed.hostname.replace(/^\[|]$/g, "");
9277
- if (isInternalHost(hostname)) {
9278
- if (options.allowLoopback === true) {
9279
- if (isLoopbackHost(hostname)) return { allow: true };
9280
- }
9281
- return {
9282
- allow: false,
9283
- reason: `Refusing to proxy to internal address "${hostname}"`
9284
- };
9285
- }
9286
- return { allow: true };
9287
- }
9288
- async function defaultResolver(hostname) {
9289
- return lookup(hostname, { all: true, verbatim: true });
9290
- }
9291
- function isLoopbackAddress(hostname) {
9292
- const lower2 = hostname.toLowerCase();
9293
- return lower2 === "::1" || lower2.startsWith("::ffff:127.") || lower2.startsWith("::ffff:7f00:") || /^127\./.test(lower2);
9294
- }
9295
- async function evaluateResolvedUpstreamUrl(rawUrl, options = {}) {
9296
- const initial = evaluateUpstreamUrl(rawUrl, options);
9297
- if (!initial.allow || options.bypass === true) return initial;
9298
- const parsed = new URL(rawUrl);
9299
- const hostname = parsed.hostname.replace(/^\[|]$/g, "");
9300
- if (isIP(hostname) !== 0 || hostname.toLowerCase() === "localhost") return initial;
9301
- let addresses;
9302
- try {
9303
- addresses = await (options.resolver ?? defaultResolver)(hostname);
9304
- } catch (error) {
9305
- return {
9306
- allow: false,
9307
- reason: `Could not safely resolve upstream host "${hostname}": ${String(error)}`
9308
- };
9309
- }
9310
- if (addresses.length === 0) {
9311
- return { allow: false, reason: `Upstream host "${hostname}" resolved to no addresses` };
9312
- }
9313
- for (const resolved of addresses) {
9314
- if (!isInternalHost(resolved.address)) continue;
9315
- if (options.allowLoopback === true && isLoopbackAddress(resolved.address)) continue;
9316
- return {
9317
- allow: false,
9318
- reason: `Refusing upstream host "${hostname}" because it resolves to internal address "${resolved.address}"`
9319
- };
9320
- }
9321
- return { allow: true };
9322
- }
9323
- function isRedirectStatus(status) {
9324
- return status >= 300 && status < 400;
9325
- }
9326
- async function safeFetch(rawUrl, init = {}, options = {}) {
9327
- const decision = await evaluateResolvedUpstreamUrl(rawUrl, options);
9328
- if (!decision.allow) {
9329
- return { ok: false, kind: "network-policy", reason: decision.reason };
9330
- }
9331
- const response = await fetch(rawUrl, { ...init, redirect: "manual" });
9332
- if (!isRedirectStatus(response.status)) return { ok: true, response };
9333
- if (response.body !== null) {
9334
- await response.body.cancel().catch(() => void 0);
9335
- }
9336
- const location = response.headers.get("location");
9337
- return {
9338
- ok: false,
9339
- kind: "redirect",
9340
- reason: location === null ? `Upstream returned blocked redirect HTTP ${String(response.status)}` : `Upstream redirect to "${location}" is blocked; configure the final API URL directly`
9341
- };
9342
- }
9343
- const BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
9344
- function isClaudeCodeBillingHeaderBlock(text) {
9345
- const trimmed = text.trimStart();
9346
- return trimmed.toLowerCase().startsWith(BILLING_HEADER_PREFIX);
9347
- }
9348
- function isObjectWithSystem(value) {
9349
- if (!isPlainRecord(value)) return false;
9350
- return Object.prototype.hasOwnProperty.call(value, "system");
9351
- }
9352
- function isBillingHeaderTextBlock(block) {
9353
- if (!isPlainRecord(block)) return false;
9354
- const typeVal = safeGetOwnProperty(block, "type");
9355
- const textVal = safeGetOwnProperty(block, "text");
9356
- if (typeof typeVal !== "string" || typeVal !== "text") return false;
9357
- if (typeof textVal !== "string") return false;
9358
- return isClaudeCodeBillingHeaderBlock(textVal);
9359
- }
9360
- function stripClaudeCodeBillingHeader(rawBody) {
9361
- let parsed;
9362
- try {
9363
- parsed = JSON.parse(rawBody);
9364
- } catch {
9365
- return { body: rawBody, removed: 0 };
9366
- }
9367
- if (!isObjectWithSystem(parsed)) {
9368
- return { body: rawBody, removed: 0 };
9369
- }
9370
- const system = parsed.system;
9371
- if (!Array.isArray(system) || system.length === 0) {
9372
- return { body: rawBody, removed: 0 };
9373
- }
9374
- const kept = [];
9375
- let removed = 0;
9376
- for (const block of system) {
9377
- if (isBillingHeaderTextBlock(block)) {
9378
- removed += 1;
9379
- continue;
9380
- }
9381
- kept.push(block);
9382
- }
9383
- if (removed === 0) {
9384
- return { body: rawBody, removed: 0 };
9385
- }
9386
- if (kept.length === 0) {
9387
- delete parsed.system;
9388
- } else {
9389
- parsed.system = kept;
9390
- }
9391
- return { body: JSON.stringify(parsed), removed };
9392
- }
9393
- const ROLE_ASSISTANT = "assistant";
9394
- const ROLE_TOOL = "tool";
9395
- function isObjectWithMessages(value) {
9396
- if (!isPlainRecord(value)) return false;
9397
- if (!Object.prototype.hasOwnProperty.call(value, "messages")) return false;
9398
- const messages = safeGetOwnProperty(value, "messages");
9399
- return Array.isArray(messages);
9400
- }
9401
- function collectAssistantToolCallIds(message, into) {
9402
- if (!isPlainRecord(message)) return;
9403
- const toolCalls = safeGetOwnProperty(message, "tool_calls");
9404
- if (!Array.isArray(toolCalls)) return;
9405
- for (const tc of toolCalls) {
9406
- if (!isPlainRecord(tc)) continue;
9407
- const id = safeGetOwnProperty(tc, "id");
9408
- if (typeof id === "string" && id.length > 0) {
9409
- into.add(id);
9410
- }
9411
- }
9412
- }
9413
- function findOrphanToolMessageIndices(messages) {
9414
- const knownToolCallIds = /* @__PURE__ */ new Set();
9415
- const indices = [];
9416
- const orphanIds = [];
9417
- for (let i = 0; i < messages.length; i++) {
9418
- const msg = messages[i];
9419
- if (!isPlainRecord(msg)) continue;
9420
- const role = safeGetOwnProperty(msg, "role");
9421
- if (role === ROLE_ASSISTANT) {
9422
- collectAssistantToolCallIds(msg, knownToolCallIds);
9423
- continue;
9424
- }
9425
- if (role === ROLE_TOOL) {
9426
- const id = safeGetOwnProperty(msg, "tool_call_id");
9427
- if (typeof id !== "string" || id.length === 0) {
9428
- indices.push(i);
9429
- orphanIds.push(null);
9430
- continue;
9431
- }
9432
- if (!knownToolCallIds.has(id)) {
9433
- indices.push(i);
9434
- orphanIds.push(id);
9435
- }
9436
- }
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;
9437
10636
  }
9438
- 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;
9439
10644
  }
9440
- function stripOpenAIOrphanToolMessages(rawBody) {
10645
+ function evaluateUpstreamUrl(rawUrl, options = {}) {
10646
+ if (options.bypass === true) return { allow: true };
9441
10647
  let parsed;
9442
10648
  try {
9443
- parsed = JSON.parse(rawBody);
10649
+ parsed = new URL(rawUrl);
9444
10650
  } catch {
9445
- return { body: rawBody, removed: 0, orphanIds: [] };
10651
+ return { allow: false, reason: "Not a valid URL" };
9446
10652
  }
9447
- if (!isObjectWithMessages(parsed)) {
9448
- return { body: rawBody, removed: 0, orphanIds: [] };
10653
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
10654
+ return { allow: false, reason: `Unsupported scheme "${parsed.protocol}"` };
9449
10655
  }
9450
- const messages = parsed.messages;
9451
- const { indices, orphanIds } = findOrphanToolMessageIndices(messages);
9452
- if (indices.length === 0) {
9453
- return { body: rawBody, removed: 0, orphanIds: [] };
10656
+ if (options.allowLoopback === true && parsed.hostname === "localhost") {
10657
+ return { allow: true };
9454
10658
  }
9455
- const dropSet = new Set(indices);
9456
- const kept = [];
9457
- for (let i = 0; i < messages.length; i++) {
9458
- 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
+ };
9459
10668
  }
9460
- parsed.messages = kept;
9461
- return { body: JSON.stringify(parsed), removed: indices.length, orphanIds };
10669
+ return { allow: true };
9462
10670
  }
9463
- const UNASSIGNED_SESSION_QUEUE = "__unassigned__";
9464
- const queues = /* @__PURE__ */ new Map();
9465
- function queueKeyForSession(sessionId) {
9466
- return sessionId ?? UNASSIGNED_SESSION_QUEUE;
10671
+ async function defaultResolver(hostname) {
10672
+ return lookup(hostname, { all: true, verbatim: true });
9467
10673
  }
9468
- function errorMessage$1(err) {
9469
- 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);
9470
10677
  }
9471
- async function runSessionTask(sessionId, taskName, task) {
9472
- 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;
9473
10688
  try {
9474
- const result = await task();
9475
- markSessionTaskFinished(sessionId, null);
9476
- return result;
9477
- } catch (err) {
9478
- const message = errorMessage$1(err);
9479
- logger.error(`[sessionRuntime] ${taskName} failed for session ${sessionId ?? "unassigned"}`);
9480
- markSessionTaskFinished(sessionId, message);
9481
- 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
+ };
9482
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 };
9483
10708
  }
9484
- function enqueueSessionTask(sessionId, taskName, task) {
9485
- const queueKey = queueKeyForSession(sessionId);
9486
- const previous = queues.get(queueKey) ?? Promise.resolve();
9487
- markSessionTaskQueued(sessionId);
9488
- const current = previous.catch(() => void 0).then(() => runSessionTask(sessionId, taskName, task));
9489
- const settled = current.then(
9490
- () => void 0,
9491
- () => void 0
9492
- );
9493
- queues.set(queueKey, settled);
9494
- void settled.then(() => {
9495
- if (queues.get(queueKey) === settled) {
9496
- queues.delete(queueKey);
9497
- }
9498
- });
9499
- return current;
9500
- }
9501
- function enqueueFinalizeLogJob(job) {
9502
- return enqueueSessionTask(
9503
- getLogSessionId(job.log),
9504
- "finalize-log",
9505
- () => executeFinalizeLogJob(job)
9506
- );
10709
+ function isRedirectStatus(status) {
10710
+ return status >= 300 && status < 400;
9507
10711
  }
9508
- async function flushSessionTasks() {
9509
- while (queues.size > 0) {
9510
- await Promise.all([...queues.values()]);
9511
- }
10712
+ function formatByteLimit(limit) {
10713
+ return `${String(limit)} bytes`;
9512
10714
  }
9513
- const DEFAULT_MEMORY_LIMIT_BYTES = 1024 * 1024;
9514
- const FILE_WRITE_BATCH_BYTES = 64 * 1024;
9515
- const DEFAULT_QUEUE_MAX_MESSAGES = 256;
9516
- const DEFAULT_QUEUE_MAX_BYTES = 8 * 1024 * 1024;
9517
- const DEFAULT_GLOBAL_QUEUE_MAX_MESSAGES = 4096;
9518
- const DEFAULT_GLOBAL_QUEUE_MAX_BYTES = 64 * 1024 * 1024;
9519
- const activeCaptures = /* @__PURE__ */ new Set();
9520
- function resolveMemoryLimitBytes() {
9521
- const raw = process.env["AGENT_INSPECTOR_STREAM_MEMORY_LIMIT_BYTES"];
9522
- if (raw === void 0 || raw === "") return DEFAULT_MEMORY_LIMIT_BYTES;
9523
- const parsed = Number(raw);
9524
- 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;
9525
10719
  return parsed;
9526
10720
  }
9527
- function resolvePositiveIntegerEnv(name2, fallback) {
9528
- const raw = process.env[name2];
9529
- if (raw === void 0 || raw === "") return fallback;
9530
- const parsed = Number(raw);
9531
- if (!Number.isSafeInteger(parsed) || parsed < 1) return fallback;
9532
- 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;
9533
10725
  }
9534
- function defaultQueueMaxMessages() {
9535
- return resolvePositiveIntegerEnv(
9536
- "AGENT_INSPECTOR_OBSERVATION_QUEUE_MESSAGES",
9537
- DEFAULT_QUEUE_MAX_MESSAGES
9538
- );
10726
+ function choosePinnedAddress(addresses) {
10727
+ return addresses[0] ?? null;
9539
10728
  }
9540
- function defaultQueueMaxBytes() {
9541
- return resolvePositiveIntegerEnv(
9542
- "AGENT_INSPECTOR_OBSERVATION_QUEUE_BYTES",
9543
- DEFAULT_QUEUE_MAX_BYTES
9544
- );
10729
+ function hostHeaderValue(parsed) {
10730
+ return parsed.port.length === 0 ? parsed.host : `${parsed.hostname}:${parsed.port}`;
9545
10731
  }
9546
- class RawStreamObservationBudget {
9547
- constructor(maxMessages, maxBytes) {
9548
- this.maxMessages = maxMessages;
9549
- this.maxBytes = maxBytes;
9550
- }
9551
- maxMessages;
9552
- maxBytes;
9553
- messages = 0;
9554
- bytes = 0;
9555
- tryAcquire(bytes) {
9556
- if (this.messages + 1 > this.maxMessages || this.bytes + bytes > this.maxBytes) return null;
9557
- this.messages += 1;
9558
- this.bytes += bytes;
9559
- let released = false;
9560
- return () => {
9561
- if (released) return;
9562
- released = true;
9563
- this.messages -= 1;
9564
- this.bytes -= bytes;
9565
- };
10732
+ function headersToRecord$1(headers, parsed) {
10733
+ const normalized = new Headers(headers);
10734
+ if (!normalized.has("host")) {
10735
+ normalized.set("host", hostHeaderValue(parsed));
9566
10736
  }
10737
+ const result = {};
10738
+ normalized.forEach((value, key) => {
10739
+ result[key] = value;
10740
+ });
10741
+ return result;
9567
10742
  }
9568
- const globalObservationBudget = new RawStreamObservationBudget(
9569
- resolvePositiveIntegerEnv(
9570
- "AGENT_INSPECTOR_OBSERVATION_GLOBAL_QUEUE_MESSAGES",
9571
- DEFAULT_GLOBAL_QUEUE_MAX_MESSAGES
9572
- ),
9573
- resolvePositiveIntegerEnv(
9574
- "AGENT_INSPECTOR_OBSERVATION_GLOBAL_QUEUE_BYTES",
9575
- DEFAULT_GLOBAL_QUEUE_MAX_BYTES
9576
- )
9577
- );
9578
- function getRawStreamDir() {
9579
- return join(getDataDir(), "raw-streams");
9580
- }
9581
- function createRawStreamPath() {
9582
- return join(getRawStreamDir(), `${Date.now()}-${randomUUID()}.stream.tmp`);
9583
- }
9584
- class RawStreamCapture {
9585
- memoryLimitBytes;
9586
- queueMaxMessages;
9587
- queueMaxBytes;
9588
- globalBudget;
9589
- chunks = [];
9590
- byteLength = 0;
9591
- filePath = null;
9592
- fileHandle = null;
9593
- pendingFileChunks = [];
9594
- pendingFileBytes = 0;
9595
- operation = Promise.resolve();
9596
- accepting = true;
9597
- closed = false;
9598
- sawChunk = false;
9599
- queuedMessages = 0;
9600
- queuedBytes = 0;
9601
- incomplete = false;
9602
- storageFailed = false;
9603
- droppedChunks = 0;
9604
- droppedBytes = 0;
9605
- gapReason = null;
9606
- constructor(options = {}) {
9607
- this.memoryLimitBytes = options.memoryLimitBytes ?? resolveMemoryLimitBytes();
9608
- this.queueMaxMessages = options.queueMaxMessages ?? defaultQueueMaxMessages();
9609
- this.queueMaxBytes = options.queueMaxBytes ?? defaultQueueMaxBytes();
9610
- this.globalBudget = options.globalBudget ?? globalObservationBudget;
9611
- activeCaptures.add(this);
9612
- }
9613
- get hasChunks() {
9614
- 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 };
9615
10747
  }
9616
- get status() {
10748
+ if (body instanceof URLSearchParams) {
9617
10749
  return {
9618
- incomplete: this.incomplete,
9619
- droppedChunks: this.droppedChunks,
9620
- droppedBytes: this.droppedBytes,
9621
- reason: this.gapReason
10750
+ ok: true,
10751
+ body: new TextEncoder().encode(body.toString()),
10752
+ contentType: "application/x-www-form-urlencoded;charset=UTF-8"
9622
10753
  };
9623
10754
  }
9624
- recordDroppedObservation(reason, observedBytes) {
9625
- const safeObservedBytes = Number.isSafeInteger(observedBytes) && observedBytes >= 0 ? observedBytes : 0;
9626
- 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 };
9627
10758
  }
9628
- async append(text) {
9629
- if (text === "") return;
9630
- const next = this.operation.then(() => this.appendInternal(text));
9631
- this.operation = next.catch(() => void 0);
9632
- return next;
10759
+ if (body instanceof Blob) {
10760
+ return { ok: true, body: new Uint8Array(await body.arrayBuffer()), contentType: body.type };
9633
10761
  }
9634
- /**
9635
- * Submit an observation without waiting for file I/O. Once capacity is exceeded, capture remains
9636
- * incomplete and all later chunks are counted as dropped so the persisted value is an honest
9637
- * prefix rather than a misleading stream with an unmarked hole.
9638
- */
9639
- tryAppend(text, observedBytes) {
9640
- const safeObservedBytes = Number.isSafeInteger(observedBytes) && observedBytes >= 0 ? observedBytes : 0;
9641
- const queuedBytes = Math.max(safeObservedBytes, Buffer.byteLength(text, "utf-8"));
9642
- if (!this.accepting || this.closed) {
9643
- this.recordGap("pipeline-closed", safeObservedBytes, safeObservedBytes > 0 || text !== "");
9644
- return false;
9645
- }
9646
- if (this.incomplete) {
9647
- this.recordGap("capacity-exceeded", safeObservedBytes, safeObservedBytes > 0 || text !== "");
9648
- return false;
9649
- }
9650
- if (text === "") return true;
9651
- if (this.queuedMessages + 1 > this.queueMaxMessages || this.queuedBytes + queuedBytes > this.queueMaxBytes) {
9652
- this.recordGap("capacity-exceeded", safeObservedBytes, true);
9653
- 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;
9654
10772
  }
9655
- const releaseGlobal = this.globalBudget.tryAcquire(queuedBytes);
9656
- if (releaseGlobal === null) {
9657
- this.recordGap("capacity-exceeded", safeObservedBytes, true);
9658
- 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();
9659
10821
  }
9660
- this.queuedMessages += 1;
9661
- this.queuedBytes += queuedBytes;
9662
- this.operation = this.operation.then(async () => {
9663
- if (this.storageFailed) {
9664
- 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();
9665
10837
  return;
9666
10838
  }
9667
- await this.appendInternal(text);
9668
- }).catch(() => {
9669
- this.storageFailed = true;
9670
- this.recordGap("write-failed", safeObservedBytes, true);
9671
- }).finally(() => {
9672
- this.queuedMessages -= 1;
9673
- this.queuedBytes -= queuedBytes;
9674
- releaseGlobal();
9675
- });
9676
- 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
+ };
9677
10871
  }
9678
- async finalize() {
9679
- this.accepting = false;
9680
- const next = this.operation.then(() => this.finalizeInternal());
9681
- this.operation = next.then(
9682
- () => void 0,
9683
- () => 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
9684
10884
  );
9685
- return next.finally(() => activeCaptures.delete(this));
9686
- }
9687
- async appendInternal(text) {
9688
- if (this.closed) return;
9689
- this.sawChunk = true;
9690
- const nextBytes = Buffer.byteLength(text, "utf-8");
9691
- if (this.filePath === null && this.byteLength + nextBytes <= this.memoryLimitBytes) {
9692
- this.chunks.push(text);
9693
- this.byteLength += nextBytes;
9694
- 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
+ }
9695
10938
  }
9696
- if (this.filePath === null) {
9697
- 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);
9698
10944
  }
9699
- if (this.filePath !== null) {
9700
- this.pendingFileChunks.push(text);
9701
- this.pendingFileBytes += nextBytes;
9702
- this.byteLength += nextBytes;
9703
- 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 });
9704
10992
  }
9705
10993
  }
9706
- recordGap(reason, observedBytes, countChunk) {
9707
- this.incomplete = true;
9708
- this.gapReason ??= reason;
9709
- if (countChunk) this.droppedChunks += 1;
9710
- 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);
9711
10999
  }
9712
- async finalizeInternal() {
9713
- if (!this.closed) {
9714
- this.closed = true;
9715
- await this.flushFileBuffer();
9716
- if (this.fileHandle !== null) {
9717
- const handle = this.fileHandle;
9718
- this.fileHandle = null;
9719
- try {
9720
- await handle.sync();
9721
- } finally {
9722
- await handle.close();
9723
- }
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
+ };
9724
11017
  }
11018
+ return { ok: true, response: responseWithByteLimit(response, maxBytes) };
9725
11019
  }
9726
- if (this.filePath !== null) {
9727
- 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);
9728
11040
  }
9729
- return { type: "memory", rawStream: this.chunks.join("") };
9730
11041
  }
9731
- async spillToFile() {
9732
- const path2 = createRawStreamPath();
9733
- assert(ensurePrivateDirectorySync(getRawStreamDir()), "Could not secure raw-stream directory");
9734
- const handle = await open(path2, "wx", PRIVATE_FILE_MODE);
9735
- let committed = false;
9736
- try {
9737
- const memoryText = this.chunks.join("");
9738
- if (memoryText.length > 0) await handle.write(memoryText, void 0, "utf-8");
9739
- assert(securePrivateFileSync(path2), "Could not secure raw-stream file");
9740
- this.filePath = path2;
9741
- this.fileHandle = handle;
9742
- this.chunks = [];
9743
- committed = true;
9744
- } finally {
9745
- if (!committed) {
9746
- await handle.close().catch(() => void 0);
9747
- await unlink(path2).catch(() => void 0);
9748
- }
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;
9749
11080
  }
11081
+ kept.push(block);
9750
11082
  }
9751
- async flushFileBuffer() {
9752
- if (this.fileHandle === null || this.pendingFileChunks.length === 0) return;
9753
- const text = this.pendingFileChunks.join("");
9754
- this.pendingFileChunks = [];
9755
- this.pendingFileBytes = 0;
9756
- await this.fileHandle.write(text, void 0, "utf-8");
11083
+ if (removed === 0) {
11084
+ return { body: rawBody, removed: 0 };
9757
11085
  }
11086
+ if (kept.length === 0) {
11087
+ delete parsed.system;
11088
+ } else {
11089
+ parsed.system = kept;
11090
+ }
11091
+ return { body: JSON.stringify(parsed), removed };
9758
11092
  }
9759
- async function flushAllRawStreamCaptures() {
9760
- await Promise.all([...activeCaptures].map((capture) => capture.finalize()));
9761
- }
9762
- let acceptingRequests = true;
9763
- let activeRequests = 0;
9764
- const drainWaiters = /* @__PURE__ */ new Set();
9765
- function notifyDrained() {
9766
- if (activeRequests !== 0) return;
9767
- for (const resolve2 of drainWaiters) resolve2();
9768
- 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);
9769
11100
  }
9770
- function acquireRuntimeRequest() {
9771
- if (!acceptingRequests || activeRequests >= MAX_ACTIVE_PROXY_REQUESTS) return null;
9772
- activeRequests += 1;
9773
- let released = false;
9774
- return {
9775
- release() {
9776
- if (released) return;
9777
- released = true;
9778
- activeRequests = Math.max(0, activeRequests - 1);
9779
- 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);
9780
11110
  }
9781
- };
9782
- }
9783
- function stopRuntimeAdmission() {
9784
- acceptingRequests = false;
9785
- notifyDrained();
11111
+ }
9786
11112
  }
9787
- async function waitForRuntimeTrafficDrain() {
9788
- if (activeRequests === 0) return;
9789
- await new Promise((resolve2) => {
9790
- drainWaiters.add(resolve2);
9791
- });
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 };
9792
11139
  }
9793
- function getRuntimeAdmissionState() {
9794
- 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 };
9795
11162
  }
9796
11163
  const PATH_V1_CHAT_COMPLETIONS = "/v1/chat/completions";
9797
11164
  function buildUpstreamUrl(upstreamBase, normalizedPath) {
@@ -10281,10 +11648,14 @@ async function handleAdmittedProxy(req) {
10281
11648
  body: bodyToForward,
10282
11649
  signal: req.signal
10283
11650
  },
10284
- { 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
+ }
10285
11656
  );
10286
11657
  if (!fetchResult.ok) {
10287
- 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;
10288
11659
  logger.warn(`[handler] Safe fetch blocked ${upstreamUrl}: ${fetchResult.reason}`);
10289
11660
  log.elapsedMs = Date.now() - startTime;
10290
11661
  log.responseStatus = status;
@@ -10292,7 +11663,7 @@ async function handleAdmittedProxy(req) {
10292
11663
  appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: fetchResult.reason });
10293
11664
  await persistFinalizedLogUpdate(log);
10294
11665
  return new Response(
10295
- 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",
10296
11667
  { status }
10297
11668
  );
10298
11669
  }
@@ -10394,23 +11765,62 @@ async function handleProxy(req) {
10394
11765
  const Route$Q = createFileRoute("/proxy/$")({
10395
11766
  server: {
10396
11767
  handlers: {
10397
- GET: ({ request }) => handleProxy(request),
10398
- POST: ({ request }) => handleProxy(request),
10399
- PUT: ({ request }) => handleProxy(request),
10400
- DELETE: ({ request }) => handleProxy(request),
10401
- PATCH: ({ request }) => handleProxy(request),
10402
- 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
+ }
10403
11813
  }
10404
11814
  }
10405
11815
  });
10406
11816
  function parseBooleanFlag(value) {
10407
11817
  return value === "1" || value === "true";
10408
11818
  }
10409
- const Route$P = createFileRoute("/api/sessions")({
11819
+ const Route$O = createFileRoute("/api/sessions")({
10410
11820
  server: {
10411
11821
  handlers: {
10412
- GET: async ({ request }) => {
10413
- const url2 = new URL(request.url);
11822
+ GET: async ({ request: request2 }) => {
11823
+ const url2 = new URL(request2.url);
10414
11824
  const sessionId = url2.searchParams.get("sessionId");
10415
11825
  if (sessionId !== null) {
10416
11826
  if (sessionId.length === 0) {
@@ -10539,7 +11949,7 @@ function runtimeDiscoveryResponse(env = process.env) {
10539
11949
  headers: { "cache-control": "no-store" }
10540
11950
  });
10541
11951
  }
10542
- const Route$O = createFileRoute("/api/runtime")({
11952
+ const Route$N = createFileRoute("/api/runtime")({
10543
11953
  server: {
10544
11954
  handlers: {
10545
11955
  GET: () => runtimeDiscoveryResponse()
@@ -11948,20 +13358,20 @@ function readInspectorRunEvidence(runId) {
11948
13358
  }
11949
13359
  return serviceOk({ run, evidence: run.evidence, markdown });
11950
13360
  }
11951
- async function readJsonBody$6(request) {
13361
+ async function readJsonBody$6(request2) {
11952
13362
  try {
11953
- const raw = await request.text();
13363
+ const raw = await request2.text();
11954
13364
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
11955
13365
  return { ok: true, value: JSON.parse(raw) };
11956
13366
  } catch {
11957
13367
  return { ok: false };
11958
13368
  }
11959
13369
  }
11960
- const Route$N = createFileRoute("/api/runs")({
13370
+ const Route$M = createFileRoute("/api/runs")({
11961
13371
  server: {
11962
13372
  handlers: {
11963
- GET: ({ request }) => {
11964
- const url2 = new URL(request.url);
13373
+ GET: ({ request: request2 }) => {
13374
+ const url2 = new URL(request2.url);
11965
13375
  if (url2.searchParams.get("failures") === "1") {
11966
13376
  const limitResult = parseBoundedIntegerParameter({
11967
13377
  value: url2.searchParams.get("limit"),
@@ -11976,8 +13386,8 @@ const Route$N = createFileRoute("/api/runs")({
11976
13386
  }
11977
13387
  return Response.json(listInspectorRuns());
11978
13388
  },
11979
- POST: async ({ request }) => {
11980
- const body = await readJsonBody$6(request);
13389
+ POST: async ({ request: request2 }) => {
13390
+ const body = await readJsonBody$6(request2);
11981
13391
  if (!body.ok) {
11982
13392
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
11983
13393
  }
@@ -12009,16 +13419,16 @@ const ProviderInputSchema = object({
12009
13419
  modelMetadata: ProviderModelMetadataListSchema.optional(),
12010
13420
  source: _enum(["company", "personal"]).optional()
12011
13421
  });
12012
- const Route$M = createFileRoute("/api/providers")({
13422
+ const Route$L = createFileRoute("/api/providers")({
12013
13423
  server: {
12014
13424
  handlers: {
12015
13425
  GET: () => {
12016
13426
  return Response.json(redactProviders(getProviders()));
12017
13427
  },
12018
- POST: async ({ request }) => {
13428
+ POST: async ({ request: request2 }) => {
12019
13429
  let body;
12020
13430
  try {
12021
- body = await request.json();
13431
+ body = await request2.json();
12022
13432
  } catch {
12023
13433
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
12024
13434
  }
@@ -13637,8 +15047,8 @@ function firstModel(provider) {
13637
15047
  }
13638
15048
  return void 0;
13639
15049
  }
13640
- function providerCandidates(request) {
13641
- if (request.providerId === void 0) {
15050
+ function providerCandidates(request2) {
15051
+ if (request2.providerId === void 0) {
13642
15052
  return getProviders().toSorted((left, right) => {
13643
15053
  const leftCompanyRank = left.source === "company" ? 0 : 1;
13644
15054
  const rightCompanyRank = right.source === "company" ? 0 : 1;
@@ -13646,12 +15056,12 @@ function providerCandidates(request) {
13646
15056
  return left.name.localeCompare(right.name);
13647
15057
  });
13648
15058
  }
13649
- const provider = getProvider(request.providerId);
15059
+ const provider = getProvider(request2.providerId);
13650
15060
  return provider === void 0 ? [] : [provider];
13651
15061
  }
13652
- function selectProvider(request) {
13653
- for (const provider of providerCandidates(request)) {
13654
- const modelName = request.model ?? firstModel(provider);
15062
+ function selectProvider(request2) {
15063
+ for (const provider of providerCandidates(request2)) {
15064
+ const modelName = request2.model ?? firstModel(provider);
13655
15065
  if (modelName === void 0) continue;
13656
15066
  const responsesBaseUrl = nonEmpty$1(provider.openaiResponsesBaseUrl);
13657
15067
  if (responsesBaseUrl !== void 0) {
@@ -13694,8 +15104,8 @@ function truncateContext(value, maxLength) {
13694
15104
  return `${value.slice(0, maxLength)}
13695
15105
  ...[truncated ${String(value.length - maxLength)} chars]`;
13696
15106
  }
13697
- function lastUserQuestion(request) {
13698
- const history = request.history ?? [];
15107
+ function lastUserQuestion(request2) {
15108
+ const history = request2.history ?? [];
13699
15109
  for (let index = history.length - 1; index >= 0; index -= 1) {
13700
15110
  const message = history[index];
13701
15111
  if (message !== void 0 && message.role === "user") return message.content;
@@ -13716,13 +15126,13 @@ function emptySummary() {
13716
15126
  sourceCounts: { log: 0, knowledge: 0, alert: 0, run: 0, group: 0, provider: 0 }
13717
15127
  };
13718
15128
  }
13719
- async function retrieveInspectorContext(request) {
15129
+ async function retrieveInspectorContext(request2) {
13720
15130
  try {
13721
15131
  return await retrievePiAgentEvidence({
13722
- question: request.question,
13723
- sessionId: request.sessionId,
13724
- recentUserQuestion: lastUserQuestion(request),
13725
- 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 }
13726
15136
  });
13727
15137
  } catch (err) {
13728
15138
  return {
@@ -13872,11 +15282,11 @@ function lastAssistantMessage(messages) {
13872
15282
  }
13873
15283
  return null;
13874
15284
  }
13875
- function boundedHistory(request) {
13876
- 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)}`);
13877
15287
  return history.length > 0 ? history.join("\n\n") : "No prior conversation in this request.";
13878
15288
  }
13879
- function buildContext(request, retrieval) {
15289
+ function buildContext(request2, retrieval) {
13880
15290
  return {
13881
15291
  systemPrompt: buildSystemPrompt(),
13882
15292
  messages: [
@@ -13884,16 +15294,16 @@ function buildContext(request, retrieval) {
13884
15294
  role: "user",
13885
15295
  content: [
13886
15296
  "Bounded conversation history:",
13887
- boundedHistory(request),
15297
+ boundedHistory(request2),
13888
15298
  "",
13889
- buildUserPrompt(request.question, retrieval)
15299
+ buildUserPrompt(request2.question, retrieval)
13890
15300
  ].join("\n"),
13891
15301
  timestamp: Date.now()
13892
15302
  }
13893
15303
  ]
13894
15304
  };
13895
15305
  }
13896
- async function askPi(selected, request, retrieval) {
15306
+ async function askPi(selected, request2, retrieval) {
13897
15307
  const { Agent } = await import(
13898
15308
  /* @vite-ignore */
13899
15309
  "../_libs/mariozechner__pi-agent-core.mjs"
@@ -13909,7 +15319,7 @@ async function askPi(selected, request, retrieval) {
13909
15319
  sessionId: selected.provider.id,
13910
15320
  maxRetryDelayMs: 1e4
13911
15321
  });
13912
- const context = buildContext(request, retrieval);
15322
+ const context = buildContext(request2, retrieval);
13913
15323
  await agent.prompt(context.messages);
13914
15324
  const assistantMessage = lastAssistantMessage(agent.state.messages);
13915
15325
  if (assistantMessage === null) {
@@ -13919,9 +15329,9 @@ async function askPi(selected, request, retrieval) {
13919
15329
  if (answer !== "") return stripReasoningTags(answer);
13920
15330
  return assistantMessage.errorMessage ?? "Inspector Assistant received an empty provider response.";
13921
15331
  }
13922
- async function runPiAgent(request) {
13923
- const retrieval = await retrieveInspectorContext(request);
13924
- const selected = selectProvider(request);
15332
+ async function runPiAgent(request2) {
15333
+ const retrieval = await retrieveInspectorContext(request2);
15334
+ const selected = selectProvider(request2);
13925
15335
  if (selected === null) {
13926
15336
  return {
13927
15337
  answer: "Inspector Assistant needs at least one Provider with OpenAI Responses, OpenAI Chat, or Anthropic Messages configured.",
@@ -13937,7 +15347,7 @@ async function runPiAgent(request) {
13937
15347
  }
13938
15348
  let answer;
13939
15349
  try {
13940
- answer = await askPi(selected, request, retrieval);
15350
+ answer = await askPi(selected, request2, retrieval);
13941
15351
  } catch (err) {
13942
15352
  answer = `Inspector Assistant provider request failed: ${err instanceof Error ? err.message : String(err)}`;
13943
15353
  }
@@ -13953,13 +15363,13 @@ async function runPiAgent(request) {
13953
15363
  queryStats: retrieval.queryStats
13954
15364
  };
13955
15365
  }
13956
- const Route$L = createFileRoute("/api/pi-agent")({
15366
+ const Route$K = createFileRoute("/api/pi-agent")({
13957
15367
  server: {
13958
15368
  handlers: {
13959
- POST: async ({ request }) => {
15369
+ POST: async ({ request: request2 }) => {
13960
15370
  let body;
13961
15371
  try {
13962
- body = await request.json();
15372
+ body = await request2.json();
13963
15373
  } catch {
13964
15374
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
13965
15375
  }
@@ -13976,7 +15386,7 @@ const Route$L = createFileRoute("/api/pi-agent")({
13976
15386
  }
13977
15387
  }
13978
15388
  });
13979
- const Route$K = createFileRoute("/api/models")({
15389
+ const Route$J = createFileRoute("/api/models")({
13980
15390
  server: {
13981
15391
  handlers: {
13982
15392
  GET: () => Response.json(getModels())
@@ -13984,7 +15394,7 @@ const Route$K = createFileRoute("/api/models")({
13984
15394
  }
13985
15395
  });
13986
15396
  const name = "@tonyclaw/agent-inspector";
13987
- const version = "3.1.19";
15397
+ const version = "3.1.21";
13988
15398
  const packageJson = {
13989
15399
  name,
13990
15400
  version
@@ -14321,8 +15731,8 @@ async function getInstanceConnectionImpl(callApi2, name2) {
14321
15731
  }
14322
15732
  async function startInstanceImpl(callApi2, input) {
14323
15733
  const path2 = instancePath(input.name, "/start");
14324
- const request = lifecycleRequest(input);
14325
- const response = await callApi2(path2, jsonPost(InstanceStartRequestSchema.parse(request)));
15734
+ const request2 = lifecycleRequest(input);
15735
+ const response = await callApi2(path2, jsonPost(InstanceStartRequestSchema.parse(request2)));
14326
15736
  return validatedInstanceResponse(response, InstanceOperationOutputSchema, `POST ${path2}`);
14327
15737
  }
14328
15738
  async function stopInstanceImpl(callApi2, name2) {
@@ -14332,8 +15742,8 @@ async function stopInstanceImpl(callApi2, name2) {
14332
15742
  }
14333
15743
  async function restartInstanceImpl(callApi2, input) {
14334
15744
  const path2 = instancePath(input.name, "/restart");
14335
- const request = lifecycleRequest(input);
14336
- const response = await callApi2(path2, jsonPost(InstanceStartRequestSchema.parse(request)));
15745
+ const request2 = lifecycleRequest(input);
15746
+ const response = await callApi2(path2, jsonPost(InstanceStartRequestSchema.parse(request2)));
14337
15747
  return validatedInstanceResponse(response, InstanceOperationOutputSchema, `POST ${path2}`);
14338
15748
  }
14339
15749
  function groupEvidenceDir(groupId) {
@@ -15992,10 +17402,10 @@ async function getServer() {
15992
17402
  }
15993
17403
  return initPromise;
15994
17404
  }
15995
- async function handleMcpRequest(request) {
15996
- return runWithRequestPublicBasePath(request, async () => {
17405
+ async function handleMcpRequest(request2) {
17406
+ return runWithRequestPublicBasePath(request2, async () => {
15997
17407
  const { transport } = await getServer();
15998
- return transport.handleRequest(request);
17408
+ return transport.handleRequest(request2);
15999
17409
  });
16000
17410
  }
16001
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.
@@ -17645,15 +19055,15 @@ const PROMPT_NAMES = [
17645
19055
  "inspector_generate_group_report",
17646
19056
  "inspector_extract_repro_steps"
17647
19057
  ];
17648
- const Route$J = createFileRoute("/api/mcp")({
19058
+ const Route$I = createFileRoute("/api/mcp")({
17649
19059
  server: {
17650
19060
  handlers: {
17651
19061
  // The SDK may issue either POST, GET, or DELETE. TanStack Start only
17652
19062
  // requires us to declare the methods we accept; routing by method is
17653
19063
  // handled inside the transport.
17654
- POST: ({ request }) => handleMcpRequest(request),
17655
- GET: ({ request }) => handleMcpRequest(request),
17656
- DELETE: ({ request }) => handleMcpRequest(request)
19064
+ POST: ({ request: request2 }) => handleMcpRequest(request2),
19065
+ GET: ({ request: request2 }) => handleMcpRequest(request2),
19066
+ DELETE: ({ request: request2 }) => handleMcpRequest(request2)
17657
19067
  }
17658
19068
  }
17659
19069
  });
@@ -17763,11 +19173,11 @@ function parseOptionalLogId(value) {
17763
19173
  const parsed = parseLogId(value);
17764
19174
  return parsed === null ? { ok: false } : { ok: true, value: parsed };
17765
19175
  }
17766
- const Route$I = createFileRoute("/api/logs")({
19176
+ const Route$H = createFileRoute("/api/logs")({
17767
19177
  server: {
17768
19178
  handlers: {
17769
- GET: async ({ request }) => {
17770
- const url2 = new URL(request.url);
19179
+ GET: async ({ request: request2 }) => {
19180
+ const url2 = new URL(request2.url);
17771
19181
  if (url2.searchParams.get("stats") === "1") {
17772
19182
  return Response.json(getLogStorageStats());
17773
19183
  }
@@ -17871,10 +19281,10 @@ const Route$I = createFileRoute("/api/logs")({
17871
19281
  await listLogsPage({ sessionId, model, clientPid, offset, limit, includeBodies })
17872
19282
  );
17873
19283
  },
17874
- DELETE: async ({ request }) => {
19284
+ DELETE: async ({ request: request2 }) => {
17875
19285
  let body = void 0;
17876
19286
  try {
17877
- const raw = await request.text();
19287
+ const raw = await request2.text();
17878
19288
  if (raw !== "") {
17879
19289
  const parsed = JSON.parse(raw);
17880
19290
  const result2 = DeleteLogsBodySchema.safeParse(parsed);
@@ -19681,10 +21091,10 @@ function instanceErrorResponse(error) {
19681
21091
  function instanceResultResponse(result, successStatus = 200) {
19682
21092
  return result.kind === "error" ? instanceErrorResponse(result.error) : Response.json(result.value, { status: successStatus, headers: NO_STORE_HEADERS$1 });
19683
21093
  }
19684
- async function parseInstanceBody(request, schema) {
21094
+ async function parseInstanceBody(request2, schema) {
19685
21095
  let value;
19686
21096
  try {
19687
- const text = await request.text();
21097
+ const text = await request2.text();
19688
21098
  value = text.trim().length === 0 ? {} : JSON.parse(text);
19689
21099
  } catch {
19690
21100
  return {
@@ -19723,7 +21133,7 @@ function instanceListResponse(instances) {
19723
21133
  { headers: NO_STORE_HEADERS$1 }
19724
21134
  );
19725
21135
  }
19726
- const Route$H = createFileRoute("/api/instances")({
21136
+ const Route$G = createFileRoute("/api/instances")({
19727
21137
  server: {
19728
21138
  handlers: {
19729
21139
  GET: async () => {
@@ -20033,6 +21443,11 @@ const tasks = /* @__PURE__ */ new Map();
20033
21443
  const activeTasks = /* @__PURE__ */ new Map();
20034
21444
  let acceptingTasks = true;
20035
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
+ }
20036
21451
  function nowIso() {
20037
21452
  return (/* @__PURE__ */ new Date()).toISOString();
20038
21453
  }
@@ -20227,6 +21642,7 @@ function listEcosystemTasks() {
20227
21642
  }
20228
21643
  function startEcosystemTask(packageId, action) {
20229
21644
  if (!acceptingTasks) return null;
21645
+ if (activeTasks.size >= activeTaskLimit()) return null;
20230
21646
  if (action === "recipe") return null;
20231
21647
  const definition = action === "runner-presets" ? findEcosystemPackage("eval-harness") : findEcosystemPackage(packageId);
20232
21648
  if (definition === null) return null;
@@ -20253,6 +21669,7 @@ function startEcosystemTask(packageId, action) {
20253
21669
  }
20254
21670
  function startEcosystemRecipeTask(recipeId) {
20255
21671
  if (!acceptingTasks) return null;
21672
+ if (activeTasks.size >= activeTaskLimit()) return null;
20256
21673
  const recipe = findEcosystemRecipe(recipeId);
20257
21674
  if (recipe === null) return null;
20258
21675
  if (!recipe.runnable) return null;
@@ -20415,7 +21832,7 @@ function installRuntimeShutdownSignalHandlers() {
20415
21832
  }
20416
21833
  installRuntimeShutdownSignalHandlers();
20417
21834
  logger.debug("Health endpoint loaded");
20418
- const Route$G = createFileRoute("/api/health")({
21835
+ const Route$F = createFileRoute("/api/health")({
20419
21836
  server: {
20420
21837
  handlers: {
20421
21838
  GET: async () => {
@@ -20428,18 +21845,18 @@ const Route$G = createFileRoute("/api/health")({
20428
21845
  }
20429
21846
  }
20430
21847
  });
20431
- async function readJsonBody$5(request) {
21848
+ async function readJsonBody$5(request2) {
20432
21849
  try {
20433
- const raw = await request.text();
21850
+ const raw = await request2.text();
20434
21851
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
20435
21852
  return { ok: true, value: JSON.parse(raw) };
20436
21853
  } catch {
20437
21854
  return { ok: false };
20438
21855
  }
20439
21856
  }
20440
- function readDeleteEvidenceFlag$1(request) {
21857
+ function readDeleteEvidenceFlag$1(request2) {
20441
21858
  try {
20442
- const value = new URL(request.url).searchParams.get("deleteEvidence");
21859
+ const value = new URL(request2.url).searchParams.get("deleteEvidence");
20443
21860
  switch (value) {
20444
21861
  case null:
20445
21862
  return { ok: true, value: void 0 };
@@ -20456,12 +21873,12 @@ function readDeleteEvidenceFlag$1(request) {
20456
21873
  return { ok: false };
20457
21874
  }
20458
21875
  }
20459
- const Route$F = createFileRoute("/api/groups")({
21876
+ const Route$E = createFileRoute("/api/groups")({
20460
21877
  server: {
20461
21878
  handlers: {
20462
21879
  GET: () => Response.json(listInspectorGroups()),
20463
- POST: async ({ request }) => {
20464
- const body = await readJsonBody$5(request);
21880
+ POST: async ({ request: request2 }) => {
21881
+ const body = await readJsonBody$5(request2);
20465
21882
  if (!body.ok) {
20466
21883
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
20467
21884
  }
@@ -20474,8 +21891,8 @@ const Route$F = createFileRoute("/api/groups")({
20474
21891
  }
20475
21892
  return Response.json(createInspectorGroup(parsed.data), { status: 201 });
20476
21893
  },
20477
- DELETE: ({ request }) => {
20478
- const deleteEvidence = readDeleteEvidenceFlag$1(request);
21894
+ DELETE: ({ request: request2 }) => {
21895
+ const deleteEvidence = readDeleteEvidenceFlag$1(request2);
20479
21896
  if (!deleteEvidence.ok) {
20480
21897
  return Response.json(
20481
21898
  { error: "deleteEvidence must be one of true, false, 1, or 0" },
@@ -20500,16 +21917,16 @@ const RuntimeConfigPatchSchema = object({
20500
21917
  }).strict().refine((v) => Object.keys(v).length > 0, {
20501
21918
  message: "At least one field must be provided"
20502
21919
  });
20503
- const Route$E = createFileRoute("/api/config")({
21920
+ const Route$D = createFileRoute("/api/config")({
20504
21921
  server: {
20505
21922
  handlers: {
20506
21923
  GET: () => {
20507
21924
  return Response.json(getConfig());
20508
21925
  },
20509
- PATCH: async ({ request }) => {
21926
+ PATCH: async ({ request: request2 }) => {
20510
21927
  let body;
20511
21928
  try {
20512
- body = await request.json();
21929
+ body = await request2.json();
20513
21930
  } catch {
20514
21931
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
20515
21932
  }
@@ -20614,11 +22031,11 @@ function parseFilters(url2) {
20614
22031
  source: sourceResult.success ? sourceResult.data : void 0
20615
22032
  };
20616
22033
  }
20617
- const Route$D = createFileRoute("/api/alerts")({
22034
+ const Route$C = createFileRoute("/api/alerts")({
20618
22035
  server: {
20619
22036
  handlers: {
20620
- GET: async ({ request }) => {
20621
- const url2 = new URL(request.url);
22037
+ GET: async ({ request: request2 }) => {
22038
+ const url2 = new URL(request2.url);
20622
22039
  const limitResult = parseBoundedIntegerParameter({
20623
22040
  value: url2.searchParams.get("limit"),
20624
22041
  parameter: "limit",
@@ -20648,16 +22065,16 @@ const Route$D = createFileRoute("/api/alerts")({
20648
22065
  }
20649
22066
  }
20650
22067
  });
20651
- async function readJsonBody$4(request) {
22068
+ async function readJsonBody$4(request2) {
20652
22069
  try {
20653
- const raw = await request.text();
22070
+ const raw = await request2.text();
20654
22071
  if (raw.trim().length === 0) return { ok: true, value: {} };
20655
22072
  return { ok: true, value: JSON.parse(raw) };
20656
22073
  } catch {
20657
22074
  return { ok: false };
20658
22075
  }
20659
22076
  }
20660
- const Route$C = createFileRoute("/api/runs/$runId")({
22077
+ const Route$B = createFileRoute("/api/runs/$runId")({
20661
22078
  server: {
20662
22079
  handlers: {
20663
22080
  GET: ({ params }) => {
@@ -20667,8 +22084,8 @@ const Route$C = createFileRoute("/api/runs/$runId")({
20667
22084
  }
20668
22085
  return Response.json(run.value);
20669
22086
  },
20670
- PATCH: async ({ params, request }) => {
20671
- const body = await readJsonBody$4(request);
22087
+ PATCH: async ({ params, request: request2 }) => {
22088
+ const body = await readJsonBody$4(request2);
20672
22089
  if (!body.ok) {
20673
22090
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
20674
22091
  }
@@ -21190,7 +22607,7 @@ function consumeProviderScan(scanId, selectionIds, now = Date.now()) {
21190
22607
  }
21191
22608
  return selected;
21192
22609
  }
21193
- const Route$B = createFileRoute("/api/providers/scan")({
22610
+ const Route$A = createFileRoute("/api/providers/scan")({
21194
22611
  server: {
21195
22612
  handlers: {
21196
22613
  POST: () => {
@@ -21236,12 +22653,12 @@ const ProviderScanImportRequestSchema = object({
21236
22653
  selectionIds: array(string().uuid()).min(1).max(100)
21237
22654
  });
21238
22655
  const ProviderFileImportSchema = union([string(), object({ providers: unknown() })]);
21239
- const Route$A = createFileRoute("/api/providers/import")({
22656
+ const Route$z = createFileRoute("/api/providers/import")({
21240
22657
  server: {
21241
22658
  handlers: {
21242
- POST: async ({ request }) => {
22659
+ POST: async ({ request: request2 }) => {
21243
22660
  try {
21244
- const rawBody = await request.text();
22661
+ const rawBody = await request2.text();
21245
22662
  let jsonContent;
21246
22663
  try {
21247
22664
  const parsedBody = JSON.parse(rawBody);
@@ -21308,7 +22725,7 @@ function exportResponse(json, filename) {
21308
22725
  }
21309
22726
  });
21310
22727
  }
21311
- const Route$z = createFileRoute("/api/providers/export")({
22728
+ const Route$y = createFileRoute("/api/providers/export")({
21312
22729
  server: {
21313
22730
  handlers: {
21314
22731
  GET: () => {
@@ -21317,8 +22734,8 @@ const Route$z = createFileRoute("/api/providers/export")({
21317
22734
  `agent-inspector-providers-safe-${Date.now()}.json`
21318
22735
  );
21319
22736
  },
21320
- POST: async ({ request }) => {
21321
- const body = await request.json().catch(() => null);
22737
+ POST: async ({ request: request2 }) => {
22738
+ const body = await request2.json().catch(() => null);
21322
22739
  const parsed = SecretExportRequestSchema.safeParse(body);
21323
22740
  if (!parsed.success) {
21324
22741
  return Response.json(
@@ -21351,7 +22768,7 @@ const ProviderUpdateSchema = object({
21351
22768
  modelMetadata: ProviderModelMetadataListSchema.optional(),
21352
22769
  source: _enum(["company", "personal"]).optional()
21353
22770
  });
21354
- const Route$y = createFileRoute("/api/providers/$providerId")({
22771
+ const Route$x = createFileRoute("/api/providers/$providerId")({
21355
22772
  server: {
21356
22773
  handlers: {
21357
22774
  GET: ({ params }) => {
@@ -21362,10 +22779,10 @@ const Route$y = createFileRoute("/api/providers/$providerId")({
21362
22779
  }
21363
22780
  return Response.json(redactProvider(provider));
21364
22781
  },
21365
- PUT: async ({ params, request }) => {
22782
+ PUT: async ({ params, request: request2 }) => {
21366
22783
  let body;
21367
22784
  try {
21368
- body = await request.json();
22785
+ body = await request2.json();
21369
22786
  } catch {
21370
22787
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
21371
22788
  }
@@ -21416,139 +22833,6 @@ const Route$y = createFileRoute("/api/providers/$providerId")({
21416
22833
  }
21417
22834
  }
21418
22835
  });
21419
- const INITIAL_STREAM_LOG_LIMIT = 100;
21420
- const UPDATE_COALESCE_MS = 50;
21421
- const sseClientLimiter = createCapacityLimiter(MAX_SSE_CLIENTS);
21422
- function streamIncludesBodies(url2) {
21423
- return url2.searchParams.get("compact") === "0";
21424
- }
21425
- function parseLastEventLogId(value) {
21426
- if (value === null) return void 0;
21427
- return parseLogId(value) ?? void 0;
21428
- }
21429
- function encodeSse(value, id) {
21430
- const idLine = id === void 0 ? "" : `id: ${String(id)}
21431
- `;
21432
- return new TextEncoder().encode(`${idLine}data: ${JSON.stringify(value)}
21433
-
21434
- `);
21435
- }
21436
- function createLogsStreamResponse(request, clientLimiter = sseClientLimiter) {
21437
- const lastEventIdHeader = request.headers.get("last-event-id");
21438
- const lastEventLogId = parseLastEventLogId(lastEventIdHeader);
21439
- if (lastEventIdHeader !== null && lastEventLogId === void 0) {
21440
- return Response.json({ error: "Invalid Last-Event-ID" }, { status: 400 });
21441
- }
21442
- const lease = clientLimiter.tryAcquire();
21443
- if (lease === null) {
21444
- return resourceLimitResponse({
21445
- error: `SSE client capacity reached. At most ${String(clientLimiter.capacity)} live clients are allowed; retry after a connection closes.`,
21446
- code: "sse-capacity-exceeded",
21447
- status: 503,
21448
- resource: "sse-clients",
21449
- limit: clientLimiter.capacity,
21450
- retryable: true,
21451
- retryAfterSeconds: 1
21452
- });
21453
- }
21454
- const url2 = new URL(request.url);
21455
- const sessionId = url2.searchParams.get("sessionId") ?? void 0;
21456
- const model = url2.searchParams.get("model") ?? void 0;
21457
- const includeBodies = streamIncludesBodies(url2);
21458
- let controllerRef = null;
21459
- let cleanedUp = false;
21460
- let updateTimer = null;
21461
- const pendingUpdates = /* @__PURE__ */ new Map();
21462
- const flushPendingUpdates = () => {
21463
- updateTimer = null;
21464
- const controller = controllerRef;
21465
- if (controller === null) return;
21466
- if (controller.desiredSize !== null && controller.desiredSize <= -16) {
21467
- controller.close();
21468
- cleanup();
21469
- return;
21470
- }
21471
- for (const log of pendingUpdates.values()) {
21472
- if (controller.desiredSize !== null && controller.desiredSize <= -16) {
21473
- controller.close();
21474
- cleanup();
21475
- return;
21476
- }
21477
- const payloadLog = includeBodies ? log : compactLogForList(log);
21478
- controller.enqueue(encodeSse({ type: "update", log: payloadLog }, log.id));
21479
- }
21480
- pendingUpdates.clear();
21481
- };
21482
- const unsubscribe = onLogUpdate((log) => {
21483
- if (controllerRef === null) return;
21484
- if (sessionId !== void 0 && getLogSessionId(log) !== sessionId) return;
21485
- if (model !== void 0 && log.model !== model) return;
21486
- pendingUpdates.set(log.id, log);
21487
- if (updateTimer === null) updateTimer = setTimeout(flushPendingUpdates, UPDATE_COALESCE_MS);
21488
- });
21489
- const cleanup = () => {
21490
- if (cleanedUp) return;
21491
- cleanedUp = true;
21492
- clearInterval(heartbeat);
21493
- if (updateTimer !== null) clearTimeout(updateTimer);
21494
- updateTimer = null;
21495
- pendingUpdates.clear();
21496
- unsubscribe();
21497
- lease.release();
21498
- controllerRef = null;
21499
- request.signal.removeEventListener("abort", cleanup);
21500
- };
21501
- const heartbeat = setInterval(() => {
21502
- if (controllerRef !== null) {
21503
- try {
21504
- controllerRef.enqueue(new TextEncoder().encode(": heartbeat\n\n"));
21505
- } catch {
21506
- cleanup();
21507
- }
21508
- }
21509
- }, 3e4);
21510
- request.signal.addEventListener("abort", cleanup, { once: true });
21511
- const stream = new ReadableStream({
21512
- async start(controller) {
21513
- controllerRef = controller;
21514
- try {
21515
- const result = await listLogsCursorPage({
21516
- sessionId,
21517
- model,
21518
- limit: INITIAL_STREAM_LOG_LIMIT,
21519
- includeBodies,
21520
- afterLogId: lastEventLogId,
21521
- anchor: lastEventLogId === void 0 ? "newest" : void 0
21522
- });
21523
- if (cleanedUp) return;
21524
- const logs = result.logs;
21525
- const newestLogId = logs.at(-1)?.id;
21526
- controller.enqueue(encodeSse({ type: "init", logs }, newestLogId));
21527
- } catch (error) {
21528
- cleanup();
21529
- controller.error(error);
21530
- }
21531
- },
21532
- cancel() {
21533
- cleanup();
21534
- }
21535
- });
21536
- return new Response(stream, {
21537
- headers: {
21538
- "Content-Type": "text/event-stream",
21539
- "Cache-Control": "no-cache",
21540
- Connection: "keep-alive",
21541
- "X-Accel-Buffering": "no"
21542
- }
21543
- });
21544
- }
21545
- const Route$x = createFileRoute("/api/logs/stream")({
21546
- server: {
21547
- handlers: {
21548
- GET: ({ request }) => createLogsStreamResponse(request)
21549
- }
21550
- }
21551
- });
21552
22836
  const DEFAULT_LOG_IMPORT_LIMITS = {
21553
22837
  maxCompressedBytes: 200 * 1024 * 1024,
21554
22838
  maxEntries: 1e4,
@@ -22150,8 +23434,8 @@ function declaredContentLength(value) {
22150
23434
  const parsed = Number.parseInt(value, 10);
22151
23435
  return Number.isSafeInteger(parsed) ? parsed : "invalid";
22152
23436
  }
22153
- async function readBoundedBody(request, maxBytes) {
22154
- const declaredBytes = declaredContentLength(request.headers.get("content-length"));
23437
+ async function readBoundedBody(request2, maxBytes) {
23438
+ const declaredBytes = declaredContentLength(request2.headers.get("content-length"));
22155
23439
  if (declaredBytes === "invalid") {
22156
23440
  return { ok: false, status: 400, message: "Invalid Content-Length header" };
22157
23441
  }
@@ -22162,7 +23446,7 @@ async function readBoundedBody(request, maxBytes) {
22162
23446
  message: `Import request exceeds maximum size of ${String(maxBytes)} bytes.`
22163
23447
  };
22164
23448
  }
22165
- const body = request.body;
23449
+ const body = request2.body;
22166
23450
  if (body === null) {
22167
23451
  return { ok: false, status: 400, message: "Expected multipart form data" };
22168
23452
  }
@@ -22198,14 +23482,14 @@ async function readBoundedBody(request, maxBytes) {
22198
23482
  function isUploadedFile(value) {
22199
23483
  return value instanceof File;
22200
23484
  }
22201
- async function readBoundedLogImportUpload(request, options = {}) {
22202
- const contentType = request.headers.get("content-type");
23485
+ async function readBoundedLogImportUpload(request2, options = {}) {
23486
+ const contentType = request2.headers.get("content-type");
22203
23487
  if (contentType === null || !contentType.toLowerCase().startsWith("multipart/form-data")) {
22204
23488
  return { ok: false, status: 400, message: "Expected multipart form data" };
22205
23489
  }
22206
23490
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_LOG_IMPORT_LIMITS.maxCompressedBytes;
22207
23491
  const maxRequestBytes = options.maxRequestBytes ?? MAX_LOG_IMPORT_REQUEST_BYTES;
22208
- const body = await readBoundedBody(request, maxRequestBytes);
23492
+ const body = await readBoundedBody(request2, maxRequestBytes);
22209
23493
  if (!body.ok) return body;
22210
23494
  let formData;
22211
23495
  try {
@@ -22235,8 +23519,8 @@ async function readBoundedLogImportUpload(request, options = {}) {
22235
23519
  const Route$w = createFileRoute("/api/logs/import")({
22236
23520
  server: {
22237
23521
  handlers: {
22238
- POST: async ({ request }) => {
22239
- const upload = await readBoundedLogImportUpload(request);
23522
+ POST: async ({ request: request2 }) => {
23523
+ const upload = await readBoundedLogImportUpload(request2);
22240
23524
  if (!upload.ok) {
22241
23525
  return Response.json({ error: upload.message }, { status: upload.status });
22242
23526
  }
@@ -22501,7 +23785,11 @@ async function searchOpenClawGateway(query, project) {
22501
23785
  sessionKey: getOpenClawSessionKey()
22502
23786
  })
22503
23787
  },
22504
- { bypass: true }
23788
+ {
23789
+ bypass: true,
23790
+ deadlineMs: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataDeadlineMs,
23791
+ responseBytes: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataResponseBytes
23792
+ }
22505
23793
  );
22506
23794
  if (!fetchResult.ok) return { results: [], warning: fetchResult.reason };
22507
23795
  const response = fetchResult.response;
@@ -22704,7 +23992,11 @@ async function promoteToOpenClaw(candidate) {
22704
23992
  headers: authHeaders(),
22705
23993
  body: JSON.stringify(buildOpenClawPayload(candidate))
22706
23994
  },
22707
- { bypass: true }
23995
+ {
23996
+ bypass: true,
23997
+ deadlineMs: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataDeadlineMs,
23998
+ responseBytes: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataResponseBytes
23999
+ }
22708
24000
  );
22709
24001
  if (!fetchResult.ok) return { success: false, error: fetchResult.reason };
22710
24002
  const response = fetchResult.response;
@@ -22736,7 +24028,11 @@ async function searchOpenClaw(query, project) {
22736
24028
  method: "GET",
22737
24029
  headers: authHeaders()
22738
24030
  },
22739
- { bypass: true }
24031
+ {
24032
+ bypass: true,
24033
+ deadlineMs: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataDeadlineMs,
24034
+ responseBytes: DEFAULT_RUNTIME_BUDGET_POLICY.upstream.metadataResponseBytes
24035
+ }
22740
24036
  );
22741
24037
  if (!fetchResult.ok) return { results: [], warning: fetchResult.reason };
22742
24038
  const response = fetchResult.response;
@@ -22776,8 +24072,8 @@ function bodySearchQuery(value) {
22776
24072
  const Route$u = createFileRoute("/api/knowledge/search")({
22777
24073
  server: {
22778
24074
  handlers: {
22779
- GET: async ({ request }) => {
22780
- const url2 = new URL(request.url);
24075
+ GET: async ({ request: request2 }) => {
24076
+ const url2 = new URL(request2.url);
22781
24077
  const query = url2.searchParams.get("q") ?? "";
22782
24078
  const project = url2.searchParams.get("project") ?? void 0;
22783
24079
  if (query === "") {
@@ -22786,10 +24082,10 @@ const Route$u = createFileRoute("/api/knowledge/search")({
22786
24082
  if (query.length > MAX_SEARCH_QUERY_CHARS) return searchLimitResponse();
22787
24083
  return Response.json(await searchOpenClaw(query, project));
22788
24084
  },
22789
- POST: async ({ request }) => {
24085
+ POST: async ({ request: request2 }) => {
22790
24086
  let body;
22791
24087
  try {
22792
- body = await request.json();
24088
+ body = await request2.json();
22793
24089
  } catch {
22794
24090
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
22795
24091
  }
@@ -22809,8 +24105,8 @@ const Route$u = createFileRoute("/api/knowledge/search")({
22809
24105
  const Route$t = createFileRoute("/api/knowledge/project-context")({
22810
24106
  server: {
22811
24107
  handlers: {
22812
- GET: async ({ request }) => {
22813
- const url2 = new URL(request.url);
24108
+ GET: async ({ request: request2 }) => {
24109
+ const url2 = new URL(request2.url);
22814
24110
  const project = url2.searchParams.get("project") ?? "";
22815
24111
  if (project === "") {
22816
24112
  return Response.json({ error: "Missing project search parameter" }, { status: 400 });
@@ -22845,18 +24141,18 @@ const Route$r = createFileRoute("/api/instances/$name")({
22845
24141
  }
22846
24142
  }
22847
24143
  });
22848
- async function readJsonBody$3(request) {
24144
+ async function readJsonBody$3(request2) {
22849
24145
  try {
22850
- const raw = await request.text();
24146
+ const raw = await request2.text();
22851
24147
  if (raw.trim().length === 0) return { ok: true, value: {} };
22852
24148
  return { ok: true, value: JSON.parse(raw) };
22853
24149
  } catch {
22854
24150
  return { ok: false };
22855
24151
  }
22856
24152
  }
22857
- function readDeleteEvidenceFlag(request) {
24153
+ function readDeleteEvidenceFlag(request2) {
22858
24154
  try {
22859
- const value = new URL(request.url).searchParams.get("deleteEvidence");
24155
+ const value = new URL(request2.url).searchParams.get("deleteEvidence");
22860
24156
  switch (value) {
22861
24157
  case null:
22862
24158
  return { ok: true, value: void 0 };
@@ -22883,8 +24179,8 @@ const Route$q = createFileRoute("/api/groups/$groupId")({
22883
24179
  }
22884
24180
  return Response.json(group.value);
22885
24181
  },
22886
- PATCH: async ({ params, request }) => {
22887
- const body = await readJsonBody$3(request);
24182
+ PATCH: async ({ params, request: request2 }) => {
24183
+ const body = await readJsonBody$3(request2);
22888
24184
  if (!body.ok) {
22889
24185
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
22890
24186
  }
@@ -22901,8 +24197,8 @@ const Route$q = createFileRoute("/api/groups/$groupId")({
22901
24197
  }
22902
24198
  return Response.json(updated.value);
22903
24199
  },
22904
- DELETE: ({ params, request }) => {
22905
- const deleteEvidence = readDeleteEvidenceFlag(request);
24200
+ DELETE: ({ params, request: request2 }) => {
24201
+ const deleteEvidence = readDeleteEvidenceFlag(request2);
22906
24202
  if (!deleteEvidence.ok) {
22907
24203
  return Response.json(
22908
24204
  { error: "deleteEvidence must be one of true, false, 1, or 0" },
@@ -22957,8 +24253,8 @@ const Route$n = createFileRoute("/api/config/paths")({
22957
24253
  const Route$m = createFileRoute("/api/alerts/summary")({
22958
24254
  server: {
22959
24255
  handlers: {
22960
- GET: async ({ request }) => {
22961
- const url2 = new URL(request.url);
24256
+ GET: async ({ request: request2 }) => {
24257
+ const url2 = new URL(request2.url);
22962
24258
  const scanLimitResult = parseBoundedIntegerParameter({
22963
24259
  value: url2.searchParams.get("scanLimit"),
22964
24260
  parameter: "scanLimit",
@@ -22977,9 +24273,9 @@ const Route$m = createFileRoute("/api/alerts/summary")({
22977
24273
  }
22978
24274
  }
22979
24275
  });
22980
- async function readJsonBody$2(request) {
24276
+ async function readJsonBody$2(request2) {
22981
24277
  try {
22982
- const raw = await request.text();
24278
+ const raw = await request2.text();
22983
24279
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
22984
24280
  return { ok: true, value: JSON.parse(raw) };
22985
24281
  } catch {
@@ -22996,8 +24292,8 @@ const Route$l = createFileRoute("/api/runs/$runId/evidence")({
22996
24292
  }
22997
24293
  return Response.json(evidence.value);
22998
24294
  },
22999
- POST: async ({ params, request }) => {
23000
- const body = await readJsonBody$2(request);
24295
+ POST: async ({ params, request: request2 }) => {
24296
+ const body = await readJsonBody$2(request2);
23001
24297
  if (!body.ok) {
23002
24298
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
23003
24299
  }
@@ -23011,7 +24307,7 @@ const Route$l = createFileRoute("/api/runs/$runId/evidence")({
23011
24307
  const exported = await exportInspectorRunEvidence(
23012
24308
  params.runId,
23013
24309
  parsed.data,
23014
- new URL(request.url).origin
24310
+ new URL(request2.url).origin
23015
24311
  );
23016
24312
  if (!exported.ok) {
23017
24313
  return Response.json({ error: exported.message }, { status: exported.status });
@@ -23919,12 +25215,12 @@ async function readRegistryJson(response) {
23919
25215
  const Route$j = createFileRoute("/api/providers/$providerId/model-metadata")({
23920
25216
  server: {
23921
25217
  handlers: {
23922
- POST: async ({ params, request }) => {
25218
+ POST: async ({ params, request: request2 }) => {
23923
25219
  const provider = getProvider(params.providerId);
23924
25220
  if (provider === void 0) {
23925
25221
  return Response.json({ error: "Provider not found" }, { status: 404 });
23926
25222
  }
23927
- const parsed = RefreshModelMetadataInputSchema.safeParse(await request.json());
25223
+ const parsed = RefreshModelMetadataInputSchema.safeParse(await request2.json());
23928
25224
  if (!parsed.success) {
23929
25225
  return Response.json({ error: parsed.error.message }, { status: 400 });
23930
25226
  }
@@ -23958,9 +25254,16 @@ const Route$j = createFileRoute("/api/providers/$providerId/model-metadata")({
23958
25254
  }
23959
25255
  let response;
23960
25256
  try {
23961
- const fetchResult = await safeFetch(sourceUrl, {
23962
- headers: { Accept: "application/json" }
23963
- });
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
+ );
23964
25267
  if (!fetchResult.ok) {
23965
25268
  return Response.json(
23966
25269
  { error: `Model metadata URL blocked: ${fetchResult.reason}` },
@@ -24034,14 +25337,14 @@ const ReplayRequestSchema = object({
24034
25337
  const Route$i = createFileRoute("/api/logs/$id/replay")({
24035
25338
  server: {
24036
25339
  handlers: {
24037
- POST: async ({ params, request }) => {
25340
+ POST: async ({ params, request: request2 }) => {
24038
25341
  const id = parseLogId(params.id);
24039
25342
  if (id === null) {
24040
25343
  return Response.json({ error: "Invalid log ID" }, { status: 400 });
24041
25344
  }
24042
25345
  let replayRequest;
24043
25346
  try {
24044
- const raw = await request.json();
25347
+ const raw = await request2.json();
24045
25348
  const parsed = ReplayRequestSchema.safeParse(raw);
24046
25349
  if (!parsed.success) {
24047
25350
  return Response.json({ error: "Invalid request body" }, { status: 400 });
@@ -24099,7 +25402,11 @@ const Route$i = createFileRoute("/api/logs/$id/replay")({
24099
25402
  headers,
24100
25403
  body: modifiedBody
24101
25404
  },
24102
- { 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
+ }
24103
25410
  );
24104
25411
  if (!fetchResult.ok) {
24105
25412
  return Response.json({
@@ -24353,12 +25660,12 @@ function parseIntegerParam(value, fallback) {
24353
25660
  const Route$g = createFileRoute("/api/logs/$id/body")({
24354
25661
  server: {
24355
25662
  handlers: {
24356
- GET: async ({ params, request }) => {
25663
+ GET: async ({ params, request: request2 }) => {
24357
25664
  const id = parseLogId(params.id);
24358
25665
  if (id === null) {
24359
25666
  return Response.json({ error: "Invalid log ID" }, { status: 400 });
24360
25667
  }
24361
- const url2 = new URL(request.url);
25668
+ const url2 = new URL(request2.url);
24362
25669
  const partResult = LogBodyPartSchema.safeParse(url2.searchParams.get("part"));
24363
25670
  if (!partResult.success) {
24364
25671
  return Response.json({ error: "Invalid body part" }, { status: 400 });
@@ -24400,7 +25707,7 @@ object({
24400
25707
  const Route$f = createFileRoute("/api/knowledge/candidates/$candidateId")({
24401
25708
  server: {
24402
25709
  handlers: {
24403
- PATCH: async ({ params, request }) => {
25710
+ PATCH: async ({ params, request: request2 }) => {
24404
25711
  const existing = getCandidate(params.candidateId);
24405
25712
  if (existing === null) {
24406
25713
  return Response.json({ error: "Knowledge candidate not found" }, { status: 404 });
@@ -24411,7 +25718,7 @@ const Route$f = createFileRoute("/api/knowledge/candidates/$candidateId")({
24411
25718
  { status: 409 }
24412
25719
  );
24413
25720
  }
24414
- const raw = await request.json().catch(() => null);
25721
+ const raw = await request2.json().catch(() => null);
24415
25722
  const parsed = CandidateUpdateSchema.safeParse(raw);
24416
25723
  if (!parsed.success) {
24417
25724
  return Response.json({ error: parsed.error.message }, { status: 400 });
@@ -24428,8 +25735,8 @@ const Route$f = createFileRoute("/api/knowledge/candidates/$candidateId")({
24428
25735
  const Route$e = createFileRoute("/api/instances/$name/stop")({
24429
25736
  server: {
24430
25737
  handlers: {
24431
- POST: async ({ params, request }) => {
24432
- const body = await parseInstanceBody(request, InstanceEmptyRequestSchema);
25738
+ POST: async ({ params, request: request2 }) => {
25739
+ const body = await parseInstanceBody(request2, InstanceEmptyRequestSchema);
24433
25740
  if (!body.ok) return body.response;
24434
25741
  const selfError = selfLifecycleError(params.name);
24435
25742
  if (selfError !== null) return instanceErrorResponse(selfError);
@@ -24441,8 +25748,8 @@ const Route$e = createFileRoute("/api/instances/$name/stop")({
24441
25748
  const Route$d = createFileRoute("/api/instances/$name/start")({
24442
25749
  server: {
24443
25750
  handlers: {
24444
- POST: async ({ params, request }) => {
24445
- const body = await parseInstanceBody(request, InstanceStartRequestSchema);
25751
+ POST: async ({ params, request: request2 }) => {
25752
+ const body = await parseInstanceBody(request2, InstanceStartRequestSchema);
24446
25753
  if (!body.ok) return body.response;
24447
25754
  const result = await startNamedInstance(params.name, body.value);
24448
25755
  const status = result.kind === "success" && result.value.outcome === "created" ? 201 : 200;
@@ -24454,8 +25761,8 @@ const Route$d = createFileRoute("/api/instances/$name/start")({
24454
25761
  const Route$c = createFileRoute("/api/instances/$name/restart")({
24455
25762
  server: {
24456
25763
  handlers: {
24457
- POST: async ({ params, request }) => {
24458
- const body = await parseInstanceBody(request, InstanceStartRequestSchema);
25764
+ POST: async ({ params, request: request2 }) => {
25765
+ const body = await parseInstanceBody(request2, InstanceStartRequestSchema);
24459
25766
  if (!body.ok) return body.response;
24460
25767
  const selfError = selfLifecycleError(params.name);
24461
25768
  if (selfError !== null) return instanceErrorResponse(selfError);
@@ -24471,9 +25778,9 @@ const Route$b = createFileRoute("/api/instances/$name/connection")({
24471
25778
  }
24472
25779
  }
24473
25780
  });
24474
- async function readJsonBody$1(request) {
25781
+ async function readJsonBody$1(request2) {
24475
25782
  try {
24476
- const raw = await request.text();
25783
+ const raw = await request2.text();
24477
25784
  if (raw.trim().length === 0) return { ok: true, value: {} };
24478
25785
  return { ok: true, value: JSON.parse(raw) };
24479
25786
  } catch {
@@ -24490,8 +25797,8 @@ const Route$a = createFileRoute("/api/groups/$groupId/sessions")({
24490
25797
  }
24491
25798
  return Response.json(sessions2.value);
24492
25799
  },
24493
- POST: async ({ params, request }) => {
24494
- const body = await readJsonBody$1(request);
25800
+ POST: async ({ params, request: request2 }) => {
25801
+ const body = await readJsonBody$1(request2);
24495
25802
  if (!body.ok) {
24496
25803
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
24497
25804
  }
@@ -24511,9 +25818,9 @@ const Route$a = createFileRoute("/api/groups/$groupId/sessions")({
24511
25818
  }
24512
25819
  }
24513
25820
  });
24514
- async function readJsonBody(request) {
25821
+ async function readJsonBody(request2) {
24515
25822
  try {
24516
- const raw = await request.text();
25823
+ const raw = await request2.text();
24517
25824
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
24518
25825
  return { ok: true, value: JSON.parse(raw) };
24519
25826
  } catch {
@@ -24530,8 +25837,8 @@ const Route$9 = createFileRoute("/api/groups/$groupId/evidence")({
24530
25837
  }
24531
25838
  return Response.json(evidence.value);
24532
25839
  },
24533
- POST: async ({ params, request }) => {
24534
- const body = await readJsonBody(request);
25840
+ POST: async ({ params, request: request2 }) => {
25841
+ const body = await readJsonBody(request2);
24535
25842
  if (!body.ok) {
24536
25843
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
24537
25844
  }
@@ -24545,7 +25852,7 @@ const Route$9 = createFileRoute("/api/groups/$groupId/evidence")({
24545
25852
  const exported = await exportInspectorGroupEvidence(
24546
25853
  params.groupId,
24547
25854
  parsed.data,
24548
- new URL(request.url).origin
25855
+ new URL(request2.url).origin
24549
25856
  );
24550
25857
  if (!exported.ok) {
24551
25858
  return Response.json({ error: exported.message }, { status: exported.status });
@@ -24885,14 +26192,14 @@ async function logModelResults(displayName, providerName, providerSessionId, ant
24885
26192
  const Route$7 = createFileRoute("/api/providers/$providerId/test/log")({
24886
26193
  server: {
24887
26194
  handlers: {
24888
- POST: async ({ params, request }) => {
26195
+ POST: async ({ params, request: request2 }) => {
24889
26196
  const provider = getProvider(params.providerId);
24890
26197
  if (!provider) {
24891
26198
  return Response.json({ error: "Provider not found" }, { status: 404 });
24892
26199
  }
24893
26200
  let body;
24894
26201
  try {
24895
- body = await request.json();
26202
+ body = await request2.json();
24896
26203
  } catch {
24897
26204
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
24898
26205
  }
@@ -25210,12 +26517,12 @@ const NO_STORE_HEADERS = { "Cache-Control": "no-store" };
25210
26517
  function noStoreJson(body, status = 200) {
25211
26518
  return Response.json(body, { status, headers: NO_STORE_HEADERS });
25212
26519
  }
25213
- async function readExecutionRequest(request) {
25214
- const body = await request.json().catch(() => null);
26520
+ async function readExecutionRequest(request2) {
26521
+ const body = await request2.json().catch(() => null);
25215
26522
  return EcosystemExecutionRequestSchema.safeParse(body);
25216
26523
  }
25217
- async function handleEcosystemPackageExecution(request, packageId, action, startTask = startEcosystemTask) {
25218
- const parsed = await readExecutionRequest(request);
26524
+ async function handleEcosystemPackageExecution(request2, packageId, action, startTask = startEcosystemTask) {
26525
+ const parsed = await readExecutionRequest(request2);
25219
26526
  if (!parsed.success) {
25220
26527
  auditEcosystemExecution("confirmation_rejected", action, null, "invalid_request");
25221
26528
  return noStoreJson({ error: "Explicit local command confirmation is required" }, 400);
@@ -25247,9 +26554,9 @@ async function handleEcosystemPackageExecution(request, packageId, action, start
25247
26554
  auditEcosystemExecution("execution_started", action, plan.targetId, "none");
25248
26555
  return noStoreJson({ task });
25249
26556
  }
25250
- async function handleEcosystemRecipeExecution(request, recipeId, startTask = startEcosystemRecipeTask) {
26557
+ async function handleEcosystemRecipeExecution(request2, recipeId, startTask = startEcosystemRecipeTask) {
25251
26558
  const action = "recipe";
25252
- const parsed = await readExecutionRequest(request);
26559
+ const parsed = await readExecutionRequest(request2);
25253
26560
  if (!parsed.success) {
25254
26561
  auditEcosystemExecution("confirmation_rejected", action, null, "invalid_request");
25255
26562
  return noStoreJson({ error: "Explicit local command confirmation is required" }, 400);
@@ -25284,149 +26591,159 @@ async function handleEcosystemRecipeExecution(request, recipeId, startTask = sta
25284
26591
  const Route$4 = createFileRoute("/api/ecosystem/recipes/$recipeId/run")({
25285
26592
  server: {
25286
26593
  handlers: {
25287
- POST: ({ params, request }) => handleEcosystemRecipeExecution(request, params.recipeId)
26594
+ POST: ({ params, request: request2 }) => handleEcosystemRecipeExecution(request2, params.recipeId)
25288
26595
  }
25289
26596
  }
25290
26597
  });
25291
26598
  const Route$3 = createFileRoute("/api/ecosystem/packages/$packageId/upgrade")({
25292
26599
  server: {
25293
26600
  handlers: {
25294
- POST: ({ params, request }) => handleEcosystemPackageExecution(request, params.packageId, "upgrade")
26601
+ POST: ({ params, request: request2 }) => handleEcosystemPackageExecution(request2, params.packageId, "upgrade")
25295
26602
  }
25296
26603
  }
25297
26604
  });
25298
26605
  const Route$2 = createFileRoute("/api/ecosystem/packages/$packageId/runner-presets")({
25299
26606
  server: {
25300
26607
  handlers: {
25301
- POST: ({ params, request }) => handleEcosystemPackageExecution(request, params.packageId, "runner-presets")
26608
+ POST: ({ params, request: request2 }) => handleEcosystemPackageExecution(request2, params.packageId, "runner-presets")
25302
26609
  }
25303
26610
  }
25304
26611
  });
25305
26612
  const Route$1 = createFileRoute("/api/ecosystem/packages/$packageId/install")({
25306
26613
  server: {
25307
26614
  handlers: {
25308
- POST: ({ params, request }) => handleEcosystemPackageExecution(request, params.packageId, "install")
26615
+ POST: ({ params, request: request2 }) => handleEcosystemPackageExecution(request2, params.packageId, "install")
25309
26616
  }
25310
26617
  }
25311
26618
  });
25312
26619
  const Route = createFileRoute("/api/ecosystem/packages/$packageId/help")({
25313
26620
  server: {
25314
26621
  handlers: {
25315
- POST: ({ params, request }) => handleEcosystemPackageExecution(request, params.packageId, "help")
26622
+ POST: ({ params, request: request2 }) => handleEcosystemPackageExecution(request2, params.packageId, "help")
25316
26623
  }
25317
26624
  }
25318
26625
  });
25319
- const ReadyzRoute = Route$T.update({
26626
+ const ReadyzRoute = Route$V.update({
25320
26627
  id: "/readyz",
25321
26628
  path: "/readyz",
25322
- getParentRoute: () => Route$U
26629
+ getParentRoute: () => Route$W
26630
+ });
26631
+ const MetricsRoute = Route$T.update({
26632
+ id: "/metrics",
26633
+ path: "/metrics",
26634
+ getParentRoute: () => Route$W
25323
26635
  });
25324
26636
  const LivezRoute = Route$S.update({
25325
26637
  id: "/livez",
25326
26638
  path: "/livez",
25327
- getParentRoute: () => Route$U
26639
+ getParentRoute: () => Route$W
25328
26640
  });
25329
26641
  const SplatRoute = Route$R.update({
25330
26642
  id: "/$",
25331
26643
  path: "/$",
25332
- getParentRoute: () => Route$U
26644
+ getParentRoute: () => Route$W
25333
26645
  });
25334
26646
  const ProxySplatRoute = Route$Q.update({
25335
26647
  id: "/proxy/$",
25336
26648
  path: "/proxy/$",
25337
- 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
25338
26655
  });
25339
- const ApiSessionsRoute = Route$P.update({
26656
+ const ApiSessionsRoute = Route$O.update({
25340
26657
  id: "/api/sessions",
25341
26658
  path: "/api/sessions",
25342
- getParentRoute: () => Route$U
26659
+ getParentRoute: () => Route$W
25343
26660
  });
25344
- const ApiRuntimeRoute = Route$O.update({
26661
+ const ApiRuntimeRoute = Route$N.update({
25345
26662
  id: "/api/runtime",
25346
26663
  path: "/api/runtime",
25347
- getParentRoute: () => Route$U
26664
+ getParentRoute: () => Route$W
25348
26665
  });
25349
- const ApiRunsRoute = Route$N.update({
26666
+ const ApiRunsRoute = Route$M.update({
25350
26667
  id: "/api/runs",
25351
26668
  path: "/api/runs",
25352
- getParentRoute: () => Route$U
26669
+ getParentRoute: () => Route$W
25353
26670
  });
25354
- const ApiProvidersRoute = Route$M.update({
26671
+ const ApiProvidersRoute = Route$L.update({
25355
26672
  id: "/api/providers",
25356
26673
  path: "/api/providers",
25357
- getParentRoute: () => Route$U
26674
+ getParentRoute: () => Route$W
25358
26675
  });
25359
- const ApiPiAgentRoute = Route$L.update({
26676
+ const ApiPiAgentRoute = Route$K.update({
25360
26677
  id: "/api/pi-agent",
25361
26678
  path: "/api/pi-agent",
25362
- getParentRoute: () => Route$U
26679
+ getParentRoute: () => Route$W
25363
26680
  });
25364
- const ApiModelsRoute = Route$K.update({
26681
+ const ApiModelsRoute = Route$J.update({
25365
26682
  id: "/api/models",
25366
26683
  path: "/api/models",
25367
- getParentRoute: () => Route$U
26684
+ getParentRoute: () => Route$W
25368
26685
  });
25369
- const ApiMcpRoute = Route$J.update({
26686
+ const ApiMcpRoute = Route$I.update({
25370
26687
  id: "/api/mcp",
25371
26688
  path: "/api/mcp",
25372
- getParentRoute: () => Route$U
26689
+ getParentRoute: () => Route$W
25373
26690
  });
25374
- const ApiLogsRoute = Route$I.update({
26691
+ const ApiLogsRoute = Route$H.update({
25375
26692
  id: "/api/logs",
25376
26693
  path: "/api/logs",
25377
- getParentRoute: () => Route$U
26694
+ getParentRoute: () => Route$W
25378
26695
  });
25379
- const ApiInstancesRoute = Route$H.update({
26696
+ const ApiInstancesRoute = Route$G.update({
25380
26697
  id: "/api/instances",
25381
26698
  path: "/api/instances",
25382
- getParentRoute: () => Route$U
26699
+ getParentRoute: () => Route$W
25383
26700
  });
25384
- const ApiHealthRoute = Route$G.update({
26701
+ const ApiHealthRoute = Route$F.update({
25385
26702
  id: "/api/health",
25386
26703
  path: "/api/health",
25387
- getParentRoute: () => Route$U
26704
+ getParentRoute: () => Route$W
25388
26705
  });
25389
- const ApiGroupsRoute = Route$F.update({
26706
+ const ApiGroupsRoute = Route$E.update({
25390
26707
  id: "/api/groups",
25391
26708
  path: "/api/groups",
25392
- getParentRoute: () => Route$U
26709
+ getParentRoute: () => Route$W
25393
26710
  });
25394
- const ApiConfigRoute = Route$E.update({
26711
+ const ApiConfigRoute = Route$D.update({
25395
26712
  id: "/api/config",
25396
26713
  path: "/api/config",
25397
- getParentRoute: () => Route$U
26714
+ getParentRoute: () => Route$W
25398
26715
  });
25399
- const ApiAlertsRoute = Route$D.update({
26716
+ const ApiAlertsRoute = Route$C.update({
25400
26717
  id: "/api/alerts",
25401
26718
  path: "/api/alerts",
25402
- getParentRoute: () => Route$U
26719
+ getParentRoute: () => Route$W
25403
26720
  });
25404
- const ApiRunsRunIdRoute = Route$C.update({
26721
+ const ApiRunsRunIdRoute = Route$B.update({
25405
26722
  id: "/$runId",
25406
26723
  path: "/$runId",
25407
26724
  getParentRoute: () => ApiRunsRoute
25408
26725
  });
25409
- const ApiProvidersScanRoute = Route$B.update({
26726
+ const ApiProvidersScanRoute = Route$A.update({
25410
26727
  id: "/scan",
25411
26728
  path: "/scan",
25412
26729
  getParentRoute: () => ApiProvidersRoute
25413
26730
  });
25414
- const ApiProvidersImportRoute = Route$A.update({
26731
+ const ApiProvidersImportRoute = Route$z.update({
25415
26732
  id: "/import",
25416
26733
  path: "/import",
25417
26734
  getParentRoute: () => ApiProvidersRoute
25418
26735
  });
25419
- const ApiProvidersExportRoute = Route$z.update({
26736
+ const ApiProvidersExportRoute = Route$y.update({
25420
26737
  id: "/export",
25421
26738
  path: "/export",
25422
26739
  getParentRoute: () => ApiProvidersRoute
25423
26740
  });
25424
- const ApiProvidersProviderIdRoute = Route$y.update({
26741
+ const ApiProvidersProviderIdRoute = Route$x.update({
25425
26742
  id: "/$providerId",
25426
26743
  path: "/$providerId",
25427
26744
  getParentRoute: () => ApiProvidersRoute
25428
26745
  });
25429
- const ApiLogsStreamRoute = Route$x.update({
26746
+ const ApiLogsStreamRoute = Route$U.update({
25430
26747
  id: "/stream",
25431
26748
  path: "/stream",
25432
26749
  getParentRoute: () => ApiLogsRoute
@@ -25444,17 +26761,17 @@ const ApiLogsIdRoute = Route$v.update({
25444
26761
  const ApiKnowledgeSearchRoute = Route$u.update({
25445
26762
  id: "/api/knowledge/search",
25446
26763
  path: "/api/knowledge/search",
25447
- getParentRoute: () => Route$U
26764
+ getParentRoute: () => Route$W
25448
26765
  });
25449
26766
  const ApiKnowledgeProjectContextRoute = Route$t.update({
25450
26767
  id: "/api/knowledge/project-context",
25451
26768
  path: "/api/knowledge/project-context",
25452
- getParentRoute: () => Route$U
26769
+ getParentRoute: () => Route$W
25453
26770
  });
25454
26771
  const ApiKnowledgeCandidatesRoute = Route$s.update({
25455
26772
  id: "/api/knowledge/candidates",
25456
26773
  path: "/api/knowledge/candidates",
25457
- getParentRoute: () => Route$U
26774
+ getParentRoute: () => Route$W
25458
26775
  });
25459
26776
  const ApiInstancesNameRoute = Route$r.update({
25460
26777
  id: "/$name",
@@ -25469,12 +26786,12 @@ const ApiGroupsGroupIdRoute = Route$q.update({
25469
26786
  const ApiEcosystemTasksRoute = Route$p.update({
25470
26787
  id: "/api/ecosystem/tasks",
25471
26788
  path: "/api/ecosystem/tasks",
25472
- getParentRoute: () => Route$U
26789
+ getParentRoute: () => Route$W
25473
26790
  });
25474
26791
  const ApiEcosystemPackagesRoute = Route$o.update({
25475
26792
  id: "/api/ecosystem/packages",
25476
26793
  path: "/api/ecosystem/packages",
25477
- getParentRoute: () => Route$U
26794
+ getParentRoute: () => Route$W
25478
26795
  });
25479
26796
  const ApiConfigPathsRoute = Route$n.update({
25480
26797
  id: "/paths",
@@ -25564,7 +26881,7 @@ const ApiProvidersProviderIdTestLogRoute = Route$7.update({
25564
26881
  const ApiKnowledgeSessionsSessionIdCandidatesRoute = Route$6.update({
25565
26882
  id: "/api/knowledge/sessions/$sessionId/candidates",
25566
26883
  path: "/api/knowledge/sessions/$sessionId/candidates",
25567
- getParentRoute: () => Route$U
26884
+ getParentRoute: () => Route$W
25568
26885
  });
25569
26886
  const ApiKnowledgeCandidatesCandidateIdPromoteRoute = Route$5.update({
25570
26887
  id: "/promote",
@@ -25574,7 +26891,7 @@ const ApiKnowledgeCandidatesCandidateIdPromoteRoute = Route$5.update({
25574
26891
  const ApiEcosystemRecipesRecipeIdRunRoute = Route$4.update({
25575
26892
  id: "/api/ecosystem/recipes/$recipeId/run",
25576
26893
  path: "/api/ecosystem/recipes/$recipeId/run",
25577
- getParentRoute: () => Route$U
26894
+ getParentRoute: () => Route$W
25578
26895
  });
25579
26896
  const ApiEcosystemPackagesPackageIdUpgradeRoute = Route$3.update({
25580
26897
  id: "/$packageId/upgrade",
@@ -25704,6 +27021,7 @@ const ApiKnowledgeCandidatesRouteWithChildren = ApiKnowledgeCandidatesRoute._add
25704
27021
  const rootRouteChildren = {
25705
27022
  SplatRoute,
25706
27023
  LivezRoute,
27024
+ MetricsRoute,
25707
27025
  ReadyzRoute,
25708
27026
  ApiAlertsRoute: ApiAlertsRouteWithChildren,
25709
27027
  ApiConfigRoute: ApiConfigRouteWithChildren,
@@ -25718,6 +27036,7 @@ const rootRouteChildren = {
25718
27036
  ApiRunsRoute: ApiRunsRouteWithChildren,
25719
27037
  ApiRuntimeRoute,
25720
27038
  ApiSessionsRoute,
27039
+ ApiStorageRoute,
25721
27040
  ProxySplatRoute,
25722
27041
  ApiEcosystemPackagesRoute: ApiEcosystemPackagesRouteWithChildren,
25723
27042
  ApiEcosystemTasksRoute: ApiEcosystemTasksRouteWithChildren,
@@ -25727,7 +27046,7 @@ const rootRouteChildren = {
25727
27046
  ApiEcosystemRecipesRecipeIdRunRoute,
25728
27047
  ApiKnowledgeSessionsSessionIdCandidatesRoute
25729
27048
  };
25730
- const routeTree = Route$U._addFileChildren(rootRouteChildren)._addFileTypes();
27049
+ const routeTree = Route$W._addFileChildren(rootRouteChildren)._addFileTypes();
25731
27050
  function defaultRouterBasePath() {
25732
27051
  return getRouterBasePath(typeof process === "undefined" ? void 0 : process.env);
25733
27052
  }