@tonyclaw/agent-inspector 2.0.28 → 2.0.30

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 (54) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-CicCP3Hb.js → CompareDrawer-BDzfRSt1.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-NtOD8Ex_.js +115 -0
  4. package/.output/public/assets/{ReplayDialog-Bc8q3ujm.js → ReplayDialog-p-5TKg75.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-Coyy3zcH.js → RequestAnatomy-G0dV8-D_.js} +1 -1
  6. package/.output/public/assets/{ResponseView-BWJzgh0c.js → ResponseView-D2wE6lD-.js} +1 -1
  7. package/.output/public/assets/{StreamingChunkSequence-BSC3uuE5.js → StreamingChunkSequence-D8JRK-j_.js} +1 -1
  8. package/.output/public/assets/_sessionId-zimzVrJE.js +1 -0
  9. package/.output/public/assets/index-Cv5vPgpr.js +1 -0
  10. package/.output/public/assets/{index-DsiKfWCp.css → index-CwlHPmgL.css} +1 -1
  11. package/.output/public/assets/{main-Brj0Gn91.js → main-XHHZsOBK.js} +2 -2
  12. package/.output/server/_libs/lucide-react.mjs +13 -13
  13. package/.output/server/{_sessionId-DDdyKVG-.mjs → _sessionId-CK9QEhkB.mjs} +3 -3
  14. package/.output/server/_ssr/{CompareDrawer-XSExe2ml.mjs → CompareDrawer-BJVPFMIx.mjs} +2 -2
  15. package/.output/server/_ssr/{ProxyViewerContainer-B5pIiBZN.mjs → ProxyViewerContainer-DhbmJ2zj.mjs} +83 -20
  16. package/.output/server/_ssr/{ReplayDialog-BOAu0ric.mjs → ReplayDialog-DXRj95Gj.mjs} +4 -4
  17. package/.output/server/_ssr/{RequestAnatomy-C8Q5lozD.mjs → RequestAnatomy-CrYJTXdJ.mjs} +2 -2
  18. package/.output/server/_ssr/{ResponseView-a_iJfPUF.mjs → ResponseView-4ESqSITr.mjs} +2 -2
  19. package/.output/server/_ssr/{StreamingChunkSequence-YMKC0j8U.mjs → StreamingChunkSequence-Czdko5n_.mjs} +2 -2
  20. package/.output/server/_ssr/{index-CkDwBPzI.mjs → index-bei0I7qL.mjs} +2 -2
  21. package/.output/server/_ssr/index.mjs +2 -2
  22. package/.output/server/_ssr/{router-DXOXdFFM.mjs → router-sQh4Gk8J.mjs} +1338 -137
  23. package/.output/server/_tanstack-start-manifest_v-C0fdSWnA.mjs +4 -0
  24. package/.output/server/index.mjs +64 -64
  25. package/README.md +24 -1
  26. package/package.json +1 -1
  27. package/src/components/ProxyViewer.tsx +3 -0
  28. package/src/components/providers/ProviderCard.tsx +32 -0
  29. package/src/components/proxy-viewer/LogEntryHeader.tsx +38 -1
  30. package/src/lib/groupContract.ts +139 -0
  31. package/src/lib/providerTestContract.ts +3 -0
  32. package/src/lib/sessionInfoContract.ts +3 -0
  33. package/src/mcp/server.ts +278 -0
  34. package/src/mcp/toolHandlers.ts +126 -0
  35. package/src/proxy/evidenceAnalysis.ts +5 -0
  36. package/src/proxy/evidenceExporter.ts +8 -3
  37. package/src/proxy/groupEvidenceExporter.ts +341 -0
  38. package/src/proxy/groupStore.ts +259 -0
  39. package/src/proxy/handler.ts +8 -1
  40. package/src/proxy/logFinalizer.ts +36 -0
  41. package/src/proxy/schemas.ts +3 -0
  42. package/src/proxy/sessionInfo.ts +3 -0
  43. package/src/proxy/store.ts +3 -0
  44. package/src/proxy/streamTiming.ts +31 -0
  45. package/src/routes/api/groups.$groupId.evidence.ts +62 -0
  46. package/src/routes/api/groups.$groupId.sessions.ts +50 -0
  47. package/src/routes/api/groups.$groupId.ts +50 -0
  48. package/src/routes/api/groups.ts +39 -0
  49. package/src/routes/api/logs.$id.replay.ts +25 -2
  50. package/src/routes/api/providers.$providerId.test.log.ts +34 -2
  51. package/.output/public/assets/ProxyViewerContainer-BccuA6p5.js +0 -115
  52. package/.output/public/assets/_sessionId-9rEF0uSE.js +0 -1
  53. package/.output/public/assets/index-B9_VaAWl.js +0 -1
  54. package/.output/server/_tanstack-start-manifest_v-DPf3uJys.mjs +0 -4
@@ -0,0 +1,4 @@
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/groups", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/runs", "/api/sessions", "/proxy/$", "/session/$sessionId", "/api/knowledge/candidates", "/api/knowledge/project-context", "/api/knowledge/search", "/api/knowledge/sessions/$sessionId/candidates"], "preloads": ["/assets/main-XHHZsOBK.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-Cv5vPgpr.js", "/assets/ProxyViewerContainer-NtOD8Ex_.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/groups": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/groups.ts", "children": ["/api/groups/$groupId"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/mcp": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/mcp.ts" }, "/api/models": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import", "/api/providers/scan"] }, "/api/runs": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/runs.ts", "children": ["/api/runs/$runId"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/proxy/$.ts" }, "/session/$sessionId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/session/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-zimzVrJE.js", "/assets/ProxyViewerContainer-NtOD8Ex_.js"] }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/config.paths.ts" }, "/api/groups/$groupId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/groups.$groupId.ts", "children": ["/api/groups/$groupId/evidence", "/api/groups/$groupId/sessions"] }, "/api/knowledge/candidates": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.ts", "children": ["/api/knowledge/candidates/$candidateId"] }, "/api/knowledge/project-context": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.project-context.ts" }, "/api/knowledge/search": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.search.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/model-metadata", "/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.import.ts" }, "/api/providers/scan": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.scan.ts" }, "/api/runs/$runId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/runs.$runId.ts", "children": ["/api/runs/$runId/evidence"] }, "/api/groups/$groupId/evidence": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/groups.$groupId.evidence.ts" }, "/api/groups/$groupId/sessions": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/groups.$groupId.sessions.ts" }, "/api/knowledge/candidates/$candidateId": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.$candidateId.ts", "children": ["/api/knowledge/candidates/$candidateId/promote"] }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/model-metadata": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.model-metadata.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.test.ts", "children": ["/api/providers/$providerId/test/log"] }, "/api/runs/$runId/evidence": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/runs.$runId.evidence.ts" }, "/api/knowledge/candidates/$candidateId/promote": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.candidates.$candidateId.promote.ts" }, "/api/knowledge/sessions/$sessionId/candidates": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/knowledge.sessions.$sessionId.candidates.ts" }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/agent-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-XHHZsOBK.js" });
2
+ export {
3
+ tsrStartManifest
4
+ };
@@ -35,103 +35,103 @@ const headers = ((m) => function headersRouteRule(event) {
35
35
  }
36
36
  });
37
37
  const assets = {
38
- "/assets/index-B9_VaAWl.js": {
38
+ "/assets/index-Cv5vPgpr.js": {
39
39
  "type": "text/javascript; charset=utf-8",
40
- "etag": '"74-wegwMejcMaCe2qDEma7y6qUh/6U"',
41
- "mtime": "2026-06-30T03:21:19.568Z",
40
+ "etag": '"74-qPu6sF/kGmsOicFgVQR8W+cHfLQ"',
41
+ "mtime": "2026-07-01T01:34:44.486Z",
42
42
  "size": 116,
43
- "path": "../public/assets/index-B9_VaAWl.js"
43
+ "path": "../public/assets/index-Cv5vPgpr.js"
44
+ },
45
+ "/assets/index-CwlHPmgL.css": {
46
+ "type": "text/css; charset=utf-8",
47
+ "etag": '"1a3f0-E2f0kOdSBi1RhjoK38+CSij/apg"',
48
+ "mtime": "2026-07-01T01:34:44.486Z",
49
+ "size": 107504,
50
+ "path": "../public/assets/index-CwlHPmgL.css"
44
51
  },
45
52
  "/assets/alibaba-TTwafVwX.svg": {
46
53
  "type": "image/svg+xml",
47
54
  "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
48
- "mtime": "2026-06-30T03:21:19.568Z",
55
+ "mtime": "2026-07-01T01:34:44.485Z",
49
56
  "size": 5915,
50
57
  "path": "../public/assets/alibaba-TTwafVwX.svg"
51
58
  },
52
- "/assets/CompareDrawer-CicCP3Hb.js": {
59
+ "/assets/CompareDrawer-BDzfRSt1.js": {
53
60
  "type": "text/javascript; charset=utf-8",
54
- "etag": '"49fe-CmOMF3QSwAcX6NWjQdoZrIdyoIw"',
55
- "mtime": "2026-06-30T03:21:19.570Z",
61
+ "etag": '"49fe-SgBlfrYSy5DyWozx9HIYT63pan8"',
62
+ "mtime": "2026-07-01T01:34:44.486Z",
56
63
  "size": 18942,
57
- "path": "../public/assets/CompareDrawer-CicCP3Hb.js"
64
+ "path": "../public/assets/CompareDrawer-BDzfRSt1.js"
58
65
  },
59
- "/assets/index-DsiKfWCp.css": {
60
- "type": "text/css; charset=utf-8",
61
- "etag": '"1a3d5-pnLJEAiyUUKlRw5OUnvwf1dCQeI"',
62
- "mtime": "2026-06-30T03:21:19.568Z",
63
- "size": 107477,
64
- "path": "../public/assets/index-DsiKfWCp.css"
66
+ "/assets/main-XHHZsOBK.js": {
67
+ "type": "text/javascript; charset=utf-8",
68
+ "etag": '"51726-1yTScfInPZoti54sKap6zxOxpvY"',
69
+ "mtime": "2026-07-01T01:34:44.486Z",
70
+ "size": 333606,
71
+ "path": "../public/assets/main-XHHZsOBK.js"
72
+ },
73
+ "/assets/qwen-CONDcHqt.png": {
74
+ "type": "image/png",
75
+ "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
76
+ "mtime": "2026-07-01T01:34:44.485Z",
77
+ "size": 357059,
78
+ "path": "../public/assets/qwen-CONDcHqt.png"
65
79
  },
66
80
  "/assets/minimax-BPMzvuL-.jpeg": {
67
81
  "type": "image/jpeg",
68
82
  "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
69
- "mtime": "2026-06-30T03:21:19.568Z",
83
+ "mtime": "2026-07-01T01:34:44.486Z",
70
84
  "size": 6918,
71
85
  "path": "../public/assets/minimax-BPMzvuL-.jpeg"
72
86
  },
73
- "/assets/qwen-CONDcHqt.png": {
74
- "type": "image/png",
75
- "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
76
- "mtime": "2026-06-30T03:21:19.568Z",
77
- "size": 357059,
78
- "path": "../public/assets/qwen-CONDcHqt.png"
87
+ "/assets/RequestAnatomy-G0dV8-D_.js": {
88
+ "type": "text/javascript; charset=utf-8",
89
+ "etag": '"4485-dLrPuUurtUkiFHTiud70zTzhUi8"',
90
+ "mtime": "2026-07-01T01:34:44.486Z",
91
+ "size": 17541,
92
+ "path": "../public/assets/RequestAnatomy-G0dV8-D_.js"
79
93
  },
80
- "/assets/ReplayDialog-Bc8q3ujm.js": {
94
+ "/assets/ReplayDialog-p-5TKg75.js": {
81
95
  "type": "text/javascript; charset=utf-8",
82
- "etag": '"318c-QS16LlsGhExlRZzuuDmYzqWLBN0"',
83
- "mtime": "2026-06-30T03:21:19.570Z",
96
+ "etag": '"318c-2IdqYt4/fxajfqXLAVb8U+TSsfM"',
97
+ "mtime": "2026-07-01T01:34:44.486Z",
84
98
  "size": 12684,
85
- "path": "../public/assets/ReplayDialog-Bc8q3ujm.js"
99
+ "path": "../public/assets/ReplayDialog-p-5TKg75.js"
100
+ },
101
+ "/assets/ResponseView-D2wE6lD-.js": {
102
+ "type": "text/javascript; charset=utf-8",
103
+ "etag": '"30d0-5TfShOzs9M0k3ghgcC/Uc1CZi5U"',
104
+ "mtime": "2026-07-01T01:34:44.486Z",
105
+ "size": 12496,
106
+ "path": "../public/assets/ResponseView-D2wE6lD-.js"
107
+ },
108
+ "/assets/StreamingChunkSequence-D8JRK-j_.js": {
109
+ "type": "text/javascript; charset=utf-8",
110
+ "etag": '"d57-jGvUqgjv7HbrCBsM6bKKdb9Ijro"',
111
+ "mtime": "2026-07-01T01:34:44.486Z",
112
+ "size": 3415,
113
+ "path": "../public/assets/StreamingChunkSequence-D8JRK-j_.js"
86
114
  },
87
115
  "/assets/zhipuai-BPNAnxo-.svg": {
88
116
  "type": "image/svg+xml",
89
117
  "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
90
- "mtime": "2026-06-30T03:21:19.568Z",
118
+ "mtime": "2026-07-01T01:34:44.485Z",
91
119
  "size": 11256,
92
120
  "path": "../public/assets/zhipuai-BPNAnxo-.svg"
93
121
  },
94
- "/assets/main-Brj0Gn91.js": {
95
- "type": "text/javascript; charset=utf-8",
96
- "etag": '"51726-mwq2VuKeQG//deWEjQSFZIL9ZWo"',
97
- "mtime": "2026-06-30T03:21:19.568Z",
98
- "size": 333606,
99
- "path": "../public/assets/main-Brj0Gn91.js"
100
- },
101
- "/assets/RequestAnatomy-Coyy3zcH.js": {
122
+ "/assets/_sessionId-zimzVrJE.js": {
102
123
  "type": "text/javascript; charset=utf-8",
103
- "etag": '"4485-KFNCPYVKcx+a4jW1uo7q7Az4T5c"',
104
- "mtime": "2026-06-30T03:21:19.570Z",
105
- "size": 17541,
106
- "path": "../public/assets/RequestAnatomy-Coyy3zcH.js"
107
- },
108
- "/assets/_sessionId-9rEF0uSE.js": {
109
- "type": "text/javascript; charset=utf-8",
110
- "etag": '"d2-+KgPaqhF/ImO1Zb9tZi0sMB8OPI"',
111
- "mtime": "2026-06-30T03:21:19.568Z",
124
+ "etag": '"d2-XtmPH9ZczbI8wqjiKa5PV3BIn0Y"',
125
+ "mtime": "2026-07-01T01:34:44.486Z",
112
126
  "size": 210,
113
- "path": "../public/assets/_sessionId-9rEF0uSE.js"
114
- },
115
- "/assets/ResponseView-BWJzgh0c.js": {
116
- "type": "text/javascript; charset=utf-8",
117
- "etag": '"30d0-XQuX3LcRTXyoZYFeRDp9J2gMpd0"',
118
- "mtime": "2026-06-30T03:21:19.570Z",
119
- "size": 12496,
120
- "path": "../public/assets/ResponseView-BWJzgh0c.js"
121
- },
122
- "/assets/StreamingChunkSequence-BSC3uuE5.js": {
123
- "type": "text/javascript; charset=utf-8",
124
- "etag": '"d57-1GnBSzcha+n1obLlcv4lwyBhPf4"',
125
- "mtime": "2026-06-30T03:21:19.570Z",
126
- "size": 3415,
127
- "path": "../public/assets/StreamingChunkSequence-BSC3uuE5.js"
127
+ "path": "../public/assets/_sessionId-zimzVrJE.js"
128
128
  },
129
- "/assets/ProxyViewerContainer-BccuA6p5.js": {
129
+ "/assets/ProxyViewerContainer-NtOD8Ex_.js": {
130
130
  "type": "text/javascript; charset=utf-8",
131
- "etag": '"af111-eqyso0QJHFS7+IdUoP/1PcawtQE"',
132
- "mtime": "2026-06-30T03:21:19.570Z",
133
- "size": 717073,
134
- "path": "../public/assets/ProxyViewerContainer-BccuA6p5.js"
131
+ "etag": '"af8bd-AtJl2HP2L1UcDbcZviRHY1WMV8U"',
132
+ "mtime": "2026-07-01T01:34:44.486Z",
133
+ "size": 719037,
134
+ "path": "../public/assets/ProxyViewerContainer-NtOD8Ex_.js"
135
135
  }
136
136
  };
137
137
  function readAsset(id) {
package/README.md CHANGED
@@ -154,7 +154,7 @@ http://localhost:25947/api/mcp
154
154
  Coding agents can use the MCP surface in three ways:
155
155
 
156
156
  - Tools: perform actions such as listing logs, reading bounded log details, testing providers,
157
- creating evaluation runs, and exporting evidence packs.
157
+ creating evaluation runs/groups, attaching sessions, and exporting evidence packs.
158
158
  - Resources: read stable evidence URIs without invoking a tool call.
159
159
  - Prompts: start common analysis/reporting tasks from reusable templates.
160
160
 
@@ -176,6 +176,24 @@ The evaluation-oriented run flow is:
176
176
  <dataDir>/evidence/<runId>/evidence.html
177
177
  ```
178
178
 
179
+ For benchmark or CI systems that spawn multiple coding-agent processes, use a group as the
180
+ outer evaluation artifact:
181
+
182
+ 1. Call `inspector_create_group` once for the evaluation batch.
183
+ 2. Start each OpenCode/Codex process through the proxy with its own model/provider.
184
+ 3. As each process discovers or reports its session id, call `inspector_add_group_session` with
185
+ `groupId`, `sessionId`, and optional `runId`, `provider`, `model`, `agent`, `label`, and
186
+ metadata.
187
+ 4. Call `inspector_update_group` to mark the whole batch `running`, `failed`, `completed`, or
188
+ `cancelled`.
189
+ 5. Call `inspector_export_group_evidence` to write an aggregate provider/model matrix under:
190
+
191
+ ```text
192
+ <dataDir>/evidence/groups/<groupId>/evidence.json
193
+ <dataDir>/evidence/groups/<groupId>/evidence.md
194
+ <dataDir>/evidence/groups/<groupId>/evidence.html
195
+ ```
196
+
179
197
  Useful MCP resource URIs:
180
198
 
181
199
  | Resource | Purpose |
@@ -187,12 +205,17 @@ Useful MCP resource URIs:
187
205
  | `inspector://failures/recent` | Read recent failed/cancelled runs and non-success evidence classifications. |
188
206
  | `inspector://runs/{runId}` | Read one declared run and its evidence paths. |
189
207
  | `inspector://evidence/{runId}` | Read the exported Markdown evidence pack for a run. |
208
+ | `inspector://groups` | List declared evaluation groups. |
209
+ | `inspector://groups/{groupId}` | Read one group, including attached session/run members and evidence paths. |
210
+ | `inspector://groups/{groupId}/sessions` | Read only the attached group member list. |
211
+ | `inspector://groups/{groupId}/evidence` | Read the exported Markdown evidence pack for a group. |
190
212
 
191
213
  Built-in prompt templates help turn captured evidence into user-facing output:
192
214
 
193
215
  - `inspector_analyze_session_failure`: explain why a session failed and cite log evidence.
194
216
  - `inspector_triage_recent_failures`: prioritize recent failed runs and suggest next actions.
195
217
  - `inspector_generate_jenkins_report`: produce a Jenkins-ready Markdown report for a run.
218
+ - `inspector_generate_group_report`: produce a provider/model evaluation report for a group.
196
219
  - `inspector_extract_repro_steps`: extract concise reproduction steps from a session.
197
220
 
198
221
  ## Jenkins And GitCode
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonyclaw/agent-inspector",
3
- "version": "2.0.28",
3
+ "version": "2.0.30",
4
4
  "type": "module",
5
5
  "description": "Agent observability and knowledge capture layer for AI coding tools.",
6
6
  "license": "MIT",
@@ -783,6 +783,9 @@ export function ProxyViewer({
783
783
  <Plus className="size-4 shrink-0 text-muted-foreground/70" aria-hidden="true" />
784
784
  <McpLogo className="size-10 shrink-0" />
785
785
  </span>
786
+ <span className="max-w-[calc(100vw-7rem)] text-sm font-semibold text-muted-foreground">
787
+ Local First. Evidence First.
788
+ </span>
786
789
  <McpReadyBadge />
787
790
  </h1>
788
791
  <div className="justify-self-end">
@@ -87,6 +87,16 @@ function hasHeaderDetails(headers: Record<string, string> | undefined): boolean
87
87
  return formatHeaders(headers) !== undefined;
88
88
  }
89
89
 
90
+ function formatElapsed(ms: number): string {
91
+ if (ms < 1000) return `${ms}ms`;
92
+ return `${(ms / 1000).toFixed(1)}s`;
93
+ }
94
+
95
+ function formatTokenRate(tokensPerSecond: number): string {
96
+ if (tokensPerSecond >= 10) return `${tokensPerSecond.toFixed(0)}/s`;
97
+ return `${tokensPerSecond.toFixed(1)}/s`;
98
+ }
99
+
90
100
  function hasDiagnosticDetails(result: TestResult): boolean {
91
101
  const debug = result.debug;
92
102
  return (
@@ -255,6 +265,7 @@ function TestStatus({ result }: { result: ProviderTestState }): JSX.Element {
255
265
  }
256
266
  if (result.success) {
257
267
  const tokenParts: ReactNode[] = [];
268
+ const timingParts: ReactNode[] = [];
258
269
  if (result.inputTokens !== undefined) {
259
270
  tokenParts.push(<span key="in">{result.inputTokens} in</span>);
260
271
  }
@@ -275,11 +286,29 @@ function TestStatus({ result }: { result: ProviderTestState }): JSX.Element {
275
286
  </span>,
276
287
  );
277
288
  }
289
+ if (result.firstChunkMs !== undefined && result.firstChunkMs !== null) {
290
+ timingParts.push(<span key="first">first {formatElapsed(result.firstChunkMs)}</span>);
291
+ }
292
+ if (result.totalStreamMs !== undefined && result.totalStreamMs !== null) {
293
+ timingParts.push(<span key="total">total {formatElapsed(result.totalStreamMs)}</span>);
294
+ }
295
+ if (
296
+ result.tokensPerSecond !== undefined &&
297
+ result.tokensPerSecond !== null &&
298
+ result.tokensPerSecond > 0
299
+ ) {
300
+ timingParts.push(<span key="rate">{formatTokenRate(result.tokensPerSecond)} tok/s</span>);
301
+ }
278
302
  const displayTokens: ReactNode[] = [];
279
303
  for (let i = 0; i < tokenParts.length; i++) {
280
304
  if (i > 0) displayTokens.push(", ");
281
305
  displayTokens.push(tokenParts[i]);
282
306
  }
307
+ const displayTiming: ReactNode[] = [];
308
+ for (let i = 0; i < timingParts.length; i++) {
309
+ if (i > 0) displayTiming.push(", ");
310
+ displayTiming.push(timingParts[i]);
311
+ }
283
312
  return (
284
313
  <TooltipProvider>
285
314
  <Tooltip>
@@ -290,6 +319,9 @@ function TestStatus({ result }: { result: ProviderTestState }): JSX.Element {
290
319
  {tokenParts.length > 0 && (
291
320
  <span className="text-muted-foreground">({displayTokens})</span>
292
321
  )}
322
+ {timingParts.length > 0 && (
323
+ <span className="hidden text-muted-foreground lg:inline">({displayTiming})</span>
324
+ )}
293
325
  </div>
294
326
  </TooltipTrigger>
295
327
  <TooltipContent>Connection test passed</TooltipContent>
@@ -11,6 +11,7 @@ import {
11
11
  Copy,
12
12
  FileDiff,
13
13
  FileTerminal,
14
+ Gauge,
14
15
  Globe,
15
16
  History,
16
17
  Loader2,
@@ -45,6 +46,11 @@ function formatElapsed(ms: number): string {
45
46
  return `${(ms / 1000).toFixed(1)}s`;
46
47
  }
47
48
 
49
+ function formatTokenRate(tokensPerSecond: number): string {
50
+ if (tokensPerSecond >= 10) return `${tokensPerSecond.toFixed(0)}/s`;
51
+ return `${tokensPerSecond.toFixed(1)}/s`;
52
+ }
53
+
48
54
  /**
49
55
  * Inline trend indicator: small arrow (green up / red down) plus the absolute
50
56
  * delta in compact form. Returns `null` when there is no trend to display.
@@ -148,6 +154,11 @@ export const LogEntryHeader = memo(function ({
148
154
  log.elapsedMs > slowResponseThresholdSeconds * 1000;
149
155
 
150
156
  const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
157
+ const firstChunkMs = log.firstChunkMs ?? null;
158
+ const tokensPerSecond = log.tokensPerSecond ?? null;
159
+ const firstChunkLabel = firstChunkMs === null ? null : formatElapsed(firstChunkMs);
160
+ const tokenRateLabel =
161
+ tokensPerSecond === null || tokensPerSecond <= 0 ? null : formatTokenRate(tokensPerSecond);
151
162
 
152
163
  return (
153
164
  <TooltipProvider>
@@ -238,11 +249,37 @@ export const LogEntryHeader = memo(function ({
238
249
  ? `Slow response: ${formatElapsed(log.elapsedMs)} exceeds ${formatElapsed(
239
250
  slowResponseThresholdSeconds * 1000,
240
251
  )}`
241
- : "Elapsed response time"}
252
+ : log.streaming
253
+ ? "Total streaming response time"
254
+ : "Elapsed response time"}
242
255
  </TooltipContent>
243
256
  </Tooltip>
244
257
  )}
245
258
 
259
+ {/* Streaming timing */}
260
+ {log.streaming && firstChunkLabel !== null && (
261
+ <Tooltip>
262
+ <TooltipTrigger asChild>
263
+ <span className="flex items-center gap-1 text-xs text-cyan-400 shrink-0">
264
+ <Radio className="size-3" />
265
+ <span className="font-mono tabular-nums">first {firstChunkLabel}</span>
266
+ </span>
267
+ </TooltipTrigger>
268
+ <TooltipContent>Time to first streaming chunk</TooltipContent>
269
+ </Tooltip>
270
+ )}
271
+ {log.streaming && tokenRateLabel !== null && (
272
+ <Tooltip>
273
+ <TooltipTrigger asChild>
274
+ <span className="flex items-center gap-1 text-xs text-emerald-400 shrink-0">
275
+ <Gauge className="size-3" />
276
+ <span className="font-mono tabular-nums">{tokenRateLabel}</span>
277
+ </span>
278
+ </TooltipTrigger>
279
+ <TooltipContent>Output token throughput after first chunk</TooltipContent>
280
+ </Tooltip>
281
+ )}
282
+
246
283
  {/* Token counts */}
247
284
  {hasTokens && (
248
285
  <span className="flex items-center gap-1 text-xs shrink-0">
@@ -0,0 +1,139 @@
1
+ import { z } from "zod";
2
+ import { JsonValueSchema } from "../proxy/schemas";
3
+ import { InspectorRunSchema, InspectorRunStatusSchema, JenkinsReportSchema } from "./runContract";
4
+ import { SessionInfoSchema, SessionTokenUsageSchema } from "./sessionInfoContract";
5
+
6
+ export const InspectorGroupStatusSchema = z.enum([
7
+ "created",
8
+ "running",
9
+ "completed",
10
+ "failed",
11
+ "cancelled",
12
+ ]);
13
+
14
+ export const InspectorGroupKindSchema = z.enum(["evaluation", "batch", "manual"]);
15
+
16
+ export const InspectorGroupEvidenceSchema = z.object({
17
+ jsonPath: z.string(),
18
+ markdownPath: z.string(),
19
+ htmlPath: z.string(),
20
+ exportedAt: z.string(),
21
+ });
22
+
23
+ export const InspectorGroupMemberSchema = z.object({
24
+ id: z.string(),
25
+ groupId: z.string(),
26
+ sessionId: z.string(),
27
+ runId: z.string().nullable(),
28
+ label: z.string().nullable(),
29
+ agent: z.string().nullable(),
30
+ provider: z.string().nullable(),
31
+ model: z.string().nullable(),
32
+ status: InspectorRunStatusSchema.nullable(),
33
+ metadata: z.record(z.string(), JsonValueSchema),
34
+ addedAt: z.string(),
35
+ updatedAt: z.string(),
36
+ });
37
+
38
+ export const InspectorGroupSchema = z.object({
39
+ id: z.string(),
40
+ title: z.string(),
41
+ kind: InspectorGroupKindSchema,
42
+ task: z.string().nullable(),
43
+ project: z.string().nullable(),
44
+ status: InspectorGroupStatusSchema,
45
+ tags: z.array(z.string()),
46
+ metadata: z.record(z.string(), JsonValueSchema),
47
+ evidence: InspectorGroupEvidenceSchema.nullable(),
48
+ members: z.array(InspectorGroupMemberSchema),
49
+ createdAt: z.string(),
50
+ updatedAt: z.string(),
51
+ });
52
+
53
+ export const CreateInspectorGroupInputSchema = z
54
+ .object({
55
+ groupId: z.string().min(1).optional(),
56
+ title: z.string().min(1).optional(),
57
+ kind: InspectorGroupKindSchema.optional(),
58
+ task: z.string().nullable().optional(),
59
+ project: z.string().nullable().optional(),
60
+ status: InspectorGroupStatusSchema.optional(),
61
+ tags: z.array(z.string()).optional(),
62
+ metadata: z.record(z.string(), JsonValueSchema).optional(),
63
+ })
64
+ .optional();
65
+
66
+ export const UpdateInspectorGroupInputSchema = z.object({
67
+ title: z.string().min(1).optional(),
68
+ kind: InspectorGroupKindSchema.optional(),
69
+ task: z.string().nullable().optional(),
70
+ project: z.string().nullable().optional(),
71
+ status: InspectorGroupStatusSchema.optional(),
72
+ tags: z.array(z.string()).optional(),
73
+ metadata: z.record(z.string(), JsonValueSchema).optional(),
74
+ });
75
+
76
+ export const AddInspectorGroupSessionInputSchema = z.object({
77
+ memberId: z.string().min(1).optional(),
78
+ sessionId: z.string().min(1),
79
+ runId: z.string().min(1).nullable().optional(),
80
+ label: z.string().min(1).nullable().optional(),
81
+ agent: z.string().min(1).nullable().optional(),
82
+ provider: z.string().min(1).nullable().optional(),
83
+ model: z.string().min(1).nullable().optional(),
84
+ status: InspectorRunStatusSchema.nullable().optional(),
85
+ metadata: z.record(z.string(), JsonValueSchema).optional(),
86
+ });
87
+
88
+ export const GroupEvidenceExportOptionsSchema = z
89
+ .object({
90
+ includeHistory: z.boolean().optional(),
91
+ latestLogLimit: z.number().int().positive().max(50).optional(),
92
+ })
93
+ .optional();
94
+
95
+ export const InspectorGroupExportMemberSchema = z.object({
96
+ member: InspectorGroupMemberSchema,
97
+ run: InspectorRunSchema.nullable(),
98
+ session: SessionInfoSchema.nullable(),
99
+ });
100
+
101
+ export const InspectorGroupEvidenceSummarySchema = z.object({
102
+ memberCount: z.number().int().nonnegative(),
103
+ sessionsWithData: z.number().int().nonnegative(),
104
+ missingSessions: z.number().int().nonnegative(),
105
+ requestCount: z.number().int().nonnegative(),
106
+ errorCount: z.number().int().nonnegative(),
107
+ tokenUsage: SessionTokenUsageSchema,
108
+ models: z.array(z.string()),
109
+ providers: z.array(z.string()),
110
+ completedMembers: z.number().int().nonnegative(),
111
+ failedMembers: z.number().int().nonnegative(),
112
+ runningMembers: z.number().int().nonnegative(),
113
+ });
114
+
115
+ export const GroupEvidenceExportResultSchema = z.object({
116
+ group: InspectorGroupSchema,
117
+ evidence: InspectorGroupEvidenceSchema,
118
+ members: z.array(InspectorGroupExportMemberSchema),
119
+ summary: InspectorGroupEvidenceSummarySchema,
120
+ jenkinsReport: JenkinsReportSchema,
121
+ });
122
+
123
+ export const InspectorGroupsListResponseSchema = z.object({
124
+ groups: z.array(InspectorGroupSchema),
125
+ });
126
+
127
+ export type InspectorGroupStatus = z.infer<typeof InspectorGroupStatusSchema>;
128
+ export type InspectorGroupKind = z.infer<typeof InspectorGroupKindSchema>;
129
+ export type InspectorGroupEvidence = z.infer<typeof InspectorGroupEvidenceSchema>;
130
+ export type InspectorGroupMember = z.infer<typeof InspectorGroupMemberSchema>;
131
+ export type InspectorGroup = z.infer<typeof InspectorGroupSchema>;
132
+ export type CreateInspectorGroupInput = z.infer<typeof CreateInspectorGroupInputSchema>;
133
+ export type UpdateInspectorGroupInput = z.infer<typeof UpdateInspectorGroupInputSchema>;
134
+ export type AddInspectorGroupSessionInput = z.infer<typeof AddInspectorGroupSessionInputSchema>;
135
+ export type GroupEvidenceExportOptions = z.infer<typeof GroupEvidenceExportOptionsSchema>;
136
+ export type InspectorGroupExportMember = z.infer<typeof InspectorGroupExportMemberSchema>;
137
+ export type InspectorGroupEvidenceSummary = z.infer<typeof InspectorGroupEvidenceSummarySchema>;
138
+ export type GroupEvidenceExportResult = z.infer<typeof GroupEvidenceExportResultSchema>;
139
+ export type InspectorGroupsListResponse = z.infer<typeof InspectorGroupsListResponseSchema>;
@@ -45,6 +45,9 @@ export const ProviderTestResultSchema = z.object({
45
45
  cacheCreationInputTokens: z.number().optional(),
46
46
  cacheReadInputTokens: z.number().optional(),
47
47
  latencyMs: z.number().optional(),
48
+ firstChunkMs: z.number().nullable().optional(),
49
+ totalStreamMs: z.number().nullable().optional(),
50
+ tokensPerSecond: z.number().nullable().optional(),
48
51
  content: z.array(ProviderTestContentSchema).optional(),
49
52
  rawResponse: z.string().optional(),
50
53
  streaming: z.boolean().optional(),
@@ -21,6 +21,9 @@ export const SessionLogSummarySchema = z.object({
21
21
  hasError: z.boolean(),
22
22
  error: z.string().nullable(),
23
23
  latencyMs: z.number().nullable(),
24
+ firstChunkMs: z.number().nullable().optional(),
25
+ totalStreamMs: z.number().nullable().optional(),
26
+ tokensPerSecond: z.number().nullable().optional(),
24
27
  tokens: z.object({
25
28
  input: z.number().nullable(),
26
29
  output: z.number().nullable(),