@giamme/forge 0.8.0 → 0.10.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 +50 -0
- package/README.md +106 -1766
- package/SKILL.md +22 -3
- package/VERSION +1 -1
- package/docs/README.md +26 -0
- package/docs/add-ons.md +33 -0
- package/docs/artifacts.md +84 -0
- package/docs/commands.md +365 -0
- package/docs/dataset.md +29 -0
- package/docs/decomposed-runs.md +310 -0
- package/docs/development.md +95 -0
- package/docs/forge-ui.md +128 -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 +245 -0
- package/docs/safety.md +55 -0
- package/docs/troubleshooting.md +54 -0
- package/forge +20 -0
- package/package.json +3 -2
- package/references/dataset.md +18 -1
- package/references/decompose.md +20 -2
- package/references/fractal.md +1 -1
- package/references/harnesses.md +18 -6
- package/references/solo.md +1 -1
- package/references/ui.md +27 -5
- package/scripts/forge-config.py +347 -0
- package/scripts/forge-control.py +645 -0
- package/scripts/forge-dispatch.sh +7 -1
- package/scripts/forge-issues.py +806 -0
- package/scripts/forge-lib-task.sh +234 -35
- package/scripts/forge-parallel-options.sh +3 -1
- package/scripts/forge-parallel.sh +3 -2
- package/scripts/forge-route.py +184 -27
- package/scripts/forge-runs-rm.py +840 -0
- package/scripts/forge-runs.py +139 -0
- package/scripts/forge-runtime.py +59 -6
- package/scripts/forge-schedule.py +227 -11
- package/scripts/forge-ui.py +85 -19
- package/scripts/forge-version.py +54 -12
- package/scripts/forge_dataset/__init__.py +77 -6
- package/scripts/forge_dataset/capture.py +5 -2
- package/scripts/forge_dataset/harvest.py +23 -1
- package/scripts/forge_dataset/importer.py +420 -18
- package/scripts/forge_fractal/__init__.py +2 -1
- package/scripts/forge_registry.py +168 -16
- package/ui/README.md +116 -23
- package/ui/THIRD_PARTY_NOTICES.md +108 -0
- package/ui/docs/api.md +283 -23
- package/ui/forge_ui/__main__.py +10 -2
- package/ui/forge_ui/control.py +575 -0
- package/ui/forge_ui/export.py +33 -9
- package/ui/forge_ui/fractal.py +1 -2
- package/ui/forge_ui/reader.py +121 -8
- package/ui/forge_ui/server.py +135 -5
- package/ui/forge_ui/static/D-DINCondensed-Bold.woff2 +0 -0
- package/ui/forge_ui/static/app.js +358 -131
- package/ui/forge_ui/static/cloud3d.js +3 -2
- package/ui/forge_ui/static/cloudui.js +8 -5
- package/ui/forge_ui/static/controls.js +619 -0
- 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/findingsview.js +412 -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 +31 -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 +92 -36
- package/ui/forge_ui/static/rundelete.js +388 -0
- 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/settings.js +399 -0
- 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 +1099 -469
- package/ui/forge_ui/static/theme.js +25 -0
- package/ui/forge_ui/static/uikit.js +161 -0
- package/ui/forge_ui/transcripts.py +608 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* forge-ui inspector: what a selected node is, which model owned it, what prompt launched it, how long it
|
|
2
2
|
took and what its reviewer found. Defines window.ForgePanel.render(host, ctx). All API values enter the
|
|
3
|
-
DOM as text. Large texts (prompts, reports) come from the lazy endpoints and are fetched once per node.
|
|
3
|
+
DOM as text. Large texts (prompts, reports) come from the lazy endpoints and are fetched once per node.
|
|
4
|
+
The run's and a task's write controls are controls.js's (ForgeControls); this file only says where they stand. */
|
|
4
5
|
window.ForgePanel = (() => {
|
|
5
6
|
'use strict';
|
|
6
7
|
const RM = window.ForgeRunModel;
|
|
@@ -9,7 +10,6 @@ window.ForgePanel = (() => {
|
|
|
9
10
|
const display = v => v === null || v === undefined || v === '' ? '—' : typeof v === 'object' ? JSON.stringify(v, null, 2) : String(v);
|
|
10
11
|
const STATUSES = new Set(['merged', 'pass', 'running', 'pending', 'fail', 'error', 'infra', 'timeout', 'unknown', 'invalidated', 'blocked',
|
|
11
12
|
'interrupted', 'nochanges', 'conflict', 'accepted', 'none']);
|
|
12
|
-
const SEV = { P0: 'fail', P1: 'conflict', P2: 'timeout', P3: 'unknown' };
|
|
13
13
|
const cache = new Map(); // path → {data, error, at}
|
|
14
14
|
function el(tag, text, className) {
|
|
15
15
|
const node = document.createElement(tag);
|
|
@@ -21,6 +21,18 @@ window.ForgePanel = (() => {
|
|
|
21
21
|
const s = String(value || 'unknown').toLowerCase();
|
|
22
22
|
return el('span', value || '—', 'chip st-' + (cls || (STATUSES.has(s) ? s : 'unknown')));
|
|
23
23
|
}
|
|
24
|
+
// a finding's severity: its own shape and ramp (style.css .sev-P0…P3), never a status lamp
|
|
25
|
+
const plate = spec => window.ForgeIcons ? window.ForgeIcons.modelPlate(spec) : el('span', spec || '—', 'mono');
|
|
26
|
+
const plates = specs => { const s = el('span', undefined, 'plates'); for (const x of specs) s.append(plate(x)); return specs.length ? s : undefined; };
|
|
27
|
+
// a long path cut in the middle: its head gives way, its last part always shows; copying takes all of it
|
|
28
|
+
function midPath(v) {
|
|
29
|
+
const s = String(v || ''), i = s.lastIndexOf('/', s.length - 2);
|
|
30
|
+
if (!s) return undefined;
|
|
31
|
+
const out = el('span', undefined, 'mid-path'); out.title = s;
|
|
32
|
+
out.append(el('span', i > 0 ? s.slice(0, i + 1) : '', 'mid-head'), el('span', i > 0 ? s.slice(i + 1) : s, 'mid-tail'));
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
const sevChip = (text, sev) => el('span', text, 'chip sev-chip sev-' + (/^P[0-3]$/.test(sev) ? sev : 'none'));
|
|
24
36
|
function time(v) {
|
|
25
37
|
if (num(v) === null) return '—';
|
|
26
38
|
const d = new Date(v * 1000); return Number.isNaN(d.getTime()) ? '—' : d.toLocaleString();
|
|
@@ -48,6 +60,15 @@ window.ForgePanel = (() => {
|
|
|
48
60
|
return a;
|
|
49
61
|
}
|
|
50
62
|
function muted(parent, text) { parent.append(el('p', text, 'muted')); }
|
|
63
|
+
// who decided an escalation: policy, a human's retry --dwarf (retry) or a pin; older rows read as policy
|
|
64
|
+
const sourcePlate = source => el('span', source || 'policy', 'plate src-plate src-' + String(source || 'policy').replace(/[^a-z]/g, ''));
|
|
65
|
+
// the write controls of a plan run (task undefined) or of one of its tasks; a page without controls.js says it is read-only
|
|
66
|
+
function controlsSection(host, ctx, task) {
|
|
67
|
+
if (ctx.run.kind === 'solo') return;
|
|
68
|
+
if (window.ForgeControls && window.ForgeUI && window.ForgeUI.confirmInline) { window.ForgeControls[task ? 'mountTask' : 'mountRun'](host, ctx, task); return; }
|
|
69
|
+
const s = section(host, 'Controls');
|
|
70
|
+
muted(s, 'Read-only: this page cannot change the run.');
|
|
71
|
+
}
|
|
51
72
|
// a link into the diff viewer (ctx.diffHash is absent where there is no viewer)
|
|
52
73
|
function diffLink(ctx, text, spec, cls = 'diff-link') { if (!ctx.diffHash) return null; const a = el('a', text, cls); a.href = ctx.diffHash(spec); return a; }
|
|
53
74
|
// a lazy resource: returns cached data, or starts the fetch and re-renders when it lands
|
|
@@ -87,7 +108,7 @@ window.ForgePanel = (() => {
|
|
|
87
108
|
const row = el('div', undefined, 'chips');
|
|
88
109
|
if (!f) { row.append(el('span', 'no findings parsed', 'muted')); parent.append(row); return; }
|
|
89
110
|
let any = false;
|
|
90
|
-
for (const sev of ['P0', 'P1', 'P2', 'P3']) if (f[sev]) { row.append(
|
|
111
|
+
for (const sev of ['P0', 'P1', 'P2', 'P3']) if (f[sev]) { row.append(sevChip(`${sev} × ${f[sev]}`, sev)); any = true; }
|
|
91
112
|
if (f.unlabelled) { row.append(chip(`unlabelled × ${f.unlabelled}`, 'unknown')); any = true; }
|
|
92
113
|
if (f.plausible) { row.append(chip(`plausible × ${f.plausible}`, 'pending')); any = true; }
|
|
93
114
|
if (!any) row.append(el('span', 'no confirmed findings', 'muted'));
|
|
@@ -124,7 +145,7 @@ window.ForgePanel = (() => {
|
|
|
124
145
|
findings.slice().sort((a, b) => order(a) - order(b)).forEach((f, i) => {
|
|
125
146
|
const li = el('li', undefined, 'finding' + (f.status === 'CONFIRMED' ? '' : ' plausible'));
|
|
126
147
|
const head = el('div', undefined, 'finding-head');
|
|
127
|
-
head.append(
|
|
148
|
+
head.append(sevChip(f.severity || 'P?', f.severity), el('span', f.status, 'tag'));
|
|
128
149
|
if (f.location) head.append(el('code', f.location));
|
|
129
150
|
li.append(head, el('p', f.summary || ''));
|
|
130
151
|
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 +180,26 @@ window.ForgePanel = (() => {
|
|
|
159
180
|
const tokens = t => t ? `${display(t.input)} in (${display(t.cached)} cached) · ${display(t.output)} out` : '—';
|
|
160
181
|
function modelFields(m) {
|
|
161
182
|
if (!m) return [['Model', '—']];
|
|
162
|
-
return [['Spec', m.spec
|
|
183
|
+
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
184
|
}
|
|
164
185
|
|
|
165
186
|
/* ---------- views ---------- */
|
|
187
|
+
// the run's record: what it was asked, how it was set up, and what verification printed (the header names and counts it)
|
|
188
|
+
function runRecord(host, ctx) {
|
|
189
|
+
const run = ctx.run, solo = run.kind === 'solo';
|
|
190
|
+
const s = section(host, 'Settings');
|
|
191
|
+
fields(s, [['Repo', midPath(run.repo), true], ['Run ID', run.run_id, true], ['Path', midPath(run.path), true], ['Source', run.source],
|
|
192
|
+
['Decompose', run.decompose_level], ['Updated', time(run.updated)], ['Explore', run.settings && run.settings.explore], ['Shadow rate', run.settings && run.settings.shadow_rate],
|
|
193
|
+
['Shadow reviewers', run.settings && plates(list(run.settings.shadow_qa))], ['QA threshold', run.settings && run.settings.qa_threshold],
|
|
194
|
+
['Max parallel', run.settings && run.settings.max_parallel], ['Final reviewer', run.settings && run.settings.final_qa ? plate(run.settings.final_qa) : undefined],
|
|
195
|
+
['Timeout (seconds)', run.settings && run.settings.timeout], ['Verification', chip((run.verification && run.verification.status) || run.verification_status)],
|
|
196
|
+
['Command', run.verification && run.verification.command, true]]);
|
|
197
|
+
details(host, 'Request', display(run.request), 'run-request');
|
|
198
|
+
if (!solo) details(host, 'Waves', list(run.waves).map((ids, i) => `${i + 1}: ${list(ids).join(', ')}`).join('\n') || '—', 'waves');
|
|
199
|
+
if (run.verification && run.verification.log_tail != null) details(host, 'Verification output', run.verification.log_tail, 'verification-output');
|
|
200
|
+
}
|
|
166
201
|
function runView(host, ctx) {
|
|
167
202
|
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
203
|
const tr = RM.timeRange(run, now);
|
|
174
204
|
const s = section(host, 'Timeline');
|
|
175
205
|
fields(s, [['Started', time(tr && tr.start)], ['Last activity', time(tr && tr.end)], ['Wall time', tr ? RM.fmtDur(tr.end - tr.start) : '—'],
|
|
@@ -196,13 +226,15 @@ window.ForgePanel = (() => {
|
|
|
196
226
|
if (F.final.findings) findingCounts(f, F.final.findings);
|
|
197
227
|
}
|
|
198
228
|
if (list(run.deferrals).length) details(host, `Deferred tasks (${run.deferrals.length})`, run.deferrals.join('\n'), 'deferrals');
|
|
229
|
+
controlsSection(host, ctx);
|
|
230
|
+
runRecord(host, ctx);
|
|
199
231
|
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
232
|
}
|
|
201
233
|
|
|
202
234
|
function taskView(host, ctx, task) {
|
|
203
235
|
const run = ctx.run, now = ctx.now, solo = run.kind === 'solo';
|
|
204
236
|
const head = el('div', undefined, 'insp-head');
|
|
205
|
-
head.append(el('
|
|
237
|
+
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
238
|
const chips = el('div', undefined, 'chips');
|
|
207
239
|
chips.append(chip(task.status));
|
|
208
240
|
if (task.verdict) chips.append(chip('review ' + task.verdict, task.verdict.toLowerCase()));
|
|
@@ -215,10 +247,14 @@ window.ForgePanel = (() => {
|
|
|
215
247
|
|
|
216
248
|
// the models: who owned it, how it was routed, what it was escalated to
|
|
217
249
|
const ms = section(host, 'Models');
|
|
218
|
-
fields(ms, [['Dwarf', task.dwarf
|
|
250
|
+
fields(ms, [['Dwarf', plate(task.dwarf)], ['Reviewer', plate(task.qa)], ['Attempt', task.attempt]]);
|
|
219
251
|
const route = list(task.route).slice(-1)[0];
|
|
220
252
|
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
|
-
for (const e of list(task.escalations))
|
|
253
|
+
for (const e of list(task.escalations)) {
|
|
254
|
+
const line = el('p', `Escalated after attempt ${display(e.attempt)}: ${display(e.from)} → ${display(e.to)} at ${time(e.at)} `, 'muted');
|
|
255
|
+
line.append(sourcePlate(e.source)); ms.append(line);
|
|
256
|
+
}
|
|
257
|
+
controlsSection(host, ctx, task);
|
|
222
258
|
|
|
223
259
|
// timings
|
|
224
260
|
const tt = RM.taskTimes(task, run, now);
|
|
@@ -229,7 +265,7 @@ window.ForgePanel = (() => {
|
|
|
229
265
|
|
|
230
266
|
// attempts, each with its dwarf and its review
|
|
231
267
|
const as = section(host, 'Attempts');
|
|
232
|
-
const tp = RM.taskParts(task);
|
|
268
|
+
const tp = RM.taskParts(task), usedEscalations = new Set();
|
|
233
269
|
if (!list(task.executions).length) muted(as, 'No executions recorded yet' + (task.status === 'PENDING' ? ': this task has not started.' : '.'));
|
|
234
270
|
for (const z of tp.zones) {
|
|
235
271
|
const box = el('div', undefined, 'attempt');
|
|
@@ -240,17 +276,26 @@ window.ForgePanel = (() => {
|
|
|
240
276
|
const p = tp.parts.find(x => x.id === pid); if (!p || p.planned) continue;
|
|
241
277
|
const line = el('div', undefined, 'exec-line');
|
|
242
278
|
const e = p.exec && list(task.executions).find(x => x.id === p.exec.id) || null;
|
|
243
|
-
const label =
|
|
279
|
+
const label = p.label.toLowerCase();
|
|
244
280
|
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));
|
|
281
|
+
line.append(target ? link(label, ctx.hash(target), () => ctx.onSelect(target)) : el('span', label), plate(p.spec), chip(p.status));
|
|
246
282
|
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' ? '
|
|
283
|
+
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
284
|
const fs = (e && e.findings) || (p.shadow && p.shadow.findings);
|
|
249
|
-
if (fs && fs.worst) line.append(
|
|
250
|
-
|
|
285
|
+
if (fs && fs.worst) line.append(sevChip(`${fs.worst}×${fs[fs.worst]}`, fs.worst));
|
|
286
|
+
// what it said and ran, and the diff a review read
|
|
287
|
+
const go = el('span', undefined, 'exec-links');
|
|
288
|
+
if (target && ctx.convoHash) { const a = el('a', 'conversation', 'diff-link small'); a.href = ctx.convoHash(target); go.append(a); }
|
|
289
|
+
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); }
|
|
290
|
+
if (go.childElementCount) line.append(go);
|
|
291
|
+
box.append(line);
|
|
292
|
+
}
|
|
293
|
+
for (const l of tp.links.filter(l => l.kind === 'escalation' && z.parts.includes(l.to))) {
|
|
294
|
+
const line = el('p', `escalated: ${l.label} `, 'muted');
|
|
295
|
+
const e = list(task.escalations).find(x => !usedEscalations.has(x) && `${x.from || '?'} → ${x.to || '?'}` === l.label); // the link carries only its label
|
|
296
|
+
if (e) { usedEscalations.add(e); line.append(sourcePlate(e.source)); }
|
|
251
297
|
box.append(line);
|
|
252
298
|
}
|
|
253
|
-
for (const l of tp.links.filter(l => l.kind === 'escalation' && z.parts.includes(l.to))) box.append(el('p', `escalated: ${l.label}`, 'muted'));
|
|
254
299
|
as.append(box);
|
|
255
300
|
}
|
|
256
301
|
|
|
@@ -268,7 +313,7 @@ window.ForgePanel = (() => {
|
|
|
268
313
|
reviews.forEach((r, i) => {
|
|
269
314
|
const box = el('div', undefined, 'review');
|
|
270
315
|
const h = el('div', undefined, 'attempt-head');
|
|
271
|
-
h.append(el('strong', `${r.kind} review · attempt ${display(r.attempt)}`),
|
|
316
|
+
h.append(el('strong', `${r.kind} review · attempt ${display(r.attempt)}`), plate(r.spec), chip(r.verdict || r.state || 'unknown'));
|
|
272
317
|
box.append(h);
|
|
273
318
|
if (r.state === 'running') muted(box, 'Still reviewing; findings appear when it finishes.');
|
|
274
319
|
else findingsList(box, r.findings, `task-${task.id}-r${i}`);
|
|
@@ -284,7 +329,7 @@ window.ForgePanel = (() => {
|
|
|
284
329
|
const cs = section(host, 'Clef predictions');
|
|
285
330
|
for (const [label, p] of preds) {
|
|
286
331
|
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 ?
|
|
332
|
+
fields(cs, [[label, `p(pass) = ${display(p.p)} → ${outcome ? outcome.status : 'not finished'}`], ['Assignment', p.assignment ? plates([p.assignment.dwarf, p.assignment.qa]) : '—']]);
|
|
288
333
|
}
|
|
289
334
|
}
|
|
290
335
|
// the prompts and the final messages
|
|
@@ -308,12 +353,12 @@ window.ForgePanel = (() => {
|
|
|
308
353
|
const e = entry.data || brief;
|
|
309
354
|
const head = el('div', undefined, 'insp-head');
|
|
310
355
|
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}`));
|
|
356
|
+
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
357
|
const chips = el('div', undefined, 'chips');
|
|
314
358
|
if (e) { chips.append(chip(RM.execStatus(e))); if (e.verdict) chips.append(chip('verdict ' + e.verdict, e.verdict.toLowerCase())); }
|
|
315
359
|
head.append(chips);
|
|
316
360
|
if (task) head.append(link('← task ' + task.id, ctx.hash({ kind: 'task', id: task.id }), () => ctx.onSelect({ kind: 'task', id: task.id })));
|
|
361
|
+
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
362
|
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
363
|
host.append(head);
|
|
319
364
|
if (!e) { loading(host, entry, 'this execution'); return; }
|
|
@@ -367,7 +412,8 @@ window.ForgePanel = (() => {
|
|
|
367
412
|
li.append(el('i', undefined, 'led st-' + String(n.ui_status || 'unknown').toLowerCase()));
|
|
368
413
|
const target = { kind: 'fractal', task: task.id, node: n.id };
|
|
369
414
|
li.append(link(n.id, ctx.hash(target), () => ctx.onSelect(target)));
|
|
370
|
-
const bits = [n.reserved ? 'reserved' : n.status, n.difficulty, n.
|
|
415
|
+
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);
|
|
416
|
+
if (n.model) li.append(plate(n.model));
|
|
371
417
|
li.append(el('span', bits.join(' · '), 'muted'));
|
|
372
418
|
const dec = n.decomposition;
|
|
373
419
|
if (dec && dec.decision) li.append(el('span', dec.decision === 'split' ? `split into ${list(dec.children).length}` : dec.decision, 'tag'));
|
|
@@ -383,7 +429,7 @@ window.ForgePanel = (() => {
|
|
|
383
429
|
const run = ctx.run, path = `${fractalPath(run, sel.task)}/nodes/${encodeURIComponent(sel.node)}`;
|
|
384
430
|
const entry = lazy(ctx, path, run.updated), n = entry.data;
|
|
385
431
|
const head = el('div', undefined, 'insp-head');
|
|
386
|
-
head.append(el('div', `Fractal node · task ${sel.task}`, 'insp-kind')
|
|
432
|
+
head.append(el('h2', sel.node), el('div', `Fractal node · task ${sel.task}`, 'insp-kind'));
|
|
387
433
|
const back = { kind: 'task', id: sel.task };
|
|
388
434
|
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
435
|
head.append(link('← task ' + sel.task, ctx.hash(back), () => ctx.onSelect(back)));
|
|
@@ -392,7 +438,7 @@ window.ForgePanel = (() => {
|
|
|
392
438
|
if (loading(host, entry, 'this node')) return;
|
|
393
439
|
if (n.goal) host.append(el('p', n.goal));
|
|
394
440
|
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
|
|
441
|
+
['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
442
|
['Phase', n.phase ? `${n.phase}${n.iteration ? ' · iteration ' + n.iteration : ''}` : '—'], ['Started', time(n.started_at)], ['Ended', time(n.ended_at)],
|
|
397
443
|
['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
444
|
if (n.error) host.append(el('p', n.error, 'warn'));
|
|
@@ -404,7 +450,7 @@ window.ForgePanel = (() => {
|
|
|
404
450
|
const adm = list(dec.admission);
|
|
405
451
|
if (adm.length) {
|
|
406
452
|
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 || '—'} ·
|
|
453
|
+
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
454
|
ds.append(el('p', 'Children it admitted:', 'muted'), ul);
|
|
409
455
|
}
|
|
410
456
|
if (dec.error) ds.append(el('p', dec.error, 'warn'));
|
|
@@ -419,7 +465,7 @@ window.ForgePanel = (() => {
|
|
|
419
465
|
function finalView(host, ctx, sel) {
|
|
420
466
|
const run = ctx.run, reviews = list(run.final_reviews);
|
|
421
467
|
const head = el('div', undefined, 'insp-head');
|
|
422
|
-
head.append(el('div', 'finish · whole-run review', 'insp-kind')
|
|
468
|
+
head.append(el('h2', 'Final review'), el('div', 'finish · whole-run review', 'insp-kind'));
|
|
423
469
|
host.append(head);
|
|
424
470
|
const pick = sel.name ? reviews.find(r => r.name === sel.name) : reviews[0];
|
|
425
471
|
if (!pick) {
|
|
@@ -433,7 +479,7 @@ window.ForgePanel = (() => {
|
|
|
433
479
|
if (pick.published) chips.append(el('span', 'published', 'tag'));
|
|
434
480
|
chips.append(el('span', pick.source === 'integrate' ? 'during integrate' : 'forge review', 'tag'));
|
|
435
481
|
head.append(chips);
|
|
436
|
-
fields(host, [['Reviewer', pick.spec
|
|
482
|
+
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
483
|
['Started', time(pick.started ?? pick.at)], ['Ended', time(pick.ended)], ['Duration', RM.fmtDur(pick.duration_s)]]);
|
|
438
484
|
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
485
|
{ 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 +503,7 @@ window.ForgePanel = (() => {
|
|
|
457
503
|
for (const r of reviews) {
|
|
458
504
|
const li = el('li');
|
|
459
505
|
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(' '),
|
|
506
|
+
if (r.findings && r.findings.worst) li.append(document.createTextNode(' '), sevChip(`${r.findings.worst}×${r.findings[r.findings.worst]}`, r.findings.worst));
|
|
461
507
|
if (r.name === pick.name) li.className = 'current';
|
|
462
508
|
ul.append(li);
|
|
463
509
|
}
|
|
@@ -468,7 +514,7 @@ window.ForgePanel = (() => {
|
|
|
468
514
|
function verificationView(host, ctx) {
|
|
469
515
|
const run = ctx.run, v = run.verification;
|
|
470
516
|
const head = el('div', undefined, 'insp-head');
|
|
471
|
-
head.append(el('
|
|
517
|
+
head.append(el('h2', 'Verification'), el('div', 'finish', 'insp-kind'), chip(v && v.status || 'pending'));
|
|
472
518
|
host.append(head);
|
|
473
519
|
if (!v) { muted(host, 'Verification has not run: it runs after every task is merged into the integration branch.'); return; }
|
|
474
520
|
fields(host, [['Command', v.command, true], ['Commit', v.commit, true]]);
|
|
@@ -484,7 +530,7 @@ window.ForgePanel = (() => {
|
|
|
484
530
|
const run = ctx.run, ig = run.integration || {};
|
|
485
531
|
const F = RM.finishState(run);
|
|
486
532
|
const head = el('div', undefined, 'insp-head');
|
|
487
|
-
head.append(el('
|
|
533
|
+
head.append(el('h2', 'Integration'), el('div', 'finish', 'insp-kind'), chip(F.integration.status));
|
|
488
534
|
host.append(head);
|
|
489
535
|
fields(host, [['Merged', `${F.integration.merged} of ${F.integration.total} tasks`], ['Base', ig.base, true], ['Accepted at', ig.accepted, true]]);
|
|
490
536
|
if (list(ig.merged).length) {
|
|
@@ -509,12 +555,22 @@ window.ForgePanel = (() => {
|
|
|
509
555
|
host.replaceChildren();
|
|
510
556
|
const run = ctx.run, sel = ctx.sel;
|
|
511
557
|
if (!run) return;
|
|
512
|
-
const close = sel && !(run.kind === 'solo' && sel.kind === 'task') ? link('
|
|
513
|
-
if (close) {
|
|
558
|
+
const close = sel && !(run.kind === 'solo' && sel.kind === 'task') ? link('', ctx.hash(null), () => ctx.onSelect(null)) : null;
|
|
559
|
+
if (close) {
|
|
560
|
+
close.className = 'insp-close button'; close.textContent = '';
|
|
561
|
+
if (window.ForgeIcons) close.append(window.ForgeIcons.icon('close'));
|
|
562
|
+
close.append(document.createTextNode('Close')); close.title = 'Back to the run (Esc on the stage)';
|
|
563
|
+
host.append(close);
|
|
564
|
+
}
|
|
514
565
|
const tid = sel && (sel.kind === 'task' ? sel.id : sel.kind === 'execution' ? sel.task : null);
|
|
515
566
|
const task = tid ? list(run.tasks).find(t => t.id === tid) : run.kind === 'solo' && sel && sel.kind === 'execution' ? list(run.tasks)[0] : null;
|
|
516
567
|
if (!sel) return runView(host, ctx);
|
|
517
|
-
if (sel.kind === 'task') {
|
|
568
|
+
if (sel.kind === 'task') {
|
|
569
|
+
if (!task) { muted(host, `Task ${sel.id} is not in this run.`); return; }
|
|
570
|
+
taskView(host, ctx, task);
|
|
571
|
+
if (run.kind === 'solo') runRecord(host, ctx); // a solo run is its one task: its record goes below it
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
518
574
|
if (sel.kind === 'execution') return execView(host, ctx, task, sel);
|
|
519
575
|
if (sel.kind === 'final') return finalView(host, ctx, sel);
|
|
520
576
|
if (sel.kind === 'verification') return verificationView(host, ctx);
|