@tonyclaw/agent-inspector 2.0.40 → 2.0.42
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-Cfqxlo-U.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-CE9pAX4c.js +117 -0
- package/.output/public/assets/{ReplayDialog-TTTVCMe9.js → ReplayDialog-daRidZo_.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-c8ru0pS-.js → RequestAnatomy-CJ7EPQGQ.js} +1 -1
- package/.output/public/assets/ResponseView-xBmr54hB.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-nK-0LpbM.js +1 -0
- package/.output/public/assets/_sessionId-CbWEG5ej.js +1 -0
- package/.output/public/assets/index-D1MkoT4l.js +1 -0
- package/.output/public/assets/{main-CKYe6UUv.js → main-DOy_Q96H.js} +2 -2
- package/.output/server/{_sessionId-CkwhOzdS.mjs → _sessionId-DtYRZHlM.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-BHDiuYMw.mjs → CompareDrawer-fy1ARwtx.mjs} +4 -3
- package/.output/server/_ssr/{ProxyViewerContainer-0c2_DBMX.mjs → ProxyViewerContainer-9GJtCWCq.mjs} +242 -45
- package/.output/server/_ssr/{ReplayDialog-CTjwzDDH.mjs → ReplayDialog-BrS7syOE.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-D36oujv2.mjs → RequestAnatomy-BtWt1cWY.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-ClZ3oODE.mjs → ResponseView-CkKGYE6m.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-Ck5l4p1m.mjs → StreamingChunkSequence-C7rB3osr.mjs} +2 -2
- package/.output/server/_ssr/{index-DKBYH0TC.mjs → index-D24WforP.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-D4I3ZyVS.mjs → router-CVqVjBzJ.mjs} +1312 -467
- package/.output/server/{_tanstack-start-manifest_v-D4vSn_wD.mjs → _tanstack-start-manifest_v-DSikvwru.mjs} +1 -1
- package/.output/server/index.mjs +59 -59
- package/README.md +36 -12
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +5 -2
- package/src/components/providers/ProviderCard.tsx +6 -3
- package/src/components/providers/ProviderForm.tsx +6 -3
- package/src/components/providers/ProvidersPanel.tsx +6 -1
- package/src/components/providers/SettingsDialog.tsx +142 -2
- package/src/components/proxy-viewer/CompareDrawer.tsx +3 -1
- package/src/components/proxy-viewer/LogEntry.tsx +8 -5
- package/src/components/proxy-viewer/useCopyFeedback.ts +3 -1
- package/src/components/ui/json-viewer.tsx +7 -2
- package/src/lib/clipboard.ts +67 -0
- 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/CompareDrawer-p0uYxW7_.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-BGqeiYKD.js +0 -115
- package/.output/public/assets/ResponseView-D59jTMy4.js +0 -1
- package/.output/public/assets/StreamingChunkSequence-UlAMVF9j.js +0 -1
- package/.output/public/assets/_sessionId-EHPexdPl.js +0 -1
- package/.output/public/assets/index-CZP-XfpB.js +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AlertTriangle, GitCompareArrows } from "lucide-react";
|
|
2
2
|
import { Suspense, type JSX } from "react";
|
|
3
|
-
import { useEffect, useMemo, useRef, useState, memo } from "react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState, memo } from "react";
|
|
4
4
|
import type { CapturedLog } from "../../contracts";
|
|
5
5
|
import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
6
6
|
import { stripClaudeCodeBillingHeader } from "../../proxy/claudeCodeStrip";
|
|
@@ -308,9 +308,12 @@ export const LogEntry = memo(function ({
|
|
|
308
308
|
},
|
|
309
309
|
});
|
|
310
310
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
311
|
+
const handleToggleExpanded = useCallback(() => {
|
|
312
|
+
const nextExpanded = !expanded;
|
|
313
|
+
if (nextExpanded) {
|
|
314
|
+
setActiveTab(anatomySegments !== null ? "anatomy" : "request");
|
|
315
|
+
}
|
|
316
|
+
setExpanded(nextExpanded);
|
|
314
317
|
}, [anatomySegments, expanded]);
|
|
315
318
|
|
|
316
319
|
useEffect(() => {
|
|
@@ -336,7 +339,7 @@ export const LogEntry = memo(function ({
|
|
|
336
339
|
messageCount={requestAnalysis.messageCount}
|
|
337
340
|
toolCount={requestAnalysis.toolCount}
|
|
338
341
|
expanded={expanded}
|
|
339
|
-
onToggle={
|
|
342
|
+
onToggle={handleToggleExpanded}
|
|
340
343
|
cacheTrend={cacheTrend}
|
|
341
344
|
slowResponseThresholdSeconds={slowResponseThresholdSeconds}
|
|
342
345
|
timeDisplayFormat={timeDisplayFormat}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState, type MouseEvent } from "react";
|
|
2
|
+
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Clipboard write with a 2s "Copied!" indicator. Returns null-safe
|
|
@@ -23,7 +24,8 @@ export function useCopyFeedback(text: string | null): {
|
|
|
23
24
|
(event: MouseEvent) => {
|
|
24
25
|
event.stopPropagation();
|
|
25
26
|
if (text === null) return;
|
|
26
|
-
void
|
|
27
|
+
void copyTextToClipboard(text).then((success) => {
|
|
28
|
+
if (!success) return;
|
|
27
29
|
setCopied(true);
|
|
28
30
|
if (timerRef.current !== null) clearTimeout(timerRef.current);
|
|
29
31
|
timerRef.current = setTimeout(() => setCopied(false), 2000);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Check, ChevronDown, ChevronRight, ChevronsDown, Copy } from "lucide-react";
|
|
2
2
|
import { type JSX, memo, useEffect, useMemo, useState } from "react";
|
|
3
3
|
import ReactMarkdown from "react-markdown";
|
|
4
|
+
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
4
5
|
import { cn } from "../../lib/utils";
|
|
5
6
|
import { Button } from "./button";
|
|
6
7
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
|
@@ -134,7 +135,8 @@ function CopyValueButton({ value }: { value: JsonValue }): JSX.Element {
|
|
|
134
135
|
|
|
135
136
|
function handleCopy(e: React.MouseEvent): void {
|
|
136
137
|
e.stopPropagation();
|
|
137
|
-
void
|
|
138
|
+
void copyTextToClipboard(JSON.stringify(value, null, 2)).then((success) => {
|
|
139
|
+
if (!success) return;
|
|
138
140
|
setCopied(true);
|
|
139
141
|
setTimeout(() => {
|
|
140
142
|
setCopied(false);
|
|
@@ -147,6 +149,7 @@ function CopyValueButton({ value }: { value: JsonValue }): JSX.Element {
|
|
|
147
149
|
type="button"
|
|
148
150
|
onClick={handleCopy}
|
|
149
151
|
className="inline-flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground transition-colors"
|
|
152
|
+
aria-label={copied ? "Copied JSON" : "Copy JSON"}
|
|
150
153
|
title="Copy JSON"
|
|
151
154
|
>
|
|
152
155
|
{copied ? <Check className="size-3 text-green-500" /> : <Copy className="size-3" />}
|
|
@@ -159,7 +162,8 @@ function CopyButton({ value }: { value: JsonValue }): JSX.Element {
|
|
|
159
162
|
|
|
160
163
|
function handleCopy(e: React.MouseEvent): void {
|
|
161
164
|
e.stopPropagation();
|
|
162
|
-
void
|
|
165
|
+
void copyTextToClipboard(JSON.stringify(value, null, 2)).then((success) => {
|
|
166
|
+
if (!success) return;
|
|
163
167
|
setCopied(true);
|
|
164
168
|
setTimeout(() => {
|
|
165
169
|
setCopied(false);
|
|
@@ -172,6 +176,7 @@ function CopyButton({ value }: { value: JsonValue }): JSX.Element {
|
|
|
172
176
|
type="button"
|
|
173
177
|
onClick={handleCopy}
|
|
174
178
|
className="opacity-0 group-hover/row:opacity-100 hover:bg-muted p-0.5 rounded transition-opacity shrink-0"
|
|
179
|
+
aria-label={copied ? "Copied JSON value" : "Copy JSON value"}
|
|
175
180
|
title="Copy to clipboard"
|
|
176
181
|
>
|
|
177
182
|
{copied ? (
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
function restoreSelection(selection: Selection | null, ranges: readonly Range[]): void {
|
|
2
|
+
if (selection === null) return;
|
|
3
|
+
selection.removeAllRanges();
|
|
4
|
+
for (const range of ranges) {
|
|
5
|
+
selection.addRange(range);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function readSelectionRanges(selection: Selection | null): Range[] {
|
|
10
|
+
if (selection === null) return [];
|
|
11
|
+
const ranges: Range[] = [];
|
|
12
|
+
for (let index = 0; index < selection.rangeCount; index++) {
|
|
13
|
+
ranges.push(selection.getRangeAt(index));
|
|
14
|
+
}
|
|
15
|
+
return ranges;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function copyWithTextareaFallback(text: string): boolean {
|
|
19
|
+
if (typeof document === "undefined") return false;
|
|
20
|
+
|
|
21
|
+
const activeElement = document.activeElement;
|
|
22
|
+
const selection = document.getSelection();
|
|
23
|
+
const ranges = readSelectionRanges(selection);
|
|
24
|
+
const textarea = document.createElement("textarea");
|
|
25
|
+
|
|
26
|
+
textarea.value = text;
|
|
27
|
+
textarea.setAttribute("readonly", "");
|
|
28
|
+
textarea.style.position = "fixed";
|
|
29
|
+
textarea.style.top = "0";
|
|
30
|
+
textarea.style.left = "-9999px";
|
|
31
|
+
textarea.style.opacity = "0";
|
|
32
|
+
|
|
33
|
+
document.body.appendChild(textarea);
|
|
34
|
+
textarea.focus();
|
|
35
|
+
textarea.select();
|
|
36
|
+
textarea.setSelectionRange(0, text.length);
|
|
37
|
+
|
|
38
|
+
let copied = false;
|
|
39
|
+
try {
|
|
40
|
+
copied = document.execCommand("copy");
|
|
41
|
+
} catch {
|
|
42
|
+
// Keep the false default.
|
|
43
|
+
} finally {
|
|
44
|
+
document.body.removeChild(textarea);
|
|
45
|
+
restoreSelection(selection, ranges);
|
|
46
|
+
if (typeof HTMLElement !== "undefined" && activeElement instanceof HTMLElement) {
|
|
47
|
+
activeElement.focus({ preventScroll: true });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return copied;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function copyTextToClipboard(text: string): Promise<boolean> {
|
|
55
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
56
|
+
if (clipboard !== undefined) {
|
|
57
|
+
try {
|
|
58
|
+
await clipboard.writeText(text);
|
|
59
|
+
return true;
|
|
60
|
+
} catch {
|
|
61
|
+
// Fall back below. Some embedded browsers expose Clipboard API but reject
|
|
62
|
+
// writes outside a secure context or without an explicit permission grant.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return copyWithTextareaFallback(text);
|
|
67
|
+
}
|
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
|
+
}
|