@tonyclaw/agent-inspector 2.0.26 → 2.0.28

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 (43) hide show
  1. package/.output/cli.js +169 -13
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-DBTFzxG5.js → CompareDrawer-CicCP3Hb.js} +1 -1
  4. package/.output/public/assets/{ProxyViewerContainer-ivZk8MgE.js → ProxyViewerContainer-BccuA6p5.js} +4 -4
  5. package/.output/public/assets/{ReplayDialog-syW2hDNE.js → ReplayDialog-Bc8q3ujm.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-BkuRtY9o.js → RequestAnatomy-Coyy3zcH.js} +1 -1
  7. package/.output/public/assets/{ResponseView-BwyvEvoE.js → ResponseView-BWJzgh0c.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-BY4MbPKg.js → StreamingChunkSequence-BSC3uuE5.js} +1 -1
  9. package/.output/public/assets/_sessionId-9rEF0uSE.js +1 -0
  10. package/.output/public/assets/index-B9_VaAWl.js +1 -0
  11. package/.output/public/assets/{main-DKRDRBdd.js → main-Brj0Gn91.js} +2 -2
  12. package/.output/server/_libs/modelcontextprotocol__server.mjs +228 -0
  13. package/.output/server/{_sessionId-VDd4N_1B.mjs → _sessionId-DDdyKVG-.mjs} +3 -3
  14. package/.output/server/_ssr/{CompareDrawer-BQZlxsOC.mjs → CompareDrawer-XSExe2ml.mjs} +2 -2
  15. package/.output/server/_ssr/{ProxyViewerContainer-njY2oQCc.mjs → ProxyViewerContainer-B5pIiBZN.mjs} +7 -7
  16. package/.output/server/_ssr/{ReplayDialog-B8lkdAIh.mjs → ReplayDialog-BOAu0ric.mjs} +3 -3
  17. package/.output/server/_ssr/{RequestAnatomy-CwOTfdwS.mjs → RequestAnatomy-C8Q5lozD.mjs} +2 -2
  18. package/.output/server/_ssr/{ResponseView-3Iz_mZpd.mjs → ResponseView-a_iJfPUF.mjs} +2 -2
  19. package/.output/server/_ssr/{StreamingChunkSequence-hmJcQNW5.mjs → StreamingChunkSequence-YMKC0j8U.mjs} +2 -2
  20. package/.output/server/_ssr/{index-UuTKM4aC.mjs → index-CkDwBPzI.mjs} +2 -2
  21. package/.output/server/_ssr/index.mjs +2 -2
  22. package/.output/server/_ssr/{router-C7InHrxE.mjs → router-DXOXdFFM.mjs} +1816 -151
  23. package/.output/server/_tanstack-start-manifest_v-DPf3uJys.mjs +4 -0
  24. package/.output/server/index.mjs +64 -64
  25. package/README.md +55 -1
  26. package/package.json +1 -1
  27. package/src/cli/networkHints.ts +150 -0
  28. package/src/cli.ts +72 -13
  29. package/src/lib/runContract.ts +162 -0
  30. package/src/mcp/server.ts +554 -2
  31. package/src/mcp/toolHandlers.ts +154 -0
  32. package/src/proxy/evidenceAnalysis.ts +522 -0
  33. package/src/proxy/evidenceExporter.ts +215 -0
  34. package/src/proxy/logSearch.ts +118 -0
  35. package/src/proxy/runFailures.ts +100 -0
  36. package/src/proxy/runStore.ts +159 -0
  37. package/src/routes/api/logs.ts +16 -0
  38. package/src/routes/api/runs.$runId.evidence.ts +62 -0
  39. package/src/routes/api/runs.$runId.ts +50 -0
  40. package/src/routes/api/runs.ts +58 -0
  41. package/.output/public/assets/_sessionId-XlPUgIIb.js +0 -1
  42. package/.output/public/assets/index-By11a28-.js +0 -1
  43. package/.output/server/_tanstack-start-manifest_v-B6yfnMHA.mjs +0 -4
@@ -1,11 +1,11 @@
1
1
  import { c as createRouter, a as createRootRoute, b as createFileRoute, l as lazyRouteComponent, O as Outlet, H as HeadContent, S as Scripts } from "../_libs/tanstack__react-router.mjs";
2
2
  import { j as jsxRuntimeExports } from "../_libs/react.mjs";
3
3
  import { S as SWRConfig } from "../_libs/swr.mjs";
4
- import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync, createReadStream, readdirSync, statSync, cpSync, rmSync, createWriteStream } from "node:fs";
4
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, renameSync, copyFileSync, unlinkSync, createReadStream, readdirSync, statSync, cpSync, rmSync, createWriteStream } from "node:fs";
5
5
  import fs, { mkdir, appendFile, readFile, writeFile, readdir, open, stat, unlink, rename } from "node:fs/promises";
6
6
  import { createInterface } from "node:readline";
7
7
  import { Buffer } from "node:buffer";
8
- import path, { join, isAbsolute, dirname } from "node:path";
8
+ import path, { join, dirname, isAbsolute } from "node:path";
9
9
  import { execFile, exec, spawn } from "node:child_process";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { C as Conf } from "../_libs/conf.mjs";
@@ -13,9 +13,9 @@ import { randomUUID } from "crypto";
13
13
  import { promisify } from "node:util";
14
14
  import { Worker } from "node:worker_threads";
15
15
  import { randomUUID as randomUUID$1 } from "node:crypto";
16
- import { M as McpServer, W as WebStandardStreamableHTTPServerTransport } from "../_libs/modelcontextprotocol__server.mjs";
16
+ import { M as McpServer, W as WebStandardStreamableHTTPServerTransport, R as ResourceTemplate } from "../_libs/modelcontextprotocol__server.mjs";
17
17
  import { homedir } from "node:os";
18
- import { d as object, b as string, a as array, _ as _enum, u as union, n as number, c as boolean, e as unknown, l as literal, r as record, g as discriminatedUnion, h as _null, k as lazy } from "../_libs/zod.mjs";
18
+ import { d as object, b as string, a as array, r as record, _ as _enum, u as union, n as number, c as boolean, e as unknown, l as literal, k as lazy, g as discriminatedUnion, h as _null } from "../_libs/zod.mjs";
19
19
  import "../_libs/tiny-warning.mjs";
20
20
  import "../_libs/tanstack__router-core.mjs";
21
21
  import "../_libs/cookie-es.mjs";
@@ -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-DsiKfWCp.css";
54
- const Route$s = createRootRoute({
54
+ const Route$v = 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-UuTKM4aC.mjs");
97
- const Route$r = createFileRoute("/")({
96
+ const $$splitComponentImporter$1 = () => import("./index-CkDwBPzI.mjs");
97
+ const Route$u = 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-VDd4N_1B.mjs");
140
- const Route$q = createFileRoute("/session/$sessionId")({
139
+ const $$splitComponentImporter = () => import("../_sessionId-DDdyKVG-.mjs");
140
+ const Route$t = createFileRoute("/session/$sessionId")({
141
141
  component: lazyRouteComponent($$splitComponentImporter, "component"),
142
142
  parseParams: (params) => ({
143
143
  sessionId: decodeSessionIdFromPath(params.sessionId)
@@ -4872,7 +4872,7 @@ async function handleProxy(req) {
4872
4872
  }
4873
4873
  return handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log, captureFullDetails);
4874
4874
  }
4875
- const Route$p = createFileRoute("/proxy/$")({
4875
+ const Route$s = createFileRoute("/proxy/$")({
4876
4876
  server: {
4877
4877
  handlers: {
4878
4878
  GET: ({ request }) => handleProxy(request),
@@ -4884,7 +4884,7 @@ const Route$p = createFileRoute("/proxy/$")({
4884
4884
  }
4885
4885
  }
4886
4886
  });
4887
- function parsePositiveInt$1(value) {
4887
+ function parsePositiveInt$2(value) {
4888
4888
  if (value === null) return void 0;
4889
4889
  const parsed = Number(value);
4890
4890
  if (!Number.isInteger(parsed) || parsed < 1) return void 0;
@@ -4893,7 +4893,7 @@ function parsePositiveInt$1(value) {
4893
4893
  function parseBooleanFlag(value) {
4894
4894
  return value === "1" || value === "true";
4895
4895
  }
4896
- const Route$o = createFileRoute("/api/sessions")({
4896
+ const Route$r = createFileRoute("/api/sessions")({
4897
4897
  server: {
4898
4898
  handlers: {
4899
4899
  GET: async ({ request }) => {
@@ -4906,7 +4906,7 @@ const Route$o = createFileRoute("/api/sessions")({
4906
4906
  const info = await getSessionInfo(sessionId, {
4907
4907
  baseUrl: url.origin,
4908
4908
  includeHistory: parseBooleanFlag(url.searchParams.get("includeHistory")),
4909
- latestLogLimit: parsePositiveInt$1(url.searchParams.get("limit"))
4909
+ latestLogLimit: parsePositiveInt$2(url.searchParams.get("limit"))
4910
4910
  });
4911
4911
  if (info === null) {
4912
4912
  return Response.json({ error: "Session not found" }, { status: 404 });
@@ -4922,6 +4922,415 @@ const Route$o = createFileRoute("/api/sessions")({
4922
4922
  }
4923
4923
  }
4924
4924
  });
4925
+ const SessionTokenUsageSchema = object({
4926
+ input: number().int().nonnegative(),
4927
+ output: number().int().nonnegative(),
4928
+ cacheCreate: number().int().nonnegative(),
4929
+ cacheRead: number().int().nonnegative(),
4930
+ total: number().int().nonnegative()
4931
+ });
4932
+ const SessionLogSummarySchema = object({
4933
+ id: number().int().positive(),
4934
+ timestamp: string(),
4935
+ provider: string().nullable(),
4936
+ model: string().nullable(),
4937
+ apiFormat: _enum(["anthropic", "openai", "unknown"]),
4938
+ method: string(),
4939
+ path: string(),
4940
+ status: number().nullable(),
4941
+ isStreaming: boolean(),
4942
+ hasError: boolean(),
4943
+ error: string().nullable(),
4944
+ latencyMs: number().nullable(),
4945
+ tokens: object({
4946
+ input: number().nullable(),
4947
+ output: number().nullable(),
4948
+ cacheCreate: number().nullable(),
4949
+ cacheRead: number().nullable()
4950
+ }),
4951
+ sessionId: string().nullable()
4952
+ });
4953
+ const SessionInfoSchema = object({
4954
+ id: string(),
4955
+ status: _enum(["active", "failed", "completed", "empty"]),
4956
+ logCoverage: _enum(["recent", "history"]),
4957
+ source: _enum(["explicit", "client-process", "client-connection", "provider-test"]).nullable(),
4958
+ runtimeMode: _enum(["in-process", "worker-thread", "child-process"]).nullable(),
4959
+ createdAt: string().nullable(),
4960
+ updatedAt: string().nullable(),
4961
+ requestCount: number().int().nonnegative(),
4962
+ activeRequests: number().int().nonnegative(),
4963
+ completedRequests: number().int().nonnegative(),
4964
+ failedRequests: number().int().nonnegative(),
4965
+ queuedTasks: number().int().nonnegative(),
4966
+ runningTasks: number().int().nonnegative(),
4967
+ lastTaskError: string().nullable(),
4968
+ logCount: number().int().nonnegative(),
4969
+ errorCount: number().int().nonnegative(),
4970
+ models: array(string()),
4971
+ providers: array(string()),
4972
+ tokenUsage: SessionTokenUsageSchema,
4973
+ lastLogId: number().nullable(),
4974
+ lastModel: string().nullable(),
4975
+ clientPid: number().nullable(),
4976
+ clientProjectFolder: string().nullable(),
4977
+ inspectorPath: string(),
4978
+ inspectorUrl: string(),
4979
+ apiPath: string(),
4980
+ apiUrl: string(),
4981
+ compactLogsPath: string(),
4982
+ compactLogsUrl: string(),
4983
+ latestLogLimit: number().int().positive(),
4984
+ latestLogs: array(SessionLogSummarySchema)
4985
+ });
4986
+ const InspectorRunStatusSchema = _enum([
4987
+ "created",
4988
+ "running",
4989
+ "completed",
4990
+ "failed",
4991
+ "cancelled"
4992
+ ]);
4993
+ const InspectorRunEvidenceSchema = object({
4994
+ jsonPath: string(),
4995
+ markdownPath: string(),
4996
+ htmlPath: string(),
4997
+ exportedAt: string()
4998
+ });
4999
+ const RunTimelineEventKindSchema = _enum([
5000
+ "run-created",
5001
+ "session-started",
5002
+ "request",
5003
+ "session-finished",
5004
+ "evidence-exported"
5005
+ ]);
5006
+ const RunTimelineEventSeveritySchema = _enum(["info", "success", "warning", "error"]);
5007
+ const RunTimelineEventSchema = object({
5008
+ timestamp: string(),
5009
+ kind: RunTimelineEventKindSchema,
5010
+ severity: RunTimelineEventSeveritySchema,
5011
+ title: string(),
5012
+ details: string(),
5013
+ logId: number().int().positive().nullable(),
5014
+ status: number().nullable(),
5015
+ model: string().nullable(),
5016
+ provider: string().nullable(),
5017
+ latencyMs: number().nullable(),
5018
+ isStreaming: boolean().nullable()
5019
+ });
5020
+ const FailureCategorySchema = _enum([
5021
+ "none",
5022
+ "active",
5023
+ "provider-timeout",
5024
+ "provider-http-error",
5025
+ "provider-auth-error",
5026
+ "provider-rate-limit",
5027
+ "network-or-container",
5028
+ "streaming-mismatch",
5029
+ "context-pressure",
5030
+ "runtime-task-error",
5031
+ "model-output-error",
5032
+ "unknown-failure",
5033
+ "no-session-data"
5034
+ ]);
5035
+ const FailureClassificationSchema = object({
5036
+ outcome: _enum(["success", "failure", "active", "unknown"]),
5037
+ severity: _enum(["none", "low", "medium", "high"]),
5038
+ category: FailureCategorySchema,
5039
+ summary: string(),
5040
+ evidenceLogIds: array(number().int().positive()),
5041
+ hints: array(string())
5042
+ });
5043
+ const JenkinsReportSchema = object({
5044
+ status: _enum(["PASS", "FAIL", "RUNNING", "UNKNOWN"]),
5045
+ summary: string(),
5046
+ markdown: string()
5047
+ });
5048
+ const InspectorRunSchema = object({
5049
+ id: string(),
5050
+ sessionId: string(),
5051
+ title: string(),
5052
+ task: string().nullable(),
5053
+ project: string().nullable(),
5054
+ agent: string().nullable(),
5055
+ status: InspectorRunStatusSchema,
5056
+ tags: array(string()),
5057
+ metadata: record(string(), JsonValueSchema),
5058
+ evidence: InspectorRunEvidenceSchema.nullable(),
5059
+ createdAt: string(),
5060
+ updatedAt: string()
5061
+ });
5062
+ const CreateInspectorRunInputSchema = object({
5063
+ runId: string().min(1).optional(),
5064
+ sessionId: string().min(1).optional(),
5065
+ title: string().min(1).optional(),
5066
+ task: string().nullable().optional(),
5067
+ project: string().nullable().optional(),
5068
+ agent: string().nullable().optional(),
5069
+ status: InspectorRunStatusSchema.optional(),
5070
+ tags: array(string()).optional(),
5071
+ metadata: record(string(), JsonValueSchema).optional()
5072
+ }).optional();
5073
+ const UpdateInspectorRunInputSchema = object({
5074
+ sessionId: string().min(1).optional(),
5075
+ title: string().min(1).optional(),
5076
+ task: string().nullable().optional(),
5077
+ project: string().nullable().optional(),
5078
+ agent: string().nullable().optional(),
5079
+ status: InspectorRunStatusSchema.optional(),
5080
+ tags: array(string()).optional(),
5081
+ metadata: record(string(), JsonValueSchema).optional()
5082
+ });
5083
+ const EvidenceExportOptionsSchema = object({
5084
+ includeHistory: boolean().optional(),
5085
+ latestLogLimit: number().int().positive().max(50).optional()
5086
+ }).optional();
5087
+ const EvidenceExportResultSchema = object({
5088
+ run: InspectorRunSchema,
5089
+ evidence: InspectorRunEvidenceSchema,
5090
+ session: SessionInfoSchema.nullable(),
5091
+ timeline: array(RunTimelineEventSchema),
5092
+ classification: FailureClassificationSchema,
5093
+ jenkinsReport: JenkinsReportSchema
5094
+ });
5095
+ const RecentFailureSchema = object({
5096
+ run: InspectorRunSchema,
5097
+ classification: FailureClassificationSchema,
5098
+ evidence: InspectorRunEvidenceSchema.nullable(),
5099
+ jenkinsReport: JenkinsReportSchema.nullable(),
5100
+ sessionId: string(),
5101
+ inspectorUrl: string().nullable(),
5102
+ evidenceMarkdownPath: string().nullable(),
5103
+ updatedAt: string()
5104
+ });
5105
+ const RecentFailuresResponseSchema = object({
5106
+ failures: array(RecentFailureSchema),
5107
+ total: number().int().nonnegative(),
5108
+ limit: number().int().positive()
5109
+ });
5110
+ const RunsFileSchema = object({
5111
+ runs: array(InspectorRunSchema)
5112
+ });
5113
+ function runsFilePath() {
5114
+ return join(getDataDir(), "runs.json");
5115
+ }
5116
+ function nowIso() {
5117
+ return (/* @__PURE__ */ new Date()).toISOString();
5118
+ }
5119
+ function stableId(input) {
5120
+ return input.trim().replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
5121
+ }
5122
+ function createRunId() {
5123
+ return `run_${Date.now().toString(36)}_${randomUUID$1().slice(0, 8)}`;
5124
+ }
5125
+ function normalizeRunId(input) {
5126
+ if (input === void 0) return createRunId();
5127
+ const normalized = stableId(input);
5128
+ return normalized.length > 0 ? normalized : createRunId();
5129
+ }
5130
+ function normalizeSessionId(input, runId) {
5131
+ if (input === void 0 || input.trim().length === 0) return runId;
5132
+ return input.trim();
5133
+ }
5134
+ function readRuns() {
5135
+ const filePath = runsFilePath();
5136
+ if (!existsSync(filePath)) return [];
5137
+ try {
5138
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
5139
+ const result = RunsFileSchema.safeParse(parsed);
5140
+ return result.success ? result.data.runs : [];
5141
+ } catch {
5142
+ return [];
5143
+ }
5144
+ }
5145
+ function writeRuns(runs) {
5146
+ const filePath = runsFilePath();
5147
+ mkdirSync(dirname(filePath), { recursive: true });
5148
+ writeFileSync(filePath, `${JSON.stringify({ runs }, null, 2)}
5149
+ `, "utf8");
5150
+ }
5151
+ function listRuns() {
5152
+ return readRuns().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
5153
+ }
5154
+ function getRun(runId) {
5155
+ return readRuns().find((run) => run.id === runId) ?? null;
5156
+ }
5157
+ function createRun(input) {
5158
+ const parsed = CreateInspectorRunInputSchema.parse(input);
5159
+ const runId = normalizeRunId(parsed?.runId);
5160
+ const existing = getRun(runId);
5161
+ if (existing !== null) return existing;
5162
+ const timestamp = nowIso();
5163
+ const sessionId = normalizeSessionId(parsed?.sessionId, runId);
5164
+ const run = {
5165
+ id: runId,
5166
+ sessionId,
5167
+ title: parsed?.title ?? `Inspector Run ${runId}`,
5168
+ task: parsed?.task ?? null,
5169
+ project: parsed?.project ?? null,
5170
+ agent: parsed?.agent ?? null,
5171
+ status: parsed?.status ?? "created",
5172
+ tags: parsed?.tags ?? [],
5173
+ metadata: parsed?.metadata ?? {},
5174
+ evidence: null,
5175
+ createdAt: timestamp,
5176
+ updatedAt: timestamp
5177
+ };
5178
+ writeRuns([...readRuns(), run]);
5179
+ return run;
5180
+ }
5181
+ function updateRunEvidence(runId, evidence) {
5182
+ const runs = readRuns();
5183
+ let updatedRun = null;
5184
+ const updatedRuns = runs.map((run) => {
5185
+ if (run.id !== runId) return run;
5186
+ updatedRun = {
5187
+ ...run,
5188
+ evidence,
5189
+ updatedAt: evidence.exportedAt
5190
+ };
5191
+ return updatedRun;
5192
+ });
5193
+ if (updatedRun === null) return null;
5194
+ writeRuns(updatedRuns);
5195
+ return updatedRun;
5196
+ }
5197
+ function updateRun(runId, input) {
5198
+ const parsed = UpdateInspectorRunInputSchema.parse(input);
5199
+ const runs = readRuns();
5200
+ let updatedRun = null;
5201
+ const updatedAt = nowIso();
5202
+ const updatedRuns = runs.map((run) => {
5203
+ if (run.id !== runId) return run;
5204
+ updatedRun = {
5205
+ ...run,
5206
+ sessionId: parsed.sessionId === void 0 ? run.sessionId : normalizeSessionId(parsed.sessionId, run.id),
5207
+ title: parsed.title ?? run.title,
5208
+ task: parsed.task === void 0 ? run.task : parsed.task,
5209
+ project: parsed.project === void 0 ? run.project : parsed.project,
5210
+ agent: parsed.agent === void 0 ? run.agent : parsed.agent,
5211
+ status: parsed.status ?? run.status,
5212
+ tags: parsed.tags ?? run.tags,
5213
+ metadata: parsed.metadata === void 0 ? run.metadata : { ...run.metadata, ...parsed.metadata },
5214
+ updatedAt
5215
+ };
5216
+ return updatedRun;
5217
+ });
5218
+ if (updatedRun === null) return null;
5219
+ writeRuns(updatedRuns);
5220
+ return updatedRun;
5221
+ }
5222
+ const RECENT_FAILURES_DEFAULT_LIMIT = 5;
5223
+ const RECENT_FAILURES_MAX_LIMIT = 20;
5224
+ function clampRecentFailuresLimit(value) {
5225
+ if (!Number.isInteger(value) || value < 1) return RECENT_FAILURES_DEFAULT_LIMIT;
5226
+ return Math.min(value, RECENT_FAILURES_MAX_LIMIT);
5227
+ }
5228
+ function readEvidenceDocument(run) {
5229
+ const path2 = run.evidence?.jsonPath;
5230
+ if (path2 === void 0 || !existsSync(path2)) return null;
5231
+ try {
5232
+ const parsed = JSON.parse(readFileSync(path2, "utf8"));
5233
+ const result = EvidenceExportResultSchema.safeParse(parsed);
5234
+ return result.success ? result.data : null;
5235
+ } catch {
5236
+ return null;
5237
+ }
5238
+ }
5239
+ function syntheticFailedClassification(run) {
5240
+ return {
5241
+ outcome: "failure",
5242
+ severity: "high",
5243
+ category: "unknown-failure",
5244
+ summary: `Run ${run.id} is marked ${run.status}, but no exported evidence document was available.`,
5245
+ evidenceLogIds: [],
5246
+ hints: [
5247
+ "Call inspector_export_evidence for this run to collect session metrics, failing log ids, and artifact paths.",
5248
+ "Confirm the run's sessionId matches the coding agent traffic captured by Inspector."
5249
+ ]
5250
+ };
5251
+ }
5252
+ function shouldIncludeRun(run, document, classification) {
5253
+ if (run.status === "failed" || run.status === "cancelled") return true;
5254
+ if (document === null) return false;
5255
+ return classification.outcome !== "success";
5256
+ }
5257
+ function evidenceFor(run, document) {
5258
+ return document?.evidence ?? run.evidence;
5259
+ }
5260
+ function jenkinsReportFor(document) {
5261
+ return document?.jenkinsReport ?? null;
5262
+ }
5263
+ function failureFromRun(run) {
5264
+ const document = readEvidenceDocument(run);
5265
+ const classification = document === null ? syntheticFailedClassification(run) : document.classification;
5266
+ if (!shouldIncludeRun(run, document, classification)) return null;
5267
+ const evidence = evidenceFor(run, document);
5268
+ return {
5269
+ run,
5270
+ classification,
5271
+ evidence,
5272
+ jenkinsReport: jenkinsReportFor(document),
5273
+ sessionId: run.sessionId,
5274
+ inspectorUrl: document?.session?.inspectorUrl ?? null,
5275
+ evidenceMarkdownPath: evidence?.markdownPath ?? null,
5276
+ updatedAt: evidence?.exportedAt ?? run.updatedAt
5277
+ };
5278
+ }
5279
+ function listRecentFailures(limit) {
5280
+ const failures = listRuns().map(failureFromRun).filter((failure) => failure !== null).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
5281
+ const clampedLimit = clampRecentFailuresLimit(limit);
5282
+ return {
5283
+ failures: failures.slice(0, clampedLimit),
5284
+ total: failures.length,
5285
+ limit: clampedLimit
5286
+ };
5287
+ }
5288
+ async function readJsonBody$2(request) {
5289
+ try {
5290
+ const raw = await request.text();
5291
+ if (raw.trim().length === 0) return { ok: true, value: void 0 };
5292
+ return { ok: true, value: JSON.parse(raw) };
5293
+ } catch {
5294
+ return { ok: false };
5295
+ }
5296
+ }
5297
+ function parsePositiveInt$1(value, fallback) {
5298
+ if (value === null) return fallback;
5299
+ const parsed = Number(value);
5300
+ if (!Number.isInteger(parsed) || parsed < 1) return fallback;
5301
+ return parsed;
5302
+ }
5303
+ const Route$q = createFileRoute("/api/runs")({
5304
+ server: {
5305
+ handlers: {
5306
+ GET: ({ request }) => {
5307
+ const url = new URL(request.url);
5308
+ if (url.searchParams.get("failures") === "1") {
5309
+ const limit = parsePositiveInt$1(
5310
+ url.searchParams.get("limit"),
5311
+ RECENT_FAILURES_DEFAULT_LIMIT
5312
+ );
5313
+ return Response.json(listRecentFailures(limit));
5314
+ }
5315
+ return Response.json({ runs: listRuns() });
5316
+ },
5317
+ POST: async ({ request }) => {
5318
+ const body = await readJsonBody$2(request);
5319
+ if (!body.ok) {
5320
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
5321
+ }
5322
+ const parsed = CreateInspectorRunInputSchema.safeParse(body.value);
5323
+ if (!parsed.success) {
5324
+ return Response.json(
5325
+ { error: "Invalid request body", details: parsed.error.format() },
5326
+ { status: 400 }
5327
+ );
5328
+ }
5329
+ return Response.json(createRun(parsed.data), { status: 201 });
5330
+ }
5331
+ }
5332
+ }
5333
+ });
4925
5334
  const ProviderInputSchema = object({
4926
5335
  name: string().min(1, "Name is required"),
4927
5336
  apiKey: string().min(1, "API key is required"),
@@ -4936,7 +5345,7 @@ const ProviderInputSchema = object({
4936
5345
  modelMetadata: array(ProviderModelMetadataSchema).optional(),
4937
5346
  source: _enum(["company", "personal"]).optional()
4938
5347
  });
4939
- const Route$n = createFileRoute("/api/providers")({
5348
+ const Route$p = createFileRoute("/api/providers")({
4940
5349
  server: {
4941
5350
  handlers: {
4942
5351
  GET: ({ request }) => {
@@ -4970,7 +5379,7 @@ const Route$n = createFileRoute("/api/providers")({
4970
5379
  }
4971
5380
  }
4972
5381
  });
4973
- const Route$m = createFileRoute("/api/models")({
5382
+ const Route$o = createFileRoute("/api/models")({
4974
5383
  server: {
4975
5384
  handlers: {
4976
5385
  GET: () => Response.json(getModels())
@@ -5127,73 +5536,20 @@ function extractResponsePreview(log) {
5127
5536
  }
5128
5537
  return null;
5129
5538
  }
5130
- const SessionTokenUsageSchema = object({
5131
- input: number().int().nonnegative(),
5132
- output: number().int().nonnegative(),
5133
- cacheCreate: number().int().nonnegative(),
5134
- cacheRead: number().int().nonnegative(),
5135
- total: number().int().nonnegative()
5136
- });
5137
- const SessionLogSummarySchema = object({
5138
- id: number().int().positive(),
5139
- timestamp: string(),
5140
- provider: string().nullable(),
5141
- model: string().nullable(),
5142
- apiFormat: _enum(["anthropic", "openai", "unknown"]),
5143
- method: string(),
5144
- path: string(),
5145
- status: number().nullable(),
5146
- isStreaming: boolean(),
5147
- hasError: boolean(),
5148
- error: string().nullable(),
5149
- latencyMs: number().nullable(),
5150
- tokens: object({
5151
- input: number().nullable(),
5152
- output: number().nullable(),
5153
- cacheCreate: number().nullable(),
5154
- cacheRead: number().nullable()
5155
- }),
5156
- sessionId: string().nullable()
5157
- });
5158
- const SessionInfoSchema = object({
5159
- id: string(),
5160
- status: _enum(["active", "failed", "completed", "empty"]),
5161
- logCoverage: _enum(["recent", "history"]),
5162
- source: _enum(["explicit", "client-process", "client-connection", "provider-test"]).nullable(),
5163
- runtimeMode: _enum(["in-process", "worker-thread", "child-process"]).nullable(),
5164
- createdAt: string().nullable(),
5165
- updatedAt: string().nullable(),
5166
- requestCount: number().int().nonnegative(),
5167
- activeRequests: number().int().nonnegative(),
5168
- completedRequests: number().int().nonnegative(),
5169
- failedRequests: number().int().nonnegative(),
5170
- queuedTasks: number().int().nonnegative(),
5171
- runningTasks: number().int().nonnegative(),
5172
- lastTaskError: string().nullable(),
5173
- logCount: number().int().nonnegative(),
5174
- errorCount: number().int().nonnegative(),
5175
- models: array(string()),
5176
- providers: array(string()),
5177
- tokenUsage: SessionTokenUsageSchema,
5178
- lastLogId: number().nullable(),
5179
- lastModel: string().nullable(),
5180
- clientPid: number().nullable(),
5181
- clientProjectFolder: string().nullable(),
5182
- inspectorPath: string(),
5183
- inspectorUrl: string(),
5184
- apiPath: string(),
5185
- apiUrl: string(),
5186
- compactLogsPath: string(),
5187
- compactLogsUrl: string(),
5188
- latestLogLimit: number().int().positive(),
5189
- latestLogs: array(SessionLogSummarySchema)
5190
- });
5191
5539
  const LogsListResponseSchema = object({
5192
5540
  logs: array(CapturedLogSchema).optional(),
5193
5541
  total: number().optional(),
5194
5542
  offset: number().optional(),
5195
5543
  limit: number().optional()
5196
5544
  });
5545
+ const SearchLogsResponseSchema = object({
5546
+ query: string(),
5547
+ logs: array(CapturedLogSchema),
5548
+ total: number(),
5549
+ offset: number(),
5550
+ limit: number(),
5551
+ scanned: number().optional()
5552
+ });
5197
5553
  const PAGINATION_DEFAULTS = { offset: 0, limit: 3 };
5198
5554
  const LIMIT_HARD_CAP = 5;
5199
5555
  const LOG_DETAIL_TEXT_LIMIT = 2e5;
@@ -5298,6 +5654,33 @@ async function listLogsImpl(callApi2, args) {
5298
5654
  const logs = parsed.data.logs ?? [];
5299
5655
  return textJson(logs.map(buildLogSummary));
5300
5656
  }
5657
+ async function searchLogsImpl(callApi2, args) {
5658
+ const offset = args.offset ?? PAGINATION_DEFAULTS.offset;
5659
+ const limit = clampLimit(args.limit);
5660
+ const params = new URLSearchParams({
5661
+ q: args.query ?? "",
5662
+ offset: String(offset),
5663
+ limit: String(limit)
5664
+ });
5665
+ if (args.scanLimit !== void 0) {
5666
+ params.set("scanLimit", String(args.scanLimit));
5667
+ }
5668
+ if (args.sessionId !== void 0 && args.sessionId.length > 0) {
5669
+ params.set("sessionId", args.sessionId);
5670
+ }
5671
+ if (args.model !== void 0 && args.model.length > 0) {
5672
+ params.set("model", args.model);
5673
+ }
5674
+ const res = await callApi2(`/api/logs?${params.toString()}`);
5675
+ if (!res.ok) return toolError(`GET /api/logs search returned ${res.status}`);
5676
+ const rawBody = await res.json();
5677
+ const parsed = SearchLogsResponseSchema.safeParse(rawBody);
5678
+ if (!parsed.success) return toolError("GET /api/logs search returned an unparseable shape");
5679
+ return textJson({
5680
+ ...parsed.data,
5681
+ logs: parsed.data.logs.map(buildLogSummary)
5682
+ });
5683
+ }
5301
5684
  async function getLogImpl(callApi2, id) {
5302
5685
  const res = await callApi2(`/api/logs/${id}`);
5303
5686
  if (!res.ok) return toolError(`GET /api/logs/${id} returned ${res.status}`);
@@ -5334,6 +5717,71 @@ async function getSessionImpl(callApi2, args) {
5334
5717
  if (!parsed.success) return toolError("GET /api/sessions returned an unparseable session info");
5335
5718
  return textJson(parsed.data);
5336
5719
  }
5720
+ async function createRunImpl(callApi2, args) {
5721
+ const res = await callApi2("/api/runs", {
5722
+ method: "POST",
5723
+ headers: { "content-type": "application/json" },
5724
+ body: JSON.stringify(args ?? {})
5725
+ });
5726
+ if (!res.ok) return toolError(`POST /api/runs returned ${res.status}`);
5727
+ const rawBody = await res.json();
5728
+ const parsed = InspectorRunSchema.safeParse(rawBody);
5729
+ if (!parsed.success) return toolError("POST /api/runs returned an unparseable run");
5730
+ return textJson(parsed.data);
5731
+ }
5732
+ async function getRunImpl(callApi2, args) {
5733
+ const path2 = `/api/runs/${encodeURIComponent(args.runId)}`;
5734
+ const res = await callApi2(path2);
5735
+ if (!res.ok) return toolError(`GET ${path2} returned ${res.status}`);
5736
+ const rawBody = await res.json();
5737
+ const parsed = InspectorRunSchema.safeParse(rawBody);
5738
+ if (!parsed.success) return toolError("GET /api/runs/{runId} returned an unparseable run");
5739
+ return textJson(parsed.data);
5740
+ }
5741
+ async function updateRunImpl(callApi2, args) {
5742
+ const { runId, ...patch } = args;
5743
+ const path2 = `/api/runs/${encodeURIComponent(runId)}`;
5744
+ const res = await callApi2(path2, {
5745
+ method: "PATCH",
5746
+ headers: { "content-type": "application/json" },
5747
+ body: JSON.stringify(patch)
5748
+ });
5749
+ if (!res.ok) return toolError(`PATCH ${path2} returned ${res.status}`);
5750
+ const rawBody = await res.json();
5751
+ const parsed = InspectorRunSchema.safeParse(rawBody);
5752
+ if (!parsed.success) return toolError("PATCH /api/runs/{runId} returned an unparseable run");
5753
+ return textJson(parsed.data);
5754
+ }
5755
+ async function getRecentFailuresImpl(callApi2, args) {
5756
+ const limit = args.limit ?? 5;
5757
+ const params = new URLSearchParams({ failures: "1", limit: String(limit) });
5758
+ const res = await callApi2(`/api/runs?${params.toString()}`);
5759
+ if (!res.ok) return toolError(`GET /api/runs?failures=1 returned ${res.status}`);
5760
+ const rawBody = await res.json();
5761
+ const parsed = RecentFailuresResponseSchema.safeParse(rawBody);
5762
+ if (!parsed.success) {
5763
+ return toolError("GET /api/runs?failures=1 returned an unparseable failure list");
5764
+ }
5765
+ return textJson(parsed.data);
5766
+ }
5767
+ async function exportEvidenceImpl(callApi2, args) {
5768
+ const path2 = `/api/runs/${encodeURIComponent(args.runId)}/evidence`;
5769
+ const res = await callApi2(path2, {
5770
+ method: "POST",
5771
+ headers: { "content-type": "application/json" },
5772
+ body: JSON.stringify({
5773
+ includeHistory: args.includeHistory,
5774
+ latestLogLimit: args.latestLogLimit
5775
+ })
5776
+ });
5777
+ if (!res.ok) return toolError(`POST ${path2} returned ${res.status}`);
5778
+ const rawBody = await res.json();
5779
+ const parsed = EvidenceExportResultSchema.safeParse(rawBody);
5780
+ if (!parsed.success) {
5781
+ return toolError("POST /api/runs/{runId}/evidence returned an unparseable evidence result");
5782
+ }
5783
+ return textJson(parsed.data);
5784
+ }
5337
5785
  async function listModelsImpl(callApi2) {
5338
5786
  const res = await callApi2("/api/models");
5339
5787
  if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
@@ -5464,9 +5912,11 @@ let initPromise = null;
5464
5912
  function buildServer() {
5465
5913
  const server = new McpServer(
5466
5914
  { name: "agent-inspector", version: "1.0.0" },
5467
- { capabilities: { tools: {} } }
5915
+ { capabilities: { tools: {}, resources: {}, prompts: {} } }
5468
5916
  );
5469
5917
  registerTools(server);
5918
+ registerResources(server);
5919
+ registerPrompts(server);
5470
5920
  const transport = new WebStandardStreamableHTTPServerTransport({
5471
5921
  sessionIdGenerator: void 0
5472
5922
  // stateless — see module docstring
@@ -5514,6 +5964,129 @@ Returns: array of "thick summary" objects, each containing:
5514
5964
 
5515
5965
  Either preview field is null when the body is unknown format, unparseable, or the relevant content is non-text (e.g., image-only).`;
5516
5966
  const PROVIDER_WRITE_WARNING = "⚠ This tool mutates provider configuration. Confirm with the user before invoking.";
5967
+ const RunsListResponseSchema = object({
5968
+ runs: array(InspectorRunSchema)
5969
+ });
5970
+ const LogResourceListResponseSchema = object({
5971
+ logs: array(object({ id: number().int().positive() }))
5972
+ });
5973
+ const EvidenceReadResponseSchema = object({
5974
+ markdown: string()
5975
+ });
5976
+ function textResource(uri, mimeType, text) {
5977
+ return {
5978
+ contents: [{ uri: uri.href, mimeType, text }]
5979
+ };
5980
+ }
5981
+ function jsonResource(uri, data) {
5982
+ return textResource(uri, "application/json", JSON.stringify(data, null, 2));
5983
+ }
5984
+ function variableString(variables, name) {
5985
+ const value = variables[name];
5986
+ if (Array.isArray(value)) return value[0] ?? null;
5987
+ return value ?? null;
5988
+ }
5989
+ function listResult(resources) {
5990
+ return { resources };
5991
+ }
5992
+ async function readApiJsonResource(uri, path2) {
5993
+ try {
5994
+ const response = await callApi(path2);
5995
+ if (!response.ok) {
5996
+ return textResource(uri, "text/plain", `GET ${path2} returned ${response.status}`);
5997
+ }
5998
+ return jsonResource(uri, await response.json());
5999
+ } catch (err) {
6000
+ const message = err instanceof Error ? err.message : String(err);
6001
+ return textResource(uri, "text/plain", `Resource read failed: ${message}`);
6002
+ }
6003
+ }
6004
+ async function listSessionResources() {
6005
+ try {
6006
+ const response = await callApi("/api/sessions");
6007
+ if (!response.ok) return listResult([]);
6008
+ const parsed = array(string()).safeParse(await response.json());
6009
+ if (!parsed.success) return listResult([]);
6010
+ return listResult(
6011
+ parsed.data.map((sessionId) => ({
6012
+ uri: `inspector://sessions/${encodeURIComponent(sessionId)}`,
6013
+ name: `session:${sessionId}`,
6014
+ title: `Inspector session ${sessionId}`,
6015
+ mimeType: "application/json"
6016
+ }))
6017
+ );
6018
+ } catch {
6019
+ return listResult([]);
6020
+ }
6021
+ }
6022
+ async function listLogResources() {
6023
+ try {
6024
+ const response = await callApi("/api/logs?offset=0&limit=5&compact=1");
6025
+ if (!response.ok) return listResult([]);
6026
+ const parsed = LogResourceListResponseSchema.safeParse(await response.json());
6027
+ if (!parsed.success) return listResult([]);
6028
+ return listResult(
6029
+ parsed.data.logs.map((log) => ({
6030
+ uri: `inspector://logs/${String(log.id)}`,
6031
+ name: `log:${String(log.id)}`,
6032
+ title: `Inspector log ${String(log.id)}`,
6033
+ mimeType: "application/json"
6034
+ }))
6035
+ );
6036
+ } catch {
6037
+ return listResult([]);
6038
+ }
6039
+ }
6040
+ async function listRunResources() {
6041
+ try {
6042
+ const response = await callApi("/api/runs");
6043
+ if (!response.ok) return listResult([]);
6044
+ const parsed = RunsListResponseSchema.safeParse(await response.json());
6045
+ if (!parsed.success) return listResult([]);
6046
+ return listResult(
6047
+ parsed.data.runs.map((run) => ({
6048
+ uri: `inspector://runs/${encodeURIComponent(run.id)}`,
6049
+ name: `run:${run.id}`,
6050
+ title: run.title,
6051
+ mimeType: "application/json"
6052
+ }))
6053
+ );
6054
+ } catch {
6055
+ return listResult([]);
6056
+ }
6057
+ }
6058
+ async function listEvidenceResources() {
6059
+ try {
6060
+ const response = await callApi("/api/runs");
6061
+ if (!response.ok) return listResult([]);
6062
+ const parsed = RunsListResponseSchema.safeParse(await response.json());
6063
+ if (!parsed.success) return listResult([]);
6064
+ return listResult(
6065
+ parsed.data.runs.filter((run) => run.evidence !== null).map((run) => ({
6066
+ uri: `inspector://evidence/${encodeURIComponent(run.id)}`,
6067
+ name: `evidence:${run.id}`,
6068
+ title: `${run.title} evidence pack`,
6069
+ mimeType: "text/markdown",
6070
+ description: run.evidence?.markdownPath
6071
+ }))
6072
+ );
6073
+ } catch {
6074
+ return listResult([]);
6075
+ }
6076
+ }
6077
+ function userPrompt(text) {
6078
+ return {
6079
+ messages: [
6080
+ {
6081
+ role: "user",
6082
+ content: {
6083
+ type: "text",
6084
+ text
6085
+ }
6086
+ }
6087
+ ]
6088
+ };
6089
+ }
5517
6090
  function registerTools(server) {
5518
6091
  server.registerTool(
5519
6092
  "inspector_list_logs",
@@ -5529,6 +6102,22 @@ function registerTools(server) {
5529
6102
  },
5530
6103
  (args) => safeCall(() => listLogsImpl(callApi, args))
5531
6104
  );
6105
+ server.registerTool(
6106
+ "inspector_search_logs",
6107
+ {
6108
+ title: "Search captured logs",
6109
+ description: "Searches recent compact proxy logs by id, session, model, provider, API path, status, error text, and client context. Results are bounded summaries; call inspector_get_log for full details after locating a candidate id.",
6110
+ inputSchema: object({
6111
+ query: string().optional().describe("Search terms. Empty or omitted returns the newest scanned logs."),
6112
+ offset: number().int().nonnegative().optional().describe("Skip this many matches."),
6113
+ limit: number().int().positive().optional().describe("How many summaries to return. Hard-capped at 5 by the MCP handler."),
6114
+ scanLimit: number().int().positive().max(1e3).optional().describe("How many recent compact logs to scan before filtering. Default 200."),
6115
+ sessionId: string().optional().describe("Optional session/affinity id filter."),
6116
+ model: string().optional().describe("Optional model name filter.")
6117
+ })
6118
+ },
6119
+ (args) => safeCall(() => searchLogsImpl(callApi, args))
6120
+ );
5532
6121
  server.registerTool(
5533
6122
  "inspector_get_log",
5534
6123
  {
@@ -5541,37 +6130,102 @@ function registerTools(server) {
5541
6130
  ({ id }) => safeCall(() => getLogImpl(callApi, id))
5542
6131
  );
5543
6132
  server.registerTool(
5544
- "inspector_get_log_chunks",
6133
+ "inspector_get_log_chunks",
6134
+ {
6135
+ title: "Get SSE streaming chunks for a captured log",
6136
+ description: "Returns the SSE chunks array for a streaming log, in the order they were received. Returns an error if the log has no chunks (i.e. was non-streaming or the log id is unknown).",
6137
+ inputSchema: object({
6138
+ id: number().int().positive().describe("The log id.")
6139
+ })
6140
+ },
6141
+ ({ id }) => safeCall(() => getLogChunksImpl(callApi, id))
6142
+ );
6143
+ server.registerTool(
6144
+ "inspector_list_sessions",
6145
+ {
6146
+ title: "List known session ids",
6147
+ description: "Returns the array of session ids that have been observed in captured logs. Useful for discovering what sessionId values to pass to inspector_list_logs.",
6148
+ inputSchema: object({})
6149
+ },
6150
+ () => safeCall(() => listSessionsImpl(callApi))
6151
+ );
6152
+ server.registerTool(
6153
+ "inspector_get_session",
6154
+ {
6155
+ title: "Get Inspector session info",
6156
+ description: "Returns a lightweight, evidence-friendly summary for one Inspector session: clickable inspectorUrl, compact logs URL, status, request/error counts, token totals, models/providers, and the latest compact log summaries. Defaults to recent in-memory/cache data for speed; set includeHistory=true only when you explicitly want to scan persisted jsonl history. It avoids raw request/response bodies so it is safe to use in evaluation artifacts.",
6157
+ inputSchema: object({
6158
+ sessionId: string().min(1).describe("The Inspector session id."),
6159
+ includeHistory: boolean().optional().describe("Scan persisted jsonl history for this session. Slower; default false."),
6160
+ latestLogLimit: number().int().positive().max(50).optional().describe("How many latest compact log summaries to include. Max 50, default 10.")
6161
+ })
6162
+ },
6163
+ (args) => safeCall(() => getSessionImpl(callApi, args))
6164
+ );
6165
+ server.registerTool(
6166
+ "inspector_create_run",
6167
+ {
6168
+ title: "Create or declare an Inspector run",
6169
+ description: "Creates an evaluation-friendly run record before a coding-agent task starts. Use this when a benchmark or Jenkins job wants a stable runId/sessionId pair instead of inferring a task after the fact from logs.",
6170
+ inputSchema: object({
6171
+ runId: string().min(1).optional().describe("Stable run id. Generated when omitted."),
6172
+ sessionId: string().min(1).optional().describe("Inspector session id to attach. Defaults to the run id."),
6173
+ title: string().min(1).optional().describe("Human-readable run title."),
6174
+ task: string().nullable().optional().describe("Task statement or benchmark case."),
6175
+ project: string().nullable().optional().describe("Project or repository under test."),
6176
+ agent: string().nullable().optional().describe("Coding agent name, e.g. Codex/OpenCode."),
6177
+ status: InspectorRunStatusSchema.optional().describe("Initial run status."),
6178
+ tags: array(string()).optional().describe("Free-form run labels."),
6179
+ metadata: record(string(), JsonValueSchema).optional().describe("JSON metadata.")
6180
+ })
6181
+ },
6182
+ (args) => safeCall(() => createRunImpl(callApi, args))
6183
+ );
6184
+ server.registerTool(
6185
+ "inspector_get_run",
6186
+ {
6187
+ title: "Get an Inspector run",
6188
+ description: "Returns one declared Inspector run, including its session binding and exported evidence paths when available.",
6189
+ inputSchema: object({
6190
+ runId: string().min(1).describe("The Inspector run id.")
6191
+ })
6192
+ },
6193
+ (args) => safeCall(() => getRunImpl(callApi, args))
6194
+ );
6195
+ server.registerTool(
6196
+ "inspector_update_run",
5545
6197
  {
5546
- title: "Get SSE streaming chunks for a captured log",
5547
- description: "Returns the SSE chunks array for a streaming log, in the order they were received. Returns an error if the log has no chunks (i.e. was non-streaming or the log id is unknown).",
5548
- inputSchema: object({
5549
- id: number().int().positive().describe("The log id.")
6198
+ title: "Update an Inspector run",
6199
+ description: "PATCH-style update for a declared run. Use this during a coding-agent task to mark status transitions such as running, failed, completed, or to attach refined task metadata. Supplied metadata keys are merged into existing metadata.",
6200
+ inputSchema: UpdateInspectorRunInputSchema.extend({
6201
+ runId: string().min(1).describe("The Inspector run id.")
5550
6202
  })
5551
6203
  },
5552
- ({ id }) => safeCall(() => getLogChunksImpl(callApi, id))
6204
+ (args) => safeCall(() => updateRunImpl(callApi, args))
5553
6205
  );
5554
6206
  server.registerTool(
5555
- "inspector_list_sessions",
6207
+ "inspector_export_evidence",
5556
6208
  {
5557
- title: "List known session ids",
5558
- description: "Returns the array of session ids that have been observed in captured logs. Useful for discovering what sessionId values to pass to inspector_list_logs.",
5559
- inputSchema: object({})
6209
+ title: "Export an Inspector evidence pack",
6210
+ description: "Exports one run's evidence pack to local JSON, Markdown, and HTML files under <dataDir>/evidence/<runId> and returns the paths plus session summary.",
6211
+ inputSchema: object({
6212
+ runId: string().min(1).describe("The Inspector run id."),
6213
+ includeHistory: boolean().optional().describe("Scan persisted jsonl history for the bound session. Slower; default false."),
6214
+ latestLogLimit: number().int().positive().max(50).optional().describe("How many compact log summaries to include in the evidence pack. Max 50.")
6215
+ })
5560
6216
  },
5561
- () => safeCall(() => listSessionsImpl(callApi))
6217
+ (args) => safeCall(() => exportEvidenceImpl(callApi, args))
5562
6218
  );
5563
6219
  server.registerTool(
5564
- "inspector_get_session",
6220
+ "inspector_get_recent_failures",
5565
6221
  {
5566
- title: "Get Inspector session info",
5567
- description: "Returns a lightweight, evidence-friendly summary for one Inspector session: clickable inspectorUrl, compact logs URL, status, request/error counts, token totals, models/providers, and the latest compact log summaries. Defaults to recent in-memory/cache data for speed; set includeHistory=true only when you explicitly want to scan persisted jsonl history. It avoids raw request/response bodies so it is safe to use in evaluation artifacts.",
6222
+ title: "Get recent Inspector failures",
6223
+ description: "Returns recent declared runs that are failed/cancelled or have non-success exported evidence, including classification, Jenkins summary, Inspector link, and evidence paths when available.",
5568
6224
  inputSchema: object({
5569
- sessionId: string().min(1).describe("The Inspector session id."),
5570
- includeHistory: boolean().optional().describe("Scan persisted jsonl history for this session. Slower; default false."),
5571
- latestLogLimit: number().int().positive().max(50).optional().describe("How many latest compact log summaries to include. Max 50, default 10.")
6225
+ limit: number().int().positive().max(20).optional().describe("Maximum failures to return. Default 5, max 20.")
5572
6226
  })
5573
6227
  },
5574
- (args) => safeCall(() => getSessionImpl(callApi, args))
6228
+ (args) => safeCall(() => getRecentFailuresImpl(callApi, args))
5575
6229
  );
5576
6230
  server.registerTool(
5577
6231
  "inspector_list_models",
@@ -5724,7 +6378,211 @@ PATCH-style update: only the fields you supply are changed. Returns the updated
5724
6378
  (args) => safeCall(() => getProjectContextImpl(callApi, args))
5725
6379
  );
5726
6380
  }
5727
- const Route$l = createFileRoute("/api/mcp")({
6381
+ function registerResources(server) {
6382
+ server.registerResource(
6383
+ "inspector_sessions",
6384
+ "inspector://sessions",
6385
+ {
6386
+ title: "Inspector sessions",
6387
+ description: "Observed Inspector session ids.",
6388
+ mimeType: "application/json"
6389
+ },
6390
+ (uri) => readApiJsonResource(uri, "/api/sessions")
6391
+ );
6392
+ server.registerResource(
6393
+ "inspector_session",
6394
+ new ResourceTemplate("inspector://sessions/{sessionId}", { list: listSessionResources }),
6395
+ {
6396
+ title: "Inspector session",
6397
+ description: "Evidence-friendly session summary with Inspector URL, compact logs URL, counts, models, providers, token totals, and recent compact logs.",
6398
+ mimeType: "application/json"
6399
+ },
6400
+ (uri, variables) => {
6401
+ const sessionId = variableString(variables, "sessionId");
6402
+ if (sessionId === null) return textResource(uri, "text/plain", "Missing sessionId");
6403
+ const params = new URLSearchParams({ sessionId, limit: "10" });
6404
+ return readApiJsonResource(uri, `/api/sessions?${params.toString()}`);
6405
+ }
6406
+ );
6407
+ server.registerResource(
6408
+ "inspector_log",
6409
+ new ResourceTemplate("inspector://logs/{id}", { list: listLogResources }),
6410
+ {
6411
+ title: "Inspector log",
6412
+ description: "Single captured proxy log by numeric id.",
6413
+ mimeType: "application/json"
6414
+ },
6415
+ async (uri, variables) => {
6416
+ const id = variableString(variables, "id");
6417
+ const parsed = id === null ? Number.NaN : Number(id);
6418
+ if (!Number.isInteger(parsed) || parsed <= 0) {
6419
+ return textResource(uri, "text/plain", "Invalid log id");
6420
+ }
6421
+ const result = await getLogImpl(callApi, parsed);
6422
+ const first = result.content[0];
6423
+ const text = first?.text ?? "";
6424
+ return textResource(uri, result.isError === true ? "text/plain" : "application/json", text);
6425
+ }
6426
+ );
6427
+ server.registerResource(
6428
+ "inspector_runs",
6429
+ "inspector://runs",
6430
+ {
6431
+ title: "Inspector runs",
6432
+ description: "Declared Inspector evaluation runs.",
6433
+ mimeType: "application/json"
6434
+ },
6435
+ (uri) => readApiJsonResource(uri, "/api/runs")
6436
+ );
6437
+ server.registerResource(
6438
+ "inspector_recent_failures",
6439
+ "inspector://failures/recent",
6440
+ {
6441
+ title: "Recent Inspector failures",
6442
+ description: "Recent declared runs with failed/cancelled status or non-success exported evidence.",
6443
+ mimeType: "application/json"
6444
+ },
6445
+ (uri) => readApiJsonResource(uri, "/api/runs?failures=1&limit=10")
6446
+ );
6447
+ server.registerResource(
6448
+ "inspector_run",
6449
+ new ResourceTemplate("inspector://runs/{runId}", { list: listRunResources }),
6450
+ {
6451
+ title: "Inspector run",
6452
+ description: "One declared Inspector run, including session binding and evidence paths.",
6453
+ mimeType: "application/json"
6454
+ },
6455
+ (uri, variables) => {
6456
+ const runId = variableString(variables, "runId");
6457
+ if (runId === null) return textResource(uri, "text/plain", "Missing runId");
6458
+ return readApiJsonResource(uri, `/api/runs/${encodeURIComponent(runId)}`);
6459
+ }
6460
+ );
6461
+ server.registerResource(
6462
+ "inspector_evidence",
6463
+ new ResourceTemplate("inspector://evidence/{runId}", { list: listEvidenceResources }),
6464
+ {
6465
+ title: "Inspector evidence pack",
6466
+ description: "Markdown evidence pack for a declared Inspector run. Generate it first with inspector_export_evidence.",
6467
+ mimeType: "text/markdown"
6468
+ },
6469
+ async (uri, variables) => {
6470
+ const runId = variableString(variables, "runId");
6471
+ if (runId === null) return textResource(uri, "text/plain", "Missing runId");
6472
+ const path2 = `/api/runs/${encodeURIComponent(runId)}/evidence`;
6473
+ try {
6474
+ const response = await callApi(path2);
6475
+ if (!response.ok) {
6476
+ return textResource(uri, "text/plain", `GET ${path2} returned ${response.status}`);
6477
+ }
6478
+ const parsed = EvidenceReadResponseSchema.safeParse(await response.json());
6479
+ if (!parsed.success) {
6480
+ return textResource(uri, "text/plain", "Evidence endpoint returned an unparseable body");
6481
+ }
6482
+ return textResource(uri, "text/markdown", parsed.data.markdown);
6483
+ } catch (err) {
6484
+ const message = err instanceof Error ? err.message : String(err);
6485
+ return textResource(uri, "text/plain", `Resource read failed: ${message}`);
6486
+ }
6487
+ }
6488
+ );
6489
+ }
6490
+ function registerPrompts(server) {
6491
+ server.registerPrompt(
6492
+ "inspector_analyze_session_failure",
6493
+ {
6494
+ title: "Analyze session failure",
6495
+ description: "Use Inspector session/log resources to explain why a coding-agent session failed.",
6496
+ argsSchema: object({
6497
+ sessionId: string().min(1).describe("The Inspector session id."),
6498
+ includeHistory: boolean().optional().describe("When true, call inspector_get_session with includeHistory=true if needed.")
6499
+ })
6500
+ },
6501
+ ({ sessionId, includeHistory }) => userPrompt(`Analyze this Agent Inspector session and explain why it failed.
6502
+
6503
+ Evidence to read:
6504
+ - Session resource: inspector://sessions/${encodeURIComponent(sessionId)}
6505
+ - Relevant log resources: inspector://logs/{id} for the failing or suspicious log ids
6506
+
6507
+ If includeHistory is ${includeHistory === true ? "true" : "false"} or the session summary looks incomplete, call inspector_get_session with includeHistory=true and latestLogLimit=50.
6508
+
6509
+ Return:
6510
+ 1. Outcome and severity
6511
+ 2. Most likely root cause
6512
+ 3. Evidence bullets with log ids, models, providers, status codes, and error text
6513
+ 4. Concrete fix or next diagnostic step
6514
+ 5. Confidence and what evidence is still missing`)
6515
+ );
6516
+ server.registerPrompt(
6517
+ "inspector_triage_recent_failures",
6518
+ {
6519
+ title: "Triage recent failures",
6520
+ description: "Use the recent failures resource and log search tool to prioritize coding-agent failures.",
6521
+ argsSchema: object({
6522
+ limit: number().int().positive().max(20).optional().describe("How many failures to triage.")
6523
+ })
6524
+ },
6525
+ ({ limit }) => userPrompt(`Triage recent Agent Inspector failures.
6526
+
6527
+ Evidence to read:
6528
+ - Recent failures resource: inspector://failures/recent
6529
+ - For missing details, call inspector_get_recent_failures with limit=${String(limit ?? 5)}
6530
+ - For suspicious sessions/models/providers, call inspector_search_logs and then inspector_get_log for cited ids
6531
+
6532
+ Return:
6533
+ 1. Highest-priority failure first
6534
+ 2. Run id, session id, category, severity, and evidence links
6535
+ 3. Likely root cause and concrete next action
6536
+ 4. Whether evidence is sufficient or inspector_export_evidence should be run again`)
6537
+ );
6538
+ server.registerPrompt(
6539
+ "inspector_generate_jenkins_report",
6540
+ {
6541
+ title: "Generate Jenkins report",
6542
+ description: "Generate a Jenkins-friendly Markdown report from an Inspector run and evidence pack.",
6543
+ argsSchema: object({
6544
+ runId: string().min(1).describe("The Inspector run id.")
6545
+ })
6546
+ },
6547
+ ({ runId }) => userPrompt(`Generate a Jenkins-ready Markdown report for this Inspector run.
6548
+
6549
+ Evidence to read:
6550
+ - Run resource: inspector://runs/${encodeURIComponent(runId)}
6551
+ - Evidence pack resource: inspector://evidence/${encodeURIComponent(runId)}
6552
+
6553
+ If the evidence pack resource says it has not been exported, call inspector_export_evidence for this run first.
6554
+
6555
+ The report should include:
6556
+ 1. Build/run status summary
6557
+ 2. Run id, session id, Inspector URLs, and artifact paths
6558
+ 3. Models/providers used, token totals, request counts, and error counts
6559
+ 4. Failure analysis or success evidence
6560
+ 5. Reproducible next action for a developer`)
6561
+ );
6562
+ server.registerPrompt(
6563
+ "inspector_extract_repro_steps",
6564
+ {
6565
+ title: "Extract reproducible steps",
6566
+ description: "Turn an Inspector session into concise repro steps for a developer or evaluator.",
6567
+ argsSchema: object({
6568
+ sessionId: string().min(1).describe("The Inspector session id.")
6569
+ })
6570
+ },
6571
+ ({ sessionId }) => userPrompt(`Extract reproducible steps from this Agent Inspector session.
6572
+
6573
+ Evidence to read:
6574
+ - Session resource: inspector://sessions/${encodeURIComponent(sessionId)}
6575
+ - Relevant log resources: inspector://logs/{id}
6576
+
6577
+ Return:
6578
+ 1. Preconditions and environment assumptions
6579
+ 2. Ordered user/agent actions that led to the behavior
6580
+ 3. Exact model/provider/API path details needed to reproduce
6581
+ 4. Expected vs actual result
6582
+ 5. Minimal verification command or UI check`)
6583
+ );
6584
+ }
6585
+ const Route$n = createFileRoute("/api/mcp")({
5728
6586
  server: {
5729
6587
  handlers: {
5730
6588
  // The SDK may issue either POST, GET, or DELETE. TanStack Start only
@@ -5736,6 +6594,89 @@ const Route$l = createFileRoute("/api/mcp")({
5736
6594
  }
5737
6595
  }
5738
6596
  });
6597
+ const LOG_SEARCH_DEFAULT_LIMIT = 10;
6598
+ const LOG_SEARCH_MAX_LIMIT = 50;
6599
+ const LOG_SEARCH_DEFAULT_SCAN_LIMIT = 200;
6600
+ const LOG_SEARCH_MAX_SCAN_LIMIT = 1e3;
6601
+ function clampPositiveInt(value, fallback, max) {
6602
+ if (!Number.isInteger(value) || value < 1) return fallback;
6603
+ return Math.min(value, max);
6604
+ }
6605
+ function clampLogSearchLimit(value) {
6606
+ return clampPositiveInt(value, LOG_SEARCH_DEFAULT_LIMIT, LOG_SEARCH_MAX_LIMIT);
6607
+ }
6608
+ function clampLogSearchScanLimit(value) {
6609
+ return clampPositiveInt(value, LOG_SEARCH_DEFAULT_SCAN_LIMIT, LOG_SEARCH_MAX_SCAN_LIMIT);
6610
+ }
6611
+ function queryTerms(query) {
6612
+ return query.trim().toLowerCase().split(/\s+/).filter((term) => term.length > 0);
6613
+ }
6614
+ function optionalText(value) {
6615
+ if (value === null || value === void 0) return null;
6616
+ return String(value);
6617
+ }
6618
+ function searchableFields(log) {
6619
+ return [
6620
+ String(log.id),
6621
+ log.timestamp,
6622
+ log.method,
6623
+ log.path,
6624
+ optionalText(log.model),
6625
+ optionalText(log.sessionId),
6626
+ optionalText(log.responseStatus),
6627
+ optionalText(log.elapsedMs),
6628
+ optionalText(log.streaming),
6629
+ log.streaming ? "streaming" : "non-streaming",
6630
+ log.apiFormat,
6631
+ optionalText(log.providerName),
6632
+ optionalText(log.userAgent),
6633
+ optionalText(log.origin),
6634
+ optionalText(log.clientPid),
6635
+ optionalText(log.clientCwd),
6636
+ optionalText(log.clientProjectFolder),
6637
+ optionalText(log.error)
6638
+ ].filter((value) => value !== null && value.length > 0);
6639
+ }
6640
+ function matchesTerms(log, terms) {
6641
+ if (terms.length === 0) return true;
6642
+ const haystack = searchableFields(log).join("\n").toLowerCase();
6643
+ return terms.every((term) => haystack.includes(term));
6644
+ }
6645
+ function sortNewestFirst(logs) {
6646
+ return logs.sort((left, right) => {
6647
+ const timestampCompare = right.timestamp.localeCompare(left.timestamp);
6648
+ return timestampCompare !== 0 ? timestampCompare : right.id - left.id;
6649
+ });
6650
+ }
6651
+ async function searchLogsPage(options) {
6652
+ const limit = clampLogSearchLimit(options.limit);
6653
+ const scanLimit = clampLogSearchScanLimit(Math.max(options.scanLimit, options.offset + limit));
6654
+ const firstPage = await listLogsPage({
6655
+ sessionId: options.sessionId,
6656
+ model: options.model,
6657
+ offset: 0,
6658
+ limit: 1,
6659
+ includeBodies: false
6660
+ });
6661
+ const scanOffset = Math.max(firstPage.total - scanLimit, 0);
6662
+ const scanPage = await listLogsPage({
6663
+ sessionId: options.sessionId,
6664
+ model: options.model,
6665
+ offset: scanOffset,
6666
+ limit: scanLimit,
6667
+ includeBodies: false
6668
+ });
6669
+ const terms = queryTerms(options.query);
6670
+ const matches = sortNewestFirst(scanPage.logs.filter((log) => matchesTerms(log, terms)));
6671
+ return {
6672
+ query: options.query,
6673
+ logs: matches.slice(options.offset, options.offset + limit),
6674
+ total: matches.length,
6675
+ offset: options.offset,
6676
+ limit,
6677
+ scanned: scanPage.logs.length
6678
+ };
6679
+ }
5739
6680
  const DeleteBodySchema = object({
5740
6681
  ids: array(number().int().positive()).optional()
5741
6682
  }).optional();
@@ -5751,7 +6692,7 @@ function parsePositiveInt(value, fallback) {
5751
6692
  if (!Number.isInteger(parsed) || parsed < 1) return fallback;
5752
6693
  return parsed;
5753
6694
  }
5754
- const Route$k = createFileRoute("/api/logs")({
6695
+ const Route$m = createFileRoute("/api/logs")({
5755
6696
  server: {
5756
6697
  handlers: {
5757
6698
  GET: async ({ request }) => {
@@ -5770,7 +6711,18 @@ const Route$k = createFileRoute("/api/logs")({
5770
6711
  }
5771
6712
  const sessionId = url.searchParams.get("sessionId") ?? void 0;
5772
6713
  const model = url.searchParams.get("model") ?? void 0;
6714
+ const query = url.searchParams.get("q") ?? url.searchParams.get("search");
5773
6715
  const offset = parseNonNegativeInt(url.searchParams.get("offset"), 0);
6716
+ if (query !== null) {
6717
+ const limit2 = parsePositiveInt(url.searchParams.get("limit"), LOG_SEARCH_DEFAULT_LIMIT);
6718
+ const scanLimit = parsePositiveInt(
6719
+ url.searchParams.get("scanLimit"),
6720
+ LOG_SEARCH_DEFAULT_SCAN_LIMIT
6721
+ );
6722
+ return Response.json(
6723
+ await searchLogsPage({ query, sessionId, model, offset, limit: limit2, scanLimit })
6724
+ );
6725
+ }
5774
6726
  const limit = parsePositiveInt(url.searchParams.get("limit"), 50);
5775
6727
  const includeBodies = url.searchParams.get("compact") !== "1";
5776
6728
  return Response.json(
@@ -5816,7 +6768,7 @@ const Route$k = createFileRoute("/api/logs")({
5816
6768
  }
5817
6769
  });
5818
6770
  logger.debug("Health endpoint loaded");
5819
- const Route$j = createFileRoute("/api/health")({
6771
+ const Route$l = createFileRoute("/api/health")({
5820
6772
  server: {
5821
6773
  handlers: {
5822
6774
  GET: () => {
@@ -5834,7 +6786,7 @@ const RuntimeConfigPatchSchema = object({
5834
6786
  }).strict().refine((v) => Object.keys(v).length > 0, {
5835
6787
  message: "At least one field must be provided"
5836
6788
  });
5837
- const Route$i = createFileRoute("/api/config")({
6789
+ const Route$k = createFileRoute("/api/config")({
5838
6790
  server: {
5839
6791
  handlers: {
5840
6792
  GET: () => {
@@ -5863,6 +6815,46 @@ const Route$i = createFileRoute("/api/config")({
5863
6815
  }
5864
6816
  }
5865
6817
  });
6818
+ async function readJsonBody$1(request) {
6819
+ try {
6820
+ const raw = await request.text();
6821
+ if (raw.trim().length === 0) return { ok: true, value: {} };
6822
+ return { ok: true, value: JSON.parse(raw) };
6823
+ } catch {
6824
+ return { ok: false };
6825
+ }
6826
+ }
6827
+ const Route$j = createFileRoute("/api/runs/$runId")({
6828
+ server: {
6829
+ handlers: {
6830
+ GET: ({ params }) => {
6831
+ const run = getRun(params.runId);
6832
+ if (run === null) {
6833
+ return Response.json({ error: "Run not found" }, { status: 404 });
6834
+ }
6835
+ return Response.json(run);
6836
+ },
6837
+ PATCH: async ({ params, request }) => {
6838
+ const body = await readJsonBody$1(request);
6839
+ if (!body.ok) {
6840
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
6841
+ }
6842
+ const parsed = UpdateInspectorRunInputSchema.safeParse(body.value);
6843
+ if (!parsed.success) {
6844
+ return Response.json(
6845
+ { error: "Invalid request body", details: parsed.error.format() },
6846
+ { status: 400 }
6847
+ );
6848
+ }
6849
+ const updated = updateRun(params.runId, parsed.data);
6850
+ if (updated === null) {
6851
+ return Response.json({ error: "Run not found" }, { status: 404 });
6852
+ }
6853
+ return Response.json(updated);
6854
+ }
6855
+ }
6856
+ }
6857
+ });
5866
6858
  const ZHIPU_OPENAI_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
5867
6859
  const ZHIPU_CODING_OPENAI_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
5868
6860
  const ZHIPU_CODING_ANTHROPIC_BASE_URL = "https://open.bigmodel.cn/api/anthropic";
@@ -6289,7 +7281,7 @@ function scanExternalProviders() {
6289
7281
  }
6290
7282
  return { providers: filteredProviders, warnings };
6291
7283
  }
6292
- const Route$h = createFileRoute("/api/providers/scan")({
7284
+ const Route$i = createFileRoute("/api/providers/scan")({
6293
7285
  server: {
6294
7286
  handlers: {
6295
7287
  GET: () => {
@@ -6310,7 +7302,7 @@ const Route$h = createFileRoute("/api/providers/scan")({
6310
7302
  }
6311
7303
  });
6312
7304
  union([string(), object({ providers: unknown() })]);
6313
- const Route$g = createFileRoute("/api/providers/import")({
7305
+ const Route$h = createFileRoute("/api/providers/import")({
6314
7306
  server: {
6315
7307
  handlers: {
6316
7308
  POST: async ({ request }) => {
@@ -6343,7 +7335,7 @@ const Route$g = createFileRoute("/api/providers/import")({
6343
7335
  }
6344
7336
  }
6345
7337
  });
6346
- const Route$f = createFileRoute("/api/providers/export")({
7338
+ const Route$g = createFileRoute("/api/providers/export")({
6347
7339
  server: {
6348
7340
  handlers: {
6349
7341
  GET: ({ request }) => {
@@ -6376,7 +7368,7 @@ const ProviderUpdateSchema = object({
6376
7368
  modelMetadata: array(ProviderModelMetadataSchema).optional(),
6377
7369
  source: _enum(["company", "personal"]).optional()
6378
7370
  });
6379
- const Route$e = createFileRoute("/api/providers/$providerId")({
7371
+ const Route$f = createFileRoute("/api/providers/$providerId")({
6380
7372
  server: {
6381
7373
  handlers: {
6382
7374
  GET: ({ params, request }) => {
@@ -6411,7 +7403,7 @@ const Route$e = createFileRoute("/api/providers/$providerId")({
6411
7403
  }
6412
7404
  });
6413
7405
  const INITIAL_STREAM_LOG_LIMIT = 100;
6414
- const Route$d = createFileRoute("/api/logs/stream")({
7406
+ const Route$e = createFileRoute("/api/logs/stream")({
6415
7407
  server: {
6416
7408
  handlers: {
6417
7409
  GET: ({ request }) => {
@@ -6484,7 +7476,7 @@ const Route$d = createFileRoute("/api/logs/stream")({
6484
7476
  }
6485
7477
  }
6486
7478
  });
6487
- const Route$c = createFileRoute("/api/logs/$id")({
7479
+ const Route$d = createFileRoute("/api/logs/$id")({
6488
7480
  server: {
6489
7481
  handlers: {
6490
7482
  GET: async ({ params }) => {
@@ -6826,10 +7818,10 @@ function normalizePathSegment(value, fallback) {
6826
7818
  const segment = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
6827
7819
  return segment === "" ? fallback : segment;
6828
7820
  }
6829
- function formatNullable(value) {
7821
+ function formatNullable$1(value) {
6830
7822
  return value === null || value === "" ? "unknown" : value;
6831
7823
  }
6832
- function formatList(values) {
7824
+ function formatList$2(values) {
6833
7825
  return values.length === 0 ? "none" : values.join(", ");
6834
7826
  }
6835
7827
  function buildEvidenceLink(candidate) {
@@ -6852,16 +7844,16 @@ function buildOpenClawMemoryMarkdown(candidate) {
6852
7844
  "",
6853
7845
  `- Type: ${sanitized.type}`,
6854
7846
  `- Status: ${sanitized.status}`,
6855
- `- Project: ${formatNullable(sanitized.evidence.project)}`,
7847
+ `- Project: ${formatNullable$1(sanitized.evidence.project)}`,
6856
7848
  `- Session: ${sanitized.sessionId}`,
6857
7849
  `- Log IDs: ${sanitized.logIds.join(", ")}`,
6858
- `- Models: ${formatList(sanitized.evidence.models)}`,
6859
- `- Tags: ${formatList(sanitized.tags)}`,
7850
+ `- Models: ${formatList$2(sanitized.evidence.models)}`,
7851
+ `- Tags: ${formatList$2(sanitized.tags)}`,
6860
7852
  `- Created: ${sanitized.createdAt}`,
6861
7853
  `- Updated: ${sanitized.updatedAt}`,
6862
7854
  `- Evidence: ${buildEvidenceLink(sanitized)}`,
6863
7855
  `- Redacted: ${String(sanitized.redaction.redacted)}`,
6864
- `- Redaction patterns: ${formatList(sanitized.redaction.patterns)}`,
7856
+ `- Redaction patterns: ${formatList$2(sanitized.redaction.patterns)}`,
6865
7857
  "",
6866
7858
  "## Durable Knowledge Candidate",
6867
7859
  "",
@@ -7021,7 +8013,7 @@ const SearchBodySchema = object({
7021
8013
  query: string().min(1),
7022
8014
  project: string().optional()
7023
8015
  });
7024
- const Route$b = createFileRoute("/api/knowledge/search")({
8016
+ const Route$c = createFileRoute("/api/knowledge/search")({
7025
8017
  server: {
7026
8018
  handlers: {
7027
8019
  GET: async ({ request }) => {
@@ -7043,7 +8035,7 @@ const Route$b = createFileRoute("/api/knowledge/search")({
7043
8035
  }
7044
8036
  }
7045
8037
  });
7046
- const Route$a = createFileRoute("/api/knowledge/project-context")({
8038
+ const Route$b = createFileRoute("/api/knowledge/project-context")({
7047
8039
  server: {
7048
8040
  handlers: {
7049
8041
  GET: async ({ request }) => {
@@ -7125,14 +8117,14 @@ function updateCandidateDraft(id, updates) {
7125
8117
  candidates.set(id, updated);
7126
8118
  return updated;
7127
8119
  }
7128
- const Route$9 = createFileRoute("/api/knowledge/candidates")({
8120
+ const Route$a = createFileRoute("/api/knowledge/candidates")({
7129
8121
  server: {
7130
8122
  handlers: {
7131
8123
  GET: () => Response.json({ candidates: listCandidates() })
7132
8124
  }
7133
8125
  }
7134
8126
  });
7135
- const Route$8 = createFileRoute("/api/config/paths")({
8127
+ const Route$9 = createFileRoute("/api/config/paths")({
7136
8128
  server: {
7137
8129
  handlers: {
7138
8130
  GET: () => {
@@ -7143,6 +8135,653 @@ const Route$8 = createFileRoute("/api/config/paths")({
7143
8135
  }
7144
8136
  }
7145
8137
  });
8138
+ function formatNullable(value) {
8139
+ return value === null ? "n/a" : String(value);
8140
+ }
8141
+ function formatList$1(values) {
8142
+ return values.length > 0 ? values.join(", ") : "n/a";
8143
+ }
8144
+ function lower(value) {
8145
+ return value.toLowerCase();
8146
+ }
8147
+ function logEvidenceText(log) {
8148
+ return lower(
8149
+ [
8150
+ log.error,
8151
+ log.path,
8152
+ log.model,
8153
+ log.provider,
8154
+ log.status === null ? null : String(log.status),
8155
+ log.isStreaming ? "streaming" : "non-streaming"
8156
+ ].filter((value) => value !== null).join(" ")
8157
+ );
8158
+ }
8159
+ function collectErrorCandidates(session) {
8160
+ const candidates2 = session.latestLogs.filter((log) => log.hasError).map((log) => ({ log, text: logEvidenceText(log) }));
8161
+ if (session.lastTaskError !== null) {
8162
+ candidates2.push({ log: null, text: lower(session.lastTaskError) });
8163
+ }
8164
+ return candidates2;
8165
+ }
8166
+ function hasText(candidates2, patterns) {
8167
+ return candidates2.some(
8168
+ (candidate) => patterns.some((pattern) => candidate.text.includes(pattern))
8169
+ );
8170
+ }
8171
+ function hasStatus(session, predicate) {
8172
+ return session.latestLogs.some((log) => log.status !== null && predicate(log.status));
8173
+ }
8174
+ function evidenceIds(candidates2) {
8175
+ const ids = /* @__PURE__ */ new Set();
8176
+ for (const candidate of candidates2) {
8177
+ if (candidate.log !== null) {
8178
+ ids.add(candidate.log.id);
8179
+ }
8180
+ }
8181
+ return [...ids].sort((left, right) => left - right);
8182
+ }
8183
+ function hasStreamingMismatch(session) {
8184
+ for (const failed of session.latestLogs.filter((log) => log.hasError)) {
8185
+ const matchingSuccess = session.latestLogs.some(
8186
+ (log) => !log.hasError && log.model === failed.model && log.provider === failed.provider && log.isStreaming !== failed.isStreaming
8187
+ );
8188
+ if (matchingSuccess) return true;
8189
+ }
8190
+ return false;
8191
+ }
8192
+ function categorySummary(category) {
8193
+ switch (category) {
8194
+ case "none":
8195
+ return "Session completed without captured errors.";
8196
+ case "active":
8197
+ return "Session still has active requests or running tasks.";
8198
+ case "provider-timeout":
8199
+ return "Provider call appears to have timed out.";
8200
+ case "provider-http-error":
8201
+ return "Provider returned an HTTP error.";
8202
+ case "provider-auth-error":
8203
+ return "Provider authentication or API key configuration appears invalid.";
8204
+ case "provider-rate-limit":
8205
+ return "Provider rejected the request because of rate limit or quota pressure.";
8206
+ case "network-or-container":
8207
+ return "Network connectivity looks suspicious; check container boundaries and localhost use.";
8208
+ case "streaming-mismatch":
8209
+ return "Streaming and non-streaming behavior differ for the same provider/model.";
8210
+ case "context-pressure":
8211
+ return "The failure looks related to context window or token pressure.";
8212
+ case "runtime-task-error":
8213
+ return "Inspector session runtime reported a task error.";
8214
+ case "model-output-error":
8215
+ return "The model output or upstream payload shape appears invalid.";
8216
+ case "unknown-failure":
8217
+ return "Inspector captured errors, but the root cause is not yet classified.";
8218
+ case "no-session-data":
8219
+ return "No Inspector session data was available for this run.";
8220
+ }
8221
+ }
8222
+ function categoryHints(category) {
8223
+ switch (category) {
8224
+ case "none":
8225
+ return ["Keep the evidence pack as the success proof for CI or release notes."];
8226
+ case "active":
8227
+ return ["Wait for the session to settle, then export evidence again."];
8228
+ case "provider-timeout":
8229
+ return [
8230
+ "Increase the provider test timeout for slow models.",
8231
+ "Compare streaming and non-streaming probes for the same model."
8232
+ ];
8233
+ case "provider-http-error":
8234
+ return [
8235
+ "Open the cited log ids and inspect upstream status, response body, and provider URL."
8236
+ ];
8237
+ case "provider-auth-error":
8238
+ return ["Verify the provider API key, auth header mode, and configured base URL."];
8239
+ case "provider-rate-limit":
8240
+ return [
8241
+ "Retry after provider quota recovers or switch to a less constrained provider/model."
8242
+ ];
8243
+ case "network-or-container":
8244
+ return [
8245
+ "If the coding agent and Inspector are in different containers, replace localhost with a reachable host.",
8246
+ "Check DNS, proxy, firewall, and container network routes."
8247
+ ];
8248
+ case "streaming-mismatch":
8249
+ return [
8250
+ "Compare the streaming and non-streaming captured logs side by side.",
8251
+ "Check whether the provider's OpenAI-compatible endpoint handles stream=false differently."
8252
+ ];
8253
+ case "context-pressure":
8254
+ return [
8255
+ "Reduce prompt/tool schema/history size or configure the model context window metadata."
8256
+ ];
8257
+ case "runtime-task-error":
8258
+ return ["Inspect the session runtime error and retry after clearing stuck session tasks."];
8259
+ case "model-output-error":
8260
+ return ["Inspect the raw response and schema parsing details for the cited log ids."];
8261
+ case "unknown-failure":
8262
+ return [
8263
+ "Open the cited logs and inspect error/status/latency fields to refine the diagnosis."
8264
+ ];
8265
+ case "no-session-data":
8266
+ return [
8267
+ "Confirm the run's sessionId matches captured traffic and that history scanning is enabled when needed."
8268
+ ];
8269
+ }
8270
+ }
8271
+ function classifyCategory(session, candidates2) {
8272
+ if (session.status === "active") return "active";
8273
+ if (session.errorCount === 0 && session.failedRequests === 0 && session.lastTaskError === null) {
8274
+ return "none";
8275
+ }
8276
+ if (hasStreamingMismatch(session)) return "streaming-mismatch";
8277
+ if (hasText(candidates2, [
8278
+ "timeout",
8279
+ "timed out",
8280
+ "etimedout",
8281
+ "deadline",
8282
+ "aborterror",
8283
+ "econnaborted"
8284
+ ])) {
8285
+ return "provider-timeout";
8286
+ }
8287
+ if (hasText(candidates2, ["localhost", "econnrefused", "enotfound", "network", "fetch failed"])) {
8288
+ return "network-or-container";
8289
+ }
8290
+ if (hasStatus(session, (status) => status === 401 || status === 403)) {
8291
+ return "provider-auth-error";
8292
+ }
8293
+ if (hasText(candidates2, ["unauthorized", "forbidden", "invalid api key", "api key"])) {
8294
+ return "provider-auth-error";
8295
+ }
8296
+ if (hasStatus(session, (status) => status === 429)) return "provider-rate-limit";
8297
+ if (hasText(candidates2, ["rate limit", "quota"])) return "provider-rate-limit";
8298
+ if (hasText(candidates2, ["context", "too many tokens", "token limit", "maximum context"])) {
8299
+ return "context-pressure";
8300
+ }
8301
+ if (session.lastTaskError !== null) return "runtime-task-error";
8302
+ if (hasText(candidates2, ["invalid json", "schema", "parse", "malformed"])) {
8303
+ return "model-output-error";
8304
+ }
8305
+ if (hasStatus(session, (status) => status >= 400)) return "provider-http-error";
8306
+ return "unknown-failure";
8307
+ }
8308
+ function severityFor(category) {
8309
+ switch (category) {
8310
+ case "none":
8311
+ return "none";
8312
+ case "active":
8313
+ return "low";
8314
+ case "provider-timeout":
8315
+ case "provider-http-error":
8316
+ case "provider-auth-error":
8317
+ case "provider-rate-limit":
8318
+ case "network-or-container":
8319
+ case "streaming-mismatch":
8320
+ case "runtime-task-error":
8321
+ return "high";
8322
+ case "context-pressure":
8323
+ case "model-output-error":
8324
+ case "unknown-failure":
8325
+ case "no-session-data":
8326
+ return "medium";
8327
+ }
8328
+ }
8329
+ function outcomeFor$1(category) {
8330
+ switch (category) {
8331
+ case "none":
8332
+ return "success";
8333
+ case "active":
8334
+ return "active";
8335
+ case "no-session-data":
8336
+ return "unknown";
8337
+ case "provider-timeout":
8338
+ case "provider-http-error":
8339
+ case "provider-auth-error":
8340
+ case "provider-rate-limit":
8341
+ case "network-or-container":
8342
+ case "streaming-mismatch":
8343
+ case "context-pressure":
8344
+ case "runtime-task-error":
8345
+ case "model-output-error":
8346
+ case "unknown-failure":
8347
+ return "failure";
8348
+ }
8349
+ }
8350
+ function classifyRunFailure(run, session) {
8351
+ if (session === null) {
8352
+ const category2 = "no-session-data";
8353
+ return {
8354
+ outcome: outcomeFor$1(category2),
8355
+ severity: severityFor(category2),
8356
+ category: category2,
8357
+ summary: `${categorySummary(category2)} Run ${run.id} is bound to session ${run.sessionId}.`,
8358
+ evidenceLogIds: [],
8359
+ hints: categoryHints(category2)
8360
+ };
8361
+ }
8362
+ const candidates2 = collectErrorCandidates(session);
8363
+ const category = classifyCategory(session, candidates2);
8364
+ const summary = category === "none" ? `${categorySummary(category)} ${String(session.requestCount)} request(s), ${String(
8365
+ session.tokenUsage.total
8366
+ )} token(s).` : `${categorySummary(category)} ${String(session.errorCount)} error log(s), ${String(
8367
+ session.failedRequests
8368
+ )} failed request(s).`;
8369
+ return {
8370
+ outcome: outcomeFor$1(category),
8371
+ severity: severityFor(category),
8372
+ category,
8373
+ summary,
8374
+ evidenceLogIds: evidenceIds(candidates2),
8375
+ hints: categoryHints(category)
8376
+ };
8377
+ }
8378
+ function logTitle(log) {
8379
+ if (log.hasError) return `Request failed: log ${String(log.id)}`;
8380
+ if (log.status !== null && log.status >= 200 && log.status < 400) {
8381
+ return `Request succeeded: log ${String(log.id)}`;
8382
+ }
8383
+ return `Request captured: log ${String(log.id)}`;
8384
+ }
8385
+ function logDetails(log) {
8386
+ const parts = [
8387
+ `path=${log.path}`,
8388
+ `status=${formatNullable(log.status)}`,
8389
+ `model=${formatNullable(log.model)}`,
8390
+ `provider=${formatNullable(log.provider)}`,
8391
+ `streaming=${log.isStreaming ? "yes" : "no"}`,
8392
+ `latencyMs=${formatNullable(log.latencyMs)}`
8393
+ ];
8394
+ if (log.error !== null) {
8395
+ parts.push(`error=${log.error}`);
8396
+ }
8397
+ return parts.join(", ");
8398
+ }
8399
+ function buildRunTimeline(run, session, exportedAt) {
8400
+ const events = [
8401
+ {
8402
+ timestamp: run.createdAt,
8403
+ kind: "run-created",
8404
+ severity: "info",
8405
+ title: "Run declared",
8406
+ details: `Run ${run.id} was declared for session ${run.sessionId}.`,
8407
+ logId: null,
8408
+ status: null,
8409
+ model: null,
8410
+ provider: null,
8411
+ latencyMs: null,
8412
+ isStreaming: null
8413
+ }
8414
+ ];
8415
+ if (session !== null && session.createdAt !== null) {
8416
+ events.push({
8417
+ timestamp: session.createdAt,
8418
+ kind: "session-started",
8419
+ severity: "info",
8420
+ title: "Session started",
8421
+ details: `Inspector session ${session.id} started with source ${session.source ?? "unknown"}.`,
8422
+ logId: null,
8423
+ status: null,
8424
+ model: null,
8425
+ provider: null,
8426
+ latencyMs: null,
8427
+ isStreaming: null
8428
+ });
8429
+ }
8430
+ if (session !== null) {
8431
+ const logs = [...session.latestLogs].sort(
8432
+ (left, right) => left.timestamp.localeCompare(right.timestamp)
8433
+ );
8434
+ for (const log of logs) {
8435
+ events.push({
8436
+ timestamp: log.timestamp,
8437
+ kind: "request",
8438
+ severity: log.hasError ? "error" : "success",
8439
+ title: logTitle(log),
8440
+ details: logDetails(log),
8441
+ logId: log.id,
8442
+ status: log.status,
8443
+ model: log.model,
8444
+ provider: log.provider,
8445
+ latencyMs: log.latencyMs,
8446
+ isStreaming: log.isStreaming
8447
+ });
8448
+ }
8449
+ if (session.updatedAt !== null) {
8450
+ events.push({
8451
+ timestamp: session.updatedAt,
8452
+ kind: "session-finished",
8453
+ severity: session.status === "failed" ? "error" : session.status === "active" ? "warning" : "success",
8454
+ title: `Session ${session.status}`,
8455
+ details: `${String(session.requestCount)} request(s), ${String(
8456
+ session.errorCount
8457
+ )} error(s), ${String(session.tokenUsage.total)} token(s).`,
8458
+ logId: session.lastLogId,
8459
+ status: null,
8460
+ model: session.lastModel,
8461
+ provider: null,
8462
+ latencyMs: null,
8463
+ isStreaming: null
8464
+ });
8465
+ }
8466
+ }
8467
+ events.push({
8468
+ timestamp: exportedAt,
8469
+ kind: "evidence-exported",
8470
+ severity: "info",
8471
+ title: "Evidence exported",
8472
+ details: `Evidence pack exported at ${exportedAt}.`,
8473
+ logId: null,
8474
+ status: null,
8475
+ model: null,
8476
+ provider: null,
8477
+ latencyMs: null,
8478
+ isStreaming: null
8479
+ });
8480
+ return events.sort((left, right) => left.timestamp.localeCompare(right.timestamp));
8481
+ }
8482
+ function reportStatus(classification) {
8483
+ switch (classification.outcome) {
8484
+ case "success":
8485
+ return "PASS";
8486
+ case "failure":
8487
+ return "FAIL";
8488
+ case "active":
8489
+ return "RUNNING";
8490
+ case "unknown":
8491
+ return "UNKNOWN";
8492
+ }
8493
+ }
8494
+ function timelineMarkdown(timeline) {
8495
+ if (timeline.length === 0) return ["No timeline events were available."];
8496
+ return [
8497
+ "| Time | Event | Severity | Evidence |",
8498
+ "| --- | --- | --- | --- |",
8499
+ ...timeline.map((event) => {
8500
+ const evidence = event.logId === null ? "n/a" : `log ${String(event.logId)}`;
8501
+ return `| ${event.timestamp} | ${event.title} | ${event.severity} | ${evidence} |`;
8502
+ })
8503
+ ];
8504
+ }
8505
+ function buildJenkinsReport(input) {
8506
+ const status = reportStatus(input.classification);
8507
+ const session = input.session;
8508
+ const summary = `[${status}] ${input.classification.summary}`;
8509
+ const lines = [
8510
+ `# Jenkins Report: ${input.run.title}`,
8511
+ "",
8512
+ `- Status: ${status}`,
8513
+ `- Run ID: ${input.run.id}`,
8514
+ `- Session ID: ${input.run.sessionId}`,
8515
+ `- Inspector URL: ${session?.inspectorUrl ?? "n/a"}`,
8516
+ `- Category: ${input.classification.category}`,
8517
+ `- Severity: ${input.classification.severity}`,
8518
+ `- Summary: ${input.classification.summary}`,
8519
+ `- Evidence Logs: ${input.classification.evidenceLogIds.length > 0 ? input.classification.evidenceLogIds.map((id) => `#${String(id)}`).join(", ") : "n/a"}`,
8520
+ "",
8521
+ "## Session Metrics",
8522
+ "",
8523
+ `- Requests: ${String(session?.requestCount ?? 0)}`,
8524
+ `- Errors: ${String(session?.errorCount ?? 0)}`,
8525
+ `- Models: ${formatList$1(session?.models ?? [])}`,
8526
+ `- Providers: ${formatList$1(session?.providers ?? [])}`,
8527
+ `- Tokens: ${String(session?.tokenUsage.total ?? 0)}`,
8528
+ "",
8529
+ "## Timeline",
8530
+ "",
8531
+ ...timelineMarkdown(input.timeline),
8532
+ "",
8533
+ "## Artifacts",
8534
+ "",
8535
+ `- JSON: ${input.evidencePaths.jsonPath}`,
8536
+ `- Markdown: ${input.evidencePaths.markdownPath}`,
8537
+ `- HTML: ${input.evidencePaths.htmlPath}`,
8538
+ "",
8539
+ "## Next Actions",
8540
+ "",
8541
+ ...input.classification.hints.map((hint) => `- ${hint}`),
8542
+ ""
8543
+ ];
8544
+ return {
8545
+ status,
8546
+ summary,
8547
+ markdown: lines.join("\n")
8548
+ };
8549
+ }
8550
+ function analyzeEvidence(input) {
8551
+ const classification = classifyRunFailure(input.run, input.session);
8552
+ const timeline = buildRunTimeline(input.run, input.session, input.exportedAt);
8553
+ const jenkinsReport = buildJenkinsReport({
8554
+ run: input.run,
8555
+ session: input.session,
8556
+ timeline,
8557
+ classification,
8558
+ evidencePaths: input.evidencePaths
8559
+ });
8560
+ return { classification, timeline, jenkinsReport };
8561
+ }
8562
+ function evidenceDir(runId) {
8563
+ return join(getDataDir(), "evidence", runId);
8564
+ }
8565
+ function jsonPath(runId) {
8566
+ return join(evidenceDir(runId), "evidence.json");
8567
+ }
8568
+ function markdownPath(runId) {
8569
+ return join(evidenceDir(runId), "evidence.md");
8570
+ }
8571
+ function htmlPath(runId) {
8572
+ return join(evidenceDir(runId), "evidence.html");
8573
+ }
8574
+ function formatList(values) {
8575
+ return values.length > 0 ? values.join(", ") : "n/a";
8576
+ }
8577
+ function escapeHtml(value) {
8578
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
8579
+ }
8580
+ function buildMarkdown(document) {
8581
+ const session = document.session;
8582
+ const lines = [
8583
+ `# ${document.run.title}`,
8584
+ "",
8585
+ `- Run ID: ${document.run.id}`,
8586
+ `- Session ID: ${document.run.sessionId}`,
8587
+ `- Status: ${document.run.status}`,
8588
+ `- Project: ${document.run.project ?? "n/a"}`,
8589
+ `- Agent: ${document.run.agent ?? "n/a"}`,
8590
+ `- Created: ${document.run.createdAt}`,
8591
+ "",
8592
+ `- Outcome: ${document.classification.outcome}`,
8593
+ `- Failure Category: ${document.classification.category}`,
8594
+ `- Severity: ${document.classification.severity}`,
8595
+ `- Evidence JSON: ${document.evidence.jsonPath}`,
8596
+ `- Evidence HTML: ${document.evidence.htmlPath}`,
8597
+ ""
8598
+ ];
8599
+ if (document.run.task !== null) {
8600
+ lines.push("## Task", "", document.run.task, "");
8601
+ }
8602
+ if (session === null) {
8603
+ lines.push("## Session", "", "No Inspector session data was available for this run.", "");
8604
+ } else {
8605
+ lines.push(
8606
+ "## Session",
8607
+ "",
8608
+ `- Inspector URL: ${session.inspectorUrl}`,
8609
+ `- Compact Logs URL: ${session.compactLogsUrl}`,
8610
+ `- Status: ${session.status}`,
8611
+ `- Coverage: ${session.logCoverage}`,
8612
+ `- Requests: ${session.requestCount}`,
8613
+ `- Errors: ${session.errorCount}`,
8614
+ `- Models: ${formatList(session.models)}`,
8615
+ `- Providers: ${formatList(session.providers)}`,
8616
+ `- Tokens: ${session.tokenUsage.total}`,
8617
+ "",
8618
+ "## Failure Classification",
8619
+ "",
8620
+ document.classification.summary,
8621
+ "",
8622
+ "### Evidence Logs",
8623
+ "",
8624
+ document.classification.evidenceLogIds.length > 0 ? document.classification.evidenceLogIds.map((id) => `- #${id}`).join("\n") : "No failing log ids were identified.",
8625
+ "",
8626
+ "### Suggested Next Actions",
8627
+ "",
8628
+ ...document.classification.hints.map((hint) => `- ${hint}`),
8629
+ "",
8630
+ "## Run Timeline",
8631
+ "",
8632
+ "| Time | Event | Severity | Evidence |",
8633
+ "| --- | --- | --- | --- |",
8634
+ ...document.timeline.map((event) => {
8635
+ const evidence = event.logId === null ? "n/a" : `log ${event.logId}`;
8636
+ return `| ${event.timestamp} | ${event.title} | ${event.severity} | ${evidence} |`;
8637
+ }),
8638
+ "",
8639
+ "## Latest Logs",
8640
+ ""
8641
+ );
8642
+ if (session.latestLogs.length === 0) {
8643
+ lines.push("No compact logs were available.", "");
8644
+ } else {
8645
+ lines.push(
8646
+ "| ID | Status | Model | Latency | Path | Error |",
8647
+ "| --- | --- | --- | --- | --- | --- |"
8648
+ );
8649
+ for (const log of session.latestLogs) {
8650
+ lines.push(
8651
+ `| ${log.id} | ${log.status ?? "n/a"} | ${log.model ?? "n/a"} | ${log.latencyMs ?? "n/a"} | ${log.path} | ${log.hasError ? "yes" : "no"} |`
8652
+ );
8653
+ }
8654
+ lines.push("");
8655
+ }
8656
+ }
8657
+ lines.push("## Jenkins Report", "", document.jenkinsReport.markdown, "");
8658
+ return `${lines.join("\n")}
8659
+ `;
8660
+ }
8661
+ function buildHtml(markdown, title) {
8662
+ return `<!doctype html>
8663
+ <html lang="en">
8664
+ <head>
8665
+ <meta charset="utf-8">
8666
+ <title>${escapeHtml(title)}</title>
8667
+ <style>
8668
+ body { font-family: system-ui, sans-serif; max-width: 960px; margin: 40px auto; line-height: 1.5; }
8669
+ pre { white-space: pre-wrap; background: #f6f8fa; padding: 16px; border-radius: 8px; }
8670
+ </style>
8671
+ </head>
8672
+ <body>
8673
+ <pre>${escapeHtml(markdown)}</pre>
8674
+ </body>
8675
+ </html>
8676
+ `;
8677
+ }
8678
+ async function exportRunEvidence(run, options, baseUrl) {
8679
+ const session = await getSessionInfo(run.sessionId, {
8680
+ baseUrl,
8681
+ includeHistory: options?.includeHistory,
8682
+ latestLogLimit: options?.latestLogLimit
8683
+ });
8684
+ const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
8685
+ const evidence = {
8686
+ jsonPath: jsonPath(run.id),
8687
+ markdownPath: markdownPath(run.id),
8688
+ htmlPath: htmlPath(run.id),
8689
+ exportedAt
8690
+ };
8691
+ const documentRun = {
8692
+ ...run,
8693
+ evidence,
8694
+ updatedAt: exportedAt
8695
+ };
8696
+ const analysis = analyzeEvidence({
8697
+ run: documentRun,
8698
+ session,
8699
+ exportedAt,
8700
+ evidencePaths: {
8701
+ jsonPath: evidence.jsonPath,
8702
+ markdownPath: evidence.markdownPath,
8703
+ htmlPath: evidence.htmlPath
8704
+ }
8705
+ });
8706
+ const document = {
8707
+ run: documentRun,
8708
+ evidence,
8709
+ session,
8710
+ timeline: analysis.timeline,
8711
+ classification: analysis.classification,
8712
+ jenkinsReport: analysis.jenkinsReport
8713
+ };
8714
+ const markdown = buildMarkdown(document);
8715
+ mkdirSync(evidenceDir(run.id), { recursive: true });
8716
+ writeFileSync(
8717
+ evidence.jsonPath,
8718
+ `${JSON.stringify({ ...document, evidence }, null, 2)}
8719
+ `,
8720
+ "utf8"
8721
+ );
8722
+ writeFileSync(evidence.markdownPath, markdown, "utf8");
8723
+ writeFileSync(evidence.htmlPath, buildHtml(markdown, run.title), "utf8");
8724
+ const updatedRun = updateRunEvidence(run.id, evidence) ?? documentRun;
8725
+ return { ...document, run: updatedRun };
8726
+ }
8727
+ function readEvidenceMarkdown(run) {
8728
+ const path2 = run.evidence?.markdownPath;
8729
+ if (path2 === void 0 || !existsSync(path2)) return null;
8730
+ try {
8731
+ return readFileSync(path2, "utf8");
8732
+ } catch {
8733
+ return null;
8734
+ }
8735
+ }
8736
+ async function readJsonBody(request) {
8737
+ try {
8738
+ const raw = await request.text();
8739
+ if (raw.trim().length === 0) return { ok: true, value: void 0 };
8740
+ return { ok: true, value: JSON.parse(raw) };
8741
+ } catch {
8742
+ return { ok: false };
8743
+ }
8744
+ }
8745
+ const Route$8 = createFileRoute("/api/runs/$runId/evidence")({
8746
+ server: {
8747
+ handlers: {
8748
+ GET: ({ params }) => {
8749
+ const run = getRun(params.runId);
8750
+ if (run === null) {
8751
+ return Response.json({ error: "Run not found" }, { status: 404 });
8752
+ }
8753
+ const markdown = readEvidenceMarkdown(run);
8754
+ if (run.evidence === null || markdown === null) {
8755
+ return Response.json(
8756
+ { error: "Evidence has not been exported for this run" },
8757
+ { status: 404 }
8758
+ );
8759
+ }
8760
+ return Response.json({ run, evidence: run.evidence, markdown });
8761
+ },
8762
+ POST: async ({ params, request }) => {
8763
+ const run = getRun(params.runId);
8764
+ if (run === null) {
8765
+ return Response.json({ error: "Run not found" }, { status: 404 });
8766
+ }
8767
+ const body = await readJsonBody(request);
8768
+ if (!body.ok) {
8769
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
8770
+ }
8771
+ const parsed = EvidenceExportOptionsSchema.safeParse(body.value);
8772
+ if (!parsed.success) {
8773
+ return Response.json(
8774
+ { error: "Invalid request body", details: parsed.error.format() },
8775
+ { status: 400 }
8776
+ );
8777
+ }
8778
+ return Response.json(
8779
+ await exportRunEvidence(run, parsed.data, new URL(request.url).origin)
8780
+ );
8781
+ }
8782
+ }
8783
+ }
8784
+ });
7146
8785
  const MEMORY_PROBE_FACTS = [
7147
8786
  "Project fact: Agent Inspector captures LLM traffic and turns it into Raw Trace, Session Episode, and Memory Candidate layers.",
7148
8787
  "Procedure: before release, run bun test, npm run typecheck, npm run lint, npm run build, and openspec validate --all --strict.",
@@ -8562,106 +10201,121 @@ const Route = createFileRoute("/api/knowledge/candidates/$candidateId/promote")(
8562
10201
  }
8563
10202
  }
8564
10203
  });
8565
- const IndexRoute = Route$r.update({
10204
+ const IndexRoute = Route$u.update({
8566
10205
  id: "/",
8567
10206
  path: "/",
8568
- getParentRoute: () => Route$s
10207
+ getParentRoute: () => Route$v
8569
10208
  });
8570
- const SessionSessionIdRoute = Route$q.update({
10209
+ const SessionSessionIdRoute = Route$t.update({
8571
10210
  id: "/session/$sessionId",
8572
10211
  path: "/session/$sessionId",
8573
- getParentRoute: () => Route$s
10212
+ getParentRoute: () => Route$v
8574
10213
  });
8575
- const ProxySplatRoute = Route$p.update({
10214
+ const ProxySplatRoute = Route$s.update({
8576
10215
  id: "/proxy/$",
8577
10216
  path: "/proxy/$",
8578
- getParentRoute: () => Route$s
10217
+ getParentRoute: () => Route$v
8579
10218
  });
8580
- const ApiSessionsRoute = Route$o.update({
10219
+ const ApiSessionsRoute = Route$r.update({
8581
10220
  id: "/api/sessions",
8582
10221
  path: "/api/sessions",
8583
- getParentRoute: () => Route$s
10222
+ getParentRoute: () => Route$v
8584
10223
  });
8585
- const ApiProvidersRoute = Route$n.update({
10224
+ const ApiRunsRoute = Route$q.update({
10225
+ id: "/api/runs",
10226
+ path: "/api/runs",
10227
+ getParentRoute: () => Route$v
10228
+ });
10229
+ const ApiProvidersRoute = Route$p.update({
8586
10230
  id: "/api/providers",
8587
10231
  path: "/api/providers",
8588
- getParentRoute: () => Route$s
10232
+ getParentRoute: () => Route$v
8589
10233
  });
8590
- const ApiModelsRoute = Route$m.update({
10234
+ const ApiModelsRoute = Route$o.update({
8591
10235
  id: "/api/models",
8592
10236
  path: "/api/models",
8593
- getParentRoute: () => Route$s
10237
+ getParentRoute: () => Route$v
8594
10238
  });
8595
- const ApiMcpRoute = Route$l.update({
10239
+ const ApiMcpRoute = Route$n.update({
8596
10240
  id: "/api/mcp",
8597
10241
  path: "/api/mcp",
8598
- getParentRoute: () => Route$s
10242
+ getParentRoute: () => Route$v
8599
10243
  });
8600
- const ApiLogsRoute = Route$k.update({
10244
+ const ApiLogsRoute = Route$m.update({
8601
10245
  id: "/api/logs",
8602
10246
  path: "/api/logs",
8603
- getParentRoute: () => Route$s
10247
+ getParentRoute: () => Route$v
8604
10248
  });
8605
- const ApiHealthRoute = Route$j.update({
10249
+ const ApiHealthRoute = Route$l.update({
8606
10250
  id: "/api/health",
8607
10251
  path: "/api/health",
8608
- getParentRoute: () => Route$s
10252
+ getParentRoute: () => Route$v
8609
10253
  });
8610
- const ApiConfigRoute = Route$i.update({
10254
+ const ApiConfigRoute = Route$k.update({
8611
10255
  id: "/api/config",
8612
10256
  path: "/api/config",
8613
- getParentRoute: () => Route$s
10257
+ getParentRoute: () => Route$v
10258
+ });
10259
+ const ApiRunsRunIdRoute = Route$j.update({
10260
+ id: "/$runId",
10261
+ path: "/$runId",
10262
+ getParentRoute: () => ApiRunsRoute
8614
10263
  });
8615
- const ApiProvidersScanRoute = Route$h.update({
10264
+ const ApiProvidersScanRoute = Route$i.update({
8616
10265
  id: "/scan",
8617
10266
  path: "/scan",
8618
10267
  getParentRoute: () => ApiProvidersRoute
8619
10268
  });
8620
- const ApiProvidersImportRoute = Route$g.update({
10269
+ const ApiProvidersImportRoute = Route$h.update({
8621
10270
  id: "/import",
8622
10271
  path: "/import",
8623
10272
  getParentRoute: () => ApiProvidersRoute
8624
10273
  });
8625
- const ApiProvidersExportRoute = Route$f.update({
10274
+ const ApiProvidersExportRoute = Route$g.update({
8626
10275
  id: "/export",
8627
10276
  path: "/export",
8628
10277
  getParentRoute: () => ApiProvidersRoute
8629
10278
  });
8630
- const ApiProvidersProviderIdRoute = Route$e.update({
10279
+ const ApiProvidersProviderIdRoute = Route$f.update({
8631
10280
  id: "/$providerId",
8632
10281
  path: "/$providerId",
8633
10282
  getParentRoute: () => ApiProvidersRoute
8634
10283
  });
8635
- const ApiLogsStreamRoute = Route$d.update({
10284
+ const ApiLogsStreamRoute = Route$e.update({
8636
10285
  id: "/stream",
8637
10286
  path: "/stream",
8638
10287
  getParentRoute: () => ApiLogsRoute
8639
10288
  });
8640
- const ApiLogsIdRoute = Route$c.update({
10289
+ const ApiLogsIdRoute = Route$d.update({
8641
10290
  id: "/$id",
8642
10291
  path: "/$id",
8643
10292
  getParentRoute: () => ApiLogsRoute
8644
10293
  });
8645
- const ApiKnowledgeSearchRoute = Route$b.update({
10294
+ const ApiKnowledgeSearchRoute = Route$c.update({
8646
10295
  id: "/api/knowledge/search",
8647
10296
  path: "/api/knowledge/search",
8648
- getParentRoute: () => Route$s
10297
+ getParentRoute: () => Route$v
8649
10298
  });
8650
- const ApiKnowledgeProjectContextRoute = Route$a.update({
10299
+ const ApiKnowledgeProjectContextRoute = Route$b.update({
8651
10300
  id: "/api/knowledge/project-context",
8652
10301
  path: "/api/knowledge/project-context",
8653
- getParentRoute: () => Route$s
10302
+ getParentRoute: () => Route$v
8654
10303
  });
8655
- const ApiKnowledgeCandidatesRoute = Route$9.update({
10304
+ const ApiKnowledgeCandidatesRoute = Route$a.update({
8656
10305
  id: "/api/knowledge/candidates",
8657
10306
  path: "/api/knowledge/candidates",
8658
- getParentRoute: () => Route$s
10307
+ getParentRoute: () => Route$v
8659
10308
  });
8660
- const ApiConfigPathsRoute = Route$8.update({
10309
+ const ApiConfigPathsRoute = Route$9.update({
8661
10310
  id: "/paths",
8662
10311
  path: "/paths",
8663
10312
  getParentRoute: () => ApiConfigRoute
8664
10313
  });
10314
+ const ApiRunsRunIdEvidenceRoute = Route$8.update({
10315
+ id: "/evidence",
10316
+ path: "/evidence",
10317
+ getParentRoute: () => ApiRunsRunIdRoute
10318
+ });
8665
10319
  const ApiProvidersProviderIdTestRoute = Route$7.update({
8666
10320
  id: "/test",
8667
10321
  path: "/test",
@@ -8695,7 +10349,7 @@ const ApiProvidersProviderIdTestLogRoute = Route$2.update({
8695
10349
  const ApiKnowledgeSessionsSessionIdCandidatesRoute = Route$1.update({
8696
10350
  id: "/api/knowledge/sessions/$sessionId/candidates",
8697
10351
  path: "/api/knowledge/sessions/$sessionId/candidates",
8698
- getParentRoute: () => Route$s
10352
+ getParentRoute: () => Route$v
8699
10353
  });
8700
10354
  const ApiKnowledgeCandidatesCandidateIdPromoteRoute = Route.update({
8701
10355
  id: "/promote",
@@ -8742,6 +10396,16 @@ const ApiProvidersRouteChildren = {
8742
10396
  const ApiProvidersRouteWithChildren = ApiProvidersRoute._addFileChildren(
8743
10397
  ApiProvidersRouteChildren
8744
10398
  );
10399
+ const ApiRunsRunIdRouteChildren = {
10400
+ ApiRunsRunIdEvidenceRoute
10401
+ };
10402
+ const ApiRunsRunIdRouteWithChildren = ApiRunsRunIdRoute._addFileChildren(
10403
+ ApiRunsRunIdRouteChildren
10404
+ );
10405
+ const ApiRunsRouteChildren = {
10406
+ ApiRunsRunIdRoute: ApiRunsRunIdRouteWithChildren
10407
+ };
10408
+ const ApiRunsRouteWithChildren = ApiRunsRoute._addFileChildren(ApiRunsRouteChildren);
8745
10409
  const ApiKnowledgeCandidatesCandidateIdRouteChildren = {
8746
10410
  ApiKnowledgeCandidatesCandidateIdPromoteRoute
8747
10411
  };
@@ -8762,6 +10426,7 @@ const rootRouteChildren = {
8762
10426
  ApiMcpRoute,
8763
10427
  ApiModelsRoute,
8764
10428
  ApiProvidersRoute: ApiProvidersRouteWithChildren,
10429
+ ApiRunsRoute: ApiRunsRouteWithChildren,
8765
10430
  ApiSessionsRoute,
8766
10431
  ProxySplatRoute,
8767
10432
  SessionSessionIdRoute,
@@ -8770,7 +10435,7 @@ const rootRouteChildren = {
8770
10435
  ApiKnowledgeSearchRoute,
8771
10436
  ApiKnowledgeSessionsSessionIdCandidatesRoute
8772
10437
  };
8773
- const routeTree = Route$s._addFileChildren(rootRouteChildren)._addFileTypes();
10438
+ const routeTree = Route$v._addFileChildren(rootRouteChildren)._addFileTypes();
8774
10439
  function getRouter() {
8775
10440
  const router2 = createRouter({
8776
10441
  routeTree,
@@ -8790,7 +10455,7 @@ export {
8790
10455
  MAX_SLOW_RESPONSE_THRESHOLD_SECONDS as M,
8791
10456
  OpenAIRequestSchema as O,
8792
10457
  ProviderConfigSchema as P,
8793
- Route$q as R,
10458
+ Route$t as R,
8794
10459
  TimeDisplayFormatSchema as T,
8795
10460
  DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS as a,
8796
10461
  DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS as b,