@giamme/forge 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +106 -1766
- package/SKILL.md +3 -2
- package/VERSION +1 -1
- package/docs/README.md +26 -0
- package/docs/add-ons.md +33 -0
- package/docs/artifacts.md +69 -0
- package/docs/commands.md +315 -0
- package/docs/dataset.md +20 -0
- package/docs/decomposed-runs.md +268 -0
- package/docs/development.md +95 -0
- package/docs/forge-ui.md +92 -0
- package/docs/fractal.md +181 -0
- package/docs/install.md +160 -0
- package/docs/memory.md +156 -0
- package/docs/models.md +118 -0
- package/docs/reviews.md +231 -0
- package/docs/safety.md +55 -0
- package/docs/troubleshooting.md +54 -0
- package/forge +5 -0
- package/package.json +3 -2
- package/references/decompose.md +1 -1
- package/references/fractal.md +1 -1
- package/references/harnesses.md +18 -6
- package/references/solo.md +1 -1
- package/scripts/forge-dispatch.sh +7 -1
- package/scripts/forge-runs.py +121 -0
- package/scripts/forge-runtime.py +59 -6
- package/scripts/forge-version.py +54 -12
- package/scripts/forge_dataset/harvest.py +1 -1
- package/scripts/forge_dataset/importer.py +1 -1
- package/scripts/forge_fractal/__init__.py +2 -1
- package/ui/README.md +80 -20
- package/ui/THIRD_PARTY_NOTICES.md +108 -0
- package/ui/docs/api.md +99 -17
- package/ui/forge_ui/export.py +16 -6
- package/ui/forge_ui/fractal.py +1 -2
- package/ui/forge_ui/reader.py +95 -7
- package/ui/forge_ui/server.py +10 -1
- package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
- package/ui/forge_ui/static/app.js +279 -124
- package/ui/forge_ui/static/cloud3d.js +3 -2
- package/ui/forge_ui/static/cloudui.js +8 -5
- package/ui/forge_ui/static/convo.js +295 -0
- package/ui/forge_ui/static/datatable.js +232 -0
- package/ui/forge_ui/static/diffview.js +29 -10
- package/ui/forge_ui/static/findings3d.js +134 -0
- package/ui/forge_ui/static/graph.js +3 -3
- package/ui/forge_ui/static/icons.js +94 -0
- package/ui/forge_ui/static/index.html +25 -13
- package/ui/forge_ui/static/iso.js +70 -19
- package/ui/forge_ui/static/leaderboard.js +44 -48
- package/ui/forge_ui/static/panel.js +68 -32
- package/ui/forge_ui/static/runmodel.js +17 -1
- package/ui/forge_ui/static/scene3d.js +518 -43
- package/ui/forge_ui/static/search.js +2 -1
- package/ui/forge_ui/static/shell.js +137 -0
- package/ui/forge_ui/static/stagectl.js +150 -0
- package/ui/forge_ui/static/style.css +924 -469
- package/ui/forge_ui/static/theme.js +25 -0
- package/ui/forge_ui/transcripts.py +608 -0
|
@@ -63,8 +63,9 @@ window.ForgeLeaderboard = (() => {
|
|
|
63
63
|
function modelCell(alias, ctx, few) {
|
|
64
64
|
const td = el('td', undefined, 'lb-model');
|
|
65
65
|
const href = ctx.modelHref ? ctx.modelHref(alias) : null;
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
const plate = window.ForgeIcons ? window.ForgeIcons.modelPlate(alias) : el('span', alias, 'mono');
|
|
67
|
+
if (href) { const a = el('a', undefined, 'lb-alias'); a.href = href; a.title = 'show its runs in the cloud'; a.append(plate); td.append(a); }
|
|
68
|
+
else td.append(plate);
|
|
68
69
|
if (few) { const s = el('span', 'few', 'lb-few'); s.title = few; td.append(s); }
|
|
69
70
|
return td;
|
|
70
71
|
}
|
|
@@ -96,11 +97,6 @@ window.ForgeLeaderboard = (() => {
|
|
|
96
97
|
td.title = `input ${count(t.input)} (cached ${count(t.cached)}) · output ${count(t.output)} · recorded by ${count(used)} of ${plural(total, unit)}`;
|
|
97
98
|
return td;
|
|
98
99
|
}
|
|
99
|
-
function head(table, cols) {
|
|
100
|
-
const thead = el('thead'), tr = el('tr');
|
|
101
|
-
for (const [label, cls, title] of cols) { const th = el('th', label, cls || ''); th.scope = 'col'; if (title) th.title = title; tr.append(th); }
|
|
102
|
-
thead.append(tr); table.append(thead);
|
|
103
|
-
}
|
|
104
100
|
|
|
105
101
|
/* ---------- implementers ---------- */
|
|
106
102
|
const IMPL_COLS = [
|
|
@@ -116,31 +112,31 @@ window.ForgeLeaderboard = (() => {
|
|
|
116
112
|
['Median model time', 'lb-num', 'Median time in the model phase per attempt'],
|
|
117
113
|
['Tokens in / out', 'lb-num', 'Summed over the attempts that recorded tokens'],
|
|
118
114
|
];
|
|
115
|
+
const colsOf = (spec, cells) => spec.map(([label, cls, title], i) => ({ ...cells[i], label, cls, title }));
|
|
116
|
+
// a sortable, reorderable table (datatable.js); one set of preferences per kind of table, shared by its copies
|
|
117
|
+
function table(id, label, columns, rows, ctx, rowClass) {
|
|
118
|
+
const host = el('div', undefined, 'lb-dt');
|
|
119
|
+
window.ForgeTable.render(host, { id, label, columns, rows, className: 'lb-table', rowClass,
|
|
120
|
+
prefs: ctx.tablePrefs ? ctx.tablePrefs(id) : null, savePrefs: p => ctx.saveTablePrefs && ctx.saveTablePrefs(id, p) });
|
|
121
|
+
return host;
|
|
122
|
+
}
|
|
119
123
|
function implementerTable(rows, ctx, minN, caption) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
numCell(r.gateAccepts),
|
|
137
|
-
numCell(r.humanAccepts),
|
|
138
|
-
numCell(dur(r.medianModelS), `median wall time per attempt ${dur(r.medianDurationS)}`),
|
|
139
|
-
tokenCell(r.tokens, r.attempts.total, 'attempt'));
|
|
140
|
-
body.append(tr);
|
|
141
|
-
}
|
|
142
|
-
table.append(body); wrap.append(table);
|
|
143
|
-
return wrap;
|
|
124
|
+
const tk = r => r.timeoutKinds;
|
|
125
|
+
const columns = colsOf(IMPL_COLS, [
|
|
126
|
+
{ id: 'model', value: r => r.alias, cell: r => modelCell(r.alias, ctx, r.small ? `Fewer than ${plural(minN, 'first-attempt outcome')}: too few to rank` : '') },
|
|
127
|
+
{ id: 'tasks', value: r => r.tasks, cell: r => numCell(r.tasks) },
|
|
128
|
+
{ id: 'pass', value: r => r.first.n ? r.first.lo : null, cell: r => rateCell(r.first) },
|
|
129
|
+
{ id: 'censored', value: r => r.first.censored, cell: r => numCell(r.first.censored, Object.entries(r.first.reasons).map(([k, v]) => `${v} ${k.replace(/_/g, ' ')}`).join(', ') || undefined) },
|
|
130
|
+
{ id: 'retries', value: r => r.retries, cell: r => numCell(r.retries) },
|
|
131
|
+
{ id: 'escalated', value: r => r.escalationsAway, cell: r => numCell(r.escalationsAway) },
|
|
132
|
+
{ id: 'timeouts', value: r => r.timeouts, cell: r => numCell(r.timeouts, r.timeouts ? `implementer ${tk(r).implementer} · reviewer ${tk(r).reviewer} · forge ${tk(r).forge}` : undefined) },
|
|
133
|
+
{ id: 'gate', value: r => r.gateAccepts, cell: r => numCell(r.gateAccepts) },
|
|
134
|
+
{ id: 'human', value: r => r.humanAccepts, cell: r => numCell(r.humanAccepts) },
|
|
135
|
+
{ id: 'model-time', value: r => num(r.medianModelS), cell: r => numCell(dur(r.medianModelS), `median wall time per attempt ${dur(r.medianDurationS)}`) },
|
|
136
|
+
{ id: 'tokens', value: r => (r.tokens.attempts ?? r.tokens.reviews) ? (r.tokens.input || 0) + (r.tokens.output || 0) : null, cell: r => tokenCell(r.tokens, r.attempts.total, 'attempt') },
|
|
137
|
+
]);
|
|
138
|
+
columns[2].title += '. Sorts by the lower end of the interval, as the ranking does';
|
|
139
|
+
return table('implementers', caption, columns, rows, ctx, r => r.small ? 'lb-small' : '');
|
|
144
140
|
}
|
|
145
141
|
|
|
146
142
|
/* ---------- reviewers ---------- */
|
|
@@ -176,7 +172,7 @@ window.ForgeLeaderboard = (() => {
|
|
|
176
172
|
const avg = r.findings[s];
|
|
177
173
|
if (avg === null) return numCell('—', 'No review reply could be read');
|
|
178
174
|
const td = numCell(avg === 0 ? 0 : avg.toFixed(avg < 10 ? 2 : 1), `${count(r.findingTotals[s])} confirmed ${s} over ${plural(r.findings.n, 'review')}`);
|
|
179
|
-
if (s === 'P0' && avg > 0) td.classList.add('lb-p0'
|
|
175
|
+
if (s === 'P0' && avg > 0) td.classList.add('lb-p0');
|
|
180
176
|
return td;
|
|
181
177
|
}
|
|
182
178
|
function agreementCell(a) {
|
|
@@ -187,25 +183,25 @@ window.ForgeLeaderboard = (() => {
|
|
|
187
183
|
return td;
|
|
188
184
|
}
|
|
189
185
|
function reviewerTable(rows, ctx) {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return
|
|
186
|
+
const columns = colsOf(REV_COLS, [
|
|
187
|
+
{ id: 'model', value: r => r.alias, cell: r => modelCell(r.alias, ctx, '') },
|
|
188
|
+
{ id: 'primary', value: r => r.reviews.primary, cell: r => numCell(r.reviews.primary) },
|
|
189
|
+
{ id: 'shadow', value: r => r.reviews.shadow, cell: r => numCell(r.reviews.shadow) },
|
|
190
|
+
{ id: 'final', value: r => r.reviews.final, cell: r => numCell(r.reviews.final) },
|
|
191
|
+
{ id: 'verdicts', value: r => { const v = r.verdicts, t = v.PASS + v.FAIL + v.other; return t ? v.PASS / t : null; }, cell: r => verdictCell(r.verdicts) },
|
|
192
|
+
...MS.SEVERITIES.map(sv => ({ id: 'sev-' + sv, value: r => r.findings[sv], cell: r => findingCell(r, sv) })),
|
|
193
|
+
{ id: 'agreement', value: r => r.agreement.n ? r.agreement.p : null, cell: r => agreementCell(r.agreement) },
|
|
194
|
+
{ id: 'time', value: r => num(r.medianDurationS), cell: r => numCell(dur(r.medianDurationS)) },
|
|
195
|
+
{ id: 'tokens', value: r => (r.tokens.attempts ?? r.tokens.reviews) ? (r.tokens.input || 0) + (r.tokens.output || 0) : null, cell: r => tokenCell(r.tokens, r.reviews.total, 'review') },
|
|
196
|
+
]);
|
|
197
|
+
return table('reviewers', 'Reviewers', columns, rows, ctx);
|
|
202
198
|
}
|
|
203
199
|
|
|
204
200
|
/* ---------- header, caveat and filters ---------- */
|
|
205
201
|
function header(L, O, f, update, withNote) {
|
|
206
202
|
const t = L.totals, frag = document.createDocumentFragment();
|
|
207
203
|
const top = el('header', undefined, 'lb-head');
|
|
208
|
-
top.append(el('h1', 'Models'), el('p', `${plural(t.runs, 'run')} · ${plural(t.tasks, 'task')} · ${plural(t.attempts, 'attempt')} · ${plural(t.reviews, 'review')} counted`, 'muted lb-summary'));
|
|
204
|
+
top.append(el('h1', 'Models', 'module-title'), el('p', `${plural(t.runs, 'run')} · ${plural(t.tasks, 'task')} · ${plural(t.attempts, 'attempt')} · ${plural(t.reviews, 'review')} counted`, 'muted lb-summary'));
|
|
209
205
|
const note = el('div', undefined, 'lb-caveat'); note.setAttribute('role', 'note');
|
|
210
206
|
const p = el('p');
|
|
211
207
|
p.append(el('strong', 'Observational, not causal. '), document.createTextNode('Forge routes each task to a model by its difficulty and by how models did before, so a model sent the hard tasks looks worse here than it is. Exploratory picks, the tasks where forge drew the model at random, are the fairest comparison.'));
|
|
@@ -276,12 +272,12 @@ window.ForgeLeaderboard = (() => {
|
|
|
276
272
|
root.append(emptyNote('No tasks or reviews match these filters.', [], reset));
|
|
277
273
|
} else {
|
|
278
274
|
const impl = el('section', undefined, 'lb-section');
|
|
279
|
-
impl.append(el('h2', 'Implementers'), el('p', 'Each task counts once, for the model that ran its first attempt; retries, escalations and accepts show what happened after. Ranked by the lower end of the 95% interval, so a high rate on few tasks does not jump the table.', 'lb-lead'));
|
|
275
|
+
impl.append(el('h2', 'Implementers', 'plate'), el('p', 'Each task counts once, for the model that ran its first attempt; retries, escalations and accepts show what happened after. Ranked by the lower end of the 95% interval, so a high rate on few tasks does not jump the table.', 'lb-lead'));
|
|
280
276
|
impl.append(L.implementers.length ? implementerTable(L.implementers, ctx, f.minN, 'Implementers') : el('p', 'No attempts with a recorded model match these filters.', 'muted'));
|
|
281
277
|
root.append(impl);
|
|
282
278
|
if (!f.difficulty && L.implementers.length) {
|
|
283
279
|
const by = el('section', undefined, 'lb-section');
|
|
284
|
-
by.append(el('h2', 'By difficulty'), el('p', 'The same table for the tasks of one declared difficulty: the comparison routing distorts least.', 'lb-lead'));
|
|
280
|
+
by.append(el('h2', 'By difficulty', 'plate'), el('p', 'The same table for the tasks of one declared difficulty: the comparison routing distorts least.', 'lb-lead'));
|
|
285
281
|
for (const d of MS.DIFFS) {
|
|
286
282
|
const rows = L.byDifficulty[d];
|
|
287
283
|
if (!rows.length) continue;
|
|
@@ -299,7 +295,7 @@ window.ForgeLeaderboard = (() => {
|
|
|
299
295
|
root.append(by);
|
|
300
296
|
}
|
|
301
297
|
const rev = el('section', undefined, 'lb-section');
|
|
302
|
-
rev.append(el('h2', 'Reviewers'), el('p', 'Primary reviews of each attempt, shadow reviews run beside them and final reviews of a whole run. Findings are confirmed findings per review.', 'lb-lead'));
|
|
298
|
+
rev.append(el('h2', 'Reviewers', 'plate'), el('p', 'Primary reviews of each attempt, shadow reviews run beside them and final reviews of a whole run. Findings are confirmed findings per review.', 'lb-lead'));
|
|
303
299
|
rev.append(L.reviewers.length ? reviewerTable(L.reviewers, ctx) : el('p', 'No reviews with a recorded model match these filters.', 'muted'));
|
|
304
300
|
root.append(rev);
|
|
305
301
|
}
|
|
@@ -9,7 +9,6 @@ window.ForgePanel = (() => {
|
|
|
9
9
|
const display = v => v === null || v === undefined || v === '' ? '—' : typeof v === 'object' ? JSON.stringify(v, null, 2) : String(v);
|
|
10
10
|
const STATUSES = new Set(['merged', 'pass', 'running', 'pending', 'fail', 'error', 'infra', 'timeout', 'unknown', 'invalidated', 'blocked',
|
|
11
11
|
'interrupted', 'nochanges', 'conflict', 'accepted', 'none']);
|
|
12
|
-
const SEV = { P0: 'fail', P1: 'conflict', P2: 'timeout', P3: 'unknown' };
|
|
13
12
|
const cache = new Map(); // path → {data, error, at}
|
|
14
13
|
function el(tag, text, className) {
|
|
15
14
|
const node = document.createElement(tag);
|
|
@@ -21,6 +20,18 @@ window.ForgePanel = (() => {
|
|
|
21
20
|
const s = String(value || 'unknown').toLowerCase();
|
|
22
21
|
return el('span', value || '—', 'chip st-' + (cls || (STATUSES.has(s) ? s : 'unknown')));
|
|
23
22
|
}
|
|
23
|
+
// a finding's severity: its own shape and ramp (style.css .sev-P0…P3), never a status lamp
|
|
24
|
+
const plate = spec => window.ForgeIcons ? window.ForgeIcons.modelPlate(spec) : el('span', spec || '—', 'mono');
|
|
25
|
+
const plates = specs => { const s = el('span', undefined, 'plates'); for (const x of specs) s.append(plate(x)); return specs.length ? s : undefined; };
|
|
26
|
+
// a long path cut in the middle: its head gives way, its last part always shows; copying takes all of it
|
|
27
|
+
function midPath(v) {
|
|
28
|
+
const s = String(v || ''), i = s.lastIndexOf('/', s.length - 2);
|
|
29
|
+
if (!s) return undefined;
|
|
30
|
+
const out = el('span', undefined, 'mid-path'); out.title = s;
|
|
31
|
+
out.append(el('span', i > 0 ? s.slice(0, i + 1) : '', 'mid-head'), el('span', i > 0 ? s.slice(i + 1) : s, 'mid-tail'));
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
const sevChip = (text, sev) => el('span', text, 'chip sev-chip sev-' + (/^P[0-3]$/.test(sev) ? sev : 'none'));
|
|
24
35
|
function time(v) {
|
|
25
36
|
if (num(v) === null) return '—';
|
|
26
37
|
const d = new Date(v * 1000); return Number.isNaN(d.getTime()) ? '—' : d.toLocaleString();
|
|
@@ -87,7 +98,7 @@ window.ForgePanel = (() => {
|
|
|
87
98
|
const row = el('div', undefined, 'chips');
|
|
88
99
|
if (!f) { row.append(el('span', 'no findings parsed', 'muted')); parent.append(row); return; }
|
|
89
100
|
let any = false;
|
|
90
|
-
for (const sev of ['P0', 'P1', 'P2', 'P3']) if (f[sev]) { row.append(
|
|
101
|
+
for (const sev of ['P0', 'P1', 'P2', 'P3']) if (f[sev]) { row.append(sevChip(`${sev} × ${f[sev]}`, sev)); any = true; }
|
|
91
102
|
if (f.unlabelled) { row.append(chip(`unlabelled × ${f.unlabelled}`, 'unknown')); any = true; }
|
|
92
103
|
if (f.plausible) { row.append(chip(`plausible × ${f.plausible}`, 'pending')); any = true; }
|
|
93
104
|
if (!any) row.append(el('span', 'no confirmed findings', 'muted'));
|
|
@@ -124,7 +135,7 @@ window.ForgePanel = (() => {
|
|
|
124
135
|
findings.slice().sort((a, b) => order(a) - order(b)).forEach((f, i) => {
|
|
125
136
|
const li = el('li', undefined, 'finding' + (f.status === 'CONFIRMED' ? '' : ' plausible'));
|
|
126
137
|
const head = el('div', undefined, 'finding-head');
|
|
127
|
-
head.append(
|
|
138
|
+
head.append(sevChip(f.severity || 'P?', f.severity), el('span', f.status, 'tag'));
|
|
128
139
|
if (f.location) head.append(el('code', f.location));
|
|
129
140
|
li.append(head, el('p', f.summary || ''));
|
|
130
141
|
if (f.text && f.text !== f.summary) { const d = el('details'); d.dataset.keep = `${key}-f${i}`; d.append(el('summary', 'Full finding'), el('pre', f.text)); li.append(d); }
|
|
@@ -159,17 +170,26 @@ window.ForgePanel = (() => {
|
|
|
159
170
|
const tokens = t => t ? `${display(t.input)} in (${display(t.cached)} cached) · ${display(t.output)} out` : '—';
|
|
160
171
|
function modelFields(m) {
|
|
161
172
|
if (!m) return [['Model', '—']];
|
|
162
|
-
return [['Spec', m.spec
|
|
173
|
+
return [['Spec', m.spec ? plate(m.spec) : undefined], ['Harness', m.harness, true], ['Model', m.model, true], ['Effort', m.effort], m.clamped ? ['Clamped', m.clamped] : ['Clamped', undefined]];
|
|
163
174
|
}
|
|
164
175
|
|
|
165
176
|
/* ---------- views ---------- */
|
|
177
|
+
// the run's record: what it was asked, how it was set up, and what verification printed (the header names and counts it)
|
|
178
|
+
function runRecord(host, ctx) {
|
|
179
|
+
const run = ctx.run, solo = run.kind === 'solo';
|
|
180
|
+
const s = section(host, 'Settings');
|
|
181
|
+
fields(s, [['Repo', midPath(run.repo), true], ['Run ID', run.run_id, true], ['Path', midPath(run.path), true], ['Source', run.source],
|
|
182
|
+
['Decompose', run.decompose_level], ['Updated', time(run.updated)], ['Explore', run.settings && run.settings.explore], ['Shadow rate', run.settings && run.settings.shadow_rate],
|
|
183
|
+
['Shadow reviewers', run.settings && plates(list(run.settings.shadow_qa))], ['QA threshold', run.settings && run.settings.qa_threshold],
|
|
184
|
+
['Max parallel', run.settings && run.settings.max_parallel], ['Final reviewer', run.settings && run.settings.final_qa ? plate(run.settings.final_qa) : undefined],
|
|
185
|
+
['Timeout (seconds)', run.settings && run.settings.timeout], ['Verification', chip((run.verification && run.verification.status) || run.verification_status)],
|
|
186
|
+
['Command', run.verification && run.verification.command, true]]);
|
|
187
|
+
details(host, 'Request', display(run.request), 'run-request');
|
|
188
|
+
if (!solo) details(host, 'Waves', list(run.waves).map((ids, i) => `${i + 1}: ${list(ids).join(', ')}`).join('\n') || '—', 'waves');
|
|
189
|
+
if (run.verification && run.verification.log_tail != null) details(host, 'Verification output', run.verification.log_tail, 'verification-output');
|
|
190
|
+
}
|
|
166
191
|
function runView(host, ctx) {
|
|
167
192
|
const run = ctx.run, now = ctx.now;
|
|
168
|
-
const head = el('div', undefined, 'insp-head');
|
|
169
|
-
head.append(el('div', run.kind === 'solo' ? 'solo run' : 'run', 'insp-kind'), el('h2', String(run.goal || 'Untitled run').split('\n')[0]));
|
|
170
|
-
const chips = el('div', undefined, 'chips');
|
|
171
|
-
for (const [s, n] of Object.entries(run.counts || {})) { const c = chip(s); c.append(document.createTextNode(' ' + n)); chips.append(c); }
|
|
172
|
-
head.append(chips); host.append(head);
|
|
173
193
|
const tr = RM.timeRange(run, now);
|
|
174
194
|
const s = section(host, 'Timeline');
|
|
175
195
|
fields(s, [['Started', time(tr && tr.start)], ['Last activity', time(tr && tr.end)], ['Wall time', tr ? RM.fmtDur(tr.end - tr.start) : '—'],
|
|
@@ -196,13 +216,14 @@ window.ForgePanel = (() => {
|
|
|
196
216
|
if (F.final.findings) findingCounts(f, F.final.findings);
|
|
197
217
|
}
|
|
198
218
|
if (list(run.deferrals).length) details(host, `Deferred tasks (${run.deferrals.length})`, run.deferrals.join('\n'), 'deferrals');
|
|
219
|
+
runRecord(host, ctx);
|
|
199
220
|
muted(host, 'Select a task, an attempt or a finish node on the map or in the table to see its prompts, models, timings and findings.');
|
|
200
221
|
}
|
|
201
222
|
|
|
202
223
|
function taskView(host, ctx, task) {
|
|
203
224
|
const run = ctx.run, now = ctx.now, solo = run.kind === 'solo';
|
|
204
225
|
const head = el('div', undefined, 'insp-head');
|
|
205
|
-
head.append(el('
|
|
226
|
+
head.append(el('h2', solo ? (task.title || 'Solo run') : `${task.id} · ${task.title || 'Untitled task'}`), el('div', solo ? 'solo task' : `task · wave ${display(task.wave)}`, 'insp-kind'));
|
|
206
227
|
const chips = el('div', undefined, 'chips');
|
|
207
228
|
chips.append(chip(task.status));
|
|
208
229
|
if (task.verdict) chips.append(chip('review ' + task.verdict, task.verdict.toLowerCase()));
|
|
@@ -215,7 +236,7 @@ window.ForgePanel = (() => {
|
|
|
215
236
|
|
|
216
237
|
// the models: who owned it, how it was routed, what it was escalated to
|
|
217
238
|
const ms = section(host, 'Models');
|
|
218
|
-
fields(ms, [['Dwarf', task.dwarf
|
|
239
|
+
fields(ms, [['Dwarf', plate(task.dwarf)], ['Reviewer', plate(task.qa)], ['Attempt', task.attempt]]);
|
|
219
240
|
const route = list(task.route).slice(-1)[0];
|
|
220
241
|
if (route) fields(ms, [['Routed', `${display(route.chosen)} from ${list(route.candidates).join(', ') || '—'}${route.explored ? ` (exploratory, p=${display(route.propensity)})` : ''}`, true], ['Tier · ε', `${display(route.tier)} · ${display(route.epsilon)}`]]);
|
|
221
242
|
for (const e of list(task.escalations)) muted(ms, `Escalated after attempt ${display(e.attempt)}: ${display(e.from)} → ${display(e.to)} at ${time(e.at)}`);
|
|
@@ -240,14 +261,18 @@ window.ForgePanel = (() => {
|
|
|
240
261
|
const p = tp.parts.find(x => x.id === pid); if (!p || p.planned) continue;
|
|
241
262
|
const line = el('div', undefined, 'exec-line');
|
|
242
263
|
const e = p.exec && list(task.executions).find(x => x.id === p.exec.id) || null;
|
|
243
|
-
const label =
|
|
264
|
+
const label = p.label.toLowerCase();
|
|
244
265
|
const target = p.exec && p.exec.id ? { kind: 'execution', task: task.id, id: task.id, exec: p.exec.id, shadow: p.kind === 'shadow' } : null;
|
|
245
|
-
line.append(target ? link(label, ctx.hash(target), () => ctx.onSelect(target)) : el('span', label), chip(p.status));
|
|
266
|
+
line.append(target ? link(label, ctx.hash(target), () => ctx.onSelect(target)) : el('span', label), plate(p.spec), chip(p.status));
|
|
246
267
|
if (e) line.append(el('span', `${RM.fmtDur(e.duration_s ?? (e.state === 'running' && num(e.started) !== null ? now - e.started : null))}${e.state === 'running' && e.phase ? ' · ' + e.phase : ''}`, 'muted'));
|
|
247
|
-
if (p.kind === 'shadow' && p.shadow) line.append(el('span', p.shadow.agreement === 'disagree' ? '
|
|
268
|
+
if (p.kind === 'shadow' && p.shadow) line.append(el('span', p.shadow.agreement === 'disagree' ? 'disagrees with the primary review' : p.shadow.agreement === 'agree' ? 'agrees' : '', p.shadow.agreement === 'disagree' ? 'warn' : 'muted'));
|
|
248
269
|
const fs = (e && e.findings) || (p.shadow && p.shadow.findings);
|
|
249
|
-
if (fs && fs.worst) line.append(
|
|
250
|
-
|
|
270
|
+
if (fs && fs.worst) line.append(sevChip(`${fs.worst}×${fs[fs.worst]}`, fs.worst));
|
|
271
|
+
// what it said and ran, and the diff a review read
|
|
272
|
+
const go = el('span', undefined, 'exec-links');
|
|
273
|
+
if (target && ctx.convoHash) { const a = el('a', 'conversation', 'diff-link small'); a.href = ctx.convoHash(target); go.append(a); }
|
|
274
|
+
if ((p.kind === 'qa' || p.kind === 'shadow') && p.exec && p.exec.id) { const a = diffLink(ctx, 'diff it read', { kind: 'task', task: task.id, source: p.exec.id }, 'diff-link small'); if (a) go.append(a); }
|
|
275
|
+
if (go.childElementCount) line.append(go);
|
|
251
276
|
box.append(line);
|
|
252
277
|
}
|
|
253
278
|
for (const l of tp.links.filter(l => l.kind === 'escalation' && z.parts.includes(l.to))) box.append(el('p', `escalated: ${l.label}`, 'muted'));
|
|
@@ -268,7 +293,7 @@ window.ForgePanel = (() => {
|
|
|
268
293
|
reviews.forEach((r, i) => {
|
|
269
294
|
const box = el('div', undefined, 'review');
|
|
270
295
|
const h = el('div', undefined, 'attempt-head');
|
|
271
|
-
h.append(el('strong', `${r.kind} review · attempt ${display(r.attempt)}`),
|
|
296
|
+
h.append(el('strong', `${r.kind} review · attempt ${display(r.attempt)}`), plate(r.spec), chip(r.verdict || r.state || 'unknown'));
|
|
272
297
|
box.append(h);
|
|
273
298
|
if (r.state === 'running') muted(box, 'Still reviewing; findings appear when it finishes.');
|
|
274
299
|
else findingsList(box, r.findings, `task-${task.id}-r${i}`);
|
|
@@ -284,7 +309,7 @@ window.ForgePanel = (() => {
|
|
|
284
309
|
const cs = section(host, 'Clef predictions');
|
|
285
310
|
for (const [label, p] of preds) {
|
|
286
311
|
const n = Number(String(label).match(/\d+/) || 1), outcome = list(task.attempts).filter(a => a.attempt === n).pop();
|
|
287
|
-
fields(cs, [[label, `p(pass) = ${display(p.p)} → ${outcome ? outcome.status : 'not finished'}`], ['Assignment', p.assignment ?
|
|
312
|
+
fields(cs, [[label, `p(pass) = ${display(p.p)} → ${outcome ? outcome.status : 'not finished'}`], ['Assignment', p.assignment ? plates([p.assignment.dwarf, p.assignment.qa]) : '—']]);
|
|
288
313
|
}
|
|
289
314
|
}
|
|
290
315
|
// the prompts and the final messages
|
|
@@ -308,12 +333,12 @@ window.ForgePanel = (() => {
|
|
|
308
333
|
const e = entry.data || brief;
|
|
309
334
|
const head = el('div', undefined, 'insp-head');
|
|
310
335
|
const role = e ? e.role : String(sel.exec).split('-')[0];
|
|
311
|
-
head.append(el('div', `${sel.shadow ? 'shadow ' : ''}${role === 'qa' ? 'review' : role} · attempt ${display(e && e.attempt)}`, 'insp-kind')
|
|
312
|
-
el('h2', `${task ? task.id + ' · ' : ''}${role === 'qa' ? 'reviewer' : role === 'dwarf' ? 'dwarf' : role}`));
|
|
336
|
+
head.append(el('h2', `${task ? task.id + ' · ' : ''}${role === 'qa' ? 'reviewer' : role === 'dwarf' ? 'dwarf' : role}`), el('div', `${sel.shadow ? 'shadow ' : ''}${role === 'qa' ? 'review' : role} · attempt ${display(e && e.attempt)}`, 'insp-kind'));
|
|
313
337
|
const chips = el('div', undefined, 'chips');
|
|
314
338
|
if (e) { chips.append(chip(RM.execStatus(e))); if (e.verdict) chips.append(chip('verdict ' + e.verdict, e.verdict.toLowerCase())); }
|
|
315
339
|
head.append(chips);
|
|
316
340
|
if (task) head.append(link('← task ' + task.id, ctx.hash({ kind: 'task', id: task.id }), () => ctx.onSelect({ kind: 'task', id: task.id })));
|
|
341
|
+
if ((role === 'qa' || role === 'dwarf') && ctx.convoHash) { const a = el('a', 'Its conversation: what it said and ran →', 'diff-link'); a.href = ctx.convoHash(sel); head.append(a); }
|
|
317
342
|
if (task && (role === 'qa' || role === 'dwarf')) { const a = diffLink(ctx, role === 'qa' ? 'The diff this review read →' : 'The diff its review read →', { kind: 'task', task: task.id, source: sel.exec }); if (a) head.append(a); }
|
|
318
343
|
host.append(head);
|
|
319
344
|
if (!e) { loading(host, entry, 'this execution'); return; }
|
|
@@ -367,7 +392,8 @@ window.ForgePanel = (() => {
|
|
|
367
392
|
li.append(el('i', undefined, 'led st-' + String(n.ui_status || 'unknown').toLowerCase()));
|
|
368
393
|
const target = { kind: 'fractal', task: task.id, node: n.id };
|
|
369
394
|
li.append(link(n.id, ctx.hash(target), () => ctx.onSelect(target)));
|
|
370
|
-
const bits = [n.reserved ? 'reserved' : n.status, n.difficulty, n.
|
|
395
|
+
const bits = [n.reserved ? 'reserved' : n.status, n.difficulty, n.phase ? `${n.phase}${n.iteration ? ' #' + n.iteration : ''}` : null, num(n.cost) !== null ? money(n.cost) : null].filter(Boolean);
|
|
396
|
+
if (n.model) li.append(plate(n.model));
|
|
371
397
|
li.append(el('span', bits.join(' · '), 'muted'));
|
|
372
398
|
const dec = n.decomposition;
|
|
373
399
|
if (dec && dec.decision) li.append(el('span', dec.decision === 'split' ? `split into ${list(dec.children).length}` : dec.decision, 'tag'));
|
|
@@ -383,7 +409,7 @@ window.ForgePanel = (() => {
|
|
|
383
409
|
const run = ctx.run, path = `${fractalPath(run, sel.task)}/nodes/${encodeURIComponent(sel.node)}`;
|
|
384
410
|
const entry = lazy(ctx, path, run.updated), n = entry.data;
|
|
385
411
|
const head = el('div', undefined, 'insp-head');
|
|
386
|
-
head.append(el('div', `Fractal node · task ${sel.task}`, 'insp-kind')
|
|
412
|
+
head.append(el('h2', sel.node), el('div', `Fractal node · task ${sel.task}`, 'insp-kind'));
|
|
387
413
|
const back = { kind: 'task', id: sel.task };
|
|
388
414
|
if (n) { const chips = el('div', undefined, 'chips'); chips.append(chip(n.reserved ? 'reserved' : n.ui_status || n.status, n.reserved ? 'none' : undefined)); if (n.decomposition && n.decomposition.decision) chips.append(el('span', n.decomposition.decision, 'tag')); head.append(chips); }
|
|
389
415
|
head.append(link('← task ' + sel.task, ctx.hash(back), () => ctx.onSelect(back)));
|
|
@@ -392,7 +418,7 @@ window.ForgePanel = (() => {
|
|
|
392
418
|
if (loading(host, entry, 'this node')) return;
|
|
393
419
|
if (n.goal) host.append(el('p', n.goal));
|
|
394
420
|
fields(host, [['Parent', n.parent ? link(n.parent, ctx.hash({ kind: 'fractal', task: sel.task, node: n.parent }), () => ctx.onSelect({ kind: 'fractal', task: sel.task, node: n.parent })) : '—'],
|
|
395
|
-
['Difficulty', n.difficulty], ['Model', n.model
|
|
421
|
+
['Difficulty', n.difficulty], ['Model', n.model ? plate(n.model) : '—'], ['Resolved', n.resolved_model ? plate(n.resolved_model) : '—'], ['Paths', list(n.paths).join('\n') || '—', true], ['Depends on', list(n.deps).join(', ') || '—', true],
|
|
396
422
|
['Phase', n.phase ? `${n.phase}${n.iteration ? ' · iteration ' + n.iteration : ''}` : '—'], ['Started', time(n.started_at)], ['Ended', time(n.ended_at)],
|
|
397
423
|
['Cost', `${money(n.cost)}${n.unknown_cost_steps ? ` (+${n.unknown_cost_steps} steps of unknown cost)` : ''}`], ['Tokens', n.tokens ? `${display(n.tokens.input_tokens)} in (${display(n.tokens.cached_input_tokens)} cached) · ${display(n.tokens.output_tokens)} out` : '—']]);
|
|
398
424
|
if (n.error) host.append(el('p', n.error, 'warn'));
|
|
@@ -404,7 +430,7 @@ window.ForgePanel = (() => {
|
|
|
404
430
|
const adm = list(dec.admission);
|
|
405
431
|
if (adm.length) {
|
|
406
432
|
const ul = el('ul', undefined, 'plain');
|
|
407
|
-
for (const a of adm) { const li = el('li'); li.append(link(a.name, ctx.hash({ kind: 'fractal', task: sel.task, node: a.name }), () => ctx.onSelect({ kind: 'fractal', task: sel.task, node: a.name })), document.createTextNode(` · ${a.difficulty || '—'} ·
|
|
433
|
+
for (const a of adm) { const li = el('li'); li.append(link(a.name, ctx.hash({ kind: 'fractal', task: sel.task, node: a.name }), () => ctx.onSelect({ kind: 'fractal', task: sel.task, node: a.name })), document.createTextNode(` · ${a.difficulty || '—'} · `), plate(a.model), document.createTextNode(` — ${a.goal || ''}`)); ul.append(li); }
|
|
408
434
|
ds.append(el('p', 'Children it admitted:', 'muted'), ul);
|
|
409
435
|
}
|
|
410
436
|
if (dec.error) ds.append(el('p', dec.error, 'warn'));
|
|
@@ -419,7 +445,7 @@ window.ForgePanel = (() => {
|
|
|
419
445
|
function finalView(host, ctx, sel) {
|
|
420
446
|
const run = ctx.run, reviews = list(run.final_reviews);
|
|
421
447
|
const head = el('div', undefined, 'insp-head');
|
|
422
|
-
head.append(el('div', 'finish · whole-run review', 'insp-kind')
|
|
448
|
+
head.append(el('h2', 'Final review'), el('div', 'finish · whole-run review', 'insp-kind'));
|
|
423
449
|
host.append(head);
|
|
424
450
|
const pick = sel.name ? reviews.find(r => r.name === sel.name) : reviews[0];
|
|
425
451
|
if (!pick) {
|
|
@@ -433,7 +459,7 @@ window.ForgePanel = (() => {
|
|
|
433
459
|
if (pick.published) chips.append(el('span', 'published', 'tag'));
|
|
434
460
|
chips.append(el('span', pick.source === 'integrate' ? 'during integrate' : 'forge review', 'tag'));
|
|
435
461
|
head.append(chips);
|
|
436
|
-
fields(host, [['Reviewer', pick.spec
|
|
462
|
+
fields(host, [['Reviewer', plate(pick.spec)], ['Threshold', pick.threshold], ['Reviewed', pick.base || pick.tip ? `${String(pick.base || '?').slice(0, 10)}..${String(pick.tip || '?').slice(0, 10)}` : '—', true],
|
|
437
463
|
['Started', time(pick.started ?? pick.at)], ['Ended', time(pick.ended)], ['Duration', RM.fmtDur(pick.duration_s)]]);
|
|
438
464
|
if (pick.execution) { const s = { kind: 'execution', task: null, exec: pick.execution }; host.append(link('Model, timings and exact prompt →', ctx.hash(s), () => ctx.onSelect(s))); }
|
|
439
465
|
{ const a = diffLink(ctx, 'The combined diff it read, findings pinned →', { kind: 'final', name: pick.name }); if (a) { host.append(el('br')); host.append(a); } }
|
|
@@ -457,7 +483,7 @@ window.ForgePanel = (() => {
|
|
|
457
483
|
for (const r of reviews) {
|
|
458
484
|
const li = el('li');
|
|
459
485
|
li.append(link(`${time(r.started ?? r.at)} · ${r.verdict || r.state}`, ctx.hash({ kind: 'final', name: r.name }), () => ctx.onSelect({ kind: 'final', name: r.name })));
|
|
460
|
-
if (r.findings && r.findings.worst) li.append(document.createTextNode(' '),
|
|
486
|
+
if (r.findings && r.findings.worst) li.append(document.createTextNode(' '), sevChip(`${r.findings.worst}×${r.findings[r.findings.worst]}`, r.findings.worst));
|
|
461
487
|
if (r.name === pick.name) li.className = 'current';
|
|
462
488
|
ul.append(li);
|
|
463
489
|
}
|
|
@@ -468,7 +494,7 @@ window.ForgePanel = (() => {
|
|
|
468
494
|
function verificationView(host, ctx) {
|
|
469
495
|
const run = ctx.run, v = run.verification;
|
|
470
496
|
const head = el('div', undefined, 'insp-head');
|
|
471
|
-
head.append(el('
|
|
497
|
+
head.append(el('h2', 'Verification'), el('div', 'finish', 'insp-kind'), chip(v && v.status || 'pending'));
|
|
472
498
|
host.append(head);
|
|
473
499
|
if (!v) { muted(host, 'Verification has not run: it runs after every task is merged into the integration branch.'); return; }
|
|
474
500
|
fields(host, [['Command', v.command, true], ['Commit', v.commit, true]]);
|
|
@@ -484,7 +510,7 @@ window.ForgePanel = (() => {
|
|
|
484
510
|
const run = ctx.run, ig = run.integration || {};
|
|
485
511
|
const F = RM.finishState(run);
|
|
486
512
|
const head = el('div', undefined, 'insp-head');
|
|
487
|
-
head.append(el('
|
|
513
|
+
head.append(el('h2', 'Integration'), el('div', 'finish', 'insp-kind'), chip(F.integration.status));
|
|
488
514
|
host.append(head);
|
|
489
515
|
fields(host, [['Merged', `${F.integration.merged} of ${F.integration.total} tasks`], ['Base', ig.base, true], ['Accepted at', ig.accepted, true]]);
|
|
490
516
|
if (list(ig.merged).length) {
|
|
@@ -509,12 +535,22 @@ window.ForgePanel = (() => {
|
|
|
509
535
|
host.replaceChildren();
|
|
510
536
|
const run = ctx.run, sel = ctx.sel;
|
|
511
537
|
if (!run) return;
|
|
512
|
-
const close = sel && !(run.kind === 'solo' && sel.kind === 'task') ? link('
|
|
513
|
-
if (close) {
|
|
538
|
+
const close = sel && !(run.kind === 'solo' && sel.kind === 'task') ? link('', ctx.hash(null), () => ctx.onSelect(null)) : null;
|
|
539
|
+
if (close) {
|
|
540
|
+
close.className = 'insp-close button'; close.textContent = '';
|
|
541
|
+
if (window.ForgeIcons) close.append(window.ForgeIcons.icon('close'));
|
|
542
|
+
close.append(document.createTextNode('Close')); close.title = 'Back to the run (Esc on the stage)';
|
|
543
|
+
host.append(close);
|
|
544
|
+
}
|
|
514
545
|
const tid = sel && (sel.kind === 'task' ? sel.id : sel.kind === 'execution' ? sel.task : null);
|
|
515
546
|
const task = tid ? list(run.tasks).find(t => t.id === tid) : run.kind === 'solo' && sel && sel.kind === 'execution' ? list(run.tasks)[0] : null;
|
|
516
547
|
if (!sel) return runView(host, ctx);
|
|
517
|
-
if (sel.kind === 'task') {
|
|
548
|
+
if (sel.kind === 'task') {
|
|
549
|
+
if (!task) { muted(host, `Task ${sel.id} is not in this run.`); return; }
|
|
550
|
+
taskView(host, ctx, task);
|
|
551
|
+
if (run.kind === 'solo') runRecord(host, ctx); // a solo run is its one task: its record goes below it
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
518
554
|
if (sel.kind === 'execution') return execView(host, ctx, task, sel);
|
|
519
555
|
if (sel.kind === 'final') return finalView(host, ctx, sel);
|
|
520
556
|
if (sel.kind === 'verification') return verificationView(host, ctx);
|
|
@@ -508,6 +508,22 @@
|
|
|
508
508
|
if (!s.length) return null;
|
|
509
509
|
return { start: Math.min(...s), end: Math.max(...e, ...s) };
|
|
510
510
|
}
|
|
511
|
+
// A task's route through the plan: the tasks it waits on, however indirectly (ancestors), and the tasks that wait
|
|
512
|
+
// on it (descendants). Unknown ids and dependency cycles end the walk instead of looping.
|
|
513
|
+
function lineage(run, id) {
|
|
514
|
+
const up = new Map(), down = new Map();
|
|
515
|
+
for (const t of list(run && run.tasks)) {
|
|
516
|
+
if (!t || t.id == null) continue;
|
|
517
|
+
up.set(t.id, list(t.deps));
|
|
518
|
+
for (const d of list(t.deps)) { if (!down.has(d)) down.set(d, []); down.get(d).push(t.id); }
|
|
519
|
+
}
|
|
520
|
+
const walk = next => {
|
|
521
|
+
const out = new Set(), stack = [...(next.get(id) || [])];
|
|
522
|
+
while (stack.length) { const x = stack.pop(); if (x === id || out.has(x) || !up.has(x)) continue; out.add(x); stack.push(...(next.get(x) || [])); }
|
|
523
|
+
return out;
|
|
524
|
+
};
|
|
525
|
+
return { ancestors: walk(up), descendants: walk(down) };
|
|
526
|
+
}
|
|
511
527
|
// Events worth flashing during a replay: attempts ending, escalations, merges, reviews.
|
|
512
528
|
function events(run) {
|
|
513
529
|
const out = [];
|
|
@@ -646,6 +662,6 @@
|
|
|
646
662
|
}
|
|
647
663
|
|
|
648
664
|
return { liveNow, runState, execStatus, matchRun, filterOptions, atlasModel, columns, planModel, finishState, taskParts,
|
|
649
|
-
taskTimes, metricValue, overlap, projectSchedule, execModel, criticalPath, concurrency, stateAt, timeRange, events, structureKey, innerKey,
|
|
665
|
+
taskTimes, metricValue, overlap, projectSchedule, execModel, criticalPath, concurrency, stateAt, timeRange, events, lineage, structureKey, innerKey,
|
|
650
666
|
fmtDur, alias, basename, weight, searchIndex, searchRank, statusEvents, fractalTree };
|
|
651
667
|
});
|