@tonyclaw/agent-inspector 2.0.41 → 2.0.43
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-DCg6S2cl.js → CompareDrawer-CxM1gCfL.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-TtRG-0E7.js +117 -0
- package/.output/public/assets/{ReplayDialog-ClVgjSDE.js → ReplayDialog-UseUkucS.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-BhqvLQp9.js → RequestAnatomy-aPxgEJ2L.js} +1 -1
- package/.output/public/assets/{ResponseView-BZAJoK6B.js → ResponseView-DA-F4F97.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-CaORmoKX.js → StreamingChunkSequence-JSQEPeNS.js} +1 -1
- package/.output/public/assets/_sessionId-BEXuCWq5.js +1 -0
- package/.output/public/assets/index-Bhsa_2xX.js +1 -0
- package/.output/public/assets/index-DOWlRJ0W.css +1 -0
- package/.output/public/assets/{main-CI3HFEcV.js → main-BpGVJcpB.js} +2 -2
- package/.output/server/{_sessionId-DQ0ljHQ8.mjs → _sessionId-DGn-TENM.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-nkVa9epn.mjs → CompareDrawer-CFElCSYY.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-CXA7iH3H.mjs → ProxyViewerContainer-B7SR9mrD.mjs} +383 -76
- package/.output/server/_ssr/{ReplayDialog-CK71-ucq.mjs → ReplayDialog-DfKapj0k.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-DDCUJJ3X.mjs → RequestAnatomy-CUxTCg31.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-C6ZA7V3B.mjs → ResponseView-BXY0w197.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-DW5v_o6G.mjs → StreamingChunkSequence-CzMnES9H.mjs} +2 -2
- package/.output/server/_ssr/{index-Ckex98yq.mjs → index-CVlT-REW.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-ChJIVv7-.mjs → router-B2d1LUx6.mjs} +1313 -468
- package/.output/server/{_tanstack-start-manifest_v-CJ4__weU.mjs → _tanstack-start-manifest_v-Drpi28BM.mjs} +1 -1
- package/.output/server/index.mjs +60 -60
- package/README.md +36 -12
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +156 -17
- package/src/components/ProxyViewerContainer.tsx +66 -4
- package/src/components/groups/GroupsDialog.tsx +11 -10
- package/src/components/providers/SettingsDialog.tsx +137 -0
- package/src/components/proxy-viewer/LogEntry.tsx +107 -56
- package/src/components/ui/dialog.tsx +1 -1
- package/src/lib/runContract.ts +3 -0
- package/src/mcp/currentContext.ts +65 -0
- package/src/mcp/mode.ts +56 -0
- package/src/mcp/server.ts +466 -15
- package/src/mcp/toolHandlers.ts +372 -0
- package/src/proxy/evidenceExporter.ts +1 -0
- package/src/proxy/runStore.ts +74 -22
- package/.output/public/assets/ProxyViewerContainer-DsU6QETm.js +0 -115
- package/.output/public/assets/_sessionId-BHqywvM3.js +0 -1
- package/.output/public/assets/index-B_LPYuM0.js +0 -1
- package/.output/public/assets/index-BfGJEb-2.css +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { AlertTriangle, GitCompareArrows } from "lucide-react";
|
|
2
2
|
import { Suspense, type JSX } from "react";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState, memo } from "react";
|
|
4
|
-
import type
|
|
4
|
+
import { CapturedLogSchema, type CapturedLog } from "../../contracts";
|
|
5
|
+
import { fetchJson } from "../../lib/apiClient";
|
|
5
6
|
import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
6
7
|
import { stripClaudeCodeBillingHeader } from "../../proxy/claudeCodeStrip";
|
|
7
8
|
import { Button } from "../ui/button";
|
|
@@ -50,6 +51,12 @@ function resolveFocusedTab(tab: LogFocusTab, anatomySegments: AnatomySegment[] |
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
type BodyHydrationState = "idle" | "loading" | "failed";
|
|
55
|
+
|
|
56
|
+
function shouldHydrateLogBody(log: CapturedLog): boolean {
|
|
57
|
+
return log.bodyContentMode === "compact" || log.bodyContentMode === "truncated";
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
export type LogEntryProps = {
|
|
54
61
|
log: CapturedLog;
|
|
55
62
|
viewMode?: "simple" | "full";
|
|
@@ -144,53 +151,56 @@ export const LogEntry = memo(function ({
|
|
|
144
151
|
const [requestDiff, setRequestDiff] = useState<boolean>(false);
|
|
145
152
|
const [activeTab, setActiveTab] = useState("request");
|
|
146
153
|
const [expandToPath, setExpandToPath] = useState<string | null>(null);
|
|
154
|
+
const [hydratedLog, setHydratedLog] = useState<CapturedLog | null>(null);
|
|
155
|
+
const [bodyHydrationState, setBodyHydrationState] = useState<BodyHydrationState>("idle");
|
|
147
156
|
const requestJsonRef = useRef<HTMLDivElement | null>(null);
|
|
148
|
-
const
|
|
157
|
+
const displayLog = hydratedLog ?? log;
|
|
158
|
+
const resolvedFormat = resolveLogFormat(displayLog);
|
|
149
159
|
const adapter = getLogFormatAdapter(resolvedFormat);
|
|
150
160
|
const requestAnalysis = useMemo(
|
|
151
|
-
() => adapter.analyzeRequest(
|
|
152
|
-
[adapter,
|
|
161
|
+
() => adapter.analyzeRequest(displayLog.rawRequestBody),
|
|
162
|
+
[adapter, displayLog.rawRequestBody],
|
|
153
163
|
);
|
|
154
164
|
const responseAnalysis = useMemo(
|
|
155
|
-
() => adapter.analyzeResponse(
|
|
156
|
-
[adapter,
|
|
165
|
+
() => adapter.analyzeResponse(displayLog.responseText),
|
|
166
|
+
[adapter, displayLog.responseText],
|
|
157
167
|
);
|
|
158
168
|
const strippedRequestBody = useMemo(() => {
|
|
159
|
-
if (!strip || resolvedFormat !== "anthropic" ||
|
|
169
|
+
if (!strip || resolvedFormat !== "anthropic" || displayLog.rawRequestBody === null) {
|
|
160
170
|
return null;
|
|
161
171
|
}
|
|
162
|
-
return stripClaudeCodeBillingHeader(
|
|
163
|
-
}, [
|
|
164
|
-
const displayedRequestBody = strippedRequestBody ??
|
|
172
|
+
return stripClaudeCodeBillingHeader(displayLog.rawRequestBody).body;
|
|
173
|
+
}, [displayLog.rawRequestBody, resolvedFormat, strip]);
|
|
174
|
+
const displayedRequestBody = strippedRequestBody ?? displayLog.rawRequestBody;
|
|
165
175
|
const requestExpansion = useJsonBulkExpansion(displayedRequestBody);
|
|
166
|
-
const rawRequestExpansion = useJsonBulkExpansion(
|
|
167
|
-
const responseExpansion = useJsonBulkExpansion(
|
|
176
|
+
const rawRequestExpansion = useJsonBulkExpansion(displayLog.rawRequestBody);
|
|
177
|
+
const responseExpansion = useJsonBulkExpansion(displayLog.responseText);
|
|
168
178
|
|
|
169
179
|
// Headers are rendered as a flat list, so we copy them as pretty-printed JSON.
|
|
170
180
|
// Only build the string when there's at least one entry — empty headers would
|
|
171
181
|
// otherwise copy "{}", which is misleading.
|
|
172
182
|
const headersText = useMemo(
|
|
173
183
|
() =>
|
|
174
|
-
|
|
175
|
-
? JSON.stringify(
|
|
184
|
+
displayLog.headers && Object.keys(displayLog.headers).length > 0
|
|
185
|
+
? JSON.stringify(displayLog.headers, null, 2)
|
|
176
186
|
: null,
|
|
177
|
-
[
|
|
187
|
+
[displayLog.headers],
|
|
178
188
|
);
|
|
179
189
|
const rawHeadersText = useMemo(
|
|
180
190
|
() =>
|
|
181
|
-
|
|
182
|
-
? JSON.stringify(
|
|
191
|
+
displayLog.rawHeaders && Object.keys(displayLog.rawHeaders).length > 0
|
|
192
|
+
? JSON.stringify(displayLog.rawHeaders, null, 2)
|
|
183
193
|
: null,
|
|
184
|
-
[
|
|
194
|
+
[displayLog.rawHeaders],
|
|
185
195
|
);
|
|
186
196
|
|
|
187
197
|
// One copy-feedback hook per JSON-bearing tab, so each tab can surface its
|
|
188
198
|
// own Copy button in the header with independent "Copied!" feedback.
|
|
189
199
|
const requestCopy = useCopyFeedback(displayedRequestBody);
|
|
190
|
-
const rawRequestCopy = useCopyFeedback(
|
|
200
|
+
const rawRequestCopy = useCopyFeedback(displayLog.rawRequestBody);
|
|
191
201
|
const headersCopy = useCopyFeedback(headersText);
|
|
192
202
|
const rawHeadersCopy = useCopyFeedback(rawHeadersText);
|
|
193
|
-
const responseCopy = useCopyFeedback(
|
|
203
|
+
const responseCopy = useCopyFeedback(displayLog.responseText);
|
|
194
204
|
|
|
195
205
|
// Per-tab action bundles consumed by the header. The header renders the
|
|
196
206
|
// entry whose key matches `activeTab`. Tabs without an entry (Context,
|
|
@@ -214,7 +224,7 @@ export const LogEntry = memo(function ({
|
|
|
214
224
|
resolvedFormat,
|
|
215
225
|
viewMode,
|
|
216
226
|
strip,
|
|
217
|
-
|
|
227
|
+
displayLog.rawRequestBody !== null,
|
|
218
228
|
)
|
|
219
229
|
? { active: requestDiff, onToggle: () => setRequestDiff(!requestDiff) }
|
|
220
230
|
: undefined,
|
|
@@ -222,12 +232,12 @@ export const LogEntry = memo(function ({
|
|
|
222
232
|
onCompareWithPrevious === undefined
|
|
223
233
|
? undefined
|
|
224
234
|
: () => {
|
|
225
|
-
onCompareWithPrevious(
|
|
235
|
+
onCompareWithPrevious(displayLog);
|
|
226
236
|
},
|
|
227
237
|
},
|
|
228
238
|
"raw-request": {
|
|
229
239
|
copyLabel: "Copy raw request",
|
|
230
|
-
copyText:
|
|
240
|
+
copyText: displayLog.rawRequestBody,
|
|
231
241
|
copyCopied: rawRequestCopy.copied,
|
|
232
242
|
onCopy: rawRequestCopy.copy,
|
|
233
243
|
expansion: {
|
|
@@ -253,7 +263,7 @@ export const LogEntry = memo(function ({
|
|
|
253
263
|
},
|
|
254
264
|
raw: {
|
|
255
265
|
copyLabel: "Copy response",
|
|
256
|
-
copyText:
|
|
266
|
+
copyText: displayLog.responseText,
|
|
257
267
|
copyCopied: responseCopy.copied,
|
|
258
268
|
onCopy: responseCopy.copy,
|
|
259
269
|
expansion: {
|
|
@@ -268,14 +278,15 @@ export const LogEntry = memo(function ({
|
|
|
268
278
|
requestCopy,
|
|
269
279
|
requestExpansion,
|
|
270
280
|
requestDiff,
|
|
271
|
-
|
|
281
|
+
displayLog,
|
|
282
|
+
displayLog.rawRequestBody,
|
|
272
283
|
rawRequestCopy,
|
|
273
284
|
rawRequestExpansion,
|
|
274
285
|
headersText,
|
|
275
286
|
headersCopy,
|
|
276
287
|
rawHeadersText,
|
|
277
288
|
rawHeadersCopy,
|
|
278
|
-
|
|
289
|
+
displayLog.responseText,
|
|
279
290
|
responseCopy,
|
|
280
291
|
responseExpansion,
|
|
281
292
|
resolvedFormat,
|
|
@@ -295,7 +306,7 @@ export const LogEntry = memo(function ({
|
|
|
295
306
|
() => parseRequestTools(requestExpansion.parsedData),
|
|
296
307
|
[requestExpansion.parsedData],
|
|
297
308
|
);
|
|
298
|
-
const warnings =
|
|
309
|
+
const warnings = displayLog.warnings ?? [];
|
|
299
310
|
const anatomyPaths = useMemo(() => {
|
|
300
311
|
if (anatomySegments === null) return undefined;
|
|
301
312
|
return new Set(anatomySegments.map((s) => s.path));
|
|
@@ -316,6 +327,34 @@ export const LogEntry = memo(function ({
|
|
|
316
327
|
setExpanded(nextExpanded);
|
|
317
328
|
}, [anatomySegments, expanded]);
|
|
318
329
|
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
setHydratedLog(null);
|
|
332
|
+
setBodyHydrationState("idle");
|
|
333
|
+
}, [log.id]);
|
|
334
|
+
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
if (!expanded) return;
|
|
337
|
+
if (!shouldHydrateLogBody(log)) return;
|
|
338
|
+
if (hydratedLog !== null || bodyHydrationState !== "idle") return;
|
|
339
|
+
|
|
340
|
+
let cancelled = false;
|
|
341
|
+
setBodyHydrationState("loading");
|
|
342
|
+
void fetchJson(`/api/logs/${String(log.id)}`, CapturedLogSchema)
|
|
343
|
+
.then((fullLog) => {
|
|
344
|
+
if (cancelled) return;
|
|
345
|
+
setHydratedLog(fullLog);
|
|
346
|
+
setBodyHydrationState("idle");
|
|
347
|
+
})
|
|
348
|
+
.catch(() => {
|
|
349
|
+
if (cancelled) return;
|
|
350
|
+
setBodyHydrationState("failed");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
return () => {
|
|
354
|
+
cancelled = true;
|
|
355
|
+
};
|
|
356
|
+
}, [bodyHydrationState, expanded, hydratedLog, log.bodyContentMode, log.id]);
|
|
357
|
+
|
|
319
358
|
useEffect(() => {
|
|
320
359
|
const handleLogFocusRequest = (event: Event): void => {
|
|
321
360
|
const request = readLogFocusRequest(event);
|
|
@@ -335,7 +374,7 @@ export const LogEntry = memo(function ({
|
|
|
335
374
|
<TooltipProvider>
|
|
336
375
|
<div className="border border-border rounded-lg mb-1 overflow-hidden">
|
|
337
376
|
<LogEntryHeader
|
|
338
|
-
log={
|
|
377
|
+
log={displayLog}
|
|
339
378
|
messageCount={requestAnalysis.messageCount}
|
|
340
379
|
toolCount={requestAnalysis.toolCount}
|
|
341
380
|
expanded={expanded}
|
|
@@ -373,7 +412,7 @@ export const LogEntry = memo(function ({
|
|
|
373
412
|
<TabsContent value="raw-request">
|
|
374
413
|
{activeTab === "raw-request" && (
|
|
375
414
|
<div className="px-4 pt-1 pb-3">
|
|
376
|
-
{
|
|
415
|
+
{displayLog.rawRequestBody === null ? (
|
|
377
416
|
<p className="text-xs text-muted-foreground italic">No request body</p>
|
|
378
417
|
) : rawRequestExpansion.parsedData !== null ? (
|
|
379
418
|
<Suspense fallback={<TabFallback />}>
|
|
@@ -385,7 +424,7 @@ export const LogEntry = memo(function ({
|
|
|
385
424
|
</Suspense>
|
|
386
425
|
) : (
|
|
387
426
|
<pre className="font-mono text-xs whitespace-pre-wrap break-words">
|
|
388
|
-
{
|
|
427
|
+
{displayLog.rawRequestBody}
|
|
389
428
|
</pre>
|
|
390
429
|
)}
|
|
391
430
|
</div>
|
|
@@ -396,6 +435,17 @@ export const LogEntry = memo(function ({
|
|
|
396
435
|
<TabsContent value="request">
|
|
397
436
|
{activeTab === "request" && (
|
|
398
437
|
<div className="px-4 pt-1 pb-3">
|
|
438
|
+
{bodyHydrationState === "loading" && (
|
|
439
|
+
<div className="mb-3 rounded border border-border bg-muted/30 p-3 text-xs text-muted-foreground">
|
|
440
|
+
Loading full request and response bodies...
|
|
441
|
+
</div>
|
|
442
|
+
)}
|
|
443
|
+
{bodyHydrationState === "failed" && (
|
|
444
|
+
<div className="mb-3 rounded border border-amber-500/30 bg-amber-500/10 p-3 text-xs text-amber-200">
|
|
445
|
+
Full request and response bodies could not be loaded. Compact metadata is
|
|
446
|
+
still available.
|
|
447
|
+
</div>
|
|
448
|
+
)}
|
|
399
449
|
{warnings.length > 0 && (
|
|
400
450
|
<div className="mb-3 rounded border border-amber-500/30 bg-amber-500/10 p-3 text-xs">
|
|
401
451
|
<div className="flex items-center gap-2 font-semibold text-amber-300">
|
|
@@ -418,7 +468,7 @@ export const LogEntry = memo(function ({
|
|
|
418
468
|
the diff or JSON view, depending on toggle state. */}
|
|
419
469
|
{requestDiff ? (
|
|
420
470
|
<RequestDiffContent
|
|
421
|
-
rawBody={
|
|
471
|
+
rawBody={displayLog.rawRequestBody}
|
|
422
472
|
displayedBody={displayedRequestBody}
|
|
423
473
|
emptyLabel="No transformation applied; raw and sent request bodies are identical."
|
|
424
474
|
/>
|
|
@@ -453,8 +503,8 @@ export const LogEntry = memo(function ({
|
|
|
453
503
|
<Suspense fallback={<TabFallback />}>
|
|
454
504
|
<LazyRequestAnatomy
|
|
455
505
|
parsed={requestExpansion.parsedData}
|
|
456
|
-
inputTokens={
|
|
457
|
-
model={
|
|
506
|
+
inputTokens={displayLog.inputTokens ?? null}
|
|
507
|
+
model={displayLog.model}
|
|
458
508
|
segments={anatomySegments}
|
|
459
509
|
onSegmentActivate={jumpToAnatomySegment}
|
|
460
510
|
/>
|
|
@@ -471,7 +521,8 @@ export const LogEntry = memo(function ({
|
|
|
471
521
|
<div className="flex justify-end gap-2 mb-2">
|
|
472
522
|
{shouldShowHeadersDiffButton(
|
|
473
523
|
viewMode,
|
|
474
|
-
|
|
524
|
+
displayLog.rawHeaders !== undefined &&
|
|
525
|
+
Object.keys(displayLog.rawHeaders).length > 0,
|
|
475
526
|
) && (
|
|
476
527
|
<DiffToggleButton
|
|
477
528
|
active={headersDiff}
|
|
@@ -484,13 +535,13 @@ export const LogEntry = memo(function ({
|
|
|
484
535
|
</div>
|
|
485
536
|
{headersDiff ? (
|
|
486
537
|
<HeadersDiffContent
|
|
487
|
-
rawHeaders={
|
|
488
|
-
headers={
|
|
538
|
+
rawHeaders={displayLog.rawHeaders}
|
|
539
|
+
headers={displayLog.headers}
|
|
489
540
|
emptyLabel="No transformation applied; raw and processed headers are identical."
|
|
490
541
|
/>
|
|
491
|
-
) :
|
|
542
|
+
) : displayLog.headers && Object.keys(displayLog.headers).length > 0 ? (
|
|
492
543
|
<div className="space-y-1 font-mono text-xs">
|
|
493
|
-
{Object.entries(
|
|
544
|
+
{Object.entries(displayLog.headers)
|
|
494
545
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
495
546
|
.map(([key, value]) => (
|
|
496
547
|
<div key={key} className="flex gap-2">
|
|
@@ -516,9 +567,9 @@ export const LogEntry = memo(function ({
|
|
|
516
567
|
{activeTab === "raw-headers" && (
|
|
517
568
|
<div className="px-4 pt-1 pb-3">
|
|
518
569
|
{/* Copy lives in the log header. */}
|
|
519
|
-
{
|
|
570
|
+
{displayLog.rawHeaders && Object.keys(displayLog.rawHeaders).length > 0 ? (
|
|
520
571
|
<div className="space-y-1 font-mono text-xs">
|
|
521
|
-
{Object.entries(
|
|
572
|
+
{Object.entries(displayLog.rawHeaders)
|
|
522
573
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
523
574
|
.map(([key, value]) => (
|
|
524
575
|
<div key={key} className="flex gap-2">
|
|
@@ -544,16 +595,16 @@ export const LogEntry = memo(function ({
|
|
|
544
595
|
<TabsContent value="raw">
|
|
545
596
|
{activeTab === "raw" && (
|
|
546
597
|
<div className="px-4 pt-1 pb-3 space-y-3">
|
|
547
|
-
{
|
|
598
|
+
{displayLog.error !== undefined && displayLog.error !== null && (
|
|
548
599
|
<div className="rounded border border-destructive/50 bg-destructive/10 p-3 text-xs">
|
|
549
600
|
<div className="font-semibold text-destructive mb-1">SSE Error</div>
|
|
550
|
-
<div className="text-muted-foreground font-mono">{
|
|
601
|
+
<div className="text-muted-foreground font-mono">{displayLog.error}</div>
|
|
551
602
|
</div>
|
|
552
603
|
)}
|
|
553
|
-
{
|
|
604
|
+
{displayLog.responseText !== null ? (
|
|
554
605
|
<Suspense fallback={<TabFallback />}>
|
|
555
606
|
<LazyJsonViewerFromString
|
|
556
|
-
text={
|
|
607
|
+
text={displayLog.responseText}
|
|
557
608
|
defaultExpandDepth={0}
|
|
558
609
|
bulkDepth={responseExpansion.bulkDepth}
|
|
559
610
|
bulkRevision={responseExpansion.bulkRevision}
|
|
@@ -562,11 +613,11 @@ export const LogEntry = memo(function ({
|
|
|
562
613
|
) : (
|
|
563
614
|
<p className="text-xs text-muted-foreground italic">No response</p>
|
|
564
615
|
)}
|
|
565
|
-
{
|
|
616
|
+
{displayLog.streaming === true && (
|
|
566
617
|
<Suspense fallback={<TabFallback />}>
|
|
567
618
|
<LazyStreamingChunkSequence
|
|
568
|
-
logId={
|
|
569
|
-
truncated={
|
|
619
|
+
logId={displayLog.id}
|
|
620
|
+
truncated={displayLog.streamingChunksPath !== null}
|
|
570
621
|
/>
|
|
571
622
|
</Suspense>
|
|
572
623
|
)}
|
|
@@ -579,15 +630,15 @@ export const LogEntry = memo(function ({
|
|
|
579
630
|
<div className="px-4 pt-1 pb-3">
|
|
580
631
|
<Suspense fallback={<TabFallback />}>
|
|
581
632
|
<LazyResponseView
|
|
582
|
-
responseText={
|
|
583
|
-
responseStatus={
|
|
584
|
-
streaming={
|
|
585
|
-
inputTokens={
|
|
586
|
-
outputTokens={
|
|
587
|
-
cacheCreationInputTokens={
|
|
588
|
-
cacheReadInputTokens={
|
|
633
|
+
responseText={displayLog.responseText}
|
|
634
|
+
responseStatus={displayLog.responseStatus}
|
|
635
|
+
streaming={displayLog.streaming}
|
|
636
|
+
inputTokens={displayLog.inputTokens}
|
|
637
|
+
outputTokens={displayLog.outputTokens}
|
|
638
|
+
cacheCreationInputTokens={displayLog.cacheCreationInputTokens}
|
|
639
|
+
cacheReadInputTokens={displayLog.cacheReadInputTokens}
|
|
589
640
|
apiFormat={resolvedFormat}
|
|
590
|
-
error={
|
|
641
|
+
error={displayLog.error}
|
|
591
642
|
/>
|
|
592
643
|
</Suspense>
|
|
593
644
|
</div>
|
|
@@ -598,7 +649,7 @@ export const LogEntry = memo(function ({
|
|
|
598
649
|
)}
|
|
599
650
|
</div>
|
|
600
651
|
<Suspense fallback={null}>
|
|
601
|
-
<LazyReplayDialog log={
|
|
652
|
+
<LazyReplayDialog log={displayLog} open={replayOpen} onOpenChange={setReplayOpen} />
|
|
602
653
|
</Suspense>
|
|
603
654
|
</TooltipProvider>
|
|
604
655
|
);
|
|
@@ -38,7 +38,7 @@ function DialogOverlay({
|
|
|
38
38
|
<DialogPrimitive.Overlay
|
|
39
39
|
data-slot="dialog-overlay"
|
|
40
40
|
className={cn(
|
|
41
|
-
"fixed inset-0 z-
|
|
41
|
+
"fixed inset-0 z-40 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
42
42
|
className,
|
|
43
43
|
)}
|
|
44
44
|
{...props}
|
package/src/lib/runContract.ts
CHANGED
|
@@ -76,6 +76,7 @@ export const EvidenceReportSchema = JenkinsReportSchema;
|
|
|
76
76
|
export const InspectorRunSchema = z.object({
|
|
77
77
|
id: z.string(),
|
|
78
78
|
sessionId: z.string(),
|
|
79
|
+
groupId: z.string().nullable().default(null),
|
|
79
80
|
title: z.string(),
|
|
80
81
|
task: z.string().nullable(),
|
|
81
82
|
project: z.string().nullable(),
|
|
@@ -92,6 +93,7 @@ export const CreateInspectorRunInputSchema = z
|
|
|
92
93
|
.object({
|
|
93
94
|
runId: z.string().min(1).optional(),
|
|
94
95
|
sessionId: z.string().min(1).optional(),
|
|
96
|
+
groupId: z.string().min(1).nullable().optional(),
|
|
95
97
|
title: z.string().min(1).optional(),
|
|
96
98
|
task: z.string().nullable().optional(),
|
|
97
99
|
project: z.string().nullable().optional(),
|
|
@@ -104,6 +106,7 @@ export const CreateInspectorRunInputSchema = z
|
|
|
104
106
|
|
|
105
107
|
export const UpdateInspectorRunInputSchema = z.object({
|
|
106
108
|
sessionId: z.string().min(1).optional(),
|
|
109
|
+
groupId: z.string().min(1).nullable().optional(),
|
|
107
110
|
title: z.string().min(1).optional(),
|
|
108
111
|
task: z.string().nullable().optional(),
|
|
109
112
|
project: z.string().nullable().optional(),
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { JsonValue } from "../contracts";
|
|
2
|
+
|
|
3
|
+
export type InspectorCurrentContext = {
|
|
4
|
+
groupId: string | null;
|
|
5
|
+
runId: string | null;
|
|
6
|
+
sessionId: string | null;
|
|
7
|
+
model: string | null;
|
|
8
|
+
provider: string | null;
|
|
9
|
+
agent: string | null;
|
|
10
|
+
project: string | null;
|
|
11
|
+
task: string | null;
|
|
12
|
+
metadata: Record<string, JsonValue>;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type InspectorCurrentContextPatch = Partial<
|
|
17
|
+
Omit<InspectorCurrentContext, "metadata" | "updatedAt">
|
|
18
|
+
> & {
|
|
19
|
+
metadata?: Record<string, JsonValue>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function envString(name: string): string | null {
|
|
23
|
+
const value = process.env[name];
|
|
24
|
+
if (value === undefined) return null;
|
|
25
|
+
const trimmed = value.trim();
|
|
26
|
+
return trimmed.length === 0 ? null : trimmed;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function initialContext(): InspectorCurrentContext {
|
|
30
|
+
return {
|
|
31
|
+
groupId: envString("INSPECTOR_GROUP_ID"),
|
|
32
|
+
runId: envString("INSPECTOR_RUN_ID"),
|
|
33
|
+
sessionId: envString("INSPECTOR_SESSION_ID"),
|
|
34
|
+
model: envString("INSPECTOR_MODEL"),
|
|
35
|
+
provider: envString("INSPECTOR_PROVIDER"),
|
|
36
|
+
agent: envString("INSPECTOR_AGENT"),
|
|
37
|
+
project: envString("INSPECTOR_PROJECT"),
|
|
38
|
+
task: envString("INSPECTOR_TASK"),
|
|
39
|
+
metadata: {},
|
|
40
|
+
updatedAt: new Date().toISOString(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let currentContext = initialContext();
|
|
45
|
+
|
|
46
|
+
export function getCurrentContext(): InspectorCurrentContext {
|
|
47
|
+
return currentContext;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function setCurrentContext(patch: InspectorCurrentContextPatch): InspectorCurrentContext {
|
|
51
|
+
currentContext = {
|
|
52
|
+
...currentContext,
|
|
53
|
+
...patch,
|
|
54
|
+
metadata:
|
|
55
|
+
patch.metadata === undefined
|
|
56
|
+
? currentContext.metadata
|
|
57
|
+
: { ...currentContext.metadata, ...patch.metadata },
|
|
58
|
+
updatedAt: new Date().toISOString(),
|
|
59
|
+
};
|
|
60
|
+
return currentContext;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function _resetCurrentContextForTests(): void {
|
|
64
|
+
currentContext = initialContext();
|
|
65
|
+
}
|
package/src/mcp/mode.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type McpWriteMode = "enabled" | "readonly";
|
|
2
|
+
|
|
3
|
+
function parseFlag(value: string | undefined): boolean | null {
|
|
4
|
+
if (value === undefined) return null;
|
|
5
|
+
switch (value.trim().toLowerCase()) {
|
|
6
|
+
case "1":
|
|
7
|
+
case "true":
|
|
8
|
+
case "yes":
|
|
9
|
+
case "on":
|
|
10
|
+
return true;
|
|
11
|
+
case "0":
|
|
12
|
+
case "false":
|
|
13
|
+
case "no":
|
|
14
|
+
case "off":
|
|
15
|
+
return false;
|
|
16
|
+
default:
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isMcpWriteEnabled(): boolean {
|
|
22
|
+
const readonlyFlag = parseFlag(process.env["AGENT_INSPECTOR_MCP_READONLY"]);
|
|
23
|
+
if (readonlyFlag === true) return false;
|
|
24
|
+
|
|
25
|
+
const writesFlag = parseFlag(process.env["AGENT_INSPECTOR_MCP_WRITES"]);
|
|
26
|
+
if (writesFlag === false) return false;
|
|
27
|
+
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getMcpWriteMode(): McpWriteMode {
|
|
32
|
+
return isMcpWriteEnabled() ? "enabled" : "readonly";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getMcpModeInfo(): {
|
|
36
|
+
writeMode: McpWriteMode;
|
|
37
|
+
writesEnabled: boolean;
|
|
38
|
+
env: {
|
|
39
|
+
AGENT_INSPECTOR_MCP_READONLY: string | null;
|
|
40
|
+
AGENT_INSPECTOR_MCP_WRITES: string | null;
|
|
41
|
+
};
|
|
42
|
+
note: string;
|
|
43
|
+
} {
|
|
44
|
+
const writesEnabled = isMcpWriteEnabled();
|
|
45
|
+
return {
|
|
46
|
+
writeMode: writesEnabled ? "enabled" : "readonly",
|
|
47
|
+
writesEnabled,
|
|
48
|
+
env: {
|
|
49
|
+
AGENT_INSPECTOR_MCP_READONLY: process.env["AGENT_INSPECTOR_MCP_READONLY"] ?? null,
|
|
50
|
+
AGENT_INSPECTOR_MCP_WRITES: process.env["AGENT_INSPECTOR_MCP_WRITES"] ?? null,
|
|
51
|
+
},
|
|
52
|
+
note: writesEnabled
|
|
53
|
+
? "MCP write/action tools are enabled. Set AGENT_INSPECTOR_MCP_READONLY=1 to expose the same catalog while blocking mutations."
|
|
54
|
+
: "MCP is in readonly mode. Read tools/resources still work, while write/action tools return an isError result.",
|
|
55
|
+
};
|
|
56
|
+
}
|