@post-print/agent-test 1.1.3 → 1.1.4
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 +1 -1
- package/dist/html-report.d.ts.map +1 -1
- package/dist/html-report.js +6 -113
- package/dist/html-report.js.map +1 -1
- package/dist/run-suite.d.ts +11 -1
- package/dist/run-suite.d.ts.map +1 -1
- package/dist/run-suite.js +38 -2
- package/dist/run-suite.js.map +1 -1
- package/dist/viewer/catalog.d.ts +6 -0
- package/dist/viewer/catalog.d.ts.map +1 -1
- package/dist/viewer/catalog.js +2 -0
- package/dist/viewer/catalog.js.map +1 -1
- package/dist/viewer/client.d.ts +7 -0
- package/dist/viewer/client.d.ts.map +1 -0
- package/dist/viewer/client.js +14 -0
- package/dist/viewer/client.js.map +1 -0
- package/dist/viewer/events.d.ts +19 -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.map +1 -1
- package/dist/viewer/live-runner.js +36 -1
- package/dist/viewer/live-runner.js.map +1 -1
- package/dist/viewer/main.d.ts +2 -0
- package/dist/viewer/main.d.ts.map +1 -0
- package/dist/viewer/main.js +11 -0
- package/dist/viewer/main.js.map +1 -0
- package/dist/viewer/page.d.ts +1 -9
- package/dist/viewer/page.d.ts.map +1 -1
- package/dist/viewer/page.js +187 -2482
- package/dist/viewer/page.js.map +1 -1
- package/dist/viewer/run-controller.d.ts +7 -0
- package/dist/viewer/run-controller.d.ts.map +1 -1
- package/dist/viewer/run-controller.js +32 -2
- package/dist/viewer/run-controller.js.map +1 -1
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +14 -18
- package/dist/viewer/server.js.map +1 -1
- package/dist/viewer/tabs.d.ts +13 -0
- package/dist/viewer/tabs.d.ts.map +1 -0
- package/dist/viewer/tabs.js +10 -0
- package/dist/viewer/tabs.js.map +1 -0
- package/package.json +9 -4
- package/dist/context-panel.d.ts +0 -5
- package/dist/context-panel.d.ts.map +0 -1
- package/dist/context-panel.js +0 -60
- package/dist/context-panel.js.map +0 -1
package/dist/viewer/page.js
CHANGED
|
@@ -1,2508 +1,213 @@
|
|
|
1
|
-
import {
|
|
2
|
-
function escapeHtml(value) {
|
|
3
|
-
return value
|
|
4
|
-
.replaceAll("&", "&")
|
|
5
|
-
.replaceAll("<", "<")
|
|
6
|
-
.replaceAll(">", ">")
|
|
7
|
-
.replaceAll('"', """)
|
|
8
|
-
.replaceAll("'", "'");
|
|
9
|
-
}
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
2
|
function embedJson(value) {
|
|
11
3
|
return JSON.stringify(value).replaceAll("<", "\\u003c");
|
|
12
4
|
}
|
|
5
|
+
function viewerClient() {
|
|
6
|
+
const candidates = [
|
|
7
|
+
new URL("./client.js", import.meta.url),
|
|
8
|
+
new URL("../../dist/viewer/client.js", import.meta.url),
|
|
9
|
+
];
|
|
10
|
+
const bundle = candidates.find((candidate) => existsSync(candidate));
|
|
11
|
+
if (!bundle) {
|
|
12
|
+
throw new Error("Viewer client bundle is missing. Run the @post-print/agent-test build first.");
|
|
13
|
+
}
|
|
14
|
+
return readFileSync(bundle, "utf8").replaceAll("</script", "<\\/script");
|
|
15
|
+
}
|
|
13
16
|
function viewerCss() {
|
|
14
17
|
return `
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
background: var(--panel);
|
|
22
|
-
border: 1px solid var(--border);
|
|
23
|
-
border-radius: 12px;
|
|
24
|
-
padding: 0.85rem 1rem;
|
|
25
|
-
margin-bottom: 1.25rem;
|
|
26
|
-
}
|
|
27
|
-
.toolbar-block { display: grid; gap: 0.35rem; }
|
|
28
|
-
.run-history { min-width: min(26rem, 80vw); }
|
|
29
|
-
.toolbar-label { color: var(--muted); font-size: 0.75rem; font-weight: 650; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
30
|
-
.host-toggles, .toolbar-actions { display: flex; flex-wrap: wrap; gap: 0.45rem; align-items: center; }
|
|
31
|
-
.host-toggles label { display: inline-flex; gap: 0.35rem; align-items: center; font-size: 0.85rem; }
|
|
32
|
-
button {
|
|
33
|
-
font: inherit;
|
|
34
|
-
border-radius: 8px;
|
|
35
|
-
border: 1px solid var(--border);
|
|
36
|
-
background: var(--panel-2);
|
|
37
|
-
color: var(--text);
|
|
38
|
-
padding: 0.35rem 0.65rem;
|
|
39
|
-
cursor: pointer;
|
|
40
|
-
}
|
|
18
|
+
html { font-size: 87.5%; }
|
|
19
|
+
html, body, #viewer-root { height: 100%; }
|
|
20
|
+
body { overflow: hidden; word-spacing: normal; }
|
|
21
|
+
button, select, input { font: inherit; }
|
|
22
|
+
button { min-height: 2rem; border-radius: 8px; border: 1px solid var(--border); background: var(--panel-2); color: var(--text); padding: .38rem .68rem; line-height: 1.2; cursor: pointer; }
|
|
23
|
+
button:disabled { cursor: not-allowed; opacity: .5; }
|
|
41
24
|
button.primary { background: color-mix(in srgb, var(--pass) 18%, var(--panel-2)); border-color: color-mix(in srgb, var(--pass) 40%, var(--border)); }
|
|
42
|
-
button.danger { border-color: color-mix(in srgb, var(--fail)
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
.
|
|
46
|
-
.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
.scenario-
|
|
55
|
-
.
|
|
56
|
-
.
|
|
57
|
-
.
|
|
58
|
-
.
|
|
59
|
-
.
|
|
60
|
-
.
|
|
61
|
-
.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
.
|
|
71
|
-
.
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
.
|
|
75
|
-
.
|
|
76
|
-
.
|
|
77
|
-
.
|
|
78
|
-
.
|
|
79
|
-
.
|
|
80
|
-
.scenario-
|
|
81
|
-
.scenario-
|
|
82
|
-
.
|
|
83
|
-
.
|
|
84
|
-
.
|
|
85
|
-
.
|
|
86
|
-
.
|
|
87
|
-
.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
.
|
|
99
|
-
.
|
|
100
|
-
.cell-
|
|
101
|
-
.
|
|
102
|
-
.
|
|
103
|
-
.
|
|
104
|
-
.
|
|
105
|
-
.
|
|
106
|
-
.
|
|
107
|
-
.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
.
|
|
115
|
-
.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
.
|
|
121
|
-
.
|
|
122
|
-
.
|
|
123
|
-
.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
.live
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
white-space: pre-wrap;
|
|
139
|
-
word-break: break-word;
|
|
140
|
-
}
|
|
141
|
-
.run-banner { font-size: 0.85rem; color: var(--muted); }
|
|
142
|
-
.run-progress {
|
|
143
|
-
display: grid;
|
|
144
|
-
gap: 0.55rem;
|
|
145
|
-
background: var(--panel);
|
|
146
|
-
border: 1px solid var(--border);
|
|
147
|
-
border-radius: 12px;
|
|
148
|
-
padding: 0.8rem 1rem;
|
|
149
|
-
margin-bottom: 1.25rem;
|
|
150
|
-
}
|
|
151
|
-
.run-progress[hidden] { display: none; }
|
|
152
|
-
.run-progress-head, .run-progress-counts {
|
|
153
|
-
display: flex;
|
|
154
|
-
flex-wrap: wrap;
|
|
155
|
-
align-items: center;
|
|
156
|
-
justify-content: space-between;
|
|
157
|
-
gap: 0.4rem 1rem;
|
|
158
|
-
}
|
|
159
|
-
.run-progress-title { margin: 0; font-weight: 700; }
|
|
160
|
-
.run-progress-counts { justify-content: flex-start; color: var(--muted); font-size: 0.8rem; }
|
|
161
|
-
.run-progress-counts strong { color: var(--text); font-variant-numeric: tabular-nums; }
|
|
162
|
-
.run-progress-counts .progress-passed strong { color: var(--pass); }
|
|
163
|
-
.run-progress-counts .progress-failed strong { color: var(--fail); }
|
|
164
|
-
.run-progress-counts .progress-skipped strong { color: var(--skip); }
|
|
165
|
-
.run-progress-track {
|
|
166
|
-
height: 0.7rem;
|
|
167
|
-
overflow: hidden;
|
|
168
|
-
border-radius: 999px;
|
|
169
|
-
background: var(--panel-2);
|
|
170
|
-
border: 1px solid var(--border);
|
|
171
|
-
}
|
|
172
|
-
.run-progress-fill {
|
|
173
|
-
display: block;
|
|
174
|
-
width: 0;
|
|
175
|
-
height: 100%;
|
|
176
|
-
background: var(--pass);
|
|
177
|
-
transition: width 180ms ease-out;
|
|
178
|
-
}
|
|
179
|
-
.run-progress[data-has-failures="true"] .run-progress-fill {
|
|
180
|
-
background: linear-gradient(90deg, var(--pass), var(--fail));
|
|
181
|
-
}
|
|
182
|
-
.compare-tab[data-status="running"] { color: var(--skip); }
|
|
183
|
-
.compare-tab[data-status="passed"] { color: var(--pass); }
|
|
184
|
-
.compare-tab[data-status="failed"] { color: var(--fail); }
|
|
185
|
-
.compare-tab[data-status="skipped"] { color: var(--muted); }
|
|
186
|
-
.compare-tabs .compare-arms,
|
|
187
|
-
.compare-tabs .compare-arms[data-arm-count] { grid-template-columns: minmax(0, 1fr); }
|
|
188
|
-
.compare-tabs .live-cell[hidden] { display: none; }
|
|
189
|
-
.compare-tabs .live-cell > h3 { display: none; }
|
|
25
|
+
button.danger { color: var(--fail); border-color: color-mix(in srgb, var(--fail) 45%, var(--border)); }
|
|
26
|
+
.viewer-shell { display: flex; flex-direction: column; width: 100%; max-width: 96rem; height: 100vh; height: 100dvh; margin: 0 auto; overflow: hidden; padding: .65rem .9rem; }
|
|
27
|
+
.viewer-topbar { display: flex; align-items: end; border-block-end: 1px solid var(--border); padding-block-end: .65rem; }
|
|
28
|
+
.viewer-identity h1 { margin: 0; font-size: 1.35rem; }
|
|
29
|
+
.section-label, .toolbar-label, .task-meta-label { color: var(--muted); font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
|
|
30
|
+
.run-history { min-width: 10rem; border: 1px solid var(--border); border-radius: 8px; background: var(--panel-2); color: var(--text); padding: .45rem .6rem; }
|
|
31
|
+
.run-banner { color: var(--muted); font-size: .82rem; }
|
|
32
|
+
.viewer-command-row { display: flex; flex-wrap: wrap; align-items: center; gap: .55rem 1rem; padding-block: .55rem; }
|
|
33
|
+
.toolbar-block { display: flex; align-items: center; gap: .65rem; }
|
|
34
|
+
.toolbar-block .toolbar-label { margin: 0; }
|
|
35
|
+
.sidebar-toggle { display: inline-grid; place-items: center; width: 2rem; min-height: 2rem; padding: 0; }
|
|
36
|
+
.sidebar-toggle svg { width: 1rem; height: 1rem; fill: none; stroke: currentColor; stroke-width: 1.8; }
|
|
37
|
+
.host-toggles, .scenario-actions, .host-tablist, .compare-tablist, .compare-definition-tabs { display: flex; flex-wrap: wrap; gap: .4rem; }
|
|
38
|
+
.host-toggles label, .parallel-control, .worker-control, .run-actions { display: inline-flex; align-items: center; gap: .3rem; font-size: .82rem; }
|
|
39
|
+
.worker-control input { min-inline-size: 4.25rem; }
|
|
40
|
+
.viewer-workspace { display: grid; grid-template-columns: minmax(13rem, 17rem) minmax(0, 1fr); flex: 1 1 auto; min-height: 0; gap: .75rem; overflow: hidden; }
|
|
41
|
+
.viewer-workspace[data-sidebar-open="false"] { grid-template-columns: minmax(0, 1fr); }
|
|
42
|
+
.test-navigator { min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; border: 1px solid var(--border); border-radius: 10px; background: var(--panel); padding: .6rem; }
|
|
43
|
+
.test-navigator-heading h2, .test-nav-suite h2 { margin: 0; }
|
|
44
|
+
.test-picker-label, .test-picker { display: none; }
|
|
45
|
+
.test-nav-suite { margin-block-start: .7rem; }
|
|
46
|
+
.test-nav-suite > header { display: flex; gap: .5rem; align-items: center; justify-content: space-between; }
|
|
47
|
+
.test-nav-suite h2 { font-size: .72rem; color: var(--muted); }
|
|
48
|
+
.test-navigator .run-suite { min-height: 1.55rem; border-radius: 6px; padding: .18rem .45rem; font-size: .68rem; line-height: 1.1; }
|
|
49
|
+
.test-nav-items { display: grid; gap: .25rem; margin-block-start: .35rem; }
|
|
50
|
+
.test-nav-item { display: grid; grid-template-columns: .55rem 1fr; gap: .4rem; width: 100%; text-align: start; padding: .42rem; }
|
|
51
|
+
.test-nav-item[aria-current="true"] { border-color: color-mix(in srgb, var(--pass) 50%, var(--border)); background: color-mix(in srgb, var(--pass) 12%, var(--panel-2)); }
|
|
52
|
+
.test-nav-status { width: .55rem; height: .55rem; margin-block-start: .18rem; border: 1px solid color-mix(in srgb, currentColor 20%, transparent); border-radius: 50%; background: var(--border); box-shadow: 0 0 0 2px color-mix(in srgb, var(--panel-2) 80%, transparent); }
|
|
53
|
+
.test-nav-item[data-status="passed"] .test-nav-status { background: var(--pass); }
|
|
54
|
+
.test-nav-item[data-status="failed"] .test-nav-status { background: var(--fail); }
|
|
55
|
+
.test-nav-item[data-status="running"] .test-nav-status { background: var(--skip); }
|
|
56
|
+
.test-nav-copy { display: grid; min-width: 0; gap: .08rem; font-size: .82rem; line-height: 1.22; }
|
|
57
|
+
.test-nav-copy small { color: var(--muted); font-size: .68rem; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
58
|
+
.test-stage { min-width: 0; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
|
|
59
|
+
.scenario-card { min-width: 0; }
|
|
60
|
+
.scenario-card[hidden] { display: none; }
|
|
61
|
+
.scenario-card { display: grid; gap: .75rem; border: 1px solid var(--border); border-radius: 12px; background: var(--panel); padding: .75rem .85rem; }
|
|
62
|
+
.scenario-focus-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 1rem; padding-block-end: .85rem; border-block-end: 1px solid var(--border); }
|
|
63
|
+
.scenario-heading-copy { display: grid; gap: .2rem; min-width: 0; }
|
|
64
|
+
.scenario-path { color: var(--muted); font-size: .7rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
|
|
65
|
+
.scenario-focus-header h2 { font-size: 1.2rem; line-height: 1.25; text-wrap: balance; }
|
|
66
|
+
.scenario-focus-header .scenario-lede { color: var(--muted); font-size: .84rem; line-height: 1.4; }
|
|
67
|
+
.focus-verdict { display: inline-flex; align-items: center; gap: .4rem; border: 1px solid var(--border); border-radius: 999px; padding: .22rem .55rem; color: var(--muted); font-size: .72rem; font-weight: 650; white-space: nowrap; }
|
|
68
|
+
.focus-verdict > span { width: .45rem; height: .45rem; border-radius: 50%; background: currentColor; }
|
|
69
|
+
.focus-verdict[data-status="passed"] { color: var(--pass); border-color: color-mix(in srgb, var(--pass) 40%, var(--border)); }
|
|
70
|
+
.focus-verdict[data-status="failed"] { color: var(--fail); border-color: color-mix(in srgb, var(--fail) 40%, var(--border)); }
|
|
71
|
+
.focus-verdict[data-status="running"], .focus-verdict[data-status="cancelling"] { color: var(--skip); border-color: color-mix(in srgb, var(--skip) 40%, var(--border)); }
|
|
72
|
+
.scenario-definition { display: grid; gap: 1rem; }
|
|
73
|
+
.scenario-detail { display: grid; gap: .3rem; }
|
|
74
|
+
.scenario-detail h2, .scenario-detail p { margin: 0; }
|
|
75
|
+
.test-intent { display: grid; gap: .35rem; }
|
|
76
|
+
.prompt-preview { color: var(--text); font-size: .92rem; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
77
|
+
.rubric-line, .arm-note { color: var(--muted); font-size: .8rem; white-space: pre-wrap; }
|
|
78
|
+
.scenario-toolbar { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .75rem 1rem; align-items: end; border-block-start: 1px solid var(--border); padding-block-start: .85rem; }
|
|
79
|
+
.host-runs { display: flex; align-items: center; gap: .55rem; }
|
|
80
|
+
.host-runs-label { color: var(--muted); font-size: .7rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
|
|
81
|
+
.host-tablist { gap: .3rem; }
|
|
82
|
+
.host-tab { min-height: 0; padding: .22rem .42rem; font-size: .72rem; line-height: 1.15; }
|
|
83
|
+
.cell-status { margin-inline-start: .1rem; color: var(--muted); font-size: .62rem; font-weight: 500; text-transform: capitalize; }
|
|
84
|
+
.host-tab[aria-selected="true"], .compare-tab[aria-selected="true"], .compare-definition-tab[aria-selected="true"] { border-color: color-mix(in srgb, var(--pass) 50%, var(--border)); background: color-mix(in srgb, var(--pass) 14%, var(--panel-2)); }
|
|
85
|
+
.task-definition { display: grid; gap: .8rem; border: 1px solid var(--border); border-radius: 12px; padding: .9rem; }
|
|
86
|
+
.task-definition-header { display: flex; justify-content: space-between; gap: 1rem; align-items: baseline; }
|
|
87
|
+
.task-definition-header h3, .criteria > h3 { margin: 0; }
|
|
88
|
+
.task-description { font-size: .96rem; }
|
|
89
|
+
.task-prompt { margin: 0; padding: .75rem; border-radius: 8px; background: var(--panel-2); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
90
|
+
.starting-context { border-block-start: 1px solid var(--border); padding-block: .55rem .8rem; }
|
|
91
|
+
.criteria-panel { border-block-start: 1px solid var(--border); padding-block: .55rem .8rem; }
|
|
92
|
+
.criteria-panel > summary { cursor: pointer; padding-block: .1rem .55rem; font-weight: 650; }
|
|
93
|
+
.criteria-panel[open] > summary { margin-block-end: .35rem; }
|
|
94
|
+
.criteria-summary { margin-inline-start: .8rem; color: var(--muted); font-size: .75rem; font-weight: 500; }
|
|
95
|
+
.criteria-empty { color: var(--muted); font-size: .84rem; }
|
|
96
|
+
.criteria { display: grid; gap: .75rem; }
|
|
97
|
+
.criterion-groups { display: flex; flex-direction: row; align-items: stretch; gap: .45rem; }
|
|
98
|
+
.criterion-groups > .criterion-group { flex: 1 1 0; min-width: 0; }
|
|
99
|
+
.criterion-group { border: 1px solid var(--border); border-radius: 10px; padding: .7rem; background: var(--panel-2); }
|
|
100
|
+
.criterion-group h4 { margin: 0 0 .45rem; }
|
|
101
|
+
.criterion-group p { color: var(--muted); font-size: .82rem; line-height: 1.45; }
|
|
102
|
+
.criterion-list { margin: 0; padding-inline-start: 1.2rem; font-size: .86rem; line-height: 1.45; }
|
|
103
|
+
.source-folder-link { color: var(--text); overflow-wrap: anywhere; text-decoration-color: color-mix(in srgb, var(--text) 45%, transparent); text-underline-offset: .18em; }
|
|
104
|
+
.source-folder-link:hover { text-decoration-color: currentColor; }
|
|
105
|
+
.criterion-group p + .criterion-list { margin-block-start: .5rem; }
|
|
106
|
+
.criterion-list li + li { margin-block-start: .38rem; }
|
|
107
|
+
.comparison-task, .comparison-criteria, .compare-definition-tabbed { display: grid; gap: .4rem; }
|
|
108
|
+
.comparison-intro { color: var(--muted); font-size: .84rem; }
|
|
109
|
+
.comparison-criteria .criterion-groups { margin-block-start: .2rem; }
|
|
110
|
+
.compare-definition-tabbed { gap: .55rem; }
|
|
111
|
+
.compare-arm-definition { display: grid; gap: .75rem; border-block-start: 1px solid var(--border); padding-block-start: .8rem; }
|
|
112
|
+
.compare-arm-header { display: grid; gap: .2rem; }
|
|
113
|
+
.compare-arm-header h3 { color: var(--text); font-size: .95rem; }
|
|
114
|
+
.compare-arm-description { color: var(--muted); font-size: .82rem; line-height: 1.4; }
|
|
115
|
+
.comparison-definition { display: grid; gap: .75rem; border: 1px solid color-mix(in srgb, var(--skip) 35%, var(--border)); border-radius: 12px; padding: .85rem; }
|
|
116
|
+
.compare-definition-arms, .compare-arms, .compare-definition-arm, .compare-arm { min-width: 0; }
|
|
117
|
+
.compare-tabs .compare-arm { border-block-start-width: 1px; border-block-start-color: var(--border); }
|
|
118
|
+
.scenario-live { display: grid; gap: .8rem; }
|
|
119
|
+
.live-cell { border: 1px solid var(--border); border-radius: 12px; background: var(--panel-2); padding: .8rem; }
|
|
120
|
+
.live-status { max-height: 9rem; overflow: auto; color: var(--muted); font: .75rem ui-monospace, monospace; }
|
|
190
121
|
.chat-running-row[hidden] { display: none; }
|
|
191
|
-
.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
display: block;
|
|
200
|
-
margin-top: 0.15rem;
|
|
201
|
-
}
|
|
202
|
-
.context-panel {
|
|
203
|
-
margin: 0 0 0.75rem;
|
|
204
|
-
border: 1px solid var(--border);
|
|
205
|
-
border-radius: 10px;
|
|
206
|
-
background: color-mix(in srgb, var(--skip) 10%, var(--panel-2));
|
|
207
|
-
padding: 0.55rem 0.75rem;
|
|
208
|
-
}
|
|
209
|
-
.context-panel-summary {
|
|
210
|
-
display: flex;
|
|
211
|
-
align-items: baseline;
|
|
212
|
-
justify-content: space-between;
|
|
213
|
-
gap: 0.75rem;
|
|
214
|
-
cursor: pointer;
|
|
215
|
-
font-weight: 700;
|
|
216
|
-
}
|
|
217
|
-
.context-panel-count { color: var(--muted); font-weight: 600; font-size: 0.82rem; }
|
|
218
|
-
.context-flow { display: flex; align-items: center; gap: 0.35rem; margin: 0.55rem 0; flex-wrap: wrap; }
|
|
219
|
-
.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; }
|
|
220
|
-
.context-flow-mode { border-color: var(--skip); }
|
|
221
|
-
.context-flow-arrow { color: var(--muted); }
|
|
222
|
-
.context-host-input { border: 1px solid var(--border); border-radius: 8px; background: var(--panel); padding: 0.4rem 0.55rem; margin-top: 0.45rem; }
|
|
223
|
-
.context-host-input summary { cursor: pointer; font-size: 0.82rem; font-weight: 700; }
|
|
224
|
-
.context-panel-lede, .context-empty { color: var(--muted); font-size: 0.85rem; margin: 0.45rem 0 0; }
|
|
225
|
-
.context-files { list-style: none; margin: 0.5rem 0 0; padding: 0; display: grid; gap: 0.4rem; }
|
|
226
|
-
.context-file {
|
|
227
|
-
border: 1px solid var(--border);
|
|
228
|
-
border-radius: 8px;
|
|
229
|
-
background: var(--panel);
|
|
230
|
-
padding: 0.4rem 0.55rem;
|
|
231
|
-
}
|
|
232
|
-
.context-file[data-reason="contextSources"] { border-left: 3px solid var(--skip); }
|
|
233
|
-
.context-file[data-reason="skills"] { border-left: 3px solid var(--tool); }
|
|
234
|
-
.context-file[data-reason="profile"] { border-left: 3px solid var(--muted); }
|
|
235
|
-
.context-file summary { cursor: pointer; }
|
|
236
|
-
.context-body {
|
|
237
|
-
margin: 0.45rem 0 0;
|
|
238
|
-
white-space: pre-wrap;
|
|
239
|
-
overflow-wrap: break-word;
|
|
240
|
-
font-size: 0.82rem;
|
|
241
|
-
line-height: 1.45;
|
|
242
|
-
max-height: 18rem;
|
|
243
|
-
overflow: auto;
|
|
244
|
-
}
|
|
245
|
-
.chat-running {
|
|
246
|
-
display: inline-flex;
|
|
247
|
-
align-items: center;
|
|
248
|
-
gap: 0.45rem;
|
|
249
|
-
color: var(--muted);
|
|
250
|
-
font-size: 0.85rem;
|
|
251
|
-
padding: 0.2rem 0.1rem;
|
|
252
|
-
}
|
|
253
|
-
.chat-progress {
|
|
254
|
-
display: inline-flex;
|
|
255
|
-
gap: 0.18rem;
|
|
256
|
-
}
|
|
257
|
-
.chat-progress span {
|
|
258
|
-
width: 0.32rem;
|
|
259
|
-
height: 0.32rem;
|
|
260
|
-
border-radius: 999px;
|
|
261
|
-
background: var(--skip);
|
|
262
|
-
animation: chat-progress 1s ease-in-out infinite;
|
|
263
|
-
}
|
|
264
|
-
.chat-progress span:nth-child(2) { animation-delay: 0.15s; }
|
|
265
|
-
.chat-progress span:nth-child(3) { animation-delay: 0.3s; }
|
|
266
|
-
@keyframes chat-progress {
|
|
267
|
-
0%, 80%, 100% { opacity: 0.2; }
|
|
268
|
-
40% { opacity: 1; }
|
|
269
|
-
}
|
|
270
|
-
.bubble.is-streaming .bubble-text::after {
|
|
271
|
-
content: "▍";
|
|
272
|
-
margin-left: 0.12rem;
|
|
273
|
-
color: var(--pass);
|
|
274
|
-
animation: chat-caret 1s step-end infinite;
|
|
275
|
-
}
|
|
276
|
-
@keyframes chat-caret {
|
|
277
|
-
50% { opacity: 0; }
|
|
278
|
-
}
|
|
122
|
+
.chat-running { display: inline-flex; align-items: center; gap: .45rem; color: var(--muted); font-size: .85rem; padding: .2rem .1rem; }
|
|
123
|
+
.chat-progress { display: inline-flex; gap: .18rem; }
|
|
124
|
+
.chat-progress span { width: .32rem; height: .32rem; border-radius: 999px; background: var(--skip); animation: chat-progress 1s ease-in-out infinite; }
|
|
125
|
+
.chat-progress span:nth-child(2) { animation-delay: .15s; }
|
|
126
|
+
.chat-progress span:nth-child(3) { animation-delay: .3s; }
|
|
127
|
+
@keyframes chat-progress { 0%, 80%, 100% { opacity: .2; } 40% { opacity: 1; } }
|
|
128
|
+
.bubble.is-streaming .bubble-text::after { content: "▍"; margin-inline-start: .12rem; color: var(--pass); animation: chat-caret 1s step-end infinite; }
|
|
129
|
+
@keyframes chat-caret { 50% { opacity: 0; } }
|
|
279
130
|
@media (prefers-reduced-motion: reduce) {
|
|
280
131
|
.chat-progress span, .bubble.is-streaming .bubble-text::after { animation: none; }
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
.
|
|
287
|
-
.
|
|
288
|
-
.
|
|
289
|
-
.
|
|
290
|
-
.
|
|
291
|
-
.
|
|
292
|
-
.
|
|
293
|
-
|
|
294
|
-
.
|
|
295
|
-
.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
.
|
|
304
|
-
.
|
|
305
|
-
.
|
|
306
|
-
.
|
|
307
|
-
.
|
|
308
|
-
.
|
|
309
|
-
.
|
|
310
|
-
.
|
|
311
|
-
.
|
|
312
|
-
.
|
|
313
|
-
.
|
|
314
|
-
.
|
|
315
|
-
.
|
|
316
|
-
.
|
|
317
|
-
.
|
|
318
|
-
.
|
|
319
|
-
.
|
|
320
|
-
.
|
|
321
|
-
.
|
|
322
|
-
.
|
|
323
|
-
.
|
|
324
|
-
.
|
|
325
|
-
.
|
|
326
|
-
.
|
|
327
|
-
.
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
.test-nav-item[aria-current="true"] { background: color-mix(in oklch, var(--pass) 9%, var(--panel)); border-color: color-mix(in oklch, var(--pass) 28%, var(--border)); }
|
|
332
|
-
.test-nav-status { width: .48rem; height: .48rem; margin-block-start: .28rem; border: 1px solid var(--muted); border-radius: 50%; }
|
|
333
|
-
.test-nav-item[data-status="running"] .test-nav-status { border-color: var(--skip); background: var(--skip); box-shadow: 0 0 0 3px color-mix(in oklch, var(--skip) 14%, transparent); }
|
|
334
|
-
.test-nav-item[data-status="passed"] .test-nav-status { border-color: var(--pass); background: var(--pass); }
|
|
335
|
-
.test-nav-item[data-status="failed"] .test-nav-status { border-color: var(--fail); background: var(--fail); }
|
|
336
|
-
.test-nav-item[data-status="skipped"] .test-nav-status { border-color: var(--skip); }
|
|
337
|
-
.test-nav-copy { display: grid; min-width: 0; }
|
|
338
|
-
.test-nav-copy strong { overflow: hidden; font-size: .82rem; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
|
|
339
|
-
.test-nav-copy small { overflow: hidden; color: var(--muted); font-size: .7rem; text-overflow: ellipsis; white-space: nowrap; }
|
|
340
|
-
.test-stage { min-width: 0; padding: clamp(1rem, 3vw, 2.25rem); }
|
|
341
|
-
.test-stage > .suite { display: block; margin: 0; }
|
|
342
|
-
.test-stage > .suite > .suite-header { display: none; }
|
|
343
|
-
.test-stage .scenario-list { display: contents; }
|
|
344
|
-
.scenario-card, .scenario-card[data-selected="true"] { max-width: 76rem; margin-inline: auto; padding: 0; gap: 0; overflow: hidden; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 12px 36px oklch(0 0 0 / .16); }
|
|
345
|
-
.scenario-card[hidden] { display: none; }
|
|
346
|
-
.scenario-focus-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: clamp(1rem, 2vw, 1.5rem); border-block-end: 1px solid var(--border); }
|
|
347
|
-
.scenario-heading-copy { display: grid; gap: .3rem; min-width: 0; }
|
|
348
|
-
.scenario-heading-copy h2 { margin: 0; font-size: clamp(1.25rem, 2.5vw, 1.75rem); letter-spacing: -.025em; }
|
|
349
|
-
.scenario-heading-copy .suite-description { max-width: 62ch; color: var(--muted); font-size: .9rem; }
|
|
350
|
-
.scenario-heading-copy .scenario-lede { max-width: 62ch; font-size: .9rem; }
|
|
351
|
-
.focus-verdict { display: inline-flex; align-items: center; gap: .4rem; flex: none; padding: .28rem .55rem; color: var(--muted); border: 1px solid var(--border); border-radius: 999px; font-size: .72rem; font-weight: 700; text-transform: uppercase; }
|
|
352
|
-
.focus-verdict > span { width: .45rem; height: .45rem; border-radius: 50%; background: currentColor; }
|
|
353
|
-
.focus-verdict[data-status="running"] { color: var(--skip); }
|
|
354
|
-
.focus-verdict[data-status="passed"] { color: var(--pass); }
|
|
355
|
-
.focus-verdict[data-status="failed"] { color: var(--fail); }
|
|
356
|
-
.scenario-definition { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(15rem, .7fr); gap: .8rem; padding: clamp(1rem, 2vw, 1.5rem); background: color-mix(in oklch, var(--panel) 88%, var(--bg)); }
|
|
357
|
-
.test-intent, .criteria-panel, .compare-definition, .comparison-criteria { min-width: 0; padding: .9rem; background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; }
|
|
358
|
-
.prompt-preview { margin: .45rem 0 0; color: var(--text); font-size: .94rem; line-height: 1.55; }
|
|
359
|
-
.task-metadata { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem .55rem; margin-block-start: .75rem; padding-block-start: .65rem; border-block-start: 1px solid var(--border); }
|
|
360
|
-
.task-metadata + .task-metadata { margin-block-start: .55rem; padding-block-start: 0; border-block-start: 0; }
|
|
361
|
-
.task-metadata .task-meta-label { color: var(--muted); font-size: .66rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; }
|
|
362
|
-
.task-metadata p { margin: 0; color: var(--muted); font-size: .76rem; line-height: 1.6; }
|
|
363
|
-
.task-metadata code { padding: .08rem .28rem; background: var(--bg); border: 1px solid var(--border); border-radius: 4px; overflow-wrap: anywhere; }
|
|
364
|
-
.criteria-panel > summary { display: flex; justify-content: space-between; gap: .5rem; cursor: pointer; font-size: .82rem; font-weight: 650; }
|
|
365
|
-
.criteria-summary { color: var(--muted); font-size: .72rem; font-weight: 400; }
|
|
366
|
-
.criterion-groups { display: grid; gap: .65rem; margin-block-start: .75rem; }
|
|
367
|
-
.criterion-group { display: grid; gap: .35rem; padding-block-start: .6rem; border-block-start: 1px solid var(--border); }
|
|
368
|
-
.criterion-group:first-child { padding-block-start: 0; border-block-start: 0; }
|
|
369
|
-
.criterion-group h4 { margin: 0; color: var(--muted); font-size: .66rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; }
|
|
370
|
-
.criterion-group p { margin: 0; color: var(--muted); font-size: .76rem; line-height: 1.4; }
|
|
371
|
-
.criterion-list { display: grid; gap: .28rem; margin: 0; padding: 0; list-style: none; }
|
|
372
|
-
.criterion-list li { color: var(--text); font-size: .78rem; line-height: 1.4; }
|
|
373
|
-
.criterion-list code { padding: .08rem .28rem; background: var(--bg); border: 1px solid var(--border); border-radius: 4px; overflow-wrap: anywhere; }
|
|
374
|
-
.criteria-empty { margin: .7rem 0 0; color: var(--muted); font-size: .78rem; line-height: 1.45; }
|
|
375
|
-
.compare-definition { grid-column: 1 / -1; }
|
|
376
|
-
.comparison-intro { margin: .35rem 0 0; color: var(--muted); font-size: .78rem; }
|
|
377
|
-
.compare-definition { padding: 0; background: transparent; border: 0; }
|
|
378
|
-
.compare-definition-tabs { display: flex; flex-wrap: wrap; gap: .35rem; margin-block-start: .55rem; }
|
|
379
|
-
.compare-definition-tab-input { position: absolute; inline-size: 1px; block-size: 1px; opacity: 0; pointer-events: none; }
|
|
380
|
-
.compare-definition-tab { padding: .35rem .65rem; color: var(--muted); background: var(--panel-2); border: 1px solid var(--border); border-radius: 7px; cursor: pointer; font-size: .78rem; }
|
|
381
|
-
.compare-definition-tab-input:checked + .compare-definition-tab { color: var(--text); background: color-mix(in oklch, var(--pass) 12%, var(--panel-2)); border-color: color-mix(in oklch, var(--pass) 36%, var(--border)); }
|
|
382
|
-
.compare-definition-tab-input:focus-visible + .compare-definition-tab { outline: 2px solid var(--pass); outline-offset: 2px; }
|
|
383
|
-
.compare-definition-grid { display: grid; grid-template-columns: minmax(0, 1fr); margin-block-start: .55rem; }
|
|
384
|
-
.compare-arm-definition { min-width: 0; overflow: hidden; background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; }
|
|
385
|
-
.compare-definition-tabbed .compare-arm-definition { display: none; }
|
|
386
|
-
.compare-arm-header { display: grid; gap: .2rem; padding: .8rem .9rem; border-block-end: 1px solid var(--border); }
|
|
387
|
-
.compare-arm-header h3 { margin: 0; font-size: .95rem; }
|
|
388
|
-
.compare-arm-description { margin: 0; color: var(--muted); font-size: .76rem; line-height: 1.45; }
|
|
389
|
-
.compare-arm-definition .test-intent, .compare-arm-definition .criteria-panel { border: 0; border-radius: 0; }
|
|
390
|
-
.compare-arm-definition .test-intent { background: transparent; }
|
|
391
|
-
.compare-arm-definition .criteria-panel { border-block-start: 1px solid var(--border); background: color-mix(in oklch, var(--panel-2) 82%, var(--bg)); }
|
|
392
|
-
.scenario-toolbar { justify-content: space-between; gap: .75rem 1rem; padding: .8rem clamp(1rem, 2vw, 1.5rem); background: var(--panel); border-block: 1px solid var(--border); }
|
|
393
|
-
.host-runs-label { font-size: .65rem; }
|
|
394
|
-
.host-tab { padding: .3rem .5rem; background: transparent; }
|
|
395
|
-
.host-tab[aria-selected="true"] { color: var(--text); background: color-mix(in oklch, var(--pass) 11%, var(--panel-2)); }
|
|
396
|
-
.scenario-actions .run-toggle { min-width: 5.6rem; padding: .28rem .55rem; font-size: .78rem; }
|
|
397
|
-
.scenario-live { padding: clamp(1rem, 2vw, 1.5rem); background: var(--bg); }
|
|
398
|
-
.scenario-live .host-empty { padding: 2rem; text-align: center; border: 1px dashed var(--border); border-radius: 8px; }
|
|
399
|
-
@container (max-width: 48rem) { .scenario-definition { grid-template-columns: minmax(0, 1fr); } .compare-definition { grid-column: auto; } }
|
|
400
|
-
@media (max-width: 800px) {
|
|
401
|
-
.viewer-topbar { position: static; grid-template-columns: minmax(0, 1fr); gap: .6rem; padding-block: .85rem; }
|
|
402
|
-
.viewer-identity { justify-content: space-between; }
|
|
403
|
-
.run-context { grid-template-columns: minmax(0, 1fr); gap: .25rem; }
|
|
132
|
+
.run-progress-segment { transition: none; }
|
|
133
|
+
}
|
|
134
|
+
.run-progress { border: 1px solid var(--border); border-radius: 10px; padding: .7rem; margin-bottom: .8rem; }
|
|
135
|
+
.run-progress-title { margin: 0 0 .4rem; }
|
|
136
|
+
.run-progress-track { display: flex; height: .5rem; border-radius: 999px; background: var(--panel-2); overflow: hidden; }
|
|
137
|
+
.run-progress-segment { display: block; height: 100%; transition: width .2s ease; }
|
|
138
|
+
.run-progress-passed { background: var(--pass); }
|
|
139
|
+
.run-progress-failed { background: var(--fail); }
|
|
140
|
+
.run-progress-skipped { background: var(--skip); }
|
|
141
|
+
.run-progress-counts { display: flex; flex-wrap: wrap; gap: 1rem; margin-top: .4rem; font-size: .78rem; color: var(--muted); }
|
|
142
|
+
.progress-passed strong { color: var(--pass); }
|
|
143
|
+
.progress-failed strong { color: var(--fail); }
|
|
144
|
+
.progress-skipped strong { color: var(--skip); }
|
|
145
|
+
.failure-panel { border-color: color-mix(in oklch, var(--fail) 42%, var(--border)); background: color-mix(in oklch, var(--fail) 5%, var(--panel-2)); }
|
|
146
|
+
.failure-section-head { display: flex; align-items: start; justify-content: space-between; gap: .75rem; padding-block-end: .65rem; border-block-end: 1px solid color-mix(in oklch, var(--fail) 22%, var(--border)); }
|
|
147
|
+
.failure-section-head h3 { color: var(--text); font-size: .86rem; }
|
|
148
|
+
.failure-section-head p { margin-block-start: .12rem; color: var(--muted); font-size: .72rem; }
|
|
149
|
+
.copy-error { min-height: 0; flex: none; padding: .28rem .48rem; font-size: .7rem; }
|
|
150
|
+
.failures { display: grid; gap: .65rem; margin-block-start: .7rem; }
|
|
151
|
+
.failures .failure-item { display: grid; gap: .32rem; padding: .62rem .68rem; border: 1px solid color-mix(in oklch, var(--fail) 24%, var(--border)); border-inline-start: 3px solid var(--fail); border-radius: 8px; background: color-mix(in oklch, var(--fail) 5%, var(--panel)); }
|
|
152
|
+
.failure-label { color: var(--fail); font-size: .68rem; font-weight: 750; letter-spacing: .055em; text-transform: uppercase; }
|
|
153
|
+
.failure-message { margin: 0; padding: 0; border: 0; background: transparent; color: var(--text); font: 650 .84rem/1.4 "Helvetica Neue", Helvetica, Arial, sans-serif; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
154
|
+
.failure-evidence-details { margin-block-start: .12rem; border-block-start: 1px solid var(--border); }
|
|
155
|
+
.failure-evidence-details > summary { cursor: pointer; padding-block: .42rem .1rem; color: var(--muted); font-size: .72rem; font-weight: 650; }
|
|
156
|
+
.failure-evidence { max-height: 11rem; margin: .4rem 0 0; padding: .5rem .55rem; overflow: auto; border: 1px dashed var(--border); border-radius: 6px; background: color-mix(in oklch, black 28%, var(--panel)); color: var(--muted); font: .7rem/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
157
|
+
.story-criteria { padding-block: .55rem .8rem; }
|
|
158
|
+
.story-criteria > h3 { margin: 0 0 .55rem; }
|
|
159
|
+
.judge-responses { display: grid; gap: .7rem; padding-block: .25rem .8rem; }
|
|
160
|
+
.judge-responses-inline { border: 0; background: transparent; padding: .75rem 0 0 1rem; }
|
|
161
|
+
.judge-response { display: grid; gap: .65rem; width: 100%; border: 1px solid color-mix(in oklch, var(--arm-a) 32%, var(--border)); border-radius: 12px; background: color-mix(in oklch, var(--arm-a) 7%, var(--panel)); padding: .9rem 1rem; }
|
|
162
|
+
.judge-response-inline { gap: .4rem; border: 0; border-radius: 0; background: transparent; padding: 0; color: color-mix(in oklch, var(--arm-a) 72%, var(--text)); }
|
|
163
|
+
.judge-answer-label { margin: 0; color: var(--muted); font-size: .7rem; font-weight: 700; letter-spacing: .055em; text-transform: uppercase; }
|
|
164
|
+
.judge-response-inline .judge-rationale, .judge-response-inline .judge-evidence, .judge-response-inline .judge-evidence q { color: inherit; }
|
|
165
|
+
.judge-response-head { display: flex; align-items: start; justify-content: space-between; gap: .75rem; }
|
|
166
|
+
.judge-response-head h3 { margin: 0; font-size: .95rem; }
|
|
167
|
+
.judge-verdict-badge { flex: none; padding: .15rem .38rem; border: 1px solid currentColor; border-radius: 999px; font-size: .62rem; font-weight: 800; letter-spacing: .055em; text-transform: uppercase; }
|
|
168
|
+
.judge-response.verdict-pass .judge-verdict-badge { color: var(--pass); }
|
|
169
|
+
.judge-response.verdict-fail .judge-verdict-badge { color: var(--fail); }
|
|
170
|
+
.judge-response.verdict-pending .judge-verdict-badge { color: var(--skip); }
|
|
171
|
+
.judge-rationale { margin: 0; color: var(--text); font-size: .95rem; line-height: 1.55; white-space: pre-wrap; }
|
|
172
|
+
.judge-evidence { display: grid; gap: .35rem; margin: 0; padding-inline-start: 1.2rem; color: var(--muted); font-size: .85rem; line-height: 1.5; }
|
|
173
|
+
.judge-evidence q { color: var(--text); }
|
|
174
|
+
.trace-command-list { display: grid; gap: .45rem; margin-block-start: .7rem; padding-block-start: .65rem; border-block-start: 1px solid var(--border); }
|
|
175
|
+
.trace-command-list h4 { margin: 0; font-size: .8rem; }
|
|
176
|
+
.trace-details .trace-summary-title { color: var(--text); font-weight: 700; }
|
|
177
|
+
.trace-details .trace-summary-meta { margin-inline-start: auto; color: var(--muted); font-weight: 400; }
|
|
178
|
+
.viewer-shell .chat { max-inline-size: none; }
|
|
179
|
+
@media (max-width: 760px) {
|
|
180
|
+
.viewer-shell { padding: .75rem; }
|
|
181
|
+
.viewer-topbar { align-items: start; }
|
|
404
182
|
.run-banner { text-align: start; }
|
|
405
|
-
.viewer-
|
|
406
|
-
.
|
|
407
|
-
.test-
|
|
408
|
-
.test-
|
|
409
|
-
.test-
|
|
410
|
-
.
|
|
411
|
-
.
|
|
412
|
-
.
|
|
413
|
-
.scenario-definition { grid-template-columns: minmax(0, 1fr); }
|
|
414
|
-
.compare-definition { grid-column: auto; }
|
|
415
|
-
.scenario-toolbar { align-items: flex-start; }
|
|
416
|
-
.host-runs { align-items: flex-start; flex-direction: column; }
|
|
417
|
-
}
|
|
418
|
-
@media (max-width: 480px) { .scenario-focus-header { display: grid; } .focus-verdict { justify-self: start; } .scenario-toolbar { display: grid; } .scenario-actions { width: 100%; } .scenario-actions .primary { flex: 1; } .viewer-command-row .run-toggle { width: 100%; margin-inline-start: 0; } }
|
|
419
|
-
`;
|
|
420
|
-
}
|
|
421
|
-
function criterionCode(value) {
|
|
422
|
-
return `<code>${escapeHtml(value)}</code>`;
|
|
423
|
-
}
|
|
424
|
-
function renderAllowedCommands(target) {
|
|
425
|
-
const commands = target.rubric.allowedCommands;
|
|
426
|
-
if (commands === undefined)
|
|
427
|
-
return "";
|
|
428
|
-
const list = commands.length
|
|
429
|
-
? commands.map(criterionCode).join('<span aria-hidden="true">, </span>')
|
|
430
|
-
: '<span class="task-command-empty">None</span>';
|
|
431
|
-
return `<div class="task-metadata task-allowed-commands"><span class="task-meta-label">Allowed commands</span><p>${list}</p></div>`;
|
|
432
|
-
}
|
|
433
|
-
function renderProvidedContext(target) {
|
|
434
|
-
if (!target.contextSources?.length)
|
|
435
|
-
return "";
|
|
436
|
-
const sources = target.contextSources
|
|
437
|
-
.map(criterionCode)
|
|
438
|
-
.join('<span aria-hidden="true">, </span>');
|
|
439
|
-
return `<div class="task-metadata task-provided-context"><span class="task-meta-label">Provided context</span><p>${sources}</p></div>`;
|
|
440
|
-
}
|
|
441
|
-
function renderSuppliedResources(target, showEmpty = false) {
|
|
442
|
-
const servers = target.suppliedMcp ?? [];
|
|
443
|
-
const tools = [...new Set(servers.flatMap((server) => server.tools))];
|
|
444
|
-
const skills = target.suppliedSkills ?? [];
|
|
445
|
-
const value = (items) => items.length
|
|
446
|
-
? items.map(criterionCode).join('<span aria-hidden="true">, </span>')
|
|
447
|
-
: '<span class="task-resource-empty">None</span>';
|
|
448
|
-
if (!showEmpty && servers.length === 0 && skills.length === 0)
|
|
449
|
-
return "";
|
|
450
|
-
return `<div class="task-resources">
|
|
451
|
-
<div class="task-metadata"><span class="task-meta-label">Supplied MCP servers</span><p>${value(servers.map((server) => server.name))}</p></div>
|
|
452
|
-
<div class="task-metadata"><span class="task-meta-label">Supplied MCP tools</span><p>${value(tools)}</p></div>
|
|
453
|
-
<div class="task-metadata"><span class="task-meta-label">Supplied skills</span><p>${value(skills)}</p></div>
|
|
454
|
-
</div>`;
|
|
455
|
-
}
|
|
456
|
-
function renderPassCriteria(target) {
|
|
457
|
-
const groups = [];
|
|
458
|
-
const rubric = target.rubric;
|
|
459
|
-
const reply = [];
|
|
460
|
-
for (const text of rubric.must ?? []) {
|
|
461
|
-
reply.push(`Final reply must include ${criterionCode(text)}.`);
|
|
462
|
-
}
|
|
463
|
-
for (const text of rubric.mustNot ?? []) {
|
|
464
|
-
reply.push(`The run must not contain ${criterionCode(text)}.`);
|
|
465
|
-
}
|
|
466
|
-
if (reply.length)
|
|
467
|
-
groups.push({ title: "Reply", items: reply });
|
|
468
|
-
if (rubric.mustRun?.length || rubric.mustRunSuccessfully?.length) {
|
|
469
|
-
groups.push({
|
|
470
|
-
title: "Required commands",
|
|
471
|
-
items: [
|
|
472
|
-
...(rubric.mustRun ?? []).map((command) => `Must run a command matching ${criterionCode(command)}.`),
|
|
473
|
-
...(rubric.mustRunSuccessfully ?? []).map((command) => `Must run a command matching ${criterionCode(command)} successfully.`),
|
|
474
|
-
],
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
const tools = [
|
|
478
|
-
...(rubric.mustCallTool ?? []).map((tool) => `Must call ${criterionCode(tool)}.`),
|
|
479
|
-
...(rubric.mustCallToolsInOrder ?? []).map((tool, index) => `${index + 1}. Call ${criterionCode(tool)}.`),
|
|
480
|
-
...(rubric.mustNotCallTool ?? []).map((tool) => `Must not call ${criterionCode(tool)}.`),
|
|
481
|
-
];
|
|
482
|
-
if (tools.length) {
|
|
483
|
-
groups.push({
|
|
484
|
-
title: "Tools",
|
|
485
|
-
intro: rubric.mustCallToolsInOrder?.length ? "Ordered tool calls are numbered." : undefined,
|
|
486
|
-
items: tools,
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
const files = [
|
|
490
|
-
...(rubric.mustReadPath ?? []).map((path) => `Must read a path matching ${criterionCode(path)}.`),
|
|
491
|
-
...(rubric.mustNotReadPath ?? []).map((path) => `Must not read a path matching ${criterionCode(path)}.`),
|
|
492
|
-
];
|
|
493
|
-
if (files.length)
|
|
494
|
-
groups.push({ title: "Files", items: files });
|
|
495
|
-
const skills = [
|
|
496
|
-
...(rubric.mustInvokeSkill ?? []).map((skill) => `Must use ${criterionCode(skill)}.`),
|
|
497
|
-
...(rubric.mustNotInvokeSkill ?? []).map((skill) => `Must not use ${criterionCode(skill)}.`),
|
|
498
|
-
];
|
|
499
|
-
if (skills.length)
|
|
500
|
-
groups.push({ title: "Skills", items: skills });
|
|
501
|
-
const routing = [
|
|
502
|
-
...(rubric.handsOnRouting
|
|
503
|
-
? ["Must announce hands-on routing before the first tool call."]
|
|
504
|
-
: []),
|
|
505
|
-
...(rubric.tier ? [`Must announce the ${criterionCode(rubric.tier)} tier.`] : []),
|
|
506
|
-
...(rubric.reviewDepth
|
|
507
|
-
? [`Must announce ${criterionCode(rubric.reviewDepth)} review depth.`]
|
|
508
|
-
: []),
|
|
509
|
-
...(rubric.routingBlock ? ["Must announce a routing block."] : []),
|
|
510
|
-
];
|
|
511
|
-
if (routing.length)
|
|
512
|
-
groups.push({ title: "Routing", items: routing });
|
|
513
|
-
if (rubric.judge?.length) {
|
|
514
|
-
groups.push({
|
|
515
|
-
title: "Judge",
|
|
516
|
-
intro: "A judge evaluates these questions after the run.",
|
|
517
|
-
items: rubric.judge.map((item) => escapeHtml(typeof item === "string" ? item : item.question)),
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
const count = groups.reduce((total, group) => total + Math.max(group.items.length, 1), 0);
|
|
521
|
-
const html = groups
|
|
522
|
-
.map((group) => `<section class="criterion-group"><h4>${escapeHtml(group.title)}</h4>${group.intro ? `<p>${escapeHtml(group.intro)}</p>` : ""}${group.items.length ? `<ul class="criterion-list">${group.items.map((item) => `<li>${item}</li>`).join("")}</ul>` : ""}</section>`)
|
|
523
|
-
.join("");
|
|
524
|
-
return { count, html };
|
|
525
|
-
}
|
|
526
|
-
function armLabel(scenario, id) {
|
|
527
|
-
return scenario.compare?.find((arm) => arm.id === id)?.label ?? id;
|
|
528
|
-
}
|
|
529
|
-
function renderComparisonCriteria(scenario) {
|
|
530
|
-
const items = scenario.gates?.length
|
|
531
|
-
? scenario.gates.map((gate) => `${escapeHtml(describeCompareGate(gate, (id) => armLabel(scenario, id)))}.`)
|
|
532
|
-
: (scenario.compare ?? []).map((arm) => `${escapeHtml(arm.label)} must pass all of its criteria.`);
|
|
533
|
-
const acceptableBehavior = scenario.judgeMetrics?.length
|
|
534
|
-
? `<section class="criterion-group comparison-acceptable-behavior"><h4>Acceptable behavior</h4><p>The same questions are judged separately for every arm. The arm-result rules below decide which verdicts are required.</p><ul class="criterion-list">${scenario.judgeMetrics.map((metric) => `<li>${escapeHtml(metric.question)}</li>`).join("")}</ul></section>`
|
|
535
|
-
: "";
|
|
536
|
-
const sharedJudge = scenario.rubric.judge?.length
|
|
537
|
-
? `<section class="criterion-group comparison-shared-judge"><h4>Comparison judge</h4><p>One judge sees all completed arms and evaluates the comparison as a whole.</p><ul class="criterion-list">${scenario.rubric.judge.map((item) => `<li>${escapeHtml(typeof item === "string" ? item : item.question)}</li>`).join("")}</ul></section>`
|
|
538
|
-
: "";
|
|
539
|
-
return {
|
|
540
|
-
count: items.length + (scenario.judgeMetrics?.length ?? 0) + (scenario.rubric.judge?.length ?? 0),
|
|
541
|
-
html: `${acceptableBehavior}${sharedJudge}<section class="criterion-group"><h4>Arm results</h4><ul class="criterion-list">${items.map((item) => `<li>${item}</li>`).join("")}</ul></section>`,
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
function renderCompareArm(arm) {
|
|
545
|
-
const criteria = renderPassCriteria(arm);
|
|
546
|
-
return `<article class="compare-arm-definition" data-compare-arm-definition="${escapeHtml(arm.id)}">
|
|
547
|
-
<header class="compare-arm-header">
|
|
548
|
-
<p class="section-label">Arm</p>
|
|
549
|
-
<h3>${escapeHtml(arm.label)}</h3>
|
|
550
|
-
${arm.description ? `<p class="compare-arm-description">${escapeHtml(arm.description)}</p>` : ""}
|
|
551
|
-
</header>
|
|
552
|
-
<section class="test-intent">
|
|
553
|
-
<p class="section-label">Task</p>
|
|
554
|
-
<p class="prompt-preview">${escapeHtml(arm.prompt)}</p>
|
|
555
|
-
${renderAllowedCommands(arm)}
|
|
556
|
-
${renderProvidedContext(arm)}
|
|
557
|
-
${renderSuppliedResources(arm, true)}
|
|
558
|
-
</section>
|
|
559
|
-
<details class="criteria-panel" open>
|
|
560
|
-
<summary><span>Pass criteria</span><span class="criteria-summary">${criteria.count ? `${criteria.count} ${criteria.count === 1 ? "check" : "checks"}` : "Completion only"}</span></summary>
|
|
561
|
-
${criteria.html ? `<div class="criterion-groups">${criteria.html}</div>` : '<p class="criteria-empty">This arm passes when it completes without a runner error.</p>'}
|
|
562
|
-
</details>
|
|
563
|
-
</article>`;
|
|
564
|
-
}
|
|
565
|
-
function comparisonDefinitionGroupId(suite, scenario) {
|
|
566
|
-
return `cd-${suite.name}-${scenario.name}`
|
|
567
|
-
.toLowerCase()
|
|
568
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
569
|
-
.replace(/^-+|-+$/g, "");
|
|
570
|
-
}
|
|
571
|
-
function renderCompareDefinition(suite, scenario) {
|
|
572
|
-
const arms = scenario.compare ?? [];
|
|
573
|
-
const group = comparisonDefinitionGroupId(suite, scenario);
|
|
574
|
-
const tabs = arms
|
|
575
|
-
.map((arm, index) => {
|
|
576
|
-
const id = `${group}-${arm.id}`;
|
|
577
|
-
return `<input type="radio" class="compare-definition-tab-input" name="${escapeHtml(group)}" id="${escapeHtml(id)}"${index === 0 ? " checked" : ""}>
|
|
578
|
-
<label class="compare-definition-tab" role="tab" for="${escapeHtml(id)}">${escapeHtml(arm.label)}</label>`;
|
|
579
|
-
})
|
|
580
|
-
.join("");
|
|
581
|
-
const rules = arms
|
|
582
|
-
.map((arm) => `.${group}:has(#${group}-${arm.id}:checked) [data-compare-arm-definition="${arm.id}"]{display:block}`)
|
|
583
|
-
.join("");
|
|
584
|
-
return `<style>${rules}</style>
|
|
585
|
-
<section class="compare-definition compare-definition-tabbed ${escapeHtml(group)}">
|
|
586
|
-
<p class="section-label">Arms</p>
|
|
587
|
-
<div class="compare-definition-tabs" role="tablist" aria-label="Comparison arm definitions">${tabs}</div>
|
|
588
|
-
<div class="compare-definition-grid">${arms.map(renderCompareArm).join("")}</div>
|
|
589
|
-
</section>`;
|
|
590
|
-
}
|
|
591
|
-
function defaultScenarioHost(suite, scenario, defaultSelectedHosts) {
|
|
592
|
-
if (scenario.host && suite.hosts.includes(scenario.host)) {
|
|
593
|
-
return scenario.host;
|
|
594
|
-
}
|
|
595
|
-
const configured = suite.hosts.find((host) => defaultSelectedHosts.includes(host));
|
|
596
|
-
if (configured) {
|
|
597
|
-
return configured;
|
|
598
|
-
}
|
|
599
|
-
return suite.hosts.length === 1 ? (suite.hosts[0] ?? "") : "";
|
|
600
|
-
}
|
|
601
|
-
function hostIsSkipped(scenario, host) {
|
|
602
|
-
return Boolean(scenario.skip || (scenario.host && scenario.host !== host));
|
|
603
|
-
}
|
|
604
|
-
function renderHostChrome(suite, scenario, selectedHost) {
|
|
605
|
-
if (suite.hosts.length <= 1) {
|
|
606
|
-
const host = suite.hosts[0] ?? selectedHost;
|
|
607
|
-
const skipped = hostIsSkipped(scenario, host);
|
|
608
|
-
return `<div class="host-status" data-cell="${escapeHtml(`${suite.name}::${scenario.name}::${host}`)}">
|
|
609
|
-
<span class="cell-status muted">${skipped ? "skip" : "idle"}</span>
|
|
610
|
-
</div>`;
|
|
611
|
-
}
|
|
612
|
-
const tabs = suite.hosts
|
|
613
|
-
.map((host) => {
|
|
614
|
-
const skipped = hostIsSkipped(scenario, host);
|
|
615
|
-
const selected = host === selectedHost;
|
|
616
|
-
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>`;
|
|
617
|
-
})
|
|
618
|
-
.join("");
|
|
619
|
-
return `<div class="host-runs"><span class="host-runs-label">Run host</span><div class="host-tablist" role="tablist">${tabs}</div><span class="host-empty-selection" hidden>No selected host can run this test.</span></div>`;
|
|
620
|
-
}
|
|
621
|
-
function renderHostPanels(suite, scenario, selectedHost, results, canRun) {
|
|
622
|
-
return suite.hosts
|
|
623
|
-
.map((host) => {
|
|
624
|
-
const selected = host === selectedHost;
|
|
625
|
-
const result = results.find((item) => item.suite === suite.name && item.scenario === scenario.name && item.host === host);
|
|
626
|
-
const hostHeading = !canRun && suite.hosts.length > 1 ? `<h3>${escapeHtml(host)}</h3>` : "";
|
|
627
|
-
return `<div class="host-panel" data-host-panel="${escapeHtml(host)}" role="tabpanel"${canRun && !selected ? " hidden" : ""}>${hostHeading}${result?.html ?? '<p class="host-empty">No run yet.</p>'}</div>`;
|
|
628
|
-
})
|
|
629
|
-
.join("");
|
|
630
|
-
}
|
|
631
|
-
function renderScenarioCard(suite, scenario, results, canRun, defaultSelectedHosts, selected) {
|
|
632
|
-
const selectedHost = defaultScenarioHost(suite, scenario, defaultSelectedHosts);
|
|
633
|
-
const runDisabled = !selectedHost || hostIsSkipped(scenario, selectedHost);
|
|
634
|
-
const hasResult = results.some((item) => item.suite === suite.name && item.scenario === scenario.name);
|
|
635
|
-
const key = escapeHtml(`${suite.name}::${scenario.name}`);
|
|
636
|
-
const criteria = renderPassCriteria(scenario);
|
|
637
|
-
const comparisonCriteria = scenario.compare ? renderComparisonCriteria(scenario) : undefined;
|
|
638
|
-
const definition = comparisonCriteria
|
|
639
|
-
? `<section class="test-intent comparison-task">
|
|
640
|
-
<p class="section-label">Comparison task</p>
|
|
641
|
-
<p class="prompt-preview">${escapeHtml(scenario.prompt)}</p>
|
|
642
|
-
</section>
|
|
643
|
-
<section class="comparison-criteria">
|
|
644
|
-
<p class="section-label">Comparison pass criteria</p>
|
|
645
|
-
<p class="comparison-intro">Each arm runs independently. These checks decide whether the comparison passes.</p>
|
|
646
|
-
<div class="criterion-groups">${comparisonCriteria.html}</div>
|
|
647
|
-
</section>
|
|
648
|
-
${renderCompareDefinition(suite, scenario)}`
|
|
649
|
-
: `<section class="test-intent">
|
|
650
|
-
<p class="section-label">Task</p>
|
|
651
|
-
<p class="prompt-preview">${escapeHtml(scenario.prompt)}</p>
|
|
652
|
-
${renderAllowedCommands(scenario)}
|
|
653
|
-
${renderProvidedContext(scenario)}
|
|
654
|
-
${renderSuppliedResources(scenario)}
|
|
655
|
-
</section>
|
|
656
|
-
<details class="criteria-panel" open>
|
|
657
|
-
<summary><span>Pass criteria</span><span class="criteria-summary">${criteria.count ? `${criteria.count} ${criteria.count === 1 ? "check" : "checks"}` : "Completion only"}</span></summary>
|
|
658
|
-
${criteria.html ? `<div class="criterion-groups">${criteria.html}</div>` : '<p class="criteria-empty">The scenario passes when it completes without a runner error.</p>'}
|
|
659
|
-
</details>`;
|
|
660
|
-
return `<article class="scenario-card" data-scenario-card="${key}" data-selected="${selected ? "true" : "false"}"${selected ? "" : " hidden"}>
|
|
661
|
-
<header class="scenario-focus-header">
|
|
662
|
-
<div class="scenario-heading-copy">
|
|
663
|
-
<p class="scenario-path">${escapeHtml(suite.name)} <span aria-hidden="true">/</span> test</p>
|
|
664
|
-
${suite.description ? `<p class="suite-description">${escapeHtml(suite.description)}</p>` : ""}
|
|
665
|
-
<h2>${escapeHtml(scenario.name)}</h2>
|
|
666
|
-
${scenario.description ? `<p class="scenario-lede">${escapeHtml(scenario.description)}</p>` : ""}
|
|
667
|
-
</div>
|
|
668
|
-
<span class="focus-verdict" data-focus-verdict="${key}" data-status="idle"><span aria-hidden="true"></span>Not run</span>
|
|
669
|
-
</header>
|
|
670
|
-
<div class="scenario-definition">
|
|
671
|
-
${definition}
|
|
672
|
-
</div>
|
|
673
|
-
<div class="scenario-toolbar">
|
|
674
|
-
${renderHostChrome(suite, scenario, selectedHost)}
|
|
675
|
-
<div class="scenario-actions">
|
|
676
|
-
<button class="primary run-toggle run-cell" data-start-label="Start test" data-suite="${escapeHtml(suite.name)}" data-scenario="${escapeHtml(scenario.name)}" data-host="${escapeHtml(selectedHost)}"${scenario.skip || !canRun ? ' disabled data-fixed-disabled="1"' : runDisabled ? " disabled" : ""}>Start test</button>
|
|
677
|
-
</div>
|
|
678
|
-
</div>
|
|
679
|
-
<div class="scenario-live"${hasResult ? "" : " hidden"} data-live-row="${key}" data-live-slot="${key}">
|
|
680
|
-
<div class="host-panels">${renderHostPanels(suite, scenario, selectedHost, results, canRun)}</div>
|
|
681
|
-
</div>
|
|
682
|
-
</article>`;
|
|
683
|
-
}
|
|
684
|
-
function renderSuiteSection(suite, results, canRun, defaultSelectedHosts, firstScenarioKey) {
|
|
685
|
-
const cards = suite.scenarios
|
|
686
|
-
.map((scenario) => renderScenarioCard(suite, scenario, results, canRun, defaultSelectedHosts, `${suite.name}::${scenario.name}` === firstScenarioKey))
|
|
687
|
-
.join("");
|
|
688
|
-
return `
|
|
689
|
-
<section class="suite" data-suite="${escapeHtml(suite.name)}">
|
|
690
|
-
<div class="scenario-list">${cards}</div>
|
|
691
|
-
</section>`;
|
|
692
|
-
}
|
|
693
|
-
function renderTestNavigator(catalog, firstScenarioKey, canRun) {
|
|
694
|
-
const groups = catalog.suites
|
|
695
|
-
.map((suite) => {
|
|
696
|
-
const tests = suite.scenarios
|
|
697
|
-
.map((scenario) => {
|
|
698
|
-
const key = `${suite.name}::${scenario.name}`;
|
|
699
|
-
const selected = key === firstScenarioKey;
|
|
700
|
-
const status = scenario.skip ? "skipped" : "idle";
|
|
701
|
-
return `<button type="button" class="test-nav-item" data-select-scenario="${escapeHtml(key)}" data-status="${status}" aria-current="${selected ? "true" : "false"}">
|
|
702
|
-
<span class="test-nav-status" aria-hidden="true"></span>
|
|
703
|
-
<span class="test-nav-copy"><strong>${escapeHtml(scenario.name)}</strong>${scenario.description ? `<small>${escapeHtml(scenario.description)}</small>` : ""}</span>
|
|
704
|
-
</button>`;
|
|
705
|
-
})
|
|
706
|
-
.join("");
|
|
707
|
-
return `<section class="test-nav-suite" data-nav-suite="${escapeHtml(suite.name)}">
|
|
708
|
-
<header><div><h2>${escapeHtml(suite.name)}</h2>${suite.description ? `<p>${escapeHtml(suite.description)}</p>` : ""}</div><button class="run-toggle run-suite" data-start-label="Start suite" data-suite="${escapeHtml(suite.name)}"${canRun ? "" : ' disabled data-fixed-disabled="1"'}>Start suite</button></header>
|
|
709
|
-
<div class="test-nav-items">${tests}</div>
|
|
710
|
-
</section>`;
|
|
711
|
-
})
|
|
712
|
-
.join("");
|
|
713
|
-
const options = catalog.suites
|
|
714
|
-
.flatMap((suite) => suite.scenarios.map((scenario) => {
|
|
715
|
-
const key = `${suite.name}::${scenario.name}`;
|
|
716
|
-
return `<option value="${escapeHtml(key)}"${key === firstScenarioKey ? " selected" : ""}>${escapeHtml(suite.name)} / ${escapeHtml(scenario.name)}</option>`;
|
|
717
|
-
}))
|
|
718
|
-
.join("");
|
|
719
|
-
return `<aside class="test-navigator" aria-label="Tests">
|
|
720
|
-
<div class="test-navigator-heading"><div><p class="section-label">Test catalog</p><h2>${catalog.suites.reduce((sum, suite) => sum + suite.scenarios.length, 0)} tests</h2></div></div>
|
|
721
|
-
<label class="test-picker-label" for="test-picker">Selected test</label>
|
|
722
|
-
<select class="test-picker" id="test-picker">${options}</select>
|
|
723
|
-
<nav class="test-tree" aria-label="Test catalog">${groups}</nav>
|
|
724
|
-
</aside>`;
|
|
725
|
-
}
|
|
726
|
-
function clientScript() {
|
|
727
|
-
return `
|
|
728
|
-
(function () {
|
|
729
|
-
var bootstrap = JSON.parse(document.getElementById("bootstrap-data").textContent);
|
|
730
|
-
var catalog = bootstrap.catalog;
|
|
731
|
-
var source = null;
|
|
732
|
-
var runId = null;
|
|
733
|
-
var selectedRunId = bootstrap.selectedRunId || null;
|
|
734
|
-
var cancelRequested = false;
|
|
735
|
-
var activeRunRequest = null;
|
|
736
|
-
var viewedRunHosts = null;
|
|
737
|
-
var progress = { total: 0, completed: 0, passed: 0, failed: 0, skipped: 0, seen: {}, experiments: {} };
|
|
738
|
-
var selectedScenarioKey = document.querySelector("[data-scenario-card][data-selected='true']")?.getAttribute("data-scenario-card") || null;
|
|
739
|
-
|
|
740
|
-
function selectScenario(key) {
|
|
741
|
-
if (!key) return;
|
|
742
|
-
selectedScenarioKey = key;
|
|
743
|
-
document.querySelectorAll("[data-scenario-card]").forEach(function (card) {
|
|
744
|
-
var selected = card.getAttribute("data-scenario-card") === key;
|
|
745
|
-
card.dataset.selected = selected ? "true" : "false";
|
|
746
|
-
card.hidden = !selected;
|
|
747
|
-
});
|
|
748
|
-
document.querySelectorAll("[data-select-scenario]").forEach(function (item) {
|
|
749
|
-
item.setAttribute("aria-current", item.getAttribute("data-select-scenario") === key ? "true" : "false");
|
|
750
|
-
});
|
|
751
|
-
var picker = document.getElementById("test-picker");
|
|
752
|
-
if (picker) picker.value = key;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
function syncScenarioStatus(suite, scenario) {
|
|
756
|
-
var key = suite + "::" + scenario;
|
|
757
|
-
var card = document.querySelector('[data-scenario-card="' + key + '"]');
|
|
758
|
-
if (!card) return;
|
|
759
|
-
var labels = Array.prototype.map.call(card.querySelectorAll(".cell-status"), function (node) { return node.textContent; });
|
|
760
|
-
var status = labels.indexOf("failed") !== -1 ? "failed"
|
|
761
|
-
: labels.some(function (label) { return label === "running" || label === "starting" || label === "cancelling"; }) ? "running"
|
|
762
|
-
: labels.indexOf("passed") !== -1 ? "passed"
|
|
763
|
-
: labels.length > 0 && labels.every(function (label) { return label === "skip" || label === "skipped" || label === "cancelled"; }) ? "skipped"
|
|
764
|
-
: "idle";
|
|
765
|
-
document.querySelectorAll('[data-select-scenario="' + key + '"]').forEach(function (item) { item.dataset.status = status; });
|
|
766
|
-
var verdict = document.querySelector('[data-focus-verdict="' + key + '"]');
|
|
767
|
-
if (verdict) {
|
|
768
|
-
verdict.dataset.status = status;
|
|
769
|
-
verdict.lastChild.textContent = status === "idle" ? "Not run" : status.charAt(0).toUpperCase() + status.slice(1);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
function selectedHosts() {
|
|
774
|
-
return Array.prototype.map.call(document.querySelectorAll("[data-host-toggle]:checked"), function (box) {
|
|
775
|
-
return box.value;
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
function updateHostSelectionControls() {
|
|
780
|
-
var hasSelectedHost = selectedHosts().length > 0;
|
|
781
|
-
syncScenarioHostChoices();
|
|
782
|
-
document.querySelectorAll(".run-toggle").forEach(function (button) {
|
|
783
|
-
if (button.dataset.runAction !== "stop") {
|
|
784
|
-
var hasTarget = button.classList.contains("run-cell")
|
|
785
|
-
? !!button.getAttribute("data-host")
|
|
786
|
-
: hasSelectedHost;
|
|
787
|
-
button.disabled = !hasTarget || button.dataset.fixedDisabled === "1";
|
|
788
|
-
}
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
function syncScenarioHostChoices() {
|
|
793
|
-
var hosts = Array.isArray(viewedRunHosts) ? viewedRunHosts : selectedHosts();
|
|
794
|
-
var isFiltered = hosts.length > 0;
|
|
795
|
-
document.querySelectorAll("[data-scenario-card]").forEach(function (card) {
|
|
796
|
-
var tabs = Array.prototype.slice.call(card.querySelectorAll(".host-tab"));
|
|
797
|
-
if (tabs.length === 0) return;
|
|
798
|
-
tabs.forEach(function (tab) {
|
|
799
|
-
var eligible = !tab.disabled && (!isFiltered || hosts.indexOf(tab.getAttribute("data-host")) !== -1);
|
|
800
|
-
tab.hidden = isFiltered && !eligible;
|
|
801
|
-
});
|
|
802
|
-
var current = tabs.find(function (tab) { return tab.getAttribute("aria-selected") === "true" && !tab.hidden && !tab.disabled; });
|
|
803
|
-
var available = tabs.find(function (tab) { return !tab.hidden && !tab.disabled; });
|
|
804
|
-
if (!current && isFiltered && available) {
|
|
805
|
-
selectHostTab(card, available.getAttribute("data-host"));
|
|
806
|
-
current = available;
|
|
807
|
-
}
|
|
808
|
-
var empty = card.querySelector(".host-empty-selection");
|
|
809
|
-
if (empty) empty.hidden = !!available;
|
|
810
|
-
if (!available) {
|
|
811
|
-
tabs.forEach(function (tab) { tab.setAttribute("aria-selected", "false"); });
|
|
812
|
-
card.querySelectorAll("[data-host-panel]").forEach(function (panel) { panel.hidden = true; });
|
|
813
|
-
var run = card.querySelector("button.run-cell");
|
|
814
|
-
if (run) {
|
|
815
|
-
run.setAttribute("data-host", "");
|
|
816
|
-
run.disabled = true;
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
function scopedRunControl(request) {
|
|
823
|
-
if (!request) return null;
|
|
824
|
-
if (request.scenario) {
|
|
825
|
-
return document.querySelector('.run-cell[data-suite="' + request.suite + '"][data-scenario="' + request.scenario + '"]');
|
|
826
|
-
}
|
|
827
|
-
if (request.suite) return document.querySelector('.run-suite[data-suite="' + request.suite + '"]');
|
|
828
|
-
return null;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
function setRunControls(status, request) {
|
|
832
|
-
activeRunRequest = status === "idle" ? null : request || activeRunRequest;
|
|
833
|
-
document.querySelectorAll(".run-toggle").forEach(function (button) {
|
|
834
|
-
button.textContent = button.dataset.startLabel || "Start run";
|
|
835
|
-
button.dataset.runAction = "start";
|
|
836
|
-
button.classList.remove("danger");
|
|
837
|
-
});
|
|
838
|
-
updateHostSelectionControls();
|
|
839
|
-
if (status === "idle") return;
|
|
840
|
-
document.querySelectorAll(".run-toggle").forEach(function (button) { button.disabled = true; });
|
|
841
|
-
var controls = [document.getElementById("run-selection"), scopedRunControl(activeRunRequest)];
|
|
842
|
-
controls.forEach(function (button) {
|
|
843
|
-
if (!button) return;
|
|
844
|
-
button.dataset.runAction = "stop";
|
|
845
|
-
button.textContent = status === "cancelling"
|
|
846
|
-
? "Stopping…"
|
|
847
|
-
: button.id === "run-selection" ? "Stop run" : (button.classList.contains("run-suite") ? "Stop suite" : "Stop test");
|
|
848
|
-
button.classList.add("danger");
|
|
849
|
-
button.disabled = status === "cancelling";
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
function parallelHosts() {
|
|
854
|
-
return document.getElementById("parallel-hosts").checked;
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
function setRunBanner(text) {
|
|
858
|
-
document.getElementById("run-banner").textContent = text;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
function runnableTargets(body) {
|
|
862
|
-
var requestedHosts = Array.isArray(body.hosts) ? body.hosts : catalog.defaultSelectedHosts;
|
|
863
|
-
var total = 0;
|
|
864
|
-
catalog.suites.forEach(function (suite) {
|
|
865
|
-
if (body.suite && suite.name !== body.suite) {
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
suite.scenarios.forEach(function (scenario) {
|
|
869
|
-
if (body.scenario && scenario.name !== body.scenario) {
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
if (scenario.skip) {
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
requestedHosts.forEach(function (host) {
|
|
876
|
-
if (suite.hosts.indexOf(host) === -1 || (scenario.host && scenario.host !== host)) {
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
total += 1;
|
|
880
|
-
});
|
|
881
|
-
});
|
|
882
|
-
});
|
|
883
|
-
return total;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
function renderProgress() {
|
|
887
|
-
var box = document.getElementById("run-progress");
|
|
888
|
-
var total = Math.max(progress.total, progress.completed);
|
|
889
|
-
var remaining = Math.max(0, total - progress.completed);
|
|
890
|
-
box.hidden = total === 0;
|
|
891
|
-
box.setAttribute("data-has-failures", progress.failed > 0 ? "true" : "false");
|
|
892
|
-
var testLabel = total === 1 ? "test" : "tests";
|
|
893
|
-
box.querySelector(".run-progress-title").textContent = progress.completed + " of " + total + " " + testLabel + " finished";
|
|
894
|
-
box.querySelector(".progress-passed strong").textContent = String(progress.passed);
|
|
895
|
-
box.querySelector(".progress-failed strong").textContent = String(progress.failed);
|
|
896
|
-
box.querySelector(".progress-skipped strong").textContent = String(progress.skipped);
|
|
897
|
-
box.querySelector(".progress-remaining strong").textContent = String(remaining);
|
|
898
|
-
var percent = total > 0 ? Math.round((progress.completed / total) * 100) : 0;
|
|
899
|
-
var fill = box.querySelector(".run-progress-fill");
|
|
900
|
-
fill.style.width = percent + "%";
|
|
901
|
-
fill.parentNode.setAttribute("aria-valuenow", String(progress.completed));
|
|
902
|
-
fill.parentNode.setAttribute("aria-valuemax", String(total));
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
function resetProgress(body) {
|
|
906
|
-
progress = { total: runnableTargets(body), completed: 0, passed: 0, failed: 0, skipped: 0, seen: {}, experiments: {} };
|
|
907
|
-
renderProgress();
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
function recordProgress(event) {
|
|
911
|
-
var key = cellKey(event);
|
|
912
|
-
if (progress.seen[key]) {
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
progress.seen[key] = true;
|
|
916
|
-
var experiment = experimentState(event);
|
|
917
|
-
experiment.cells[event.arm || "_"] = event;
|
|
918
|
-
var scenario = findScenario(event.suite, event.scenario);
|
|
919
|
-
var expectedArms = scenario && scenario.compare && scenario.compare.length ? scenario.compare.length : 1;
|
|
920
|
-
if (Object.keys(experiment.cells).length < expectedArms || experiment.counted) {
|
|
921
|
-
renderProgress();
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
|
-
experiment.counted = true;
|
|
925
|
-
progress.completed += 1;
|
|
926
|
-
var outcome = experimentOutcome(scenario, experiment.cells);
|
|
927
|
-
if (outcome === "skipped") progress.skipped += 1;
|
|
928
|
-
else if (outcome === "passed") progress.passed += 1;
|
|
929
|
-
else progress.failed += 1;
|
|
930
|
-
renderProgress();
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
function finishProgress(event) {
|
|
934
|
-
// The stream reports one cell per comparison arm. Progress is based on
|
|
935
|
-
// the completed scenario/host experiment, so expected failed controls do
|
|
936
|
-
// not turn a passing comparison into a red suite result.
|
|
937
|
-
progress.total = Math.max(progress.total, progress.completed);
|
|
938
|
-
renderProgress();
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
function experimentKey(event) {
|
|
942
|
-
return [event.suite, event.scenario, event.host].join("::");
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
function experimentState(event) {
|
|
946
|
-
var key = experimentKey(event);
|
|
947
|
-
if (!progress.experiments[key]) {
|
|
948
|
-
progress.experiments[key] = { cells: {}, counted: false };
|
|
949
|
-
}
|
|
950
|
-
return progress.experiments[key];
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
function experimentOutcome(scenario, cells) {
|
|
954
|
-
var events = Object.keys(cells).map(function (id) { return cells[id]; });
|
|
955
|
-
if (events.some(function (event) { return event.skipped; })) return "skipped";
|
|
956
|
-
var byArm = {};
|
|
957
|
-
events.forEach(function (event) { byArm[event.arm || "_"] = event; });
|
|
958
|
-
if (!scenario || !scenario.compare || !scenario.compare.length) {
|
|
959
|
-
return events.every(function (event) { return event.passed; }) ? "passed" : "failed";
|
|
960
|
-
}
|
|
961
|
-
var gates = scenario.gates || [];
|
|
962
|
-
var behaviorOk = events.every(function (event) {
|
|
963
|
-
return event.passed || (event.failures || []).every(function (failure) {
|
|
964
|
-
return gates.some(function (gate) {
|
|
965
|
-
return gate.metric === "outcome" && gate.arm === event.arm && gate.operator === "equal" && gate.value === "fail";
|
|
966
|
-
});
|
|
967
|
-
});
|
|
968
|
-
});
|
|
969
|
-
var gatesOk = gates.every(function (gate) {
|
|
970
|
-
if (gate.metric === "outcome" && gate.arm) {
|
|
971
|
-
var event = byArm[gate.arm];
|
|
972
|
-
if (!event) return false;
|
|
973
|
-
var actual = event.passed ? "pass" : "fail";
|
|
974
|
-
return gate.operator === "equal" ? actual === gate.value : false;
|
|
975
|
-
}
|
|
976
|
-
if (["turns", "tokens", "tools", "durationMs"].indexOf(gate.metric) !== -1 && gate.winner && gate.loser) {
|
|
977
|
-
var winner = byArm[gate.winner];
|
|
978
|
-
var loser = byArm[gate.loser];
|
|
979
|
-
var field = gate.metric === "durationMs" ? "durationMs" : gate.metric;
|
|
980
|
-
var winnerValue = winner && winner.metrics && winner.metrics[field];
|
|
981
|
-
var loserValue = loser && loser.metrics && loser.metrics[field];
|
|
982
|
-
return typeof winnerValue === "number" && typeof loserValue === "number" && winnerValue < loserValue;
|
|
983
|
-
}
|
|
984
|
-
return true;
|
|
985
|
-
});
|
|
986
|
-
return behaviorOk && gatesOk ? "passed" : "failed";
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
function cellKey(event) {
|
|
990
|
-
return [event.suite, event.scenario, event.host, event.arm || "_"].join("::");
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
function paneId(event) {
|
|
994
|
-
return "live-" + cellKey(event).replace(/[^a-z0-9]+/gi, "-");
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
function compareWrapId(event) {
|
|
998
|
-
return ("live-wrap-" + event.suite + "-" + event.scenario + "-" + event.host).replace(/[^a-z0-9]+/gi, "-");
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
function selectCompareTab(wrap, armId) {
|
|
1002
|
-
wrap.querySelectorAll(".compare-tab").forEach(function (tab) {
|
|
1003
|
-
var on = tab.getAttribute("data-arm") === armId;
|
|
1004
|
-
tab.setAttribute("aria-selected", on ? "true" : "false");
|
|
1005
|
-
});
|
|
1006
|
-
wrap.querySelectorAll(".live-cell").forEach(function (panel) {
|
|
1007
|
-
panel.hidden = panel.getAttribute("data-arm") !== armId;
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
function scenarioCard(event) {
|
|
1012
|
-
return document.querySelector('[data-scenario-card="' + event.suite + "::" + event.scenario + '"]');
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
function selectHostTab(card, host) {
|
|
1016
|
-
if (!card || !host) {
|
|
1017
|
-
return;
|
|
1018
|
-
}
|
|
1019
|
-
card.querySelectorAll(".host-tab").forEach(function (tab) {
|
|
1020
|
-
var on = tab.getAttribute("data-host") === host;
|
|
1021
|
-
tab.setAttribute("aria-selected", on ? "true" : "false");
|
|
1022
|
-
});
|
|
1023
|
-
card.querySelectorAll("[data-host-panel]").forEach(function (panel) {
|
|
1024
|
-
panel.hidden = panel.getAttribute("data-host-panel") !== host;
|
|
1025
|
-
});
|
|
1026
|
-
var run = card.querySelector("button.run-cell");
|
|
1027
|
-
if (run) {
|
|
1028
|
-
run.setAttribute("data-host", host);
|
|
1029
|
-
var tab = card.querySelector('.host-tab[data-host="' + host + '"]');
|
|
1030
|
-
if (tab) {
|
|
1031
|
-
run.disabled = tab.disabled;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
function revealLive(event) {
|
|
1037
|
-
var card = scenarioCard(event);
|
|
1038
|
-
if (!card) {
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
var live = card.querySelector(".scenario-live");
|
|
1042
|
-
if (live) {
|
|
1043
|
-
live.hidden = false;
|
|
1044
|
-
}
|
|
1045
|
-
if (card.dataset.userPickedHost === "1") {
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
|
-
if (card.dataset.followHost && card.dataset.followHost !== event.host) {
|
|
1049
|
-
return;
|
|
1050
|
-
}
|
|
1051
|
-
card.dataset.followHost = event.host;
|
|
1052
|
-
selectHostTab(card, event.host);
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
function hostPanel(event) {
|
|
1056
|
-
var card = scenarioCard(event);
|
|
1057
|
-
return card ? card.querySelector('[data-host-panel="' + event.host + '"]') : null;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
function markArmTab(wrap, armId, status) {
|
|
1061
|
-
var tab = wrap.querySelector('.compare-tab[data-arm="' + armId + '"]');
|
|
1062
|
-
if (tab) {
|
|
1063
|
-
tab.setAttribute("data-status", status);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
function createLiveArticle(event, title) {
|
|
1068
|
-
var article = document.createElement("article");
|
|
1069
|
-
article.className = "live-cell";
|
|
1070
|
-
article.id = paneId(event);
|
|
1071
|
-
article.dataset.chatKey = cellKey(event);
|
|
1072
|
-
article.innerHTML = "<h3></h3><ol class=\\"live-status\\"></ol><div class=\\"chat\\"></div>";
|
|
1073
|
-
article.querySelector("h3").textContent = title;
|
|
1074
|
-
return article;
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
var openChats = {};
|
|
1078
|
-
|
|
1079
|
-
function resetArticle(article) {
|
|
1080
|
-
if (!article) {
|
|
1081
|
-
return;
|
|
1082
|
-
}
|
|
1083
|
-
var status = article.querySelector(".live-status");
|
|
1084
|
-
if (status) {
|
|
1085
|
-
status.replaceChildren();
|
|
1086
|
-
}
|
|
1087
|
-
var chat = article.querySelector(".chat");
|
|
1088
|
-
if (chat) {
|
|
1089
|
-
chat.replaceChildren();
|
|
1090
|
-
}
|
|
1091
|
-
var result = article.querySelector(".cell-result");
|
|
1092
|
-
if (result) {
|
|
1093
|
-
result.remove();
|
|
1094
|
-
}
|
|
1095
|
-
var contextPanel = article.querySelector(".context-panel");
|
|
1096
|
-
if (contextPanel) {
|
|
1097
|
-
contextPanel.remove();
|
|
1098
|
-
}
|
|
1099
|
-
var panel = article.closest("[data-host-panel]");
|
|
1100
|
-
var empty = panel ? panel.querySelector(".host-empty") : null;
|
|
1101
|
-
if (empty) {
|
|
1102
|
-
empty.hidden = false;
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
function chatIsOpen(event) {
|
|
1107
|
-
return !!openChats[cellKey(event)];
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
function startChat(event) {
|
|
1111
|
-
openChats[cellKey(event)] = true;
|
|
1112
|
-
delete assistantBodies[cellKey(event)];
|
|
1113
|
-
delete finishedMetrics[cellKey(event)];
|
|
1114
|
-
ensurePane(event);
|
|
1115
|
-
resetArticle(document.getElementById(paneId(event)));
|
|
1116
|
-
var panel = hostPanel(event);
|
|
1117
|
-
var empty = panel ? panel.querySelector(".host-empty") : null;
|
|
1118
|
-
if (empty) {
|
|
1119
|
-
empty.hidden = true;
|
|
1120
|
-
}
|
|
183
|
+
.viewer-workspace { grid-template-columns: 1fr; }
|
|
184
|
+
.test-navigator { overflow: visible; }
|
|
185
|
+
.test-picker-label { display: block; margin-top: .7rem; }
|
|
186
|
+
.test-picker { display: block; width: 100%; margin-top: .3rem; }
|
|
187
|
+
.test-tree { display: none; }
|
|
188
|
+
.criterion-groups { flex-direction: column; }
|
|
189
|
+
.scenario-focus-header { grid-template-columns: 1fr; gap: .55rem; }
|
|
190
|
+
.focus-verdict { justify-self: start; }
|
|
1121
191
|
}
|
|
1122
|
-
|
|
1123
|
-
function markRunningIdle() {
|
|
1124
|
-
document.querySelectorAll("[data-cell]").forEach(function (node) {
|
|
1125
|
-
var status = node.querySelector(".cell-status");
|
|
1126
|
-
if (!status) {
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
var label = status.textContent;
|
|
1130
|
-
if (label !== "running" && label !== "starting") {
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
|
-
status.textContent = "idle";
|
|
1134
|
-
status.className = "cell-status muted";
|
|
1135
|
-
if (node.classList.contains("host-tab")) {
|
|
1136
|
-
node.setAttribute("data-status", "idle");
|
|
1137
|
-
}
|
|
1138
|
-
});
|
|
1139
|
-
document.querySelectorAll(".compare-tab[data-status='running']").forEach(function (tab) {
|
|
1140
|
-
tab.setAttribute("data-status", "idle");
|
|
1141
|
-
});
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
function closeAllChats() {
|
|
1145
|
-
Object.keys(openChats).forEach(function (key) {
|
|
1146
|
-
resetArticle(document.getElementById("live-" + key.replace(/[^a-z0-9]+/gi, "-")));
|
|
1147
|
-
});
|
|
1148
|
-
openChats = {};
|
|
1149
|
-
assistantBodies = {};
|
|
1150
|
-
hideAllRunning();
|
|
1151
|
-
markRunningIdle();
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
function resetViewerCards() {
|
|
1155
|
-
closeAllChats();
|
|
1156
|
-
document.querySelectorAll("[data-host-panel]").forEach(function (panel) {
|
|
1157
|
-
panel.innerHTML = '<p class="host-empty">No run yet.</p>';
|
|
1158
|
-
});
|
|
1159
|
-
document.querySelectorAll(".scenario-live").forEach(function (live) { live.hidden = true; });
|
|
1160
|
-
document.querySelectorAll(".cell-status").forEach(function (status) {
|
|
1161
|
-
if (status.textContent !== "skip") {
|
|
1162
|
-
status.textContent = "idle";
|
|
1163
|
-
status.className = "cell-status muted";
|
|
1164
|
-
}
|
|
1165
|
-
});
|
|
1166
|
-
catalog.suites.forEach(function (suite) {
|
|
1167
|
-
suite.scenarios.forEach(function (scenario) { syncScenarioStatus(suite.name, scenario.name); });
|
|
1168
|
-
});
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
function runLabel(run) {
|
|
1172
|
-
var scope = run.request && (run.request.scenario || run.request.suite) || "all tests";
|
|
1173
|
-
var time = new Date(run.startedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
1174
|
-
return time + " · " + scope + " · " + run.status;
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
function renderRunHistory(runs) {
|
|
1178
|
-
bootstrap.runs = runs;
|
|
1179
|
-
var select = document.getElementById("run-history");
|
|
1180
|
-
select.replaceChildren();
|
|
1181
|
-
var idle = document.createElement("option");
|
|
1182
|
-
idle.value = "";
|
|
1183
|
-
idle.textContent = "Tests · no run selected";
|
|
1184
|
-
select.appendChild(idle);
|
|
1185
|
-
runs.forEach(function (run) {
|
|
1186
|
-
var option = document.createElement("option");
|
|
1187
|
-
option.value = run.id;
|
|
1188
|
-
option.textContent = runLabel(run);
|
|
1189
|
-
select.appendChild(option);
|
|
1190
|
-
});
|
|
1191
|
-
select.value = selectedRunId || "";
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
function completedRunBanner(run) {
|
|
1195
|
-
if (run.status === "cancelled") return "Run cancelled.";
|
|
1196
|
-
if (run.status !== "completed") return "Run " + run.id + " · " + run.status + ".";
|
|
1197
|
-
var counts = (run.reports || []).reduce(function (total, report) {
|
|
1198
|
-
total.passed += report.passed || 0;
|
|
1199
|
-
total.failed += report.failed || 0;
|
|
1200
|
-
total.skipped += report.skipped || 0;
|
|
1201
|
-
return total;
|
|
1202
|
-
}, { passed: 0, failed: 0, skipped: 0 });
|
|
1203
|
-
return "Run finished. " + counts.passed + " passed. " + counts.failed + " failed. " + counts.skipped + " skipped.";
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
async function refreshRunHistory() {
|
|
1207
|
-
if (!bootstrap.capabilities.canRun) return;
|
|
1208
|
-
var response = await fetch("/api/runs");
|
|
1209
|
-
if (!response.ok) return;
|
|
1210
|
-
renderRunHistory(await response.json());
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
async function selectRun(id) {
|
|
1214
|
-
selectedRunId = id || null;
|
|
1215
|
-
viewedRunHosts = null;
|
|
1216
|
-
if (source) { source.close(); source = null; }
|
|
1217
|
-
runId = null;
|
|
1218
|
-
cancelRequested = false;
|
|
1219
|
-
resetViewerCards();
|
|
1220
|
-
if (!selectedRunId) {
|
|
1221
|
-
setRunBanner("Choose tests to run.");
|
|
1222
|
-
setRunControls("idle");
|
|
1223
|
-
renderRunHistory(bootstrap.runs || []);
|
|
1224
|
-
return;
|
|
1225
|
-
}
|
|
1226
|
-
var response = await fetch("/api/runs/" + selectedRunId);
|
|
1227
|
-
if (!response.ok) return;
|
|
1228
|
-
var payload = await response.json();
|
|
1229
|
-
viewedRunHosts = Array.isArray(payload.run.request && payload.run.request.hosts) ? payload.run.request.hosts : null;
|
|
1230
|
-
syncScenarioHostChoices();
|
|
1231
|
-
(payload.fragments || []).forEach(function (fragment) {
|
|
1232
|
-
var card = document.querySelector('[data-scenario-card="' + fragment.suite + "::" + fragment.scenario + '"]');
|
|
1233
|
-
var panel = card && card.querySelector('[data-host-panel="' + fragment.host + '"]');
|
|
1234
|
-
if (!card || !panel) return;
|
|
1235
|
-
panel.innerHTML = fragment.html;
|
|
1236
|
-
installCopyErrorButtons(panel);
|
|
1237
|
-
var live = card.querySelector(".scenario-live");
|
|
1238
|
-
if (live) live.hidden = false;
|
|
1239
|
-
markCell(fragment, fragment.skipped ? "skipped" : fragment.passed ? "passed" : "failed", fragment.skipped ? "status-skipped" : fragment.passed ? "status-passed" : "status-failed");
|
|
1240
|
-
});
|
|
1241
|
-
var preferred = (payload.fragments || []).find(function (fragment) { return !fragment.skipped && !fragment.passed; }) || (payload.fragments || [])[0];
|
|
1242
|
-
if (preferred) selectScenario(preferred.suite + "::" + preferred.scenario, false);
|
|
1243
|
-
if (payload.run.status === "cancelled") {
|
|
1244
|
-
markRunTargets(payload.run.request || {}, "cancelled", "status-skipped");
|
|
1245
|
-
}
|
|
1246
|
-
setRunBanner(completedRunBanner(payload.run));
|
|
1247
|
-
renderRunHistory((bootstrap.runs || []).map(function (item) { return item.id === payload.run.id ? payload.run : item; }));
|
|
1248
|
-
if (payload.run.status === "running" || payload.run.status === "cancelling") {
|
|
1249
|
-
runId = payload.run.id;
|
|
1250
|
-
setRunControls(payload.run.status === "cancelling" ? "cancelling" : "running", payload.run.request);
|
|
1251
|
-
source = new EventSource("/api/runs/" + runId + "/events");
|
|
1252
|
-
source.onmessage = function (message) { handleEvent(JSON.parse(message.data)); };
|
|
1253
|
-
} else {
|
|
1254
|
-
setRunControls("idle");
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
function requestRunStop() {
|
|
1259
|
-
if (!runId || cancelRequested) return;
|
|
1260
|
-
cancelRequested = true;
|
|
1261
|
-
setRunBanner("Stopping the run.");
|
|
1262
|
-
setRunControls("cancelling", activeRunRequest);
|
|
1263
|
-
cancelOpenChats();
|
|
1264
|
-
fetch("/api/runs/" + runId + "/cancel", { method: "POST" }).then(function (response) {
|
|
1265
|
-
if (!response.ok && cancelRequested) {
|
|
1266
|
-
setRunBanner("Stop failed.");
|
|
1267
|
-
cancelRequested = false;
|
|
1268
|
-
setRunControls("running", activeRunRequest);
|
|
1269
|
-
}
|
|
1270
|
-
}).catch(function () {
|
|
1271
|
-
if (cancelRequested) {
|
|
1272
|
-
setRunBanner("Stop failed.");
|
|
1273
|
-
cancelRequested = false;
|
|
1274
|
-
setRunControls("running", activeRunRequest);
|
|
1275
|
-
}
|
|
1276
|
-
});
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
function showCancelledNote(article, text) {
|
|
1280
|
-
if (!article) {
|
|
1281
|
-
return;
|
|
1282
|
-
}
|
|
1283
|
-
var existing = article.querySelector(".cell-result");
|
|
1284
|
-
if (existing) {
|
|
1285
|
-
existing.remove();
|
|
1286
|
-
}
|
|
1287
|
-
var box = document.createElement("div");
|
|
1288
|
-
box.className = "cell-result status-skipped";
|
|
1289
|
-
var verdict = document.createElement("p");
|
|
1290
|
-
verdict.className = "cell-result-verdict";
|
|
1291
|
-
verdict.textContent = text;
|
|
1292
|
-
box.appendChild(verdict);
|
|
1293
|
-
article.appendChild(box);
|
|
1294
|
-
var panel = article.closest("[data-host-panel]");
|
|
1295
|
-
var empty = panel ? panel.querySelector(".host-empty") : null;
|
|
1296
|
-
if (empty) {
|
|
1297
|
-
empty.hidden = true;
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
function cancelOpenChats() {
|
|
1302
|
-
document.querySelectorAll(".cell-status").forEach(function (status) {
|
|
1303
|
-
if (status.textContent !== "running" && status.textContent !== "starting") {
|
|
1304
|
-
return;
|
|
1305
|
-
}
|
|
1306
|
-
status.textContent = "cancelling";
|
|
1307
|
-
status.className = "cell-status status-skipped";
|
|
1308
|
-
var tab = status.closest(".host-tab");
|
|
1309
|
-
if (tab) {
|
|
1310
|
-
tab.setAttribute("data-status", "cancelling");
|
|
1311
|
-
}
|
|
1312
|
-
});
|
|
1313
|
-
document.querySelectorAll(".compare-tab[data-status='running']").forEach(function (tab) {
|
|
1314
|
-
tab.setAttribute("data-status", "cancelling");
|
|
1315
|
-
});
|
|
1316
|
-
hideAllRunning();
|
|
1317
|
-
document.querySelectorAll("[data-cell]").forEach(function (node) {
|
|
1318
|
-
var status = node.querySelector(".cell-status");
|
|
1319
|
-
if (!status || status.textContent !== "cancelling") {
|
|
1320
|
-
return;
|
|
1321
|
-
}
|
|
1322
|
-
var card = node.closest("[data-scenario-card]");
|
|
1323
|
-
if (!card) {
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
var live = card.querySelector(".scenario-live");
|
|
1327
|
-
if (live) {
|
|
1328
|
-
live.hidden = false;
|
|
1329
|
-
}
|
|
1330
|
-
var host = node.getAttribute("data-host") || (node.getAttribute("data-cell") || "").split("::")[2];
|
|
1331
|
-
var panel = card.querySelector('[data-host-panel="' + host + '"]');
|
|
1332
|
-
if (!panel) {
|
|
1333
|
-
return;
|
|
1334
|
-
}
|
|
1335
|
-
var articles = panel.querySelectorAll("article.live-cell");
|
|
1336
|
-
if (articles.length === 0) {
|
|
1337
|
-
showCancelledNote(panel, "Cancelled. Stopping the host agent.");
|
|
1338
|
-
return;
|
|
1339
|
-
}
|
|
1340
|
-
articles.forEach(function (article) {
|
|
1341
|
-
var key = article.dataset.chatKey;
|
|
1342
|
-
resetArticle(article);
|
|
1343
|
-
showCancelledNote(article, "Cancelled. Stopping the host agent.");
|
|
1344
|
-
if (key) {
|
|
1345
|
-
delete openChats[key];
|
|
1346
|
-
delete assistantBodies[key];
|
|
1347
|
-
}
|
|
1348
|
-
});
|
|
1349
|
-
});
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
function markCancelledCells() {
|
|
1353
|
-
document.querySelectorAll(".cell-status").forEach(function (status) {
|
|
1354
|
-
if (status.textContent === "cancelling") {
|
|
1355
|
-
status.textContent = "cancelled";
|
|
1356
|
-
status.className = "cell-status status-skipped";
|
|
1357
|
-
var tab = status.closest(".host-tab");
|
|
1358
|
-
if (tab) {
|
|
1359
|
-
tab.setAttribute("data-status", "cancelled");
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
});
|
|
1363
|
-
document.querySelectorAll(".compare-tab[data-status='cancelling']").forEach(function (tab) {
|
|
1364
|
-
tab.setAttribute("data-status", "cancelled");
|
|
1365
|
-
});
|
|
1366
|
-
document.querySelectorAll(".cell-result-verdict").forEach(function (node) {
|
|
1367
|
-
if (node.textContent === "Cancelled. Stopping the host agent.") {
|
|
1368
|
-
node.textContent = "Cancelled.";
|
|
1369
|
-
}
|
|
1370
|
-
});
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
function sealAllChats() {
|
|
1374
|
-
openChats = {};
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
function addArmTab(wrap, list, article, armId, label, selected) {
|
|
1378
|
-
article.setAttribute("role", "tabpanel");
|
|
1379
|
-
article.setAttribute("data-arm", armId);
|
|
1380
|
-
article.hidden = !selected;
|
|
1381
|
-
var tab = document.createElement("button");
|
|
1382
|
-
tab.type = "button";
|
|
1383
|
-
tab.className = "compare-tab";
|
|
1384
|
-
tab.setAttribute("role", "tab");
|
|
1385
|
-
tab.setAttribute("data-arm", armId);
|
|
1386
|
-
tab.setAttribute("aria-controls", article.id);
|
|
1387
|
-
tab.setAttribute("aria-selected", selected ? "true" : "false");
|
|
1388
|
-
tab.textContent = label;
|
|
1389
|
-
tab.addEventListener("click", function () {
|
|
1390
|
-
wrap.dataset.userPicked = "1";
|
|
1391
|
-
selectCompareTab(wrap, armId);
|
|
1392
|
-
});
|
|
1393
|
-
list.appendChild(tab);
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
function ensureCompareWrap(event, scenario, slot) {
|
|
1397
|
-
var wrapId = compareWrapId(event);
|
|
1398
|
-
var wrap = document.getElementById(wrapId);
|
|
1399
|
-
if (!wrap) {
|
|
1400
|
-
wrap = document.createElement("div");
|
|
1401
|
-
wrap.className = "compare-layout compare-tabs";
|
|
1402
|
-
wrap.id = wrapId;
|
|
1403
|
-
var list = document.createElement("div");
|
|
1404
|
-
list.className = "compare-tablist";
|
|
1405
|
-
list.setAttribute("role", "tablist");
|
|
1406
|
-
list.setAttribute("aria-label", "Comparison arms");
|
|
1407
|
-
wrap.appendChild(list);
|
|
1408
|
-
var arms = document.createElement("div");
|
|
1409
|
-
arms.className = "compare-arms";
|
|
1410
|
-
if (scenario && scenario.compare) {
|
|
1411
|
-
arms.setAttribute("data-arm-count", String(scenario.compare.length));
|
|
1412
|
-
}
|
|
1413
|
-
wrap.appendChild(arms);
|
|
1414
|
-
if (scenario && scenario.compare) {
|
|
1415
|
-
scenario.compare.forEach(function (arm, index) {
|
|
1416
|
-
var article = createLiveArticle({
|
|
1417
|
-
suite: event.suite,
|
|
1418
|
-
scenario: event.scenario,
|
|
1419
|
-
host: event.host,
|
|
1420
|
-
arm: arm.id,
|
|
1421
|
-
}, arm.label);
|
|
1422
|
-
addArmTab(wrap, list, article, arm.id, arm.label, index === 0);
|
|
1423
|
-
arms.appendChild(article);
|
|
1424
|
-
});
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
if (!wrap.parentNode) {
|
|
1428
|
-
slot.appendChild(wrap);
|
|
1429
|
-
}
|
|
1430
|
-
return wrap;
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
function ensurePane(event) {
|
|
1434
|
-
var existing = document.getElementById(paneId(event));
|
|
1435
|
-
if (existing) {
|
|
1436
|
-
revealLive(event);
|
|
1437
|
-
return existing.querySelector(".chat");
|
|
1438
|
-
}
|
|
1439
|
-
var slot = hostPanel(event);
|
|
1440
|
-
if (!slot) {
|
|
1441
|
-
return null;
|
|
1442
|
-
}
|
|
1443
|
-
revealLive(event);
|
|
1444
|
-
var empty = slot.querySelector(".host-empty");
|
|
1445
|
-
if (empty) {
|
|
1446
|
-
empty.hidden = true;
|
|
1447
|
-
}
|
|
1448
|
-
var leftover = slot.querySelector(":scope > .cell-result");
|
|
1449
|
-
if (leftover) {
|
|
1450
|
-
leftover.remove();
|
|
1451
|
-
}
|
|
1452
|
-
var scenario = findScenario(event.suite, event.scenario);
|
|
1453
|
-
if (event.arm) {
|
|
1454
|
-
var wrap = ensureCompareWrap(event, scenario, slot);
|
|
1455
|
-
existing = document.getElementById(paneId(event));
|
|
1456
|
-
if (existing) {
|
|
1457
|
-
if (wrap.dataset.userPicked !== "1") {
|
|
1458
|
-
selectCompareTab(wrap, event.arm);
|
|
1459
|
-
}
|
|
1460
|
-
return existing.querySelector(".chat");
|
|
1461
|
-
}
|
|
1462
|
-
return null;
|
|
1463
|
-
}
|
|
1464
|
-
var article = createLiveArticle(event, event.host);
|
|
1465
|
-
slot.appendChild(article);
|
|
1466
|
-
return article.querySelector(".chat");
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
function appendStatus(event, text) {
|
|
1470
|
-
var article = document.getElementById(paneId(event));
|
|
1471
|
-
var list = article ? article.querySelector(".live-status") : null;
|
|
1472
|
-
if (!list) {
|
|
1473
|
-
ensurePane(event);
|
|
1474
|
-
article = document.getElementById(paneId(event));
|
|
1475
|
-
list = article ? article.querySelector(".live-status") : null;
|
|
1476
|
-
}
|
|
1477
|
-
if (!list) {
|
|
1478
|
-
return;
|
|
1479
|
-
}
|
|
1480
|
-
var item = document.createElement("li");
|
|
1481
|
-
item.textContent = text;
|
|
1482
|
-
list.appendChild(item);
|
|
1483
|
-
list.scrollTop = list.scrollHeight;
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
function appendBubble(chat, role, text) {
|
|
1487
|
-
var row = document.createElement("div");
|
|
1488
|
-
row.className = "chat-row " + (role === "user" ? "side-right" : "side-left");
|
|
1489
|
-
var bubble = document.createElement("div");
|
|
1490
|
-
bubble.className = "bubble role-" + role;
|
|
1491
|
-
var label = document.createElement("div");
|
|
1492
|
-
label.className = "bubble-label";
|
|
1493
|
-
label.textContent = role;
|
|
1494
|
-
var body = document.createElement("div");
|
|
1495
|
-
body.className = "bubble-text";
|
|
1496
|
-
body.textContent = text;
|
|
1497
|
-
bubble.appendChild(label);
|
|
1498
|
-
bubble.appendChild(body);
|
|
1499
|
-
row.appendChild(bubble);
|
|
1500
|
-
chat.appendChild(row);
|
|
1501
|
-
return body;
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
function runningRow(article) {
|
|
1505
|
-
return article.querySelector(".chat-running-row");
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
function showRunning(event) {
|
|
1509
|
-
var article = document.getElementById(paneId(event));
|
|
1510
|
-
if (!article) {
|
|
1511
|
-
ensurePane(event);
|
|
1512
|
-
article = document.getElementById(paneId(event));
|
|
1513
|
-
}
|
|
1514
|
-
if (!article) {
|
|
1515
|
-
return;
|
|
1516
|
-
}
|
|
1517
|
-
var chat = article.querySelector(".chat");
|
|
1518
|
-
if (!chat) {
|
|
1519
|
-
return;
|
|
1520
|
-
}
|
|
1521
|
-
var row = runningRow(article);
|
|
1522
|
-
if (!row) {
|
|
1523
|
-
row = document.createElement("div");
|
|
1524
|
-
row.className = "chat-row side-left chat-running-row";
|
|
1525
|
-
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>';
|
|
1526
|
-
chat.appendChild(row);
|
|
1527
|
-
}
|
|
1528
|
-
row.hidden = false;
|
|
1529
|
-
chat.appendChild(row);
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
function hideRunning(event) {
|
|
1533
|
-
var article = document.getElementById(paneId(event));
|
|
1534
|
-
var row = article ? runningRow(article) : null;
|
|
1535
|
-
if (row) {
|
|
1536
|
-
row.remove();
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
function hideAllRunning() {
|
|
1541
|
-
document.querySelectorAll(".chat-running-row").forEach(function (row) {
|
|
1542
|
-
row.remove();
|
|
1543
|
-
});
|
|
1544
|
-
document.querySelectorAll(".bubble.is-streaming").forEach(function (bubble) {
|
|
1545
|
-
bubble.classList.remove("is-streaming");
|
|
1546
|
-
});
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
function setStreaming(event, on) {
|
|
1550
|
-
var body = assistantBodies[cellKey(event)];
|
|
1551
|
-
var bubble = body ? body.parentNode : null;
|
|
1552
|
-
if (bubble) {
|
|
1553
|
-
if (on) {
|
|
1554
|
-
bubble.classList.add("is-streaming");
|
|
1555
|
-
} else {
|
|
1556
|
-
bubble.classList.remove("is-streaming");
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
function appendContextPanel(article, mode, files, hostInput) {
|
|
1562
|
-
if (!article) {
|
|
1563
|
-
return;
|
|
1564
|
-
}
|
|
1565
|
-
var existing = article.querySelector(".context-panel");
|
|
1566
|
-
if (existing) {
|
|
1567
|
-
existing.remove();
|
|
1568
|
-
}
|
|
1569
|
-
var list = files || [];
|
|
1570
|
-
var panel = document.createElement("details");
|
|
1571
|
-
panel.className = "context-panel";
|
|
1572
|
-
panel.open = true;
|
|
1573
|
-
panel.setAttribute("data-context-mode", mode || "harness-preamble");
|
|
1574
|
-
panel.setAttribute("data-file-count", String(list.length));
|
|
1575
|
-
var summary = document.createElement("summary");
|
|
1576
|
-
summary.className = "context-panel-summary";
|
|
1577
|
-
var title = document.createElement("span");
|
|
1578
|
-
title.className = "context-panel-title";
|
|
1579
|
-
title.textContent = "Context delivery";
|
|
1580
|
-
var count = document.createElement("span");
|
|
1581
|
-
count.className = "context-panel-count";
|
|
1582
|
-
count.textContent = mode === "host-native"
|
|
1583
|
-
? "Host-native"
|
|
1584
|
-
: "Harness preamble · " + (list.length === 1 ? "1 file" : list.length + " files");
|
|
1585
|
-
summary.appendChild(title);
|
|
1586
|
-
summary.appendChild(count);
|
|
1587
|
-
panel.appendChild(summary);
|
|
1588
|
-
|
|
1589
|
-
var flow = document.createElement("div");
|
|
1590
|
-
flow.className = "context-flow";
|
|
1591
|
-
flow.setAttribute("aria-label", "Scenario prompt delivery path");
|
|
1592
|
-
["Scenario", "→", mode === "host-native" ? "Host discovery" : "Harness preamble", "→", "Host"].forEach(function (label, index) {
|
|
1593
|
-
var item = document.createElement("span");
|
|
1594
|
-
item.className = index === 1 || index === 3
|
|
1595
|
-
? "context-flow-arrow"
|
|
1596
|
-
: "context-flow-node" + (index === 2 ? " context-flow-mode" : "");
|
|
1597
|
-
item.textContent = label;
|
|
1598
|
-
flow.appendChild(item);
|
|
1599
|
-
});
|
|
1600
|
-
panel.appendChild(flow);
|
|
1601
|
-
|
|
1602
|
-
if (typeof hostInput === "string") {
|
|
1603
|
-
var input = document.createElement("details");
|
|
1604
|
-
input.className = "context-host-input";
|
|
1605
|
-
input.open = true;
|
|
1606
|
-
var inputSummary = document.createElement("summary");
|
|
1607
|
-
inputSummary.textContent = "Exact submitted user input";
|
|
1608
|
-
var inputBody = document.createElement("pre");
|
|
1609
|
-
inputBody.className = "context-body";
|
|
1610
|
-
inputBody.textContent = hostInput;
|
|
1611
|
-
input.appendChild(inputSummary);
|
|
1612
|
-
input.appendChild(inputBody);
|
|
1613
|
-
panel.appendChild(input);
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
if (mode === "host-native") {
|
|
1617
|
-
var nativeLede = document.createElement("p");
|
|
1618
|
-
nativeLede.className = "context-panel-lede";
|
|
1619
|
-
nativeLede.textContent = "No harness preamble. Workspace files remain on disk for the host to discover.";
|
|
1620
|
-
panel.appendChild(nativeLede);
|
|
1621
|
-
var nativeUnknown = document.createElement("p");
|
|
1622
|
-
nativeUnknown.className = "context-empty";
|
|
1623
|
-
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.";
|
|
1624
|
-
panel.appendChild(nativeUnknown);
|
|
1625
|
-
} else if (list.length === 0) {
|
|
1626
|
-
var empty = document.createElement("p");
|
|
1627
|
-
empty.className = "context-empty";
|
|
1628
|
-
empty.textContent = "No preamble files. The agent received the prompt only.";
|
|
1629
|
-
panel.appendChild(empty);
|
|
1630
|
-
} else {
|
|
1631
|
-
var lede = document.createElement("p");
|
|
1632
|
-
lede.className = "context-panel-lede";
|
|
1633
|
-
lede.textContent = "These files were in the host preamble for this run.";
|
|
1634
|
-
panel.appendChild(lede);
|
|
1635
|
-
var ol = document.createElement("ol");
|
|
1636
|
-
ol.className = "context-files";
|
|
1637
|
-
list.forEach(function (file) {
|
|
1638
|
-
var item = document.createElement("li");
|
|
1639
|
-
var details = document.createElement("details");
|
|
1640
|
-
details.className = "context-file";
|
|
1641
|
-
details.open = list.length === 1;
|
|
1642
|
-
details.setAttribute("data-reason", file.reason || "profile");
|
|
1643
|
-
details.setAttribute("data-path", file.path || "context");
|
|
1644
|
-
var fileSummary = document.createElement("summary");
|
|
1645
|
-
var path = document.createElement("span");
|
|
1646
|
-
path.className = "context-path";
|
|
1647
|
-
path.textContent = file.path || "context";
|
|
1648
|
-
var why = document.createElement("span");
|
|
1649
|
-
why.className = "context-why";
|
|
1650
|
-
why.textContent = file.why || "The runner loaded this file into the host preamble.";
|
|
1651
|
-
fileSummary.appendChild(path);
|
|
1652
|
-
fileSummary.appendChild(why);
|
|
1653
|
-
var body = document.createElement("pre");
|
|
1654
|
-
body.className = "context-body";
|
|
1655
|
-
body.textContent = file.text || "";
|
|
1656
|
-
details.appendChild(fileSummary);
|
|
1657
|
-
details.appendChild(body);
|
|
1658
|
-
item.appendChild(details);
|
|
1659
|
-
ol.appendChild(item);
|
|
1660
|
-
});
|
|
1661
|
-
panel.appendChild(ol);
|
|
1662
|
-
}
|
|
1663
|
-
var chat = article.querySelector(".chat");
|
|
1664
|
-
if (chat) {
|
|
1665
|
-
article.insertBefore(panel, chat);
|
|
1666
|
-
} else {
|
|
1667
|
-
article.appendChild(panel);
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
function appendContextFiles(chat, mode, files, hostInput) {
|
|
1672
|
-
appendContextPanel(chat ? chat.closest("article.live-cell") : null, mode, files, hostInput);
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
function displayToolPath(path) {
|
|
1676
|
-
var stripped = String(path).replace(/^file:\\/\\//, "").replace(/\\/+$/, "");
|
|
1677
|
-
var workspace = stripped.match(/\\/agent-harness-seal-[^/]+(?:\\/(.*))?$/);
|
|
1678
|
-
if (workspace) {
|
|
1679
|
-
return workspace[1] ? workspace[1] : ".";
|
|
1680
|
-
}
|
|
1681
|
-
var sealed = stripped.match(/\\/(?:\\.agents|\\.claude|\\.codex|AGENTS\\.md|CLAUDE\\.md)(?:\\/|$)/);
|
|
1682
|
-
if (sealed && sealed.index !== undefined) {
|
|
1683
|
-
return stripped.slice(sealed.index + 1);
|
|
1684
|
-
}
|
|
1685
|
-
var parts = stripped.split("/").filter(Boolean);
|
|
1686
|
-
if (parts.length > 3) {
|
|
1687
|
-
return ".../" + parts.slice(-3).join("/");
|
|
1688
|
-
}
|
|
1689
|
-
return stripped;
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
function formatArgValue(key, value) {
|
|
1693
|
-
var pathKeys = { path: 1, file_path: 1, filePath: 1, target_file: 1, uri: 1, cwd: 1 };
|
|
1694
|
-
if (typeof value === "string" && (pathKeys[key] || value.indexOf("/") === 0 || value.indexOf("file://") === 0)) {
|
|
1695
|
-
return { text: displayToolPath(value), title: value };
|
|
1696
|
-
}
|
|
1697
|
-
var raw = typeof value === "string" ? value : JSON.stringify(value);
|
|
1698
|
-
var oneLine = raw.replace(/\\n/g, " ↵ ");
|
|
1699
|
-
return { text: oneLine.length > 140 ? oneLine.slice(0, 140) + "…" : oneLine };
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
function appendTool(chat, name, args) {
|
|
1703
|
-
var row = document.createElement("div");
|
|
1704
|
-
row.className = "chat-row side-left";
|
|
1705
|
-
var card = document.createElement("div");
|
|
1706
|
-
card.className = "tool-card";
|
|
1707
|
-
var head = document.createElement("div");
|
|
1708
|
-
head.className = "tool-card-head";
|
|
1709
|
-
var toolName = document.createElement("span");
|
|
1710
|
-
toolName.className = "tool-name";
|
|
1711
|
-
toolName.textContent = name;
|
|
1712
|
-
head.appendChild(toolName);
|
|
1713
|
-
card.appendChild(head);
|
|
1714
|
-
if (args) {
|
|
1715
|
-
var box = document.createElement("div");
|
|
1716
|
-
box.className = "tool-args";
|
|
1717
|
-
Object.keys(args).forEach(function (key) {
|
|
1718
|
-
var line = document.createElement("div");
|
|
1719
|
-
line.className = "tool-arg";
|
|
1720
|
-
var keyEl = document.createElement("span");
|
|
1721
|
-
keyEl.className = "tool-arg-key";
|
|
1722
|
-
keyEl.textContent = key;
|
|
1723
|
-
var value = document.createElement("code");
|
|
1724
|
-
var formatted = formatArgValue(key, args[key]);
|
|
1725
|
-
value.textContent = formatted.text;
|
|
1726
|
-
if (formatted.title && formatted.title !== formatted.text) {
|
|
1727
|
-
value.title = formatted.title;
|
|
1728
|
-
}
|
|
1729
|
-
line.appendChild(keyEl);
|
|
1730
|
-
line.appendChild(value);
|
|
1731
|
-
box.appendChild(line);
|
|
1732
|
-
});
|
|
1733
|
-
card.appendChild(box);
|
|
1734
|
-
}
|
|
1735
|
-
row.appendChild(card);
|
|
1736
|
-
chat.appendChild(row);
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
var assistantBodies = {};
|
|
1740
|
-
var finishedMetrics = {};
|
|
1741
|
-
|
|
1742
|
-
function findScenario(suiteName, scenarioName) {
|
|
1743
|
-
for (var i = 0; i < catalog.suites.length; i++) {
|
|
1744
|
-
if (catalog.suites[i].name !== suiteName) {
|
|
1745
|
-
continue;
|
|
1746
|
-
}
|
|
1747
|
-
var scenarios = catalog.suites[i].scenarios;
|
|
1748
|
-
for (var j = 0; j < scenarios.length; j++) {
|
|
1749
|
-
if (scenarios[j].name === scenarioName) {
|
|
1750
|
-
return scenarios[j];
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
return null;
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
function joinLabels(labels) {
|
|
1758
|
-
if (labels.length <= 1) {
|
|
1759
|
-
return labels[0] || "";
|
|
1760
|
-
}
|
|
1761
|
-
if (labels.length === 2) {
|
|
1762
|
-
return labels[0] + " and " + labels[1];
|
|
1763
|
-
}
|
|
1764
|
-
return labels.slice(0, -1).join(", ") + ", and " + labels[labels.length - 1];
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
function summarizeMetric(arms, id, label) {
|
|
1768
|
-
var values = arms.map(function (arm) { return arm[id]; });
|
|
1769
|
-
var numeric = values.filter(function (value) { return typeof value === "number"; });
|
|
1770
|
-
if (numeric.length < 2) {
|
|
1771
|
-
return { line: label + ": n/a." };
|
|
1772
|
-
}
|
|
1773
|
-
var lowest = Math.min.apply(null, numeric);
|
|
1774
|
-
var winners = arms.filter(function (arm) { return arm[id] === lowest; });
|
|
1775
|
-
var shown = values.map(function (value) { return typeof value === "number" ? String(value) : "n/a"; }).join(" vs ");
|
|
1776
|
-
if (winners.length > 1) {
|
|
1777
|
-
return {
|
|
1778
|
-
line: arms.length === 2
|
|
1779
|
-
? label + ": tie (" + shown + ")."
|
|
1780
|
-
: label + ": lowest is a tie between " + joinLabels(winners.map(function (arm) { return arm.label; })) + " (" + shown + ").",
|
|
1781
|
-
};
|
|
1782
|
-
}
|
|
1783
|
-
var winner = winners[0];
|
|
1784
|
-
if (!winner) {
|
|
1785
|
-
return { line: label + ": n/a." };
|
|
1786
|
-
}
|
|
1787
|
-
if (arms.length === 2) {
|
|
1788
|
-
return { line: label + ": " + winner.label + " wins (" + shown + ")." };
|
|
1789
|
-
}
|
|
1790
|
-
return { line: label + ": lowest is " + winner.label + " (" + shown + ")." };
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
function summarizeGates(arms, gates) {
|
|
1794
|
-
var byId = {};
|
|
1795
|
-
arms.forEach(function (arm) { byId[arm.id] = arm; });
|
|
1796
|
-
return (gates || []).map(function (gate) {
|
|
1797
|
-
var field = gate.metric === "durationMs" ? "durationMs" : gate.metric;
|
|
1798
|
-
var winner = byId[gate.winner];
|
|
1799
|
-
var loser = byId[gate.loser];
|
|
1800
|
-
if (!winner || !loser || ["turns", "tokens", "tools", "durationMs"].indexOf(field) === -1) {
|
|
1801
|
-
return { passed: true, line: gate.metric + ": scored in the final result." };
|
|
1802
|
-
}
|
|
1803
|
-
var winnerValue = winner[field];
|
|
1804
|
-
var loserValue = loser[field];
|
|
1805
|
-
if (typeof winnerValue !== "number" || typeof loserValue !== "number") {
|
|
1806
|
-
return { passed: false, line: gate.metric + ": one arm did not report a value. Fail." };
|
|
1807
|
-
}
|
|
1808
|
-
var passed = winnerValue < loserValue;
|
|
1809
|
-
return { passed: passed, line: winner.label + " must use fewer " + gate.metric + " than " + loser.label + " (" + winnerValue + " vs " + loserValue + "). " + (passed ? "Pass." : "Fail.") };
|
|
1810
|
-
});
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
function renderCompareWinners(event) {
|
|
1814
|
-
var scenario = findScenario(event.suite, event.scenario);
|
|
1815
|
-
if (!scenario || !scenario.compare || !scenario.compare.length) {
|
|
1816
|
-
return;
|
|
1817
|
-
}
|
|
1818
|
-
var wrap = document.getElementById(compareWrapId(event));
|
|
1819
|
-
if (!wrap) {
|
|
1820
|
-
return;
|
|
1821
|
-
}
|
|
1822
|
-
var rows = [];
|
|
1823
|
-
for (var i = 0; i < scenario.compare.length; i++) {
|
|
1824
|
-
var arm = scenario.compare[i];
|
|
1825
|
-
var metrics = finishedMetrics[[event.suite, event.scenario, event.host, arm.id].join("::")];
|
|
1826
|
-
if (!metrics) {
|
|
1827
|
-
return;
|
|
1828
|
-
}
|
|
1829
|
-
rows.push({
|
|
1830
|
-
id: arm.id,
|
|
1831
|
-
label: arm.label,
|
|
1832
|
-
turns: metrics.turns,
|
|
1833
|
-
tokens: metrics.tokens,
|
|
1834
|
-
tools: metrics.tools,
|
|
1835
|
-
durationMs: metrics.durationMs,
|
|
1836
|
-
passed: metrics.passed,
|
|
1837
|
-
});
|
|
1838
|
-
}
|
|
1839
|
-
var metricLines = [
|
|
1840
|
-
summarizeMetric(rows, "turns", "Turns"),
|
|
1841
|
-
summarizeMetric(rows, "tokens", "Tokens"),
|
|
1842
|
-
summarizeMetric(rows, "tools", "Tools"),
|
|
1843
|
-
];
|
|
1844
|
-
var gateLines = summarizeGates(rows, scenario.gates);
|
|
1845
|
-
var existing = wrap.querySelector(".compare-winners");
|
|
1846
|
-
if (existing) {
|
|
1847
|
-
existing.remove();
|
|
1848
|
-
}
|
|
1849
|
-
var section = document.createElement("section");
|
|
1850
|
-
section.className = "compare-winners";
|
|
1851
|
-
var heading = document.createElement("h3");
|
|
1852
|
-
heading.textContent = "Winners";
|
|
1853
|
-
var list = document.createElement("ul");
|
|
1854
|
-
metricLines.forEach(function (metric) {
|
|
1855
|
-
var item = document.createElement("li");
|
|
1856
|
-
item.textContent = metric.line;
|
|
1857
|
-
list.appendChild(item);
|
|
1858
|
-
});
|
|
1859
|
-
gateLines.forEach(function (gate) {
|
|
1860
|
-
var item = document.createElement("li");
|
|
1861
|
-
item.className = gate.passed ? "compare-winner-pass" : "compare-winner-fail";
|
|
1862
|
-
item.textContent = gate.line;
|
|
1863
|
-
list.appendChild(item);
|
|
1864
|
-
});
|
|
1865
|
-
section.appendChild(heading);
|
|
1866
|
-
section.appendChild(list);
|
|
1867
|
-
wrap.appendChild(section);
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
function markCell(event, label, statusClass) {
|
|
1871
|
-
var selector = '[data-cell="' + event.suite + "::" + event.scenario + "::" + event.host + '"]';
|
|
1872
|
-
document.querySelectorAll(selector).forEach(function (node) {
|
|
1873
|
-
var status = node.querySelector(".cell-status");
|
|
1874
|
-
if (status) {
|
|
1875
|
-
status.textContent = label;
|
|
1876
|
-
status.className = "cell-status " + statusClass;
|
|
1877
|
-
}
|
|
1878
|
-
if (node.classList.contains("host-tab")) {
|
|
1879
|
-
node.setAttribute("data-status", label);
|
|
1880
|
-
}
|
|
1881
|
-
});
|
|
1882
|
-
syncScenarioStatus(event.suite, event.scenario);
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
function metricPart(count, one, many) {
|
|
1886
|
-
if (typeof count !== "number") {
|
|
1887
|
-
return "";
|
|
1888
|
-
}
|
|
1889
|
-
return count + " " + (count === 1 ? one : many);
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
function formatMetrics(metrics) {
|
|
1893
|
-
if (!metrics) {
|
|
1894
|
-
return "";
|
|
1895
|
-
}
|
|
1896
|
-
return [metricPart(metrics.turns, "turn", "turns"), metricPart(metrics.tokens, "token", "tokens"), metricPart(metrics.tools, "tool", "tools")]
|
|
1897
|
-
.filter(Boolean)
|
|
1898
|
-
.join(" · ");
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
function failureText(container) {
|
|
1902
|
-
var messages = Array.prototype.map.call(
|
|
1903
|
-
container.querySelectorAll(".failure-message, .cell-result-failures li"),
|
|
1904
|
-
function (node) { return node.textContent.trim(); }
|
|
1905
|
-
).filter(Boolean);
|
|
1906
|
-
var evidence = Array.prototype.map.call(
|
|
1907
|
-
container.querySelectorAll(".failure-evidence"),
|
|
1908
|
-
function (node) { return node.textContent.trim(); }
|
|
1909
|
-
).filter(Boolean);
|
|
1910
|
-
return messages.concat(evidence).join("\\n\\n");
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
function copyText(text) {
|
|
1914
|
-
if (navigator.clipboard && window.isSecureContext) {
|
|
1915
|
-
return navigator.clipboard.writeText(text);
|
|
1916
|
-
}
|
|
1917
|
-
return new Promise(function (resolve, reject) {
|
|
1918
|
-
var input = document.createElement("textarea");
|
|
1919
|
-
input.value = text;
|
|
1920
|
-
input.setAttribute("readonly", "");
|
|
1921
|
-
input.style.position = "fixed";
|
|
1922
|
-
input.style.opacity = "0";
|
|
1923
|
-
document.body.appendChild(input);
|
|
1924
|
-
input.select();
|
|
1925
|
-
var copied = document.execCommand("copy");
|
|
1926
|
-
input.remove();
|
|
1927
|
-
if (copied) resolve();
|
|
1928
|
-
else reject(new Error("Clipboard copy failed"));
|
|
1929
|
-
});
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
function copyErrorButton(container) {
|
|
1933
|
-
var button = document.createElement("button");
|
|
1934
|
-
button.type = "button";
|
|
1935
|
-
button.className = "copy-error";
|
|
1936
|
-
button.textContent = "Copy error";
|
|
1937
|
-
button.addEventListener("click", function () {
|
|
1938
|
-
var text = failureText(container);
|
|
1939
|
-
copyText(text).then(function () {
|
|
1940
|
-
button.textContent = "Copied";
|
|
1941
|
-
window.setTimeout(function () { button.textContent = "Copy error"; }, 1500);
|
|
1942
|
-
}).catch(function () {
|
|
1943
|
-
button.textContent = "Copy failed";
|
|
1944
|
-
window.setTimeout(function () { button.textContent = "Copy error"; }, 1500);
|
|
1945
|
-
});
|
|
1946
|
-
});
|
|
1947
|
-
return button;
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
function installCopyErrorButtons(root) {
|
|
1951
|
-
root.querySelectorAll(".cell-result.status-failed").forEach(function (box) {
|
|
1952
|
-
if (box.querySelector(".copy-error") || !box.querySelector(".cell-result-failures")) return;
|
|
1953
|
-
var verdict = box.querySelector(".cell-result-verdict");
|
|
1954
|
-
if (!verdict) return;
|
|
1955
|
-
var head = document.createElement("div");
|
|
1956
|
-
head.className = "cell-result-head";
|
|
1957
|
-
box.insertBefore(head, verdict);
|
|
1958
|
-
head.appendChild(verdict);
|
|
1959
|
-
head.appendChild(copyErrorButton(box));
|
|
1960
|
-
});
|
|
1961
|
-
root.querySelectorAll("details.scenario.status-failed .diagnostics > section").forEach(function (section) {
|
|
1962
|
-
if (section.querySelector(".copy-error") || !section.querySelector(".failures")) return;
|
|
1963
|
-
var heading = section.querySelector("h3");
|
|
1964
|
-
if (!heading || heading.textContent.trim() !== "What went wrong") return;
|
|
1965
|
-
var head = document.createElement("div");
|
|
1966
|
-
head.className = "failure-section-head";
|
|
1967
|
-
section.insertBefore(head, heading);
|
|
1968
|
-
head.appendChild(heading);
|
|
1969
|
-
head.appendChild(copyErrorButton(section));
|
|
1970
|
-
});
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
function clearCellResult(event) {
|
|
1974
|
-
var article = document.getElementById(paneId(event));
|
|
1975
|
-
var existing = article ? article.querySelector(".cell-result") : null;
|
|
1976
|
-
if (existing) {
|
|
1977
|
-
existing.remove();
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
function renderCellResult(event) {
|
|
1982
|
-
ensurePane(event);
|
|
1983
|
-
var article = document.getElementById(paneId(event));
|
|
1984
|
-
if (!article) {
|
|
1985
|
-
return;
|
|
1986
|
-
}
|
|
1987
|
-
var existing = article.querySelector(".cell-result");
|
|
1988
|
-
if (existing) {
|
|
1989
|
-
existing.remove();
|
|
1990
|
-
}
|
|
1991
|
-
var skipped = !!event.skipped;
|
|
1992
|
-
var passed = !!event.passed && !skipped;
|
|
1993
|
-
var box = document.createElement("div");
|
|
1994
|
-
box.className = "cell-result " + (skipped ? "status-skipped" : passed ? "status-passed" : "status-failed");
|
|
1995
|
-
var verdict = document.createElement("p");
|
|
1996
|
-
verdict.className = "cell-result-verdict";
|
|
1997
|
-
verdict.textContent = skipped ? "Skipped." : passed ? "Passed." : "Failed.";
|
|
1998
|
-
box.appendChild(verdict);
|
|
1999
|
-
var metrics = formatMetrics(event.metrics);
|
|
2000
|
-
if (metrics) {
|
|
2001
|
-
var line = document.createElement("p");
|
|
2002
|
-
line.className = "cell-result-metrics";
|
|
2003
|
-
line.textContent = metrics;
|
|
2004
|
-
box.appendChild(line);
|
|
2005
|
-
}
|
|
2006
|
-
if (!skipped && !passed) {
|
|
2007
|
-
var list = document.createElement("ul");
|
|
2008
|
-
list.className = "cell-result-failures";
|
|
2009
|
-
var failures = event.failures || [];
|
|
2010
|
-
if (failures.length === 0) {
|
|
2011
|
-
var item = document.createElement("li");
|
|
2012
|
-
item.textContent = "The run failed. The host did not report a reason.";
|
|
2013
|
-
list.appendChild(item);
|
|
2014
|
-
} else {
|
|
2015
|
-
failures.forEach(function (failure) {
|
|
2016
|
-
var item = document.createElement("li");
|
|
2017
|
-
item.textContent = failure.message || failure.matcher || "The run failed.";
|
|
2018
|
-
list.appendChild(item);
|
|
2019
|
-
});
|
|
2020
|
-
}
|
|
2021
|
-
box.appendChild(list);
|
|
2022
|
-
}
|
|
2023
|
-
article.appendChild(box);
|
|
2024
|
-
installCopyErrorButtons(article);
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
function appendJudgeVerdicts(event) {
|
|
2028
|
-
ensurePane(event);
|
|
2029
|
-
var article = document.getElementById(paneId(event));
|
|
2030
|
-
if (!article) {
|
|
2031
|
-
return;
|
|
2032
|
-
}
|
|
2033
|
-
var box = article.querySelector(".cell-result");
|
|
2034
|
-
if (!box) {
|
|
2035
|
-
box = document.createElement("div");
|
|
2036
|
-
box.className = "cell-result";
|
|
2037
|
-
article.appendChild(box);
|
|
2038
|
-
}
|
|
2039
|
-
var existing = box.querySelector(".cell-result-judges");
|
|
2040
|
-
if (existing) {
|
|
2041
|
-
existing.remove();
|
|
2042
|
-
}
|
|
2043
|
-
var list = document.createElement("ul");
|
|
2044
|
-
list.className = "cell-result-judges";
|
|
2045
|
-
(event.verdicts || []).forEach(function (verdict) {
|
|
2046
|
-
var item = document.createElement("li");
|
|
2047
|
-
item.className = verdict.pass ? "judge-pass" : "judge-fail";
|
|
2048
|
-
var text = (verdict.pass ? "Pass. " : "Fail. ") + (verdict.question || "");
|
|
2049
|
-
if (verdict.rationale) {
|
|
2050
|
-
text += " " + verdict.rationale;
|
|
2051
|
-
}
|
|
2052
|
-
item.textContent = text;
|
|
2053
|
-
list.appendChild(item);
|
|
2054
|
-
});
|
|
2055
|
-
box.appendChild(list);
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
function handleEvent(event) {
|
|
2059
|
-
if (event.type === "run_started") {
|
|
2060
|
-
if (cancelRequested) {
|
|
2061
|
-
return;
|
|
2062
|
-
}
|
|
2063
|
-
setRunBanner("Run " + event.runId + " started.");
|
|
2064
|
-
setRunControls("running", activeRunRequest);
|
|
2065
|
-
return;
|
|
2066
|
-
}
|
|
2067
|
-
if (event.type === "run_finished") {
|
|
2068
|
-
var wasCancelled = cancelRequested;
|
|
2069
|
-
finishProgress(event);
|
|
2070
|
-
hideAllRunning();
|
|
2071
|
-
sealAllChats();
|
|
2072
|
-
if (cancelRequested) {
|
|
2073
|
-
markCancelledCells();
|
|
2074
|
-
setRunBanner("Run cancelled.");
|
|
2075
|
-
} else {
|
|
2076
|
-
setRunBanner(
|
|
2077
|
-
"Run finished. " + progress.passed + " passed. " + progress.failed + " failed. " + progress.skipped + " skipped."
|
|
2078
|
-
);
|
|
2079
|
-
}
|
|
2080
|
-
setRunControls("idle");
|
|
2081
|
-
cancelRequested = false;
|
|
2082
|
-
runId = null;
|
|
2083
|
-
if (source) {
|
|
2084
|
-
source.close();
|
|
2085
|
-
source = null;
|
|
2086
|
-
}
|
|
2087
|
-
void refreshRunHistory().then(function () {
|
|
2088
|
-
if (!wasCancelled) return selectRun(event.runId);
|
|
2089
|
-
});
|
|
2090
|
-
return;
|
|
2091
|
-
}
|
|
2092
|
-
if (cancelRequested) {
|
|
2093
|
-
return;
|
|
2094
|
-
}
|
|
2095
|
-
if (event.type === "error") {
|
|
2096
|
-
setRunBanner(event.message);
|
|
2097
|
-
if (event.suite && chatIsOpen(event)) {
|
|
2098
|
-
appendStatus(event, event.message);
|
|
2099
|
-
var errorChat = ensurePane(event);
|
|
2100
|
-
if (errorChat) {
|
|
2101
|
-
appendBubble(errorChat, "system", event.message);
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
return;
|
|
2105
|
-
}
|
|
2106
|
-
if (event.type === "scenario_result") {
|
|
2107
|
-
fetch("/api/runs/" + (runId || selectedRunId)).then(function (response) {
|
|
2108
|
-
return response.ok ? response.json() : null;
|
|
2109
|
-
}).then(function (payload) {
|
|
2110
|
-
if (!payload || selectedRunId !== payload.run.id) return;
|
|
2111
|
-
var fragment = (payload.fragments || []).find(function (item) {
|
|
2112
|
-
return item.suite === event.suite && item.scenario === event.scenario && item.host === event.host;
|
|
2113
|
-
});
|
|
2114
|
-
if (!fragment) return;
|
|
2115
|
-
var card = scenarioCard(event);
|
|
2116
|
-
var panel = card && card.querySelector('[data-host-panel="' + event.host + '"]');
|
|
2117
|
-
if (panel) panel.innerHTML = fragment.html;
|
|
2118
|
-
if (panel) installCopyErrorButtons(panel);
|
|
2119
|
-
}).catch(function () {});
|
|
2120
|
-
return;
|
|
2121
|
-
}
|
|
2122
|
-
if (event.type === "cell_started") {
|
|
2123
|
-
markCell(event, "running", "status-skipped");
|
|
2124
|
-
startChat(event);
|
|
2125
|
-
if (event.arm) {
|
|
2126
|
-
var startedWrap = document.getElementById(compareWrapId(event));
|
|
2127
|
-
if (startedWrap) {
|
|
2128
|
-
markArmTab(startedWrap, event.arm, "running");
|
|
2129
|
-
if (startedWrap.classList.contains("compare-tabs") && startedWrap.dataset.userPicked !== "1") {
|
|
2130
|
-
selectCompareTab(startedWrap, event.arm);
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
}
|
|
2134
|
-
return;
|
|
2135
|
-
}
|
|
2136
|
-
if (event.type === "status") {
|
|
2137
|
-
if (!chatIsOpen(event)) {
|
|
2138
|
-
return;
|
|
2139
|
-
}
|
|
2140
|
-
appendStatus(event, event.text);
|
|
2141
|
-
return;
|
|
2142
|
-
}
|
|
2143
|
-
if (event.type === "context") {
|
|
2144
|
-
if (!chatIsOpen(event)) {
|
|
2145
|
-
return;
|
|
2146
|
-
}
|
|
2147
|
-
var contextChat = ensurePane(event);
|
|
2148
|
-
if (contextChat) {
|
|
2149
|
-
appendContextFiles(contextChat, event.mode, event.files, event.hostInput);
|
|
2150
|
-
}
|
|
2151
|
-
return;
|
|
2152
|
-
}
|
|
2153
|
-
if (event.type === "prompt") {
|
|
2154
|
-
if (!chatIsOpen(event)) {
|
|
2155
|
-
return;
|
|
2156
|
-
}
|
|
2157
|
-
var promptChat = ensurePane(event);
|
|
2158
|
-
if (promptChat) {
|
|
2159
|
-
appendBubble(promptChat, "user", event.text);
|
|
2160
|
-
showRunning(event);
|
|
2161
|
-
}
|
|
2162
|
-
return;
|
|
2163
|
-
}
|
|
2164
|
-
if (event.type === "text") {
|
|
2165
|
-
if (!chatIsOpen(event)) {
|
|
2166
|
-
return;
|
|
2167
|
-
}
|
|
2168
|
-
var key = cellKey(event);
|
|
2169
|
-
var chat = ensurePane(event);
|
|
2170
|
-
if (!chat) {
|
|
2171
|
-
return;
|
|
2172
|
-
}
|
|
2173
|
-
hideRunning(event);
|
|
2174
|
-
if (!assistantBodies[key]) {
|
|
2175
|
-
assistantBodies[key] = appendBubble(chat, "assistant", event.text);
|
|
2176
|
-
} else {
|
|
2177
|
-
assistantBodies[key].textContent = event.text;
|
|
2178
|
-
}
|
|
2179
|
-
setStreaming(event, true);
|
|
2180
|
-
return;
|
|
2181
|
-
}
|
|
2182
|
-
if (event.type === "tool") {
|
|
2183
|
-
if (!chatIsOpen(event)) {
|
|
2184
|
-
return;
|
|
2185
|
-
}
|
|
2186
|
-
var toolChat = ensurePane(event);
|
|
2187
|
-
if (!toolChat) {
|
|
2188
|
-
return;
|
|
2189
|
-
}
|
|
2190
|
-
setStreaming(event, false);
|
|
2191
|
-
assistantBodies[cellKey(event)] = null;
|
|
2192
|
-
hideRunning(event);
|
|
2193
|
-
appendTool(toolChat, event.name, event.args);
|
|
2194
|
-
showRunning(event);
|
|
2195
|
-
return;
|
|
2196
|
-
}
|
|
2197
|
-
if (event.type === "cell_finished") {
|
|
2198
|
-
recordProgress(event);
|
|
2199
|
-
if (!chatIsOpen(event)) {
|
|
2200
|
-
return;
|
|
2201
|
-
}
|
|
2202
|
-
hideRunning(event);
|
|
2203
|
-
setStreaming(event, false);
|
|
2204
|
-
markCell(event, event.skipped ? "skipped" : event.passed ? "passed" : "failed", event.skipped ? "status-skipped" : event.passed ? "status-passed" : "status-failed");
|
|
2205
|
-
finishedMetrics[cellKey(event)] = Object.assign({}, event.metrics || {}, {
|
|
2206
|
-
durationMs: event.durationMs,
|
|
2207
|
-
passed: event.passed,
|
|
2208
|
-
});
|
|
2209
|
-
renderCellResult(event);
|
|
2210
|
-
if (event.arm) {
|
|
2211
|
-
var finishedWrap = document.getElementById(compareWrapId(event));
|
|
2212
|
-
if (finishedWrap) {
|
|
2213
|
-
markArmTab(
|
|
2214
|
-
finishedWrap,
|
|
2215
|
-
event.arm,
|
|
2216
|
-
event.skipped ? "skipped" : event.passed ? "passed" : "failed"
|
|
2217
|
-
);
|
|
2218
|
-
}
|
|
2219
|
-
renderCompareWinners(event);
|
|
2220
|
-
}
|
|
2221
|
-
return;
|
|
2222
|
-
}
|
|
2223
|
-
if (event.type === "judge") {
|
|
2224
|
-
if (!chatIsOpen(event)) {
|
|
2225
|
-
return;
|
|
2226
|
-
}
|
|
2227
|
-
appendJudgeVerdicts(event);
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
function markRunTargets(body, label, statusClass) {
|
|
2232
|
-
var hosts = Array.isArray(body.hosts) ? body.hosts : catalog.defaultSelectedHosts;
|
|
2233
|
-
catalog.suites.forEach(function (suite) {
|
|
2234
|
-
if (body.suite && suite.name !== body.suite) {
|
|
2235
|
-
return;
|
|
2236
|
-
}
|
|
2237
|
-
suite.scenarios.forEach(function (scenario) {
|
|
2238
|
-
if (body.scenario && scenario.name !== body.scenario) {
|
|
2239
|
-
return;
|
|
2240
|
-
}
|
|
2241
|
-
if (scenario.skip) {
|
|
2242
|
-
return;
|
|
2243
|
-
}
|
|
2244
|
-
hosts.forEach(function (host) {
|
|
2245
|
-
if (scenario.host && scenario.host !== host) {
|
|
2246
|
-
return;
|
|
2247
|
-
}
|
|
2248
|
-
markCell({ suite: suite.name, scenario: scenario.name, host: host }, label, statusClass);
|
|
2249
|
-
});
|
|
2250
|
-
});
|
|
2251
|
-
});
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
async function startRun(body) {
|
|
2255
|
-
if (!bootstrap.capabilities.canRun) return;
|
|
2256
|
-
if (Array.isArray(body.hosts) && body.hosts.length === 0) {
|
|
2257
|
-
setRunBanner("Select at least one host.");
|
|
2258
|
-
return;
|
|
2259
|
-
}
|
|
2260
|
-
if (runId) {
|
|
2261
|
-
setRunBanner(
|
|
2262
|
-
cancelRequested
|
|
2263
|
-
? "Cancelling the run. Wait for it to stop."
|
|
2264
|
-
: "A run is already in progress. Cancel it first."
|
|
2265
|
-
);
|
|
2266
|
-
return;
|
|
2267
|
-
}
|
|
2268
|
-
resetViewerCards();
|
|
2269
|
-
activeRunRequest = body;
|
|
2270
|
-
viewedRunHosts = Array.isArray(body.hosts) ? body.hosts : null;
|
|
2271
|
-
syncScenarioHostChoices();
|
|
2272
|
-
setRunControls("running", body);
|
|
2273
|
-
resetProgress(body);
|
|
2274
|
-
markRunTargets(body, "starting", "status-skipped");
|
|
2275
|
-
var firstCard = document.querySelector(
|
|
2276
|
-
body.suite && body.scenario
|
|
2277
|
-
? '[data-scenario-card="' + body.suite + "::" + body.scenario + '"]'
|
|
2278
|
-
: body.suite
|
|
2279
|
-
? '[data-suite="' + body.suite + '"] [data-scenario-card]'
|
|
2280
|
-
: "[data-scenario-card]"
|
|
2281
|
-
);
|
|
2282
|
-
document.querySelectorAll("[data-scenario-card]").forEach(function (card) {
|
|
2283
|
-
if (body.suite && card.getAttribute("data-scenario-card").indexOf(body.suite + "::") !== 0) {
|
|
2284
|
-
return;
|
|
2285
|
-
}
|
|
2286
|
-
if (body.scenario && card.getAttribute("data-scenario-card") !== body.suite + "::" + body.scenario) {
|
|
2287
|
-
return;
|
|
2288
|
-
}
|
|
2289
|
-
delete card.dataset.followHost;
|
|
2290
|
-
});
|
|
2291
|
-
if (firstCard) {
|
|
2292
|
-
selectScenario(firstCard.getAttribute("data-scenario-card"), false);
|
|
2293
|
-
var live = firstCard.querySelector(".scenario-live");
|
|
2294
|
-
if (live) {
|
|
2295
|
-
live.hidden = false;
|
|
2296
|
-
}
|
|
2297
|
-
if (body.hosts && body.hosts.length === 1 && firstCard.dataset.userPickedHost !== "1") {
|
|
2298
|
-
firstCard.dataset.followHost = body.hosts[0];
|
|
2299
|
-
selectHostTab(firstCard, body.hosts[0]);
|
|
2300
|
-
}
|
|
2301
|
-
if (live) {
|
|
2302
|
-
live.scrollIntoView({ block: "nearest" });
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
try {
|
|
2306
|
-
var response = await fetch("/api/runs", {
|
|
2307
|
-
method: "POST",
|
|
2308
|
-
headers: { "content-type": "application/json" },
|
|
2309
|
-
body: JSON.stringify(body),
|
|
2310
|
-
});
|
|
2311
|
-
var payload = await response.json();
|
|
2312
|
-
if (!response.ok) {
|
|
2313
|
-
setRunBanner(payload.error || "Run failed to start.");
|
|
2314
|
-
progress = { total: 0, completed: 0, passed: 0, failed: 0, skipped: 0, seen: {} };
|
|
2315
|
-
renderProgress();
|
|
2316
|
-
markRunTargets(body, "idle", "muted");
|
|
2317
|
-
setRunControls("idle");
|
|
2318
|
-
return;
|
|
2319
|
-
}
|
|
2320
|
-
runId = payload.runId;
|
|
2321
|
-
selectedRunId = runId;
|
|
2322
|
-
await refreshRunHistory();
|
|
2323
|
-
cancelRequested = false;
|
|
2324
|
-
setRunBanner("Run " + runId + " started.");
|
|
2325
|
-
setRunControls("running", body);
|
|
2326
|
-
if (source) {
|
|
2327
|
-
source.close();
|
|
2328
|
-
}
|
|
2329
|
-
source = new EventSource("/api/runs/" + runId + "/events");
|
|
2330
|
-
source.onmessage = function (message) {
|
|
2331
|
-
handleEvent(JSON.parse(message.data));
|
|
2332
|
-
};
|
|
2333
|
-
source.onerror = function () {
|
|
2334
|
-
source.close();
|
|
2335
|
-
if (!runId) {
|
|
2336
|
-
return;
|
|
2337
|
-
}
|
|
2338
|
-
setRunBanner("Live event stream dropped.");
|
|
2339
|
-
};
|
|
2340
|
-
} catch (error) {
|
|
2341
|
-
setRunBanner(error instanceof Error ? error.message : String(error));
|
|
2342
|
-
markRunTargets(body, "idle", "muted");
|
|
2343
|
-
setRunControls("idle");
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
|
|
2347
|
-
document.getElementById("run-selection").addEventListener("click", function (event) {
|
|
2348
|
-
if (event.currentTarget.dataset.runAction === "stop") return requestRunStop();
|
|
2349
|
-
startRun({ hosts: selectedHosts(), parallelHosts: parallelHosts() });
|
|
2350
|
-
});
|
|
2351
|
-
document.querySelectorAll("[data-host-toggle]").forEach(function (toggle) {
|
|
2352
|
-
toggle.addEventListener("change", updateHostSelectionControls);
|
|
2353
|
-
});
|
|
2354
|
-
document.getElementById("run-history").addEventListener("change", function (event) {
|
|
2355
|
-
void selectRun(event.target.value);
|
|
2356
|
-
});
|
|
2357
|
-
document.body.addEventListener("click", function (event) {
|
|
2358
|
-
var button = event.target.closest("button");
|
|
2359
|
-
if (!button) {
|
|
2360
|
-
return;
|
|
2361
|
-
}
|
|
2362
|
-
if (button.classList.contains("test-nav-item")) {
|
|
2363
|
-
selectScenario(button.getAttribute("data-select-scenario"), false);
|
|
2364
|
-
return;
|
|
2365
|
-
}
|
|
2366
|
-
if (button.classList.contains("host-tab")) {
|
|
2367
|
-
var card = button.closest("[data-scenario-card]");
|
|
2368
|
-
if (card) {
|
|
2369
|
-
card.dataset.userPickedHost = "1";
|
|
2370
|
-
selectHostTab(card, button.getAttribute("data-host"));
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
if (button.classList.contains("run-cell")) {
|
|
2374
|
-
if (button.dataset.runAction === "stop") return requestRunStop();
|
|
2375
|
-
startRun({
|
|
2376
|
-
suite: button.getAttribute("data-suite"),
|
|
2377
|
-
scenario: button.getAttribute("data-scenario"),
|
|
2378
|
-
hosts: [button.getAttribute("data-host")],
|
|
2379
|
-
parallelHosts: false,
|
|
2380
|
-
});
|
|
2381
|
-
}
|
|
2382
|
-
if (button.classList.contains("run-suite")) {
|
|
2383
|
-
if (button.dataset.runAction === "stop") return requestRunStop();
|
|
2384
|
-
startRun({
|
|
2385
|
-
suite: button.getAttribute("data-suite"),
|
|
2386
|
-
hosts: selectedHosts(),
|
|
2387
|
-
parallelHosts: parallelHosts(),
|
|
2388
|
-
});
|
|
2389
|
-
}
|
|
2390
|
-
});
|
|
2391
|
-
document.getElementById("test-picker")?.addEventListener("change", function (event) {
|
|
2392
|
-
selectScenario(event.target.value, false);
|
|
2393
|
-
});
|
|
2394
|
-
|
|
2395
|
-
renderRunHistory(bootstrap.runs || []);
|
|
2396
|
-
installCopyErrorButtons(document);
|
|
2397
|
-
selectScenario(selectedScenarioKey, false);
|
|
2398
|
-
updateHostSelectionControls();
|
|
2399
|
-
if (selectedRunId && bootstrap.capabilities.canRun) void selectRun(selectedRunId);
|
|
2400
|
-
if (bootstrap.capabilities.canRun) setInterval(function () { void refreshRunHistory(); }, 1000);
|
|
2401
|
-
})();
|
|
2402
|
-
`;
|
|
2403
|
-
}
|
|
2404
|
-
function staticNavigationScript() {
|
|
2405
|
-
return `
|
|
2406
|
-
(function () {
|
|
2407
|
-
function selectScenario(key) {
|
|
2408
|
-
document.querySelectorAll("[data-scenario-card]").forEach(function (card) {
|
|
2409
|
-
var selected = card.getAttribute("data-scenario-card") === key;
|
|
2410
|
-
card.dataset.selected = selected ? "true" : "false";
|
|
2411
|
-
card.hidden = !selected;
|
|
2412
|
-
});
|
|
2413
|
-
document.querySelectorAll("[data-select-scenario]").forEach(function (item) {
|
|
2414
|
-
item.setAttribute("aria-current", item.getAttribute("data-select-scenario") === key ? "true" : "false");
|
|
2415
|
-
});
|
|
2416
|
-
var picker = document.getElementById("test-picker");
|
|
2417
|
-
if (picker) picker.value = key;
|
|
2418
|
-
}
|
|
2419
|
-
document.body.addEventListener("click", function (event) {
|
|
2420
|
-
var item = event.target.closest("[data-select-scenario]");
|
|
2421
|
-
if (item) selectScenario(item.getAttribute("data-select-scenario"));
|
|
2422
|
-
});
|
|
2423
|
-
document.getElementById("test-picker")?.addEventListener("change", function (event) {
|
|
2424
|
-
selectScenario(event.target.value);
|
|
2425
|
-
});
|
|
2426
|
-
})();
|
|
2427
192
|
`;
|
|
2428
193
|
}
|
|
194
|
+
/** Render the self-contained shell used by both the live viewer and saved reports. */
|
|
2429
195
|
export function renderViewerPage(input, options = {}) {
|
|
2430
196
|
const bootstrap = "catalog" in input ? input : { catalog: input, runs: [], capabilities: { canRun: true } };
|
|
2431
|
-
const catalog = bootstrap.catalog;
|
|
2432
|
-
const canRun = bootstrap.capabilities.canRun;
|
|
2433
|
-
const firstScenarioKey = catalog.suites[0]?.scenarios[0]
|
|
2434
|
-
? `${catalog.suites[0].name}::${catalog.suites[0].scenarios[0].name}`
|
|
2435
|
-
: "";
|
|
2436
|
-
const hostToggles = Array.from(new Set(catalog.suites.flatMap((suite) => suite.hosts)))
|
|
2437
|
-
.map((host) => {
|
|
2438
|
-
const checked = catalog.defaultSelectedHosts.includes(host) ? " checked" : "";
|
|
2439
|
-
return `<label><input type="checkbox" data-host-toggle value="${escapeHtml(host)}"${checked}>${escapeHtml(host)}</label>`;
|
|
2440
|
-
})
|
|
2441
|
-
.join("");
|
|
2442
|
-
const suites = catalog.suites
|
|
2443
|
-
.map((suite) => renderSuiteSection(suite, options.results ?? [], canRun, catalog.defaultSelectedHosts, firstScenarioKey))
|
|
2444
|
-
.join("\n");
|
|
2445
|
-
const navigator = renderTestNavigator(catalog, firstScenarioKey, canRun);
|
|
2446
|
-
const historyOptions = [
|
|
2447
|
-
`<option value="">Tests · no run selected</option>`,
|
|
2448
|
-
...bootstrap.runs.map((run) => {
|
|
2449
|
-
const selected = run.id === bootstrap.selectedRunId ? " selected" : "";
|
|
2450
|
-
return `<option value="${escapeHtml(run.id)}"${selected}>${escapeHtml(run.id)} · ${escapeHtml(run.status)}</option>`;
|
|
2451
|
-
}),
|
|
2452
|
-
].join("");
|
|
2453
197
|
return `<!DOCTYPE html>
|
|
2454
198
|
<html lang="en">
|
|
2455
199
|
<head>
|
|
2456
200
|
<meta charset="utf-8">
|
|
2457
201
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2458
|
-
<title>agent-test ${canRun ? "viewer" : "report"}</title>
|
|
2459
|
-
<style>
|
|
2460
|
-
${options.baseCss ?? ""}
|
|
2461
|
-
${viewerCss()}
|
|
2462
|
-
</style>
|
|
202
|
+
<title>agent-test ${bootstrap.capabilities.canRun ? "viewer" : "report"}</title>
|
|
203
|
+
<style>${options.baseCss ?? ""}${viewerCss()}</style>
|
|
2463
204
|
</head>
|
|
2464
205
|
<body>
|
|
2465
|
-
<
|
|
2466
|
-
|
|
2467
|
-
<div class="viewer-identity"><p class="brand">agent-test</p><h1>${canRun ? "Test viewer" : "Run report"}</h1></div>
|
|
2468
|
-
<div class="run-context">
|
|
2469
|
-
<label class="toolbar-label" for="run-history">Viewing run</label>
|
|
2470
|
-
<select class="run-history" id="run-history" aria-label="Run history"${canRun ? "" : " disabled"}>${historyOptions}</select>
|
|
2471
|
-
</div>
|
|
2472
|
-
<p class="run-banner${canRun ? "" : " lede"}" id="run-banner" aria-live="polite">${canRun ? "Choose a test to inspect or run." : escapeHtml(options.headerLede ?? "Read-only report.")}</p>
|
|
2473
|
-
</header>
|
|
2474
|
-
<div class="viewer-command-row">
|
|
2475
|
-
<div class="toolbar-block"><p class="toolbar-label">Hosts</p><div class="host-toggles">${hostToggles}</div></div>
|
|
2476
|
-
<label class="parallel-control"><input type="checkbox" id="parallel-hosts" checked> Run hosts together</label>
|
|
2477
|
-
<button class="primary run-toggle" id="run-selection" data-start-label="Start run"${canRun ? "" : ' disabled data-fixed-disabled="1"'}>Start run</button>
|
|
2478
|
-
</div>
|
|
2479
|
-
${options.overviewHtml ?? ""}
|
|
2480
|
-
<section class="run-progress" id="run-progress" aria-live="polite" hidden>
|
|
2481
|
-
<div class="run-progress-head">
|
|
2482
|
-
<p class="run-progress-title">0 of 0 tests finished</p>
|
|
2483
|
-
</div>
|
|
2484
|
-
<div class="run-progress-track" role="progressbar" aria-label="Suite run progress" aria-valuemin="0" aria-valuenow="0" aria-valuemax="0">
|
|
2485
|
-
<span class="run-progress-fill"></span>
|
|
2486
|
-
</div>
|
|
2487
|
-
<div class="run-progress-counts">
|
|
2488
|
-
<span class="progress-passed">Passed <strong>0</strong></span>
|
|
2489
|
-
<span class="progress-failed">Failed <strong>0</strong></span>
|
|
2490
|
-
<span class="progress-skipped">Skipped <strong>0</strong></span>
|
|
2491
|
-
<span class="progress-remaining">Remaining <strong>0</strong></span>
|
|
2492
|
-
</div>
|
|
2493
|
-
</section>
|
|
2494
|
-
<div class="viewer-workspace">
|
|
2495
|
-
${navigator}
|
|
2496
|
-
<section class="test-stage" aria-label="Selected test">${suites}</section>
|
|
2497
|
-
</div>
|
|
2498
|
-
</main>
|
|
2499
|
-
<script type="application/json" id="catalog-data">${embedJson(catalog)}</script>
|
|
206
|
+
<div id="viewer-root"></div>
|
|
207
|
+
<script type="application/json" id="catalog-data">${embedJson(bootstrap.catalog)}</script>
|
|
2500
208
|
<script type="application/json" id="bootstrap-data">${embedJson(bootstrap)}</script>
|
|
2501
|
-
<script
|
|
2502
|
-
${canRun ? clientScript() : staticNavigationScript()}
|
|
2503
|
-
</script>
|
|
209
|
+
<script>${viewerClient()}</script>
|
|
2504
210
|
</body>
|
|
2505
|
-
</html
|
|
2506
|
-
`;
|
|
211
|
+
</html>`;
|
|
2507
212
|
}
|
|
2508
213
|
//# sourceMappingURL=page.js.map
|