@hackerrank/astra-cli 0.1.20 → 0.1.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hackerrank/astra-cli",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Minimal zero-dependency AI coding agent for the HackerRank AI Gateway.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/agent.js CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  buildSystemPrompt,
27
27
  INSTANCE_TEMPLATE,
28
28
  FORMAT_ERROR,
29
+ TRUNCATED_COMMAND,
29
30
  OBSERVATION_TEMPLATE,
30
31
  render,
31
32
  } from "./prompts.js";
@@ -162,6 +163,14 @@ export class Agent {
162
163
  if (this.mode === "interactive" && err.code === "NO_COMMAND") {
163
164
  return { kind: "chat", content };
164
165
  }
166
+ if (isTruncatedShellResponse(content, usage)) {
167
+ // A provider length limit can cut off a large heredoc before the
168
+ // closing fence. No command has run, so recover with smaller writes
169
+ // rather than consuming the terminal format-error budget.
170
+ this.formatErrorStreak = 0;
171
+ this.add("user", TRUNCATED_COMMAND);
172
+ return { kind: "format_error", error: "truncated shell response" };
173
+ }
165
174
  this.formatErrorStreak++;
166
175
  this.nFormatErrors++;
167
176
  if (
@@ -352,6 +361,12 @@ export function parseCommand(text, { allowMultiple = false } = {}) {
352
361
  return cmd;
353
362
  }
354
363
 
364
+ function isTruncatedShellResponse(content, usage) {
365
+ if (usage?.finish_reason !== "length") return false;
366
+ const fences = String(content || "").match(/^```(?:bash|sh|shell|zsh)?[ \t]*$/gim) || [];
367
+ return fences.length % 2 === 1;
368
+ }
369
+
355
370
  /** Returns the submission string if the sentinel was echoed, else null. */
356
371
  export function checkSubmitted(output) {
357
372
  if (output.returncode !== 0) return null;
package/src/model.js CHANGED
@@ -48,7 +48,7 @@ export class GatewayModel {
48
48
  this.maxTokens = maxTokens;
49
49
  this.baseUrl = (baseUrl || process.env.ASTRA_GATEWAY_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");
50
50
  this.apiKey = apiKey || process.env.ASTRA_GATEWAY_API_KEY || "";
51
- this.modelKwargs = modelKwargs;
51
+ this.modelKwargs = sanitizeModelKwargs(model, modelKwargs);
52
52
  this.maxRetries = maxRetries;
53
53
  this.requestTimeoutMs = requestTimeoutMs;
54
54
  this.onRetry = onRetry || (() => {});
@@ -144,6 +144,7 @@ export class GatewayModel {
144
144
  this.nCalls++;
145
145
  const content = data?.choices?.[0]?.message?.content ?? "";
146
146
  const usage = normalizeUsage(data?.usage);
147
+ usage.finish_reason = data?.choices?.[0]?.finish_reason ?? null;
147
148
  this.totalPromptTokens += usage.prompt_tokens;
148
149
  this.totalCompletionTokens += usage.completion_tokens;
149
150
  this.totalReasoningTokens += usage.reasoning_tokens || 0;
@@ -338,3 +339,12 @@ export function normalizeUsage(usage) {
338
339
  function num(v) {
339
340
  return Number.isFinite(v) ? v : 0;
340
341
  }
342
+
343
+ function sanitizeModelKwargs(model, modelKwargs) {
344
+ const kwargs = { ...(modelKwargs || {}) };
345
+ // Gemini's OpenAI-compatible chat endpoint rejects this Anthropic-style
346
+ // cache-routing field. Keep the gateway client safe even when a caller or
347
+ // persisted configuration passes provider-specific kwargs through.
348
+ if (/^gemini(?:-|$)/i.test(model)) delete kwargs.prompt_cache_key;
349
+ return kwargs;
350
+ }
package/src/prompts.js CHANGED
@@ -39,6 +39,9 @@ Autonomous mode:
39
39
  environment variables, and config files needed to complete the task.
40
40
  - In autonomous mode, you may provide multiple fenced bash blocks when they
41
41
  form one coherent sequence; Astra executes them together in order.
42
+ - Keep each command response small enough to finish completely (roughly 6,000
43
+ characters). Do not write a large source file in one heredoc: create it in
44
+ small appended chunks, then verify it.
42
45
  - When completely finished, run this and NOTHING else:
43
46
  \`echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT\`
44
47
  After that command you cannot continue.`;
@@ -78,6 +81,10 @@ Respond again with exactly ONE bash code block containing ONE command, like:
78
81
  your_command_here
79
82
  \`\`\``;
80
83
 
84
+ export const TRUNCATED_COMMAND = `Your previous response was cut off before its shell block closed, so no command was run.
85
+
86
+ Do not repeat a large file write in one response. Continue with exactly one complete bash block containing a small command (under roughly 6,000 characters); append files in chunks or make a smaller focused change.`;
87
+
81
88
  export const OBSERVATION_TEMPLATE = `{{exception}}<returncode>{{returncode}}</returncode>
82
89
  <output>
83
90
  {{output}}
package/src/report.html CHANGED
@@ -8,16 +8,15 @@
8
8
  <style>
9
9
  :root { --ink:#11131a; --muted:#69707d; --wash:#f5f6f8; --line:#e4e7eb; --lime:#a7ff63; --blue:#5285f7; --purple:#9160d8; --green:#39ae76; --orange:#f3a021; --red:#d85668; font-family:Satoshi,"Avenir Next",Arial,sans-serif; }
10
10
  * { box-sizing:border-box; } body { margin:0; background:#fff; color:var(--ink); font-size:14px; line-height:1.45; } .wrap { max-width:1240px; margin:auto; padding:28px 32px 68px; }
11
- header.top { display:flex; justify-content:space-between; gap:20px; align-items:flex-start; padding-bottom:28px; border-bottom:1px solid var(--line); } header.top h1 { font-size:32px; letter-spacing:-.045em; line-height:1; margin:8px 0 0; font-weight:700; } .eyebrow { display:inline-block; padding:5px 14px; border-radius:99px; background:var(--lime); font-size:12px; font-weight:700; } header.top .meta { color:var(--muted); font-size:12px; text-align:right; }
11
+ header.top { display:flex; justify-content:space-between; gap:20px; align-items:flex-start; padding-bottom:28px; border-bottom:1px solid var(--line); } header.top h1 { font-size:32px; letter-spacing:-.045em; line-height:1; margin:8px 0 0; font-weight:700; } .eyebrow { display:inline-block; padding:5px 14px; border-radius:99px; background:var(--lime); font-size:12px; font-weight:700; } header.top .meta { color:var(--muted); font-size:12px; text-align:left; }
12
12
  .kpis { display:grid; grid-template-columns:repeat(5,1fr); margin:24px 0 52px; } .kpi { min-height:96px; padding:8px 18px; border-left:1px solid var(--line); } .kpi:first-child { border:0; padding-left:0; } .kpi .v { font-size:28px; letter-spacing:-.045em; font-weight:700; } .kpi .l { color:var(--muted); font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; margin-top:8px; }
13
- section { margin:56px 0; } section > h2 { font-size:20px; letter-spacing:-.035em; margin:0 0 22px; } section > h2 .sub { color:var(--muted); font-size:13px; font-weight:400; letter-spacing:0; } .grid2 { display:grid; grid-template-columns:1.1fr 1fr; gap:44px; } .panel { padding:0; } .panel h3 { color:var(--muted); letter-spacing:.07em; text-transform:uppercase; font-size:12px; margin:0 0 16px; }
13
+ section { margin:56px 0; } section > h2 { font-size:20px; letter-spacing:-.035em; margin:0 0 22px; } .grid2 { display:grid; grid-template-columns:1.1fr 1fr; gap:44px; } .panel { padding:0; } .panel h3 { color:var(--muted); letter-spacing:.07em; text-transform:uppercase; font-size:12px; margin:0 0 16px; }
14
14
  table { width: 100%; border-collapse: collapse; font-size: 13px; }
15
15
  th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
16
16
  th { color: var(--muted); font-weight: 600; font-size: 11.5px; text-transform: uppercase; letter-spacing: .03em; cursor: pointer; user-select: none; }
17
17
  th:hover { color: var(--text); }
18
18
  th .arrow { opacity: .5; font-size: 10px; margin-left: 3px; }
19
19
  tbody tr:hover { background: rgba(255,255,255,0.02); }
20
- tbody tr.run-row { cursor: pointer; }
21
20
  .pill { display: inline-block; padding: 1px 8px; border-radius: 100px; font-size: 11px; font-weight: 600; }
22
21
  .pill.ok { background: rgba(62,207,142,.15); color: var(--good); }
23
22
  .pill.bad { background: rgba(239,90,111,.15); color: var(--bad); }
@@ -33,17 +32,6 @@
33
32
  .legend .sw { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 5px; vertical-align: -1px; }
34
33
  .heat-table td { text-align: center; font-variant-numeric: tabular-nums; }
35
34
  .heat-cell { display: inline-block; min-width: 44px; padding: 3px 6px; border-radius: 5px; font-size: 12px; }
36
- details.run-detail { margin: 0; }
37
- details.run-detail > summary { display: none; }
38
- .timeline { margin: 0 0 18px; background:var(--wash); padding:0 14px; overflow: hidden; }
39
- .timeline .step { padding: 10px 14px; border-bottom: 1px solid var(--border); }
40
- .timeline .step:last-child { border-bottom: none; }
41
- .timeline .step .head { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; margin-bottom: 4px; }
42
- .timeline .step .n { color: var(--accent); font-weight: 700; font-size: 12px; }
43
- .timeline .step .stats { color: var(--muted); font-size: 11.5px; }
44
- .timeline .step .thought { color: var(--muted); font-size: 12.5px; margin-bottom: 6px; }
45
- .timeline .step pre { background: var(--panel-2); border-radius: 6px; padding: 8px 10px; margin: 4px 0; font-size: 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-word; }
46
- .rc0 { color: var(--good); } .rcN { color: var(--bad); }
47
35
  .toolbar { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; align-items: center; }
48
36
  .toolbar select, .toolbar input { background: var(--wash); border:0; color:var(--ink); border-radius:4px; padding:7px 9px; font-size:12px; } .model-filter { align-items:end; } .model-filter > span { color:var(--muted); font-size:11px; } .model-menu { position:relative; } .model-menu summary { cursor:pointer; list-style:none; min-width:220px; padding:9px 32px 9px 11px; background:var(--wash); border-radius:5px; font-size:12px; position:relative; } .model-menu summary::-webkit-details-marker { display:none; } .model-menu summary::after { content:"⌄"; position:absolute; right:11px; color:var(--muted); } .model-options { position:absolute; z-index:2; top:calc(100% + 6px); left:0; width:260px; max-height:240px; overflow:auto; padding:6px; background:#fff; border:1px solid var(--line); box-shadow:0 10px 24px rgba(17,19,26,.1); } .model-option { display:flex; gap:9px; align-items:center; padding:8px; font-size:12px; cursor:pointer; } .model-option:hover { background:var(--wash); } .model-option input { accent-color:var(--blue); }
49
37
  .toolbar label { color: var(--muted); font-size: 12px; }
@@ -61,6 +49,17 @@
61
49
  .result-badge.passed { background:rgba(57,174,118,.14); color:#16734a; }
62
50
  .result-badge.failed { background:rgba(216,86,104,.14); color:#ad3042; }
63
51
  .result-badge.unscored { background:var(--wash); color:var(--muted); }
52
+ .score-chart-scroll { overflow-x:auto; padding:4px 0 12px; }
53
+ .score-chart { position:relative; height:280px; padding:18px 12px 68px 44px; display:flex; align-items:end; gap:4px; background:repeating-linear-gradient(to top, transparent 0, transparent 25%, rgba(228,231,235,.8) 25.2%, transparent 25.5%); }
54
+ .score-axis { position:absolute; left:0; color:var(--muted); font-size:10px; font-variant-numeric:tabular-nums; }
55
+ .score-axis.top { top:12px; } .score-axis.mid { top:50%; } .score-axis.zero { bottom:62px; }
56
+ .score-item { position:relative; height:100%; min-width:40px; flex:1 0 40px; display:flex; align-items:end; }
57
+ .score-bar { width:100%; min-height:2px; position:relative; border-radius:4px 4px 0 0; background:var(--blue); }
58
+ .score-bar.unscored { height:3px !important; }
59
+ .score-value { position:absolute; left:0; right:0; bottom:7px; color:#fff; text-align:center; font-size:10px; font-weight:700; font-variant-numeric:tabular-nums; }
60
+ .score-bar.unscored .score-value { color:var(--muted); bottom:7px; }
61
+ .score-label { position:absolute; top:calc(100% + 8px); left:0; width:40px; color:var(--muted); font-size:8px; line-height:1.2; overflow-wrap:anywhere; text-align:center; white-space:normal; transform:none; }
62
+ @media(max-width:800px){ .score-chart { height:260px; padding-bottom:64px; } .score-axis.zero { bottom:58px; } }
64
63
  footer { color: var(--muted); font-size: 11.5px; margin-top: 40px; text-align: center; }
65
64
  svg text { fill: var(--muted); font-size: 10.5px; }
66
65
  .axis line, .axis path { stroke: var(--line); }
@@ -70,15 +69,19 @@
70
69
  <body>
71
70
  <div class="wrap">
72
71
  <header class="top">
73
- <div><h1>Benchmark report</h1><div class="meta" id="generated"></div><div class="meta" id="schema"></div></div>
72
+ <div><h1>Benchmark report</h1><div class="meta" id="generated"></div></div>
74
73
  </header>
75
74
 
76
75
  <div class="kpis" id="kpis"></div>
77
76
 
77
+ <section id="section-score-distribution">
78
+ <h2>Score distribution</h2>
79
+ <div class="score-chart-scroll"><div id="chart-score-distribution" role="img" aria-label="Verified score by model and reasoning configuration"></div></div>
80
+ </section>
81
+
78
82
  <section>
79
- <h2>Run outcomes <span class="sub">generation completion versus verified correctness</span></h2>
83
+ <h2>Run outcomes</h2>
80
84
  <div class="toolbar model-filter">
81
- <span>Models</span>
82
85
  <details class="model-menu">
83
86
  <summary id="model-filter-label">All models</summary>
84
87
  <div id="leaderboard-model-options" class="model-options"></div>
@@ -90,22 +93,15 @@
90
93
  </section>
91
94
 
92
95
  <section>
93
- <h2>Test-case results <span class="sub">points earned by one selected model</span></h2>
96
+ <h2>Test-case results</h2>
94
97
  <div class="toolbar"><label for="verifier-model-select">Model</label><select id="verifier-model-select"></select></div>
95
98
  <div class="panel">
96
99
  <table id="tbl-verifier-matrix"></table>
97
100
  </div>
98
101
  </section>
99
102
 
100
- <section>
101
- <h2>Run details <span class="sub">generation and benchmarking status for each attempt</span></h2>
102
- <div class="panel">
103
- <table id="tbl-runs"></table>
104
- </div>
105
- </section>
106
-
107
103
  <section id="section-series">
108
- <h2>Trajectory <span class="sub">where context and cost accumulate</span></h2>
104
+ <h2>Trajectory</h2>
109
105
  <div class="grid2">
110
106
  <div class="panel">
111
107
  <h3>Prompt tokens (context) vs. step</h3>
@@ -150,6 +146,21 @@
150
146
  }
151
147
 
152
148
  function fmt(n) { return Number(n || 0).toLocaleString("en-US"); }
149
+ function fmtDuration(seconds) {
150
+ var total = Math.max(0, Math.round(Number(seconds) || 0));
151
+ if (total < 60) return total + "s";
152
+ var minutes = Math.floor(total / 60);
153
+ var remainder = total % 60;
154
+ if (minutes < 60) return minutes + "m " + String(remainder).padStart(2, "0") + "s";
155
+ var hours = Math.floor(minutes / 60);
156
+ return hours + "h " + String(minutes % 60).padStart(2, "0") + "m";
157
+ }
158
+ function fmtTokens(tokens) {
159
+ var value = Math.max(0, Number(tokens) || 0);
160
+ if (value < 1000) return fmt(Math.round(value));
161
+ if (value < 1000000) return (value / 1000).toFixed(1).replace(/\.0$/, "") + "K";
162
+ return (value / 1000000).toFixed(1).replace(/\.0$/, "") + "M";
163
+ }
153
164
  function fmt1(n) { return (Number(n) || 0).toFixed(1); }
154
165
  function pct(n) { return Math.round((Number(n) || 0) * 100) + "%"; }
155
166
  function fmtUsd(n) {
@@ -179,19 +190,17 @@
179
190
 
180
191
  // ---------------------------------------------------------------- header
181
192
  document.getElementById("generated").textContent = DATA.generated_at
182
- ? "Generated " + new Date(DATA.generated_at).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" })
193
+ ? "Generated on " + new Date(DATA.generated_at).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" })
183
194
  : "";
184
- document.getElementById("schema").textContent = DATA.schema || "";
185
-
186
195
  // ---------------------------------------------------------------- KPIs
187
196
  (function renderKpis() {
188
197
  var k = DATA.kpis || {};
189
198
  var cards = [
190
- ["Models Benchmarked", fmt(k.models)],
199
+ ["Model–reasoning configurations", fmt(k.models)],
191
200
  ["Average score", k.average_score == null ? "n/a" : fmt1(k.average_score) + "%"],
192
- ["Total duration", fmt(k.elapsed_seconds) + "s"],
201
+ ["Average duration", fmtDuration(k.runs ? k.elapsed_seconds / k.runs : 0)],
193
202
  ["Total cost", fmtUsd(k.cost_usd) + (k.cost_source === "estimated" ? "~" : "")],
194
- ["Tokens used", fmt(k.tokens)],
203
+ ["Tokens used", fmtTokens(k.tokens)],
195
204
  ];
196
205
  var host = document.getElementById("kpis");
197
206
  cards.forEach(function (c) {
@@ -199,6 +208,37 @@
199
208
  });
200
209
  })();
201
210
 
211
+ // ---------------------------------------------------------------- score distribution
212
+ (function renderScoreDistribution() {
213
+ var host = document.getElementById("chart-score-distribution");
214
+ if (!host) return;
215
+ var rows = (DATA.leaderboard || []).map(function (row) {
216
+ var verified = typeof row.average_score === "number";
217
+ return { slug: row.slug, score: verified ? Math.max(0, Math.min(100, row.average_score)) : null, verified: verified };
218
+ }).sort(function (a, b) {
219
+ if (a.verified !== b.verified) return a.verified ? -1 : 1;
220
+ return (b.score || 0) - (a.score || 0) || a.slug.localeCompare(b.slug);
221
+ });
222
+ if (!rows.length) {
223
+ document.getElementById("section-score-distribution").style.display = "none";
224
+ return;
225
+ }
226
+ host.className = "score-chart";
227
+ host.style.minWidth = Math.max(760, rows.length * 45) + "px";
228
+ [["top", "100"], ["mid", "50"], ["zero", "0"]].forEach(function (axis) {
229
+ host.appendChild(el("span", { class: "score-axis " + axis[0] }, axis[1]));
230
+ });
231
+ rows.forEach(function (row) {
232
+ var item = el("div", { class: "score-item" });
233
+ var bar = el("div", { class: "score-bar", title: row.slug + ": " + (row.verified ? fmt1(row.score) + "%" : "n/a") });
234
+ if (row.verified) bar.style.height = Math.max(2, row.score) + "%";
235
+ bar.appendChild(el("span", { class: "score-value" }, row.verified ? fmt1(row.score) + "%" : "n/a"));
236
+ item.appendChild(bar);
237
+ item.appendChild(el("span", { class: "score-label" }, esc(row.slug)));
238
+ host.appendChild(item);
239
+ });
240
+ })();
241
+
202
242
  // ---------------------------------------------------------------- sortable table helper
203
243
  function sortableTable(container, columns, rows, opts) {
204
244
  opts = opts || {};
@@ -206,6 +246,10 @@
206
246
  function draw() {
207
247
  var sorted = rows.slice().sort(function (a, b) {
208
248
  var va = a[state.key], vb = b[state.key];
249
+ if (va == null || vb == null) {
250
+ if (va == null && vb == null) return 0;
251
+ return va == null ? 1 : -1;
252
+ }
209
253
  if (typeof va === "string" || typeof vb === "string") {
210
254
  return state.dir * String(va).localeCompare(String(vb));
211
255
  }
@@ -291,13 +335,13 @@
291
335
  { key: "runs", label: "Runs", render: function (r) { return fmt(r.runs); } },
292
336
  { key: "average_score", label: "Score", render: function (r) { return r.average_score == null ? '<span class="n-a">n/a</span>' : fmt1(r.average_score) + "%"; } },
293
337
  { key: "sum_steps", label: "Total steps", render: function (r) { return fmt(r.sum_steps); } },
294
- { key: "sum_elapsed_seconds", label: "Time taken", render: function (r) { return fmt(r.sum_elapsed_seconds) + "s"; } },
295
- { key: "sum_tokens", label: "Tokens", render: function (r) { return fmt(r.sum_tokens); } },
338
+ { key: "sum_elapsed_seconds", label: "Time taken", render: function (r) { return fmtDuration(r.sum_elapsed_seconds); } },
339
+ { key: "sum_tokens", label: "Tokens", render: function (r) { return fmtTokens(r.sum_tokens); } },
296
340
  { key: "cost_usd", label: "Cost", render: function (r) { return fmtUsd(r.cost_usd) + (r.cost_source === "estimated" ? "~" : ""); } },
297
341
  ];
298
342
  (DATA.leaderboard || []).forEach(function (r) {
299
343
  var option = el("label", { class: "model-option" });
300
- option.appendChild(el("input", { type: "checkbox", value: r.slug }));
344
+ option.appendChild(el("input", { type: "checkbox", value: r.slug, checked: "checked" }));
301
345
  option.appendChild(document.createTextNode(r.model + " · " + r.reasoning));
302
346
  options.appendChild(option);
303
347
  });
@@ -305,7 +349,7 @@
305
349
  var selected = Array.prototype.slice.call(options.querySelectorAll("input:checked")).map(function (o) { return o.value; });
306
350
  var rows = selected.length ? (DATA.leaderboard || []).filter(function (r) { return selected.indexOf(r.slug) !== -1; }) : DATA.leaderboard || [];
307
351
  filterLabel.textContent = selected.length ? selected.length + " model" + (selected.length === 1 ? "" : "s") + " selected" : "All models";
308
- sortableTable(container, columns, rows, { defaultKey: "sum_tokens" });
352
+ sortableTable(container, columns, rows, { defaultKey: "average_score" });
309
353
  }
310
354
  options.addEventListener("change", draw);
311
355
  draw();
@@ -389,7 +433,7 @@
389
433
  if (w > 0) track.appendChild(el("div", { class: "bar-seg", style: "width:" + w + "%;background:" + colors[k] }));
390
434
  });
391
435
  row.appendChild(track);
392
- row.appendChild(el("div", { class: "bar-val" }, fmt(total)));
436
+ row.appendChild(el("div", { class: "bar-val" }, fmtTokens(total)));
393
437
  host.appendChild(row);
394
438
  });
395
439
  keys.forEach(function (k) {
@@ -414,7 +458,7 @@
414
458
  [0, 0.25, 0.5, 0.75, 1].forEach(function (t) {
415
459
  var ty = y(maxDuration * t);
416
460
  var lab = svgEl("text", { x: pad.l - 8, y: ty + 3, "text-anchor": "end" });
417
- lab.textContent = Math.round(maxDuration * t) + "s";
461
+ lab.textContent = fmtDuration(maxDuration * t);
418
462
  svg.appendChild(lab);
419
463
  });
420
464
  var lab2 = svgEl("text", { x: W - pad.r, y: H - pad.b + 20, "text-anchor": "end" });
@@ -510,7 +554,7 @@
510
554
  document.getElementById("section-series").style.display = "none";
511
555
  return;
512
556
  }
513
- lineChart(document.getElementById("chart-line-tokens"), series, function (p) { return p.prompt_tokens.p50; }, { fmt: fmt });
557
+ lineChart(document.getElementById("chart-line-tokens"), series, function (p) { return p.prompt_tokens.p50; }, { fmt: fmtTokens });
514
558
  lineChart(document.getElementById("chart-line-cost"), series, function (p) { return p.cum_cost_usd.mean; }, { fmt: fmtUsd });
515
559
  var legend = document.getElementById("legend-series");
516
560
  series.forEach(function (s) {
@@ -553,99 +597,6 @@
553
597
  container.innerHTML = thead + tbody;
554
598
  })();
555
599
 
556
- // ---------------------------------------------------------------- runs table + filters + expandable timeline
557
- (function () {
558
- var runs = DATA.runs || [];
559
- var container = document.getElementById("tbl-runs");
560
- var fModel = document.getElementById("filter-model");
561
- var fTask = document.getElementById("filter-task");
562
- var fOutcome = document.getElementById("filter-outcome");
563
- if (!container) return;
564
-
565
- function uniq(fn) {
566
- var seen = {}, out = [];
567
- runs.forEach(function (r) { var v = fn(r); if (v && !seen[v]) { seen[v] = 1; out.push(v); } });
568
- return out.sort();
569
- }
570
- if (fModel) uniq(function (r) { return r.slug; }).forEach(function (v) { fModel.appendChild(el("option", { value: v }, esc(v))); });
571
- if (fTask) uniq(function (r) { return r.task_id; }).forEach(function (v) { fTask.appendChild(el("option", { value: v }, esc(v))); });
572
- if (fOutcome) uniq(function (r) { return r.status; }).forEach(function (v) { fOutcome.appendChild(el("option", { value: v }, esc(v))); });
573
-
574
- var columns = [
575
- { key: "slug", label: "Model", render: function (r) { return '<span class="mono">' + esc(r.slug) + "</span>"; } },
576
- { key: "status", label: "generation_status", render: function (r) {
577
- var cls = r.completed ? "ok" : (r.status === "request_error" ? "bad" : "warn");
578
- return '<span class="pill ' + cls + '">' + esc(r.status || "Unknown") + "</span>";
579
- } },
580
- { key: "verifier_status", label: "benchmarking_status", render: function (r) {
581
- var cls = r.verifier_status === "passed" ? "ok" : (r.verifier_status === "not_configured" ? "warn" : "bad");
582
- return '<span class="pill ' + cls + '">' + esc(r.verifier_status || "NA") + "</span>";
583
- } },
584
- { key: "solved_score", label: "Solved", render: function (r) { return typeof r.solved_score === "number" ? fmt1(r.solved_score) + "%" : '<span class="n-a">NA</span>'; } },
585
- { key: "steps", label: "Steps", render: function (r) { return fmt(r.steps); } },
586
- { key: "elapsed_seconds", label: "Time", render: function (r) { return fmt(r.elapsed_seconds) + "s"; } },
587
- { key: "tokens", label: "Tokens", render: function (r) { return fmt(r.tokens.total); } },
588
- { key: "cost_usd", label: "Cost", render: function (r) { return fmtUsd(r.cost_usd) + (r.cost_source === "estimated" ? "~" : ""); } },
589
- { key: "n_failed_commands", label: "Fails", render: function (r) { return fmt(r.n_failed_commands); } },
590
- ];
591
-
592
- function renderTimeline(run) {
593
- var box = el("div", { class: "timeline" });
594
- (run.timeline || []).forEach(function (s) {
595
- var rcClass = s.returncode === 0 ? "rc0" : (s.returncode == null ? "muted" : "rcN");
596
- var step = el("div", { class: "step" });
597
- step.innerHTML =
598
- '<div class="head"><span class="n">step ' + s.step + '</span>' +
599
- '<span class="stats">' +
600
- fmt(s.tokens.total) + " tok" +
601
- (s.cost_usd != null ? " \u00b7 " + fmtUsd(s.cost_usd) : "") +
602
- (s.returncode != null ? ' \u00b7 rc=<span class="' + rcClass + '">' + s.returncode + "</span>" : "") +
603
- "</span></div>" +
604
- (s.thought ? '<div class="thought">' + esc(s.thought) + "</div>" : "") +
605
- (s.command ? "<pre>$ " + esc(s.command) + "</pre>" : "") +
606
- (s.output_preview ? "<pre>" + esc(s.output_preview) + "</pre>" : "");
607
- box.appendChild(step);
608
- });
609
- return box;
610
- }
611
-
612
- function draw() {
613
- var filtered = runs.filter(function (r) {
614
- if (fModel && fModel.value && r.slug !== fModel.value) return false;
615
- if (fTask && fTask.value && r.task_id !== fTask.value) return false;
616
- if (fOutcome && fOutcome.value && r.status !== fOutcome.value) return false;
617
- return true;
618
- });
619
- sortableTable(container, columns, filtered, {
620
- defaultKey: "slug",
621
- defaultDir: 1,
622
- rowAttrs: function (r, i) { return 'class="run-row" data-idx="' + i + '"'; },
623
- afterDraw: function (containerEl, sorted) {
624
- containerEl.querySelectorAll("tbody tr").forEach(function (tr) {
625
- var idx = Number(tr.getAttribute("data-idx"));
626
- var run = sorted[idx];
627
- tr.addEventListener("click", function () {
628
- var next = tr.nextElementSibling;
629
- if (next && next.classList.contains("detail-row")) {
630
- next.remove();
631
- return;
632
- }
633
- containerEl.querySelectorAll(".detail-row").forEach(function (d) { d.remove(); });
634
- var detailRow = document.createElement("tr");
635
- detailRow.className = "detail-row";
636
- var td = document.createElement("td");
637
- td.colSpan = columns.length;
638
- td.appendChild(renderTimeline(run));
639
- detailRow.appendChild(td);
640
- tr.parentNode.insertBefore(detailRow, tr.nextSibling);
641
- });
642
- });
643
- },
644
- });
645
- }
646
- [fModel, fTask, fOutcome].filter(Boolean).forEach(function (elm) { elm.addEventListener("change", draw); });
647
- draw();
648
- })();
649
600
  })();
650
601
  </script>
651
602
  </body>
package/src/report.js CHANGED
@@ -148,7 +148,13 @@ export function buildSummary(runs) {
148
148
  avg_n_retries: avg(rs, (r) => r.n_retries),
149
149
  };
150
150
  });
151
- leaderboard.sort((a, b) => b.completion_rate - a.completion_rate || a.avg_steps - b.avg_steps);
151
+ leaderboard.sort((a, b) => {
152
+ const aScored = a.average_score != null;
153
+ const bScored = b.average_score != null;
154
+ if (aScored !== bScored) return aScored ? -1 : 1;
155
+ return (b.average_score ?? 0) - (a.average_score ?? 0)
156
+ || a.slug.localeCompare(b.slug);
157
+ });
152
158
 
153
159
  const matrix = groupBy(runs, (r) => `${r.slug}\u0000${r.task_id}`).map(([, rs]) => {
154
160
  const completed = rs.filter((r) => r.completed).length;
@@ -153,7 +153,7 @@ export async function runVerifier({ project, cell, runDir, candidateDir, config,
153
153
  if (!validation.ok) return { status: "error", failureOwner: "infrastructure", solvedScore: "NA", durationSeconds, error: validation.error, exitCode: commandResult.code };
154
154
  return {
155
155
  status: raw.status,
156
- failureOwner: raw.status === "failed" ? "candidate" : raw.status === "passed" ? null : "infrastructure",
156
+ failureOwner: raw.status === "failed" || raw.status === "blocked" ? "candidate" : raw.status === "passed" ? null : "infrastructure",
157
157
  score: raw.score,
158
158
  solvedScore: raw.score?.percentage ?? "NA",
159
159
  criteria: raw.criteria,