@post-print/agent-test 0.3.13 → 1.1.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.
- package/README.md +23 -7
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/compare-scenario.d.ts +16 -28
- package/dist/compare-scenario.d.ts.map +1 -1
- package/dist/compare-scenario.js +163 -261
- package/dist/compare-scenario.js.map +1 -1
- package/dist/context-panel.d.ts +5 -0
- package/dist/context-panel.d.ts.map +1 -0
- package/dist/context-panel.js +60 -0
- package/dist/context-panel.js.map +1 -0
- package/dist/debug-bundle.d.ts.map +1 -1
- package/dist/debug-bundle.js +2 -0
- package/dist/debug-bundle.js.map +1 -1
- package/dist/expect.d.ts +1 -0
- package/dist/expect.d.ts.map +1 -1
- package/dist/expect.js +19 -0
- package/dist/expect.js.map +1 -1
- package/dist/html-report.d.ts.map +1 -1
- package/dist/html-report.js +238 -38
- package/dist/html-report.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/live-isolation.d.ts +2 -0
- package/dist/live-isolation.d.ts.map +1 -1
- package/dist/live-isolation.js +66 -6
- package/dist/live-isolation.js.map +1 -1
- package/dist/record-trace.d.ts +7 -1
- package/dist/record-trace.d.ts.map +1 -1
- package/dist/record-trace.js.map +1 -1
- package/dist/run-suite.d.ts.map +1 -1
- package/dist/run-suite.js +137 -31
- package/dist/run-suite.js.map +1 -1
- package/dist/scenario-story.d.ts +4 -3
- package/dist/scenario-story.d.ts.map +1 -1
- package/dist/scenario-story.js +47 -71
- package/dist/scenario-story.js.map +1 -1
- package/dist/scenario-usage.d.ts.map +1 -1
- package/dist/scenario-usage.js +2 -3
- package/dist/scenario-usage.js.map +1 -1
- package/dist/types.d.ts +61 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-suite.d.ts.map +1 -1
- package/dist/validate-suite.js +100 -38
- package/dist/validate-suite.js.map +1 -1
- package/dist/viewer/catalog.d.ts +6 -2
- package/dist/viewer/catalog.d.ts.map +1 -1
- package/dist/viewer/catalog.js +14 -0
- package/dist/viewer/catalog.js.map +1 -1
- package/dist/viewer/compare-metrics.d.ts +30 -0
- package/dist/viewer/compare-metrics.d.ts.map +1 -0
- package/dist/viewer/compare-metrics.js +102 -0
- package/dist/viewer/compare-metrics.js.map +1 -0
- package/dist/viewer/context-files.d.ts +8 -0
- package/dist/viewer/context-files.d.ts.map +1 -0
- package/dist/viewer/context-files.js +57 -0
- package/dist/viewer/context-files.js.map +1 -0
- package/dist/viewer/events.d.ts +20 -0
- package/dist/viewer/events.d.ts.map +1 -1
- package/dist/viewer/events.js +2 -0
- package/dist/viewer/events.js.map +1 -1
- package/dist/viewer/live-runner.d.ts +6 -0
- package/dist/viewer/live-runner.d.ts.map +1 -1
- package/dist/viewer/live-runner.js +34 -5
- package/dist/viewer/live-runner.js.map +1 -1
- package/dist/viewer/page.d.ts.map +1 -1
- package/dist/viewer/page.js +1463 -93
- package/dist/viewer/page.js.map +1 -1
- package/dist/viewer/run-controller.d.ts +5 -0
- package/dist/viewer/run-controller.d.ts.map +1 -1
- package/dist/viewer/run-controller.js +27 -1
- package/dist/viewer/run-controller.js.map +1 -1
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +18 -11
- package/dist/viewer/server.js.map +1 -1
- package/package.json +7 -5
package/dist/viewer/page.js
CHANGED
|
@@ -41,23 +41,239 @@ function viewerCss() {
|
|
|
41
41
|
button.danger { border-color: color-mix(in srgb, var(--fail) 40%, var(--border)); color: var(--fail); }
|
|
42
42
|
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
43
43
|
.warn { color: var(--skip); font-size: 0.8rem; }
|
|
44
|
-
.
|
|
45
|
-
.
|
|
46
|
-
|
|
44
|
+
.scenario-list { display: grid; gap: 0.75rem; }
|
|
45
|
+
.scenario-card {
|
|
46
|
+
background: var(--panel);
|
|
47
|
+
border: 1px solid var(--border);
|
|
48
|
+
border-radius: 12px;
|
|
49
|
+
padding: 0.85rem 0.95rem;
|
|
50
|
+
display: grid;
|
|
51
|
+
gap: 0.7rem;
|
|
52
|
+
}
|
|
47
53
|
.scenario-detail { display: grid; gap: 0.35rem; }
|
|
48
54
|
.prompt-preview { color: var(--muted); font-size: 0.78rem; white-space: pre-wrap; }
|
|
49
55
|
.rubric-line { font-size: 0.75rem; color: var(--muted); }
|
|
50
56
|
.arm-note { font-size: 0.78rem; color: var(--muted); }
|
|
51
|
-
.
|
|
57
|
+
.scenario-toolbar { display: flex; flex-wrap: wrap; gap: 0.55rem 0.85rem; align-items: center; }
|
|
58
|
+
.scenario-actions { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }
|
|
59
|
+
.host-tablist { display: flex; flex-wrap: wrap; gap: 0.35rem; }
|
|
60
|
+
.host-tab {
|
|
61
|
+
display: inline-flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 0.4rem;
|
|
64
|
+
}
|
|
65
|
+
.host-tab[aria-selected="true"] {
|
|
66
|
+
background: color-mix(in srgb, var(--pass) 16%, var(--panel-2));
|
|
67
|
+
border-color: color-mix(in srgb, var(--pass) 40%, var(--border));
|
|
68
|
+
}
|
|
69
|
+
.host-tab[data-status="running"],
|
|
70
|
+
.host-tab[data-status="cancelling"],
|
|
71
|
+
.host-tab[data-status="cancelled"] { color: var(--skip); }
|
|
72
|
+
.host-tab[data-status="passed"] { color: var(--pass); }
|
|
73
|
+
.host-tab[data-status="failed"] { color: var(--fail); }
|
|
74
|
+
.host-tab[data-status="skipped"], .host-tab[data-status="skip"] { color: var(--muted); }
|
|
75
|
+
.host-status { display: inline-flex; align-items: center; }
|
|
52
76
|
.cell-status { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; }
|
|
53
|
-
.live
|
|
54
|
-
.live-
|
|
77
|
+
.scenario-live { display: grid; gap: 0.75rem; }
|
|
78
|
+
.scenario-live[hidden], .host-panel[hidden], .host-empty[hidden] { display: none; }
|
|
79
|
+
.host-panels { display: grid; min-width: 0; }
|
|
80
|
+
.host-panel:not([hidden]) { min-height: 4rem; }
|
|
81
|
+
.host-empty { color: var(--muted); font-size: 0.85rem; margin: 0; }
|
|
82
|
+
.host-panel > .live-cell > h3 { display: none; }
|
|
83
|
+
.live-cell { background: var(--panel-2); border: 1px solid var(--border); border-radius: 12px; padding: 0.75rem; min-width: 0; }
|
|
84
|
+
.cell-result {
|
|
85
|
+
margin-top: 0.65rem;
|
|
86
|
+
border-left: 3px solid var(--border);
|
|
87
|
+
padding: 0.45rem 0.7rem;
|
|
88
|
+
}
|
|
89
|
+
.cell-result.status-passed { border-left-color: var(--pass); }
|
|
90
|
+
.cell-result.status-failed { border-left-color: var(--fail); }
|
|
91
|
+
.cell-result.status-skipped { border-left-color: var(--skip); }
|
|
92
|
+
.cell-result-verdict { font-weight: 650; margin: 0 0 0.25rem; }
|
|
93
|
+
.cell-result.status-passed .cell-result-verdict { color: var(--pass); }
|
|
94
|
+
.cell-result.status-failed .cell-result-verdict { color: var(--fail); }
|
|
95
|
+
.cell-result-metrics { color: var(--muted); font-size: 0.8rem; margin: 0 0 0.35rem; }
|
|
96
|
+
.cell-result-failures, .cell-result-judges {
|
|
97
|
+
list-style: none;
|
|
98
|
+
margin: 0;
|
|
99
|
+
padding: 0;
|
|
100
|
+
display: grid;
|
|
101
|
+
gap: 0.35rem;
|
|
102
|
+
}
|
|
103
|
+
.cell-result-judges { margin-top: 0.45rem; }
|
|
104
|
+
.cell-result-failures li, .cell-result-judges li {
|
|
105
|
+
font-size: 0.85rem;
|
|
106
|
+
white-space: pre-wrap;
|
|
107
|
+
word-break: break-word;
|
|
108
|
+
}
|
|
109
|
+
.judge-pass { color: var(--pass); }
|
|
110
|
+
.judge-fail { color: var(--fail); }
|
|
55
111
|
.live-cell h3 { margin-bottom: 0.45rem; }
|
|
112
|
+
.live-status {
|
|
113
|
+
list-style: none;
|
|
114
|
+
margin: 0 0 0.55rem;
|
|
115
|
+
padding: 0;
|
|
116
|
+
display: grid;
|
|
117
|
+
gap: 0.2rem;
|
|
118
|
+
max-height: 9rem;
|
|
119
|
+
overflow: auto;
|
|
120
|
+
font-size: 0.75rem;
|
|
121
|
+
color: var(--muted);
|
|
122
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
123
|
+
}
|
|
124
|
+
.live-status li {
|
|
125
|
+
border-left: 2px solid var(--border);
|
|
126
|
+
padding: 0.12rem 0 0.12rem 0.5rem;
|
|
127
|
+
white-space: pre-wrap;
|
|
128
|
+
word-break: break-word;
|
|
129
|
+
}
|
|
56
130
|
.run-banner { font-size: 0.85rem; color: var(--muted); }
|
|
131
|
+
.run-progress {
|
|
132
|
+
display: grid;
|
|
133
|
+
gap: 0.55rem;
|
|
134
|
+
background: var(--panel);
|
|
135
|
+
border: 1px solid var(--border);
|
|
136
|
+
border-radius: 12px;
|
|
137
|
+
padding: 0.8rem 1rem;
|
|
138
|
+
margin-bottom: 1.25rem;
|
|
139
|
+
}
|
|
140
|
+
.run-progress[hidden] { display: none; }
|
|
141
|
+
.run-progress-head, .run-progress-counts {
|
|
142
|
+
display: flex;
|
|
143
|
+
flex-wrap: wrap;
|
|
144
|
+
align-items: center;
|
|
145
|
+
justify-content: space-between;
|
|
146
|
+
gap: 0.4rem 1rem;
|
|
147
|
+
}
|
|
148
|
+
.run-progress-title { margin: 0; font-weight: 700; }
|
|
149
|
+
.run-progress-counts { justify-content: flex-start; color: var(--muted); font-size: 0.8rem; }
|
|
150
|
+
.run-progress-counts strong { color: var(--text); font-variant-numeric: tabular-nums; }
|
|
151
|
+
.run-progress-counts .progress-passed strong { color: var(--pass); }
|
|
152
|
+
.run-progress-counts .progress-failed strong { color: var(--fail); }
|
|
153
|
+
.run-progress-counts .progress-skipped strong { color: var(--skip); }
|
|
154
|
+
.run-progress-track {
|
|
155
|
+
height: 0.7rem;
|
|
156
|
+
overflow: hidden;
|
|
157
|
+
border-radius: 999px;
|
|
158
|
+
background: var(--panel-2);
|
|
159
|
+
border: 1px solid var(--border);
|
|
160
|
+
}
|
|
161
|
+
.run-progress-fill {
|
|
162
|
+
display: block;
|
|
163
|
+
width: 0;
|
|
164
|
+
height: 100%;
|
|
165
|
+
background: var(--pass);
|
|
166
|
+
transition: width 180ms ease-out;
|
|
167
|
+
}
|
|
168
|
+
.run-progress[data-has-failures="true"] .run-progress-fill {
|
|
169
|
+
background: linear-gradient(90deg, var(--pass), var(--fail));
|
|
170
|
+
}
|
|
171
|
+
.compare-tab[data-status="running"] { color: var(--skip); }
|
|
172
|
+
.compare-tab[data-status="passed"] { color: var(--pass); }
|
|
173
|
+
.compare-tab[data-status="failed"] { color: var(--fail); }
|
|
174
|
+
.compare-tab[data-status="skipped"] { color: var(--muted); }
|
|
175
|
+
.compare-tabs .compare-arms,
|
|
176
|
+
.compare-tabs .compare-arms[data-arm-count] { grid-template-columns: minmax(0, 1fr); }
|
|
177
|
+
.compare-tabs .live-cell[hidden] { display: none; }
|
|
178
|
+
.compare-tabs .live-cell > h3 { display: none; }
|
|
179
|
+
.chat-running-row[hidden] { display: none; }
|
|
180
|
+
.context-path {
|
|
181
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
182
|
+
font-size: 0.82rem;
|
|
183
|
+
display: block;
|
|
184
|
+
}
|
|
185
|
+
.context-why {
|
|
186
|
+
color: var(--muted);
|
|
187
|
+
font-size: 0.8rem;
|
|
188
|
+
display: block;
|
|
189
|
+
margin-top: 0.15rem;
|
|
190
|
+
}
|
|
191
|
+
.context-panel {
|
|
192
|
+
margin: 0 0 0.75rem;
|
|
193
|
+
border: 1px solid var(--border);
|
|
194
|
+
border-radius: 10px;
|
|
195
|
+
background: color-mix(in srgb, var(--skip) 10%, var(--panel-2));
|
|
196
|
+
padding: 0.55rem 0.75rem;
|
|
197
|
+
}
|
|
198
|
+
.context-panel-summary {
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: baseline;
|
|
201
|
+
justify-content: space-between;
|
|
202
|
+
gap: 0.75rem;
|
|
203
|
+
cursor: pointer;
|
|
204
|
+
font-weight: 700;
|
|
205
|
+
}
|
|
206
|
+
.context-panel-count { color: var(--muted); font-weight: 600; font-size: 0.82rem; }
|
|
207
|
+
.context-flow { display: flex; align-items: center; gap: 0.35rem; margin: 0.55rem 0; flex-wrap: wrap; }
|
|
208
|
+
.context-flow-node { border: 1px solid var(--border); border-radius: 999px; background: var(--panel); padding: 0.2rem 0.55rem; font-size: 0.78rem; font-weight: 650; }
|
|
209
|
+
.context-flow-mode { border-color: var(--skip); }
|
|
210
|
+
.context-flow-arrow { color: var(--muted); }
|
|
211
|
+
.context-host-input { border: 1px solid var(--border); border-radius: 8px; background: var(--panel); padding: 0.4rem 0.55rem; margin-top: 0.45rem; }
|
|
212
|
+
.context-host-input summary { cursor: pointer; font-size: 0.82rem; font-weight: 700; }
|
|
213
|
+
.context-panel-lede, .context-empty { color: var(--muted); font-size: 0.85rem; margin: 0.45rem 0 0; }
|
|
214
|
+
.context-files { list-style: none; margin: 0.5rem 0 0; padding: 0; display: grid; gap: 0.4rem; }
|
|
215
|
+
.context-file {
|
|
216
|
+
border: 1px solid var(--border);
|
|
217
|
+
border-radius: 8px;
|
|
218
|
+
background: var(--panel);
|
|
219
|
+
padding: 0.4rem 0.55rem;
|
|
220
|
+
}
|
|
221
|
+
.context-file[data-reason="contextSources"] { border-left: 3px solid var(--skip); }
|
|
222
|
+
.context-file[data-reason="skills"] { border-left: 3px solid var(--tool); }
|
|
223
|
+
.context-file[data-reason="profile"] { border-left: 3px solid var(--muted); }
|
|
224
|
+
.context-file summary { cursor: pointer; }
|
|
225
|
+
.context-body {
|
|
226
|
+
margin: 0.45rem 0 0;
|
|
227
|
+
white-space: pre-wrap;
|
|
228
|
+
overflow-wrap: break-word;
|
|
229
|
+
font-size: 0.82rem;
|
|
230
|
+
line-height: 1.45;
|
|
231
|
+
max-height: 18rem;
|
|
232
|
+
overflow: auto;
|
|
233
|
+
}
|
|
234
|
+
.chat-running {
|
|
235
|
+
display: inline-flex;
|
|
236
|
+
align-items: center;
|
|
237
|
+
gap: 0.45rem;
|
|
238
|
+
color: var(--muted);
|
|
239
|
+
font-size: 0.85rem;
|
|
240
|
+
padding: 0.2rem 0.1rem;
|
|
241
|
+
}
|
|
242
|
+
.chat-progress {
|
|
243
|
+
display: inline-flex;
|
|
244
|
+
gap: 0.18rem;
|
|
245
|
+
}
|
|
246
|
+
.chat-progress span {
|
|
247
|
+
width: 0.32rem;
|
|
248
|
+
height: 0.32rem;
|
|
249
|
+
border-radius: 999px;
|
|
250
|
+
background: var(--skip);
|
|
251
|
+
animation: chat-progress 1s ease-in-out infinite;
|
|
252
|
+
}
|
|
253
|
+
.chat-progress span:nth-child(2) { animation-delay: 0.15s; }
|
|
254
|
+
.chat-progress span:nth-child(3) { animation-delay: 0.3s; }
|
|
255
|
+
@keyframes chat-progress {
|
|
256
|
+
0%, 80%, 100% { opacity: 0.2; }
|
|
257
|
+
40% { opacity: 1; }
|
|
258
|
+
}
|
|
259
|
+
.bubble.is-streaming .bubble-text::after {
|
|
260
|
+
content: "▍";
|
|
261
|
+
margin-left: 0.12rem;
|
|
262
|
+
color: var(--pass);
|
|
263
|
+
animation: chat-caret 1s step-end infinite;
|
|
264
|
+
}
|
|
265
|
+
@keyframes chat-caret {
|
|
266
|
+
50% { opacity: 0; }
|
|
267
|
+
}
|
|
268
|
+
@media (prefers-reduced-motion: reduce) {
|
|
269
|
+
.chat-progress span, .bubble.is-streaming .bubble-text::after { animation: none; }
|
|
270
|
+
}
|
|
57
271
|
`;
|
|
58
272
|
}
|
|
59
273
|
function rubricSummary(scenario) {
|
|
60
|
-
const parts = [
|
|
274
|
+
const parts = [
|
|
275
|
+
scenario.contextMode === "host-native" ? "host-native context" : "harness preamble",
|
|
276
|
+
];
|
|
61
277
|
const rubric = scenario.rubric;
|
|
62
278
|
if (rubric.must?.length) {
|
|
63
279
|
parts.push(`must ${rubric.must.join(", ")}`);
|
|
@@ -85,43 +301,79 @@ function rubricSummary(scenario) {
|
|
|
85
301
|
if (rubric.judge) {
|
|
86
302
|
parts.push("judge");
|
|
87
303
|
}
|
|
304
|
+
if (scenario.contextSources?.length) {
|
|
305
|
+
parts.push(`contextSources ${scenario.contextSources.join(", ")}`);
|
|
306
|
+
}
|
|
88
307
|
return parts.join(" · ");
|
|
89
308
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
309
|
+
function defaultScenarioHost(suite, scenario) {
|
|
310
|
+
if (scenario.host && suite.hosts.includes(scenario.host)) {
|
|
311
|
+
return scenario.host;
|
|
312
|
+
}
|
|
313
|
+
if (suite.hosts.includes("cursor")) {
|
|
314
|
+
return "cursor";
|
|
315
|
+
}
|
|
316
|
+
return suite.hosts[0] ?? "cursor";
|
|
317
|
+
}
|
|
318
|
+
function hostIsSkipped(scenario, host) {
|
|
319
|
+
return Boolean(scenario.skip || (scenario.host && scenario.host !== host));
|
|
320
|
+
}
|
|
321
|
+
function renderHostChrome(suite, scenario, selectedHost) {
|
|
322
|
+
if (suite.hosts.length <= 1) {
|
|
323
|
+
const host = suite.hosts[0] ?? selectedHost;
|
|
324
|
+
const skipped = hostIsSkipped(scenario, host);
|
|
325
|
+
return `<div class="host-status" data-cell="${escapeHtml(`${suite.name}::${scenario.name}::${host}`)}">
|
|
326
|
+
<span class="cell-status muted">${skipped ? "skip" : "idle"}</span>
|
|
327
|
+
</div>`;
|
|
328
|
+
}
|
|
329
|
+
const tabs = suite.hosts
|
|
330
|
+
.map((host) => {
|
|
331
|
+
const skipped = hostIsSkipped(scenario, host);
|
|
332
|
+
const selected = host === selectedHost;
|
|
333
|
+
return `<button type="button" class="host-tab" role="tab" aria-label="${escapeHtml(host)}" data-cell="${escapeHtml(`${suite.name}::${scenario.name}::${host}`)}" data-host="${escapeHtml(host)}" aria-selected="${selected ? "true" : "false"}"${skipped ? " disabled" : ""}>${escapeHtml(host)} <span class="cell-status muted" aria-hidden="true">${skipped ? "skip" : "idle"}</span></button>`;
|
|
334
|
+
})
|
|
335
|
+
.join("");
|
|
336
|
+
return `<div class="host-tablist" role="tablist">${tabs}</div>`;
|
|
337
|
+
}
|
|
338
|
+
function renderHostPanels(suite, selectedHost) {
|
|
339
|
+
return suite.hosts
|
|
92
340
|
.map((host) => {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
return `<td data-cell="${escapeHtml(`${suite.name}::${scenario.name}::${host}`)}">
|
|
96
|
-
<div class="cell-actions">
|
|
97
|
-
<button class="primary run-cell" data-suite="${escapeHtml(suite.name)}" data-scenario="${escapeHtml(scenario.name)}" data-host="${escapeHtml(host)}"${skipped ? " disabled" : ""}>Run</button>
|
|
98
|
-
<span class="cell-status muted">${skipped ? "skip" : "idle"}</span>
|
|
99
|
-
</div>
|
|
100
|
-
</td>`;
|
|
341
|
+
const selected = host === selectedHost;
|
|
342
|
+
return `<div class="host-panel" data-host-panel="${escapeHtml(host)}" role="tabpanel"${selected ? "" : " hidden"}><p class="host-empty">No run yet.</p></div>`;
|
|
101
343
|
})
|
|
102
344
|
.join("");
|
|
345
|
+
}
|
|
346
|
+
function renderScenarioCard(suite, scenario) {
|
|
347
|
+
const selectedHost = defaultScenarioHost(suite, scenario);
|
|
348
|
+
const runDisabled = hostIsSkipped(scenario, selectedHost);
|
|
103
349
|
const compare = scenario.compare
|
|
104
350
|
? scenario.compare
|
|
105
351
|
.map((arm) => `<p class="arm-note">${escapeHtml(arm.label)}: ${escapeHtml(arm.description ?? arm.prompt ?? "")}</p>`)
|
|
106
352
|
.join("")
|
|
107
353
|
: "";
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
354
|
+
const key = escapeHtml(`${suite.name}::${scenario.name}`);
|
|
355
|
+
return `<article class="scenario-card" data-scenario-card="${key}">
|
|
356
|
+
<div class="scenario-detail">
|
|
357
|
+
<strong>${escapeHtml(scenario.name)}</strong>
|
|
358
|
+
${scenario.description ? `<p class="scenario-lede">${escapeHtml(scenario.description)}</p>` : ""}
|
|
359
|
+
<p class="prompt-preview">${escapeHtml(scenario.prompt)}</p>
|
|
360
|
+
<p class="rubric-line">${escapeHtml(rubricSummary(scenario))}</p>
|
|
361
|
+
${compare}
|
|
362
|
+
</div>
|
|
363
|
+
<div class="scenario-toolbar">
|
|
364
|
+
${renderHostChrome(suite, scenario, selectedHost)}
|
|
365
|
+
<div class="scenario-actions">
|
|
366
|
+
<button class="primary run-cell" data-suite="${escapeHtml(suite.name)}" data-scenario="${escapeHtml(scenario.name)}" data-host="${escapeHtml(selectedHost)}"${runDisabled ? " disabled" : ""}>Run</button>
|
|
367
|
+
<button class="run-row" data-suite="${escapeHtml(suite.name)}" data-scenario="${escapeHtml(scenario.name)}"${scenario.skip ? " disabled" : ""}>Run selected hosts</button>
|
|
117
368
|
</div>
|
|
118
|
-
</
|
|
119
|
-
${
|
|
120
|
-
|
|
369
|
+
</div>
|
|
370
|
+
<div class="scenario-live" hidden data-live-row="${key}" data-live-slot="${key}">
|
|
371
|
+
<div class="host-panels">${renderHostPanels(suite, selectedHost)}</div>
|
|
372
|
+
</div>
|
|
373
|
+
</article>`;
|
|
121
374
|
}
|
|
122
375
|
function renderSuiteSection(suite) {
|
|
123
|
-
const
|
|
124
|
-
const rows = suite.scenarios.map((scenario) => renderScenarioRow(suite, scenario)).join("");
|
|
376
|
+
const cards = suite.scenarios.map((scenario) => renderScenarioCard(suite, scenario)).join("");
|
|
125
377
|
return `
|
|
126
378
|
<section class="suite" data-suite="${escapeHtml(suite.name)}">
|
|
127
379
|
<header class="suite-header">
|
|
@@ -131,10 +383,7 @@ function renderSuiteSection(suite) {
|
|
|
131
383
|
</div>
|
|
132
384
|
<button class="run-suite" data-suite="${escapeHtml(suite.name)}">Run suite</button>
|
|
133
385
|
</header>
|
|
134
|
-
<
|
|
135
|
-
<thead><tr><th>Scenario</th>${columns}</tr></thead>
|
|
136
|
-
<tbody>${rows}</tbody>
|
|
137
|
-
</table>
|
|
386
|
+
<div class="scenario-list">${cards}</div>
|
|
138
387
|
</section>`;
|
|
139
388
|
}
|
|
140
389
|
function clientScript() {
|
|
@@ -143,6 +392,8 @@ function clientScript() {
|
|
|
143
392
|
var catalog = JSON.parse(document.getElementById("catalog-data").textContent);
|
|
144
393
|
var source = null;
|
|
145
394
|
var runId = null;
|
|
395
|
+
var cancelRequested = false;
|
|
396
|
+
var progress = { total: 0, completed: 0, passed: 0, failed: 0, skipped: 0, seen: {}, experiments: {} };
|
|
146
397
|
|
|
147
398
|
function selectedHosts() {
|
|
148
399
|
return Array.prototype.map.call(document.querySelectorAll("[data-host-toggle]:checked"), function (box) {
|
|
@@ -158,6 +409,134 @@ function clientScript() {
|
|
|
158
409
|
document.getElementById("run-banner").textContent = text;
|
|
159
410
|
}
|
|
160
411
|
|
|
412
|
+
function runnableTargets(body) {
|
|
413
|
+
var requestedHosts = body.hosts && body.hosts.length ? body.hosts : catalog.defaultSelectedHosts;
|
|
414
|
+
var total = 0;
|
|
415
|
+
catalog.suites.forEach(function (suite) {
|
|
416
|
+
if (body.suite && suite.name !== body.suite) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
suite.scenarios.forEach(function (scenario) {
|
|
420
|
+
if (body.scenario && scenario.name !== body.scenario) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (scenario.skip) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
requestedHosts.forEach(function (host) {
|
|
427
|
+
if (suite.hosts.indexOf(host) === -1 || (scenario.host && scenario.host !== host)) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
total += 1;
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
return total;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function renderProgress() {
|
|
438
|
+
var box = document.getElementById("run-progress");
|
|
439
|
+
var total = Math.max(progress.total, progress.completed);
|
|
440
|
+
var remaining = Math.max(0, total - progress.completed);
|
|
441
|
+
box.hidden = total === 0;
|
|
442
|
+
box.setAttribute("data-has-failures", progress.failed > 0 ? "true" : "false");
|
|
443
|
+
var testLabel = total === 1 ? "test" : "tests";
|
|
444
|
+
box.querySelector(".run-progress-title").textContent = progress.completed + " of " + total + " " + testLabel + " finished";
|
|
445
|
+
box.querySelector(".progress-passed strong").textContent = String(progress.passed);
|
|
446
|
+
box.querySelector(".progress-failed strong").textContent = String(progress.failed);
|
|
447
|
+
box.querySelector(".progress-skipped strong").textContent = String(progress.skipped);
|
|
448
|
+
box.querySelector(".progress-remaining strong").textContent = String(remaining);
|
|
449
|
+
var percent = total > 0 ? Math.round((progress.completed / total) * 100) : 0;
|
|
450
|
+
var fill = box.querySelector(".run-progress-fill");
|
|
451
|
+
fill.style.width = percent + "%";
|
|
452
|
+
fill.parentNode.setAttribute("aria-valuenow", String(progress.completed));
|
|
453
|
+
fill.parentNode.setAttribute("aria-valuemax", String(total));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function resetProgress(body) {
|
|
457
|
+
progress = { total: runnableTargets(body), completed: 0, passed: 0, failed: 0, skipped: 0, seen: {}, experiments: {} };
|
|
458
|
+
renderProgress();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function recordProgress(event) {
|
|
462
|
+
var key = cellKey(event);
|
|
463
|
+
if (progress.seen[key]) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
progress.seen[key] = true;
|
|
467
|
+
var experiment = experimentState(event);
|
|
468
|
+
experiment.cells[event.arm || "_"] = event;
|
|
469
|
+
var scenario = findScenario(event.suite, event.scenario);
|
|
470
|
+
var expectedArms = scenario && scenario.compare && scenario.compare.length ? scenario.compare.length : 1;
|
|
471
|
+
if (Object.keys(experiment.cells).length < expectedArms || experiment.counted) {
|
|
472
|
+
renderProgress();
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
experiment.counted = true;
|
|
476
|
+
progress.completed += 1;
|
|
477
|
+
var outcome = experimentOutcome(scenario, experiment.cells);
|
|
478
|
+
if (outcome === "skipped") progress.skipped += 1;
|
|
479
|
+
else if (outcome === "passed") progress.passed += 1;
|
|
480
|
+
else progress.failed += 1;
|
|
481
|
+
renderProgress();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function finishProgress(event) {
|
|
485
|
+
// The stream reports one cell per comparison arm. Progress is based on
|
|
486
|
+
// the completed scenario/host experiment, so expected failed controls do
|
|
487
|
+
// not turn a passing comparison into a red suite result.
|
|
488
|
+
progress.total = Math.max(progress.total, progress.completed);
|
|
489
|
+
renderProgress();
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function experimentKey(event) {
|
|
493
|
+
return [event.suite, event.scenario, event.host].join("::");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function experimentState(event) {
|
|
497
|
+
var key = experimentKey(event);
|
|
498
|
+
if (!progress.experiments[key]) {
|
|
499
|
+
progress.experiments[key] = { cells: {}, counted: false };
|
|
500
|
+
}
|
|
501
|
+
return progress.experiments[key];
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function experimentOutcome(scenario, cells) {
|
|
505
|
+
var events = Object.keys(cells).map(function (id) { return cells[id]; });
|
|
506
|
+
if (events.some(function (event) { return event.skipped; })) return "skipped";
|
|
507
|
+
var byArm = {};
|
|
508
|
+
events.forEach(function (event) { byArm[event.arm || "_"] = event; });
|
|
509
|
+
if (!scenario || !scenario.compare || !scenario.compare.length) {
|
|
510
|
+
return events.every(function (event) { return event.passed; }) ? "passed" : "failed";
|
|
511
|
+
}
|
|
512
|
+
var gates = scenario.gates || [];
|
|
513
|
+
var behaviorOk = events.every(function (event) {
|
|
514
|
+
return event.passed || (event.failures || []).every(function (failure) {
|
|
515
|
+
return gates.some(function (gate) {
|
|
516
|
+
return gate.metric === "outcome" && gate.arm === event.arm && gate.operator === "equal" && gate.value === "fail";
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
var gatesOk = gates.every(function (gate) {
|
|
521
|
+
if (gate.metric === "outcome" && gate.arm) {
|
|
522
|
+
var event = byArm[gate.arm];
|
|
523
|
+
if (!event) return false;
|
|
524
|
+
var actual = event.passed ? "pass" : "fail";
|
|
525
|
+
return gate.operator === "equal" ? actual === gate.value : false;
|
|
526
|
+
}
|
|
527
|
+
if (["turns", "tokens", "tools", "durationMs"].indexOf(gate.metric) !== -1 && gate.winner && gate.loser) {
|
|
528
|
+
var winner = byArm[gate.winner];
|
|
529
|
+
var loser = byArm[gate.loser];
|
|
530
|
+
var field = gate.metric === "durationMs" ? "durationMs" : gate.metric;
|
|
531
|
+
var winnerValue = winner && winner.metrics && winner.metrics[field];
|
|
532
|
+
var loserValue = loser && loser.metrics && loser.metrics[field];
|
|
533
|
+
return typeof winnerValue === "number" && typeof loserValue === "number" && winnerValue < loserValue;
|
|
534
|
+
}
|
|
535
|
+
return true;
|
|
536
|
+
});
|
|
537
|
+
return behaviorOk && gatesOk ? "passed" : "failed";
|
|
538
|
+
}
|
|
539
|
+
|
|
161
540
|
function cellKey(event) {
|
|
162
541
|
return [event.suite, event.scenario, event.host, event.arm || "_"].join("::");
|
|
163
542
|
}
|
|
@@ -166,41 +545,376 @@ function clientScript() {
|
|
|
166
545
|
return "live-" + cellKey(event).replace(/[^a-z0-9]+/gi, "-");
|
|
167
546
|
}
|
|
168
547
|
|
|
548
|
+
function compareWrapId(event) {
|
|
549
|
+
return ("live-wrap-" + event.suite + "-" + event.scenario + "-" + event.host).replace(/[^a-z0-9]+/gi, "-");
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function selectCompareTab(wrap, armId) {
|
|
553
|
+
wrap.querySelectorAll(".compare-tab").forEach(function (tab) {
|
|
554
|
+
var on = tab.getAttribute("data-arm") === armId;
|
|
555
|
+
tab.setAttribute("aria-selected", on ? "true" : "false");
|
|
556
|
+
});
|
|
557
|
+
wrap.querySelectorAll(".live-cell").forEach(function (panel) {
|
|
558
|
+
panel.hidden = panel.getAttribute("data-arm") !== armId;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function scenarioCard(event) {
|
|
563
|
+
return document.querySelector('[data-scenario-card="' + event.suite + "::" + event.scenario + '"]');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function selectHostTab(card, host) {
|
|
567
|
+
if (!card || !host) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
card.querySelectorAll(".host-tab").forEach(function (tab) {
|
|
571
|
+
var on = tab.getAttribute("data-host") === host;
|
|
572
|
+
tab.setAttribute("aria-selected", on ? "true" : "false");
|
|
573
|
+
});
|
|
574
|
+
card.querySelectorAll("[data-host-panel]").forEach(function (panel) {
|
|
575
|
+
panel.hidden = panel.getAttribute("data-host-panel") !== host;
|
|
576
|
+
});
|
|
577
|
+
var run = card.querySelector("button.run-cell");
|
|
578
|
+
if (run) {
|
|
579
|
+
run.setAttribute("data-host", host);
|
|
580
|
+
var tab = card.querySelector('.host-tab[data-host="' + host + '"]');
|
|
581
|
+
if (tab) {
|
|
582
|
+
run.disabled = tab.disabled;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function revealLive(event) {
|
|
588
|
+
var card = scenarioCard(event);
|
|
589
|
+
if (!card) {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
var live = card.querySelector(".scenario-live");
|
|
593
|
+
if (live) {
|
|
594
|
+
live.hidden = false;
|
|
595
|
+
}
|
|
596
|
+
if (card.dataset.userPickedHost === "1") {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (card.dataset.followHost && card.dataset.followHost !== event.host) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
card.dataset.followHost = event.host;
|
|
603
|
+
selectHostTab(card, event.host);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function hostPanel(event) {
|
|
607
|
+
var card = scenarioCard(event);
|
|
608
|
+
return card ? card.querySelector('[data-host-panel="' + event.host + '"]') : null;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function markArmTab(wrap, armId, status) {
|
|
612
|
+
var tab = wrap.querySelector('.compare-tab[data-arm="' + armId + '"]');
|
|
613
|
+
if (tab) {
|
|
614
|
+
tab.setAttribute("data-status", status);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function createLiveArticle(event, title) {
|
|
619
|
+
var article = document.createElement("article");
|
|
620
|
+
article.className = "live-cell";
|
|
621
|
+
article.id = paneId(event);
|
|
622
|
+
article.dataset.chatKey = cellKey(event);
|
|
623
|
+
article.innerHTML = "<h3></h3><ol class=\\"live-status\\"></ol><div class=\\"chat\\"></div>";
|
|
624
|
+
article.querySelector("h3").textContent = title;
|
|
625
|
+
return article;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
var openChats = {};
|
|
629
|
+
|
|
630
|
+
function resetArticle(article) {
|
|
631
|
+
if (!article) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
var status = article.querySelector(".live-status");
|
|
635
|
+
if (status) {
|
|
636
|
+
status.replaceChildren();
|
|
637
|
+
}
|
|
638
|
+
var chat = article.querySelector(".chat");
|
|
639
|
+
if (chat) {
|
|
640
|
+
chat.replaceChildren();
|
|
641
|
+
}
|
|
642
|
+
var result = article.querySelector(".cell-result");
|
|
643
|
+
if (result) {
|
|
644
|
+
result.remove();
|
|
645
|
+
}
|
|
646
|
+
var contextPanel = article.querySelector(".context-panel");
|
|
647
|
+
if (contextPanel) {
|
|
648
|
+
contextPanel.remove();
|
|
649
|
+
}
|
|
650
|
+
var panel = article.closest("[data-host-panel]");
|
|
651
|
+
var empty = panel ? panel.querySelector(".host-empty") : null;
|
|
652
|
+
if (empty) {
|
|
653
|
+
empty.hidden = false;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function chatIsOpen(event) {
|
|
658
|
+
return !!openChats[cellKey(event)];
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function startChat(event) {
|
|
662
|
+
openChats[cellKey(event)] = true;
|
|
663
|
+
delete assistantBodies[cellKey(event)];
|
|
664
|
+
delete finishedMetrics[cellKey(event)];
|
|
665
|
+
ensurePane(event);
|
|
666
|
+
resetArticle(document.getElementById(paneId(event)));
|
|
667
|
+
var panel = hostPanel(event);
|
|
668
|
+
var empty = panel ? panel.querySelector(".host-empty") : null;
|
|
669
|
+
if (empty) {
|
|
670
|
+
empty.hidden = true;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function markRunningIdle() {
|
|
675
|
+
document.querySelectorAll("[data-cell]").forEach(function (node) {
|
|
676
|
+
var status = node.querySelector(".cell-status");
|
|
677
|
+
if (!status) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
var label = status.textContent;
|
|
681
|
+
if (label !== "running" && label !== "starting") {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
status.textContent = "idle";
|
|
685
|
+
status.className = "cell-status muted";
|
|
686
|
+
if (node.classList.contains("host-tab")) {
|
|
687
|
+
node.setAttribute("data-status", "idle");
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
document.querySelectorAll(".compare-tab[data-status='running']").forEach(function (tab) {
|
|
691
|
+
tab.setAttribute("data-status", "idle");
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function closeAllChats() {
|
|
696
|
+
Object.keys(openChats).forEach(function (key) {
|
|
697
|
+
resetArticle(document.getElementById("live-" + key.replace(/[^a-z0-9]+/gi, "-")));
|
|
698
|
+
});
|
|
699
|
+
openChats = {};
|
|
700
|
+
assistantBodies = {};
|
|
701
|
+
hideAllRunning();
|
|
702
|
+
markRunningIdle();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function setCancelButton(label, disabled) {
|
|
706
|
+
var button = document.getElementById("cancel-run");
|
|
707
|
+
button.textContent = label;
|
|
708
|
+
button.disabled = disabled;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function showCancelledNote(article, text) {
|
|
712
|
+
if (!article) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
var existing = article.querySelector(".cell-result");
|
|
716
|
+
if (existing) {
|
|
717
|
+
existing.remove();
|
|
718
|
+
}
|
|
719
|
+
var box = document.createElement("div");
|
|
720
|
+
box.className = "cell-result status-skipped";
|
|
721
|
+
var verdict = document.createElement("p");
|
|
722
|
+
verdict.className = "cell-result-verdict";
|
|
723
|
+
verdict.textContent = text;
|
|
724
|
+
box.appendChild(verdict);
|
|
725
|
+
article.appendChild(box);
|
|
726
|
+
var panel = article.closest("[data-host-panel]");
|
|
727
|
+
var empty = panel ? panel.querySelector(".host-empty") : null;
|
|
728
|
+
if (empty) {
|
|
729
|
+
empty.hidden = true;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function cancelOpenChats() {
|
|
734
|
+
document.querySelectorAll(".cell-status").forEach(function (status) {
|
|
735
|
+
if (status.textContent !== "running" && status.textContent !== "starting") {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
status.textContent = "cancelling";
|
|
739
|
+
status.className = "cell-status status-skipped";
|
|
740
|
+
var tab = status.closest(".host-tab");
|
|
741
|
+
if (tab) {
|
|
742
|
+
tab.setAttribute("data-status", "cancelling");
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
document.querySelectorAll(".compare-tab[data-status='running']").forEach(function (tab) {
|
|
746
|
+
tab.setAttribute("data-status", "cancelling");
|
|
747
|
+
});
|
|
748
|
+
hideAllRunning();
|
|
749
|
+
document.querySelectorAll("[data-cell]").forEach(function (node) {
|
|
750
|
+
var status = node.querySelector(".cell-status");
|
|
751
|
+
if (!status || status.textContent !== "cancelling") {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
var card = node.closest("[data-scenario-card]");
|
|
755
|
+
if (!card) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
var live = card.querySelector(".scenario-live");
|
|
759
|
+
if (live) {
|
|
760
|
+
live.hidden = false;
|
|
761
|
+
}
|
|
762
|
+
var host = node.getAttribute("data-host") || (node.getAttribute("data-cell") || "").split("::")[2];
|
|
763
|
+
var panel = card.querySelector('[data-host-panel="' + host + '"]');
|
|
764
|
+
if (!panel) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
var articles = panel.querySelectorAll("article.live-cell");
|
|
768
|
+
if (articles.length === 0) {
|
|
769
|
+
showCancelledNote(panel, "Cancelled. Stopping the host agent.");
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
articles.forEach(function (article) {
|
|
773
|
+
var key = article.dataset.chatKey;
|
|
774
|
+
resetArticle(article);
|
|
775
|
+
showCancelledNote(article, "Cancelled. Stopping the host agent.");
|
|
776
|
+
if (key) {
|
|
777
|
+
delete openChats[key];
|
|
778
|
+
delete assistantBodies[key];
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function markCancelledCells() {
|
|
785
|
+
document.querySelectorAll(".cell-status").forEach(function (status) {
|
|
786
|
+
if (status.textContent === "cancelling") {
|
|
787
|
+
status.textContent = "cancelled";
|
|
788
|
+
status.className = "cell-status status-skipped";
|
|
789
|
+
var tab = status.closest(".host-tab");
|
|
790
|
+
if (tab) {
|
|
791
|
+
tab.setAttribute("data-status", "cancelled");
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
document.querySelectorAll(".compare-tab[data-status='cancelling']").forEach(function (tab) {
|
|
796
|
+
tab.setAttribute("data-status", "cancelled");
|
|
797
|
+
});
|
|
798
|
+
document.querySelectorAll(".cell-result-verdict").forEach(function (node) {
|
|
799
|
+
if (node.textContent === "Cancelled. Stopping the host agent.") {
|
|
800
|
+
node.textContent = "Cancelled.";
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function sealAllChats() {
|
|
806
|
+
openChats = {};
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function addArmTab(wrap, list, article, armId, label, selected) {
|
|
810
|
+
article.setAttribute("role", "tabpanel");
|
|
811
|
+
article.setAttribute("data-arm", armId);
|
|
812
|
+
article.hidden = !selected;
|
|
813
|
+
var tab = document.createElement("button");
|
|
814
|
+
tab.type = "button";
|
|
815
|
+
tab.className = "compare-tab";
|
|
816
|
+
tab.setAttribute("role", "tab");
|
|
817
|
+
tab.setAttribute("data-arm", armId);
|
|
818
|
+
tab.setAttribute("aria-controls", article.id);
|
|
819
|
+
tab.setAttribute("aria-selected", selected ? "true" : "false");
|
|
820
|
+
tab.textContent = label;
|
|
821
|
+
tab.addEventListener("click", function () {
|
|
822
|
+
wrap.dataset.userPicked = "1";
|
|
823
|
+
selectCompareTab(wrap, armId);
|
|
824
|
+
});
|
|
825
|
+
list.appendChild(tab);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function ensureCompareWrap(event, scenario, slot) {
|
|
829
|
+
var wrapId = compareWrapId(event);
|
|
830
|
+
var wrap = document.getElementById(wrapId);
|
|
831
|
+
if (!wrap) {
|
|
832
|
+
wrap = document.createElement("div");
|
|
833
|
+
wrap.className = "compare-layout compare-tabs";
|
|
834
|
+
wrap.id = wrapId;
|
|
835
|
+
var list = document.createElement("div");
|
|
836
|
+
list.className = "compare-tablist";
|
|
837
|
+
list.setAttribute("role", "tablist");
|
|
838
|
+
list.setAttribute("aria-label", "Comparison arms");
|
|
839
|
+
wrap.appendChild(list);
|
|
840
|
+
var arms = document.createElement("div");
|
|
841
|
+
arms.className = "compare-arms";
|
|
842
|
+
if (scenario && scenario.compare) {
|
|
843
|
+
arms.setAttribute("data-arm-count", String(scenario.compare.length));
|
|
844
|
+
}
|
|
845
|
+
wrap.appendChild(arms);
|
|
846
|
+
if (scenario && scenario.compare) {
|
|
847
|
+
scenario.compare.forEach(function (arm, index) {
|
|
848
|
+
var article = createLiveArticle({
|
|
849
|
+
suite: event.suite,
|
|
850
|
+
scenario: event.scenario,
|
|
851
|
+
host: event.host,
|
|
852
|
+
arm: arm.id,
|
|
853
|
+
}, arm.label);
|
|
854
|
+
addArmTab(wrap, list, article, arm.id, arm.label, index === 0);
|
|
855
|
+
arms.appendChild(article);
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (!wrap.parentNode) {
|
|
860
|
+
slot.appendChild(wrap);
|
|
861
|
+
}
|
|
862
|
+
return wrap;
|
|
863
|
+
}
|
|
864
|
+
|
|
169
865
|
function ensurePane(event) {
|
|
170
|
-
var
|
|
171
|
-
var existing = document.getElementById(id);
|
|
866
|
+
var existing = document.getElementById(paneId(event));
|
|
172
867
|
if (existing) {
|
|
868
|
+
revealLive(event);
|
|
173
869
|
return existing.querySelector(".chat");
|
|
174
870
|
}
|
|
175
|
-
var
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
article.id = id;
|
|
179
|
-
var title = event.suite + " / " + event.scenario + " / " + event.host;
|
|
180
|
-
if (event.arm) {
|
|
181
|
-
title += " / arm " + event.arm;
|
|
871
|
+
var slot = hostPanel(event);
|
|
872
|
+
if (!slot) {
|
|
873
|
+
return null;
|
|
182
874
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
875
|
+
revealLive(event);
|
|
876
|
+
var empty = slot.querySelector(".host-empty");
|
|
877
|
+
if (empty) {
|
|
878
|
+
empty.hidden = true;
|
|
879
|
+
}
|
|
880
|
+
var leftover = slot.querySelector(":scope > .cell-result");
|
|
881
|
+
if (leftover) {
|
|
882
|
+
leftover.remove();
|
|
883
|
+
}
|
|
884
|
+
var scenario = findScenario(event.suite, event.scenario);
|
|
186
885
|
if (event.arm) {
|
|
187
|
-
var
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
wrap
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
parent.appendChild(article);
|
|
886
|
+
var wrap = ensureCompareWrap(event, scenario, slot);
|
|
887
|
+
existing = document.getElementById(paneId(event));
|
|
888
|
+
if (existing) {
|
|
889
|
+
if (wrap.dataset.userPicked !== "1") {
|
|
890
|
+
selectCompareTab(wrap, event.arm);
|
|
891
|
+
}
|
|
892
|
+
return existing.querySelector(".chat");
|
|
893
|
+
}
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
var article = createLiveArticle(event, event.host);
|
|
897
|
+
slot.appendChild(article);
|
|
201
898
|
return article.querySelector(".chat");
|
|
202
899
|
}
|
|
203
900
|
|
|
901
|
+
function appendStatus(event, text) {
|
|
902
|
+
var article = document.getElementById(paneId(event));
|
|
903
|
+
var list = article ? article.querySelector(".live-status") : null;
|
|
904
|
+
if (!list) {
|
|
905
|
+
ensurePane(event);
|
|
906
|
+
article = document.getElementById(paneId(event));
|
|
907
|
+
list = article ? article.querySelector(".live-status") : null;
|
|
908
|
+
}
|
|
909
|
+
if (!list) {
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
var item = document.createElement("li");
|
|
913
|
+
item.textContent = text;
|
|
914
|
+
list.appendChild(item);
|
|
915
|
+
list.scrollTop = list.scrollHeight;
|
|
916
|
+
}
|
|
917
|
+
|
|
204
918
|
function appendBubble(chat, role, text) {
|
|
205
919
|
var row = document.createElement("div");
|
|
206
920
|
row.className = "chat-row " + (role === "user" ? "side-right" : "side-left");
|
|
@@ -219,6 +933,204 @@ function clientScript() {
|
|
|
219
933
|
return body;
|
|
220
934
|
}
|
|
221
935
|
|
|
936
|
+
function runningRow(article) {
|
|
937
|
+
return article.querySelector(".chat-running-row");
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function showRunning(event) {
|
|
941
|
+
var article = document.getElementById(paneId(event));
|
|
942
|
+
if (!article) {
|
|
943
|
+
ensurePane(event);
|
|
944
|
+
article = document.getElementById(paneId(event));
|
|
945
|
+
}
|
|
946
|
+
if (!article) {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
var chat = article.querySelector(".chat");
|
|
950
|
+
if (!chat) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
var row = runningRow(article);
|
|
954
|
+
if (!row) {
|
|
955
|
+
row = document.createElement("div");
|
|
956
|
+
row.className = "chat-row side-left chat-running-row";
|
|
957
|
+
row.innerHTML = '<div class="chat-running" aria-live="polite">Running<span class="chat-progress" aria-hidden="true"><span></span><span></span><span></span></span></div>';
|
|
958
|
+
chat.appendChild(row);
|
|
959
|
+
}
|
|
960
|
+
row.hidden = false;
|
|
961
|
+
chat.appendChild(row);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function hideRunning(event) {
|
|
965
|
+
var article = document.getElementById(paneId(event));
|
|
966
|
+
var row = article ? runningRow(article) : null;
|
|
967
|
+
if (row) {
|
|
968
|
+
row.remove();
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function hideAllRunning() {
|
|
973
|
+
document.querySelectorAll(".chat-running-row").forEach(function (row) {
|
|
974
|
+
row.remove();
|
|
975
|
+
});
|
|
976
|
+
document.querySelectorAll(".bubble.is-streaming").forEach(function (bubble) {
|
|
977
|
+
bubble.classList.remove("is-streaming");
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function setStreaming(event, on) {
|
|
982
|
+
var body = assistantBodies[cellKey(event)];
|
|
983
|
+
var bubble = body ? body.parentNode : null;
|
|
984
|
+
if (bubble) {
|
|
985
|
+
if (on) {
|
|
986
|
+
bubble.classList.add("is-streaming");
|
|
987
|
+
} else {
|
|
988
|
+
bubble.classList.remove("is-streaming");
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function appendContextPanel(article, mode, files, hostInput) {
|
|
994
|
+
if (!article) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
var existing = article.querySelector(".context-panel");
|
|
998
|
+
if (existing) {
|
|
999
|
+
existing.remove();
|
|
1000
|
+
}
|
|
1001
|
+
var list = files || [];
|
|
1002
|
+
var panel = document.createElement("details");
|
|
1003
|
+
panel.className = "context-panel";
|
|
1004
|
+
panel.open = true;
|
|
1005
|
+
panel.setAttribute("data-context-mode", mode || "harness-preamble");
|
|
1006
|
+
panel.setAttribute("data-file-count", String(list.length));
|
|
1007
|
+
var summary = document.createElement("summary");
|
|
1008
|
+
summary.className = "context-panel-summary";
|
|
1009
|
+
var title = document.createElement("span");
|
|
1010
|
+
title.className = "context-panel-title";
|
|
1011
|
+
title.textContent = "Context delivery";
|
|
1012
|
+
var count = document.createElement("span");
|
|
1013
|
+
count.className = "context-panel-count";
|
|
1014
|
+
count.textContent = mode === "host-native"
|
|
1015
|
+
? "Host-native"
|
|
1016
|
+
: "Harness preamble · " + (list.length === 1 ? "1 file" : list.length + " files");
|
|
1017
|
+
summary.appendChild(title);
|
|
1018
|
+
summary.appendChild(count);
|
|
1019
|
+
panel.appendChild(summary);
|
|
1020
|
+
|
|
1021
|
+
var flow = document.createElement("div");
|
|
1022
|
+
flow.className = "context-flow";
|
|
1023
|
+
flow.setAttribute("aria-label", "Scenario prompt delivery path");
|
|
1024
|
+
["Scenario", "→", mode === "host-native" ? "Host discovery" : "Harness preamble", "→", "Host"].forEach(function (label, index) {
|
|
1025
|
+
var item = document.createElement("span");
|
|
1026
|
+
item.className = index === 1 || index === 3
|
|
1027
|
+
? "context-flow-arrow"
|
|
1028
|
+
: "context-flow-node" + (index === 2 ? " context-flow-mode" : "");
|
|
1029
|
+
item.textContent = label;
|
|
1030
|
+
flow.appendChild(item);
|
|
1031
|
+
});
|
|
1032
|
+
panel.appendChild(flow);
|
|
1033
|
+
|
|
1034
|
+
if (typeof hostInput === "string") {
|
|
1035
|
+
var input = document.createElement("details");
|
|
1036
|
+
input.className = "context-host-input";
|
|
1037
|
+
input.open = true;
|
|
1038
|
+
var inputSummary = document.createElement("summary");
|
|
1039
|
+
inputSummary.textContent = "Exact submitted user input";
|
|
1040
|
+
var inputBody = document.createElement("pre");
|
|
1041
|
+
inputBody.className = "context-body";
|
|
1042
|
+
inputBody.textContent = hostInput;
|
|
1043
|
+
input.appendChild(inputSummary);
|
|
1044
|
+
input.appendChild(inputBody);
|
|
1045
|
+
panel.appendChild(input);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
if (mode === "host-native") {
|
|
1049
|
+
var nativeLede = document.createElement("p");
|
|
1050
|
+
nativeLede.className = "context-panel-lede";
|
|
1051
|
+
nativeLede.textContent = "No harness preamble. Workspace files remain on disk for the host to discover.";
|
|
1052
|
+
panel.appendChild(nativeLede);
|
|
1053
|
+
var nativeUnknown = document.createElement("p");
|
|
1054
|
+
nativeUnknown.className = "context-empty";
|
|
1055
|
+
nativeUnknown.textContent = "Host-owned system instructions and native discovery are not exposed as one inspectable payload. agent-test does not claim which workspace instructions or skills the host loaded.";
|
|
1056
|
+
panel.appendChild(nativeUnknown);
|
|
1057
|
+
} else if (list.length === 0) {
|
|
1058
|
+
var empty = document.createElement("p");
|
|
1059
|
+
empty.className = "context-empty";
|
|
1060
|
+
empty.textContent = "No preamble files. The agent received the prompt only.";
|
|
1061
|
+
panel.appendChild(empty);
|
|
1062
|
+
} else {
|
|
1063
|
+
var lede = document.createElement("p");
|
|
1064
|
+
lede.className = "context-panel-lede";
|
|
1065
|
+
lede.textContent = "These files were in the host preamble for this run.";
|
|
1066
|
+
panel.appendChild(lede);
|
|
1067
|
+
var ol = document.createElement("ol");
|
|
1068
|
+
ol.className = "context-files";
|
|
1069
|
+
list.forEach(function (file) {
|
|
1070
|
+
var item = document.createElement("li");
|
|
1071
|
+
var details = document.createElement("details");
|
|
1072
|
+
details.className = "context-file";
|
|
1073
|
+
details.open = list.length === 1;
|
|
1074
|
+
details.setAttribute("data-reason", file.reason || "profile");
|
|
1075
|
+
details.setAttribute("data-path", file.path || "context");
|
|
1076
|
+
var fileSummary = document.createElement("summary");
|
|
1077
|
+
var path = document.createElement("span");
|
|
1078
|
+
path.className = "context-path";
|
|
1079
|
+
path.textContent = file.path || "context";
|
|
1080
|
+
var why = document.createElement("span");
|
|
1081
|
+
why.className = "context-why";
|
|
1082
|
+
why.textContent = file.why || "The runner loaded this file into the host preamble.";
|
|
1083
|
+
fileSummary.appendChild(path);
|
|
1084
|
+
fileSummary.appendChild(why);
|
|
1085
|
+
var body = document.createElement("pre");
|
|
1086
|
+
body.className = "context-body";
|
|
1087
|
+
body.textContent = file.text || "";
|
|
1088
|
+
details.appendChild(fileSummary);
|
|
1089
|
+
details.appendChild(body);
|
|
1090
|
+
item.appendChild(details);
|
|
1091
|
+
ol.appendChild(item);
|
|
1092
|
+
});
|
|
1093
|
+
panel.appendChild(ol);
|
|
1094
|
+
}
|
|
1095
|
+
var chat = article.querySelector(".chat");
|
|
1096
|
+
if (chat) {
|
|
1097
|
+
article.insertBefore(panel, chat);
|
|
1098
|
+
} else {
|
|
1099
|
+
article.appendChild(panel);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function appendContextFiles(chat, mode, files, hostInput) {
|
|
1104
|
+
appendContextPanel(chat ? chat.closest("article.live-cell") : null, mode, files, hostInput);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function displayToolPath(path) {
|
|
1108
|
+
var stripped = String(path).replace(/^file:\\/\\//, "").replace(/\\/+$/, "");
|
|
1109
|
+
var workspace = stripped.match(/\\/agent-harness-seal-[^/]+(?:\\/(.*))?$/);
|
|
1110
|
+
if (workspace) {
|
|
1111
|
+
return workspace[1] ? workspace[1] : ".";
|
|
1112
|
+
}
|
|
1113
|
+
var sealed = stripped.match(/\\/(?:\\.agents|\\.claude|\\.codex|AGENTS\\.md|CLAUDE\\.md)(?:\\/|$)/);
|
|
1114
|
+
if (sealed && sealed.index !== undefined) {
|
|
1115
|
+
return stripped.slice(sealed.index + 1);
|
|
1116
|
+
}
|
|
1117
|
+
var parts = stripped.split("/").filter(Boolean);
|
|
1118
|
+
if (parts.length > 3) {
|
|
1119
|
+
return ".../" + parts.slice(-3).join("/");
|
|
1120
|
+
}
|
|
1121
|
+
return stripped;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function formatArgValue(key, value) {
|
|
1125
|
+
var pathKeys = { path: 1, file_path: 1, filePath: 1, target_file: 1, uri: 1, cwd: 1 };
|
|
1126
|
+
if (typeof value === "string" && (pathKeys[key] || value.indexOf("/") === 0 || value.indexOf("file://") === 0)) {
|
|
1127
|
+
return { text: displayToolPath(value), title: value };
|
|
1128
|
+
}
|
|
1129
|
+
var raw = typeof value === "string" ? value : JSON.stringify(value);
|
|
1130
|
+
var oneLine = raw.replace(/\\n/g, " ↵ ");
|
|
1131
|
+
return { text: oneLine.length > 140 ? oneLine.slice(0, 140) + "…" : oneLine };
|
|
1132
|
+
}
|
|
1133
|
+
|
|
222
1134
|
function appendTool(chat, name, args) {
|
|
223
1135
|
var row = document.createElement("div");
|
|
224
1136
|
row.className = "chat-row side-left";
|
|
@@ -241,7 +1153,11 @@ function clientScript() {
|
|
|
241
1153
|
keyEl.className = "tool-arg-key";
|
|
242
1154
|
keyEl.textContent = key;
|
|
243
1155
|
var value = document.createElement("code");
|
|
244
|
-
|
|
1156
|
+
var formatted = formatArgValue(key, args[key]);
|
|
1157
|
+
value.textContent = formatted.text;
|
|
1158
|
+
if (formatted.title && formatted.title !== formatted.text) {
|
|
1159
|
+
value.title = formatted.title;
|
|
1160
|
+
}
|
|
245
1161
|
line.appendChild(keyEl);
|
|
246
1162
|
line.appendChild(value);
|
|
247
1163
|
box.appendChild(line);
|
|
@@ -253,101 +1169,542 @@ function clientScript() {
|
|
|
253
1169
|
}
|
|
254
1170
|
|
|
255
1171
|
var assistantBodies = {};
|
|
1172
|
+
var finishedMetrics = {};
|
|
1173
|
+
|
|
1174
|
+
function findScenario(suiteName, scenarioName) {
|
|
1175
|
+
for (var i = 0; i < catalog.suites.length; i++) {
|
|
1176
|
+
if (catalog.suites[i].name !== suiteName) {
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
var scenarios = catalog.suites[i].scenarios;
|
|
1180
|
+
for (var j = 0; j < scenarios.length; j++) {
|
|
1181
|
+
if (scenarios[j].name === scenarioName) {
|
|
1182
|
+
return scenarios[j];
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function joinLabels(labels) {
|
|
1190
|
+
if (labels.length <= 1) {
|
|
1191
|
+
return labels[0] || "";
|
|
1192
|
+
}
|
|
1193
|
+
if (labels.length === 2) {
|
|
1194
|
+
return labels[0] + " and " + labels[1];
|
|
1195
|
+
}
|
|
1196
|
+
return labels.slice(0, -1).join(", ") + ", and " + labels[labels.length - 1];
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function summarizeMetric(arms, id, label) {
|
|
1200
|
+
var values = arms.map(function (arm) { return arm[id]; });
|
|
1201
|
+
var numeric = values.filter(function (value) { return typeof value === "number"; });
|
|
1202
|
+
if (numeric.length < 2) {
|
|
1203
|
+
return { line: label + ": n/a." };
|
|
1204
|
+
}
|
|
1205
|
+
var lowest = Math.min.apply(null, numeric);
|
|
1206
|
+
var winners = arms.filter(function (arm) { return arm[id] === lowest; });
|
|
1207
|
+
var shown = values.map(function (value) { return typeof value === "number" ? String(value) : "n/a"; }).join(" vs ");
|
|
1208
|
+
if (winners.length > 1) {
|
|
1209
|
+
return {
|
|
1210
|
+
line: arms.length === 2
|
|
1211
|
+
? label + ": tie (" + shown + ")."
|
|
1212
|
+
: label + ": lowest is a tie between " + joinLabels(winners.map(function (arm) { return arm.label; })) + " (" + shown + ").",
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
var winner = winners[0];
|
|
1216
|
+
if (!winner) {
|
|
1217
|
+
return { line: label + ": n/a." };
|
|
1218
|
+
}
|
|
1219
|
+
if (arms.length === 2) {
|
|
1220
|
+
return { line: label + ": " + winner.label + " wins (" + shown + ")." };
|
|
1221
|
+
}
|
|
1222
|
+
return { line: label + ": lowest is " + winner.label + " (" + shown + ")." };
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function summarizeGates(arms, gates) {
|
|
1226
|
+
var byId = {};
|
|
1227
|
+
arms.forEach(function (arm) { byId[arm.id] = arm; });
|
|
1228
|
+
return (gates || []).map(function (gate) {
|
|
1229
|
+
var field = gate.metric === "durationMs" ? "durationMs" : gate.metric;
|
|
1230
|
+
var winner = byId[gate.winner];
|
|
1231
|
+
var loser = byId[gate.loser];
|
|
1232
|
+
if (!winner || !loser || ["turns", "tokens", "tools", "durationMs"].indexOf(field) === -1) {
|
|
1233
|
+
return { passed: true, line: gate.metric + ": scored in the final result." };
|
|
1234
|
+
}
|
|
1235
|
+
var winnerValue = winner[field];
|
|
1236
|
+
var loserValue = loser[field];
|
|
1237
|
+
if (typeof winnerValue !== "number" || typeof loserValue !== "number") {
|
|
1238
|
+
return { passed: false, line: gate.metric + ": one arm did not report a value. Fail." };
|
|
1239
|
+
}
|
|
1240
|
+
var passed = winnerValue < loserValue;
|
|
1241
|
+
return { passed: passed, line: winner.label + " must beat " + loser.label + " on " + gate.metric + " (" + winnerValue + " vs " + loserValue + "). " + (passed ? "Pass." : "Fail.") };
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function renderCompareWinners(event) {
|
|
1246
|
+
var scenario = findScenario(event.suite, event.scenario);
|
|
1247
|
+
if (!scenario || !scenario.compare || !scenario.compare.length) {
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
var wrap = document.getElementById(compareWrapId(event));
|
|
1251
|
+
if (!wrap) {
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
var rows = [];
|
|
1255
|
+
for (var i = 0; i < scenario.compare.length; i++) {
|
|
1256
|
+
var arm = scenario.compare[i];
|
|
1257
|
+
var metrics = finishedMetrics[[event.suite, event.scenario, event.host, arm.id].join("::")];
|
|
1258
|
+
if (!metrics) {
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
rows.push({
|
|
1262
|
+
id: arm.id,
|
|
1263
|
+
label: arm.label,
|
|
1264
|
+
turns: metrics.turns,
|
|
1265
|
+
tokens: metrics.tokens,
|
|
1266
|
+
tools: metrics.tools,
|
|
1267
|
+
durationMs: metrics.durationMs,
|
|
1268
|
+
passed: metrics.passed,
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
var metricLines = [
|
|
1272
|
+
summarizeMetric(rows, "turns", "Turns"),
|
|
1273
|
+
summarizeMetric(rows, "tokens", "Tokens"),
|
|
1274
|
+
summarizeMetric(rows, "tools", "Tools"),
|
|
1275
|
+
summarizeMetric(rows, "durationMs", "Duration"),
|
|
1276
|
+
];
|
|
1277
|
+
var gateLines = summarizeGates(rows, scenario.gates);
|
|
1278
|
+
var existing = wrap.querySelector(".compare-winners");
|
|
1279
|
+
if (existing) {
|
|
1280
|
+
existing.remove();
|
|
1281
|
+
}
|
|
1282
|
+
var section = document.createElement("section");
|
|
1283
|
+
section.className = "compare-winners";
|
|
1284
|
+
var heading = document.createElement("h3");
|
|
1285
|
+
heading.textContent = "Winners";
|
|
1286
|
+
var list = document.createElement("ul");
|
|
1287
|
+
metricLines.forEach(function (metric) {
|
|
1288
|
+
var item = document.createElement("li");
|
|
1289
|
+
item.textContent = metric.line;
|
|
1290
|
+
list.appendChild(item);
|
|
1291
|
+
});
|
|
1292
|
+
gateLines.forEach(function (gate) {
|
|
1293
|
+
var item = document.createElement("li");
|
|
1294
|
+
item.className = gate.passed ? "compare-winner-pass" : "compare-winner-fail";
|
|
1295
|
+
item.textContent = gate.line;
|
|
1296
|
+
list.appendChild(item);
|
|
1297
|
+
});
|
|
1298
|
+
section.appendChild(heading);
|
|
1299
|
+
section.appendChild(list);
|
|
1300
|
+
wrap.appendChild(section);
|
|
1301
|
+
}
|
|
256
1302
|
|
|
257
1303
|
function markCell(event, label, statusClass) {
|
|
258
|
-
var selector = '[data-cell="' + event.suite + "::" + event.scenario + "::" + event.host + '"]
|
|
1304
|
+
var selector = '[data-cell="' + event.suite + "::" + event.scenario + "::" + event.host + '"]';
|
|
259
1305
|
document.querySelectorAll(selector).forEach(function (node) {
|
|
260
|
-
|
|
261
|
-
|
|
1306
|
+
var status = node.querySelector(".cell-status");
|
|
1307
|
+
if (status) {
|
|
1308
|
+
status.textContent = label;
|
|
1309
|
+
status.className = "cell-status " + statusClass;
|
|
1310
|
+
}
|
|
1311
|
+
if (node.classList.contains("host-tab")) {
|
|
1312
|
+
node.setAttribute("data-status", label);
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function metricPart(count, one, many) {
|
|
1318
|
+
if (typeof count !== "number") {
|
|
1319
|
+
return "";
|
|
1320
|
+
}
|
|
1321
|
+
return count + " " + (count === 1 ? one : many);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function formatMetrics(metrics) {
|
|
1325
|
+
if (!metrics) {
|
|
1326
|
+
return "";
|
|
1327
|
+
}
|
|
1328
|
+
return [metricPart(metrics.turns, "turn", "turns"), metricPart(metrics.tokens, "token", "tokens"), metricPart(metrics.tools, "tool", "tools")]
|
|
1329
|
+
.filter(Boolean)
|
|
1330
|
+
.join(" · ");
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function clearCellResult(event) {
|
|
1334
|
+
var article = document.getElementById(paneId(event));
|
|
1335
|
+
var existing = article ? article.querySelector(".cell-result") : null;
|
|
1336
|
+
if (existing) {
|
|
1337
|
+
existing.remove();
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function renderCellResult(event) {
|
|
1342
|
+
ensurePane(event);
|
|
1343
|
+
var article = document.getElementById(paneId(event));
|
|
1344
|
+
if (!article) {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
var existing = article.querySelector(".cell-result");
|
|
1348
|
+
if (existing) {
|
|
1349
|
+
existing.remove();
|
|
1350
|
+
}
|
|
1351
|
+
var skipped = !!event.skipped;
|
|
1352
|
+
var passed = !!event.passed && !skipped;
|
|
1353
|
+
var box = document.createElement("div");
|
|
1354
|
+
box.className = "cell-result " + (skipped ? "status-skipped" : passed ? "status-passed" : "status-failed");
|
|
1355
|
+
var verdict = document.createElement("p");
|
|
1356
|
+
verdict.className = "cell-result-verdict";
|
|
1357
|
+
verdict.textContent = skipped ? "Skipped." : passed ? "Passed." : "Failed.";
|
|
1358
|
+
box.appendChild(verdict);
|
|
1359
|
+
var metrics = formatMetrics(event.metrics);
|
|
1360
|
+
if (metrics) {
|
|
1361
|
+
var line = document.createElement("p");
|
|
1362
|
+
line.className = "cell-result-metrics";
|
|
1363
|
+
line.textContent = metrics;
|
|
1364
|
+
box.appendChild(line);
|
|
1365
|
+
}
|
|
1366
|
+
if (!skipped && !passed) {
|
|
1367
|
+
var list = document.createElement("ul");
|
|
1368
|
+
list.className = "cell-result-failures";
|
|
1369
|
+
var failures = event.failures || [];
|
|
1370
|
+
if (failures.length === 0) {
|
|
1371
|
+
var item = document.createElement("li");
|
|
1372
|
+
item.textContent = "The run failed. The host did not report a reason.";
|
|
1373
|
+
list.appendChild(item);
|
|
1374
|
+
} else {
|
|
1375
|
+
failures.forEach(function (failure) {
|
|
1376
|
+
var item = document.createElement("li");
|
|
1377
|
+
item.textContent = failure.message || failure.matcher || "The run failed.";
|
|
1378
|
+
list.appendChild(item);
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
box.appendChild(list);
|
|
1382
|
+
}
|
|
1383
|
+
article.appendChild(box);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
function appendJudgeVerdicts(event) {
|
|
1387
|
+
ensurePane(event);
|
|
1388
|
+
var article = document.getElementById(paneId(event));
|
|
1389
|
+
if (!article) {
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
var box = article.querySelector(".cell-result");
|
|
1393
|
+
if (!box) {
|
|
1394
|
+
box = document.createElement("div");
|
|
1395
|
+
box.className = "cell-result";
|
|
1396
|
+
article.appendChild(box);
|
|
1397
|
+
}
|
|
1398
|
+
var existing = box.querySelector(".cell-result-judges");
|
|
1399
|
+
if (existing) {
|
|
1400
|
+
existing.remove();
|
|
1401
|
+
}
|
|
1402
|
+
var list = document.createElement("ul");
|
|
1403
|
+
list.className = "cell-result-judges";
|
|
1404
|
+
(event.verdicts || []).forEach(function (verdict) {
|
|
1405
|
+
var item = document.createElement("li");
|
|
1406
|
+
item.className = verdict.pass ? "judge-pass" : "judge-fail";
|
|
1407
|
+
var text = (verdict.pass ? "Pass. " : "Fail. ") + (verdict.question || "");
|
|
1408
|
+
if (verdict.rationale) {
|
|
1409
|
+
text += " " + verdict.rationale;
|
|
1410
|
+
}
|
|
1411
|
+
item.textContent = text;
|
|
1412
|
+
list.appendChild(item);
|
|
262
1413
|
});
|
|
1414
|
+
box.appendChild(list);
|
|
263
1415
|
}
|
|
264
1416
|
|
|
265
1417
|
function handleEvent(event) {
|
|
266
1418
|
if (event.type === "run_started") {
|
|
1419
|
+
if (cancelRequested) {
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
267
1422
|
setRunBanner("Run " + event.runId + " started.");
|
|
268
|
-
|
|
1423
|
+
setCancelButton("Cancel run", false);
|
|
269
1424
|
return;
|
|
270
1425
|
}
|
|
271
1426
|
if (event.type === "run_finished") {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
);
|
|
275
|
-
|
|
1427
|
+
finishProgress(event);
|
|
1428
|
+
hideAllRunning();
|
|
1429
|
+
sealAllChats();
|
|
1430
|
+
if (cancelRequested) {
|
|
1431
|
+
markCancelledCells();
|
|
1432
|
+
setRunBanner("Run cancelled.");
|
|
1433
|
+
} else {
|
|
1434
|
+
setRunBanner(
|
|
1435
|
+
"Run finished. " + progress.passed + " passed. " + progress.failed + " failed. " + progress.skipped + " skipped."
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
setCancelButton("Cancel run", true);
|
|
1439
|
+
cancelRequested = false;
|
|
276
1440
|
runId = null;
|
|
1441
|
+
if (source) {
|
|
1442
|
+
source.close();
|
|
1443
|
+
source = null;
|
|
1444
|
+
}
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
if (cancelRequested) {
|
|
277
1448
|
return;
|
|
278
1449
|
}
|
|
279
1450
|
if (event.type === "error") {
|
|
280
1451
|
setRunBanner(event.message);
|
|
1452
|
+
if (event.suite && chatIsOpen(event)) {
|
|
1453
|
+
appendStatus(event, event.message);
|
|
1454
|
+
var errorChat = ensurePane(event);
|
|
1455
|
+
if (errorChat) {
|
|
1456
|
+
appendBubble(errorChat, "system", event.message);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
281
1459
|
return;
|
|
282
1460
|
}
|
|
283
1461
|
if (event.type === "cell_started") {
|
|
284
1462
|
markCell(event, "running", "status-skipped");
|
|
285
|
-
|
|
1463
|
+
startChat(event);
|
|
1464
|
+
if (event.arm) {
|
|
1465
|
+
var startedWrap = document.getElementById(compareWrapId(event));
|
|
1466
|
+
if (startedWrap) {
|
|
1467
|
+
markArmTab(startedWrap, event.arm, "running");
|
|
1468
|
+
if (startedWrap.classList.contains("compare-tabs") && startedWrap.dataset.userPicked !== "1") {
|
|
1469
|
+
selectCompareTab(startedWrap, event.arm);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
if (event.type === "status") {
|
|
1476
|
+
if (!chatIsOpen(event)) {
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
appendStatus(event, event.text);
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
if (event.type === "context") {
|
|
1483
|
+
if (!chatIsOpen(event)) {
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
var contextChat = ensurePane(event);
|
|
1487
|
+
if (contextChat) {
|
|
1488
|
+
appendContextFiles(contextChat, event.mode, event.files, event.hostInput);
|
|
1489
|
+
}
|
|
286
1490
|
return;
|
|
287
1491
|
}
|
|
288
1492
|
if (event.type === "prompt") {
|
|
289
|
-
|
|
1493
|
+
if (!chatIsOpen(event)) {
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
var promptChat = ensurePane(event);
|
|
1497
|
+
if (promptChat) {
|
|
1498
|
+
appendBubble(promptChat, "user", event.text);
|
|
1499
|
+
showRunning(event);
|
|
1500
|
+
}
|
|
290
1501
|
return;
|
|
291
1502
|
}
|
|
292
1503
|
if (event.type === "text") {
|
|
1504
|
+
if (!chatIsOpen(event)) {
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
293
1507
|
var key = cellKey(event);
|
|
294
1508
|
var chat = ensurePane(event);
|
|
1509
|
+
if (!chat) {
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
hideRunning(event);
|
|
295
1513
|
if (!assistantBodies[key]) {
|
|
296
1514
|
assistantBodies[key] = appendBubble(chat, "assistant", event.text);
|
|
297
1515
|
} else {
|
|
298
1516
|
assistantBodies[key].textContent = event.text;
|
|
299
1517
|
}
|
|
1518
|
+
setStreaming(event, true);
|
|
300
1519
|
return;
|
|
301
1520
|
}
|
|
302
1521
|
if (event.type === "tool") {
|
|
1522
|
+
if (!chatIsOpen(event)) {
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
var toolChat = ensurePane(event);
|
|
1526
|
+
if (!toolChat) {
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
setStreaming(event, false);
|
|
303
1530
|
assistantBodies[cellKey(event)] = null;
|
|
304
|
-
|
|
1531
|
+
hideRunning(event);
|
|
1532
|
+
appendTool(toolChat, event.name, event.args);
|
|
1533
|
+
showRunning(event);
|
|
305
1534
|
return;
|
|
306
1535
|
}
|
|
307
1536
|
if (event.type === "cell_finished") {
|
|
1537
|
+
recordProgress(event);
|
|
1538
|
+
if (!chatIsOpen(event)) {
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
hideRunning(event);
|
|
1542
|
+
setStreaming(event, false);
|
|
308
1543
|
markCell(event, event.skipped ? "skipped" : event.passed ? "passed" : "failed", event.skipped ? "status-skipped" : event.passed ? "status-passed" : "status-failed");
|
|
1544
|
+
finishedMetrics[cellKey(event)] = Object.assign({}, event.metrics || {}, {
|
|
1545
|
+
durationMs: event.durationMs,
|
|
1546
|
+
passed: event.passed,
|
|
1547
|
+
});
|
|
1548
|
+
renderCellResult(event);
|
|
1549
|
+
if (event.arm) {
|
|
1550
|
+
var finishedWrap = document.getElementById(compareWrapId(event));
|
|
1551
|
+
if (finishedWrap) {
|
|
1552
|
+
markArmTab(
|
|
1553
|
+
finishedWrap,
|
|
1554
|
+
event.arm,
|
|
1555
|
+
event.skipped ? "skipped" : event.passed ? "passed" : "failed"
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
renderCompareWinners(event);
|
|
1559
|
+
}
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
if (event.type === "judge") {
|
|
1563
|
+
if (!chatIsOpen(event)) {
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
appendJudgeVerdicts(event);
|
|
309
1567
|
}
|
|
310
1568
|
}
|
|
311
1569
|
|
|
1570
|
+
function markRunTargets(body, label, statusClass) {
|
|
1571
|
+
var hosts = body.hosts && body.hosts.length ? body.hosts : catalog.defaultSelectedHosts;
|
|
1572
|
+
catalog.suites.forEach(function (suite) {
|
|
1573
|
+
if (body.suite && suite.name !== body.suite) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
suite.scenarios.forEach(function (scenario) {
|
|
1577
|
+
if (body.scenario && scenario.name !== body.scenario) {
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
if (scenario.skip) {
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
hosts.forEach(function (host) {
|
|
1584
|
+
if (scenario.host && scenario.host !== host) {
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
markCell({ suite: suite.name, scenario: scenario.name, host: host }, label, statusClass);
|
|
1588
|
+
});
|
|
1589
|
+
});
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
|
|
312
1593
|
async function startRun(body) {
|
|
313
1594
|
if (runId) {
|
|
314
|
-
setRunBanner(
|
|
1595
|
+
setRunBanner(
|
|
1596
|
+
cancelRequested
|
|
1597
|
+
? "Cancelling the run. Wait for it to stop."
|
|
1598
|
+
: "A run is already in progress. Cancel it first."
|
|
1599
|
+
);
|
|
315
1600
|
return;
|
|
316
1601
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
body
|
|
1602
|
+
resetProgress(body);
|
|
1603
|
+
markRunTargets(body, "starting", "status-skipped");
|
|
1604
|
+
var firstCard = document.querySelector(
|
|
1605
|
+
body.suite && body.scenario
|
|
1606
|
+
? '[data-scenario-card="' + body.suite + "::" + body.scenario + '"]'
|
|
1607
|
+
: body.suite
|
|
1608
|
+
? '[data-suite="' + body.suite + '"] [data-scenario-card]'
|
|
1609
|
+
: "[data-scenario-card]"
|
|
1610
|
+
);
|
|
1611
|
+
document.querySelectorAll("[data-scenario-card]").forEach(function (card) {
|
|
1612
|
+
if (body.suite && card.getAttribute("data-scenario-card").indexOf(body.suite + "::") !== 0) {
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
if (body.scenario && card.getAttribute("data-scenario-card") !== body.suite + "::" + body.scenario) {
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
delete card.dataset.followHost;
|
|
321
1619
|
});
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
1620
|
+
if (firstCard) {
|
|
1621
|
+
var live = firstCard.querySelector(".scenario-live");
|
|
1622
|
+
if (live) {
|
|
1623
|
+
live.hidden = false;
|
|
1624
|
+
}
|
|
1625
|
+
if (body.hosts && body.hosts.length === 1 && firstCard.dataset.userPickedHost !== "1") {
|
|
1626
|
+
firstCard.dataset.followHost = body.hosts[0];
|
|
1627
|
+
selectHostTab(firstCard, body.hosts[0]);
|
|
1628
|
+
}
|
|
1629
|
+
if (live) {
|
|
1630
|
+
live.scrollIntoView({ block: "nearest" });
|
|
1631
|
+
}
|
|
326
1632
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
1633
|
+
try {
|
|
1634
|
+
var response = await fetch("/api/runs", {
|
|
1635
|
+
method: "POST",
|
|
1636
|
+
headers: { "content-type": "application/json" },
|
|
1637
|
+
body: JSON.stringify(body),
|
|
1638
|
+
});
|
|
1639
|
+
var payload = await response.json();
|
|
1640
|
+
if (!response.ok) {
|
|
1641
|
+
setRunBanner(payload.error || "Run failed to start.");
|
|
1642
|
+
progress = { total: 0, completed: 0, passed: 0, failed: 0, skipped: 0, seen: {} };
|
|
1643
|
+
renderProgress();
|
|
1644
|
+
markRunTargets(body, "idle", "muted");
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
runId = payload.runId;
|
|
1648
|
+
cancelRequested = false;
|
|
1649
|
+
setRunBanner("Run " + runId + " started.");
|
|
1650
|
+
setCancelButton("Cancel run", false);
|
|
1651
|
+
if (source) {
|
|
1652
|
+
source.close();
|
|
1653
|
+
}
|
|
1654
|
+
source = new EventSource("/api/runs/" + runId + "/events");
|
|
1655
|
+
source.onmessage = function (message) {
|
|
1656
|
+
handleEvent(JSON.parse(message.data));
|
|
1657
|
+
};
|
|
1658
|
+
source.onerror = function () {
|
|
1659
|
+
source.close();
|
|
1660
|
+
if (!runId) {
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
setRunBanner("Live event stream dropped.");
|
|
1664
|
+
};
|
|
1665
|
+
} catch (error) {
|
|
1666
|
+
setRunBanner(error instanceof Error ? error.message : String(error));
|
|
1667
|
+
markRunTargets(body, "idle", "muted");
|
|
330
1668
|
}
|
|
331
|
-
source = new EventSource("/api/runs/" + runId + "/events");
|
|
332
|
-
source.onmessage = function (message) {
|
|
333
|
-
handleEvent(JSON.parse(message.data));
|
|
334
|
-
};
|
|
335
1669
|
}
|
|
336
1670
|
|
|
337
1671
|
document.getElementById("run-selection").addEventListener("click", function () {
|
|
338
1672
|
startRun({ hosts: selectedHosts(), parallelHosts: parallelHosts() });
|
|
339
1673
|
});
|
|
340
1674
|
document.getElementById("cancel-run").addEventListener("click", function () {
|
|
341
|
-
if (!runId) {
|
|
1675
|
+
if (!runId || cancelRequested) {
|
|
342
1676
|
return;
|
|
343
1677
|
}
|
|
344
|
-
|
|
1678
|
+
cancelRequested = true;
|
|
1679
|
+
setRunBanner("Cancelling the run.");
|
|
1680
|
+
setCancelButton("Cancelling", true);
|
|
1681
|
+
cancelOpenChats();
|
|
1682
|
+
fetch("/api/runs/" + runId + "/cancel", { method: "POST" }).then(function (response) {
|
|
1683
|
+
if (!response.ok && cancelRequested) {
|
|
1684
|
+
setRunBanner("Cancel failed.");
|
|
1685
|
+
setCancelButton("Cancel run", false);
|
|
1686
|
+
cancelRequested = false;
|
|
1687
|
+
}
|
|
1688
|
+
}).catch(function () {
|
|
1689
|
+
if (cancelRequested) {
|
|
1690
|
+
setRunBanner("Cancel failed.");
|
|
1691
|
+
setCancelButton("Cancel run", false);
|
|
1692
|
+
cancelRequested = false;
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
345
1695
|
});
|
|
346
1696
|
document.body.addEventListener("click", function (event) {
|
|
347
1697
|
var button = event.target.closest("button");
|
|
348
1698
|
if (!button) {
|
|
349
1699
|
return;
|
|
350
1700
|
}
|
|
1701
|
+
if (button.classList.contains("host-tab")) {
|
|
1702
|
+
var card = button.closest("[data-scenario-card]");
|
|
1703
|
+
if (card) {
|
|
1704
|
+
card.dataset.userPickedHost = "1";
|
|
1705
|
+
selectHostTab(card, button.getAttribute("data-host"));
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
351
1708
|
if (button.classList.contains("run-cell")) {
|
|
352
1709
|
startRun({
|
|
353
1710
|
suite: button.getAttribute("data-suite"),
|
|
@@ -408,7 +1765,7 @@ ${viewerCss()}
|
|
|
408
1765
|
<div class="host-toggles">${hostToggles}</div>
|
|
409
1766
|
</div>
|
|
410
1767
|
<div class="toolbar-block">
|
|
411
|
-
<p class="toolbar-label">
|
|
1768
|
+
<p class="toolbar-label">Run order</p>
|
|
412
1769
|
<label><input type="checkbox" id="parallel-hosts"> Run hosts together</label>
|
|
413
1770
|
</div>
|
|
414
1771
|
<div class="toolbar-actions">
|
|
@@ -417,8 +1774,21 @@ ${viewerCss()}
|
|
|
417
1774
|
</div>
|
|
418
1775
|
<p class="run-banner" id="run-banner"></p>
|
|
419
1776
|
</section>
|
|
1777
|
+
<section class="run-progress" id="run-progress" aria-live="polite" hidden>
|
|
1778
|
+
<div class="run-progress-head">
|
|
1779
|
+
<p class="run-progress-title">0 of 0 tests finished</p>
|
|
1780
|
+
</div>
|
|
1781
|
+
<div class="run-progress-track" role="progressbar" aria-label="Suite run progress" aria-valuemin="0" aria-valuenow="0" aria-valuemax="0">
|
|
1782
|
+
<span class="run-progress-fill"></span>
|
|
1783
|
+
</div>
|
|
1784
|
+
<div class="run-progress-counts">
|
|
1785
|
+
<span class="progress-passed">Passed <strong>0</strong></span>
|
|
1786
|
+
<span class="progress-failed">Failed <strong>0</strong></span>
|
|
1787
|
+
<span class="progress-skipped">Skipped <strong>0</strong></span>
|
|
1788
|
+
<span class="progress-remaining">Remaining <strong>0</strong></span>
|
|
1789
|
+
</div>
|
|
1790
|
+
</section>
|
|
420
1791
|
${suites}
|
|
421
|
-
<section class="live-dock" id="live-dock"></section>
|
|
422
1792
|
</main>
|
|
423
1793
|
<script type="application/json" id="catalog-data">${embedJson(catalog)}</script>
|
|
424
1794
|
<script>
|