proofscan 0.10.62 → 0.11.1

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 (199) hide show
  1. package/README.ja.md +1 -0
  2. package/README.md +2 -0
  3. package/dist/a2a/agent-card.d.ts +2 -0
  4. package/dist/a2a/agent-card.d.ts.map +1 -1
  5. package/dist/a2a/agent-card.js +2 -2
  6. package/dist/a2a/agent-card.js.map +1 -1
  7. package/dist/a2a/client.d.ts +74 -12
  8. package/dist/a2a/client.d.ts.map +1 -1
  9. package/dist/a2a/client.js +228 -29
  10. package/dist/a2a/client.js.map +1 -1
  11. package/dist/a2a/normalizer.d.ts +4 -0
  12. package/dist/a2a/normalizer.d.ts.map +1 -1
  13. package/dist/a2a/normalizer.js +7 -4
  14. package/dist/a2a/normalizer.js.map +1 -1
  15. package/dist/a2a/session-manager.d.ts +81 -0
  16. package/dist/a2a/session-manager.d.ts.map +1 -0
  17. package/dist/a2a/session-manager.js +176 -0
  18. package/dist/a2a/session-manager.js.map +1 -0
  19. package/dist/a2a/types.d.ts +60 -0
  20. package/dist/a2a/types.d.ts.map +1 -1
  21. package/dist/cli.d.ts +2 -1
  22. package/dist/cli.d.ts.map +1 -1
  23. package/dist/cli.js +6 -3
  24. package/dist/cli.js.map +1 -1
  25. package/dist/commands/agent.d.ts.map +1 -1
  26. package/dist/commands/agent.js +35 -10
  27. package/dist/commands/agent.js.map +1 -1
  28. package/dist/commands/analyze.d.ts.map +1 -1
  29. package/dist/commands/analyze.js +12 -10
  30. package/dist/commands/analyze.js.map +1 -1
  31. package/dist/commands/connectors.js +2 -2
  32. package/dist/commands/connectors.js.map +1 -1
  33. package/dist/commands/index.d.ts +1 -0
  34. package/dist/commands/index.d.ts.map +1 -1
  35. package/dist/commands/index.js +1 -0
  36. package/dist/commands/index.js.map +1 -1
  37. package/dist/commands/plans.js +1 -1
  38. package/dist/commands/plans.js.map +1 -1
  39. package/dist/commands/record.js +5 -4
  40. package/dist/commands/record.js.map +1 -1
  41. package/dist/commands/rpc.d.ts.map +1 -1
  42. package/dist/commands/rpc.js +90 -28
  43. package/dist/commands/rpc.js.map +1 -1
  44. package/dist/commands/scan.d.ts.map +1 -1
  45. package/dist/commands/scan.js +8 -10
  46. package/dist/commands/scan.js.map +1 -1
  47. package/dist/commands/secrets.d.ts.map +1 -1
  48. package/dist/commands/secrets.js +11 -10
  49. package/dist/commands/secrets.js.map +1 -1
  50. package/dist/commands/sessions.js +2 -2
  51. package/dist/commands/sessions.js.map +1 -1
  52. package/dist/commands/summary.d.ts.map +1 -1
  53. package/dist/commands/summary.js +4 -2
  54. package/dist/commands/summary.js.map +1 -1
  55. package/dist/commands/task.d.ts +14 -0
  56. package/dist/commands/task.d.ts.map +1 -0
  57. package/dist/commands/task.js +520 -0
  58. package/dist/commands/task.js.map +1 -0
  59. package/dist/db/connection.d.ts.map +1 -1
  60. package/dist/db/connection.js +68 -21
  61. package/dist/db/connection.js.map +1 -1
  62. package/dist/db/events-store.d.ts +307 -8
  63. package/dist/db/events-store.d.ts.map +1 -1
  64. package/dist/db/events-store.js +620 -26
  65. package/dist/db/events-store.js.map +1 -1
  66. package/dist/db/proofs-store.d.ts +8 -1
  67. package/dist/db/proofs-store.d.ts.map +1 -1
  68. package/dist/db/proofs-store.js +18 -8
  69. package/dist/db/proofs-store.js.map +1 -1
  70. package/dist/db/schema.d.ts +15 -3
  71. package/dist/db/schema.d.ts.map +1 -1
  72. package/dist/db/schema.js +150 -5
  73. package/dist/db/schema.js.map +1 -1
  74. package/dist/db/tool-analysis.d.ts +15 -3
  75. package/dist/db/tool-analysis.d.ts.map +1 -1
  76. package/dist/db/tool-analysis.js +35 -17
  77. package/dist/db/tool-analysis.js.map +1 -1
  78. package/dist/db/types.d.ts +64 -1
  79. package/dist/db/types.d.ts.map +1 -1
  80. package/dist/filter/fields.d.ts.map +1 -1
  81. package/dist/filter/fields.js +22 -0
  82. package/dist/filter/fields.js.map +1 -1
  83. package/dist/filter/parser.js +2 -2
  84. package/dist/filter/parser.js.map +1 -1
  85. package/dist/filter/types.d.ts +1 -1
  86. package/dist/filter/types.d.ts.map +1 -1
  87. package/dist/html/analytics.test.ts +682 -0
  88. package/dist/html/analytics.ts +499 -0
  89. package/dist/html/browser.ts +39 -0
  90. package/dist/html/index.ts +97 -0
  91. package/dist/html/rpc-inspector.test.ts +529 -0
  92. package/dist/html/rpc-inspector.ts +1700 -0
  93. package/dist/html/templates.js +4 -4
  94. package/dist/html/templates.js.map +1 -1
  95. package/dist/html/templates.test.ts +861 -0
  96. package/dist/html/templates.ts +3163 -0
  97. package/dist/html/trace-viewer.html +624 -0
  98. package/dist/html/types.d.ts +3 -3
  99. package/dist/html/types.d.ts.map +1 -1
  100. package/dist/html/types.ts +491 -0
  101. package/dist/html/utils.ts +107 -0
  102. package/dist/monitor/data/connectors.d.ts.map +1 -1
  103. package/dist/monitor/data/connectors.js +113 -8
  104. package/dist/monitor/data/connectors.js.map +1 -1
  105. package/dist/monitor/data/popl.js +2 -2
  106. package/dist/monitor/data/popl.js.map +1 -1
  107. package/dist/monitor/routes/api.js +2 -2
  108. package/dist/monitor/routes/api.js.map +1 -1
  109. package/dist/monitor/routes/connectors.js +15 -15
  110. package/dist/monitor/routes/connectors.js.map +1 -1
  111. package/dist/monitor/routes/popl.js +5 -5
  112. package/dist/monitor/routes/popl.js.map +1 -1
  113. package/dist/monitor/templates/components.js +2 -2
  114. package/dist/monitor/templates/components.js.map +1 -1
  115. package/dist/monitor/templates/popl.js +4 -4
  116. package/dist/monitor/templates/popl.js.map +1 -1
  117. package/dist/monitor/types.d.ts +2 -2
  118. package/dist/monitor/types.d.ts.map +1 -1
  119. package/dist/proxy/bridge-utils.d.ts +41 -0
  120. package/dist/proxy/bridge-utils.d.ts.map +1 -0
  121. package/dist/proxy/bridge-utils.js +60 -0
  122. package/dist/proxy/bridge-utils.js.map +1 -0
  123. package/dist/proxy/ipc-client.d.ts.map +1 -1
  124. package/dist/proxy/ipc-client.js +1 -2
  125. package/dist/proxy/ipc-client.js.map +1 -1
  126. package/dist/proxy/ipc-server.d.ts.map +1 -1
  127. package/dist/proxy/ipc-server.js +4 -2
  128. package/dist/proxy/ipc-server.js.map +1 -1
  129. package/dist/proxy/mcp-server.d.ts +31 -0
  130. package/dist/proxy/mcp-server.d.ts.map +1 -1
  131. package/dist/proxy/mcp-server.js +393 -4
  132. package/dist/proxy/mcp-server.js.map +1 -1
  133. package/dist/proxy/types.d.ts +95 -0
  134. package/dist/proxy/types.d.ts.map +1 -1
  135. package/dist/secrets/management.d.ts +2 -2
  136. package/dist/secrets/management.d.ts.map +1 -1
  137. package/dist/secrets/management.js +7 -7
  138. package/dist/secrets/management.js.map +1 -1
  139. package/dist/shell/completer.d.ts.map +1 -1
  140. package/dist/shell/completer.js +16 -0
  141. package/dist/shell/completer.js.map +1 -1
  142. package/dist/shell/context-applicator.d.ts.map +1 -1
  143. package/dist/shell/context-applicator.js +32 -0
  144. package/dist/shell/context-applicator.js.map +1 -1
  145. package/dist/shell/filter-mappers.d.ts +5 -1
  146. package/dist/shell/filter-mappers.d.ts.map +1 -1
  147. package/dist/shell/filter-mappers.js +12 -0
  148. package/dist/shell/filter-mappers.js.map +1 -1
  149. package/dist/shell/find-command.js +13 -13
  150. package/dist/shell/find-command.js.map +1 -1
  151. package/dist/shell/inscribe-commands.js +5 -5
  152. package/dist/shell/inscribe-commands.js.map +1 -1
  153. package/dist/shell/pager/less-pager.d.ts +1 -1
  154. package/dist/shell/pager/less-pager.d.ts.map +1 -1
  155. package/dist/shell/pager/less-pager.js +5 -2
  156. package/dist/shell/pager/less-pager.js.map +1 -1
  157. package/dist/shell/pager/more-pager.d.ts +1 -1
  158. package/dist/shell/pager/more-pager.d.ts.map +1 -1
  159. package/dist/shell/pager/more-pager.js +3 -2
  160. package/dist/shell/pager/more-pager.js.map +1 -1
  161. package/dist/shell/pager/renderer.d.ts.map +1 -1
  162. package/dist/shell/pager/renderer.js +66 -15
  163. package/dist/shell/pager/renderer.js.map +1 -1
  164. package/dist/shell/pager/types.d.ts +5 -2
  165. package/dist/shell/pager/types.d.ts.map +1 -1
  166. package/dist/shell/pager/utils.d.ts +5 -2
  167. package/dist/shell/pager/utils.d.ts.map +1 -1
  168. package/dist/shell/pager/utils.js +14 -17
  169. package/dist/shell/pager/utils.js.map +1 -1
  170. package/dist/shell/pipeline-types.d.ts +12 -4
  171. package/dist/shell/pipeline-types.d.ts.map +1 -1
  172. package/dist/shell/ref-commands.js +7 -7
  173. package/dist/shell/ref-commands.js.map +1 -1
  174. package/dist/shell/ref-resolver.d.ts +15 -15
  175. package/dist/shell/ref-resolver.d.ts.map +1 -1
  176. package/dist/shell/ref-resolver.js +34 -20
  177. package/dist/shell/ref-resolver.js.map +1 -1
  178. package/dist/shell/repl.d.ts +25 -0
  179. package/dist/shell/repl.d.ts.map +1 -1
  180. package/dist/shell/repl.js +285 -51
  181. package/dist/shell/repl.js.map +1 -1
  182. package/dist/shell/router-commands.d.ts +30 -0
  183. package/dist/shell/router-commands.d.ts.map +1 -1
  184. package/dist/shell/router-commands.js +1011 -62
  185. package/dist/shell/router-commands.js.map +1 -1
  186. package/dist/shell/selector.d.ts +1 -1
  187. package/dist/shell/selector.d.ts.map +1 -1
  188. package/dist/shell/selector.js +1 -1
  189. package/dist/shell/selector.js.map +1 -1
  190. package/dist/shell/types.d.ts.map +1 -1
  191. package/dist/shell/types.js +3 -1
  192. package/dist/shell/types.js.map +1 -1
  193. package/dist/shell/where-command.d.ts.map +1 -1
  194. package/dist/shell/where-command.js +19 -3
  195. package/dist/shell/where-command.js.map +1 -1
  196. package/dist/utils/output.d.ts.map +1 -1
  197. package/dist/utils/output.js +7 -1
  198. package/dist/utils/output.js.map +1 -1
  199. package/package.json +2 -2
@@ -0,0 +1,499 @@
1
+ /**
2
+ * Analytics computation for Connector HTML reports (Phase 5.2)
3
+ *
4
+ * Computes KPIs, heatmap, latency histogram, and top tools
5
+ * from session report data.
6
+ */
7
+
8
+ import type {
9
+ HtmlSessionReportV1,
10
+ SessionRpcDetail,
11
+ HtmlConnectorAnalyticsV1,
12
+ HtmlConnectorKpis,
13
+ HtmlHeatmapData,
14
+ HtmlHeatmapCell,
15
+ HtmlLatencyHistogram,
16
+ HtmlLatencyBucket,
17
+ HtmlMethodLatencyData,
18
+ HtmlMethodLatencyEntry,
19
+ HtmlTopToolsData,
20
+ HtmlTopTool,
21
+ HtmlMethodDistribution,
22
+ HtmlMethodSlice,
23
+ } from './types.js';
24
+
25
+ // ============================================================================
26
+ // Constants
27
+ // ============================================================================
28
+
29
+ /**
30
+ * Minimum sample size for P95 calculation.
31
+ * Below this threshold, P95 returns null to avoid statistical noise.
32
+ */
33
+ export const P95_MIN_SAMPLES = 20;
34
+
35
+ /**
36
+ * Fixed latency histogram buckets.
37
+ * to_ms: null means +infinity (for "1000+" bucket)
38
+ */
39
+ export const LATENCY_BUCKETS = [
40
+ { label: '0-10', from_ms: 0, to_ms: 10 },
41
+ { label: '10-25', from_ms: 10, to_ms: 25 },
42
+ { label: '25-50', from_ms: 25, to_ms: 50 },
43
+ { label: '50-100', from_ms: 50, to_ms: 100 },
44
+ { label: '100-250', from_ms: 100, to_ms: 250 },
45
+ { label: '250-500', from_ms: 250, to_ms: 500 },
46
+ { label: '500-1000', from_ms: 500, to_ms: 1000 },
47
+ { label: '1000+', from_ms: 1000, to_ms: null }, // null = +infinity
48
+ ] as const;
49
+
50
+ // ============================================================================
51
+ // Main Entry Point
52
+ // ============================================================================
53
+
54
+ /**
55
+ * Compute all analytics for a connector report.
56
+ */
57
+ export function computeConnectorAnalytics(args: {
58
+ sessionReports: Record<string, HtmlSessionReportV1>;
59
+ sessionsTotal: number;
60
+ sessionsDisplayed: number;
61
+ }): HtmlConnectorAnalyticsV1 {
62
+ const { sessionReports, sessionsTotal, sessionsDisplayed } = args;
63
+
64
+ // Collect all RPCs from all sessions
65
+ const allRpcs: SessionRpcDetail[] = [];
66
+ for (const report of Object.values(sessionReports)) {
67
+ allRpcs.push(...report.rpcs);
68
+ }
69
+
70
+ // Compute each analytics component
71
+ const topTools = computeTopTools(allRpcs);
72
+ const kpis = computeKpis(allRpcs, sessionsTotal, sessionsDisplayed, topTools);
73
+ const heatmap = computeHeatmap(allRpcs);
74
+ const latency = computeLatencyHistogram(allRpcs);
75
+ const methodLatency = computeMethodLatency(allRpcs);
76
+ const methodDistribution = computeMethodDistribution(allRpcs);
77
+
78
+ return {
79
+ kpis,
80
+ heatmap,
81
+ latency,
82
+ method_latency: methodLatency,
83
+ top_tools: topTools,
84
+ method_distribution: methodDistribution,
85
+ };
86
+ }
87
+
88
+ // ============================================================================
89
+ // KPI Computation
90
+ // ============================================================================
91
+
92
+ /**
93
+ * Compute KPI metrics from RPCs.
94
+ */
95
+ function computeKpis(
96
+ rpcs: SessionRpcDetail[],
97
+ sessionsTotal: number,
98
+ sessionsDisplayed: number,
99
+ topTools: HtmlTopToolsData
100
+ ): HtmlConnectorKpis {
101
+ // Count by status
102
+ let okCount = 0;
103
+ let errCount = 0;
104
+ let pendingCount = 0;
105
+
106
+ // Latency collection
107
+ const latencies: number[] = [];
108
+
109
+ // Bytes
110
+ let totalRequestBytes = 0;
111
+ let totalResponseBytes = 0;
112
+
113
+ for (const rpc of rpcs) {
114
+ // Status counting
115
+ switch (rpc.status) {
116
+ case 'OK':
117
+ okCount++;
118
+ break;
119
+ case 'ERR':
120
+ errCount++;
121
+ break;
122
+ case 'PENDING':
123
+ pendingCount++;
124
+ break;
125
+ }
126
+
127
+ // Latency
128
+ if (rpc.latency_ms !== null) {
129
+ latencies.push(rpc.latency_ms);
130
+ }
131
+
132
+ // Bytes
133
+ totalRequestBytes += rpc.request.size;
134
+ totalResponseBytes += rpc.response.size;
135
+ }
136
+
137
+ // Latency statistics
138
+ const avgLatency = latencies.length > 0 ? Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length) : null;
139
+ const p95Latency = computeP95(latencies);
140
+ const maxLatency = latencies.length > 0 ? Math.max(...latencies) : null;
141
+
142
+ // Top tool from topTools
143
+ const topToolName = topTools.items.length > 0 ? topTools.items[0].name : null;
144
+ const topToolCalls = topTools.items.length > 0 ? topTools.items[0].count : null;
145
+
146
+ return {
147
+ rpc_total: rpcs.length,
148
+ rpc_ok: okCount,
149
+ rpc_err: errCount,
150
+ rpc_pending: pendingCount,
151
+ avg_latency_ms: avgLatency,
152
+ p95_latency_ms: p95Latency,
153
+ max_latency_ms: maxLatency,
154
+ total_request_bytes: totalRequestBytes,
155
+ total_response_bytes: totalResponseBytes,
156
+ sessions_total: sessionsTotal,
157
+ sessions_displayed: sessionsDisplayed,
158
+ top_tool_name: topToolName,
159
+ top_tool_calls: topToolCalls,
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Compute P95 latency using nearest-rank method.
165
+ * Returns null if sample size < P95_MIN_SAMPLES.
166
+ *
167
+ * Algorithm: k = ceil(0.95 * n), take sorted[k-1]
168
+ */
169
+ function computeP95(latencies: number[]): number | null {
170
+ const n = latencies.length;
171
+ if (n < P95_MIN_SAMPLES) {
172
+ return null;
173
+ }
174
+
175
+ const sorted = [...latencies].sort((a, b) => a - b);
176
+ const k = Math.ceil(0.95 * n);
177
+ return sorted[k - 1];
178
+ }
179
+
180
+ // ============================================================================
181
+ // Heatmap Computation
182
+ // ============================================================================
183
+
184
+ /**
185
+ * Compute activity heatmap from RPCs.
186
+ * Groups RPCs by UTC date and fills gaps with zero-count days.
187
+ *
188
+ * Note: request_ts is stored in UTC ISO8601 format (Z suffix),
189
+ * so slice(0, 10) gives the UTC date.
190
+ */
191
+ function computeHeatmap(rpcs: SessionRpcDetail[]): HtmlHeatmapData {
192
+ if (rpcs.length === 0) {
193
+ const today = new Date().toISOString().slice(0, 10);
194
+ return {
195
+ start_date: today,
196
+ end_date: today,
197
+ cells: [{ date: today, count: 0 }],
198
+ max_count: 0,
199
+ };
200
+ }
201
+
202
+ // Count RPCs per UTC date
203
+ const dateCounts = new Map<string, number>();
204
+ for (const rpc of rpcs) {
205
+ // request_ts is stored in UTC ISO8601 (Z suffix), so slice(0, 10) gives UTC date
206
+ const date = rpc.request_ts.slice(0, 10);
207
+ dateCounts.set(date, (dateCounts.get(date) || 0) + 1);
208
+ }
209
+
210
+ // Find date range
211
+ const dates = Array.from(dateCounts.keys()).sort();
212
+ const startDate = dates[0];
213
+ const endDate = dates[dates.length - 1];
214
+
215
+ // Fill gaps with zero-count days
216
+ const cells: HtmlHeatmapCell[] = [];
217
+ const current = new Date(startDate + 'T00:00:00Z');
218
+ const end = new Date(endDate + 'T00:00:00Z');
219
+
220
+ while (current <= end) {
221
+ const dateStr = current.toISOString().slice(0, 10);
222
+ cells.push({
223
+ date: dateStr,
224
+ count: dateCounts.get(dateStr) || 0,
225
+ });
226
+ current.setUTCDate(current.getUTCDate() + 1);
227
+ }
228
+
229
+ // Calculate max count for intensity scaling
230
+ const maxCount = Math.max(...cells.map((c) => c.count));
231
+
232
+ return {
233
+ start_date: startDate,
234
+ end_date: endDate,
235
+ cells,
236
+ max_count: maxCount,
237
+ };
238
+ }
239
+
240
+ // ============================================================================
241
+ // Latency Histogram Computation
242
+ // ============================================================================
243
+
244
+ /**
245
+ * Compute latency histogram using fixed buckets.
246
+ */
247
+ function computeLatencyHistogram(rpcs: SessionRpcDetail[]): HtmlLatencyHistogram {
248
+ // Initialize buckets with zero counts
249
+ const buckets: HtmlLatencyBucket[] = LATENCY_BUCKETS.map((b) => ({
250
+ label: b.label,
251
+ from_ms: b.from_ms,
252
+ to_ms: b.to_ms,
253
+ count: 0,
254
+ }));
255
+
256
+ let sampleSize = 0;
257
+ let excludedCount = 0;
258
+
259
+ for (const rpc of rpcs) {
260
+ if (rpc.latency_ms === null) {
261
+ excludedCount++;
262
+ continue;
263
+ }
264
+
265
+ sampleSize++;
266
+ const latency = rpc.latency_ms;
267
+
268
+ // Find matching bucket
269
+ // Bucket condition: from_ms <= latency < to_ms (or to_ms is null for +infinity)
270
+ for (const bucket of buckets) {
271
+ const inLowerBound = latency >= bucket.from_ms;
272
+ const inUpperBound = bucket.to_ms === null || latency < bucket.to_ms;
273
+ if (inLowerBound && inUpperBound) {
274
+ bucket.count++;
275
+ break;
276
+ }
277
+ }
278
+ }
279
+
280
+ return {
281
+ buckets,
282
+ sample_size: sampleSize,
283
+ excluded_count: excludedCount,
284
+ };
285
+ }
286
+
287
+ // ============================================================================
288
+ // Method Latency Computation
289
+ // ============================================================================
290
+
291
+ /**
292
+ * Compute method-based latency data for bar chart.
293
+ * Groups latencies by method name (e.g., initialize, tools/list, tools/call).
294
+ */
295
+ function computeMethodLatency(rpcs: SessionRpcDetail[]): HtmlMethodLatencyData {
296
+ if (rpcs.length === 0) {
297
+ return {
298
+ methods: [],
299
+ sample_size: 0,
300
+ max_latency_ms: 0,
301
+ };
302
+ }
303
+
304
+ // Group latencies by method
305
+ const methodLatencies = new Map<string, number[]>();
306
+ let sampleSize = 0;
307
+ let maxLatency = 0;
308
+
309
+ for (const rpc of rpcs) {
310
+ if (rpc.latency_ms === null) {
311
+ continue;
312
+ }
313
+
314
+ sampleSize++;
315
+ if (rpc.latency_ms > maxLatency) {
316
+ maxLatency = rpc.latency_ms;
317
+ }
318
+
319
+ const existing = methodLatencies.get(rpc.method);
320
+ if (existing) {
321
+ existing.push(rpc.latency_ms);
322
+ } else {
323
+ methodLatencies.set(rpc.method, [rpc.latency_ms]);
324
+ }
325
+ }
326
+
327
+ // Convert to entries with statistics
328
+ const methods: HtmlMethodLatencyEntry[] = [];
329
+
330
+ for (const [method, latencies] of methodLatencies.entries()) {
331
+ const sorted = [...latencies].sort((a, b) => a - b);
332
+ const count = latencies.length;
333
+ const min = sorted[0];
334
+ const max = sorted[sorted.length - 1];
335
+ const avg = Math.round(latencies.reduce((a, b) => a + b, 0) / count);
336
+ // Median (P50)
337
+ const p50Idx = Math.floor(count / 2);
338
+ const p50 = count % 2 === 0
339
+ ? Math.round((sorted[p50Idx - 1] + sorted[p50Idx]) / 2)
340
+ : sorted[p50Idx];
341
+
342
+ methods.push({
343
+ method,
344
+ latencies: sorted,
345
+ min_ms: min,
346
+ max_ms: max,
347
+ avg_ms: avg,
348
+ p50_ms: p50,
349
+ count,
350
+ });
351
+ }
352
+
353
+ // Sort by total call count descending, take top 6 methods
354
+ methods.sort((a, b) => b.count - a.count);
355
+ const topMethods = methods.slice(0, 6);
356
+
357
+ return {
358
+ methods: topMethods,
359
+ sample_size: sampleSize,
360
+ max_latency_ms: maxLatency,
361
+ };
362
+ }
363
+
364
+ // ============================================================================
365
+ // Top Tools Computation
366
+ // ============================================================================
367
+
368
+ /**
369
+ * Compute top 5 tools from tools/call RPCs.
370
+ *
371
+ * Tool name extraction fallback chain: params.name ?? params.tool ?? params.toolName
372
+ * If none found, the RPC is skipped (not counted as "unknown").
373
+ */
374
+ function computeTopTools(rpcs: SessionRpcDetail[]): HtmlTopToolsData {
375
+ const toolCounts = new Map<string, number>();
376
+ let totalCalls = 0;
377
+
378
+ for (const rpc of rpcs) {
379
+ if (rpc.method !== 'tools/call') {
380
+ continue;
381
+ }
382
+
383
+ // Extract tool name from request payload
384
+ const toolName = extractToolName(rpc.request.json);
385
+ if (toolName === null) {
386
+ continue; // Skip if no tool name found
387
+ }
388
+
389
+ toolCounts.set(toolName, (toolCounts.get(toolName) || 0) + 1);
390
+ totalCalls++;
391
+ }
392
+
393
+ // Sort by count descending and take top 5
394
+ const sorted = Array.from(toolCounts.entries())
395
+ .sort((a, b) => b[1] - a[1])
396
+ .slice(0, 5);
397
+
398
+ const items: HtmlTopTool[] = sorted.map(([name, count]) => ({
399
+ name,
400
+ count,
401
+ pct: totalCalls > 0 ? Math.round((count / totalCalls) * 100) : 0,
402
+ }));
403
+
404
+ return {
405
+ items,
406
+ total_calls: totalCalls,
407
+ };
408
+ }
409
+
410
+ /**
411
+ * Extract tool name from request JSON.
412
+ * Fallback chain: params.name ?? params.tool ?? params.toolName
413
+ */
414
+ function extractToolName(json: unknown): string | null {
415
+ if (json === null || typeof json !== 'object') {
416
+ return null;
417
+ }
418
+
419
+ const obj = json as Record<string, unknown>;
420
+ const params = obj.params;
421
+
422
+ if (params === null || typeof params !== 'object') {
423
+ return null;
424
+ }
425
+
426
+ const p = params as Record<string, unknown>;
427
+
428
+ // Fallback chain: params.name ?? params.tool ?? params.toolName
429
+ if (typeof p.name === 'string' && p.name.length > 0) {
430
+ return p.name;
431
+ }
432
+ if (typeof p.tool === 'string' && p.tool.length > 0) {
433
+ return p.tool;
434
+ }
435
+ if (typeof p.toolName === 'string' && p.toolName.length > 0) {
436
+ return p.toolName;
437
+ }
438
+
439
+ return null;
440
+ }
441
+
442
+ // ============================================================================
443
+ // Method Distribution Computation
444
+ // ============================================================================
445
+
446
+ /**
447
+ * Compute method distribution for donut chart.
448
+ * Shows top 5 methods + "Others" bucket.
449
+ */
450
+ function computeMethodDistribution(rpcs: SessionRpcDetail[]): HtmlMethodDistribution {
451
+ if (rpcs.length === 0) {
452
+ return {
453
+ slices: [],
454
+ total_rpcs: 0,
455
+ };
456
+ }
457
+
458
+ // Count by method
459
+ const methodCounts = new Map<string, number>();
460
+ for (const rpc of rpcs) {
461
+ methodCounts.set(rpc.method, (methodCounts.get(rpc.method) || 0) + 1);
462
+ }
463
+
464
+ // Sort by count descending
465
+ const sorted = Array.from(methodCounts.entries()).sort((a, b) => b[1] - a[1]);
466
+
467
+ const totalRpcs = rpcs.length;
468
+ const slices: HtmlMethodSlice[] = [];
469
+
470
+ // Take top 5
471
+ const top5 = sorted.slice(0, 5);
472
+ let top5Total = 0;
473
+
474
+ for (const [method, count] of top5) {
475
+ top5Total += count;
476
+ slices.push({
477
+ method,
478
+ count,
479
+ pct: Math.round((count / totalRpcs) * 100),
480
+ });
481
+ }
482
+
483
+ // Add "Others" if there are more than 5 methods
484
+ if (sorted.length > 5) {
485
+ const othersCount = totalRpcs - top5Total;
486
+ if (othersCount > 0) {
487
+ slices.push({
488
+ method: 'Others',
489
+ count: othersCount,
490
+ pct: Math.round((othersCount / totalRpcs) * 100),
491
+ });
492
+ }
493
+ }
494
+
495
+ return {
496
+ slices,
497
+ total_rpcs: totalRpcs,
498
+ };
499
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Browser Utility (Phase 5.0)
3
+ *
4
+ * Cross-platform browser opening functionality.
5
+ */
6
+
7
+ import { exec } from 'child_process';
8
+
9
+ /**
10
+ * Open a file in the default browser
11
+ *
12
+ * Platform-specific commands:
13
+ * - macOS: open
14
+ * - Windows: cmd /c start "" (handles paths with spaces)
15
+ * - Linux: xdg-open
16
+ */
17
+ export function openInBrowser(filePath: string): Promise<void> {
18
+ return new Promise((resolve, reject) => {
19
+ let cmd: string;
20
+
21
+ if (process.platform === 'darwin') {
22
+ cmd = `open "${filePath}"`;
23
+ } else if (process.platform === 'win32') {
24
+ // Windows: cmd /c start "" to handle paths with spaces
25
+ cmd = `cmd /c start "" "${filePath}"`;
26
+ } else {
27
+ // Linux and others
28
+ cmd = `xdg-open "${filePath}"`;
29
+ }
30
+
31
+ exec(cmd, (error) => {
32
+ if (error) {
33
+ reject(error);
34
+ } else {
35
+ resolve();
36
+ }
37
+ });
38
+ });
39
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * HTML Export Module (Phase 5.0)
3
+ *
4
+ * Exports standalone HTML reports for RPC and Session data.
5
+ */
6
+
7
+ // Types
8
+ export {
9
+ HTML_REPORT_SCHEMA_VERSION,
10
+ DEFAULT_EMBED_MAX_BYTES,
11
+ TRUNCATION_PREVIEW_LENGTH,
12
+ SHORT_ID_LENGTH,
13
+ DEFAULT_EXPORT_OPTIONS,
14
+ toRpcStatus,
15
+ getStatusSymbol,
16
+ createPayloadData,
17
+ getRpcHtmlFilename,
18
+ getSessionHtmlFilename,
19
+ getSpillFilename,
20
+ getConnectorHtmlFilename,
21
+ } from './types.js';
22
+
23
+ export type {
24
+ HtmlReportMeta,
25
+ RpcStatus,
26
+ HtmlExportOptions,
27
+ PayloadData,
28
+ HtmlRpcData,
29
+ HtmlRpcReportV1,
30
+ SessionRpcDetail,
31
+ HtmlSessionData,
32
+ HtmlSessionReportV1,
33
+ // Connector HTML types (Phase 5.1)
34
+ HtmlMcpCapabilities,
35
+ HtmlMcpServerInfo,
36
+ HtmlConnectorInfo,
37
+ HtmlConnectorSessionRow,
38
+ HtmlConnectorReportV1,
39
+ // Connector Analytics types (Phase 5.2)
40
+ HtmlConnectorKpis,
41
+ HtmlHeatmapCell,
42
+ HtmlHeatmapData,
43
+ HtmlLatencyBucket,
44
+ HtmlLatencyHistogram,
45
+ HtmlTopTool,
46
+ HtmlTopToolsData,
47
+ HtmlMethodSlice,
48
+ HtmlMethodDistribution,
49
+ HtmlConnectorAnalyticsV1,
50
+ } from './types.js';
51
+
52
+ // Templates
53
+ export {
54
+ escapeHtml,
55
+ escapeJsonForScript,
56
+ generateRpcHtml,
57
+ generateSessionHtml,
58
+ generateConnectorHtml,
59
+ renderHeatmap,
60
+ renderMethodDistribution,
61
+ } from './templates.js';
62
+
63
+ // Browser
64
+ export { openInBrowser } from './browser.js';
65
+
66
+ // Utilities
67
+ export {
68
+ getPackageVersion,
69
+ validateOutputPath,
70
+ validateEmbedMaxBytes,
71
+ ensureOutputDir,
72
+ safeWriteFile,
73
+ } from './utils.js';
74
+
75
+ // Analytics (Phase 5.2)
76
+ export { computeConnectorAnalytics, LATENCY_BUCKETS, P95_MIN_SAMPLES } from './analytics.js';
77
+
78
+ // RPC Inspector (Phase 11.5)
79
+ export {
80
+ escapeJsonPointer,
81
+ renderJsonWithPaths,
82
+ renderMethodSummary,
83
+ renderRequestSummary,
84
+ renderResponseSummary,
85
+ renderSummaryRowsHtml,
86
+ registerMethodHandler,
87
+ getRpcInspectorStyles,
88
+ getRpcInspectorScript,
89
+ } from './rpc-inspector.js';
90
+
91
+ export type {
92
+ JsonPointerTarget,
93
+ JsonPointer,
94
+ SummaryRow,
95
+ MethodSummary,
96
+ MethodSummaryHandler,
97
+ } from './types.js';