@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.
Files changed (42) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-DCg6S2cl.js → CompareDrawer-CxM1gCfL.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-TtRG-0E7.js +117 -0
  4. package/.output/public/assets/{ReplayDialog-ClVgjSDE.js → ReplayDialog-UseUkucS.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-BhqvLQp9.js → RequestAnatomy-aPxgEJ2L.js} +1 -1
  6. package/.output/public/assets/{ResponseView-BZAJoK6B.js → ResponseView-DA-F4F97.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-CaORmoKX.js → StreamingChunkSequence-JSQEPeNS.js} +1 -1
  8. package/.output/public/assets/_sessionId-BEXuCWq5.js +1 -0
  9. package/.output/public/assets/index-Bhsa_2xX.js +1 -0
  10. package/.output/public/assets/index-DOWlRJ0W.css +1 -0
  11. package/.output/public/assets/{main-CI3HFEcV.js → main-BpGVJcpB.js} +2 -2
  12. package/.output/server/{_sessionId-DQ0ljHQ8.mjs → _sessionId-DGn-TENM.mjs} +2 -2
  13. package/.output/server/_ssr/{CompareDrawer-nkVa9epn.mjs → CompareDrawer-CFElCSYY.mjs} +2 -2
  14. package/.output/server/_ssr/{ProxyViewerContainer-CXA7iH3H.mjs → ProxyViewerContainer-B7SR9mrD.mjs} +383 -76
  15. package/.output/server/_ssr/{ReplayDialog-CK71-ucq.mjs → ReplayDialog-DfKapj0k.mjs} +3 -3
  16. package/.output/server/_ssr/{RequestAnatomy-DDCUJJ3X.mjs → RequestAnatomy-CUxTCg31.mjs} +2 -2
  17. package/.output/server/_ssr/{ResponseView-C6ZA7V3B.mjs → ResponseView-BXY0w197.mjs} +2 -2
  18. package/.output/server/_ssr/{StreamingChunkSequence-DW5v_o6G.mjs → StreamingChunkSequence-CzMnES9H.mjs} +2 -2
  19. package/.output/server/_ssr/{index-Ckex98yq.mjs → index-CVlT-REW.mjs} +2 -2
  20. package/.output/server/_ssr/index.mjs +2 -2
  21. package/.output/server/_ssr/{router-ChJIVv7-.mjs → router-B2d1LUx6.mjs} +1313 -468
  22. package/.output/server/{_tanstack-start-manifest_v-CJ4__weU.mjs → _tanstack-start-manifest_v-Drpi28BM.mjs} +1 -1
  23. package/.output/server/index.mjs +60 -60
  24. package/README.md +36 -12
  25. package/package.json +1 -1
  26. package/src/components/ProxyViewer.tsx +156 -17
  27. package/src/components/ProxyViewerContainer.tsx +66 -4
  28. package/src/components/groups/GroupsDialog.tsx +11 -10
  29. package/src/components/providers/SettingsDialog.tsx +137 -0
  30. package/src/components/proxy-viewer/LogEntry.tsx +107 -56
  31. package/src/components/ui/dialog.tsx +1 -1
  32. package/src/lib/runContract.ts +3 -0
  33. package/src/mcp/currentContext.ts +65 -0
  34. package/src/mcp/mode.ts +56 -0
  35. package/src/mcp/server.ts +466 -15
  36. package/src/mcp/toolHandlers.ts +372 -0
  37. package/src/proxy/evidenceExporter.ts +1 -0
  38. package/src/proxy/runStore.ts +74 -22
  39. package/.output/public/assets/ProxyViewerContainer-DsU6QETm.js +0 -115
  40. package/.output/public/assets/_sessionId-BHqywvM3.js +0 -1
  41. package/.output/public/assets/index-B_LPYuM0.js +0 -1
  42. 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
- if (sessionId === undefined) return "/api/logs/stream";
71
- const params = new URLSearchParams({ sessionId });
72
- return `/api/logs/stream?${params.toString()}`;
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
- <div className="flex min-w-0 items-center gap-2">
928
- <a
929
- href={sessionPath}
930
- target="_blank"
931
- rel="noreferrer"
932
- className="min-w-0 truncate font-mono text-xs text-primary hover:underline"
933
- >
934
- {firstNonEmpty(member.label, member.sessionId)}
935
- </a>
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
- </div>
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,