@tonyclaw/agent-inspector 2.0.25 → 2.0.27
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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-DVNvS0GQ.js → CompareDrawer-B_p6vmMQ.js} +1 -1
- package/.output/public/assets/{ProxyViewerContainer-By3XtQhZ.js → ProxyViewerContainer-DItqh1EO.js} +4 -4
- package/.output/public/assets/{ReplayDialog-Cw6bN-NR.js → ReplayDialog-BmprJ6D3.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-DnIsirBE.js → RequestAnatomy-DqO0_SVJ.js} +1 -1
- package/.output/public/assets/{ResponseView-ygsUs7FU.js → ResponseView-FZbPNdHa.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-DMUn8qXi.js → StreamingChunkSequence-BZPzfJWj.js} +1 -1
- package/.output/public/assets/_sessionId-y2ATIp51.js +1 -0
- package/.output/public/assets/index-CQT4higx.js +1 -0
- package/.output/public/assets/{main-kBQ49n13.js → main-CzItFZlM.js} +2 -2
- package/.output/server/_libs/modelcontextprotocol__server.mjs +228 -0
- package/.output/server/{_sessionId-CYKaI68v.mjs → _sessionId-DZH8SrEZ.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-CHk4p41X.mjs → CompareDrawer-L0aE1UV1.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-Co9ENCKV.mjs → ProxyViewerContainer-B62RB9ER.mjs} +7 -7
- package/.output/server/_ssr/{ReplayDialog-BRs3fk8H.mjs → ReplayDialog-FVWnpx2C.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-Cb68EeZz.mjs → RequestAnatomy-DIyqW-Ny.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-DGdbfEjs.mjs → ResponseView-BX4mxEZ5.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-DptYgirK.mjs → StreamingChunkSequence-Cs3nzOor.mjs} +2 -2
- package/.output/server/_ssr/{index-Ef5mCuww.mjs → index-Dik2Mc3h.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-Cvv7SJnd.mjs → router-DCPg8ykx.mjs} +2087 -80
- package/.output/server/_tanstack-start-manifest_v-BaoL3JCh.mjs +4 -0
- package/.output/server/index.mjs +61 -61
- package/README.md +55 -1
- package/package.json +1 -1
- package/src/lib/runContract.ts +162 -0
- package/src/lib/sessionInfoContract.ts +69 -0
- package/src/mcp/server.ts +580 -2
- package/src/mcp/toolHandlers.ts +181 -0
- package/src/proxy/evidenceAnalysis.ts +522 -0
- package/src/proxy/evidenceExporter.ts +215 -0
- package/src/proxy/logSearch.ts +118 -0
- package/src/proxy/runFailures.ts +100 -0
- package/src/proxy/runStore.ts +159 -0
- package/src/proxy/sessionInfo.ts +222 -0
- package/src/proxy/sessionSupervisor.ts +8 -5
- package/src/proxy/store.ts +77 -0
- package/src/routes/api/logs.ts +16 -0
- package/src/routes/api/runs.$runId.evidence.ts +62 -0
- package/src/routes/api/runs.$runId.ts +50 -0
- package/src/routes/api/runs.ts +58 -0
- package/src/routes/api/sessions.ts +29 -2
- package/.output/public/assets/_sessionId-DoqvnFx7.js +0 -1
- package/.output/public/assets/index-B0YcFeL-.js +0 -1
- package/.output/server/_tanstack-start-manifest_v-DQoXxIFQ.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,
|
|
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,
|
|
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,
|
|
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$
|
|
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-
|
|
97
|
-
const Route$
|
|
96
|
+
const $$splitComponentImporter$1 = () => import("./index-Dik2Mc3h.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-
|
|
140
|
-
const Route$
|
|
139
|
+
const $$splitComponentImporter = () => import("../_sessionId-DZH8SrEZ.mjs");
|
|
140
|
+
const Route$t = createFileRoute("/session/$sessionId")({
|
|
141
141
|
component: lazyRouteComponent($$splitComponentImporter, "component"),
|
|
142
142
|
parseParams: (params) => ({
|
|
143
143
|
sessionId: decodeSessionIdFromPath(params.sessionId)
|
|
@@ -1030,6 +1030,184 @@ function getCurrentLogFile() {
|
|
|
1030
1030
|
const dd = String(now.getUTCDate()).padStart(2, "0");
|
|
1031
1031
|
return `${yyyy}-${mm}-${dd}.jsonl`;
|
|
1032
1032
|
}
|
|
1033
|
+
const SESSION_INFO_DEFAULT_LATEST_LOG_LIMIT = 10;
|
|
1034
|
+
const SESSION_INFO_MAX_LATEST_LOG_LIMIT = 50;
|
|
1035
|
+
function clampSessionInfoLogLimit(input) {
|
|
1036
|
+
if (input === void 0) return SESSION_INFO_DEFAULT_LATEST_LOG_LIMIT;
|
|
1037
|
+
if (!Number.isFinite(input) || input < 1) return 1;
|
|
1038
|
+
return Math.min(Math.floor(input), SESSION_INFO_MAX_LATEST_LOG_LIMIT);
|
|
1039
|
+
}
|
|
1040
|
+
function normalizeString(value) {
|
|
1041
|
+
if (value === null || value === void 0 || value.length === 0) return null;
|
|
1042
|
+
return value;
|
|
1043
|
+
}
|
|
1044
|
+
function truncateError(value) {
|
|
1045
|
+
const normalized = normalizeString(value);
|
|
1046
|
+
if (normalized === null) return null;
|
|
1047
|
+
if (normalized.length <= 500) return normalized;
|
|
1048
|
+
return `${normalized.slice(0, 500)}...`;
|
|
1049
|
+
}
|
|
1050
|
+
function hasLogError(log) {
|
|
1051
|
+
const error = normalizeString(log.error);
|
|
1052
|
+
if (error !== null) return true;
|
|
1053
|
+
return log.responseStatus !== null && log.responseStatus >= 400;
|
|
1054
|
+
}
|
|
1055
|
+
function buildSessionLogSummary(log) {
|
|
1056
|
+
return {
|
|
1057
|
+
id: log.id,
|
|
1058
|
+
timestamp: log.timestamp,
|
|
1059
|
+
provider: normalizeString(log.providerName),
|
|
1060
|
+
model: normalizeString(log.model),
|
|
1061
|
+
apiFormat: log.apiFormat,
|
|
1062
|
+
method: log.method,
|
|
1063
|
+
path: log.path,
|
|
1064
|
+
status: log.responseStatus,
|
|
1065
|
+
isStreaming: log.streaming,
|
|
1066
|
+
hasError: hasLogError(log),
|
|
1067
|
+
error: truncateError(log.error),
|
|
1068
|
+
latencyMs: log.elapsedMs,
|
|
1069
|
+
tokens: {
|
|
1070
|
+
input: log.inputTokens,
|
|
1071
|
+
output: log.outputTokens,
|
|
1072
|
+
cacheCreate: log.cacheCreationInputTokens,
|
|
1073
|
+
cacheRead: log.cacheReadInputTokens
|
|
1074
|
+
},
|
|
1075
|
+
sessionId: normalizeString(log.sessionId)
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
function nonNegativeInteger(value) {
|
|
1079
|
+
if (value === null || !Number.isFinite(value) || value < 0) return 0;
|
|
1080
|
+
return Math.floor(value);
|
|
1081
|
+
}
|
|
1082
|
+
function addTokenUsage(total, summary) {
|
|
1083
|
+
const input = nonNegativeInteger(summary.tokens.input);
|
|
1084
|
+
const output = nonNegativeInteger(summary.tokens.output);
|
|
1085
|
+
const cacheCreate = nonNegativeInteger(summary.tokens.cacheCreate);
|
|
1086
|
+
const cacheRead = nonNegativeInteger(summary.tokens.cacheRead);
|
|
1087
|
+
return {
|
|
1088
|
+
input: total.input + input,
|
|
1089
|
+
output: total.output + output,
|
|
1090
|
+
cacheCreate: total.cacheCreate + cacheCreate,
|
|
1091
|
+
cacheRead: total.cacheRead + cacheRead,
|
|
1092
|
+
total: total.total + input + output + cacheCreate + cacheRead
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function buildRelativePath(pathname, params) {
|
|
1096
|
+
const query = params.toString();
|
|
1097
|
+
return query.length > 0 ? `${pathname}?${query}` : pathname;
|
|
1098
|
+
}
|
|
1099
|
+
function buildAbsoluteUrl(baseUrl, path2) {
|
|
1100
|
+
try {
|
|
1101
|
+
return new URL(path2, baseUrl).toString();
|
|
1102
|
+
} catch {
|
|
1103
|
+
return path2;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
function buildApiPath(sessionId) {
|
|
1107
|
+
return buildRelativePath("/api/sessions", new URLSearchParams({ sessionId }));
|
|
1108
|
+
}
|
|
1109
|
+
function buildCompactLogsPath(sessionId) {
|
|
1110
|
+
return buildRelativePath("/api/logs", new URLSearchParams({ compact: "1", sessionId }));
|
|
1111
|
+
}
|
|
1112
|
+
function collectSortedValues(values) {
|
|
1113
|
+
const set = /* @__PURE__ */ new Set();
|
|
1114
|
+
for (const value of values) {
|
|
1115
|
+
if (value !== null && value.length > 0) {
|
|
1116
|
+
set.add(value);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return [...set].sort();
|
|
1120
|
+
}
|
|
1121
|
+
function countCompletedLogs(summaries) {
|
|
1122
|
+
let count = 0;
|
|
1123
|
+
for (const summary of summaries) {
|
|
1124
|
+
if (summary.status !== null || summary.latencyMs !== null) {
|
|
1125
|
+
count += 1;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return count;
|
|
1129
|
+
}
|
|
1130
|
+
function countErrorLogs(summaries) {
|
|
1131
|
+
let count = 0;
|
|
1132
|
+
for (const summary of summaries) {
|
|
1133
|
+
if (summary.hasError) {
|
|
1134
|
+
count += 1;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return count;
|
|
1138
|
+
}
|
|
1139
|
+
function deriveSessionStatus(input) {
|
|
1140
|
+
if (input.activeRequests > 0 || input.runningTasks > 0) return "active";
|
|
1141
|
+
if (input.failedRequests > 0 || input.errorCount > 0 || input.lastTaskError !== null) {
|
|
1142
|
+
return "failed";
|
|
1143
|
+
}
|
|
1144
|
+
if (input.completedRequests > 0 || input.logCount > 0) return "completed";
|
|
1145
|
+
return "empty";
|
|
1146
|
+
}
|
|
1147
|
+
function buildSessionInfo(input) {
|
|
1148
|
+
const latestLogLimit = clampSessionInfoLogLimit(input.latestLogLimit);
|
|
1149
|
+
const summaries = [...input.summaries].sort((left, right) => right.id - left.id);
|
|
1150
|
+
const latestLog = summaries[0] ?? null;
|
|
1151
|
+
const oldestLog = summaries.length > 0 ? summaries[summaries.length - 1] : null;
|
|
1152
|
+
const logCount = input.logCoverage === "history" ? summaries.length : Math.max(input.snapshot?.requestCount ?? 0, summaries.length);
|
|
1153
|
+
const errorCount = Math.max(countErrorLogs(summaries), input.snapshot?.failedRequests ?? 0);
|
|
1154
|
+
const completedLogCount = countCompletedLogs(summaries);
|
|
1155
|
+
const tokenUsage = summaries.reduce(
|
|
1156
|
+
(total, summary) => addTokenUsage(total, summary),
|
|
1157
|
+
{ input: 0, output: 0, cacheCreate: 0, cacheRead: 0, total: 0 }
|
|
1158
|
+
);
|
|
1159
|
+
const requestCount = Math.max(input.snapshot?.requestCount ?? 0, logCount);
|
|
1160
|
+
const activeRequests = input.snapshot?.activeRequests ?? 0;
|
|
1161
|
+
const completedRequests = Math.max(input.snapshot?.completedRequests ?? 0, completedLogCount);
|
|
1162
|
+
const failedRequests = Math.max(input.snapshot?.failedRequests ?? 0, errorCount);
|
|
1163
|
+
const queuedTasks = input.snapshot?.queuedTasks ?? 0;
|
|
1164
|
+
const runningTasks = input.snapshot?.runningTasks ?? 0;
|
|
1165
|
+
const lastTaskError = normalizeString(input.snapshot?.lastTaskError);
|
|
1166
|
+
const inspectorPath = getSessionPath(input.sessionId);
|
|
1167
|
+
const apiPath = buildApiPath(input.sessionId);
|
|
1168
|
+
const compactLogsPath = buildCompactLogsPath(input.sessionId);
|
|
1169
|
+
return {
|
|
1170
|
+
id: input.sessionId,
|
|
1171
|
+
status: deriveSessionStatus({
|
|
1172
|
+
activeRequests,
|
|
1173
|
+
runningTasks,
|
|
1174
|
+
failedRequests,
|
|
1175
|
+
errorCount,
|
|
1176
|
+
lastTaskError,
|
|
1177
|
+
completedRequests,
|
|
1178
|
+
logCount
|
|
1179
|
+
}),
|
|
1180
|
+
logCoverage: input.logCoverage,
|
|
1181
|
+
source: input.snapshot?.source ?? null,
|
|
1182
|
+
runtimeMode: input.snapshot?.runtimeMode ?? null,
|
|
1183
|
+
createdAt: input.snapshot?.createdAt ?? oldestLog?.timestamp ?? null,
|
|
1184
|
+
updatedAt: input.snapshot?.updatedAt ?? latestLog?.timestamp ?? null,
|
|
1185
|
+
requestCount,
|
|
1186
|
+
activeRequests,
|
|
1187
|
+
completedRequests,
|
|
1188
|
+
failedRequests,
|
|
1189
|
+
queuedTasks,
|
|
1190
|
+
runningTasks,
|
|
1191
|
+
lastTaskError,
|
|
1192
|
+
logCount,
|
|
1193
|
+
errorCount,
|
|
1194
|
+
models: collectSortedValues(summaries.map((summary) => summary.model)),
|
|
1195
|
+
providers: collectSortedValues(summaries.map((summary) => summary.provider)),
|
|
1196
|
+
tokenUsage,
|
|
1197
|
+
lastLogId: input.snapshot?.lastLogId ?? latestLog?.id ?? null,
|
|
1198
|
+
lastModel: normalizeString(input.snapshot?.lastModel) ?? latestLog?.model ?? null,
|
|
1199
|
+
clientPid: input.snapshot?.clientPid ?? null,
|
|
1200
|
+
clientProjectFolder: normalizeString(input.snapshot?.clientProjectFolder),
|
|
1201
|
+
inspectorPath,
|
|
1202
|
+
inspectorUrl: buildAbsoluteUrl(input.baseUrl, inspectorPath),
|
|
1203
|
+
apiPath,
|
|
1204
|
+
apiUrl: buildAbsoluteUrl(input.baseUrl, apiPath),
|
|
1205
|
+
compactLogsPath,
|
|
1206
|
+
compactLogsUrl: buildAbsoluteUrl(input.baseUrl, compactLogsPath),
|
|
1207
|
+
latestLogLimit,
|
|
1208
|
+
latestLogs: summaries.slice(0, latestLogLimit)
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1033
1211
|
const IDLE_TIMEOUT_MS = Number(process.env["SESSION_PROCESS_IDLE_MS"]) || 5 * 60 * 1e3;
|
|
1034
1212
|
const MAX_RESTARTS = 3;
|
|
1035
1213
|
const _processes = /* @__PURE__ */ new Map();
|
|
@@ -1222,11 +1400,14 @@ function upsertSession(log, sourceOverride) {
|
|
|
1222
1400
|
if (source === null) return null;
|
|
1223
1401
|
const existing = sessions.get(identity.id);
|
|
1224
1402
|
const session = existing ?? createSessionRecord(identity.id, source, log);
|
|
1225
|
-
session.
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1403
|
+
const isSameOrNewerLog = session.lastLogId === null || log.id >= session.lastLogId;
|
|
1404
|
+
if (isSameOrNewerLog) {
|
|
1405
|
+
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1406
|
+
session.lastLogId = log.id;
|
|
1407
|
+
session.lastModel = log.model ?? session.lastModel;
|
|
1408
|
+
session.clientPid = log.clientPid ?? session.clientPid;
|
|
1409
|
+
session.clientProjectFolder = log.clientProjectFolder ?? session.clientProjectFolder;
|
|
1410
|
+
}
|
|
1230
1411
|
sessions.set(identity.id, session);
|
|
1231
1412
|
return session;
|
|
1232
1413
|
}
|
|
@@ -1644,6 +1825,53 @@ async function listLogsPage(options) {
|
|
|
1644
1825
|
limit: options.limit
|
|
1645
1826
|
};
|
|
1646
1827
|
}
|
|
1828
|
+
function findSessionSnapshot(sessionId) {
|
|
1829
|
+
for (const snapshot of getSessionSnapshots()) {
|
|
1830
|
+
if (snapshot.id === sessionId) return snapshot;
|
|
1831
|
+
}
|
|
1832
|
+
return null;
|
|
1833
|
+
}
|
|
1834
|
+
async function collectSessionLogSummaries(sessionId, includeHistory) {
|
|
1835
|
+
const summariesById = /* @__PURE__ */ new Map();
|
|
1836
|
+
const observeMatchingLog = (log) => {
|
|
1837
|
+
if (!matchesLogFilters(log, sessionId, void 0)) return;
|
|
1838
|
+
summariesById.set(log.id, buildSessionLogSummary(log));
|
|
1839
|
+
};
|
|
1840
|
+
if (includeHistory) {
|
|
1841
|
+
await visitPersistedLogs((log) => {
|
|
1842
|
+
observeSessionLog(log);
|
|
1843
|
+
observeMatchingLog(log);
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
for (const log of memoryCache.values()) {
|
|
1847
|
+
observeMatchingLog(log);
|
|
1848
|
+
}
|
|
1849
|
+
return [...summariesById.values()];
|
|
1850
|
+
}
|
|
1851
|
+
async function includeSnapshotLastLogSummary(sessionId, snapshot, summaries) {
|
|
1852
|
+
if (snapshot === null || snapshot.lastLogId === null) return summaries;
|
|
1853
|
+
for (const summary of summaries) {
|
|
1854
|
+
if (summary.id === snapshot.lastLogId) return summaries;
|
|
1855
|
+
}
|
|
1856
|
+
const log = await getLogById(snapshot.lastLogId);
|
|
1857
|
+
if (log === null || !matchesLogFilters(log, sessionId, void 0)) return summaries;
|
|
1858
|
+
return [...summaries, buildSessionLogSummary(log)];
|
|
1859
|
+
}
|
|
1860
|
+
async function getSessionInfo(sessionId, options) {
|
|
1861
|
+
const includeHistory = options.includeHistory === true;
|
|
1862
|
+
const collectedSummaries = await collectSessionLogSummaries(sessionId, includeHistory);
|
|
1863
|
+
const snapshot = findSessionSnapshot(sessionId);
|
|
1864
|
+
const summaries = includeHistory ? collectedSummaries : await includeSnapshotLastLogSummary(sessionId, snapshot, collectedSummaries);
|
|
1865
|
+
if (snapshot === null && summaries.length === 0) return null;
|
|
1866
|
+
return buildSessionInfo({
|
|
1867
|
+
sessionId,
|
|
1868
|
+
baseUrl: options.baseUrl,
|
|
1869
|
+
snapshot,
|
|
1870
|
+
summaries,
|
|
1871
|
+
logCoverage: includeHistory ? "history" : "recent",
|
|
1872
|
+
latestLogLimit: options.latestLogLimit
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1647
1875
|
function getSessions() {
|
|
1648
1876
|
return getSessionIds();
|
|
1649
1877
|
}
|
|
@@ -4644,7 +4872,7 @@ async function handleProxy(req) {
|
|
|
4644
4872
|
}
|
|
4645
4873
|
return handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log, captureFullDetails);
|
|
4646
4874
|
}
|
|
4647
|
-
const Route$
|
|
4875
|
+
const Route$s = createFileRoute("/proxy/$")({
|
|
4648
4876
|
server: {
|
|
4649
4877
|
handlers: {
|
|
4650
4878
|
GET: ({ request }) => handleProxy(request),
|
|
@@ -4656,11 +4884,35 @@ const Route$p = createFileRoute("/proxy/$")({
|
|
|
4656
4884
|
}
|
|
4657
4885
|
}
|
|
4658
4886
|
});
|
|
4659
|
-
|
|
4887
|
+
function parsePositiveInt$2(value) {
|
|
4888
|
+
if (value === null) return void 0;
|
|
4889
|
+
const parsed = Number(value);
|
|
4890
|
+
if (!Number.isInteger(parsed) || parsed < 1) return void 0;
|
|
4891
|
+
return parsed;
|
|
4892
|
+
}
|
|
4893
|
+
function parseBooleanFlag(value) {
|
|
4894
|
+
return value === "1" || value === "true";
|
|
4895
|
+
}
|
|
4896
|
+
const Route$r = createFileRoute("/api/sessions")({
|
|
4660
4897
|
server: {
|
|
4661
4898
|
handlers: {
|
|
4662
|
-
GET: ({ request }) => {
|
|
4899
|
+
GET: async ({ request }) => {
|
|
4663
4900
|
const url = new URL(request.url);
|
|
4901
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
4902
|
+
if (sessionId !== null) {
|
|
4903
|
+
if (sessionId.length === 0) {
|
|
4904
|
+
return Response.json({ error: "sessionId is required" }, { status: 400 });
|
|
4905
|
+
}
|
|
4906
|
+
const info = await getSessionInfo(sessionId, {
|
|
4907
|
+
baseUrl: url.origin,
|
|
4908
|
+
includeHistory: parseBooleanFlag(url.searchParams.get("includeHistory")),
|
|
4909
|
+
latestLogLimit: parsePositiveInt$2(url.searchParams.get("limit"))
|
|
4910
|
+
});
|
|
4911
|
+
if (info === null) {
|
|
4912
|
+
return Response.json({ error: "Session not found" }, { status: 404 });
|
|
4913
|
+
}
|
|
4914
|
+
return Response.json(info);
|
|
4915
|
+
}
|
|
4664
4916
|
const details = url.searchParams.get("details");
|
|
4665
4917
|
if (details === "true" || details === "1") {
|
|
4666
4918
|
return Response.json(getSessionSnapshots());
|
|
@@ -4670,6 +4922,415 @@ const Route$o = createFileRoute("/api/sessions")({
|
|
|
4670
4922
|
}
|
|
4671
4923
|
}
|
|
4672
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
|
+
});
|
|
4673
5334
|
const ProviderInputSchema = object({
|
|
4674
5335
|
name: string().min(1, "Name is required"),
|
|
4675
5336
|
apiKey: string().min(1, "API key is required"),
|
|
@@ -4684,7 +5345,7 @@ const ProviderInputSchema = object({
|
|
|
4684
5345
|
modelMetadata: array(ProviderModelMetadataSchema).optional(),
|
|
4685
5346
|
source: _enum(["company", "personal"]).optional()
|
|
4686
5347
|
});
|
|
4687
|
-
const Route$
|
|
5348
|
+
const Route$p = createFileRoute("/api/providers")({
|
|
4688
5349
|
server: {
|
|
4689
5350
|
handlers: {
|
|
4690
5351
|
GET: ({ request }) => {
|
|
@@ -4718,7 +5379,7 @@ const Route$n = createFileRoute("/api/providers")({
|
|
|
4718
5379
|
}
|
|
4719
5380
|
}
|
|
4720
5381
|
});
|
|
4721
|
-
const Route$
|
|
5382
|
+
const Route$o = createFileRoute("/api/models")({
|
|
4722
5383
|
server: {
|
|
4723
5384
|
handlers: {
|
|
4724
5385
|
GET: () => Response.json(getModels())
|
|
@@ -4881,6 +5542,14 @@ const LogsListResponseSchema = object({
|
|
|
4881
5542
|
offset: number().optional(),
|
|
4882
5543
|
limit: number().optional()
|
|
4883
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
|
+
});
|
|
4884
5553
|
const PAGINATION_DEFAULTS = { offset: 0, limit: 3 };
|
|
4885
5554
|
const LIMIT_HARD_CAP = 5;
|
|
4886
5555
|
const LOG_DETAIL_TEXT_LIMIT = 2e5;
|
|
@@ -4985,6 +5654,33 @@ async function listLogsImpl(callApi2, args) {
|
|
|
4985
5654
|
const logs = parsed.data.logs ?? [];
|
|
4986
5655
|
return textJson(logs.map(buildLogSummary));
|
|
4987
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
|
+
}
|
|
4988
5684
|
async function getLogImpl(callApi2, id) {
|
|
4989
5685
|
const res = await callApi2(`/api/logs/${id}`);
|
|
4990
5686
|
if (!res.ok) return toolError(`GET /api/logs/${id} returned ${res.status}`);
|
|
@@ -5005,6 +5701,87 @@ async function listSessionsImpl(callApi2) {
|
|
|
5005
5701
|
if (!res.ok) return toolError(`GET /api/sessions returned ${res.status}`);
|
|
5006
5702
|
return textJson(await res.json());
|
|
5007
5703
|
}
|
|
5704
|
+
async function getSessionImpl(callApi2, args) {
|
|
5705
|
+
const params = new URLSearchParams({ sessionId: args.sessionId });
|
|
5706
|
+
if (args.latestLogLimit !== void 0) {
|
|
5707
|
+
params.set("limit", String(args.latestLogLimit));
|
|
5708
|
+
}
|
|
5709
|
+
if (args.includeHistory === true) {
|
|
5710
|
+
params.set("includeHistory", "1");
|
|
5711
|
+
}
|
|
5712
|
+
const path2 = `/api/sessions?${params.toString()}`;
|
|
5713
|
+
const res = await callApi2(path2);
|
|
5714
|
+
if (!res.ok) return toolError(`GET ${path2} returned ${res.status}`);
|
|
5715
|
+
const rawBody = await res.json();
|
|
5716
|
+
const parsed = SessionInfoSchema.safeParse(rawBody);
|
|
5717
|
+
if (!parsed.success) return toolError("GET /api/sessions returned an unparseable session info");
|
|
5718
|
+
return textJson(parsed.data);
|
|
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
|
+
}
|
|
5008
5785
|
async function listModelsImpl(callApi2) {
|
|
5009
5786
|
const res = await callApi2("/api/models");
|
|
5010
5787
|
if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
|
|
@@ -5135,9 +5912,11 @@ let initPromise = null;
|
|
|
5135
5912
|
function buildServer() {
|
|
5136
5913
|
const server = new McpServer(
|
|
5137
5914
|
{ name: "agent-inspector", version: "1.0.0" },
|
|
5138
|
-
{ capabilities: { tools: {} } }
|
|
5915
|
+
{ capabilities: { tools: {}, resources: {}, prompts: {} } }
|
|
5139
5916
|
);
|
|
5140
5917
|
registerTools(server);
|
|
5918
|
+
registerResources(server);
|
|
5919
|
+
registerPrompts(server);
|
|
5141
5920
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
5142
5921
|
sessionIdGenerator: void 0
|
|
5143
5922
|
// stateless — see module docstring
|
|
@@ -5185,6 +5964,129 @@ Returns: array of "thick summary" objects, each containing:
|
|
|
5185
5964
|
|
|
5186
5965
|
Either preview field is null when the body is unknown format, unparseable, or the relevant content is non-text (e.g., image-only).`;
|
|
5187
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
|
+
}
|
|
5188
6090
|
function registerTools(server) {
|
|
5189
6091
|
server.registerTool(
|
|
5190
6092
|
"inspector_list_logs",
|
|
@@ -5201,12 +6103,28 @@ function registerTools(server) {
|
|
|
5201
6103
|
(args) => safeCall(() => listLogsImpl(callApi, args))
|
|
5202
6104
|
);
|
|
5203
6105
|
server.registerTool(
|
|
5204
|
-
"
|
|
6106
|
+
"inspector_search_logs",
|
|
5205
6107
|
{
|
|
5206
|
-
title: "
|
|
5207
|
-
description: "
|
|
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.",
|
|
5208
6110
|
inputSchema: object({
|
|
5209
|
-
|
|
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
|
+
);
|
|
6121
|
+
server.registerTool(
|
|
6122
|
+
"inspector_get_log",
|
|
6123
|
+
{
|
|
6124
|
+
title: "Get a single captured log by id",
|
|
6125
|
+
description: "Returns the full CapturedLog for the given id, including rawRequestBody and responseText with no truncation. SSE streaming chunks are NOT included — use inspector_get_log_chunks for those. Returns an error if the id does not exist.",
|
|
6126
|
+
inputSchema: object({
|
|
6127
|
+
id: number().int().positive().describe("The log id.")
|
|
5210
6128
|
})
|
|
5211
6129
|
},
|
|
5212
6130
|
({ id }) => safeCall(() => getLogImpl(callApi, id))
|
|
@@ -5231,6 +6149,84 @@ function registerTools(server) {
|
|
|
5231
6149
|
},
|
|
5232
6150
|
() => safeCall(() => listSessionsImpl(callApi))
|
|
5233
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",
|
|
6197
|
+
{
|
|
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.")
|
|
6202
|
+
})
|
|
6203
|
+
},
|
|
6204
|
+
(args) => safeCall(() => updateRunImpl(callApi, args))
|
|
6205
|
+
);
|
|
6206
|
+
server.registerTool(
|
|
6207
|
+
"inspector_export_evidence",
|
|
6208
|
+
{
|
|
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
|
+
})
|
|
6216
|
+
},
|
|
6217
|
+
(args) => safeCall(() => exportEvidenceImpl(callApi, args))
|
|
6218
|
+
);
|
|
6219
|
+
server.registerTool(
|
|
6220
|
+
"inspector_get_recent_failures",
|
|
6221
|
+
{
|
|
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.",
|
|
6224
|
+
inputSchema: object({
|
|
6225
|
+
limit: number().int().positive().max(20).optional().describe("Maximum failures to return. Default 5, max 20.")
|
|
6226
|
+
})
|
|
6227
|
+
},
|
|
6228
|
+
(args) => safeCall(() => getRecentFailuresImpl(callApi, args))
|
|
6229
|
+
);
|
|
5234
6230
|
server.registerTool(
|
|
5235
6231
|
"inspector_list_models",
|
|
5236
6232
|
{
|
|
@@ -5382,7 +6378,211 @@ PATCH-style update: only the fields you supply are changed. Returns the updated
|
|
|
5382
6378
|
(args) => safeCall(() => getProjectContextImpl(callApi, args))
|
|
5383
6379
|
);
|
|
5384
6380
|
}
|
|
5385
|
-
|
|
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")({
|
|
5386
6586
|
server: {
|
|
5387
6587
|
handlers: {
|
|
5388
6588
|
// The SDK may issue either POST, GET, or DELETE. TanStack Start only
|
|
@@ -5394,6 +6594,89 @@ const Route$l = createFileRoute("/api/mcp")({
|
|
|
5394
6594
|
}
|
|
5395
6595
|
}
|
|
5396
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
|
+
}
|
|
5397
6680
|
const DeleteBodySchema = object({
|
|
5398
6681
|
ids: array(number().int().positive()).optional()
|
|
5399
6682
|
}).optional();
|
|
@@ -5409,7 +6692,7 @@ function parsePositiveInt(value, fallback) {
|
|
|
5409
6692
|
if (!Number.isInteger(parsed) || parsed < 1) return fallback;
|
|
5410
6693
|
return parsed;
|
|
5411
6694
|
}
|
|
5412
|
-
const Route$
|
|
6695
|
+
const Route$m = createFileRoute("/api/logs")({
|
|
5413
6696
|
server: {
|
|
5414
6697
|
handlers: {
|
|
5415
6698
|
GET: async ({ request }) => {
|
|
@@ -5428,7 +6711,18 @@ const Route$k = createFileRoute("/api/logs")({
|
|
|
5428
6711
|
}
|
|
5429
6712
|
const sessionId = url.searchParams.get("sessionId") ?? void 0;
|
|
5430
6713
|
const model = url.searchParams.get("model") ?? void 0;
|
|
6714
|
+
const query = url.searchParams.get("q") ?? url.searchParams.get("search");
|
|
5431
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
|
+
}
|
|
5432
6726
|
const limit = parsePositiveInt(url.searchParams.get("limit"), 50);
|
|
5433
6727
|
const includeBodies = url.searchParams.get("compact") !== "1";
|
|
5434
6728
|
return Response.json(
|
|
@@ -5474,7 +6768,7 @@ const Route$k = createFileRoute("/api/logs")({
|
|
|
5474
6768
|
}
|
|
5475
6769
|
});
|
|
5476
6770
|
logger.debug("Health endpoint loaded");
|
|
5477
|
-
const Route$
|
|
6771
|
+
const Route$l = createFileRoute("/api/health")({
|
|
5478
6772
|
server: {
|
|
5479
6773
|
handlers: {
|
|
5480
6774
|
GET: () => {
|
|
@@ -5492,7 +6786,7 @@ const RuntimeConfigPatchSchema = object({
|
|
|
5492
6786
|
}).strict().refine((v) => Object.keys(v).length > 0, {
|
|
5493
6787
|
message: "At least one field must be provided"
|
|
5494
6788
|
});
|
|
5495
|
-
const Route$
|
|
6789
|
+
const Route$k = createFileRoute("/api/config")({
|
|
5496
6790
|
server: {
|
|
5497
6791
|
handlers: {
|
|
5498
6792
|
GET: () => {
|
|
@@ -5521,6 +6815,46 @@ const Route$i = createFileRoute("/api/config")({
|
|
|
5521
6815
|
}
|
|
5522
6816
|
}
|
|
5523
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
|
+
});
|
|
5524
6858
|
const ZHIPU_OPENAI_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
|
|
5525
6859
|
const ZHIPU_CODING_OPENAI_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
|
|
5526
6860
|
const ZHIPU_CODING_ANTHROPIC_BASE_URL = "https://open.bigmodel.cn/api/anthropic";
|
|
@@ -5947,7 +7281,7 @@ function scanExternalProviders() {
|
|
|
5947
7281
|
}
|
|
5948
7282
|
return { providers: filteredProviders, warnings };
|
|
5949
7283
|
}
|
|
5950
|
-
const Route$
|
|
7284
|
+
const Route$i = createFileRoute("/api/providers/scan")({
|
|
5951
7285
|
server: {
|
|
5952
7286
|
handlers: {
|
|
5953
7287
|
GET: () => {
|
|
@@ -5968,7 +7302,7 @@ const Route$h = createFileRoute("/api/providers/scan")({
|
|
|
5968
7302
|
}
|
|
5969
7303
|
});
|
|
5970
7304
|
union([string(), object({ providers: unknown() })]);
|
|
5971
|
-
const Route$
|
|
7305
|
+
const Route$h = createFileRoute("/api/providers/import")({
|
|
5972
7306
|
server: {
|
|
5973
7307
|
handlers: {
|
|
5974
7308
|
POST: async ({ request }) => {
|
|
@@ -6001,7 +7335,7 @@ const Route$g = createFileRoute("/api/providers/import")({
|
|
|
6001
7335
|
}
|
|
6002
7336
|
}
|
|
6003
7337
|
});
|
|
6004
|
-
const Route$
|
|
7338
|
+
const Route$g = createFileRoute("/api/providers/export")({
|
|
6005
7339
|
server: {
|
|
6006
7340
|
handlers: {
|
|
6007
7341
|
GET: ({ request }) => {
|
|
@@ -6034,7 +7368,7 @@ const ProviderUpdateSchema = object({
|
|
|
6034
7368
|
modelMetadata: array(ProviderModelMetadataSchema).optional(),
|
|
6035
7369
|
source: _enum(["company", "personal"]).optional()
|
|
6036
7370
|
});
|
|
6037
|
-
const Route$
|
|
7371
|
+
const Route$f = createFileRoute("/api/providers/$providerId")({
|
|
6038
7372
|
server: {
|
|
6039
7373
|
handlers: {
|
|
6040
7374
|
GET: ({ params, request }) => {
|
|
@@ -6069,7 +7403,7 @@ const Route$e = createFileRoute("/api/providers/$providerId")({
|
|
|
6069
7403
|
}
|
|
6070
7404
|
});
|
|
6071
7405
|
const INITIAL_STREAM_LOG_LIMIT = 100;
|
|
6072
|
-
const Route$
|
|
7406
|
+
const Route$e = createFileRoute("/api/logs/stream")({
|
|
6073
7407
|
server: {
|
|
6074
7408
|
handlers: {
|
|
6075
7409
|
GET: ({ request }) => {
|
|
@@ -6142,7 +7476,7 @@ const Route$d = createFileRoute("/api/logs/stream")({
|
|
|
6142
7476
|
}
|
|
6143
7477
|
}
|
|
6144
7478
|
});
|
|
6145
|
-
const Route$
|
|
7479
|
+
const Route$d = createFileRoute("/api/logs/$id")({
|
|
6146
7480
|
server: {
|
|
6147
7481
|
handlers: {
|
|
6148
7482
|
GET: async ({ params }) => {
|
|
@@ -6484,10 +7818,10 @@ function normalizePathSegment(value, fallback) {
|
|
|
6484
7818
|
const segment = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96);
|
|
6485
7819
|
return segment === "" ? fallback : segment;
|
|
6486
7820
|
}
|
|
6487
|
-
function formatNullable(value) {
|
|
7821
|
+
function formatNullable$1(value) {
|
|
6488
7822
|
return value === null || value === "" ? "unknown" : value;
|
|
6489
7823
|
}
|
|
6490
|
-
function formatList(values) {
|
|
7824
|
+
function formatList$2(values) {
|
|
6491
7825
|
return values.length === 0 ? "none" : values.join(", ");
|
|
6492
7826
|
}
|
|
6493
7827
|
function buildEvidenceLink(candidate) {
|
|
@@ -6510,16 +7844,16 @@ function buildOpenClawMemoryMarkdown(candidate) {
|
|
|
6510
7844
|
"",
|
|
6511
7845
|
`- Type: ${sanitized.type}`,
|
|
6512
7846
|
`- Status: ${sanitized.status}`,
|
|
6513
|
-
`- Project: ${formatNullable(sanitized.evidence.project)}`,
|
|
7847
|
+
`- Project: ${formatNullable$1(sanitized.evidence.project)}`,
|
|
6514
7848
|
`- Session: ${sanitized.sessionId}`,
|
|
6515
7849
|
`- Log IDs: ${sanitized.logIds.join(", ")}`,
|
|
6516
|
-
`- Models: ${formatList(sanitized.evidence.models)}`,
|
|
6517
|
-
`- Tags: ${formatList(sanitized.tags)}`,
|
|
7850
|
+
`- Models: ${formatList$2(sanitized.evidence.models)}`,
|
|
7851
|
+
`- Tags: ${formatList$2(sanitized.tags)}`,
|
|
6518
7852
|
`- Created: ${sanitized.createdAt}`,
|
|
6519
7853
|
`- Updated: ${sanitized.updatedAt}`,
|
|
6520
7854
|
`- Evidence: ${buildEvidenceLink(sanitized)}`,
|
|
6521
7855
|
`- Redacted: ${String(sanitized.redaction.redacted)}`,
|
|
6522
|
-
`- Redaction patterns: ${formatList(sanitized.redaction.patterns)}`,
|
|
7856
|
+
`- Redaction patterns: ${formatList$2(sanitized.redaction.patterns)}`,
|
|
6523
7857
|
"",
|
|
6524
7858
|
"## Durable Knowledge Candidate",
|
|
6525
7859
|
"",
|
|
@@ -6679,7 +8013,7 @@ const SearchBodySchema = object({
|
|
|
6679
8013
|
query: string().min(1),
|
|
6680
8014
|
project: string().optional()
|
|
6681
8015
|
});
|
|
6682
|
-
const Route$
|
|
8016
|
+
const Route$c = createFileRoute("/api/knowledge/search")({
|
|
6683
8017
|
server: {
|
|
6684
8018
|
handlers: {
|
|
6685
8019
|
GET: async ({ request }) => {
|
|
@@ -6701,7 +8035,7 @@ const Route$b = createFileRoute("/api/knowledge/search")({
|
|
|
6701
8035
|
}
|
|
6702
8036
|
}
|
|
6703
8037
|
});
|
|
6704
|
-
const Route$
|
|
8038
|
+
const Route$b = createFileRoute("/api/knowledge/project-context")({
|
|
6705
8039
|
server: {
|
|
6706
8040
|
handlers: {
|
|
6707
8041
|
GET: async ({ request }) => {
|
|
@@ -6783,14 +8117,14 @@ function updateCandidateDraft(id, updates) {
|
|
|
6783
8117
|
candidates.set(id, updated);
|
|
6784
8118
|
return updated;
|
|
6785
8119
|
}
|
|
6786
|
-
const Route$
|
|
8120
|
+
const Route$a = createFileRoute("/api/knowledge/candidates")({
|
|
6787
8121
|
server: {
|
|
6788
8122
|
handlers: {
|
|
6789
8123
|
GET: () => Response.json({ candidates: listCandidates() })
|
|
6790
8124
|
}
|
|
6791
8125
|
}
|
|
6792
8126
|
});
|
|
6793
|
-
const Route$
|
|
8127
|
+
const Route$9 = createFileRoute("/api/config/paths")({
|
|
6794
8128
|
server: {
|
|
6795
8129
|
handlers: {
|
|
6796
8130
|
GET: () => {
|
|
@@ -6801,6 +8135,653 @@ const Route$8 = createFileRoute("/api/config/paths")({
|
|
|
6801
8135
|
}
|
|
6802
8136
|
}
|
|
6803
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
+
});
|
|
6804
8785
|
const MEMORY_PROBE_FACTS = [
|
|
6805
8786
|
"Project fact: Agent Inspector captures LLM traffic and turns it into Raw Trace, Session Episode, and Memory Candidate layers.",
|
|
6806
8787
|
"Procedure: before release, run bun test, npm run typecheck, npm run lint, npm run build, and openspec validate --all --strict.",
|
|
@@ -8220,106 +10201,121 @@ const Route = createFileRoute("/api/knowledge/candidates/$candidateId/promote")(
|
|
|
8220
10201
|
}
|
|
8221
10202
|
}
|
|
8222
10203
|
});
|
|
8223
|
-
const IndexRoute = Route$
|
|
10204
|
+
const IndexRoute = Route$u.update({
|
|
8224
10205
|
id: "/",
|
|
8225
10206
|
path: "/",
|
|
8226
|
-
getParentRoute: () => Route$
|
|
10207
|
+
getParentRoute: () => Route$v
|
|
8227
10208
|
});
|
|
8228
|
-
const SessionSessionIdRoute = Route$
|
|
10209
|
+
const SessionSessionIdRoute = Route$t.update({
|
|
8229
10210
|
id: "/session/$sessionId",
|
|
8230
10211
|
path: "/session/$sessionId",
|
|
8231
|
-
getParentRoute: () => Route$
|
|
10212
|
+
getParentRoute: () => Route$v
|
|
8232
10213
|
});
|
|
8233
|
-
const ProxySplatRoute = Route$
|
|
10214
|
+
const ProxySplatRoute = Route$s.update({
|
|
8234
10215
|
id: "/proxy/$",
|
|
8235
10216
|
path: "/proxy/$",
|
|
8236
|
-
getParentRoute: () => Route$
|
|
10217
|
+
getParentRoute: () => Route$v
|
|
8237
10218
|
});
|
|
8238
|
-
const ApiSessionsRoute = Route$
|
|
10219
|
+
const ApiSessionsRoute = Route$r.update({
|
|
8239
10220
|
id: "/api/sessions",
|
|
8240
10221
|
path: "/api/sessions",
|
|
8241
|
-
getParentRoute: () => Route$
|
|
10222
|
+
getParentRoute: () => Route$v
|
|
8242
10223
|
});
|
|
8243
|
-
const
|
|
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({
|
|
8244
10230
|
id: "/api/providers",
|
|
8245
10231
|
path: "/api/providers",
|
|
8246
|
-
getParentRoute: () => Route$
|
|
10232
|
+
getParentRoute: () => Route$v
|
|
8247
10233
|
});
|
|
8248
|
-
const ApiModelsRoute = Route$
|
|
10234
|
+
const ApiModelsRoute = Route$o.update({
|
|
8249
10235
|
id: "/api/models",
|
|
8250
10236
|
path: "/api/models",
|
|
8251
|
-
getParentRoute: () => Route$
|
|
10237
|
+
getParentRoute: () => Route$v
|
|
8252
10238
|
});
|
|
8253
|
-
const ApiMcpRoute = Route$
|
|
10239
|
+
const ApiMcpRoute = Route$n.update({
|
|
8254
10240
|
id: "/api/mcp",
|
|
8255
10241
|
path: "/api/mcp",
|
|
8256
|
-
getParentRoute: () => Route$
|
|
10242
|
+
getParentRoute: () => Route$v
|
|
8257
10243
|
});
|
|
8258
|
-
const ApiLogsRoute = Route$
|
|
10244
|
+
const ApiLogsRoute = Route$m.update({
|
|
8259
10245
|
id: "/api/logs",
|
|
8260
10246
|
path: "/api/logs",
|
|
8261
|
-
getParentRoute: () => Route$
|
|
10247
|
+
getParentRoute: () => Route$v
|
|
8262
10248
|
});
|
|
8263
|
-
const ApiHealthRoute = Route$
|
|
10249
|
+
const ApiHealthRoute = Route$l.update({
|
|
8264
10250
|
id: "/api/health",
|
|
8265
10251
|
path: "/api/health",
|
|
8266
|
-
getParentRoute: () => Route$
|
|
10252
|
+
getParentRoute: () => Route$v
|
|
8267
10253
|
});
|
|
8268
|
-
const ApiConfigRoute = Route$
|
|
10254
|
+
const ApiConfigRoute = Route$k.update({
|
|
8269
10255
|
id: "/api/config",
|
|
8270
10256
|
path: "/api/config",
|
|
8271
|
-
getParentRoute: () => Route$
|
|
10257
|
+
getParentRoute: () => Route$v
|
|
10258
|
+
});
|
|
10259
|
+
const ApiRunsRunIdRoute = Route$j.update({
|
|
10260
|
+
id: "/$runId",
|
|
10261
|
+
path: "/$runId",
|
|
10262
|
+
getParentRoute: () => ApiRunsRoute
|
|
8272
10263
|
});
|
|
8273
|
-
const ApiProvidersScanRoute = Route$
|
|
10264
|
+
const ApiProvidersScanRoute = Route$i.update({
|
|
8274
10265
|
id: "/scan",
|
|
8275
10266
|
path: "/scan",
|
|
8276
10267
|
getParentRoute: () => ApiProvidersRoute
|
|
8277
10268
|
});
|
|
8278
|
-
const ApiProvidersImportRoute = Route$
|
|
10269
|
+
const ApiProvidersImportRoute = Route$h.update({
|
|
8279
10270
|
id: "/import",
|
|
8280
10271
|
path: "/import",
|
|
8281
10272
|
getParentRoute: () => ApiProvidersRoute
|
|
8282
10273
|
});
|
|
8283
|
-
const ApiProvidersExportRoute = Route$
|
|
10274
|
+
const ApiProvidersExportRoute = Route$g.update({
|
|
8284
10275
|
id: "/export",
|
|
8285
10276
|
path: "/export",
|
|
8286
10277
|
getParentRoute: () => ApiProvidersRoute
|
|
8287
10278
|
});
|
|
8288
|
-
const ApiProvidersProviderIdRoute = Route$
|
|
10279
|
+
const ApiProvidersProviderIdRoute = Route$f.update({
|
|
8289
10280
|
id: "/$providerId",
|
|
8290
10281
|
path: "/$providerId",
|
|
8291
10282
|
getParentRoute: () => ApiProvidersRoute
|
|
8292
10283
|
});
|
|
8293
|
-
const ApiLogsStreamRoute = Route$
|
|
10284
|
+
const ApiLogsStreamRoute = Route$e.update({
|
|
8294
10285
|
id: "/stream",
|
|
8295
10286
|
path: "/stream",
|
|
8296
10287
|
getParentRoute: () => ApiLogsRoute
|
|
8297
10288
|
});
|
|
8298
|
-
const ApiLogsIdRoute = Route$
|
|
10289
|
+
const ApiLogsIdRoute = Route$d.update({
|
|
8299
10290
|
id: "/$id",
|
|
8300
10291
|
path: "/$id",
|
|
8301
10292
|
getParentRoute: () => ApiLogsRoute
|
|
8302
10293
|
});
|
|
8303
|
-
const ApiKnowledgeSearchRoute = Route$
|
|
10294
|
+
const ApiKnowledgeSearchRoute = Route$c.update({
|
|
8304
10295
|
id: "/api/knowledge/search",
|
|
8305
10296
|
path: "/api/knowledge/search",
|
|
8306
|
-
getParentRoute: () => Route$
|
|
10297
|
+
getParentRoute: () => Route$v
|
|
8307
10298
|
});
|
|
8308
|
-
const ApiKnowledgeProjectContextRoute = Route$
|
|
10299
|
+
const ApiKnowledgeProjectContextRoute = Route$b.update({
|
|
8309
10300
|
id: "/api/knowledge/project-context",
|
|
8310
10301
|
path: "/api/knowledge/project-context",
|
|
8311
|
-
getParentRoute: () => Route$
|
|
10302
|
+
getParentRoute: () => Route$v
|
|
8312
10303
|
});
|
|
8313
|
-
const ApiKnowledgeCandidatesRoute = Route$
|
|
10304
|
+
const ApiKnowledgeCandidatesRoute = Route$a.update({
|
|
8314
10305
|
id: "/api/knowledge/candidates",
|
|
8315
10306
|
path: "/api/knowledge/candidates",
|
|
8316
|
-
getParentRoute: () => Route$
|
|
10307
|
+
getParentRoute: () => Route$v
|
|
8317
10308
|
});
|
|
8318
|
-
const ApiConfigPathsRoute = Route$
|
|
10309
|
+
const ApiConfigPathsRoute = Route$9.update({
|
|
8319
10310
|
id: "/paths",
|
|
8320
10311
|
path: "/paths",
|
|
8321
10312
|
getParentRoute: () => ApiConfigRoute
|
|
8322
10313
|
});
|
|
10314
|
+
const ApiRunsRunIdEvidenceRoute = Route$8.update({
|
|
10315
|
+
id: "/evidence",
|
|
10316
|
+
path: "/evidence",
|
|
10317
|
+
getParentRoute: () => ApiRunsRunIdRoute
|
|
10318
|
+
});
|
|
8323
10319
|
const ApiProvidersProviderIdTestRoute = Route$7.update({
|
|
8324
10320
|
id: "/test",
|
|
8325
10321
|
path: "/test",
|
|
@@ -8353,7 +10349,7 @@ const ApiProvidersProviderIdTestLogRoute = Route$2.update({
|
|
|
8353
10349
|
const ApiKnowledgeSessionsSessionIdCandidatesRoute = Route$1.update({
|
|
8354
10350
|
id: "/api/knowledge/sessions/$sessionId/candidates",
|
|
8355
10351
|
path: "/api/knowledge/sessions/$sessionId/candidates",
|
|
8356
|
-
getParentRoute: () => Route$
|
|
10352
|
+
getParentRoute: () => Route$v
|
|
8357
10353
|
});
|
|
8358
10354
|
const ApiKnowledgeCandidatesCandidateIdPromoteRoute = Route.update({
|
|
8359
10355
|
id: "/promote",
|
|
@@ -8400,6 +10396,16 @@ const ApiProvidersRouteChildren = {
|
|
|
8400
10396
|
const ApiProvidersRouteWithChildren = ApiProvidersRoute._addFileChildren(
|
|
8401
10397
|
ApiProvidersRouteChildren
|
|
8402
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);
|
|
8403
10409
|
const ApiKnowledgeCandidatesCandidateIdRouteChildren = {
|
|
8404
10410
|
ApiKnowledgeCandidatesCandidateIdPromoteRoute
|
|
8405
10411
|
};
|
|
@@ -8420,6 +10426,7 @@ const rootRouteChildren = {
|
|
|
8420
10426
|
ApiMcpRoute,
|
|
8421
10427
|
ApiModelsRoute,
|
|
8422
10428
|
ApiProvidersRoute: ApiProvidersRouteWithChildren,
|
|
10429
|
+
ApiRunsRoute: ApiRunsRouteWithChildren,
|
|
8423
10430
|
ApiSessionsRoute,
|
|
8424
10431
|
ProxySplatRoute,
|
|
8425
10432
|
SessionSessionIdRoute,
|
|
@@ -8428,7 +10435,7 @@ const rootRouteChildren = {
|
|
|
8428
10435
|
ApiKnowledgeSearchRoute,
|
|
8429
10436
|
ApiKnowledgeSessionsSessionIdCandidatesRoute
|
|
8430
10437
|
};
|
|
8431
|
-
const routeTree = Route$
|
|
10438
|
+
const routeTree = Route$v._addFileChildren(rootRouteChildren)._addFileTypes();
|
|
8432
10439
|
function getRouter() {
|
|
8433
10440
|
const router2 = createRouter({
|
|
8434
10441
|
routeTree,
|
|
@@ -8448,7 +10455,7 @@ export {
|
|
|
8448
10455
|
MAX_SLOW_RESPONSE_THRESHOLD_SECONDS as M,
|
|
8449
10456
|
OpenAIRequestSchema as O,
|
|
8450
10457
|
ProviderConfigSchema as P,
|
|
8451
|
-
Route$
|
|
10458
|
+
Route$t as R,
|
|
8452
10459
|
TimeDisplayFormatSchema as T,
|
|
8453
10460
|
DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS as a,
|
|
8454
10461
|
DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS as b,
|