@wix/pathgrade 0.29.0

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 (238) hide show
  1. package/bin/pathgrade.js +2 -0
  2. package/dist/affected/anchor.d.ts +18 -0
  3. package/dist/affected/anchor.js +42 -0
  4. package/dist/affected/config.d.ts +25 -0
  5. package/dist/affected/config.js +98 -0
  6. package/dist/affected/format.d.ts +45 -0
  7. package/dist/affected/format.js +87 -0
  8. package/dist/affected/git.d.ts +33 -0
  9. package/dist/affected/git.js +68 -0
  10. package/dist/affected/glob.d.ts +17 -0
  11. package/dist/affected/glob.js +25 -0
  12. package/dist/affected/meta.d.ts +26 -0
  13. package/dist/affected/meta.js +158 -0
  14. package/dist/affected/select.d.ts +30 -0
  15. package/dist/affected/select.js +101 -0
  16. package/dist/affected/sidecar.d.ts +32 -0
  17. package/dist/affected/sidecar.js +86 -0
  18. package/dist/affected/types.d.ts +43 -0
  19. package/dist/affected/types.js +7 -0
  20. package/dist/agents/claude/ask-user-answer-store.d.ts +28 -0
  21. package/dist/agents/claude/ask-user-answer-store.js +29 -0
  22. package/dist/agents/claude/ask-user-bridge.d.ts +56 -0
  23. package/dist/agents/claude/ask-user-bridge.js +162 -0
  24. package/dist/agents/claude/sdk-message-projector.d.ts +43 -0
  25. package/dist/agents/claude/sdk-message-projector.js +208 -0
  26. package/dist/agents/claude/sdk-options.d.ts +52 -0
  27. package/dist/agents/claude/sdk-options.js +79 -0
  28. package/dist/agents/claude.d.ts +56 -0
  29. package/dist/agents/claude.js +143 -0
  30. package/dist/agents/codex-app-server/agent.d.ts +35 -0
  31. package/dist/agents/codex-app-server/agent.js +388 -0
  32. package/dist/agents/codex-app-server/fixtures/app-server-client.d.ts +19 -0
  33. package/dist/agents/codex-app-server/fixtures/app-server-client.js +104 -0
  34. package/dist/agents/codex-app-server/fixtures/run-gate.d.ts +25 -0
  35. package/dist/agents/codex-app-server/fixtures/run-gate.js +48 -0
  36. package/dist/agents/codex-app-server/protocol/ClientRequest.d.ts +27 -0
  37. package/dist/agents/codex-app-server/protocol/ClientRequest.js +7 -0
  38. package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.d.ts +9 -0
  39. package/dist/agents/codex-app-server/protocol/DynamicToolCallParams.js +4 -0
  40. package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.d.ts +6 -0
  41. package/dist/agents/codex-app-server/protocol/GrantedPermissionProfile.js +7 -0
  42. package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.d.ts +23 -0
  43. package/dist/agents/codex-app-server/protocol/McpElicitationRequestParams.js +8 -0
  44. package/dist/agents/codex-app-server/protocol/Op.d.ts +11 -0
  45. package/dist/agents/codex-app-server/protocol/Op.js +5 -0
  46. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.d.ts +10 -0
  47. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalParams.js +6 -0
  48. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.d.ts +15 -0
  49. package/dist/agents/codex-app-server/protocol/PermissionsRequestApprovalResponse.js +4 -0
  50. package/dist/agents/codex-app-server/protocol/SandboxMode.d.ts +1 -0
  51. package/dist/agents/codex-app-server/protocol/SandboxMode.js +4 -0
  52. package/dist/agents/codex-app-server/protocol/ServerRequest.d.ts +42 -0
  53. package/dist/agents/codex-app-server/protocol/ServerRequest.js +7 -0
  54. package/dist/agents/codex-app-server/protocol/ThreadStartParams.d.ts +45 -0
  55. package/dist/agents/codex-app-server/protocol/ThreadStartParams.js +10 -0
  56. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.d.ts +6 -0
  57. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputAnswer.js +4 -0
  58. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.d.ts +7 -0
  59. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputOption.js +4 -0
  60. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.d.ts +10 -0
  61. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputParams.js +4 -0
  62. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.d.ts +12 -0
  63. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputQuestion.js +4 -0
  64. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.d.ts +9 -0
  65. package/dist/agents/codex-app-server/protocol/ToolRequestUserInputResponse.js +4 -0
  66. package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.d.ts +5 -0
  67. package/dist/agents/codex-app-server/protocol/TurnCompletedNotification.js +4 -0
  68. package/dist/agents/codex-app-server/protocol/index.d.ts +16 -0
  69. package/dist/agents/codex-app-server/protocol/index.js +11 -0
  70. package/dist/agents/codex-app-server/transport.d.ts +84 -0
  71. package/dist/agents/codex-app-server/transport.js +246 -0
  72. package/dist/agents/codex-app-server/wire-translators.d.ts +19 -0
  73. package/dist/agents/codex-app-server/wire-translators.js +52 -0
  74. package/dist/agents/codex.d.ts +7 -0
  75. package/dist/agents/codex.js +164 -0
  76. package/dist/agents/cursor.d.ts +39 -0
  77. package/dist/agents/cursor.js +233 -0
  78. package/dist/agents/registry.d.ts +17 -0
  79. package/dist/agents/registry.js +27 -0
  80. package/dist/agents/transcript-agent.d.ts +14 -0
  81. package/dist/agents/transcript-agent.js +59 -0
  82. package/dist/analytics/engine.d.ts +18 -0
  83. package/dist/analytics/engine.js +66 -0
  84. package/dist/commands/affected.d.ts +31 -0
  85. package/dist/commands/affected.js +150 -0
  86. package/dist/commands/analyze.d.ts +21 -0
  87. package/dist/commands/analyze.js +167 -0
  88. package/dist/commands/init.d.ts +3 -0
  89. package/dist/commands/init.js +242 -0
  90. package/dist/commands/preview-reactions.d.ts +10 -0
  91. package/dist/commands/preview-reactions.js +88 -0
  92. package/dist/commands/preview.d.ts +5 -0
  93. package/dist/commands/preview.js +17 -0
  94. package/dist/commands/report.d.ts +39 -0
  95. package/dist/commands/report.js +108 -0
  96. package/dist/commands/run-args.d.ts +22 -0
  97. package/dist/commands/run-args.js +69 -0
  98. package/dist/commands/run-changed.d.ts +26 -0
  99. package/dist/commands/run-changed.js +138 -0
  100. package/dist/commands/validate.d.ts +13 -0
  101. package/dist/commands/validate.js +233 -0
  102. package/dist/core/mcp-mock.d.ts +3 -0
  103. package/dist/core/mcp-mock.js +23 -0
  104. package/dist/core/mcp-mock.types.d.ts +15 -0
  105. package/dist/core/mcp-mock.types.js +1 -0
  106. package/dist/core/skills.d.ts +15 -0
  107. package/dist/core/skills.js +76 -0
  108. package/dist/evals/discovery.d.ts +7 -0
  109. package/dist/evals/discovery.js +69 -0
  110. package/dist/mcp-mock-server.d.ts +1 -0
  111. package/dist/mcp-mock-server.js +116 -0
  112. package/dist/pathgrade.d.ts +10 -0
  113. package/dist/pathgrade.js +243 -0
  114. package/dist/plugin/index.d.ts +9 -0
  115. package/dist/plugin/index.js +81 -0
  116. package/dist/plugin/lifecycle.d.ts +45 -0
  117. package/dist/plugin/lifecycle.js +146 -0
  118. package/dist/plugin/reporter.d.ts +36 -0
  119. package/dist/plugin/reporter.js +275 -0
  120. package/dist/plugin/setup.d.ts +1 -0
  121. package/dist/plugin/setup.js +3 -0
  122. package/dist/providers/copy-filter.d.ts +18 -0
  123. package/dist/providers/copy-filter.js +66 -0
  124. package/dist/providers/credentials.d.ts +36 -0
  125. package/dist/providers/credentials.js +202 -0
  126. package/dist/providers/mcp-config.d.ts +34 -0
  127. package/dist/providers/mcp-config.js +55 -0
  128. package/dist/providers/sandbox-exec.d.ts +8 -0
  129. package/dist/providers/sandbox-exec.js +82 -0
  130. package/dist/providers/sandbox.d.ts +22 -0
  131. package/dist/providers/sandbox.js +158 -0
  132. package/dist/providers/sandboxed-claude-spawn.d.ts +24 -0
  133. package/dist/providers/sandboxed-claude-spawn.js +57 -0
  134. package/dist/providers/workspace.d.ts +15 -0
  135. package/dist/providers/workspace.js +73 -0
  136. package/dist/reporters/browser.d.ts +2 -0
  137. package/dist/reporters/browser.js +85 -0
  138. package/dist/reporters/cli.d.ts +2 -0
  139. package/dist/reporters/cli.js +131 -0
  140. package/dist/reporters/diagnostics.d.ts +46 -0
  141. package/dist/reporters/diagnostics.js +147 -0
  142. package/dist/reporters/github-comment.d.ts +67 -0
  143. package/dist/reporters/github-comment.js +252 -0
  144. package/dist/reporters/loader.d.ts +8 -0
  145. package/dist/reporters/loader.js +36 -0
  146. package/dist/reporters/results-path.d.ts +2 -0
  147. package/dist/reporters/results-path.js +10 -0
  148. package/dist/reporters/verbose-emitter.d.ts +69 -0
  149. package/dist/reporters/verbose-emitter.js +103 -0
  150. package/dist/sdk/agent-crash.d.ts +19 -0
  151. package/dist/sdk/agent-crash.js +20 -0
  152. package/dist/sdk/agent-resolution.d.ts +12 -0
  153. package/dist/sdk/agent-resolution.js +21 -0
  154. package/dist/sdk/agent-result-log.d.ts +19 -0
  155. package/dist/sdk/agent-result-log.js +45 -0
  156. package/dist/sdk/agent.d.ts +4 -0
  157. package/dist/sdk/agent.js +354 -0
  158. package/dist/sdk/ask-bus/bus.d.ts +21 -0
  159. package/dist/sdk/ask-bus/bus.js +129 -0
  160. package/dist/sdk/ask-bus/handler.d.ts +31 -0
  161. package/dist/sdk/ask-bus/handler.js +244 -0
  162. package/dist/sdk/ask-bus/parsers.d.ts +9 -0
  163. package/dist/sdk/ask-bus/parsers.js +30 -0
  164. package/dist/sdk/ask-bus/projection.d.ts +26 -0
  165. package/dist/sdk/ask-bus/projection.js +45 -0
  166. package/dist/sdk/ask-bus/types.d.ts +69 -0
  167. package/dist/sdk/ask-bus/types.js +1 -0
  168. package/dist/sdk/chat.d.ts +29 -0
  169. package/dist/sdk/chat.js +93 -0
  170. package/dist/sdk/conversation-window.d.ts +14 -0
  171. package/dist/sdk/conversation-window.js +38 -0
  172. package/dist/sdk/converse.d.ts +42 -0
  173. package/dist/sdk/converse.js +329 -0
  174. package/dist/sdk/eval-runtime.d.ts +10 -0
  175. package/dist/sdk/eval-runtime.js +17 -0
  176. package/dist/sdk/evaluate.d.ts +13 -0
  177. package/dist/sdk/evaluate.js +325 -0
  178. package/dist/sdk/index.d.ts +31 -0
  179. package/dist/sdk/index.js +20 -0
  180. package/dist/sdk/judge-pipeline.d.ts +7 -0
  181. package/dist/sdk/judge-pipeline.js +178 -0
  182. package/dist/sdk/judge-prompt-builder.d.ts +7 -0
  183. package/dist/sdk/judge-prompt-builder.js +76 -0
  184. package/dist/sdk/judge-tool-runner.d.ts +19 -0
  185. package/dist/sdk/judge-tool-runner.js +51 -0
  186. package/dist/sdk/judge-tool-session.d.ts +47 -0
  187. package/dist/sdk/judge-tool-session.js +213 -0
  188. package/dist/sdk/judge-tools.d.ts +29 -0
  189. package/dist/sdk/judge-tools.js +283 -0
  190. package/dist/sdk/managed-session.d.ts +50 -0
  191. package/dist/sdk/managed-session.js +110 -0
  192. package/dist/sdk/persona.d.ts +5 -0
  193. package/dist/sdk/persona.js +33 -0
  194. package/dist/sdk/reaction-loader.d.ts +4 -0
  195. package/dist/sdk/reaction-loader.js +119 -0
  196. package/dist/sdk/reaction-preview.d.ts +6 -0
  197. package/dist/sdk/reaction-preview.js +94 -0
  198. package/dist/sdk/run-scorer.d.ts +10 -0
  199. package/dist/sdk/run-scorer.js +124 -0
  200. package/dist/sdk/runtime-policy.d.ts +21 -0
  201. package/dist/sdk/runtime-policy.js +89 -0
  202. package/dist/sdk/scorer-utils.d.ts +6 -0
  203. package/dist/sdk/scorer-utils.js +39 -0
  204. package/dist/sdk/scorers.d.ts +64 -0
  205. package/dist/sdk/scorers.js +80 -0
  206. package/dist/sdk/snapshots.d.ts +41 -0
  207. package/dist/sdk/snapshots.js +148 -0
  208. package/dist/sdk/types.d.ts +416 -0
  209. package/dist/sdk/types.js +17 -0
  210. package/dist/sdk/visible-turn.d.ts +4 -0
  211. package/dist/sdk/visible-turn.js +23 -0
  212. package/dist/tool-events.d.ts +45 -0
  213. package/dist/tool-events.js +167 -0
  214. package/dist/types.d.ts +372 -0
  215. package/dist/types.js +37 -0
  216. package/dist/utils/cli.d.ts +55 -0
  217. package/dist/utils/cli.js +131 -0
  218. package/dist/utils/env.d.ts +8 -0
  219. package/dist/utils/env.js +26 -0
  220. package/dist/utils/llm-mocks.d.ts +43 -0
  221. package/dist/utils/llm-mocks.js +108 -0
  222. package/dist/utils/llm-providers/anthropic.d.ts +2 -0
  223. package/dist/utils/llm-providers/anthropic.js +132 -0
  224. package/dist/utils/llm-providers/cli.d.ts +20 -0
  225. package/dist/utils/llm-providers/cli.js +162 -0
  226. package/dist/utils/llm-providers/openai.d.ts +2 -0
  227. package/dist/utils/llm-providers/openai.js +56 -0
  228. package/dist/utils/llm-types.d.ts +107 -0
  229. package/dist/utils/llm-types.js +1 -0
  230. package/dist/utils/llm.d.ts +36 -0
  231. package/dist/utils/llm.js +209 -0
  232. package/dist/utils/shutdown.d.ts +15 -0
  233. package/dist/utils/shutdown.js +41 -0
  234. package/dist/utils/timeout.d.ts +8 -0
  235. package/dist/utils/timeout.js +32 -0
  236. package/dist/viewer.html +1178 -0
  237. package/package.json +88 -0
  238. package/templates/eval.ts.template +28 -0
@@ -0,0 +1,1178 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <title>Pathgrade</title>
8
+ <style>
9
+ /* ─── Reset & Base ──────────────────────────────────────── */
10
+ *,
11
+ *::before,
12
+ *::after {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ :root {
19
+ --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
20
+ --mono: 'SFMono-Regular', 'Menlo', 'Consolas', monospace;
21
+ --radius: 10px;
22
+ --radius-sm: 6px;
23
+ --transition: 0.2s ease;
24
+
25
+ /* Light theme (default) */
26
+ --bg: #f8f9fc;
27
+ --bg-card: #ffffff;
28
+ --bg-code: #f1f3f8;
29
+ --bg-hover: #f0f2f7;
30
+ --border: #e2e5ed;
31
+ --text: #1a1d27;
32
+ --text-secondary: #5c6370;
33
+ --text-muted: #9099a8;
34
+ --accent: #1a1d27;
35
+ --accent-hover: #333;
36
+ --green: #22c55e;
37
+ --green-bg: #ecfdf5;
38
+ --green-border: #bbf7d0;
39
+ --red: #ef4444;
40
+ --red-bg: #fef2f2;
41
+ --red-border: #fecaca;
42
+ --yellow: #f59e0b;
43
+ --yellow-bg: #fffbeb;
44
+ --purple: #6b7280;
45
+ --purple-bg: #f3f4f6;
46
+ --blue: #6b7280;
47
+ --blue-bg: #f3f4f6;
48
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
49
+ --shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.08);
50
+ }
51
+
52
+ [data-theme="dark"] {
53
+ --bg: #0c0d12;
54
+ --bg-card: #14161e;
55
+ --bg-code: #1a1d28;
56
+ --bg-hover: #1c1f2b;
57
+ --border: #262a38;
58
+ --text: #e4e7ef;
59
+ --text-secondary: #8b92a5;
60
+ --text-muted: #565d72;
61
+ --accent: #e4e7ef;
62
+ --accent-hover: #ccc;
63
+ --green: #34d87a;
64
+ --green-bg: #0c2618;
65
+ --green-border: #1a4a30;
66
+ --red: #f87171;
67
+ --red-bg: #2a1216;
68
+ --red-border: #4a2228;
69
+ --yellow: #fbbf24;
70
+ --yellow-bg: #261f0c;
71
+ --purple: #9ca3af;
72
+ --purple-bg: #1f2937;
73
+ --blue: #9ca3af;
74
+ --blue-bg: #1f2937;
75
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
76
+ --shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.4);
77
+ }
78
+
79
+ html {
80
+ font-size: 15px;
81
+ }
82
+
83
+ body {
84
+ font-family: var(--font);
85
+ background: var(--bg);
86
+ color: var(--text);
87
+ line-height: 1.6;
88
+ transition: background var(--transition), color var(--transition);
89
+ min-height: 100vh;
90
+ }
91
+
92
+ /* ─── Layout ────────────────────────────────────────────── */
93
+ .shell {
94
+ max-width: 1100px;
95
+ margin: 0 auto;
96
+ padding: 2rem 1.5rem;
97
+ }
98
+
99
+ .topbar {
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: space-between;
103
+ margin-bottom: 2rem;
104
+ padding-bottom: 1.25rem;
105
+ border-bottom: 1px solid var(--border);
106
+ }
107
+
108
+ .topbar h1 {
109
+ font-size: 1.25rem;
110
+ font-weight: 700;
111
+ letter-spacing: -0.02em;
112
+ }
113
+
114
+ .topbar h1 span {
115
+ color: var(--accent);
116
+ }
117
+
118
+ .theme-btn {
119
+ background: var(--bg-card);
120
+ border: 1px solid var(--border);
121
+ border-radius: 999px;
122
+ padding: 0.4rem 0.85rem;
123
+ font-size: 0.85rem;
124
+ cursor: pointer;
125
+ color: var(--text-secondary);
126
+ transition: all var(--transition);
127
+ }
128
+
129
+ .theme-btn:hover {
130
+ border-color: var(--accent);
131
+ color: var(--accent);
132
+ }
133
+
134
+ /* ─── Reports List ──────────────────────────────────────── */
135
+ .reports-grid {
136
+ display: flex;
137
+ flex-direction: column;
138
+ gap: 0.5rem;
139
+ }
140
+
141
+ .reports-header {
142
+ display: grid;
143
+ grid-template-columns: 1fr 140px 80px 70px 70px 60px 36px;
144
+ gap: 1.25rem;
145
+ align-items: center;
146
+ padding: 0.5rem 1.25rem;
147
+ font-size: 0.7rem;
148
+ font-weight: 600;
149
+ text-transform: uppercase;
150
+ letter-spacing: 0.06em;
151
+ color: var(--text-muted);
152
+ }
153
+
154
+ .sort-col {
155
+ cursor: pointer;
156
+ user-select: none;
157
+ display: flex;
158
+ align-items: center;
159
+ gap: 0.3rem;
160
+ transition: color var(--transition);
161
+ }
162
+
163
+ .sort-col:hover {
164
+ color: var(--accent);
165
+ }
166
+
167
+ .sort-col.active {
168
+ color: var(--accent);
169
+ }
170
+
171
+ .sort-arrow {
172
+ font-size: 0.6rem;
173
+ opacity: 0.4;
174
+ }
175
+
176
+ .sort-col.active .sort-arrow {
177
+ opacity: 1;
178
+ }
179
+
180
+ .delete-btn {
181
+ background: none;
182
+ border: 1px solid transparent;
183
+ border-radius: var(--radius-sm);
184
+ color: var(--text-muted);
185
+ cursor: pointer;
186
+ padding: 0.25rem 0.4rem;
187
+ font-size: 0.85rem;
188
+ line-height: 1;
189
+ transition: all var(--transition);
190
+ }
191
+
192
+ .delete-btn:hover {
193
+ color: var(--red);
194
+ border-color: var(--red-border);
195
+ background: var(--red-bg);
196
+ }
197
+
198
+ .report-row {
199
+ display: grid;
200
+ grid-template-columns: 1fr 140px 80px 70px 70px 60px 36px;
201
+ gap: 1.25rem;
202
+ align-items: center;
203
+ background: var(--bg-card);
204
+ border: 1px solid var(--border);
205
+ border-radius: var(--radius);
206
+ padding: 1rem 1.25rem;
207
+ cursor: pointer;
208
+ transition: all var(--transition);
209
+ text-decoration: none;
210
+ color: inherit;
211
+ }
212
+
213
+ .report-row:hover {
214
+ border-color: var(--accent);
215
+ box-shadow: var(--shadow-lg);
216
+ transform: translateY(-1px);
217
+ }
218
+
219
+ .report-task {
220
+ font-weight: 600;
221
+ font-size: 0.95rem;
222
+ }
223
+
224
+ .report-date {
225
+ font-family: var(--mono);
226
+ font-size: 0.75rem;
227
+ color: var(--text-secondary);
228
+ }
229
+
230
+ .report-metric {
231
+ text-align: center;
232
+ }
233
+
234
+ .report-metric .val {
235
+ font-weight: 700;
236
+ font-size: 1.1rem;
237
+ }
238
+
239
+ .report-metric .lbl {
240
+ font-size: 0.7rem;
241
+ text-transform: uppercase;
242
+ letter-spacing: 0.06em;
243
+ color: var(--text-muted);
244
+ }
245
+
246
+ .badge {
247
+ display: inline-block;
248
+ padding: 0.15rem 0.55rem;
249
+ border-radius: 999px;
250
+ font-size: 0.72rem;
251
+ font-weight: 600;
252
+ }
253
+
254
+ .badge-pass {
255
+ background: var(--green-bg);
256
+ color: var(--green);
257
+ border: 1px solid var(--green-border);
258
+ }
259
+
260
+ .badge-fail {
261
+ background: var(--red-bg);
262
+ color: var(--red);
263
+ border: 1px solid var(--red-border);
264
+ }
265
+
266
+ .badge-type {
267
+ background: var(--purple-bg);
268
+ color: var(--purple);
269
+ }
270
+
271
+ .badge-blue {
272
+ background: var(--blue-bg);
273
+ color: var(--blue);
274
+ }
275
+
276
+ /* ─── Detail View ───────────────────────────────────────── */
277
+ .back-link {
278
+ display: inline-flex;
279
+ align-items: center;
280
+ gap: 0.4rem;
281
+ color: var(--accent);
282
+ text-decoration: none;
283
+ font-size: 0.9rem;
284
+ font-weight: 500;
285
+ margin-bottom: 1.25rem;
286
+ }
287
+
288
+ .back-link:hover {
289
+ text-decoration: underline;
290
+ }
291
+
292
+ .detail-header {
293
+ margin-bottom: 1.5rem;
294
+ }
295
+
296
+ .detail-header h2 {
297
+ font-size: 1.5rem;
298
+ font-weight: 700;
299
+ letter-spacing: -0.02em;
300
+ }
301
+
302
+ .detail-stats {
303
+ display: flex;
304
+ gap: 1.5rem;
305
+ margin-top: 0.75rem;
306
+ flex-wrap: wrap;
307
+ }
308
+
309
+ .stat-pill {
310
+ background: var(--bg-card);
311
+ border: 1px solid var(--border);
312
+ border-radius: var(--radius);
313
+ padding: 0.6rem 1rem;
314
+ display: flex;
315
+ flex-direction: column;
316
+ align-items: center;
317
+ }
318
+
319
+ .stat-pill .val {
320
+ font-weight: 700;
321
+ font-size: 1.2rem;
322
+ }
323
+
324
+ .stat-pill .lbl {
325
+ font-size: 0.7rem;
326
+ color: var(--text-muted);
327
+ text-transform: uppercase;
328
+ letter-spacing: 0.05em;
329
+ }
330
+
331
+ /* ─── Trial Card ────────────────────────────────────────── */
332
+ .trial-card {
333
+ background: var(--bg-card);
334
+ border: 1px solid var(--border);
335
+ border-radius: var(--radius);
336
+ margin-bottom: 0.75rem;
337
+ overflow: hidden;
338
+ }
339
+
340
+ .trial-header {
341
+ display: flex;
342
+ align-items: center;
343
+ gap: 0.75rem;
344
+ padding: 0.85rem 1.25rem;
345
+ cursor: pointer;
346
+ transition: background var(--transition);
347
+ }
348
+
349
+ .trial-header:hover {
350
+ background: var(--bg-hover);
351
+ }
352
+
353
+ .trial-number {
354
+ font-weight: 700;
355
+ font-size: 0.85rem;
356
+ color: var(--text-muted);
357
+ min-width: 4rem;
358
+ }
359
+
360
+ .trial-reward {
361
+ font-weight: 700;
362
+ font-size: 1rem;
363
+ }
364
+
365
+ .trial-reward.pass {
366
+ color: var(--green);
367
+ }
368
+
369
+ .trial-reward.fail {
370
+ color: var(--red);
371
+ }
372
+
373
+ .trial-meta {
374
+ font-size: 0.8rem;
375
+ color: var(--text-secondary);
376
+ margin-left: auto;
377
+ display: flex;
378
+ gap: 1rem;
379
+ }
380
+
381
+ .trial-chevron {
382
+ color: var(--text-muted);
383
+ transition: transform var(--transition);
384
+ font-size: 0.8rem;
385
+ }
386
+
387
+ .trial-card.open .trial-chevron {
388
+ transform: rotate(90deg);
389
+ }
390
+
391
+ .trial-body {
392
+ display: none;
393
+ border-top: 1px solid var(--border);
394
+ }
395
+
396
+ .trial-card.open .trial-body {
397
+ display: block;
398
+ }
399
+
400
+ /* ─── Scorer Results ────────────────────────────────────── */
401
+ .scorers-section {
402
+ padding: 1rem 1.25rem;
403
+ background: var(--bg-hover);
404
+ }
405
+
406
+ .scorer-row {
407
+ display: flex;
408
+ align-items: flex-start;
409
+ gap: 0.75rem;
410
+ padding: 0.6rem 0;
411
+ border-bottom: 1px solid var(--border);
412
+ }
413
+
414
+ .scorer-row:last-child {
415
+ border-bottom: none;
416
+ }
417
+
418
+ .scorer-score {
419
+ font-weight: 700;
420
+ font-family: var(--mono);
421
+ min-width: 3rem;
422
+ }
423
+
424
+ .scorer-score.pass {
425
+ color: var(--green);
426
+ }
427
+
428
+ .scorer-score.fail {
429
+ color: var(--red);
430
+ }
431
+
432
+ .scorer-weight {
433
+ font-size: 0.75rem;
434
+ color: var(--text-muted);
435
+ font-family: var(--mono);
436
+ }
437
+
438
+ .scorer-details {
439
+ flex: 1;
440
+ font-size: 0.85rem;
441
+ color: var(--text-secondary);
442
+ line-height: 1.5;
443
+ }
444
+
445
+ /* ─── Session Log ───────────────────────────────────────── */
446
+ .log-section {
447
+ padding: 0 1.25rem 1.25rem;
448
+ }
449
+
450
+ .log-toggle {
451
+ display: flex;
452
+ align-items: center;
453
+ gap: 0.5rem;
454
+ padding: 0.75rem 0;
455
+ cursor: pointer;
456
+ border: none;
457
+ background: none;
458
+ color: var(--accent);
459
+ font-size: 0.85rem;
460
+ font-weight: 500;
461
+ font-family: var(--font);
462
+ width: 100%;
463
+ }
464
+
465
+ .log-toggle:hover {
466
+ text-decoration: underline;
467
+ }
468
+
469
+ .log-entries {
470
+ display: none;
471
+ }
472
+
473
+ .log-entries.open {
474
+ display: block;
475
+ }
476
+
477
+ .log-entry {
478
+ border-left: 3px solid var(--border);
479
+ padding: 0.5rem 0.75rem;
480
+ margin-bottom: 2px;
481
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
482
+ transition: background var(--transition);
483
+ }
484
+
485
+ .log-entry:hover {
486
+ background: var(--bg-hover);
487
+ }
488
+
489
+ .log-entry.agent_start {
490
+ border-left-color: var(--purple);
491
+ }
492
+
493
+ .log-entry.command {
494
+ border-left-color: var(--blue);
495
+ }
496
+
497
+ .log-entry.agent_result {
498
+ border-left-color: var(--green);
499
+ }
500
+
501
+ .log-entry.scorer {
502
+ border-left-color: var(--yellow);
503
+ }
504
+
505
+ .log-entry.reward {
506
+ border-left-color: var(--red);
507
+ }
508
+
509
+ .log-label {
510
+ display: inline-block;
511
+ font-size: 0.65rem;
512
+ font-weight: 600;
513
+ text-transform: uppercase;
514
+ letter-spacing: 0.06em;
515
+ padding: 0.1rem 0.4rem;
516
+ border-radius: 3px;
517
+ margin-right: 0.5rem;
518
+ font-family: var(--mono);
519
+ }
520
+
521
+ .agent_start .log-label {
522
+ background: var(--purple-bg);
523
+ color: var(--purple);
524
+ }
525
+
526
+ .command .log-label {
527
+ background: var(--blue-bg);
528
+ color: var(--blue);
529
+ }
530
+
531
+ .agent_result .log-label {
532
+ background: var(--green-bg);
533
+ color: var(--green);
534
+ }
535
+
536
+ .scorer .log-label {
537
+ background: var(--yellow-bg);
538
+ color: var(--yellow);
539
+ }
540
+
541
+ .reward .log-label {
542
+ background: var(--red-bg);
543
+ color: var(--red);
544
+ }
545
+
546
+ .log-ts {
547
+ font-size: 0.65rem;
548
+ color: var(--text-muted);
549
+ font-family: var(--mono);
550
+ }
551
+
552
+ .log-content {
553
+ margin-top: 0.35rem;
554
+ }
555
+
556
+ .log-command-details {
557
+ border: 1px solid var(--border);
558
+ border-radius: var(--radius-sm);
559
+ background: var(--bg-code);
560
+ overflow: hidden;
561
+ }
562
+
563
+ .log-command-summary {
564
+ display: flex;
565
+ align-items: center;
566
+ gap: 0.6rem;
567
+ padding: 0.55rem 0.7rem;
568
+ cursor: pointer;
569
+ list-style: none;
570
+ }
571
+
572
+ .log-command-summary::-webkit-details-marker {
573
+ display: none;
574
+ }
575
+
576
+ .log-command-summary::before {
577
+ content: '▶';
578
+ color: var(--text-muted);
579
+ font-size: 0.7rem;
580
+ flex: 0 0 auto;
581
+ transition: transform var(--transition);
582
+ }
583
+
584
+ .log-command-details[open] .log-command-summary::before {
585
+ transform: rotate(90deg);
586
+ }
587
+
588
+ .log-command-summary:hover {
589
+ background: var(--bg-hover);
590
+ }
591
+
592
+ .log-command-text {
593
+ flex: 1;
594
+ min-width: 0;
595
+ white-space: nowrap;
596
+ overflow: hidden;
597
+ text-overflow: ellipsis;
598
+ }
599
+
600
+ .log-command-meta {
601
+ display: flex;
602
+ align-items: center;
603
+ gap: 0.5rem;
604
+ flex: 0 0 auto;
605
+ }
606
+
607
+ .log-command-hint {
608
+ font-size: 0.7rem;
609
+ color: var(--text-muted);
610
+ text-transform: uppercase;
611
+ letter-spacing: 0.06em;
612
+ }
613
+
614
+ .log-command-hint::before {
615
+ content: 'Expand';
616
+ }
617
+
618
+ .log-command-details[open] .log-command-hint::before {
619
+ content: 'Collapse';
620
+ }
621
+
622
+ .log-command-output {
623
+ border-top: 1px solid var(--border);
624
+ padding: 0.65rem 0.7rem 0.7rem;
625
+ }
626
+
627
+ pre.code-block {
628
+ font-family: var(--mono);
629
+ font-size: 0.8rem;
630
+ background: var(--bg-code);
631
+ padding: 0.6rem 0.75rem;
632
+ border-radius: var(--radius-sm);
633
+ overflow-x: auto;
634
+ white-space: pre-wrap;
635
+ word-break: break-word;
636
+ line-height: 1.5;
637
+ max-height: 350px;
638
+ overflow-y: auto;
639
+ border: 1px solid var(--border);
640
+ }
641
+
642
+ .cmd-line {
643
+ font-family: var(--mono);
644
+ color: var(--green);
645
+ font-weight: 500;
646
+ font-size: 0.85rem;
647
+ }
648
+
649
+ .cmd-line::before {
650
+ content: '$ ';
651
+ color: var(--text-muted);
652
+ }
653
+
654
+ .exit-badge {
655
+ font-family: var(--mono);
656
+ font-size: 0.7rem;
657
+ margin-top: 0.25rem;
658
+ }
659
+
660
+ .exit-badge.ok {
661
+ color: var(--green);
662
+ }
663
+
664
+ .exit-badge.err {
665
+ color: var(--red);
666
+ }
667
+
668
+ .stderr-text {
669
+ color: var(--red);
670
+ }
671
+
672
+ .reward-val {
673
+ font-size: 1.4rem;
674
+ font-weight: 700;
675
+ }
676
+
677
+ .reward-val.pass {
678
+ color: var(--green);
679
+ }
680
+
681
+ .reward-val.fail {
682
+ color: var(--red);
683
+ }
684
+
685
+ /* ─── Conversation Transcript ────────────────────────────── */
686
+ .conv-section {
687
+ padding: 1rem 1.25rem;
688
+ border-top: 1px solid var(--border);
689
+ }
690
+
691
+ .conv-header {
692
+ display: flex;
693
+ align-items: center;
694
+ gap: 0.5rem;
695
+ margin-bottom: 0.75rem;
696
+ font-size: 0.85rem;
697
+ font-weight: 600;
698
+ }
699
+
700
+ .conv-meta {
701
+ font-weight: 400;
702
+ font-size: 0.75rem;
703
+ color: var(--text-muted);
704
+ }
705
+
706
+ .conv-turn {
707
+ border: 1px solid var(--border);
708
+ border-radius: var(--radius-sm);
709
+ margin-bottom: 0.5rem;
710
+ overflow: hidden;
711
+ }
712
+
713
+ .conv-turn-header {
714
+ display: flex;
715
+ align-items: center;
716
+ gap: 0.5rem;
717
+ padding: 0.4rem 0.75rem;
718
+ background: var(--bg-hover);
719
+ font-size: 0.75rem;
720
+ color: var(--text-secondary);
721
+ font-family: var(--mono);
722
+ }
723
+
724
+ .conv-msg {
725
+ padding: 0.5rem 0.75rem;
726
+ }
727
+
728
+ .conv-msg-label {
729
+ font-size: 0.65rem;
730
+ font-weight: 600;
731
+ text-transform: uppercase;
732
+ letter-spacing: 0.06em;
733
+ color: var(--text-muted);
734
+ margin-bottom: 0.2rem;
735
+ }
736
+
737
+ .conv-msg pre {
738
+ font-family: var(--mono);
739
+ font-size: 0.8rem;
740
+ background: var(--bg-code);
741
+ padding: 0.5rem 0.65rem;
742
+ border-radius: var(--radius-sm);
743
+ white-space: pre-wrap;
744
+ word-break: break-word;
745
+ max-height: 200px;
746
+ overflow-y: auto;
747
+ border: 1px solid var(--border);
748
+ }
749
+
750
+ .conv-step-scorers {
751
+ padding: 0.4rem 0.75rem 0.5rem;
752
+ border-top: 1px solid var(--border);
753
+ background: var(--bg-hover);
754
+ }
755
+
756
+ .conv-step-scorer-row {
757
+ display: flex;
758
+ align-items: center;
759
+ gap: 0.5rem;
760
+ font-size: 0.8rem;
761
+ padding: 0.2rem 0;
762
+ }
763
+
764
+ /* ─── Empty State ───────────────────────────────────────── */
765
+ .empty {
766
+ text-align: center;
767
+ padding: 4rem 2rem;
768
+ color: var(--text-muted);
769
+ }
770
+
771
+ .empty-icon {
772
+ font-size: 3rem;
773
+ margin-bottom: 1rem;
774
+ }
775
+
776
+ /* ─── Loading ───────────────────────────────────────────── */
777
+ .spinner {
778
+ display: flex;
779
+ align-items: center;
780
+ justify-content: center;
781
+ padding: 4rem;
782
+ color: var(--text-muted);
783
+ }
784
+
785
+ @keyframes spin {
786
+ to {
787
+ transform: rotate(360deg);
788
+ }
789
+ }
790
+
791
+ .spinner::before {
792
+ content: '';
793
+ width: 24px;
794
+ height: 24px;
795
+ border: 2px solid var(--border);
796
+ border-top-color: var(--accent);
797
+ border-radius: 50%;
798
+ animation: spin 0.6s linear infinite;
799
+ margin-right: 0.75rem;
800
+ }
801
+ </style>
802
+ </head>
803
+
804
+ <body>
805
+ <div class="shell" id="app">
806
+ <div class="topbar">
807
+ <h1 style="display:flex;align-items:center">
808
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 48" fill="none" height="42">
809
+ <circle cx="8" cy="24" r="4" fill="#22c55e"/>
810
+ <line x1="12" y1="24" x2="20" y2="24" stroke="#22c55e" stroke-width="2" stroke-linecap="round"/>
811
+ <circle cx="24" cy="24" r="3" fill="none" stroke="#22c55e" stroke-width="1.5"/>
812
+ <line x1="27" y1="24" x2="35" y2="24" stroke="#22c55e" stroke-width="2" stroke-linecap="round"/>
813
+ <circle cx="39" cy="24" r="3" fill="none" stroke="#22c55e" stroke-width="1.5"/>
814
+ <line x1="42" y1="24" x2="50" y2="24" stroke="#22c55e" stroke-width="2" stroke-linecap="round"/>
815
+ <circle cx="56" cy="24" r="6" fill="#22c55e"/>
816
+ <polyline points="52.5,24 55,26.5 59.5,21.5" stroke="white" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
817
+ <text x="72" y="30" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" font-size="24" font-weight="600">
818
+ <tspan fill="var(--text)">path</tspan><tspan fill="#22c55e">grade</tspan>
819
+ </text>
820
+ </svg>
821
+ </h1>
822
+ <button class="theme-btn" onclick="toggleTheme()" id="themeBtn">◐ Dark</button>
823
+ </div>
824
+ <div id="content">
825
+ <div class="spinner">Loading reports…</div>
826
+ </div>
827
+ </div>
828
+
829
+ <script>
830
+ // ─── Theme ─────────────────────────────────────────────────
831
+ function getTheme() { return localStorage.getItem('theme') || (matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light'); }
832
+ function applyTheme(t) {
833
+ document.documentElement.setAttribute('data-theme', t);
834
+ localStorage.setItem('theme', t);
835
+ document.getElementById('themeBtn').textContent = t === 'dark' ? '☀ Light' : '◐ Dark';
836
+ }
837
+ function toggleTheme() { applyTheme(getTheme() === 'dark' ? 'light' : 'dark'); }
838
+ applyTheme(getTheme());
839
+
840
+ // ─── State ─────────────────────────────────────────────────
841
+ let reports = [];
842
+ let sortKey = 'date';
843
+ let sortDir = 'desc';
844
+ const $ = id => document.getElementById(id);
845
+ const esc = s => s ? String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : '';
846
+
847
+ function parseDate(file) {
848
+ // filename format: 2026-04-08T14-25-03-134Z → 2026-04-08T14:25:03.134
849
+ const m = file.match(/(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d+)/);
850
+ if (!m) return '';
851
+ return m[1] + 'T' + m[2] + ':' + m[3] + ':' + m[4] + '.' + m[5];
852
+ }
853
+
854
+ function formatDate(ts) {
855
+ if (!ts) return '';
856
+ try {
857
+ const d = new Date(ts);
858
+ if (isNaN(d)) return ts;
859
+ const now = new Date();
860
+ const diff = now - d;
861
+ const mins = Math.floor(diff / 60000);
862
+ const hrs = Math.floor(diff / 3600000);
863
+ const days = Math.floor(diff / 86400000);
864
+ const time = d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
865
+ if (mins < 1) return 'Just now';
866
+ if (mins < 60) return mins + 'm ago';
867
+ if (hrs < 24) return hrs + 'h ago · ' + time;
868
+ if (days < 7) return days + 'd ago · ' + time;
869
+ return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' · ' + time;
870
+ } catch { return ts; }
871
+ }
872
+
873
+ function sortReports(arr) {
874
+ const sorted = [...arr];
875
+ sorted.sort((a, b) => {
876
+ let va, vb;
877
+ switch (sortKey) {
878
+ case 'task': va = (a.task || '').toLowerCase(); vb = (b.task || '').toLowerCase(); break;
879
+ case 'date': va = parseDate(a.file); vb = parseDate(b.file); break;
880
+ case 'pass_rate': va = a.pass_rate || 0; vb = b.pass_rate || 0; break;
881
+ case 'pass_at_k': va = a.pass_at_k ?? -1; vb = b.pass_at_k ?? -1; break;
882
+ case 'pass_pow_k': va = a.pass_pow_k ?? -1; vb = b.pass_pow_k ?? -1; break;
883
+ case 'status': va = a.pass_rate >= 0.5 ? 1 : 0; vb = b.pass_rate >= 0.5 ? 1 : 0; break;
884
+ default: return 0;
885
+ }
886
+ if (va < vb) return sortDir === 'asc' ? -1 : 1;
887
+ if (va > vb) return sortDir === 'asc' ? 1 : -1;
888
+ return 0;
889
+ });
890
+ return sorted;
891
+ }
892
+
893
+ function setSort(key) {
894
+ if (sortKey === key) sortDir = sortDir === 'asc' ? 'desc' : 'asc';
895
+ else { sortKey = key; sortDir = key === 'date' ? 'desc' : 'asc'; }
896
+ renderListFromCache();
897
+ }
898
+
899
+ // ─── Router ────────────────────────────────────────────────
900
+ function navigate(view, data) {
901
+ if (view === 'list') renderList();
902
+ else if (view === 'detail') loadDetail(data);
903
+ }
904
+
905
+ async function deleteReport(file) {
906
+ if (!confirm('Delete this report?')) return;
907
+ const res = await fetch('/api/report?file=' + encodeURIComponent(file), { method: 'DELETE' });
908
+ if (res.ok) {
909
+ reports = reports.filter(r => r.file !== file);
910
+ navigate('list');
911
+ }
912
+ }
913
+
914
+ // ─── List View ─────────────────────────────────────────────
915
+ async function renderList() {
916
+ const res = await fetch('/api/reports');
917
+ reports = await res.json();
918
+
919
+ if (!reports.length) {
920
+ $('content').innerHTML = '<div class="empty"><div class="empty-icon">📭</div><div>No reports found. Run an eval first.</div></div>';
921
+ return;
922
+ }
923
+ renderListFromCache();
924
+ }
925
+
926
+ function renderListFromCache() {
927
+ const sorted = sortReports(reports);
928
+ const arrow = dir => dir === 'asc' ? '▲' : '▼';
929
+ const colHeader = (key, label, justify) => {
930
+ const active = sortKey === key;
931
+ return `<div class="sort-col ${active ? 'active' : ''}" onclick="setSort('${key}')" style="justify-content:${justify || 'flex-start'}">
932
+ ${label}<span class="sort-arrow">${active ? arrow(sortDir) : '▲'}</span>
933
+ </div>`;
934
+ };
935
+
936
+ const header = `<div class="reports-header">
937
+ ${colHeader('task', 'Task')}
938
+ ${colHeader('date', 'Date')}
939
+ ${colHeader('pass_rate', 'Pass Rate', 'center')}
940
+ ${colHeader('pass_at_k', 'pass@k', 'center')}
941
+ ${colHeader('pass_pow_k', 'pass^k', 'center')}
942
+ ${colHeader('status', 'Status', 'center')}
943
+ <div></div>
944
+ </div>`;
945
+
946
+ const rows = sorted.map(r => {
947
+ const pr = (r.pass_rate * 100).toFixed(0);
948
+ const isPass = r.pass_rate >= 0.5;
949
+ const ts = r.timestamp || parseDate(r.file);
950
+
951
+ return `<a class="report-row" onclick="navigate('detail','${esc(r.file)}')" href="javascript:void(0)">
952
+ <div class="report-task">${esc(r.task)}</div>
953
+ <div class="report-date">${formatDate(ts)}</div>
954
+ <div class="report-metric"><div class="val">${pr}%</div></div>
955
+ <div class="report-metric"><div class="val">${r.pass_at_k != null ? (r.pass_at_k * 100).toFixed(0) + '%' : '—'}</div></div>
956
+ <div class="report-metric"><div class="val">${r.pass_pow_k != null ? (r.pass_pow_k * 100).toFixed(0) + '%' : '—'}</div></div>
957
+ <div style="text-align:center"><span class="badge ${isPass ? 'badge-pass' : 'badge-fail'}">${isPass ? 'PASS' : 'FAIL'}</span></div>
958
+ <button class="delete-btn" onclick="event.preventDefault();event.stopPropagation();deleteReport('${esc(r.file)}')" title="Delete report">&#x2715;</button>
959
+ </a>`;
960
+ }).join('');
961
+
962
+ $('content').innerHTML = header + '<div class="reports-grid">' + rows + '</div>';
963
+ }
964
+
965
+ // ─── Detail View ───────────────────────────────────────────
966
+ async function loadDetail(file) {
967
+ $('content').innerHTML = '<div class="spinner">Loading…</div>';
968
+ const res = await fetch('/api/report?file=' + encodeURIComponent(file));
969
+ const report = await res.json();
970
+ renderDetail(report, file);
971
+ }
972
+
973
+ function renderDetail(r, file) {
974
+ const avgDur = r.trials.reduce((s, t) => s + (t.duration_ms || 0), 0) / r.trials.length;
975
+ const totalTokens = r.trials.reduce((s, t) => s + (t.input_tokens || 0) + (t.output_tokens || 0) + (t.conversation_input_tokens || 0) + (t.conversation_output_tokens || 0), 0);
976
+
977
+ const stats = `
978
+ <div class="stat-pill"><div class="val">${(r.pass_rate * 100).toFixed(1)}%</div><div class="lbl">Pass Rate</div></div>
979
+ <div class="stat-pill"><div class="val">${r.pass_at_k != null ? (r.pass_at_k * 100).toFixed(1) + '%' : '—'}</div><div class="lbl">pass@k</div></div>
980
+ <div class="stat-pill"><div class="val">${r.pass_pow_k != null ? (r.pass_pow_k * 100).toFixed(1) + '%' : '—'}</div><div class="lbl">pass^k</div></div>
981
+ <div class="stat-pill"><div class="val">${(avgDur / 1000).toFixed(1)}s</div><div class="lbl">Avg Duration</div></div>
982
+ <div class="stat-pill"><div class="val">~${totalTokens}</div><div class="lbl">Total Tokens</div></div>
983
+ <div class="stat-pill"><div class="val">${r.skills_used?.join(', ') || 'none'}</div><div class="lbl">Skills</div></div>
984
+ `;
985
+
986
+ const trials = r.trials.map((t, i) => renderTrial(t, i)).join('');
987
+
988
+ $('content').innerHTML = `
989
+ <a class="back-link" href="javascript:void(0)" onclick="navigate('list')">← All Reports</a>
990
+ <div class="detail-header">
991
+ <div style="display:flex;align-items:center;gap:0.75rem">
992
+ <h2 style="flex:1">${esc(r.task)}</h2>
993
+ <button class="delete-btn" onclick="deleteReport('${esc(file)}')" title="Delete report">&#x2715; Delete</button>
994
+ </div>
995
+ <div class="detail-stats">${stats}</div>
996
+ </div>
997
+ ${trials}
998
+ `;
999
+ }
1000
+
1001
+ function renderTrial(t, idx) {
1002
+ const pass = t.reward >= 0.5;
1003
+ const dur = ((t.duration_ms || 0) / 1000).toFixed(1);
1004
+ const tokens = (t.input_tokens || 0) + (t.output_tokens || 0);
1005
+ const convTokens = (t.conversation_input_tokens || 0) + (t.conversation_output_tokens || 0);
1006
+
1007
+ const scorers = (t.scorer_results || []).map(g => `
1008
+ <div class="scorer-row">
1009
+ <span class="badge badge-type">${esc(g.scorer_type)}</span>
1010
+ <span class="scorer-score ${g.score >= 0.5 ? 'pass' : 'fail'}">${g.score.toFixed(2)}</span>
1011
+ <span class="scorer-weight">×${g.weight}</span>
1012
+ <div class="scorer-details">${esc(g.details)}</div>
1013
+ </div>
1014
+ `).join('');
1015
+
1016
+ const convSection = t.conversation ? renderConversation(t.conversation) : '';
1017
+
1018
+ const logId = 'log-' + idx;
1019
+ const logs = (t.session_log || []).map(e => renderLogEntry(e)).join('');
1020
+
1021
+ const convMeta = t.conversation
1022
+ ? `<span>${t.conversation.total_turns} turns</span><span>${esc(t.conversation.completion_reason)}</span>`
1023
+ : '';
1024
+
1025
+ return `
1026
+ <div class="trial-card" id="trial-${idx}">
1027
+ <div class="trial-header" onclick="toggleTrial(${idx})">
1028
+ <span class="trial-number">${t.name ? esc(t.name) : 'Trial ' + t.trial_id}</span>
1029
+ <span class="trial-reward ${pass ? 'pass' : 'fail'}">${t.reward.toFixed(2)}</span>
1030
+ <span class="badge ${pass ? 'badge-pass' : 'badge-fail'}">${pass ? 'PASS' : 'FAIL'}</span>
1031
+ <span class="trial-meta">
1032
+ <span>${dur}s</span>
1033
+ <span>${t.n_commands || 0} cmds</span>
1034
+ <span>~${tokens} tokens${convTokens ? ' (+' + convTokens + ' conv)' : ''}</span>
1035
+ ${convMeta}
1036
+ </span>
1037
+ <span class="trial-chevron">▶</span>
1038
+ </div>
1039
+ <div class="trial-body">
1040
+ ${scorers ? '<div class="scorers-section">' + scorers + '</div>' : ''}
1041
+ ${convSection}
1042
+ <div class="log-section">
1043
+ <button class="log-toggle" onclick="toggleLog('${logId}')">▶ Session Log (${t.session_log?.length || 0} entries)</button>
1044
+ <div class="log-entries" id="${logId}">${logs}</div>
1045
+ </div>
1046
+ </div>
1047
+ </div>
1048
+ `;
1049
+ }
1050
+
1051
+ function renderConversation(conv) {
1052
+ const turns = (conv.turns || []).map(turn => {
1053
+ const stepScorers = (turn.step_scorer_results || []).map(g => {
1054
+ const scoreClass = g.score >= 0.5 ? 'pass' : 'fail';
1055
+ return `<div class="conv-step-scorer-row">
1056
+ <span class="badge badge-type">${esc(g.scorer_type)}</span>
1057
+ <span class="scorer-score ${scoreClass}">${g.score.toFixed(2)}</span>
1058
+ <span class="scorer-details">${esc(g.details).substring(0, 120)}</span>
1059
+ </div>`;
1060
+ }).join('');
1061
+
1062
+ return `<div class="conv-turn">
1063
+ <div class="conv-turn-header">
1064
+ <span>Turn ${turn.turn_number}</span>
1065
+ <span class="badge badge-blue">${esc(turn.user_message_source)}</span>
1066
+ <span>${((turn.duration_ms || 0) / 1000).toFixed(1)}s</span>
1067
+ <span>${turn.commands?.length || 0} cmds</span>
1068
+ </div>
1069
+ <div class="conv-msg">
1070
+ <div class="conv-msg-label">User</div>
1071
+ <pre>${esc(turn.user_message)}</pre>
1072
+ </div>
1073
+ <div class="conv-msg">
1074
+ <div class="conv-msg-label">Assistant</div>
1075
+ <pre>${esc(turn.assistant_message)}</pre>
1076
+ </div>
1077
+ ${stepScorers ? '<div class="conv-step-scorers">' + stepScorers + '</div>' : ''}
1078
+ </div>`;
1079
+ }).join('');
1080
+
1081
+ return `<div class="conv-section">
1082
+ <div class="conv-header">
1083
+ Conversation
1084
+ <span class="conv-meta">${conv.total_turns} turns · ${esc(conv.completion_reason)}</span>
1085
+ </div>
1086
+ ${turns}
1087
+ </div>`;
1088
+ }
1089
+
1090
+ function renderLogEntry(e) {
1091
+ let body = '';
1092
+ switch (e.type) {
1093
+ case 'agent_start':
1094
+ case 'user_reply':
1095
+ body = '<pre class="code-block">' + esc(e.instruction || '') + '</pre>';
1096
+ break;
1097
+ case 'command':
1098
+ body = '<details class="log-command-details">'
1099
+ + '<summary class="log-command-summary">'
1100
+ + '<span class="cmd-line log-command-text">' + esc(e.command || '') + '</span>'
1101
+ + '<span class="log-command-meta">'
1102
+ + '<span class="exit-badge ' + (e.exitCode === 0 ? 'ok' : 'err') + '">exit ' + (e.exitCode ?? '?') + '</span>'
1103
+ + '<span class="log-command-hint"></span>'
1104
+ + '</span>'
1105
+ + '</summary>'
1106
+ + '<div class="log-command-output">'
1107
+ + '<pre class="code-block">' + esc(e.stdout || '')
1108
+ + (e.stderr ? '<span class="stderr-text">' + esc(e.stderr) + '</span>' : '')
1109
+ + '</pre>'
1110
+ + '</div>'
1111
+ + '</details>';
1112
+ break;
1113
+ case 'agent_result':
1114
+ body = '<pre class="code-block">' + esc(e.output || '') + '</pre>';
1115
+ break;
1116
+ case 'scorer':
1117
+ if (e.scorer_result) {
1118
+ const g = e.scorer_result;
1119
+ body = '<span class="scorer-score ' + (g.score >= 0.5 ? 'pass' : 'fail') + '">'
1120
+ + g.score.toFixed(2) + '</span> '
1121
+ + '<span class="badge badge-type">' + esc(g.scorer_type) + '</span> '
1122
+ + '<span class="scorer-details">' + esc(g.details) + '</span>';
1123
+ }
1124
+ break;
1125
+ case 'reward':
1126
+ body = '<span class="reward-val ' + ((e.value || 0) >= 0.5 ? 'pass' : 'fail') + '">'
1127
+ + (e.value?.toFixed(2) || '0.00') + '</span>'
1128
+ + (e.output ? '<div class="scorer-details" style="margin-top:0.25rem">' + esc(e.output) + '</div>' : '');
1129
+ break;
1130
+ case 'tool_event':
1131
+ body = '<span class="badge badge-type">' + esc(e.tool_event?.action || 'unknown') + '</span> '
1132
+ + '<span class="scorer-details">' + esc(e.tool_event?.summary || '') + '</span>';
1133
+ break;
1134
+ case 'judge_tool_call': {
1135
+ const tc = e.judge_tool_call || {};
1136
+ const okBadge = tc.ok
1137
+ ? '<span class="exit-badge ok">ok</span>'
1138
+ : '<span class="exit-badge err">error</span>';
1139
+ const inputJson = JSON.stringify(tc.input ?? {});
1140
+ const errorLine = tc.ok ? '' : '<div class="stderr-text">' + esc(tc.errorMessage || '') + '</div>';
1141
+ body = '<details class="log-command-details">'
1142
+ + '<summary class="log-command-summary">'
1143
+ + '<span class="badge badge-type">' + esc(tc.name || '?') + '</span> '
1144
+ + '<span class="scorer-details">' + esc('judge:' + (tc.judge_name || '?')) + '</span>'
1145
+ + '<span class="log-command-meta">'
1146
+ + okBadge
1147
+ + '<span class="log-command-hint">' + (tc.bytes || 0) + ' bytes</span>'
1148
+ + '</span>'
1149
+ + '</summary>'
1150
+ + '<div class="log-command-output">'
1151
+ + '<pre class="code-block">' + esc(inputJson) + '</pre>'
1152
+ + errorLine
1153
+ + '</div>'
1154
+ + '</details>';
1155
+ break;
1156
+ }
1157
+ }
1158
+ return '<div class="log-entry ' + e.type + '">'
1159
+ + '<span class="log-label">' + e.type + '</span>'
1160
+ + '<span class="log-ts">' + (e.timestamp || '') + '</span>'
1161
+ + '<div class="log-content">' + body + '</div>'
1162
+ + '</div>';
1163
+ }
1164
+
1165
+ // ─── Interactions ──────────────────────────────────────────
1166
+ function toggleTrial(idx) {
1167
+ document.getElementById('trial-' + idx).classList.toggle('open');
1168
+ }
1169
+ function toggleLog(id) {
1170
+ document.getElementById(id).classList.toggle('open');
1171
+ }
1172
+
1173
+ // ─── Init ──────────────────────────────────────────────────
1174
+ navigate('list');
1175
+ </script>
1176
+ </body>
1177
+
1178
+ </html>