@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,9 +1,10 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef, useMemo, type JSX } from "react";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { CapturedLogSchema, type CapturedLog } from "../contracts";
|
|
4
|
+
import { InspectorGroupsListResponseSchema, type InspectorGroup } from "../lib/groupContract";
|
|
4
5
|
import { useStripConfig } from "../lib/useStripConfig";
|
|
5
6
|
import { OnboardingBanner } from "./OnboardingBanner";
|
|
6
|
-
import { ProxyViewer } from "./ProxyViewer";
|
|
7
|
+
import { ProxyViewer, type SessionMembershipEvidence } from "./ProxyViewer";
|
|
7
8
|
import { dispatchLogFocusRequest } from "./proxy-viewer/logFocus";
|
|
8
9
|
|
|
9
10
|
type SSEUpdate =
|
|
@@ -27,6 +28,29 @@ const SSEUpdateSchema = z.union([
|
|
|
27
28
|
}),
|
|
28
29
|
]);
|
|
29
30
|
|
|
31
|
+
function buildSessionMembershipEvidence(
|
|
32
|
+
groups: readonly InspectorGroup[],
|
|
33
|
+
sessionId: string,
|
|
34
|
+
): SessionMembershipEvidence[] {
|
|
35
|
+
const memberships: SessionMembershipEvidence[] = [];
|
|
36
|
+
for (const group of groups) {
|
|
37
|
+
for (const member of group.members) {
|
|
38
|
+
if (member.sessionId !== sessionId) continue;
|
|
39
|
+
memberships.push({
|
|
40
|
+
groupId: group.id,
|
|
41
|
+
groupTitle: group.title,
|
|
42
|
+
groupKind: group.kind,
|
|
43
|
+
groupStatus: group.status,
|
|
44
|
+
groupTask: group.task,
|
|
45
|
+
groupProject: group.project,
|
|
46
|
+
groupEvidence: group.evidence,
|
|
47
|
+
member,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return memberships;
|
|
52
|
+
}
|
|
53
|
+
|
|
30
54
|
function extractSessions(logs: CapturedLog[]): string[] {
|
|
31
55
|
const set = new Set<string>();
|
|
32
56
|
for (const l of logs) {
|
|
@@ -67,9 +91,13 @@ const HASH_HIGHLIGHT_MS = 1800;
|
|
|
67
91
|
const MAX_CLIENT_LOGS = 500;
|
|
68
92
|
|
|
69
93
|
function buildLogsStreamUrl(sessionId: string | undefined): string {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
94
|
+
const params = new URLSearchParams();
|
|
95
|
+
if (sessionId !== undefined) {
|
|
96
|
+
params.set("sessionId", sessionId);
|
|
97
|
+
params.set("compact", "1");
|
|
98
|
+
}
|
|
99
|
+
const query = params.toString();
|
|
100
|
+
return query.length > 0 ? `/api/logs/stream?${query}` : "/api/logs/stream";
|
|
73
101
|
}
|
|
74
102
|
|
|
75
103
|
function buildLogIndex(logs: readonly CapturedLog[]): Map<number, number> {
|
|
@@ -109,6 +137,8 @@ export function ProxyViewerContainer({
|
|
|
109
137
|
const [selectedModel, setSelectedModel] = useState("__all__");
|
|
110
138
|
const [viewMode, setViewMode] = useState<"simple" | "full">("simple");
|
|
111
139
|
const [error, setError] = useState<string | null>(null);
|
|
140
|
+
const [streamInitialized, setStreamInitialized] = useState(initialSessionId === undefined);
|
|
141
|
+
const [sessionMemberships, setSessionMemberships] = useState<SessionMembershipEvidence[]>([]);
|
|
112
142
|
const eventSourceRef = useRef<EventSource | null>(null);
|
|
113
143
|
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
114
144
|
const handledHashRef = useRef<string | null>(null);
|
|
@@ -169,6 +199,7 @@ export function ProxyViewerContainer({
|
|
|
169
199
|
eventSourceRef.current.close();
|
|
170
200
|
}
|
|
171
201
|
|
|
202
|
+
setStreamInitialized(false);
|
|
172
203
|
const es = new EventSource(buildLogsStreamUrl(initialSessionId));
|
|
173
204
|
eventSourceRef.current = es;
|
|
174
205
|
|
|
@@ -193,6 +224,7 @@ export function ProxyViewerContainer({
|
|
|
193
224
|
const nextLogs = trimClientLogs(update.logs);
|
|
194
225
|
logIndexRef.current = buildLogIndex(nextLogs);
|
|
195
226
|
setAllLogs(nextLogs);
|
|
227
|
+
setStreamInitialized(true);
|
|
196
228
|
setError(null);
|
|
197
229
|
} else if (update.type === "update") {
|
|
198
230
|
scheduleUpdate(update.log);
|
|
@@ -204,6 +236,7 @@ export function ProxyViewerContainer({
|
|
|
204
236
|
|
|
205
237
|
es.onerror = () => {
|
|
206
238
|
setError("SSE connection lost, reconnecting...");
|
|
239
|
+
setStreamInitialized(true);
|
|
207
240
|
es.close();
|
|
208
241
|
if (reconnectTimeoutRef.current !== null) {
|
|
209
242
|
clearTimeout(reconnectTimeoutRef.current);
|
|
@@ -212,6 +245,33 @@ export function ProxyViewerContainer({
|
|
|
212
245
|
};
|
|
213
246
|
}, [initialSessionId, scheduleUpdate]);
|
|
214
247
|
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
if (initialSessionId === undefined) {
|
|
250
|
+
setSessionMemberships([]);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let cancelled = false;
|
|
255
|
+
void fetch("/api/groups")
|
|
256
|
+
.then(async (response) => {
|
|
257
|
+
if (!response.ok) return null;
|
|
258
|
+
const raw: unknown = await response.json();
|
|
259
|
+
const parsed = InspectorGroupsListResponseSchema.safeParse(raw);
|
|
260
|
+
return parsed.success ? parsed.data.groups : null;
|
|
261
|
+
})
|
|
262
|
+
.then((groups) => {
|
|
263
|
+
if (cancelled || groups === null) return;
|
|
264
|
+
setSessionMemberships(buildSessionMembershipEvidence(groups, initialSessionId));
|
|
265
|
+
})
|
|
266
|
+
.catch(() => {
|
|
267
|
+
if (!cancelled) setSessionMemberships([]);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return () => {
|
|
271
|
+
cancelled = true;
|
|
272
|
+
};
|
|
273
|
+
}, [initialSessionId]);
|
|
274
|
+
|
|
215
275
|
useEffect(() => {
|
|
216
276
|
connectSSE();
|
|
217
277
|
return () => {
|
|
@@ -361,6 +421,8 @@ export function ProxyViewerContainer({
|
|
|
361
421
|
onModelChange={setSelectedModel}
|
|
362
422
|
onClearAll={handleClearAll}
|
|
363
423
|
onClearGroup={handleClearGroup}
|
|
424
|
+
isLoading={initialSessionId !== undefined && !streamInitialized}
|
|
425
|
+
sessionMemberships={sessionMemberships}
|
|
364
426
|
viewMode={viewMode}
|
|
365
427
|
onViewModeChange={setViewMode}
|
|
366
428
|
captureMode={captureMode}
|
|
@@ -920,21 +920,22 @@ function MemberRow({ row }: { row: GroupMemberRow }): JSX.Element {
|
|
|
920
920
|
const errors = session?.errorCount ?? null;
|
|
921
921
|
const tokens = session?.tokenUsage.total ?? null;
|
|
922
922
|
const firstChunkMs = log?.firstChunkMs ?? null;
|
|
923
|
+
const sessionLabel = firstNonEmpty(member.label, member.sessionId);
|
|
923
924
|
|
|
924
925
|
return (
|
|
925
926
|
<tr className="border-b border-border last:border-0">
|
|
926
927
|
<td className="max-w-56 px-3 py-2">
|
|
927
|
-
<
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
</
|
|
928
|
+
<a
|
|
929
|
+
href={sessionPath}
|
|
930
|
+
target="_blank"
|
|
931
|
+
rel="noreferrer"
|
|
932
|
+
className="inline-flex max-w-full min-w-0 items-center gap-2 rounded-sm font-mono text-xs text-primary outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
933
|
+
aria-label={`Open session ${sessionLabel}`}
|
|
934
|
+
title={`Open session ${sessionLabel}`}
|
|
935
|
+
>
|
|
936
|
+
<span className="min-w-0 truncate">{sessionLabel}</span>
|
|
936
937
|
<ExternalLink className="size-3 shrink-0 text-muted-foreground" />
|
|
937
|
-
</
|
|
938
|
+
</a>
|
|
938
939
|
<div className="mt-0.5 truncate text-[11px] text-muted-foreground">
|
|
939
940
|
{displayText(member.agent)}
|
|
940
941
|
</div>
|
|
@@ -76,6 +76,7 @@ export function SettingsDialog(): JSX.Element {
|
|
|
76
76
|
<TabsTrigger value="providers">Providers</TabsTrigger>
|
|
77
77
|
<TabsTrigger value="proxy">Proxy</TabsTrigger>
|
|
78
78
|
<TabsTrigger value="storage">Storage</TabsTrigger>
|
|
79
|
+
<TabsTrigger value="mcp">MCP</TabsTrigger>
|
|
79
80
|
<TabsTrigger value="onboarding">Onboarding</TabsTrigger>
|
|
80
81
|
</TabsList>
|
|
81
82
|
|
|
@@ -101,6 +102,9 @@ export function SettingsDialog(): JSX.Element {
|
|
|
101
102
|
<TabsContent value="storage">
|
|
102
103
|
<StorageSettingsTab />
|
|
103
104
|
</TabsContent>
|
|
105
|
+
<TabsContent value="mcp">
|
|
106
|
+
<McpSettingsTab />
|
|
107
|
+
</TabsContent>
|
|
104
108
|
<TabsContent value="onboarding">
|
|
105
109
|
<OnboardingSettingsTab />
|
|
106
110
|
</TabsContent>
|
|
@@ -390,6 +394,139 @@ function OnboardingSettingsTab(): JSX.Element {
|
|
|
390
394
|
);
|
|
391
395
|
}
|
|
392
396
|
|
|
397
|
+
function McpSettingsTab(): JSX.Element {
|
|
398
|
+
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
399
|
+
const origin = useMemo(() => {
|
|
400
|
+
if (typeof window === "undefined") return "http://localhost:25947";
|
|
401
|
+
return window.location.origin;
|
|
402
|
+
}, []);
|
|
403
|
+
const endpoint = `${origin}/api/mcp`;
|
|
404
|
+
const values = useMemo(
|
|
405
|
+
() => [
|
|
406
|
+
{ id: "mcp-url", label: "Streamable HTTP MCP URL", value: endpoint },
|
|
407
|
+
{
|
|
408
|
+
id: "stdio",
|
|
409
|
+
label: "Stdio bridge command",
|
|
410
|
+
value: `agent-inspector-mcp stdio --url ${endpoint}`,
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: "doctor",
|
|
414
|
+
label: "Bridge doctor",
|
|
415
|
+
value: `agent-inspector-mcp doctor --url ${endpoint}`,
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
id: "readonly",
|
|
419
|
+
label: "Readonly mode",
|
|
420
|
+
value: "AGENT_INSPECTOR_MCP_READONLY=1 agent-inspector",
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
id: "capabilities",
|
|
424
|
+
label: "Capabilities resource",
|
|
425
|
+
value: "inspector://mcp/capabilities",
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
id: "context",
|
|
429
|
+
label: "Current context resource",
|
|
430
|
+
value: "inspector://context/current",
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
id: "streamable-json",
|
|
434
|
+
label: "Streamable HTTP JSON",
|
|
435
|
+
value: JSON.stringify(
|
|
436
|
+
{
|
|
437
|
+
mcpServers: {
|
|
438
|
+
"agent-inspector": {
|
|
439
|
+
type: "streamableHttp",
|
|
440
|
+
url: endpoint,
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
null,
|
|
445
|
+
2,
|
|
446
|
+
),
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
id: "stdio-json",
|
|
450
|
+
label: "Stdio MCP JSON",
|
|
451
|
+
value: JSON.stringify(
|
|
452
|
+
{
|
|
453
|
+
mcpServers: {
|
|
454
|
+
"agent-inspector": {
|
|
455
|
+
command: "agent-inspector-mcp",
|
|
456
|
+
args: ["stdio", "--url", endpoint],
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
null,
|
|
461
|
+
2,
|
|
462
|
+
),
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: "opencode-local",
|
|
466
|
+
label: "OpenCode local MCP",
|
|
467
|
+
value: JSON.stringify(
|
|
468
|
+
{
|
|
469
|
+
mcp: {
|
|
470
|
+
"agent-inspector": {
|
|
471
|
+
type: "local",
|
|
472
|
+
command: ["agent-inspector-mcp", "stdio", "--url", endpoint],
|
|
473
|
+
enabled: true,
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
null,
|
|
478
|
+
2,
|
|
479
|
+
),
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
id: "codex-toml",
|
|
483
|
+
label: "Codex TOML",
|
|
484
|
+
value: `[mcp_servers.agent-inspector]\ncommand = "agent-inspector-mcp"\nargs = ["stdio", "--url", "${endpoint}"]`,
|
|
485
|
+
},
|
|
486
|
+
],
|
|
487
|
+
[endpoint],
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
const handleCopy = useCallback((id: string, value: string) => {
|
|
491
|
+
void copyTextToClipboard(value).then((success) => {
|
|
492
|
+
if (!success) return;
|
|
493
|
+
setCopiedId(id);
|
|
494
|
+
setTimeout(() => setCopiedId(null), 1600);
|
|
495
|
+
});
|
|
496
|
+
}, []);
|
|
497
|
+
|
|
498
|
+
return (
|
|
499
|
+
<div className="min-w-0 space-y-4">
|
|
500
|
+
<div className="flex items-center gap-2">
|
|
501
|
+
<Terminal className="size-4 text-muted-foreground" />
|
|
502
|
+
<h3 className="text-sm font-semibold">MCP access</h3>
|
|
503
|
+
</div>
|
|
504
|
+
<div className="grid gap-2 rounded-md border border-border bg-background px-3 py-2 text-xs text-muted-foreground">
|
|
505
|
+
<div>
|
|
506
|
+
Use Streamable HTTP when the client supports URL-based MCP. Use the stdio bridge only for
|
|
507
|
+
stdio-only clients. Configure one entry for the same Inspector instance.
|
|
508
|
+
</div>
|
|
509
|
+
<div>
|
|
510
|
+
If the MCP client runs in another container or host, replace localhost with a reachable
|
|
511
|
+
host/IP and verify the network path.
|
|
512
|
+
</div>
|
|
513
|
+
</div>
|
|
514
|
+
<div className="grid gap-2">
|
|
515
|
+
{values.map((item) => (
|
|
516
|
+
<CopyableSetupValue
|
|
517
|
+
key={item.id}
|
|
518
|
+
id={item.id}
|
|
519
|
+
label={item.label}
|
|
520
|
+
value={item.value}
|
|
521
|
+
copiedId={copiedId}
|
|
522
|
+
onCopy={handleCopy}
|
|
523
|
+
/>
|
|
524
|
+
))}
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
393
530
|
function ProxySettingsTab(): JSX.Element {
|
|
394
531
|
const {
|
|
395
532
|
strip,
|