@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.
Files changed (47) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/CompareDrawer-Cfqxlo-U.js +1 -0
  3. package/.output/public/assets/ProxyViewerContainer-CE9pAX4c.js +117 -0
  4. package/.output/public/assets/{ReplayDialog-TTTVCMe9.js → ReplayDialog-daRidZo_.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-c8ru0pS-.js → RequestAnatomy-CJ7EPQGQ.js} +1 -1
  6. package/.output/public/assets/ResponseView-xBmr54hB.js +1 -0
  7. package/.output/public/assets/StreamingChunkSequence-nK-0LpbM.js +1 -0
  8. package/.output/public/assets/_sessionId-CbWEG5ej.js +1 -0
  9. package/.output/public/assets/index-D1MkoT4l.js +1 -0
  10. package/.output/public/assets/{main-CKYe6UUv.js → main-DOy_Q96H.js} +2 -2
  11. package/.output/server/{_sessionId-CkwhOzdS.mjs → _sessionId-DtYRZHlM.mjs} +2 -2
  12. package/.output/server/_ssr/{CompareDrawer-BHDiuYMw.mjs → CompareDrawer-fy1ARwtx.mjs} +4 -3
  13. package/.output/server/_ssr/{ProxyViewerContainer-0c2_DBMX.mjs → ProxyViewerContainer-9GJtCWCq.mjs} +242 -45
  14. package/.output/server/_ssr/{ReplayDialog-CTjwzDDH.mjs → ReplayDialog-BrS7syOE.mjs} +3 -3
  15. package/.output/server/_ssr/{RequestAnatomy-D36oujv2.mjs → RequestAnatomy-BtWt1cWY.mjs} +2 -2
  16. package/.output/server/_ssr/{ResponseView-ClZ3oODE.mjs → ResponseView-CkKGYE6m.mjs} +2 -2
  17. package/.output/server/_ssr/{StreamingChunkSequence-Ck5l4p1m.mjs → StreamingChunkSequence-C7rB3osr.mjs} +2 -2
  18. package/.output/server/_ssr/{index-DKBYH0TC.mjs → index-D24WforP.mjs} +2 -2
  19. package/.output/server/_ssr/index.mjs +2 -2
  20. package/.output/server/_ssr/{router-D4I3ZyVS.mjs → router-CVqVjBzJ.mjs} +1312 -467
  21. package/.output/server/{_tanstack-start-manifest_v-D4vSn_wD.mjs → _tanstack-start-manifest_v-DSikvwru.mjs} +1 -1
  22. package/.output/server/index.mjs +59 -59
  23. package/README.md +36 -12
  24. package/package.json +1 -1
  25. package/src/components/ProxyViewer.tsx +5 -2
  26. package/src/components/providers/ProviderCard.tsx +6 -3
  27. package/src/components/providers/ProviderForm.tsx +6 -3
  28. package/src/components/providers/ProvidersPanel.tsx +6 -1
  29. package/src/components/providers/SettingsDialog.tsx +142 -2
  30. package/src/components/proxy-viewer/CompareDrawer.tsx +3 -1
  31. package/src/components/proxy-viewer/LogEntry.tsx +8 -5
  32. package/src/components/proxy-viewer/useCopyFeedback.ts +3 -1
  33. package/src/components/ui/json-viewer.tsx +7 -2
  34. package/src/lib/clipboard.ts +67 -0
  35. package/src/lib/runContract.ts +3 -0
  36. package/src/mcp/currentContext.ts +65 -0
  37. package/src/mcp/mode.ts +56 -0
  38. package/src/mcp/server.ts +466 -15
  39. package/src/mcp/toolHandlers.ts +372 -0
  40. package/src/proxy/evidenceExporter.ts +1 -0
  41. package/src/proxy/runStore.ts +74 -22
  42. package/.output/public/assets/CompareDrawer-p0uYxW7_.js +0 -1
  43. package/.output/public/assets/ProxyViewerContainer-BGqeiYKD.js +0 -115
  44. package/.output/public/assets/ResponseView-D59jTMy4.js +0 -1
  45. package/.output/public/assets/StreamingChunkSequence-UlAMVF9j.js +0 -1
  46. package/.output/public/assets/_sessionId-EHPexdPl.js +0 -1
  47. 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
- useEffect(() => {
312
- if (!expanded) return;
313
- setActiveTab(anatomySegments !== null ? "anatomy" : "request");
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={() => setExpanded(!expanded)}
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 window.navigator.clipboard.writeText(text).then(() => {
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 window.navigator.clipboard.writeText(JSON.stringify(value, null, 2)).then(() => {
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 window.navigator.clipboard.writeText(JSON.stringify(value, null, 2)).then(() => {
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
+ }
@@ -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
+ }
@@ -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
+ }