@tonyclaw/agent-inspector 2.0.29 → 2.0.30

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 (36) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-DcqxlgSQ.js → CompareDrawer-BDzfRSt1.js} +1 -1
  3. package/.output/public/assets/{ProxyViewerContainer-D650kQlv.js → ProxyViewerContainer-NtOD8Ex_.js} +4 -4
  4. package/.output/public/assets/{ReplayDialog-CBcnPgx1.js → ReplayDialog-p-5TKg75.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-DSgSqCYt.js → RequestAnatomy-G0dV8-D_.js} +1 -1
  6. package/.output/public/assets/{ResponseView-CjqzBSoF.js → ResponseView-D2wE6lD-.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-DdBBKolI.js → StreamingChunkSequence-D8JRK-j_.js} +1 -1
  8. package/.output/public/assets/_sessionId-zimzVrJE.js +1 -0
  9. package/.output/public/assets/index-Cv5vPgpr.js +1 -0
  10. package/.output/public/assets/{main-B6OLZCp9.js → main-XHHZsOBK.js} +2 -2
  11. package/.output/server/{_sessionId-CCfKJJ19.mjs → _sessionId-CK9QEhkB.mjs} +3 -3
  12. package/.output/server/_ssr/{CompareDrawer-uaPI5R6e.mjs → CompareDrawer-BJVPFMIx.mjs} +2 -2
  13. package/.output/server/_ssr/{ProxyViewerContainer-BV2nIxXy.mjs → ProxyViewerContainer-DhbmJ2zj.mjs} +7 -7
  14. package/.output/server/_ssr/{ReplayDialog-eE2Hy2Nl.mjs → ReplayDialog-DXRj95Gj.mjs} +3 -3
  15. package/.output/server/_ssr/{RequestAnatomy-B7vwWo57.mjs → RequestAnatomy-CrYJTXdJ.mjs} +2 -2
  16. package/.output/server/_ssr/{ResponseView-Bm6Qp8mo.mjs → ResponseView-4ESqSITr.mjs} +2 -2
  17. package/.output/server/_ssr/{StreamingChunkSequence-CbAJKJQx.mjs → StreamingChunkSequence-Czdko5n_.mjs} +2 -2
  18. package/.output/server/_ssr/{index-u14_Aj60.mjs → index-bei0I7qL.mjs} +2 -2
  19. package/.output/server/_ssr/index.mjs +2 -2
  20. package/.output/server/_ssr/{router-qgeGUp1k.mjs → router-sQh4Gk8J.mjs} +1189 -131
  21. package/.output/server/_tanstack-start-manifest_v-C0fdSWnA.mjs +4 -0
  22. package/.output/server/index.mjs +62 -62
  23. package/README.md +24 -1
  24. package/package.json +1 -1
  25. package/src/lib/groupContract.ts +139 -0
  26. package/src/mcp/server.ts +278 -0
  27. package/src/mcp/toolHandlers.ts +123 -0
  28. package/src/proxy/groupEvidenceExporter.ts +341 -0
  29. package/src/proxy/groupStore.ts +259 -0
  30. package/src/routes/api/groups.$groupId.evidence.ts +62 -0
  31. package/src/routes/api/groups.$groupId.sessions.ts +50 -0
  32. package/src/routes/api/groups.$groupId.ts +50 -0
  33. package/src/routes/api/groups.ts +39 -0
  34. package/.output/public/assets/_sessionId-wctOoGh_.js +0 -1
  35. package/.output/public/assets/index-BhQGSdhG.js +0 -1
  36. package/.output/server/_tanstack-start-manifest_v-BrfUXnre.mjs +0 -4
@@ -51,7 +51,7 @@ import "../_libs/semver.mjs";
51
51
  import "../_libs/uint8array-extras.mjs";
52
52
  const faviconSvg = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2064%2064'%20role='img'%20aria-label='Agent%20Inspector'%3e%3crect%20width='64'%20height='64'%20rx='14'%20fill='%23111827'%20/%3e%3cg%20fill='none'%20stroke='%23f59e0b'%20stroke-width='4.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3e%3cpath%20fill='%23f59e0b'%20d='M15%2036c0-11%207-18%2017-18s17%207%2017%2018c0%208-7%2013-17%2013s-17-5-17-13z'%20/%3e%3cpath%20d='M16%2031c-6-5-12-3-12%204%200%205%206%206%2011%202'%20/%3e%3cpath%20d='M48%2031c6-5%2012-3%2012%204%200%205-6%206-11%202'%20/%3e%3cpath%20d='M27%2019l-3-7'%20/%3e%3cpath%20d='M37%2019l3-7'%20/%3e%3cpath%20d='M19%2045l-6%209'%20/%3e%3cpath%20d='M27%2048l-3%209'%20/%3e%3cpath%20d='M37%2048l3%209'%20/%3e%3cpath%20d='M45%2045l6%209'%20/%3e%3c/g%3e%3cpath%20d='M14%2047l9-8%208%209c-5%203.5-12%203-17-1z'%20fill='%232f6b3f'%20opacity='.95'%20/%3e%3cpath%20d='M18%2046l5-5%205%206c-3%201.6-7%201.4-10-1z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M23%2041v10'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='1.6'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='31'%20cy='48'%20r='1.8'%20fill='%23c2412d'%20/%3e%3cpath%20d='M24%2044l13-11%2014%2013c-8%205.8-18%205.3-27-2z'%20fill='%232f6b3f'%20opacity='.97'%20/%3e%3cpath%20d='M30%2043l7-7%209%209c-5%202.7-11%202.5-16-2z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M37%2036v14'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='2'%20stroke-linecap='round'%20/%3e%3cpath%20d='M30%2043c5-2%2011-1.5%2016%202'%20fill='none'%20stroke='%239fca78'%20stroke-width='1.8'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='51'%20cy='46'%20r='2'%20fill='%23c2412d'%20/%3e%3cpath%20d='M40%2050l8-8%207%208c-4.2%203.2-10%203-15%200z'%20fill='%232f6b3f'%20opacity='.95'%20/%3e%3cpath%20d='M43%2049l5-5%204.5%205.5c-3%201.5-6%201.2-9.5-.5z'%20fill='%236fb36f'%20opacity='.95'%20/%3e%3cpath%20d='M48%2044v9'%20fill='none'%20stroke='%23d6b45f'%20stroke-width='1.5'%20stroke-linecap='round'%20/%3e%3ccircle%20cx='55'%20cy='50'%20r='1.7'%20fill='%23c2412d'%20/%3e%3ccircle%20cx='24'%20cy='11'%20r='3.2'%20fill='%23f59e0b'%20/%3e%3ccircle%20cx='40'%20cy='11'%20r='3.2'%20fill='%23f59e0b'%20/%3e%3ccircle%20cx='25'%20cy='34'%20r='2.1'%20fill='%23111827'%20/%3e%3ccircle%20cx='39'%20cy='34'%20r='2.1'%20fill='%23111827'%20/%3e%3c/svg%3e";
53
53
  const appCss = "/assets/index-CwlHPmgL.css";
54
- const Route$v = createRootRoute({
54
+ const Route$z = createRootRoute({
55
55
  head: () => ({
56
56
  meta: [
57
57
  { charSet: "utf-8" },
@@ -93,8 +93,8 @@ function RootDocument({ children }) {
93
93
  ] })
94
94
  ] });
95
95
  }
96
- const $$splitComponentImporter$1 = () => import("./index-u14_Aj60.mjs");
97
- const Route$u = createFileRoute("/")({
96
+ const $$splitComponentImporter$1 = () => import("./index-bei0I7qL.mjs");
97
+ const Route$y = createFileRoute("/")({
98
98
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
99
99
  });
100
100
  const B64URL_RE = /^[A-Za-z0-9_-]+$/;
@@ -136,8 +136,8 @@ function decodeSessionIdFromPath(encoded) {
136
136
  function getSessionPath(sessionId) {
137
137
  return `/session/${encodeSessionIdForPath(sessionId)}`;
138
138
  }
139
- const $$splitComponentImporter = () => import("../_sessionId-CCfKJJ19.mjs");
140
- const Route$t = createFileRoute("/session/$sessionId")({
139
+ const $$splitComponentImporter = () => import("../_sessionId-CK9QEhkB.mjs");
140
+ const Route$x = createFileRoute("/session/$sessionId")({
141
141
  component: lazyRouteComponent($$splitComponentImporter, "component"),
142
142
  parseParams: (params) => ({
143
143
  sessionId: decodeSessionIdFromPath(params.sessionId)
@@ -1085,7 +1085,7 @@ function nonNegativeInteger(value) {
1085
1085
  if (value === null || !Number.isFinite(value) || value < 0) return 0;
1086
1086
  return Math.floor(value);
1087
1087
  }
1088
- function addTokenUsage(total, summary) {
1088
+ function addTokenUsage$1(total, summary) {
1089
1089
  const input = nonNegativeInteger(summary.tokens.input);
1090
1090
  const output = nonNegativeInteger(summary.tokens.output);
1091
1091
  const cacheCreate = nonNegativeInteger(summary.tokens.cacheCreate);
@@ -1115,7 +1115,7 @@ function buildApiPath(sessionId) {
1115
1115
  function buildCompactLogsPath(sessionId) {
1116
1116
  return buildRelativePath("/api/logs", new URLSearchParams({ compact: "1", sessionId }));
1117
1117
  }
1118
- function collectSortedValues(values) {
1118
+ function collectSortedValues$1(values) {
1119
1119
  const set = /* @__PURE__ */ new Set();
1120
1120
  for (const value of values) {
1121
1121
  if (value !== null && value.length > 0) {
@@ -1159,7 +1159,7 @@ function buildSessionInfo(input) {
1159
1159
  const errorCount = Math.max(countErrorLogs(summaries), input.snapshot?.failedRequests ?? 0);
1160
1160
  const completedLogCount = countCompletedLogs(summaries);
1161
1161
  const tokenUsage = summaries.reduce(
1162
- (total, summary) => addTokenUsage(total, summary),
1162
+ (total, summary) => addTokenUsage$1(total, summary),
1163
1163
  { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, total: 0 }
1164
1164
  );
1165
1165
  const requestCount = Math.max(input.snapshot?.requestCount ?? 0, logCount);
@@ -1197,8 +1197,8 @@ function buildSessionInfo(input) {
1197
1197
  lastTaskError,
1198
1198
  logCount,
1199
1199
  errorCount,
1200
- models: collectSortedValues(summaries.map((summary) => summary.model)),
1201
- providers: collectSortedValues(summaries.map((summary) => summary.provider)),
1200
+ models: collectSortedValues$1(summaries.map((summary) => summary.model)),
1201
+ providers: collectSortedValues$1(summaries.map((summary) => summary.provider)),
1202
1202
  tokenUsage,
1203
1203
  lastLogId: input.snapshot?.lastLogId ?? latestLog?.id ?? null,
1204
1204
  lastModel: normalizeString(input.snapshot?.lastModel) ?? latestLog?.model ?? null,
@@ -4935,7 +4935,7 @@ async function handleProxy(req) {
4935
4935
  }
4936
4936
  return handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log, captureFullDetails);
4937
4937
  }
4938
- const Route$s = createFileRoute("/proxy/$")({
4938
+ const Route$w = createFileRoute("/proxy/$")({
4939
4939
  server: {
4940
4940
  handlers: {
4941
4941
  GET: ({ request }) => handleProxy(request),
@@ -4956,7 +4956,7 @@ function parsePositiveInt$2(value) {
4956
4956
  function parseBooleanFlag(value) {
4957
4957
  return value === "1" || value === "true";
4958
4958
  }
4959
- const Route$r = createFileRoute("/api/sessions")({
4959
+ const Route$v = createFileRoute("/api/sessions")({
4960
4960
  server: {
4961
4961
  handlers: {
4962
4962
  GET: async ({ request }) => {
@@ -5179,10 +5179,10 @@ const RunsFileSchema = object({
5179
5179
  function runsFilePath() {
5180
5180
  return join(getDataDir(), "runs.json");
5181
5181
  }
5182
- function nowIso() {
5182
+ function nowIso$1() {
5183
5183
  return (/* @__PURE__ */ new Date()).toISOString();
5184
5184
  }
5185
- function stableId(input) {
5185
+ function stableId$1(input) {
5186
5186
  return input.trim().replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
5187
5187
  }
5188
5188
  function createRunId() {
@@ -5190,7 +5190,7 @@ function createRunId() {
5190
5190
  }
5191
5191
  function normalizeRunId(input) {
5192
5192
  if (input === void 0) return createRunId();
5193
- const normalized = stableId(input);
5193
+ const normalized = stableId$1(input);
5194
5194
  return normalized.length > 0 ? normalized : createRunId();
5195
5195
  }
5196
5196
  function normalizeSessionId(input, runId) {
@@ -5225,7 +5225,7 @@ function createRun(input) {
5225
5225
  const runId = normalizeRunId(parsed?.runId);
5226
5226
  const existing = getRun(runId);
5227
5227
  if (existing !== null) return existing;
5228
- const timestamp = nowIso();
5228
+ const timestamp = nowIso$1();
5229
5229
  const sessionId = normalizeSessionId(parsed?.sessionId, runId);
5230
5230
  const run = {
5231
5231
  id: runId,
@@ -5264,7 +5264,7 @@ function updateRun(runId, input) {
5264
5264
  const parsed = UpdateInspectorRunInputSchema.parse(input);
5265
5265
  const runs = readRuns();
5266
5266
  let updatedRun = null;
5267
- const updatedAt = nowIso();
5267
+ const updatedAt = nowIso$1();
5268
5268
  const updatedRuns = runs.map((run) => {
5269
5269
  if (run.id !== runId) return run;
5270
5270
  updatedRun = {
@@ -5351,7 +5351,7 @@ function listRecentFailures(limit) {
5351
5351
  limit: clampedLimit
5352
5352
  };
5353
5353
  }
5354
- async function readJsonBody$2(request) {
5354
+ async function readJsonBody$6(request) {
5355
5355
  try {
5356
5356
  const raw = await request.text();
5357
5357
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
@@ -5366,7 +5366,7 @@ function parsePositiveInt$1(value, fallback) {
5366
5366
  if (!Number.isInteger(parsed) || parsed < 1) return fallback;
5367
5367
  return parsed;
5368
5368
  }
5369
- const Route$q = createFileRoute("/api/runs")({
5369
+ const Route$u = createFileRoute("/api/runs")({
5370
5370
  server: {
5371
5371
  handlers: {
5372
5372
  GET: ({ request }) => {
@@ -5381,7 +5381,7 @@ const Route$q = createFileRoute("/api/runs")({
5381
5381
  return Response.json({ runs: listRuns() });
5382
5382
  },
5383
5383
  POST: async ({ request }) => {
5384
- const body = await readJsonBody$2(request);
5384
+ const body = await readJsonBody$6(request);
5385
5385
  if (!body.ok) {
5386
5386
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
5387
5387
  }
@@ -5411,7 +5411,7 @@ const ProviderInputSchema = object({
5411
5411
  modelMetadata: array(ProviderModelMetadataSchema).optional(),
5412
5412
  source: _enum(["company", "personal"]).optional()
5413
5413
  });
5414
- const Route$p = createFileRoute("/api/providers")({
5414
+ const Route$t = createFileRoute("/api/providers")({
5415
5415
  server: {
5416
5416
  handlers: {
5417
5417
  GET: ({ request }) => {
@@ -5445,13 +5445,117 @@ const Route$p = createFileRoute("/api/providers")({
5445
5445
  }
5446
5446
  }
5447
5447
  });
5448
- const Route$o = createFileRoute("/api/models")({
5448
+ const Route$s = createFileRoute("/api/models")({
5449
5449
  server: {
5450
5450
  handlers: {
5451
5451
  GET: () => Response.json(getModels())
5452
5452
  }
5453
5453
  }
5454
5454
  });
5455
+ const InspectorGroupStatusSchema = _enum([
5456
+ "created",
5457
+ "running",
5458
+ "completed",
5459
+ "failed",
5460
+ "cancelled"
5461
+ ]);
5462
+ const InspectorGroupKindSchema = _enum(["evaluation", "batch", "manual"]);
5463
+ const InspectorGroupEvidenceSchema = object({
5464
+ jsonPath: string(),
5465
+ markdownPath: string(),
5466
+ htmlPath: string(),
5467
+ exportedAt: string()
5468
+ });
5469
+ const InspectorGroupMemberSchema = object({
5470
+ id: string(),
5471
+ groupId: string(),
5472
+ sessionId: string(),
5473
+ runId: string().nullable(),
5474
+ label: string().nullable(),
5475
+ agent: string().nullable(),
5476
+ provider: string().nullable(),
5477
+ model: string().nullable(),
5478
+ status: InspectorRunStatusSchema.nullable(),
5479
+ metadata: record(string(), JsonValueSchema),
5480
+ addedAt: string(),
5481
+ updatedAt: string()
5482
+ });
5483
+ const InspectorGroupSchema = object({
5484
+ id: string(),
5485
+ title: string(),
5486
+ kind: InspectorGroupKindSchema,
5487
+ task: string().nullable(),
5488
+ project: string().nullable(),
5489
+ status: InspectorGroupStatusSchema,
5490
+ tags: array(string()),
5491
+ metadata: record(string(), JsonValueSchema),
5492
+ evidence: InspectorGroupEvidenceSchema.nullable(),
5493
+ members: array(InspectorGroupMemberSchema),
5494
+ createdAt: string(),
5495
+ updatedAt: string()
5496
+ });
5497
+ const CreateInspectorGroupInputSchema = object({
5498
+ groupId: string().min(1).optional(),
5499
+ title: string().min(1).optional(),
5500
+ kind: InspectorGroupKindSchema.optional(),
5501
+ task: string().nullable().optional(),
5502
+ project: string().nullable().optional(),
5503
+ status: InspectorGroupStatusSchema.optional(),
5504
+ tags: array(string()).optional(),
5505
+ metadata: record(string(), JsonValueSchema).optional()
5506
+ }).optional();
5507
+ const UpdateInspectorGroupInputSchema = object({
5508
+ title: string().min(1).optional(),
5509
+ kind: InspectorGroupKindSchema.optional(),
5510
+ task: string().nullable().optional(),
5511
+ project: string().nullable().optional(),
5512
+ status: InspectorGroupStatusSchema.optional(),
5513
+ tags: array(string()).optional(),
5514
+ metadata: record(string(), JsonValueSchema).optional()
5515
+ });
5516
+ const AddInspectorGroupSessionInputSchema = object({
5517
+ memberId: string().min(1).optional(),
5518
+ sessionId: string().min(1),
5519
+ runId: string().min(1).nullable().optional(),
5520
+ label: string().min(1).nullable().optional(),
5521
+ agent: string().min(1).nullable().optional(),
5522
+ provider: string().min(1).nullable().optional(),
5523
+ model: string().min(1).nullable().optional(),
5524
+ status: InspectorRunStatusSchema.nullable().optional(),
5525
+ metadata: record(string(), JsonValueSchema).optional()
5526
+ });
5527
+ const GroupEvidenceExportOptionsSchema = object({
5528
+ includeHistory: boolean().optional(),
5529
+ latestLogLimit: number().int().positive().max(50).optional()
5530
+ }).optional();
5531
+ const InspectorGroupExportMemberSchema = object({
5532
+ member: InspectorGroupMemberSchema,
5533
+ run: InspectorRunSchema.nullable(),
5534
+ session: SessionInfoSchema.nullable()
5535
+ });
5536
+ const InspectorGroupEvidenceSummarySchema = object({
5537
+ memberCount: number().int().nonnegative(),
5538
+ sessionsWithData: number().int().nonnegative(),
5539
+ missingSessions: number().int().nonnegative(),
5540
+ requestCount: number().int().nonnegative(),
5541
+ errorCount: number().int().nonnegative(),
5542
+ tokenUsage: SessionTokenUsageSchema,
5543
+ models: array(string()),
5544
+ providers: array(string()),
5545
+ completedMembers: number().int().nonnegative(),
5546
+ failedMembers: number().int().nonnegative(),
5547
+ runningMembers: number().int().nonnegative()
5548
+ });
5549
+ const GroupEvidenceExportResultSchema = object({
5550
+ group: InspectorGroupSchema,
5551
+ evidence: InspectorGroupEvidenceSchema,
5552
+ members: array(InspectorGroupExportMemberSchema),
5553
+ summary: InspectorGroupEvidenceSummarySchema,
5554
+ jenkinsReport: JenkinsReportSchema
5555
+ });
5556
+ const InspectorGroupsListResponseSchema = object({
5557
+ groups: array(InspectorGroupSchema)
5558
+ });
5455
5559
  let overridePort = null;
5456
5560
  function setCurrentPort(port) {
5457
5561
  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
@@ -5851,6 +5955,85 @@ async function exportEvidenceImpl(callApi2, args) {
5851
5955
  }
5852
5956
  return textJson(parsed.data);
5853
5957
  }
5958
+ async function listGroupsImpl(callApi2) {
5959
+ const res = await callApi2("/api/groups");
5960
+ if (!res.ok) return toolError(`GET /api/groups returned ${res.status}`);
5961
+ const rawBody = await res.json();
5962
+ const parsed = InspectorGroupsListResponseSchema.safeParse(rawBody);
5963
+ if (!parsed.success) return toolError("GET /api/groups returned an unparseable group list");
5964
+ return textJson(parsed.data);
5965
+ }
5966
+ async function createGroupImpl(callApi2, args) {
5967
+ const res = await callApi2("/api/groups", {
5968
+ method: "POST",
5969
+ headers: { "content-type": "application/json" },
5970
+ body: JSON.stringify(args ?? {})
5971
+ });
5972
+ if (!res.ok) return toolError(`POST /api/groups returned ${res.status}`);
5973
+ const rawBody = await res.json();
5974
+ const parsed = InspectorGroupSchema.safeParse(rawBody);
5975
+ if (!parsed.success) return toolError("POST /api/groups returned an unparseable group");
5976
+ return textJson(parsed.data);
5977
+ }
5978
+ async function getGroupImpl(callApi2, args) {
5979
+ const path2 = `/api/groups/${encodeURIComponent(args.groupId)}`;
5980
+ const res = await callApi2(path2);
5981
+ if (!res.ok) return toolError(`GET ${path2} returned ${res.status}`);
5982
+ const rawBody = await res.json();
5983
+ const parsed = InspectorGroupSchema.safeParse(rawBody);
5984
+ if (!parsed.success) return toolError("GET /api/groups/{groupId} returned an unparseable group");
5985
+ return textJson(parsed.data);
5986
+ }
5987
+ async function updateGroupImpl(callApi2, args) {
5988
+ const { groupId, ...patch } = args;
5989
+ const path2 = `/api/groups/${encodeURIComponent(groupId)}`;
5990
+ const res = await callApi2(path2, {
5991
+ method: "PATCH",
5992
+ headers: { "content-type": "application/json" },
5993
+ body: JSON.stringify(patch)
5994
+ });
5995
+ if (!res.ok) return toolError(`PATCH ${path2} returned ${res.status}`);
5996
+ const rawBody = await res.json();
5997
+ const parsed = InspectorGroupSchema.safeParse(rawBody);
5998
+ if (!parsed.success) {
5999
+ return toolError("PATCH /api/groups/{groupId} returned an unparseable group");
6000
+ }
6001
+ return textJson(parsed.data);
6002
+ }
6003
+ async function addGroupSessionImpl(callApi2, args) {
6004
+ const { groupId, ...session } = args;
6005
+ const path2 = `/api/groups/${encodeURIComponent(groupId)}/sessions`;
6006
+ const res = await callApi2(path2, {
6007
+ method: "POST",
6008
+ headers: { "content-type": "application/json" },
6009
+ body: JSON.stringify(session)
6010
+ });
6011
+ if (!res.ok) return toolError(`POST ${path2} returned ${res.status}`);
6012
+ const rawBody = await res.json();
6013
+ const parsed = InspectorGroupSchema.safeParse(rawBody);
6014
+ if (!parsed.success) {
6015
+ return toolError("POST /api/groups/{groupId}/sessions returned an unparseable group");
6016
+ }
6017
+ return textJson(parsed.data);
6018
+ }
6019
+ async function exportGroupEvidenceImpl(callApi2, args) {
6020
+ const path2 = `/api/groups/${encodeURIComponent(args.groupId)}/evidence`;
6021
+ const res = await callApi2(path2, {
6022
+ method: "POST",
6023
+ headers: { "content-type": "application/json" },
6024
+ body: JSON.stringify({
6025
+ includeHistory: args.includeHistory,
6026
+ latestLogLimit: args.latestLogLimit
6027
+ })
6028
+ });
6029
+ if (!res.ok) return toolError(`POST ${path2} returned ${res.status}`);
6030
+ const rawBody = await res.json();
6031
+ const parsed = GroupEvidenceExportResultSchema.safeParse(rawBody);
6032
+ if (!parsed.success) {
6033
+ return toolError("POST /api/groups/{groupId}/evidence returned an unparseable evidence result");
6034
+ }
6035
+ return textJson(parsed.data);
6036
+ }
5854
6037
  async function listModelsImpl(callApi2) {
5855
6038
  const res = await callApi2("/api/models");
5856
6039
  if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
@@ -6042,6 +6225,9 @@ const LogResourceListResponseSchema = object({
6042
6225
  const EvidenceReadResponseSchema = object({
6043
6226
  markdown: string()
6044
6227
  });
6228
+ const GroupEvidenceReadResponseSchema = object({
6229
+ markdown: string()
6230
+ });
6045
6231
  function textResource(uri, mimeType, text) {
6046
6232
  return {
6047
6233
  contents: [{ uri: uri.href, mimeType, text }]
@@ -6124,6 +6310,61 @@ async function listRunResources() {
6124
6310
  return listResult([]);
6125
6311
  }
6126
6312
  }
6313
+ async function listGroupResources() {
6314
+ try {
6315
+ const response = await callApi("/api/groups");
6316
+ if (!response.ok) return listResult([]);
6317
+ const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
6318
+ if (!parsed.success) return listResult([]);
6319
+ return listResult(
6320
+ parsed.data.groups.map((group) => ({
6321
+ uri: `inspector://groups/${encodeURIComponent(group.id)}`,
6322
+ name: `group:${group.id}`,
6323
+ title: group.title,
6324
+ mimeType: "application/json"
6325
+ }))
6326
+ );
6327
+ } catch {
6328
+ return listResult([]);
6329
+ }
6330
+ }
6331
+ async function listGroupSessionResources() {
6332
+ try {
6333
+ const response = await callApi("/api/groups");
6334
+ if (!response.ok) return listResult([]);
6335
+ const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
6336
+ if (!parsed.success) return listResult([]);
6337
+ return listResult(
6338
+ parsed.data.groups.map((group) => ({
6339
+ uri: `inspector://groups/${encodeURIComponent(group.id)}/sessions`,
6340
+ name: `group-sessions:${group.id}`,
6341
+ title: `${group.title} sessions`,
6342
+ mimeType: "application/json"
6343
+ }))
6344
+ );
6345
+ } catch {
6346
+ return listResult([]);
6347
+ }
6348
+ }
6349
+ async function listGroupEvidenceResources() {
6350
+ try {
6351
+ const response = await callApi("/api/groups");
6352
+ if (!response.ok) return listResult([]);
6353
+ const parsed = InspectorGroupsListResponseSchema.safeParse(await response.json());
6354
+ if (!parsed.success) return listResult([]);
6355
+ return listResult(
6356
+ parsed.data.groups.filter((group) => group.evidence !== null).map((group) => ({
6357
+ uri: `inspector://groups/${encodeURIComponent(group.id)}/evidence`,
6358
+ name: `group-evidence:${group.id}`,
6359
+ title: `${group.title} evidence pack`,
6360
+ mimeType: "text/markdown",
6361
+ description: group.evidence?.markdownPath
6362
+ }))
6363
+ );
6364
+ } catch {
6365
+ return listResult([]);
6366
+ }
6367
+ }
6127
6368
  async function listEvidenceResources() {
6128
6369
  try {
6129
6370
  const response = await callApi("/api/runs");
@@ -6296,6 +6537,77 @@ function registerTools(server) {
6296
6537
  },
6297
6538
  (args) => safeCall(() => getRecentFailuresImpl(callApi, args))
6298
6539
  );
6540
+ server.registerTool(
6541
+ "inspector_list_groups",
6542
+ {
6543
+ title: "List Inspector groups",
6544
+ description: "Lists declared Inspector groups. Groups aggregate multiple sessions/runs, commonly one evaluation matrix spanning several providers or models.",
6545
+ inputSchema: object({})
6546
+ },
6547
+ () => safeCall(() => listGroupsImpl(callApi))
6548
+ );
6549
+ server.registerTool(
6550
+ "inspector_create_group",
6551
+ {
6552
+ title: "Create or declare an Inspector group",
6553
+ description: "Creates an evaluation/batch group that external systems can use to aggregate multiple OpenCode/Codex sessions across models such as MiniMax, DeepSeek, or GLM.",
6554
+ inputSchema: object({
6555
+ groupId: string().min(1).optional().describe("Stable group id. Generated when omitted."),
6556
+ title: string().min(1).optional().describe("Human-readable group title."),
6557
+ kind: _enum(["evaluation", "batch", "manual"]).optional().describe("Group kind. Defaults to evaluation."),
6558
+ task: string().nullable().optional().describe("Task, benchmark, or evaluation goal."),
6559
+ project: string().nullable().optional().describe("Project or repository under test."),
6560
+ status: InspectorGroupStatusSchema.optional().describe("Initial group status."),
6561
+ tags: array(string()).optional().describe("Free-form group labels."),
6562
+ metadata: record(string(), JsonValueSchema).optional().describe("JSON metadata.")
6563
+ })
6564
+ },
6565
+ (args) => safeCall(() => createGroupImpl(callApi, args))
6566
+ );
6567
+ server.registerTool(
6568
+ "inspector_get_group",
6569
+ {
6570
+ title: "Get an Inspector group",
6571
+ description: "Returns one declared Inspector group with its attached session/run members and exported evidence paths when available.",
6572
+ inputSchema: object({
6573
+ groupId: string().min(1).describe("The Inspector group id.")
6574
+ })
6575
+ },
6576
+ (args) => safeCall(() => getGroupImpl(callApi, args))
6577
+ );
6578
+ server.registerTool(
6579
+ "inspector_update_group",
6580
+ {
6581
+ title: "Update an Inspector group",
6582
+ description: "PATCH-style update for a declared group. Use this to mark a whole evaluation running, failed, completed, or to merge CI metadata.",
6583
+ inputSchema: UpdateInspectorGroupInputSchema.extend({
6584
+ groupId: string().min(1).describe("The Inspector group id.")
6585
+ })
6586
+ },
6587
+ (args) => safeCall(() => updateGroupImpl(callApi, args))
6588
+ );
6589
+ server.registerTool(
6590
+ "inspector_add_group_session",
6591
+ {
6592
+ title: "Attach a session to an Inspector group",
6593
+ description: "Adds or updates one group member. Use it after each OpenCode process discovers its session id, or while an evaluation matrix is running to attach model/provider labels.",
6594
+ inputSchema: AddInspectorGroupSessionInputSchema.extend({
6595
+ groupId: string().min(1).describe("The Inspector group id.")
6596
+ })
6597
+ },
6598
+ (args) => safeCall(() => addGroupSessionImpl(callApi, args))
6599
+ );
6600
+ server.registerTool(
6601
+ "inspector_export_group_evidence",
6602
+ {
6603
+ title: "Export an Inspector group evidence pack",
6604
+ description: "Exports a whole group's evidence pack to local JSON, Markdown, and HTML files under <dataDir>/evidence/groups/<groupId>. The export contains a provider/model matrix and compact session summaries.",
6605
+ inputSchema: GroupEvidenceExportOptionsSchema.unwrap().extend({
6606
+ groupId: string().min(1).describe("The Inspector group id.")
6607
+ })
6608
+ },
6609
+ (args) => safeCall(() => exportGroupEvidenceImpl(callApi, args))
6610
+ );
6299
6611
  server.registerTool(
6300
6612
  "inspector_list_models",
6301
6613
  {
@@ -6503,6 +6815,46 @@ function registerResources(server) {
6503
6815
  },
6504
6816
  (uri) => readApiJsonResource(uri, "/api/runs")
6505
6817
  );
6818
+ server.registerResource(
6819
+ "inspector_groups",
6820
+ "inspector://groups",
6821
+ {
6822
+ title: "Inspector groups",
6823
+ description: "Declared Inspector groups aggregating multiple sessions or runs.",
6824
+ mimeType: "application/json"
6825
+ },
6826
+ (uri) => readApiJsonResource(uri, "/api/groups")
6827
+ );
6828
+ server.registerResource(
6829
+ "inspector_group",
6830
+ new ResourceTemplate("inspector://groups/{groupId}", { list: listGroupResources }),
6831
+ {
6832
+ title: "Inspector group",
6833
+ description: "One declared Inspector group with attached session/run members.",
6834
+ mimeType: "application/json"
6835
+ },
6836
+ (uri, variables) => {
6837
+ const groupId = variableString(variables, "groupId");
6838
+ if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
6839
+ return readApiJsonResource(uri, `/api/groups/${encodeURIComponent(groupId)}`);
6840
+ }
6841
+ );
6842
+ server.registerResource(
6843
+ "inspector_group_sessions",
6844
+ new ResourceTemplate("inspector://groups/{groupId}/sessions", {
6845
+ list: listGroupSessionResources
6846
+ }),
6847
+ {
6848
+ title: "Inspector group sessions",
6849
+ description: "Attached session/run members for one Inspector group.",
6850
+ mimeType: "application/json"
6851
+ },
6852
+ (uri, variables) => {
6853
+ const groupId = variableString(variables, "groupId");
6854
+ if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
6855
+ return readApiJsonResource(uri, `/api/groups/${encodeURIComponent(groupId)}/sessions`);
6856
+ }
6857
+ );
6506
6858
  server.registerResource(
6507
6859
  "inspector_recent_failures",
6508
6860
  "inspector://failures/recent",
@@ -6555,6 +6907,36 @@ function registerResources(server) {
6555
6907
  }
6556
6908
  }
6557
6909
  );
6910
+ server.registerResource(
6911
+ "inspector_group_evidence",
6912
+ new ResourceTemplate("inspector://groups/{groupId}/evidence", {
6913
+ list: listGroupEvidenceResources
6914
+ }),
6915
+ {
6916
+ title: "Inspector group evidence pack",
6917
+ description: "Markdown evidence pack for an Inspector group. Generate it first with inspector_export_group_evidence.",
6918
+ mimeType: "text/markdown"
6919
+ },
6920
+ async (uri, variables) => {
6921
+ const groupId = variableString(variables, "groupId");
6922
+ if (groupId === null) return textResource(uri, "text/plain", "Missing groupId");
6923
+ const path2 = `/api/groups/${encodeURIComponent(groupId)}/evidence`;
6924
+ try {
6925
+ const response = await callApi(path2);
6926
+ if (!response.ok) {
6927
+ return textResource(uri, "text/plain", `GET ${path2} returned ${response.status}`);
6928
+ }
6929
+ const parsed = GroupEvidenceReadResponseSchema.safeParse(await response.json());
6930
+ if (!parsed.success) {
6931
+ return textResource(uri, "text/plain", "Evidence endpoint returned an unparseable body");
6932
+ }
6933
+ return textResource(uri, "text/markdown", parsed.data.markdown);
6934
+ } catch (err) {
6935
+ const message = err instanceof Error ? err.message : String(err);
6936
+ return textResource(uri, "text/plain", `Resource read failed: ${message}`);
6937
+ }
6938
+ }
6939
+ );
6558
6940
  }
6559
6941
  function registerPrompts(server) {
6560
6942
  server.registerPrompt(
@@ -6627,6 +7009,31 @@ The report should include:
6627
7009
  3. Models/providers used, token totals, request counts, and error counts
6628
7010
  4. Failure analysis or success evidence
6629
7011
  5. Reproducible next action for a developer`)
7012
+ );
7013
+ server.registerPrompt(
7014
+ "inspector_generate_group_report",
7015
+ {
7016
+ title: "Generate group report",
7017
+ description: "Generate a model/provider evaluation report from an Inspector group and group evidence pack.",
7018
+ argsSchema: object({
7019
+ groupId: string().min(1).describe("The Inspector group id.")
7020
+ })
7021
+ },
7022
+ ({ groupId }) => userPrompt(`Generate a model/provider evaluation report for this Inspector group.
7023
+
7024
+ Evidence to read:
7025
+ - Group resource: inspector://groups/${encodeURIComponent(groupId)}
7026
+ - Group session members: inspector://groups/${encodeURIComponent(groupId)}/sessions
7027
+ - Group evidence pack: inspector://groups/${encodeURIComponent(groupId)}/evidence
7028
+
7029
+ If the evidence pack resource says it has not been exported, call inspector_export_group_evidence for this group first.
7030
+
7031
+ The report should include:
7032
+ 1. Evaluation status summary
7033
+ 2. Provider/model matrix with session links
7034
+ 3. Request counts, token totals, streaming timing, and error counts
7035
+ 4. Winner/loser observations when the evidence supports them
7036
+ 5. Concrete next actions for failed or missing sessions`)
6630
7037
  );
6631
7038
  server.registerPrompt(
6632
7039
  "inspector_extract_repro_steps",
@@ -6651,7 +7058,7 @@ Return:
6651
7058
  5. Minimal verification command or UI check`)
6652
7059
  );
6653
7060
  }
6654
- const Route$n = createFileRoute("/api/mcp")({
7061
+ const Route$r = createFileRoute("/api/mcp")({
6655
7062
  server: {
6656
7063
  handlers: {
6657
7064
  // The SDK may issue either POST, GET, or DELETE. TanStack Start only
@@ -6761,7 +7168,7 @@ function parsePositiveInt(value, fallback) {
6761
7168
  if (!Number.isInteger(parsed) || parsed < 1) return fallback;
6762
7169
  return parsed;
6763
7170
  }
6764
- const Route$m = createFileRoute("/api/logs")({
7171
+ const Route$q = createFileRoute("/api/logs")({
6765
7172
  server: {
6766
7173
  handlers: {
6767
7174
  GET: async ({ request }) => {
@@ -6837,7 +7244,7 @@ const Route$m = createFileRoute("/api/logs")({
6837
7244
  }
6838
7245
  });
6839
7246
  logger.debug("Health endpoint loaded");
6840
- const Route$l = createFileRoute("/api/health")({
7247
+ const Route$p = createFileRoute("/api/health")({
6841
7248
  server: {
6842
7249
  handlers: {
6843
7250
  GET: () => {
@@ -6846,32 +7253,245 @@ const Route$l = createFileRoute("/api/health")({
6846
7253
  }
6847
7254
  }
6848
7255
  });
6849
- const RuntimeConfigPatchSchema = object({
6850
- stripClaudeCodeBillingHeader: boolean().optional(),
6851
- hasSeenOnboarding: boolean().optional(),
6852
- slowResponseThresholdSeconds: number().int().min(0).max(MAX_SLOW_RESPONSE_THRESHOLD_SECONDS).optional(),
6853
- providerTestTimeoutSeconds: number().int().min(1).max(MAX_PROVIDER_TEST_TIMEOUT_SECONDS).optional(),
6854
- timeDisplayFormat: TimeDisplayFormatSchema.optional()
6855
- }).strict().refine((v) => Object.keys(v).length > 0, {
6856
- message: "At least one field must be provided"
7256
+ const GroupsFileSchema = object({
7257
+ groups: array(InspectorGroupSchema)
6857
7258
  });
6858
- const Route$k = createFileRoute("/api/config")({
6859
- server: {
6860
- handlers: {
6861
- GET: () => {
6862
- return Response.json(getConfig());
6863
- },
6864
- PATCH: async ({ request }) => {
6865
- let body;
6866
- try {
6867
- body = await request.json();
6868
- } catch {
6869
- return Response.json({ error: "Invalid JSON body" }, { status: 400 });
6870
- }
6871
- const result = RuntimeConfigPatchSchema.safeParse(body);
6872
- if (!result.success) {
6873
- return Response.json(
6874
- {
7259
+ function groupsFilePath() {
7260
+ return join(getDataDir(), "groups.json");
7261
+ }
7262
+ function nowIso() {
7263
+ return (/* @__PURE__ */ new Date()).toISOString();
7264
+ }
7265
+ function stableId(input) {
7266
+ return input.trim().replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
7267
+ }
7268
+ function createGroupId() {
7269
+ return `grp_${Date.now().toString(36)}_${randomUUID$1().slice(0, 8)}`;
7270
+ }
7271
+ function createMemberId() {
7272
+ return `mem_${Date.now().toString(36)}_${randomUUID$1().slice(0, 8)}`;
7273
+ }
7274
+ function normalizeGroupId(input) {
7275
+ if (input === void 0) return createGroupId();
7276
+ const normalized = stableId(input);
7277
+ return normalized.length > 0 ? normalized : createGroupId();
7278
+ }
7279
+ function normalizeMemberId(input, sessionId) {
7280
+ if (input === void 0) {
7281
+ const normalizedSession = stableId(sessionId);
7282
+ return normalizedSession.length > 0 ? `mem_${normalizedSession}` : createMemberId();
7283
+ }
7284
+ const normalized = stableId(input);
7285
+ return normalized.length > 0 ? normalized : createMemberId();
7286
+ }
7287
+ function nullableText(value) {
7288
+ if (value === void 0 || value === null) return null;
7289
+ const trimmed = value.trim();
7290
+ return trimmed.length > 0 ? trimmed : null;
7291
+ }
7292
+ function readGroups() {
7293
+ const filePath = groupsFilePath();
7294
+ if (!existsSync(filePath)) return [];
7295
+ try {
7296
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
7297
+ const result = GroupsFileSchema.safeParse(parsed);
7298
+ return result.success ? result.data.groups : [];
7299
+ } catch {
7300
+ return [];
7301
+ }
7302
+ }
7303
+ function writeGroups(groups) {
7304
+ const filePath = groupsFilePath();
7305
+ mkdirSync(dirname(filePath), { recursive: true });
7306
+ writeFileSync(filePath, `${JSON.stringify({ groups }, null, 2)}
7307
+ `, "utf8");
7308
+ }
7309
+ function sortedGroups(groups) {
7310
+ return [...groups].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
7311
+ }
7312
+ function listGroups() {
7313
+ return sortedGroups(readGroups());
7314
+ }
7315
+ function getGroup(groupId) {
7316
+ return readGroups().find((group) => group.id === groupId) ?? null;
7317
+ }
7318
+ function createGroup(input) {
7319
+ const parsed = CreateInspectorGroupInputSchema.parse(input);
7320
+ const groupId = normalizeGroupId(parsed?.groupId);
7321
+ const existing = getGroup(groupId);
7322
+ if (existing !== null) return existing;
7323
+ const timestamp = nowIso();
7324
+ const group = {
7325
+ id: groupId,
7326
+ title: parsed?.title ?? `Inspector Group ${groupId}`,
7327
+ kind: parsed?.kind ?? "evaluation",
7328
+ task: parsed?.task ?? null,
7329
+ project: parsed?.project ?? null,
7330
+ status: parsed?.status ?? "created",
7331
+ tags: parsed?.tags ?? [],
7332
+ metadata: parsed?.metadata ?? {},
7333
+ evidence: null,
7334
+ members: [],
7335
+ createdAt: timestamp,
7336
+ updatedAt: timestamp
7337
+ };
7338
+ writeGroups([...readGroups(), group]);
7339
+ return group;
7340
+ }
7341
+ function updateGroup(groupId, input) {
7342
+ const parsed = UpdateInspectorGroupInputSchema.parse(input);
7343
+ const groups = readGroups();
7344
+ let updatedGroup = null;
7345
+ const updatedAt = nowIso();
7346
+ const updatedGroups = groups.map((group) => {
7347
+ if (group.id !== groupId) return group;
7348
+ updatedGroup = {
7349
+ ...group,
7350
+ title: parsed.title ?? group.title,
7351
+ kind: parsed.kind ?? group.kind,
7352
+ task: parsed.task === void 0 ? group.task : parsed.task,
7353
+ project: parsed.project === void 0 ? group.project : parsed.project,
7354
+ status: parsed.status ?? group.status,
7355
+ tags: parsed.tags ?? group.tags,
7356
+ metadata: parsed.metadata === void 0 ? group.metadata : { ...group.metadata, ...parsed.metadata },
7357
+ updatedAt
7358
+ };
7359
+ return updatedGroup;
7360
+ });
7361
+ if (updatedGroup === null) return null;
7362
+ writeGroups(updatedGroups);
7363
+ return updatedGroup;
7364
+ }
7365
+ function matchesExistingMember(member, memberId, sessionId, runId) {
7366
+ if (member.id === memberId) return true;
7367
+ if (member.sessionId !== sessionId) return false;
7368
+ return member.runId === runId;
7369
+ }
7370
+ function upsertMember(group, input, updatedAt) {
7371
+ const parsed = AddInspectorGroupSessionInputSchema.parse(input);
7372
+ const sessionId = parsed.sessionId.trim();
7373
+ const runId = nullableText(parsed.runId);
7374
+ const memberId = normalizeMemberId(parsed.memberId, sessionId);
7375
+ let matched = false;
7376
+ const members = group.members.map((member) => {
7377
+ if (!matchesExistingMember(member, memberId, sessionId, runId)) return member;
7378
+ matched = true;
7379
+ return {
7380
+ ...member,
7381
+ sessionId,
7382
+ runId,
7383
+ label: parsed.label === void 0 ? member.label : nullableText(parsed.label),
7384
+ agent: parsed.agent === void 0 ? member.agent : nullableText(parsed.agent),
7385
+ provider: parsed.provider === void 0 ? member.provider : nullableText(parsed.provider),
7386
+ model: parsed.model === void 0 ? member.model : nullableText(parsed.model),
7387
+ status: parsed.status === void 0 ? member.status : parsed.status,
7388
+ metadata: parsed.metadata === void 0 ? member.metadata : { ...member.metadata, ...parsed.metadata },
7389
+ updatedAt
7390
+ };
7391
+ });
7392
+ if (!matched) {
7393
+ members.push({
7394
+ id: memberId,
7395
+ groupId: group.id,
7396
+ sessionId,
7397
+ runId,
7398
+ label: nullableText(parsed.label),
7399
+ agent: nullableText(parsed.agent),
7400
+ provider: nullableText(parsed.provider),
7401
+ model: nullableText(parsed.model),
7402
+ status: parsed.status ?? null,
7403
+ metadata: parsed.metadata ?? {},
7404
+ addedAt: updatedAt,
7405
+ updatedAt
7406
+ });
7407
+ }
7408
+ return { ...group, members, updatedAt };
7409
+ }
7410
+ function addGroupSession(groupId, input) {
7411
+ const groups = readGroups();
7412
+ let updatedGroup = null;
7413
+ const updatedAt = nowIso();
7414
+ const updatedGroups = groups.map((group) => {
7415
+ if (group.id !== groupId) return group;
7416
+ updatedGroup = upsertMember(group, input, updatedAt);
7417
+ return updatedGroup;
7418
+ });
7419
+ if (updatedGroup === null) return null;
7420
+ writeGroups(updatedGroups);
7421
+ return updatedGroup;
7422
+ }
7423
+ function updateGroupEvidence(groupId, evidence) {
7424
+ const groups = readGroups();
7425
+ let updatedGroup = null;
7426
+ const updatedGroups = groups.map((group) => {
7427
+ if (group.id !== groupId) return group;
7428
+ updatedGroup = {
7429
+ ...group,
7430
+ evidence,
7431
+ updatedAt: evidence.exportedAt
7432
+ };
7433
+ return updatedGroup;
7434
+ });
7435
+ if (updatedGroup === null) return null;
7436
+ writeGroups(updatedGroups);
7437
+ return updatedGroup;
7438
+ }
7439
+ async function readJsonBody$5(request) {
7440
+ try {
7441
+ const raw = await request.text();
7442
+ if (raw.trim().length === 0) return { ok: true, value: void 0 };
7443
+ return { ok: true, value: JSON.parse(raw) };
7444
+ } catch {
7445
+ return { ok: false };
7446
+ }
7447
+ }
7448
+ const Route$o = createFileRoute("/api/groups")({
7449
+ server: {
7450
+ handlers: {
7451
+ GET: () => Response.json({ groups: listGroups() }),
7452
+ POST: async ({ request }) => {
7453
+ const body = await readJsonBody$5(request);
7454
+ if (!body.ok) {
7455
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
7456
+ }
7457
+ const parsed = CreateInspectorGroupInputSchema.safeParse(body.value);
7458
+ if (!parsed.success) {
7459
+ return Response.json(
7460
+ { error: "Invalid request body", details: parsed.error.format() },
7461
+ { status: 400 }
7462
+ );
7463
+ }
7464
+ return Response.json(createGroup(parsed.data), { status: 201 });
7465
+ }
7466
+ }
7467
+ }
7468
+ });
7469
+ const RuntimeConfigPatchSchema = object({
7470
+ stripClaudeCodeBillingHeader: boolean().optional(),
7471
+ hasSeenOnboarding: boolean().optional(),
7472
+ slowResponseThresholdSeconds: number().int().min(0).max(MAX_SLOW_RESPONSE_THRESHOLD_SECONDS).optional(),
7473
+ providerTestTimeoutSeconds: number().int().min(1).max(MAX_PROVIDER_TEST_TIMEOUT_SECONDS).optional(),
7474
+ timeDisplayFormat: TimeDisplayFormatSchema.optional()
7475
+ }).strict().refine((v) => Object.keys(v).length > 0, {
7476
+ message: "At least one field must be provided"
7477
+ });
7478
+ const Route$n = createFileRoute("/api/config")({
7479
+ server: {
7480
+ handlers: {
7481
+ GET: () => {
7482
+ return Response.json(getConfig());
7483
+ },
7484
+ PATCH: async ({ request }) => {
7485
+ let body;
7486
+ try {
7487
+ body = await request.json();
7488
+ } catch {
7489
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
7490
+ }
7491
+ const result = RuntimeConfigPatchSchema.safeParse(body);
7492
+ if (!result.success) {
7493
+ return Response.json(
7494
+ {
6875
7495
  error: "Invalid config payload",
6876
7496
  issues: result.error.issues
6877
7497
  },
@@ -6884,7 +7504,7 @@ const Route$k = createFileRoute("/api/config")({
6884
7504
  }
6885
7505
  }
6886
7506
  });
6887
- async function readJsonBody$1(request) {
7507
+ async function readJsonBody$4(request) {
6888
7508
  try {
6889
7509
  const raw = await request.text();
6890
7510
  if (raw.trim().length === 0) return { ok: true, value: {} };
@@ -6893,7 +7513,7 @@ async function readJsonBody$1(request) {
6893
7513
  return { ok: false };
6894
7514
  }
6895
7515
  }
6896
- const Route$j = createFileRoute("/api/runs/$runId")({
7516
+ const Route$m = createFileRoute("/api/runs/$runId")({
6897
7517
  server: {
6898
7518
  handlers: {
6899
7519
  GET: ({ params }) => {
@@ -6904,7 +7524,7 @@ const Route$j = createFileRoute("/api/runs/$runId")({
6904
7524
  return Response.json(run);
6905
7525
  },
6906
7526
  PATCH: async ({ params, request }) => {
6907
- const body = await readJsonBody$1(request);
7527
+ const body = await readJsonBody$4(request);
6908
7528
  if (!body.ok) {
6909
7529
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
6910
7530
  }
@@ -7350,7 +7970,7 @@ function scanExternalProviders() {
7350
7970
  }
7351
7971
  return { providers: filteredProviders, warnings };
7352
7972
  }
7353
- const Route$i = createFileRoute("/api/providers/scan")({
7973
+ const Route$l = createFileRoute("/api/providers/scan")({
7354
7974
  server: {
7355
7975
  handlers: {
7356
7976
  GET: () => {
@@ -7371,7 +7991,7 @@ const Route$i = createFileRoute("/api/providers/scan")({
7371
7991
  }
7372
7992
  });
7373
7993
  union([string(), object({ providers: unknown() })]);
7374
- const Route$h = createFileRoute("/api/providers/import")({
7994
+ const Route$k = createFileRoute("/api/providers/import")({
7375
7995
  server: {
7376
7996
  handlers: {
7377
7997
  POST: async ({ request }) => {
@@ -7404,7 +8024,7 @@ const Route$h = createFileRoute("/api/providers/import")({
7404
8024
  }
7405
8025
  }
7406
8026
  });
7407
- const Route$g = createFileRoute("/api/providers/export")({
8027
+ const Route$j = createFileRoute("/api/providers/export")({
7408
8028
  server: {
7409
8029
  handlers: {
7410
8030
  GET: ({ request }) => {
@@ -7437,7 +8057,7 @@ const ProviderUpdateSchema = object({
7437
8057
  modelMetadata: array(ProviderModelMetadataSchema).optional(),
7438
8058
  source: _enum(["company", "personal"]).optional()
7439
8059
  });
7440
- const Route$f = createFileRoute("/api/providers/$providerId")({
8060
+ const Route$i = createFileRoute("/api/providers/$providerId")({
7441
8061
  server: {
7442
8062
  handlers: {
7443
8063
  GET: ({ params, request }) => {
@@ -7472,7 +8092,7 @@ const Route$f = createFileRoute("/api/providers/$providerId")({
7472
8092
  }
7473
8093
  });
7474
8094
  const INITIAL_STREAM_LOG_LIMIT = 100;
7475
- const Route$e = createFileRoute("/api/logs/stream")({
8095
+ const Route$h = createFileRoute("/api/logs/stream")({
7476
8096
  server: {
7477
8097
  handlers: {
7478
8098
  GET: ({ request }) => {
@@ -7545,7 +8165,7 @@ const Route$e = createFileRoute("/api/logs/stream")({
7545
8165
  }
7546
8166
  }
7547
8167
  });
7548
- const Route$d = createFileRoute("/api/logs/$id")({
8168
+ const Route$g = createFileRoute("/api/logs/$id")({
7549
8169
  server: {
7550
8170
  handlers: {
7551
8171
  GET: async ({ params }) => {
@@ -8082,7 +8702,7 @@ const SearchBodySchema = object({
8082
8702
  query: string().min(1),
8083
8703
  project: string().optional()
8084
8704
  });
8085
- const Route$c = createFileRoute("/api/knowledge/search")({
8705
+ const Route$f = createFileRoute("/api/knowledge/search")({
8086
8706
  server: {
8087
8707
  handlers: {
8088
8708
  GET: async ({ request }) => {
@@ -8104,7 +8724,7 @@ const Route$c = createFileRoute("/api/knowledge/search")({
8104
8724
  }
8105
8725
  }
8106
8726
  });
8107
- const Route$b = createFileRoute("/api/knowledge/project-context")({
8727
+ const Route$e = createFileRoute("/api/knowledge/project-context")({
8108
8728
  server: {
8109
8729
  handlers: {
8110
8730
  GET: async ({ request }) => {
@@ -8186,14 +8806,54 @@ function updateCandidateDraft(id, updates) {
8186
8806
  candidates.set(id, updated);
8187
8807
  return updated;
8188
8808
  }
8189
- const Route$a = createFileRoute("/api/knowledge/candidates")({
8809
+ const Route$d = createFileRoute("/api/knowledge/candidates")({
8190
8810
  server: {
8191
8811
  handlers: {
8192
8812
  GET: () => Response.json({ candidates: listCandidates() })
8193
8813
  }
8194
8814
  }
8195
8815
  });
8196
- const Route$9 = createFileRoute("/api/config/paths")({
8816
+ async function readJsonBody$3(request) {
8817
+ try {
8818
+ const raw = await request.text();
8819
+ if (raw.trim().length === 0) return { ok: true, value: {} };
8820
+ return { ok: true, value: JSON.parse(raw) };
8821
+ } catch {
8822
+ return { ok: false };
8823
+ }
8824
+ }
8825
+ const Route$c = createFileRoute("/api/groups/$groupId")({
8826
+ server: {
8827
+ handlers: {
8828
+ GET: ({ params }) => {
8829
+ const group = getGroup(params.groupId);
8830
+ if (group === null) {
8831
+ return Response.json({ error: "Group not found" }, { status: 404 });
8832
+ }
8833
+ return Response.json(group);
8834
+ },
8835
+ PATCH: async ({ params, request }) => {
8836
+ const body = await readJsonBody$3(request);
8837
+ if (!body.ok) {
8838
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
8839
+ }
8840
+ const parsed = UpdateInspectorGroupInputSchema.safeParse(body.value);
8841
+ if (!parsed.success) {
8842
+ return Response.json(
8843
+ { error: "Invalid request body", details: parsed.error.format() },
8844
+ { status: 400 }
8845
+ );
8846
+ }
8847
+ const updated = updateGroup(params.groupId, parsed.data);
8848
+ if (updated === null) {
8849
+ return Response.json({ error: "Group not found" }, { status: 404 });
8850
+ }
8851
+ return Response.json(updated);
8852
+ }
8853
+ }
8854
+ }
8855
+ });
8856
+ const Route$b = createFileRoute("/api/config/paths")({
8197
8857
  server: {
8198
8858
  handlers: {
8199
8859
  GET: () => {
@@ -8576,7 +9236,7 @@ function timelineMarkdown(timeline) {
8576
9236
  })
8577
9237
  ];
8578
9238
  }
8579
- function buildJenkinsReport(input) {
9239
+ function buildJenkinsReport$1(input) {
8580
9240
  const status = reportStatus(input.classification);
8581
9241
  const session = input.session;
8582
9242
  const summary = `[${status}] ${input.classification.summary}`;
@@ -8624,7 +9284,7 @@ function buildJenkinsReport(input) {
8624
9284
  function analyzeEvidence(input) {
8625
9285
  const classification = classifyRunFailure(input.run, input.session);
8626
9286
  const timeline = buildRunTimeline(input.run, input.session, input.exportedAt);
8627
- const jenkinsReport = buildJenkinsReport({
9287
+ const jenkinsReport = buildJenkinsReport$1({
8628
9288
  run: input.run,
8629
9289
  session: input.session,
8630
9290
  timeline,
@@ -8636,22 +9296,22 @@ function analyzeEvidence(input) {
8636
9296
  function evidenceDir(runId) {
8637
9297
  return join(getDataDir(), "evidence", runId);
8638
9298
  }
8639
- function jsonPath(runId) {
9299
+ function jsonPath$1(runId) {
8640
9300
  return join(evidenceDir(runId), "evidence.json");
8641
9301
  }
8642
- function markdownPath(runId) {
9302
+ function markdownPath$1(runId) {
8643
9303
  return join(evidenceDir(runId), "evidence.md");
8644
9304
  }
8645
- function htmlPath(runId) {
9305
+ function htmlPath$1(runId) {
8646
9306
  return join(evidenceDir(runId), "evidence.html");
8647
9307
  }
8648
9308
  function formatList(values) {
8649
9309
  return values.length > 0 ? values.join(", ") : "n/a";
8650
9310
  }
8651
- function escapeHtml(value) {
9311
+ function escapeHtml$1(value) {
8652
9312
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
8653
9313
  }
8654
- function buildMarkdown(document) {
9314
+ function buildMarkdown$1(document) {
8655
9315
  const session = document.session;
8656
9316
  const lines = [
8657
9317
  `# ${document.run.title}`,
@@ -8733,19 +9393,19 @@ function buildMarkdown(document) {
8733
9393
  return `${lines.join("\n")}
8734
9394
  `;
8735
9395
  }
8736
- function buildHtml(markdown, title) {
9396
+ function buildHtml$1(markdown, title) {
8737
9397
  return `<!doctype html>
8738
9398
  <html lang="en">
8739
9399
  <head>
8740
9400
  <meta charset="utf-8">
8741
- <title>${escapeHtml(title)}</title>
9401
+ <title>${escapeHtml$1(title)}</title>
8742
9402
  <style>
8743
9403
  body { font-family: system-ui, sans-serif; max-width: 960px; margin: 40px auto; line-height: 1.5; }
8744
9404
  pre { white-space: pre-wrap; background: #f6f8fa; padding: 16px; border-radius: 8px; }
8745
9405
  </style>
8746
9406
  </head>
8747
9407
  <body>
8748
- <pre>${escapeHtml(markdown)}</pre>
9408
+ <pre>${escapeHtml$1(markdown)}</pre>
8749
9409
  </body>
8750
9410
  </html>
8751
9411
  `;
@@ -8758,9 +9418,9 @@ async function exportRunEvidence(run, options, baseUrl) {
8758
9418
  });
8759
9419
  const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
8760
9420
  const evidence = {
8761
- jsonPath: jsonPath(run.id),
8762
- markdownPath: markdownPath(run.id),
8763
- htmlPath: htmlPath(run.id),
9421
+ jsonPath: jsonPath$1(run.id),
9422
+ markdownPath: markdownPath$1(run.id),
9423
+ htmlPath: htmlPath$1(run.id),
8764
9424
  exportedAt
8765
9425
  };
8766
9426
  const documentRun = {
@@ -8786,7 +9446,7 @@ async function exportRunEvidence(run, options, baseUrl) {
8786
9446
  classification: analysis.classification,
8787
9447
  jenkinsReport: analysis.jenkinsReport
8788
9448
  };
8789
- const markdown = buildMarkdown(document);
9449
+ const markdown = buildMarkdown$1(document);
8790
9450
  mkdirSync(evidenceDir(run.id), { recursive: true });
8791
9451
  writeFileSync(
8792
9452
  evidence.jsonPath,
@@ -8795,7 +9455,7 @@ async function exportRunEvidence(run, options, baseUrl) {
8795
9455
  "utf8"
8796
9456
  );
8797
9457
  writeFileSync(evidence.markdownPath, markdown, "utf8");
8798
- writeFileSync(evidence.htmlPath, buildHtml(markdown, run.title), "utf8");
9458
+ writeFileSync(evidence.htmlPath, buildHtml$1(markdown, run.title), "utf8");
8799
9459
  const updatedRun = updateRunEvidence(run.id, evidence) ?? documentRun;
8800
9460
  return { ...document, run: updatedRun };
8801
9461
  }
@@ -8808,7 +9468,7 @@ function readEvidenceMarkdown(run) {
8808
9468
  return null;
8809
9469
  }
8810
9470
  }
8811
- async function readJsonBody(request) {
9471
+ async function readJsonBody$2(request) {
8812
9472
  try {
8813
9473
  const raw = await request.text();
8814
9474
  if (raw.trim().length === 0) return { ok: true, value: void 0 };
@@ -8817,7 +9477,7 @@ async function readJsonBody(request) {
8817
9477
  return { ok: false };
8818
9478
  }
8819
9479
  }
8820
- const Route$8 = createFileRoute("/api/runs/$runId/evidence")({
9480
+ const Route$a = createFileRoute("/api/runs/$runId/evidence")({
8821
9481
  server: {
8822
9482
  handlers: {
8823
9483
  GET: ({ params }) => {
@@ -8839,7 +9499,7 @@ const Route$8 = createFileRoute("/api/runs/$runId/evidence")({
8839
9499
  if (run === null) {
8840
9500
  return Response.json({ error: "Run not found" }, { status: 404 });
8841
9501
  }
8842
- const body = await readJsonBody(request);
9502
+ const body = await readJsonBody$2(request);
8843
9503
  if (!body.ok) {
8844
9504
  return Response.json({ error: "Invalid JSON body" }, { status: 400 });
8845
9505
  }
@@ -9473,7 +10133,7 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, isOpenAI)
9473
10133
  clearTimeout(timeoutId);
9474
10134
  }
9475
10135
  }
9476
- const Route$7 = createFileRoute("/api/providers/$providerId/test")({
10136
+ const Route$9 = createFileRoute("/api/providers/$providerId/test")({
9477
10137
  server: {
9478
10138
  handlers: {
9479
10139
  POST: async ({ params }) => {
@@ -9567,7 +10227,7 @@ async function readRegistryJson(response) {
9567
10227
  return null;
9568
10228
  }
9569
10229
  }
9570
- const Route$6 = createFileRoute("/api/providers/$providerId/model-metadata")({
10230
+ const Route$8 = createFileRoute("/api/providers/$providerId/model-metadata")({
9571
10231
  server: {
9572
10232
  handlers: {
9573
10233
  POST: async ({ params, request }) => {
@@ -9657,7 +10317,7 @@ const Route$6 = createFileRoute("/api/providers/$providerId/model-metadata")({
9657
10317
  const ReplayRequestSchema = object({
9658
10318
  modifiedBody: string()
9659
10319
  });
9660
- const Route$5 = createFileRoute("/api/logs/$id/replay")({
10320
+ const Route$7 = createFileRoute("/api/logs/$id/replay")({
9661
10321
  server: {
9662
10322
  handlers: {
9663
10323
  POST: async ({ params, request }) => {
@@ -9858,7 +10518,7 @@ const Route$5 = createFileRoute("/api/logs/$id/replay")({
9858
10518
  }
9859
10519
  }
9860
10520
  });
9861
- const Route$4 = createFileRoute("/api/logs/$id/chunks")({
10521
+ const Route$6 = createFileRoute("/api/logs/$id/chunks")({
9862
10522
  server: {
9863
10523
  handlers: {
9864
10524
  GET: async ({ params }) => {
@@ -9893,7 +10553,7 @@ const CandidateUpdateSchema = object({
9893
10553
  object({
9894
10554
  candidate: KnowledgeCandidateSchema
9895
10555
  });
9896
- const Route$3 = createFileRoute("/api/knowledge/candidates/$candidateId")({
10556
+ const Route$5 = createFileRoute("/api/knowledge/candidates/$candidateId")({
9897
10557
  server: {
9898
10558
  handlers: {
9899
10559
  PATCH: async ({ params, request }) => {
@@ -9921,6 +10581,372 @@ const Route$3 = createFileRoute("/api/knowledge/candidates/$candidateId")({
9921
10581
  }
9922
10582
  }
9923
10583
  });
10584
+ async function readJsonBody$1(request) {
10585
+ try {
10586
+ const raw = await request.text();
10587
+ if (raw.trim().length === 0) return { ok: true, value: {} };
10588
+ return { ok: true, value: JSON.parse(raw) };
10589
+ } catch {
10590
+ return { ok: false };
10591
+ }
10592
+ }
10593
+ const Route$4 = createFileRoute("/api/groups/$groupId/sessions")({
10594
+ server: {
10595
+ handlers: {
10596
+ GET: ({ params }) => {
10597
+ const group = getGroup(params.groupId);
10598
+ if (group === null) {
10599
+ return Response.json({ error: "Group not found" }, { status: 404 });
10600
+ }
10601
+ return Response.json({ groupId: group.id, members: group.members });
10602
+ },
10603
+ POST: async ({ params, request }) => {
10604
+ const body = await readJsonBody$1(request);
10605
+ if (!body.ok) {
10606
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
10607
+ }
10608
+ const parsed = AddInspectorGroupSessionInputSchema.safeParse(body.value);
10609
+ if (!parsed.success) {
10610
+ return Response.json(
10611
+ { error: "Invalid request body", details: parsed.error.format() },
10612
+ { status: 400 }
10613
+ );
10614
+ }
10615
+ const updated = addGroupSession(params.groupId, parsed.data);
10616
+ if (updated === null) {
10617
+ return Response.json({ error: "Group not found" }, { status: 404 });
10618
+ }
10619
+ return Response.json(updated);
10620
+ }
10621
+ }
10622
+ }
10623
+ });
10624
+ function groupEvidenceDir(groupId) {
10625
+ return join(getDataDir(), "evidence", "groups", groupId);
10626
+ }
10627
+ function jsonPath(groupId) {
10628
+ return join(groupEvidenceDir(groupId), "evidence.json");
10629
+ }
10630
+ function markdownPath(groupId) {
10631
+ return join(groupEvidenceDir(groupId), "evidence.md");
10632
+ }
10633
+ function htmlPath(groupId) {
10634
+ return join(groupEvidenceDir(groupId), "evidence.html");
10635
+ }
10636
+ function escapeMarkdownCell(value) {
10637
+ return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
10638
+ }
10639
+ function escapeHtml(value) {
10640
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
10641
+ }
10642
+ function formatText(value) {
10643
+ if (value === null || value === void 0 || value.length === 0) return "n/a";
10644
+ return value;
10645
+ }
10646
+ function formatNumber(value) {
10647
+ if (value === null || value === void 0) return "n/a";
10648
+ return String(value);
10649
+ }
10650
+ function collectSortedValues(values) {
10651
+ const set = /* @__PURE__ */ new Set();
10652
+ for (const value of values) {
10653
+ if (value !== null && value !== void 0 && value.length > 0) {
10654
+ set.add(value);
10655
+ }
10656
+ }
10657
+ return [...set].sort();
10658
+ }
10659
+ function addTokenUsage(total, session) {
10660
+ if (session === null) return total;
10661
+ return {
10662
+ input: total.input + session.tokenUsage.input,
10663
+ output: total.output + session.tokenUsage.output,
10664
+ cacheCreate: total.cacheCreate + session.tokenUsage.cacheCreate,
10665
+ cacheRead: total.cacheRead + session.tokenUsage.cacheRead,
10666
+ total: total.total + session.tokenUsage.total
10667
+ };
10668
+ }
10669
+ function memberEffectiveStatus(member) {
10670
+ if (member.member.status !== null) return member.member.status;
10671
+ if (member.run !== null) return member.run.status;
10672
+ return member.session?.status ?? "unknown";
10673
+ }
10674
+ function isCompletedMember(member) {
10675
+ const status = memberEffectiveStatus(member);
10676
+ return status === "completed";
10677
+ }
10678
+ function isFailedMember(member) {
10679
+ const status = memberEffectiveStatus(member);
10680
+ return status === "failed" || status === "cancelled";
10681
+ }
10682
+ function isRunningMember(member) {
10683
+ const status = memberEffectiveStatus(member);
10684
+ return status === "running" || status === "active";
10685
+ }
10686
+ function latestLogMetric(session, field) {
10687
+ if (session === null) return null;
10688
+ for (const log of session.latestLogs) {
10689
+ const value = log[field];
10690
+ if (value !== void 0 && value !== null) return value;
10691
+ }
10692
+ return null;
10693
+ }
10694
+ function buildSummary(members) {
10695
+ const tokenUsage = members.reduce(
10696
+ (total, member) => addTokenUsage(total, member.session),
10697
+ { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, total: 0 }
10698
+ );
10699
+ const sessionsWithData = members.filter((member) => member.session !== null).length;
10700
+ const requestCount = members.reduce(
10701
+ (total, member) => total + (member.session?.requestCount ?? 0),
10702
+ 0
10703
+ );
10704
+ const errorCount = members.reduce(
10705
+ (total, member) => total + (member.session?.errorCount ?? 0),
10706
+ 0
10707
+ );
10708
+ return {
10709
+ memberCount: members.length,
10710
+ sessionsWithData,
10711
+ missingSessions: members.length - sessionsWithData,
10712
+ requestCount,
10713
+ errorCount,
10714
+ tokenUsage,
10715
+ models: collectSortedValues(
10716
+ members.flatMap((member) => [member.member.model, ...member.session?.models ?? []])
10717
+ ),
10718
+ providers: collectSortedValues(
10719
+ members.flatMap((member) => [member.member.provider, ...member.session?.providers ?? []])
10720
+ ),
10721
+ completedMembers: members.filter(isCompletedMember).length,
10722
+ failedMembers: members.filter(isFailedMember).length,
10723
+ runningMembers: members.filter(isRunningMember).length
10724
+ };
10725
+ }
10726
+ function buildJenkinsReport(group, summary) {
10727
+ if (summary.failedMembers > 0 || summary.errorCount > 0 || group.status === "failed") {
10728
+ return {
10729
+ status: "FAIL",
10730
+ summary: `[FAIL] ${summary.failedMembers} failed members, ${summary.errorCount} captured errors.`,
10731
+ markdown: `# Jenkins Report: ${group.title}
10732
+
10733
+ Status: FAIL
10734
+
10735
+ ${summary.failedMembers} failed members and ${summary.errorCount} captured errors need triage.`
10736
+ };
10737
+ }
10738
+ if (summary.runningMembers > 0 || group.status === "running") {
10739
+ return {
10740
+ status: "RUNNING",
10741
+ summary: `[RUNNING] ${summary.runningMembers} group members are still active.`,
10742
+ markdown: `# Jenkins Report: ${group.title}
10743
+
10744
+ Status: RUNNING
10745
+
10746
+ ${summary.runningMembers} group members are still active.`
10747
+ };
10748
+ }
10749
+ if (summary.memberCount > 0 && summary.sessionsWithData > 0 && summary.errorCount === 0 && group.status === "completed") {
10750
+ return {
10751
+ status: "PASS",
10752
+ summary: `[PASS] ${summary.memberCount} group members completed with no captured errors.`,
10753
+ markdown: `# Jenkins Report: ${group.title}
10754
+
10755
+ Status: PASS
10756
+
10757
+ ${summary.memberCount} group members completed with no captured errors.`
10758
+ };
10759
+ }
10760
+ return {
10761
+ status: "UNKNOWN",
10762
+ summary: `[UNKNOWN] Group has ${summary.memberCount} members and ${summary.missingSessions} missing sessions.`,
10763
+ markdown: `# Jenkins Report: ${group.title}
10764
+
10765
+ Status: UNKNOWN
10766
+
10767
+ Exported group evidence, but the outcome is not conclusive yet.`
10768
+ };
10769
+ }
10770
+ function buildMemberRows(members) {
10771
+ if (members.length === 0) return ["No sessions have been attached to this group.", ""];
10772
+ const rows = [
10773
+ "| Label | Provider | Model | Session | Run | Status | Requests | Errors | Tokens | First Chunk | Stream Total | TPS | Inspector |",
10774
+ "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"
10775
+ ];
10776
+ for (const member of members) {
10777
+ const session = member.session;
10778
+ rows.push(
10779
+ [
10780
+ formatText(member.member.label),
10781
+ formatText(member.member.provider ?? session?.providers[0]),
10782
+ formatText(member.member.model ?? session?.lastModel),
10783
+ member.member.sessionId,
10784
+ formatText(member.member.runId),
10785
+ memberEffectiveStatus(member),
10786
+ formatNumber(session?.requestCount),
10787
+ formatNumber(session?.errorCount),
10788
+ formatNumber(session?.tokenUsage.total),
10789
+ formatNumber(latestLogMetric(session, "firstChunkMs")),
10790
+ formatNumber(latestLogMetric(session, "totalStreamMs")),
10791
+ formatNumber(latestLogMetric(session, "tokensPerSecond")),
10792
+ formatText(session?.inspectorUrl)
10793
+ ].map(escapeMarkdownCell).join(" | ").replace(/^/, "| ").replace(/$/, " |")
10794
+ );
10795
+ }
10796
+ rows.push("");
10797
+ return rows;
10798
+ }
10799
+ function buildMarkdown(document) {
10800
+ const group = document.group;
10801
+ const summary = document.summary;
10802
+ const lines = [
10803
+ `# ${group.title}`,
10804
+ "",
10805
+ `- Group ID: ${group.id}`,
10806
+ `- Kind: ${group.kind}`,
10807
+ `- Status: ${group.status}`,
10808
+ `- Project: ${group.project ?? "n/a"}`,
10809
+ `- Created: ${group.createdAt}`,
10810
+ `- Members: ${summary.memberCount}`,
10811
+ `- Sessions With Data: ${summary.sessionsWithData}`,
10812
+ `- Missing Sessions: ${summary.missingSessions}`,
10813
+ `- Requests: ${summary.requestCount}`,
10814
+ `- Errors: ${summary.errorCount}`,
10815
+ `- Tokens: ${summary.tokenUsage.total}`,
10816
+ `- Models: ${summary.models.length > 0 ? summary.models.join(", ") : "n/a"}`,
10817
+ `- Providers: ${summary.providers.length > 0 ? summary.providers.join(", ") : "n/a"}`,
10818
+ `- Evidence JSON: ${document.evidence.jsonPath}`,
10819
+ `- Evidence HTML: ${document.evidence.htmlPath}`,
10820
+ ""
10821
+ ];
10822
+ if (group.task !== null) {
10823
+ lines.push("## Task", "", group.task, "");
10824
+ }
10825
+ lines.push("## Evaluation Matrix", "", ...buildMemberRows(document.members));
10826
+ lines.push("## Jenkins Report", "", document.jenkinsReport.markdown, "");
10827
+ return `${lines.join("\n")}
10828
+ `;
10829
+ }
10830
+ function buildHtml(markdown, title) {
10831
+ return `<!doctype html>
10832
+ <html lang="en">
10833
+ <head>
10834
+ <meta charset="utf-8">
10835
+ <title>${escapeHtml(title)}</title>
10836
+ <style>
10837
+ body { font-family: system-ui, sans-serif; max-width: 1120px; margin: 40px auto; line-height: 1.5; }
10838
+ pre { white-space: pre-wrap; background: #f6f8fa; padding: 16px; border-radius: 8px; }
10839
+ </style>
10840
+ </head>
10841
+ <body>
10842
+ <pre>${escapeHtml(markdown)}</pre>
10843
+ </body>
10844
+ </html>
10845
+ `;
10846
+ }
10847
+ async function buildExportMembers(group, options, baseUrl) {
10848
+ const members = [];
10849
+ for (const member of group.members) {
10850
+ const run = member.runId === null ? null : getRun(member.runId);
10851
+ const session = await getSessionInfo(member.sessionId, {
10852
+ baseUrl,
10853
+ includeHistory: options?.includeHistory,
10854
+ latestLogLimit: options?.latestLogLimit
10855
+ });
10856
+ members.push({ member, run, session });
10857
+ }
10858
+ return members;
10859
+ }
10860
+ async function exportGroupEvidence(group, options, baseUrl) {
10861
+ const members = await buildExportMembers(group, options, baseUrl);
10862
+ const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
10863
+ const evidence = {
10864
+ jsonPath: jsonPath(group.id),
10865
+ markdownPath: markdownPath(group.id),
10866
+ htmlPath: htmlPath(group.id),
10867
+ exportedAt
10868
+ };
10869
+ const documentGroup = {
10870
+ ...group,
10871
+ evidence,
10872
+ updatedAt: exportedAt
10873
+ };
10874
+ const summary = buildSummary(members);
10875
+ const jenkinsReport = buildJenkinsReport(documentGroup, summary);
10876
+ const document = {
10877
+ group: documentGroup,
10878
+ evidence,
10879
+ members,
10880
+ summary,
10881
+ jenkinsReport
10882
+ };
10883
+ const markdown = buildMarkdown(document);
10884
+ mkdirSync(groupEvidenceDir(group.id), { recursive: true });
10885
+ writeFileSync(evidence.jsonPath, `${JSON.stringify(document, null, 2)}
10886
+ `, "utf8");
10887
+ writeFileSync(evidence.markdownPath, markdown, "utf8");
10888
+ writeFileSync(evidence.htmlPath, buildHtml(markdown, group.title), "utf8");
10889
+ const updatedGroup = updateGroupEvidence(group.id, evidence) ?? documentGroup;
10890
+ return { ...document, group: updatedGroup };
10891
+ }
10892
+ function readGroupEvidenceMarkdown(group) {
10893
+ const path2 = group.evidence?.markdownPath;
10894
+ if (path2 === void 0 || !existsSync(path2)) return null;
10895
+ try {
10896
+ return readFileSync(path2, "utf8");
10897
+ } catch {
10898
+ return null;
10899
+ }
10900
+ }
10901
+ async function readJsonBody(request) {
10902
+ try {
10903
+ const raw = await request.text();
10904
+ if (raw.trim().length === 0) return { ok: true, value: void 0 };
10905
+ return { ok: true, value: JSON.parse(raw) };
10906
+ } catch {
10907
+ return { ok: false };
10908
+ }
10909
+ }
10910
+ const Route$3 = createFileRoute("/api/groups/$groupId/evidence")({
10911
+ server: {
10912
+ handlers: {
10913
+ GET: ({ params }) => {
10914
+ const group = getGroup(params.groupId);
10915
+ if (group === null) {
10916
+ return Response.json({ error: "Group not found" }, { status: 404 });
10917
+ }
10918
+ const markdown = readGroupEvidenceMarkdown(group);
10919
+ if (group.evidence === null || markdown === null) {
10920
+ return Response.json(
10921
+ { error: "Evidence has not been exported for this group" },
10922
+ { status: 404 }
10923
+ );
10924
+ }
10925
+ return Response.json({ group, evidence: group.evidence, markdown });
10926
+ },
10927
+ POST: async ({ params, request }) => {
10928
+ const group = getGroup(params.groupId);
10929
+ if (group === null) {
10930
+ return Response.json({ error: "Group not found" }, { status: 404 });
10931
+ }
10932
+ const body = await readJsonBody(request);
10933
+ if (!body.ok) {
10934
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
10935
+ }
10936
+ const parsed = GroupEvidenceExportOptionsSchema.safeParse(body.value);
10937
+ if (!parsed.success) {
10938
+ return Response.json(
10939
+ { error: "Invalid request body", details: parsed.error.format() },
10940
+ { status: 400 }
10941
+ );
10942
+ }
10943
+ return Response.json(
10944
+ await exportGroupEvidence(group, parsed.data, new URL(request.url).origin)
10945
+ );
10946
+ }
10947
+ }
10948
+ }
10949
+ });
9924
10950
  const ProviderTestErrorTypeSchema = _enum([
9925
10951
  "timeout",
9926
10952
  "network_unreachable",
@@ -10344,146 +11370,166 @@ const Route = createFileRoute("/api/knowledge/candidates/$candidateId/promote")(
10344
11370
  }
10345
11371
  }
10346
11372
  });
10347
- const IndexRoute = Route$u.update({
11373
+ const IndexRoute = Route$y.update({
10348
11374
  id: "/",
10349
11375
  path: "/",
10350
- getParentRoute: () => Route$v
11376
+ getParentRoute: () => Route$z
10351
11377
  });
10352
- const SessionSessionIdRoute = Route$t.update({
11378
+ const SessionSessionIdRoute = Route$x.update({
10353
11379
  id: "/session/$sessionId",
10354
11380
  path: "/session/$sessionId",
10355
- getParentRoute: () => Route$v
11381
+ getParentRoute: () => Route$z
10356
11382
  });
10357
- const ProxySplatRoute = Route$s.update({
11383
+ const ProxySplatRoute = Route$w.update({
10358
11384
  id: "/proxy/$",
10359
11385
  path: "/proxy/$",
10360
- getParentRoute: () => Route$v
11386
+ getParentRoute: () => Route$z
10361
11387
  });
10362
- const ApiSessionsRoute = Route$r.update({
11388
+ const ApiSessionsRoute = Route$v.update({
10363
11389
  id: "/api/sessions",
10364
11390
  path: "/api/sessions",
10365
- getParentRoute: () => Route$v
11391
+ getParentRoute: () => Route$z
10366
11392
  });
10367
- const ApiRunsRoute = Route$q.update({
11393
+ const ApiRunsRoute = Route$u.update({
10368
11394
  id: "/api/runs",
10369
11395
  path: "/api/runs",
10370
- getParentRoute: () => Route$v
11396
+ getParentRoute: () => Route$z
10371
11397
  });
10372
- const ApiProvidersRoute = Route$p.update({
11398
+ const ApiProvidersRoute = Route$t.update({
10373
11399
  id: "/api/providers",
10374
11400
  path: "/api/providers",
10375
- getParentRoute: () => Route$v
11401
+ getParentRoute: () => Route$z
10376
11402
  });
10377
- const ApiModelsRoute = Route$o.update({
11403
+ const ApiModelsRoute = Route$s.update({
10378
11404
  id: "/api/models",
10379
11405
  path: "/api/models",
10380
- getParentRoute: () => Route$v
11406
+ getParentRoute: () => Route$z
10381
11407
  });
10382
- const ApiMcpRoute = Route$n.update({
11408
+ const ApiMcpRoute = Route$r.update({
10383
11409
  id: "/api/mcp",
10384
11410
  path: "/api/mcp",
10385
- getParentRoute: () => Route$v
11411
+ getParentRoute: () => Route$z
10386
11412
  });
10387
- const ApiLogsRoute = Route$m.update({
11413
+ const ApiLogsRoute = Route$q.update({
10388
11414
  id: "/api/logs",
10389
11415
  path: "/api/logs",
10390
- getParentRoute: () => Route$v
11416
+ getParentRoute: () => Route$z
10391
11417
  });
10392
- const ApiHealthRoute = Route$l.update({
11418
+ const ApiHealthRoute = Route$p.update({
10393
11419
  id: "/api/health",
10394
11420
  path: "/api/health",
10395
- getParentRoute: () => Route$v
11421
+ getParentRoute: () => Route$z
11422
+ });
11423
+ const ApiGroupsRoute = Route$o.update({
11424
+ id: "/api/groups",
11425
+ path: "/api/groups",
11426
+ getParentRoute: () => Route$z
10396
11427
  });
10397
- const ApiConfigRoute = Route$k.update({
11428
+ const ApiConfigRoute = Route$n.update({
10398
11429
  id: "/api/config",
10399
11430
  path: "/api/config",
10400
- getParentRoute: () => Route$v
11431
+ getParentRoute: () => Route$z
10401
11432
  });
10402
- const ApiRunsRunIdRoute = Route$j.update({
11433
+ const ApiRunsRunIdRoute = Route$m.update({
10403
11434
  id: "/$runId",
10404
11435
  path: "/$runId",
10405
11436
  getParentRoute: () => ApiRunsRoute
10406
11437
  });
10407
- const ApiProvidersScanRoute = Route$i.update({
11438
+ const ApiProvidersScanRoute = Route$l.update({
10408
11439
  id: "/scan",
10409
11440
  path: "/scan",
10410
11441
  getParentRoute: () => ApiProvidersRoute
10411
11442
  });
10412
- const ApiProvidersImportRoute = Route$h.update({
11443
+ const ApiProvidersImportRoute = Route$k.update({
10413
11444
  id: "/import",
10414
11445
  path: "/import",
10415
11446
  getParentRoute: () => ApiProvidersRoute
10416
11447
  });
10417
- const ApiProvidersExportRoute = Route$g.update({
11448
+ const ApiProvidersExportRoute = Route$j.update({
10418
11449
  id: "/export",
10419
11450
  path: "/export",
10420
11451
  getParentRoute: () => ApiProvidersRoute
10421
11452
  });
10422
- const ApiProvidersProviderIdRoute = Route$f.update({
11453
+ const ApiProvidersProviderIdRoute = Route$i.update({
10423
11454
  id: "/$providerId",
10424
11455
  path: "/$providerId",
10425
11456
  getParentRoute: () => ApiProvidersRoute
10426
11457
  });
10427
- const ApiLogsStreamRoute = Route$e.update({
11458
+ const ApiLogsStreamRoute = Route$h.update({
10428
11459
  id: "/stream",
10429
11460
  path: "/stream",
10430
11461
  getParentRoute: () => ApiLogsRoute
10431
11462
  });
10432
- const ApiLogsIdRoute = Route$d.update({
11463
+ const ApiLogsIdRoute = Route$g.update({
10433
11464
  id: "/$id",
10434
11465
  path: "/$id",
10435
11466
  getParentRoute: () => ApiLogsRoute
10436
11467
  });
10437
- const ApiKnowledgeSearchRoute = Route$c.update({
11468
+ const ApiKnowledgeSearchRoute = Route$f.update({
10438
11469
  id: "/api/knowledge/search",
10439
11470
  path: "/api/knowledge/search",
10440
- getParentRoute: () => Route$v
11471
+ getParentRoute: () => Route$z
10441
11472
  });
10442
- const ApiKnowledgeProjectContextRoute = Route$b.update({
11473
+ const ApiKnowledgeProjectContextRoute = Route$e.update({
10443
11474
  id: "/api/knowledge/project-context",
10444
11475
  path: "/api/knowledge/project-context",
10445
- getParentRoute: () => Route$v
11476
+ getParentRoute: () => Route$z
10446
11477
  });
10447
- const ApiKnowledgeCandidatesRoute = Route$a.update({
11478
+ const ApiKnowledgeCandidatesRoute = Route$d.update({
10448
11479
  id: "/api/knowledge/candidates",
10449
11480
  path: "/api/knowledge/candidates",
10450
- getParentRoute: () => Route$v
11481
+ getParentRoute: () => Route$z
11482
+ });
11483
+ const ApiGroupsGroupIdRoute = Route$c.update({
11484
+ id: "/$groupId",
11485
+ path: "/$groupId",
11486
+ getParentRoute: () => ApiGroupsRoute
10451
11487
  });
10452
- const ApiConfigPathsRoute = Route$9.update({
11488
+ const ApiConfigPathsRoute = Route$b.update({
10453
11489
  id: "/paths",
10454
11490
  path: "/paths",
10455
11491
  getParentRoute: () => ApiConfigRoute
10456
11492
  });
10457
- const ApiRunsRunIdEvidenceRoute = Route$8.update({
11493
+ const ApiRunsRunIdEvidenceRoute = Route$a.update({
10458
11494
  id: "/evidence",
10459
11495
  path: "/evidence",
10460
11496
  getParentRoute: () => ApiRunsRunIdRoute
10461
11497
  });
10462
- const ApiProvidersProviderIdTestRoute = Route$7.update({
11498
+ const ApiProvidersProviderIdTestRoute = Route$9.update({
10463
11499
  id: "/test",
10464
11500
  path: "/test",
10465
11501
  getParentRoute: () => ApiProvidersProviderIdRoute
10466
11502
  });
10467
- const ApiProvidersProviderIdModelMetadataRoute = Route$6.update({
11503
+ const ApiProvidersProviderIdModelMetadataRoute = Route$8.update({
10468
11504
  id: "/model-metadata",
10469
11505
  path: "/model-metadata",
10470
11506
  getParentRoute: () => ApiProvidersProviderIdRoute
10471
11507
  });
10472
- const ApiLogsIdReplayRoute = Route$5.update({
11508
+ const ApiLogsIdReplayRoute = Route$7.update({
10473
11509
  id: "/replay",
10474
11510
  path: "/replay",
10475
11511
  getParentRoute: () => ApiLogsIdRoute
10476
11512
  });
10477
- const ApiLogsIdChunksRoute = Route$4.update({
11513
+ const ApiLogsIdChunksRoute = Route$6.update({
10478
11514
  id: "/chunks",
10479
11515
  path: "/chunks",
10480
11516
  getParentRoute: () => ApiLogsIdRoute
10481
11517
  });
10482
- const ApiKnowledgeCandidatesCandidateIdRoute = Route$3.update({
11518
+ const ApiKnowledgeCandidatesCandidateIdRoute = Route$5.update({
10483
11519
  id: "/$candidateId",
10484
11520
  path: "/$candidateId",
10485
11521
  getParentRoute: () => ApiKnowledgeCandidatesRoute
10486
11522
  });
11523
+ const ApiGroupsGroupIdSessionsRoute = Route$4.update({
11524
+ id: "/sessions",
11525
+ path: "/sessions",
11526
+ getParentRoute: () => ApiGroupsGroupIdRoute
11527
+ });
11528
+ const ApiGroupsGroupIdEvidenceRoute = Route$3.update({
11529
+ id: "/evidence",
11530
+ path: "/evidence",
11531
+ getParentRoute: () => ApiGroupsGroupIdRoute
11532
+ });
10487
11533
  const ApiProvidersProviderIdTestLogRoute = Route$2.update({
10488
11534
  id: "/log",
10489
11535
  path: "/log",
@@ -10492,7 +11538,7 @@ const ApiProvidersProviderIdTestLogRoute = Route$2.update({
10492
11538
  const ApiKnowledgeSessionsSessionIdCandidatesRoute = Route$1.update({
10493
11539
  id: "/api/knowledge/sessions/$sessionId/candidates",
10494
11540
  path: "/api/knowledge/sessions/$sessionId/candidates",
10495
- getParentRoute: () => Route$v
11541
+ getParentRoute: () => Route$z
10496
11542
  });
10497
11543
  const ApiKnowledgeCandidatesCandidateIdPromoteRoute = Route.update({
10498
11544
  id: "/promote",
@@ -10505,6 +11551,17 @@ const ApiConfigRouteChildren = {
10505
11551
  const ApiConfigRouteWithChildren = ApiConfigRoute._addFileChildren(
10506
11552
  ApiConfigRouteChildren
10507
11553
  );
11554
+ const ApiGroupsGroupIdRouteChildren = {
11555
+ ApiGroupsGroupIdEvidenceRoute,
11556
+ ApiGroupsGroupIdSessionsRoute
11557
+ };
11558
+ const ApiGroupsGroupIdRouteWithChildren = ApiGroupsGroupIdRoute._addFileChildren(ApiGroupsGroupIdRouteChildren);
11559
+ const ApiGroupsRouteChildren = {
11560
+ ApiGroupsGroupIdRoute: ApiGroupsGroupIdRouteWithChildren
11561
+ };
11562
+ const ApiGroupsRouteWithChildren = ApiGroupsRoute._addFileChildren(
11563
+ ApiGroupsRouteChildren
11564
+ );
10508
11565
  const ApiLogsIdRouteChildren = {
10509
11566
  ApiLogsIdChunksRoute,
10510
11567
  ApiLogsIdReplayRoute
@@ -10564,6 +11621,7 @@ const ApiKnowledgeCandidatesRouteWithChildren = ApiKnowledgeCandidatesRoute._add
10564
11621
  const rootRouteChildren = {
10565
11622
  IndexRoute,
10566
11623
  ApiConfigRoute: ApiConfigRouteWithChildren,
11624
+ ApiGroupsRoute: ApiGroupsRouteWithChildren,
10567
11625
  ApiHealthRoute,
10568
11626
  ApiLogsRoute: ApiLogsRouteWithChildren,
10569
11627
  ApiMcpRoute,
@@ -10578,7 +11636,7 @@ const rootRouteChildren = {
10578
11636
  ApiKnowledgeSearchRoute,
10579
11637
  ApiKnowledgeSessionsSessionIdCandidatesRoute
10580
11638
  };
10581
- const routeTree = Route$v._addFileChildren(rootRouteChildren)._addFileTypes();
11639
+ const routeTree = Route$z._addFileChildren(rootRouteChildren)._addFileTypes();
10582
11640
  function getRouter() {
10583
11641
  const router2 = createRouter({
10584
11642
  routeTree,
@@ -10598,7 +11656,7 @@ export {
10598
11656
  MAX_SLOW_RESPONSE_THRESHOLD_SECONDS as M,
10599
11657
  OpenAIRequestSchema as O,
10600
11658
  ProviderConfigSchema as P,
10601
- Route$t as R,
11659
+ Route$x as R,
10602
11660
  TimeDisplayFormatSchema as T,
10603
11661
  DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS as a,
10604
11662
  DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS as b,