@tonyclaw/agent-inspector 2.1.7 → 2.1.9

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 (36) hide show
  1. package/.output/cli.js +274 -140
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-BLS7CVvA.js → CompareDrawer-Dv423tJJ.js} +1 -1
  4. package/.output/public/assets/{ProxyViewerContainer-BFZ1WG07.js → ProxyViewerContainer-6mRRv0F1.js} +35 -35
  5. package/.output/public/assets/{ReplayDialog-DIGF807X.js → ReplayDialog-Bury80WZ.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-C3Nlvp9P.js → RequestAnatomy-Bh07p6Yj.js} +1 -1
  7. package/.output/public/assets/{ResponseView-BbZywqHe.js → ResponseView-C8_ZLXNd.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-Dk27PfPn.js → StreamingChunkSequence-BKvT1lyU.js} +1 -1
  9. package/.output/public/assets/_sessionId-DnxfC3cB.js +1 -0
  10. package/.output/public/assets/index-B1naZUTb.js +1 -0
  11. package/.output/public/assets/index-CmtfjQPv.css +1 -0
  12. package/.output/public/assets/{main-BxHXMs6B.js → main-D9YPHKL0.js} +2 -2
  13. package/.output/server/{_sessionId-D5HNwb9A.mjs → _sessionId-D2z4VD7h.mjs} +3 -3
  14. package/.output/server/_ssr/{CompareDrawer-QNk1twf8.mjs → CompareDrawer-DNDkVh7T.mjs} +3 -3
  15. package/.output/server/_ssr/{ProxyViewerContainer-DGhc2le_.mjs → ProxyViewerContainer-CI-fjnaf.mjs} +284 -217
  16. package/.output/server/_ssr/{ReplayDialog-D8wg4VNL.mjs → ReplayDialog-Dx4um0uS.mjs} +4 -4
  17. package/.output/server/_ssr/{RequestAnatomy-BtJHKVl2.mjs → RequestAnatomy-CKPBN_PB.mjs} +3 -3
  18. package/.output/server/_ssr/{ResponseView-CndDItiU.mjs → ResponseView-3wkt24-z.mjs} +3 -3
  19. package/.output/server/_ssr/{StreamingChunkSequence-D5Ywpbr7.mjs → StreamingChunkSequence-CSK53g5a.mjs} +3 -3
  20. package/.output/server/_ssr/{index-DtcUTHAX.mjs → index-1uaJ_KuG.mjs} +3 -3
  21. package/.output/server/_ssr/index.mjs +2 -2
  22. package/.output/server/_ssr/{router-C0apgqfC.mjs → router-KEX70DxH.mjs} +17 -8
  23. package/.output/server/{_tanstack-start-manifest_v-BoTPaB-V.mjs → _tanstack-start-manifest_v-VhueDt-w.mjs} +1 -1
  24. package/.output/server/index.mjs +63 -63
  25. package/README.md +10 -6
  26. package/package.json +1 -1
  27. package/src/cli/detect-tools.ts +2 -1
  28. package/src/cli/onboard.ts +334 -167
  29. package/src/components/providers/SettingsDialog.tsx +47 -0
  30. package/src/components/proxy-viewer/ConversationGroup.tsx +31 -9
  31. package/src/components/proxy-viewer/ConversationHeader.tsx +97 -37
  32. package/src/components/proxy-viewer/LogEntryHeader.tsx +3 -12
  33. package/src/mcp/server.ts +18 -4
  34. package/.output/public/assets/_sessionId-BHpX8AVf.js +0 -1
  35. package/.output/public/assets/index-BXHM9OMb.js +0 -1
  36. package/.output/public/assets/index-DP4NzCZ5.css +0 -1
@@ -331,6 +331,18 @@ function OnboardingSettingsTab(): JSX.Element {
331
331
  },
332
332
  { id: "opencode-verify", label: "OpenCode verify", value: "opencode mcp list" },
333
333
  { id: "opencode-proxy", label: "OpenCode proxy", value: `LLM_BASE_URL=${origin}/proxy` },
334
+ {
335
+ id: "mimo-config",
336
+ label: "MiMo Code config",
337
+ value: "agent-inspector onboard --mimo-only",
338
+ },
339
+ {
340
+ id: "mimo-status",
341
+ label: "MiMo Code status",
342
+ value: "agent-inspector onboard --status --mimo-only",
343
+ },
344
+ { id: "mimo-verify", label: "MiMo Code verify", value: "mimo mcp list" },
345
+ { id: "mimo-proxy", label: "MiMo Code proxy", value: `OPENAI_BASE_URL=${origin}/proxy` },
334
346
  { id: "mcp", label: "MCP URL", value: `${origin}/api/mcp` },
335
347
  { id: "proxy", label: "Proxy URL", value: `${origin}/proxy` },
336
348
  { id: "anthropic", label: "Anthropic base", value: `ANTHROPIC_BASE_URL=${origin}/proxy` },
@@ -402,6 +414,24 @@ function OnboardingSettingsTab(): JSX.Element {
402
414
  .
403
415
  </span>
404
416
  </div>
417
+ <div className="flex items-start gap-2">
418
+ <Check className="mt-0.5 size-3.5 text-emerald-500" />
419
+ <span>
420
+ MiMo Code onboarding safely merges{" "}
421
+ <code className="rounded bg-muted px-1 py-0.5 font-mono text-[11px] text-foreground">
422
+ mcp.agent-inspector
423
+ </code>{" "}
424
+ into{" "}
425
+ <code className="rounded bg-muted px-1 py-0.5 font-mono text-[11px] text-foreground">
426
+ mimocode.jsonc
427
+ </code>
428
+ , then route OpenAI-format traffic with{" "}
429
+ <code className="rounded bg-muted px-1 py-0.5 font-mono text-[11px] text-foreground">
430
+ OPENAI_BASE_URL
431
+ </code>
432
+ .
433
+ </span>
434
+ </div>
405
435
  <div className="flex items-start gap-2">
406
436
  <Check className="mt-0.5 size-3.5 text-emerald-500" />
407
437
  <span>
@@ -498,6 +528,23 @@ function McpSettingsTab(): JSX.Element {
498
528
  2,
499
529
  ),
500
530
  },
531
+ {
532
+ id: "mimo-local",
533
+ label: "MiMo Code local MCP",
534
+ value: JSON.stringify(
535
+ {
536
+ mcp: {
537
+ "agent-inspector": {
538
+ type: "local",
539
+ command: ["agent-inspector-mcp", "stdio", "--url", endpoint],
540
+ enabled: true,
541
+ },
542
+ },
543
+ },
544
+ null,
545
+ 2,
546
+ ),
547
+ },
501
548
  {
502
549
  id: "codex-toml",
503
550
  label: "Codex TOML",
@@ -2,12 +2,8 @@ import { useState, memo, useCallback, useEffect, useMemo } from "react";
2
2
  import type { JSX } from "react";
3
3
  import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
4
4
  import type { CapturedLog } from "../../contracts";
5
- import {
6
- ConversationHeader,
7
- getGroupApiFormat,
8
- hasMixedApiFormat,
9
- type ConversationGroupData,
10
- } from "./ConversationHeader";
5
+ import { ConversationHeader, type ConversationGroupData } from "./ConversationHeader";
6
+ import { detectProvider } from "../providers/ProviderLogo";
11
7
  import { TurnGroup } from "./TurnGroup";
12
8
  import { AgentTraceSummary } from "./AgentTraceSummary";
13
9
  import type { CacheTrendEntry } from "./cacheTrend";
@@ -56,6 +52,31 @@ function computeStats(logs: CapturedLog[]): {
56
52
  return { totalInputTokens: totalInput, totalOutputTokens: totalOutput };
57
53
  }
58
54
 
55
+ /** Bucket each log's model under its detected provider and return one entry
56
+ * per provider with the deduplicated model names. Used by the session
57
+ * header to render one logo per provider used in the session. */
58
+ function collectProviders(logs: CapturedLog[]): {
59
+ provider: import("../providers/ProviderLogo").Provider;
60
+ models: string[];
61
+ }[] {
62
+ const buckets = new Map<import("../providers/ProviderLogo").Provider, Set<string>>();
63
+ for (const log of logs) {
64
+ if (log.model === null) continue;
65
+ const provider = detectProvider(log.model);
66
+ if (provider === "unknown") continue;
67
+ let models = buckets.get(provider);
68
+ if (models === undefined) {
69
+ models = new Set();
70
+ buckets.set(provider, models);
71
+ }
72
+ models.add(log.model);
73
+ }
74
+ return [...buckets.entries()].map(([provider, models]) => ({
75
+ provider,
76
+ models: [...models],
77
+ }));
78
+ }
79
+
59
80
  export const ConversationGroup = memo(function ({
60
81
  group,
61
82
  viewMode = "simple",
@@ -84,9 +105,9 @@ export const ConversationGroup = memo(function ({
84
105
  [onExpandedChange],
85
106
  );
86
107
  const stats = useMemo(() => computeStats(group.logs), [group.logs]);
108
+ const providers = useMemo(() => collectProviders(group.logs), [group.logs]);
87
109
  const startTime = group.logs[0]?.timestamp ?? new Date().toISOString();
88
110
  const endTime = group.logs[group.logs.length - 1]?.timestamp ?? new Date().toISOString();
89
- const mixed = hasMixedApiFormat(group.logs);
90
111
  const isLoading = group.logs.some((log) => log.responseStatus === null);
91
112
  const showTraceRollupMetrics = standalone && !hasPinnedSessionContext;
92
113
 
@@ -118,12 +139,13 @@ export const ConversationGroup = memo(function ({
118
139
  totalCalls={group.logs.length}
119
140
  totalInputTokens={stats.totalInputTokens}
120
141
  totalOutputTokens={stats.totalOutputTokens}
121
- apiFormat={getGroupApiFormat(group.logs)}
122
142
  expanded={expanded}
123
143
  onToggle={() => setExpanded(!expanded)}
124
- hideApiFormat={mixed}
144
+ providers={providers}
125
145
  isLoading={isLoading}
126
146
  userAgent={group.logs[0]?.userAgent ?? null}
147
+ clientPid={group.logs[0]?.clientPid ?? null}
148
+ clientProjectFolder={group.logs[0]?.clientProjectFolder ?? null}
127
149
  timeDisplayFormat={timeDisplayFormat}
128
150
  onClear={() => onClearGroup(group.logs.map((l) => l.id))}
129
151
  />
@@ -16,16 +16,11 @@ import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
16
16
  import { formatTimestampRange } from "../../lib/timeDisplay";
17
17
  import { cn, formatTokens } from "../../lib/utils";
18
18
  import type { CapturedLog } from "../../contracts";
19
- import { Badge } from "../ui/badge";
20
19
  import { ConfirmDialog } from "../ui/confirm-dialog";
20
+ import { ProviderLogo, type Provider } from "../providers/ProviderLogo";
21
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip";
21
22
  import { resolveLogFormat } from "./log-formats";
22
23
 
23
- const API_FORMAT_LABELS: Record<"anthropic" | "openai" | "unknown", string> = {
24
- anthropic: "Anthropic",
25
- openai: "OpenAI",
26
- unknown: "Unknown",
27
- };
28
-
29
24
  export type ConversationHeaderProps = {
30
25
  conversationId: string;
31
26
  startTime: string;
@@ -33,17 +28,28 @@ export type ConversationHeaderProps = {
33
28
  totalCalls: number;
34
29
  totalInputTokens: number;
35
30
  totalOutputTokens: number;
36
- apiFormat: "anthropic" | "openai" | "unknown";
37
31
  expanded: boolean;
38
32
  onToggle: () => void;
39
- /** Hide the API format badge on the header (used when the group contains
40
- * mixed formats - the per-log badges are shown instead). */
41
- hideApiFormat?: boolean;
33
+ /** Unique providers whose models appear in this group, with their distinct
34
+ * model names. Rendered as a row of provider logos next to the header —
35
+ * one per provider so model-switching inside a single session is visible
36
+ * at a glance. `undefined` hides the strip (default). */
37
+ providers?: { provider: Provider; models: string[] }[];
42
38
  /** When true and the group is collapsed, show a spinner instead of the
43
39
  * expand chevron to indicate an in-flight request inside the group. */
44
40
  isLoading?: boolean;
45
41
  /** User-Agent string from the first log in the group. */
46
42
  userAgent?: string | null;
43
+ /** Process ID of the client that issued the first log in the group, used
44
+ * to render the session identifier. When `conversationId` is a real
45
+ * session id this is shown as a secondary pill; when `conversationId` is
46
+ * a fallback (no sessionId was emitted by the client, e.g. `PID:123|/cwd`)
47
+ * the PID is shown as the primary identifier instead. */
48
+ clientPid?: number | null;
49
+ /** Project folder of the client that issued the first log in the group.
50
+ * Only shown as a secondary label when the session lacks a real
51
+ * `sessionId` (the same fallback path used for `clientPid`). */
52
+ clientProjectFolder?: string | null;
47
53
  /** Controls whether timestamps render as compact local time or full ISO strings. */
48
54
  timeDisplayFormat: TimeDisplayFormat;
49
55
  /** Clear all logs in this group. After confirmation the parent removes them
@@ -58,12 +64,13 @@ export function ConversationHeader({
58
64
  totalCalls,
59
65
  totalInputTokens,
60
66
  totalOutputTokens,
61
- apiFormat,
62
67
  expanded,
63
68
  onToggle,
64
- hideApiFormat = false,
69
+ providers,
65
70
  isLoading = false,
66
71
  userAgent,
72
+ clientPid,
73
+ clientProjectFolder,
67
74
  timeDisplayFormat,
68
75
  onClear,
69
76
  }: ConversationHeaderProps): JSX.Element {
@@ -114,17 +121,59 @@ export function ConversationHeader({
114
121
  <ChevronRight className="size-4 text-muted-foreground shrink-0" />
115
122
  )}
116
123
 
117
- {/* Conversation ID */}
118
- <span
119
- className="text-purple-400/90 font-mono text-xs font-semibold shrink-0"
120
- title={conversationId}
121
- >
122
- {conversationId.startsWith("PID:") || conversationId.includes("|")
123
- ? conversationId
124
- : conversationId.length > 24
125
- ? conversationId.slice(0, 12) + "..." + conversationId.slice(-12)
126
- : conversationId}
127
- </span>
124
+ {/* Session identifier.
125
+ - Real session id (e.g. `sess_abc123...`) → render the id with the
126
+ standard ellipsis truncation; show `PID <n>` as a secondary pill
127
+ when we know the originating process.
128
+ - Fallback id (no sessionId was emitted) → the conversationId is
129
+ already a synthesized `PID:<n>|<folder>` marker; render the PID
130
+ as the primary identifier (purple, same weight as a real id)
131
+ and the project folder as a secondary muted label. */}
132
+ {conversationId.startsWith("PID:") || conversationId.includes("|") ? (
133
+ <>
134
+ {clientPid !== null && clientPid !== undefined ? (
135
+ <span
136
+ className="text-purple-400/90 font-mono text-xs font-semibold shrink-0"
137
+ title={`PID ${clientPid}`}
138
+ >
139
+ PID {clientPid}
140
+ </span>
141
+ ) : (
142
+ <span className="text-purple-400/90 font-mono text-xs font-semibold shrink-0">
143
+ {conversationId}
144
+ </span>
145
+ )}
146
+ {clientProjectFolder !== null &&
147
+ clientProjectFolder !== undefined &&
148
+ clientProjectFolder !== "" && (
149
+ <span
150
+ className="text-muted-foreground text-xs font-mono truncate max-w-[200px] shrink-0"
151
+ title={clientProjectFolder}
152
+ >
153
+ {clientProjectFolder}
154
+ </span>
155
+ )}
156
+ </>
157
+ ) : (
158
+ <>
159
+ <span
160
+ className="text-purple-400/90 font-mono text-xs font-semibold shrink-0"
161
+ title={conversationId}
162
+ >
163
+ {conversationId.length > 24
164
+ ? conversationId.slice(0, 12) + "..." + conversationId.slice(-12)
165
+ : conversationId}
166
+ </span>
167
+ {clientPid !== null && clientPid !== undefined && (
168
+ <span
169
+ className="text-muted-foreground text-xs font-mono tabular-nums shrink-0 px-1.5 py-0.5 rounded border border-border bg-muted/40"
170
+ title={`Client process ID ${clientPid}`}
171
+ >
172
+ PID {clientPid}
173
+ </span>
174
+ )}
175
+ </>
176
+ )}
128
177
 
129
178
  {/* User-Agent */}
130
179
  {userAgent !== null && userAgent !== undefined && userAgent !== "" && (
@@ -137,19 +186,30 @@ export function ConversationHeader({
137
186
  </span>
138
187
  )}
139
188
 
140
- {/* API Format Badge */}
141
- {!hideApiFormat && (
142
- <Badge
143
- variant="outline"
144
- className={cn(
145
- "text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
146
- apiFormat === "openai" && "border-blue-500/40 text-blue-400",
147
- apiFormat === "anthropic" && "border-orange-500/40 text-orange-400",
148
- apiFormat === "unknown" && "border-muted text-muted-foreground",
149
- )}
150
- >
151
- {API_FORMAT_LABELS[apiFormat]}
152
- </Badge>
189
+ {/* Provider logos one per unique provider used in this session. When
190
+ a session switched models mid-stream (e.g. claude-3-5 → deepseek-v4)
191
+ multiple logos appear side-by-side; tooltip lists every distinct
192
+ model name. */}
193
+ {providers !== undefined && providers.length > 0 && (
194
+ <TooltipProvider delayDuration={150}>
195
+ <span
196
+ className="flex items-center gap-0.5 shrink-0"
197
+ aria-label="Models used in this session"
198
+ >
199
+ {providers.map(({ provider, models }) => (
200
+ <Tooltip key={provider}>
201
+ <TooltipTrigger asChild>
202
+ <span className="shrink-0">
203
+ <ProviderLogo provider={provider} className="size-4" />
204
+ </span>
205
+ </TooltipTrigger>
206
+ <TooltipContent>
207
+ {models.length === 1 ? models[0] : models.join(", ")}
208
+ </TooltipContent>
209
+ </Tooltip>
210
+ ))}
211
+ </span>
212
+ </TooltipProvider>
153
213
  )}
154
214
 
155
215
  {/* Time range */}
@@ -30,7 +30,6 @@ import { cn, formatTokens, getStatusCategory, type StatusCategory } from "../../
30
30
  import type { CapturedLog } from "../../contracts";
31
31
  import { Badge } from "../ui/badge";
32
32
  import { Button } from "../ui/button";
33
- import { ProviderLogo, detectProvider } from "../providers/ProviderLogo";
34
33
  import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
35
34
  import type { CacheTrend } from "./cacheTrend";
36
35
 
@@ -199,17 +198,9 @@ export const LogEntryHeader = memo(function ({
199
198
  </span>
200
199
  </span>
201
200
 
202
- {/* Model logo icon only, model name in tooltip */}
203
- {log.model !== null && (
204
- <Tooltip>
205
- <TooltipTrigger asChild>
206
- <span className="shrink-0">
207
- <ProviderLogo provider={detectProvider(log.model)} className="size-4" />
208
- </span>
209
- </TooltipTrigger>
210
- <TooltipContent>{log.model}</TooltipContent>
211
- </Tooltip>
212
- )}
201
+ {/* Model logo moved to the session/conversation header per-log rows
202
+ no longer render the logo (sessions with many logs would show the
203
+ same provider logo dozens of times). */}
213
204
 
214
205
  {/* Response Status — only shown for non-200 or pending. Each category
215
206
  carries a distinct icon in addition to color so the meaning is
package/src/mcp/server.ts CHANGED
@@ -316,6 +316,15 @@ function mcpConfig(): Record<string, unknown> {
316
316
  },
317
317
  },
318
318
  },
319
+ mimoCodeLocal: {
320
+ mcp: {
321
+ "agent-inspector": {
322
+ type: "local",
323
+ command: ["agent-inspector-mcp", "stdio", "--url", endpoint],
324
+ enabled: true,
325
+ },
326
+ },
327
+ },
319
328
  networkNote:
320
329
  "If the MCP client runs in another container or host, replace localhost with an address reachable from that client. Configure only one transport for the same Inspector instance.",
321
330
  };
@@ -739,7 +748,11 @@ function registerTools(server: McpServer): void {
739
748
  title: z.string().min(1).optional().describe("Human-readable run title."),
740
749
  task: z.string().nullable().optional().describe("Task statement or benchmark case."),
741
750
  project: z.string().nullable().optional().describe("Project or repository under test."),
742
- agent: z.string().nullable().optional().describe("Coding agent name, e.g. Codex/OpenCode."),
751
+ agent: z
752
+ .string()
753
+ .nullable()
754
+ .optional()
755
+ .describe("Coding agent name, e.g. Codex/OpenCode/MiMo Code."),
743
756
  status: InspectorRunStatusSchema.optional().describe("Initial run status."),
744
757
  tags: z.array(z.string()).optional().describe("Free-form run labels."),
745
758
  metadata: z.record(z.string(), JsonValueSchema).optional().describe("JSON metadata."),
@@ -860,7 +873,7 @@ function registerTools(server: McpServer): void {
860
873
  {
861
874
  title: "Create or declare an Inspector group",
862
875
  description:
863
- "Creates an evaluation/batch group that external systems can use to aggregate multiple OpenCode/Codex sessions across models such as MiniMax, DeepSeek, or GLM.",
876
+ "Creates an evaluation/batch group that external systems can use to aggregate multiple Codex, OpenCode, or MiMo Code sessions across models such as MiniMax, DeepSeek, or GLM.",
864
877
  inputSchema: z.object({
865
878
  groupId: z.string().min(1).optional().describe("Stable group id. Generated when omitted."),
866
879
  title: z.string().min(1).optional().describe("Human-readable group title."),
@@ -922,7 +935,7 @@ function registerTools(server: McpServer): void {
922
935
  {
923
936
  title: "Attach a session to an Inspector group",
924
937
  description:
925
- "Adds or updates one group member. Use it after each OpenCode process discovers its session id, or while an evaluation matrix is running to attach model/provider labels.",
938
+ "Adds or updates one group member. Use it after each coding-agent process discovers its session id, or while an evaluation matrix is running to attach model/provider labels.",
926
939
  inputSchema: AddInspectorGroupSessionInputSchema.extend({
927
940
  groupId: z.string().min(1).describe("The Inspector group id."),
928
941
  }),
@@ -1204,7 +1217,8 @@ function registerResources(server: McpServer): void {
1204
1217
  "inspector://mcp/config",
1205
1218
  {
1206
1219
  title: "Inspector MCP client config",
1207
- description: "Streamable HTTP, stdio bridge, and OpenCode MCP configuration snippets.",
1220
+ description:
1221
+ "Streamable HTTP, stdio bridge, OpenCode, and MiMo Code MCP configuration snippets.",
1208
1222
  mimeType: "application/json",
1209
1223
  },
1210
1224
  (uri) => jsonResource(uri, mcpConfig()),
@@ -1 +0,0 @@
1
- import{R as s,j as e}from"./main-BxHXMs6B.js";import{P as i}from"./ProxyViewerContainer-BFZ1WG07.js";function t(){const{sessionId:o}=s.useParams();return e.jsx(i,{initialSessionId:o},o)}export{t as component};
@@ -1 +0,0 @@
1
- import{P as o}from"./ProxyViewerContainer-BFZ1WG07.js";import"./main-BxHXMs6B.js";const r=o;export{r as component};