@contraagent/ui 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/custom-elements.json +104 -78
- package/dist/adapters/journal-columns.d.ts +2 -0
- package/dist/adapters/journal-columns.d.ts.map +1 -1
- package/dist/adapters/journal-columns.js +18 -5
- package/dist/adapters/journal-columns.js.map +1 -1
- package/dist/adapters/kpi-tiles.d.ts +8 -3
- package/dist/adapters/kpi-tiles.d.ts.map +1 -1
- package/dist/adapters/kpi-tiles.js +35 -15
- package/dist/adapters/kpi-tiles.js.map +1 -1
- package/dist/adapters/transcript/sections.d.ts +5 -0
- package/dist/adapters/transcript/sections.d.ts.map +1 -1
- package/dist/adapters/transcript/sections.js +7 -1
- package/dist/adapters/transcript/sections.js.map +1 -1
- package/dist/contraagent-ui.js +689 -700
- package/dist/elements/lists/ca-contracts.d.ts +3 -4
- package/dist/elements/lists/ca-contracts.d.ts.map +1 -1
- package/dist/elements/lists/ca-contracts.js +25 -19
- package/dist/elements/lists/ca-contracts.js.map +1 -1
- package/dist/elements/overview/ca-kpis.d.ts +3 -1
- package/dist/elements/overview/ca-kpis.d.ts.map +1 -1
- package/dist/elements/overview/ca-kpis.js +7 -2
- package/dist/elements/overview/ca-kpis.js.map +1 -1
- package/dist/elements/overview/ca-model-usage.d.ts +2 -1
- package/dist/elements/overview/ca-model-usage.d.ts.map +1 -1
- package/dist/elements/overview/ca-model-usage.js +3 -2
- package/dist/elements/overview/ca-model-usage.js.map +1 -1
- package/dist/elements/overview/ca-overview.d.ts +1 -5
- package/dist/elements/overview/ca-overview.d.ts.map +1 -1
- package/dist/elements/overview/ca-overview.js +53 -77
- package/dist/elements/overview/ca-overview.js.map +1 -1
- package/dist/elements/overview/ca-pipeline-graph.d.ts +8 -1
- package/dist/elements/overview/ca-pipeline-graph.d.ts.map +1 -1
- package/dist/elements/overview/ca-pipeline-graph.js +11 -6
- package/dist/elements/overview/ca-pipeline-graph.js.map +1 -1
- package/dist/elements/overview/ca-run-driver.d.ts.map +1 -1
- package/dist/elements/overview/ca-run-driver.js +6 -5
- package/dist/elements/overview/ca-run-driver.js.map +1 -1
- package/dist/elements/overview/pipeline-graph/strings.js +3 -3
- package/dist/elements/overview/pipeline-graph/strings.js.map +1 -1
- package/dist/elements/shared/ca-definition.d.ts.map +1 -1
- package/dist/elements/shared/ca-definition.js +1 -2
- package/dist/elements/shared/ca-definition.js.map +1 -1
- package/dist/elements/shell/ca-hosts.d.ts.map +1 -1
- package/dist/elements/shell/ca-hosts.js +1 -2
- package/dist/elements/shell/ca-hosts.js.map +1 -1
- package/dist/elements/shell/ca-monitor.d.ts +3 -1
- package/dist/elements/shell/ca-monitor.d.ts.map +1 -1
- package/dist/elements/shell/ca-monitor.js +20 -9
- package/dist/elements/shell/ca-monitor.js.map +1 -1
- package/dist/elements/shell/ca-run-picker.js +1 -1
- package/dist/elements/shell/ca-run-picker.js.map +1 -1
- package/dist/elements/shell/ca-theme-switch.d.ts.map +1 -1
- package/dist/elements/shell/ca-theme-switch.js +6 -1
- package/dist/elements/shell/ca-theme-switch.js.map +1 -1
- package/dist/elements/task/ca-task-view.js +2 -2
- package/dist/elements/task/ca-task-view.js.map +1 -1
- package/dist/elements/task/ca-transcript.d.ts +3 -0
- package/dist/elements/task/ca-transcript.d.ts.map +1 -1
- package/dist/elements/task/ca-transcript.js +9 -2
- package/dist/elements/task/ca-transcript.js.map +1 -1
- package/dist/elements/task/header.d.ts +3 -0
- package/dist/elements/task/header.d.ts.map +1 -1
- package/dist/elements/task/header.js +21 -12
- package/dist/elements/task/header.js.map +1 -1
- package/dist/elements/task/panes/io.d.ts +1 -1
- package/dist/elements/task/panes/io.d.ts.map +1 -1
- package/dist/elements/task/panes/io.js +14 -21
- package/dist/elements/task/panes/io.js.map +1 -1
- package/dist/elements/trends/ca-trends.d.ts.map +1 -1
- package/dist/elements/trends/ca-trends.js +2 -1
- package/dist/elements/trends/ca-trends.js.map +1 -1
- package/dist/locales/ja.d.ts +14 -27
- package/dist/locales/ja.d.ts.map +1 -1
- package/dist/locales/ja.js +14 -27
- package/dist/locales/ja.js.map +1 -1
- package/dist/words/quota.d.ts +2 -0
- package/dist/words/quota.d.ts.map +1 -1
- package/dist/words/quota.js +4 -0
- package/dist/words/quota.js.map +1 -1
- package/dist/words/run-line.d.ts +5 -3
- package/dist/words/run-line.d.ts.map +1 -1
- package/dist/words/run-line.js +6 -4
- package/dist/words/run-line.js.map +1 -1
- package/dist/words/trends.d.ts +2 -2
- package/dist/words/trends.d.ts.map +1 -1
- package/dist/words/trends.js +5 -3
- package/dist/words/trends.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/journal-columns.ts +19 -5
- package/src/adapters/kpi-tiles.ts +37 -16
- package/src/adapters/transcript/sections.ts +10 -2
- package/src/elements/lists/ca-contracts.ts +25 -19
- package/src/elements/overview/ca-kpis.ts +4 -2
- package/src/elements/overview/ca-model-usage.ts +3 -2
- package/src/elements/overview/ca-overview.ts +49 -79
- package/src/elements/overview/ca-pipeline-graph.ts +11 -6
- package/src/elements/overview/ca-run-driver.ts +5 -6
- package/src/elements/overview/pipeline-graph/strings.ts +3 -3
- package/src/elements/shared/ca-definition.ts +1 -2
- package/src/elements/shell/ca-hosts.ts +1 -2
- package/src/elements/shell/ca-monitor.ts +19 -8
- package/src/elements/shell/ca-run-picker.ts +1 -1
- package/src/elements/shell/ca-theme-switch.ts +6 -1
- package/src/elements/task/ca-task-view.ts +2 -2
- package/src/elements/task/ca-transcript.ts +7 -2
- package/src/elements/task/header.ts +23 -13
- package/src/elements/task/panes/io.ts +14 -21
- package/src/elements/trends/ca-trends.ts +2 -1
- package/src/locales/ja.ts +14 -27
- package/src/words/quota.ts +6 -0
- package/src/words/run-line.ts +8 -5
- package/src/words/trends.ts +5 -3
|
@@ -36,9 +36,19 @@ export function shortStop(stop: string | null | undefined): string {
|
|
|
36
36
|
return stop === 'invalid_input' ? outcomeLabel('not_started') : stopLabel(stop);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/** The
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
/** The identities an attempt authenticated as, in order; both when a model switch changed it. */
|
|
40
|
+
const identities = (e: JournalRow): string[] => [...new Set([...(e.hops ?? []).map((h) => h.credential), e.credential].filter((c): c is string => !!c))];
|
|
41
|
+
|
|
42
|
+
/** The one identity every attempt that ran authenticated as alone; '' when they differ or none is known. */
|
|
43
|
+
export function sharedIdentity(journal: readonly JournalRow[]): string {
|
|
44
|
+
const seen = new Set(journal.filter(isLaunched).map((e) => identities(e).join('\n')));
|
|
45
|
+
const [only] = seen;
|
|
46
|
+
return seen.size === 1 && only && !only.includes('\n') ? only : '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The identity the attempt authenticated as (both when a model switch changed it), unless `shared`: then the column's header names it. */
|
|
50
|
+
function identity(e: JournalRow, shared: string) {
|
|
51
|
+
const ids = identities(e);
|
|
42
52
|
if (!ids.length) return nothing;
|
|
43
53
|
// The subscription usage the attempt's first session started at, when the agent reported one.
|
|
44
54
|
const start = (e.quota ?? [])[0];
|
|
@@ -47,6 +57,7 @@ function identity(e: JournalRow) {
|
|
|
47
57
|
const own = start && ids.length === 1 && (start.credential ?? '') === ids[0];
|
|
48
58
|
const shown = start ? (own ? readingShort(start) : accountReading(start)) : '';
|
|
49
59
|
const tips = [tip, ...ids.map(credentialTip)].filter(Boolean).join('\n');
|
|
60
|
+
if (shared) return start ? html`<span class="sub" title=${tips || nothing}><span class="q">${shown}</span></span>` : nothing;
|
|
50
61
|
return html`<span class="sub" title=${tips || nothing}>${ids.map((c) => credentialLabel(c)).join(' → ')}${start ? html` <span class="q">${shown}</span>` : nothing}</span>`;
|
|
51
62
|
}
|
|
52
63
|
|
|
@@ -55,6 +66,8 @@ export function journalColumns(ctx: JournalColumnsContext): DataColumn<JournalRo
|
|
|
55
66
|
const maxWall = Math.max(1, ...ctx.rows.map((e) => e.wall_s || 0));
|
|
56
67
|
const tries = attemptFacts(ctx.journal);
|
|
57
68
|
const index = new Map(ctx.journal.map((r, i) => [r, i]));
|
|
69
|
+
// Every attempt ran as one identity: the header names it once, the rows do not.
|
|
70
|
+
const shared = sharedIdentity(ctx.journal);
|
|
58
71
|
/** The attempt that later did the task, after this one failed (0: none). */
|
|
59
72
|
const laterTry = (e: JournalRow): number => {
|
|
60
73
|
const f = tries.get(e.key);
|
|
@@ -133,11 +146,12 @@ export function journalColumns(ctx: JournalColumnsContext): DataColumn<JournalRo
|
|
|
133
146
|
},
|
|
134
147
|
{
|
|
135
148
|
id: 'agent',
|
|
136
|
-
header: msg('Agent, model, identity'),
|
|
149
|
+
header: shared ? msg('Agent, model') : msg('Agent, model, identity'),
|
|
150
|
+
...(shared ? { tip: [msg(str`Identity: ${credentialLabel(shared)}`), credentialTip(shared)].filter(Boolean).join('\n') } : {}),
|
|
137
151
|
width: 'minmax(150px,1fr)',
|
|
138
152
|
cell: (e) =>
|
|
139
153
|
html`<span class="jmono jdim two" title=${facts(e.route, modelSwitch(e.hops, null, e.skipped_hops)) || nothing}>${isLaunched(e)
|
|
140
|
-
? html`<span>${[e.agent, e.model].filter(Boolean).join(' / ') || '—'}</span>${identity(e)}`
|
|
154
|
+
? html`<span>${[e.agent, e.model].filter(Boolean).join(' / ') || '—'}</span>${identity(e, shared)}`
|
|
141
155
|
: '—'}</span>`,
|
|
142
156
|
},
|
|
143
157
|
{
|
|
@@ -31,9 +31,10 @@ export interface Kpis {
|
|
|
31
31
|
/** Tries after a task's first, and the tasks they belong to. */
|
|
32
32
|
retries: number;
|
|
33
33
|
retriedTasks: number;
|
|
34
|
-
/** The keys of the tasks that were retried / had fix-ups (to open one directly when it is the only one). */
|
|
34
|
+
/** The keys of the tasks that were retried / had fix-ups / switched models (to name or open one directly when it is the only one). */
|
|
35
35
|
retriedKeys: string[];
|
|
36
36
|
fixupKeys: string[];
|
|
37
|
+
switchKeys: string[];
|
|
37
38
|
/** Fix-ups (sessions resumed with check errors), and the tasks that had any. */
|
|
38
39
|
fixups: number;
|
|
39
40
|
fixupTasks: number;
|
|
@@ -67,6 +68,7 @@ export function kpis(journal: readonly JournalRow[]): Kpis {
|
|
|
67
68
|
fixupTasks: tasks(fixed),
|
|
68
69
|
switches: switched.reduce((a, e) => a + (e.hops || []).length - 1, 0),
|
|
69
70
|
switchTasks: tasks(switched),
|
|
71
|
+
switchKeys: keys(switched),
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
74
|
|
|
@@ -100,21 +102,40 @@ function notStartedCauses(outcomes: Iterable<TaskOutcome>): string[] {
|
|
|
100
102
|
/** The history filtered by `stop`, named `label`. */
|
|
101
103
|
const history = (stop: string, label: string): StatTile['action'] => ({ id: `history:${stop}`, label });
|
|
102
104
|
|
|
103
|
-
/** The
|
|
104
|
-
function
|
|
105
|
+
/** The one task behind a count, by key and name; undefined when there are several. */
|
|
106
|
+
function onlyTask(journal: readonly JournalRow[], keys: readonly string[]): { key: string; name: string } | undefined {
|
|
105
107
|
const only = keys.length === 1 ? keys[0] : undefined;
|
|
106
|
-
if (!only) return
|
|
108
|
+
if (!only) return undefined;
|
|
107
109
|
const row = journal.find((r) => r.key === only);
|
|
108
|
-
return {
|
|
110
|
+
return { key: only, name: row ? kindUnit(row) : only };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The tasks behind a count: the task itself when it is one (named), else the history filtered to them (`label`). */
|
|
114
|
+
function show(journal: readonly JournalRow[], keys: readonly string[], stop: string, label: string): StatTile['action'] {
|
|
115
|
+
const one = onlyTask(journal, keys);
|
|
116
|
+
return one ? { id: `task:${one.key}`, label: one.name } : history(stop, label);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The line under a count of events: the one task they belong to by name, else the
|
|
121
|
+
* count of tasks (`many`) when it differs from the count of events; none at 0.
|
|
122
|
+
*/
|
|
123
|
+
function tasksLine(journal: readonly JournalRow[], count: number, keys: readonly string[], many: (n: number) => string): string[][] {
|
|
124
|
+
if (!count) return [];
|
|
125
|
+
const one = onlyTask(journal, keys);
|
|
126
|
+
if (one) return [[one.name]];
|
|
127
|
+
return keys.length === count ? [] : [[many(keys.length)]];
|
|
109
128
|
}
|
|
110
129
|
|
|
111
130
|
/**
|
|
112
131
|
* The tiles: tasks (with `d` from the plan; from the journal alone otherwise),
|
|
113
132
|
* attempts, cost and tokens (when reported), failed attempts, not started,
|
|
114
133
|
* agent time, retries, corrections and model switches. Neutral by default:
|
|
115
|
-
* only a problem count above 0 takes its tone (`problemTone`).
|
|
134
|
+
* only a problem count above 0 takes its tone (`problemTone`). A tile's lines
|
|
135
|
+
* add facts the value does not give: a 0 has none. `highestCost: false` leaves
|
|
136
|
+
* the kind with the highest cost out of the cost tile.
|
|
116
137
|
*/
|
|
117
|
-
export function kpiTiles(journal: readonly JournalRow[], d: Pick<RunDerived, 'outcomes' | 'counts'
|
|
138
|
+
export function kpiTiles(journal: readonly JournalRow[], d: Pick<RunDerived, 'outcomes' | 'counts'>, opts: { highestCost?: boolean } = {}): StatTile[] {
|
|
118
139
|
const k = kpis(journal);
|
|
119
140
|
const { outcomes, counts: c } = d;
|
|
120
141
|
// The total is neutral; the parts that are problems carry their own status.
|
|
@@ -135,13 +156,13 @@ export function kpiTiles(journal: readonly JournalRow[], d: Pick<RunDerived, 'ou
|
|
|
135
156
|
sub: [factList(k.launches === 1 ? msg('1 agent session') : msg(str`${k.launches} agent sessions`), k.python === 1 ? msg('1 Python task attempt') : msg(str`${k.python} Python task attempts`))],
|
|
136
157
|
tip: msg('Attempts that ran, retries included (journal.jsonl)'),
|
|
137
158
|
},
|
|
138
|
-
...spendTiles(journal),
|
|
159
|
+
...spendTiles(journal, opts.highestCost ?? true),
|
|
139
160
|
{
|
|
140
161
|
id: 'bad',
|
|
141
162
|
tone: problemTone(k.bad, 'failed'),
|
|
142
163
|
label: msg('Failed attempts'),
|
|
143
164
|
value: k.bad,
|
|
144
|
-
sub: [
|
|
165
|
+
sub: [],
|
|
145
166
|
...(k.bad ? { action: history('bad', msg('Failed attempts in history')) } : {}),
|
|
146
167
|
},
|
|
147
168
|
{
|
|
@@ -149,7 +170,7 @@ export function kpiTiles(journal: readonly JournalRow[], d: Pick<RunDerived, 'ou
|
|
|
149
170
|
tone: problemTone(c.not_started, 'attention'),
|
|
150
171
|
label: msg('Not started'),
|
|
151
172
|
value: c.not_started,
|
|
152
|
-
sub:
|
|
173
|
+
sub: c.not_started ? [notStartedCauses(outcomes.values())] : [],
|
|
153
174
|
...(c.not_started ? { action: history('invalid_input', msg('Tasks not started in history')) } : {}),
|
|
154
175
|
},
|
|
155
176
|
{
|
|
@@ -163,7 +184,7 @@ export function kpiTiles(journal: readonly JournalRow[], d: Pick<RunDerived, 'ou
|
|
|
163
184
|
id: 'retry',
|
|
164
185
|
label: msg('Retries'),
|
|
165
186
|
value: k.retries,
|
|
166
|
-
sub:
|
|
187
|
+
sub: tasksLine(journal, k.retries, k.retriedKeys, (n) => msg(str`${n} tasks retried`)),
|
|
167
188
|
tip: retryTip(),
|
|
168
189
|
...(k.retries ? { action: show(journal, k.retriedKeys, 'retried', msg('Retried tasks in history')) } : {}),
|
|
169
190
|
},
|
|
@@ -171,7 +192,7 @@ export function kpiTiles(journal: readonly JournalRow[], d: Pick<RunDerived, 'ou
|
|
|
171
192
|
id: 'ct',
|
|
172
193
|
label: msg('Corrections'),
|
|
173
194
|
value: k.fixups,
|
|
174
|
-
sub:
|
|
195
|
+
sub: tasksLine(journal, k.fixups, k.fixupKeys, (n) => msg(str`${n} tasks with corrections`)),
|
|
175
196
|
tip: correctionTip(),
|
|
176
197
|
...(k.fixups ? { action: show(journal, k.fixupKeys, 'fixup', msg('Attempts with corrections in history')) } : {}),
|
|
177
198
|
},
|
|
@@ -179,14 +200,14 @@ export function kpiTiles(journal: readonly JournalRow[], d: Pick<RunDerived, 'ou
|
|
|
179
200
|
id: 'fo',
|
|
180
201
|
label: msg('Model switches'),
|
|
181
202
|
value: k.switches,
|
|
182
|
-
sub:
|
|
203
|
+
sub: tasksLine(journal, k.switches, k.switchKeys, (n) => msg(str`${n} tasks with model switches`)),
|
|
183
204
|
tip: modelSwitchTip(),
|
|
184
205
|
},
|
|
185
206
|
];
|
|
186
207
|
}
|
|
187
208
|
|
|
188
|
-
/** Cost and tokens, when any attempt reported them
|
|
189
|
-
function spendTiles(journal: readonly JournalRow[]): StatTile[] {
|
|
209
|
+
/** Cost and tokens, when any attempt reported them; the cost tile names the kind with the highest cost when `highest`. */
|
|
210
|
+
function spendTiles(journal: readonly JournalRow[], highest: boolean): StatTile[] {
|
|
190
211
|
const s = spendOf(journal);
|
|
191
212
|
if (!s) return [];
|
|
192
213
|
const top = spendByKind(journal)[0];
|
|
@@ -195,7 +216,7 @@ function spendTiles(journal: readonly JournalRow[]): StatTile[] {
|
|
|
195
216
|
id: 'cost',
|
|
196
217
|
label: msg('Cost'),
|
|
197
218
|
value: usd(s.cost),
|
|
198
|
-
sub: top && top.share !== null ? [[msg(str`Highest cost: ${top.kind}`)], factList(usd(top.spend.cost), msg(str`${Math.round(top.share * 100)}% of the total`))] : [[]],
|
|
219
|
+
sub: highest && top && top.share !== null ? [[msg(str`Highest cost: ${top.kind}`)], factList(usd(top.spend.cost), msg(str`${Math.round(top.share * 100)}% of the total`))] : [[]],
|
|
199
220
|
tip: msg('Reported by the coding agent'),
|
|
200
221
|
},
|
|
201
222
|
{
|
|
@@ -9,7 +9,7 @@ import { attemptNames } from '../../words/attempts.js';
|
|
|
9
9
|
import { roundLabel, roundMark, switchText } from '../../words/rounds.js';
|
|
10
10
|
import { stopLabel } from '../../words/stop.js';
|
|
11
11
|
import type { Transcript } from './model.js';
|
|
12
|
-
import { whoTags } from './who.js';
|
|
12
|
+
import { whoTags, type Who } from './who.js';
|
|
13
13
|
|
|
14
14
|
export interface SectionsContext {
|
|
15
15
|
/** The run id: paths in a reason are shown relative to it. */
|
|
@@ -18,6 +18,14 @@ export interface SectionsContext {
|
|
|
18
18
|
rows: readonly JournalRow[];
|
|
19
19
|
/** The latest round is still being written (the task runs and its transcript is tailed). */
|
|
20
20
|
active: boolean;
|
|
21
|
+
/** Who the page names already (the task's header): a section's head shows only what differs from it. */
|
|
22
|
+
shownWho?: Who | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** What of `who` differs from `shown`: the agent, model and role the reader has not seen yet. */
|
|
26
|
+
export function whoBeyond(who: Who | null | undefined, shown: Who | null | undefined): Who | null {
|
|
27
|
+
if (!who || !shown) return who ?? null;
|
|
28
|
+
return { agent: who.agent === shown.agent ? '' : who.agent, model: who.model === shown.model ? '' : who.model, role: who.role === shown.role ? '' : who.role };
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
/** The command line, and the prompt with what was not saved of it. */
|
|
@@ -72,7 +80,7 @@ export function transcriptSections(t: Transcript, rounds: readonly RoundInfo[],
|
|
|
72
80
|
facts: model ? [model.replace(/^claude-/, '')] : [],
|
|
73
81
|
verdict: mark ? { tone: mark === '✓' ? 'done' : 'failed', text: word, tip } : undefined,
|
|
74
82
|
active: i === last && ctx.active,
|
|
75
|
-
tags: r.launch ? whoTags(r.who) : [],
|
|
83
|
+
tags: r.launch ? whoTags(whoBeyond(r.who, ctx.shownWho)) : [],
|
|
76
84
|
details: r.launch ? launchDetails(r.launch) : [],
|
|
77
85
|
// the model leaves an event's title to the reader's words: the only event is an API request sent again
|
|
78
86
|
items: r.items.map((it): TranscriptItem => (it.type === 'event' ? { ...it, title: msg('API request sent again') } : it)),
|
|
@@ -8,19 +8,16 @@ import { RememberedSet } from '../../core/memory.js';
|
|
|
8
8
|
import type { TaskView } from '../../core/view-route.js';
|
|
9
9
|
import { base } from '@contraagent/elements/styles';
|
|
10
10
|
import { executorIcon, executorIconStyles } from '../../words/executor.js';
|
|
11
|
-
import { kindUnit } from '../../words/format.js';
|
|
12
|
-
import { correctionRoundTip, correctionRoundsLabel, notRunText, type Producer } from '../../words/contract.js';
|
|
11
|
+
import { facts, kindUnit } from '../../words/format.js';
|
|
12
|
+
import { contractStatusLabel, correctionRoundTip, correctionRoundsLabel, notRunText, type Producer } from '../../words/contract.js';
|
|
13
13
|
import { contractChip, contractStyles, fieldCheckTable, fileConditions, inputChip } from '../shared/contract-parts.js';
|
|
14
14
|
|
|
15
15
|
const CT_ORDER: readonly ContractStatus[] = ['invalid', 'refused', 'failed', 'running', 'pending', 'valid'];
|
|
16
16
|
|
|
17
|
-
/** Output contracts per status, worst first
|
|
18
|
-
|
|
17
|
+
/** Output contracts per status that is not passed, worst first: the passed ones head their own group. */
|
|
18
|
+
function ctChips(outputs: Outputs | null) {
|
|
19
19
|
const t = outputs?.tally;
|
|
20
|
-
|
|
21
|
-
return chips.length
|
|
22
|
-
? chips.map((k) => contractChip(k, t?.[k]))
|
|
23
|
-
: html`<span class="meta">${msg('No checked files')}</span>`;
|
|
20
|
+
return (t ? CT_ORDER.filter((k) => k !== 'valid' && t[k]) : []).map((k) => contractChip(k, t?.[k]));
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
const shortPath = (p: string) => p.split('/').slice(-2).join('/');
|
|
@@ -34,8 +31,9 @@ const rank = (s: ContractStatus | null | undefined) => (s ? CT_ORDER.indexOf(s)
|
|
|
34
31
|
|
|
35
32
|
/**
|
|
36
33
|
* The files each task must write (`OutputContract`) and read (`InputContract`),
|
|
37
|
-
* problems first, each
|
|
38
|
-
*
|
|
34
|
+
* problems first, each with the chip of its status, then the passed ones under
|
|
35
|
+
* one chip; each checked field by field against its schema. Links open the
|
|
36
|
+
* task (`ca-open-task`).
|
|
39
37
|
*
|
|
40
38
|
* @fires ca-open-task - `{key, view}`: a link to the task's input, output or transcript
|
|
41
39
|
*/
|
|
@@ -49,6 +47,8 @@ export class CaContracts extends LitElement {
|
|
|
49
47
|
css`
|
|
50
48
|
:host { display: block; container-type: inline-size; }
|
|
51
49
|
.ct-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
|
|
50
|
+
/* the passed outputs: one chip heads them, their rows carry none */
|
|
51
|
+
.passed { margin: 6px 0 0; padding: 10px 0 8px; font: inherit; border-top: 1px solid var(--line); }
|
|
52
52
|
details.ct { border-top: 1px solid var(--line); }
|
|
53
53
|
details.ct > summary { list-style: none; cursor: pointer; padding: 7px 4px; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; }
|
|
54
54
|
details.ct > summary > * { min-width: 0; overflow-wrap: anywhere; }
|
|
@@ -85,24 +85,30 @@ export class CaContracts extends LitElement {
|
|
|
85
85
|
const cs = this.outputs?.contracts ?? [];
|
|
86
86
|
this.outcomes = this.plan ? deriveRun(this.journal, this.plan).outcomes : new Map<string, TaskOutcome>();
|
|
87
87
|
if (!cs.length) {
|
|
88
|
-
return html`<div class="blank"><span class="ic" aria-hidden="true">▣</span><b>${msg('No checked files')}</b
|
|
89
|
-
<span>${msg('No task declares an OutputContract or an InputContract')}</span></div>`;
|
|
88
|
+
return html`<div class="blank"><span class="ic" aria-hidden="true">▣</span><b>${facts(msg('No checked files'), msg('no task declares an OutputContract or an InputContract'))}</b></div>`;
|
|
90
89
|
}
|
|
91
90
|
const outs = cs
|
|
92
91
|
.flatMap((c) => (c.outputs ?? []).map((o) => [c, o] as const))
|
|
93
92
|
.sort(([a, x], [b, y]) => rank(x.status) - rank(y.status) || a.key.localeCompare(b.key));
|
|
94
93
|
const ins = cs.flatMap((c) => (c.inputs ?? []).map((i) => [c, i] as const));
|
|
94
|
+
const others = outs.filter(([, o]) => o.status !== 'valid');
|
|
95
|
+
const passed = outs.filter(([, o]) => o.status === 'valid');
|
|
96
|
+
const chips = ctChips(this.outputs);
|
|
95
97
|
return html`<div class="g12">
|
|
96
98
|
<section class="panel c12">
|
|
97
|
-
<h3 title=${msg('OutputContract')}>${msg('Outputs')}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
${
|
|
99
|
+
<h3 title=${msg('Files each task must write (OutputContract)')}>${msg('Outputs')}</h3>
|
|
100
|
+
${chips.length ? html`<div class="ct-row">${chips}</div>` : nothing}
|
|
101
|
+
${repeat(others, ([c, o]) => `out|${c.key}|${o.path}`, ([c, o]) => this.outRow(c, o))}
|
|
102
|
+
${passed.length
|
|
103
|
+
? html`<h4 class="passed">${contractChip('valid', passed.length)}</h4>
|
|
104
|
+
<div role="group" aria-label=${contractStatusLabel('valid')}>
|
|
105
|
+
${repeat(passed, ([c, o]) => `out|${c.key}|${o.path}`, ([c, o]) => this.outRow(c, o))}
|
|
106
|
+
</div>`
|
|
107
|
+
: nothing}
|
|
101
108
|
</section>
|
|
102
109
|
${ins.length
|
|
103
110
|
? html`<section class="panel c12">
|
|
104
|
-
<h3 title=${msg('InputContract')}>${msg('Inputs')}
|
|
105
|
-
<span class="r">${msg('Files each task reads')}</span></h3>
|
|
111
|
+
<h3 title=${msg('Files each task reads (InputContract)')}>${msg('Inputs')}</h3>
|
|
106
112
|
${repeat(ins, ([c, i]) => `in|${c.key}|${i.path}`, ([c, i]) => this.inRow(c, i))}
|
|
107
113
|
</section>`
|
|
108
114
|
: nothing}
|
|
@@ -165,7 +171,7 @@ export class CaContracts extends LitElement {
|
|
|
165
171
|
// The chip says why there is no file; the size column shows a size only when there is one.
|
|
166
172
|
return this.details(
|
|
167
173
|
`out|${c.key}|${o.path}`,
|
|
168
|
-
html`${contractChip(status)}
|
|
174
|
+
html`${status === 'valid' ? nothing : contractChip(status)}
|
|
169
175
|
${rounds > 1 ? html`<span class="meta" title=${correctionRoundTip()}>${correctionRoundsLabel(rounds, status === 'valid')}</span>` : nothing}
|
|
170
176
|
${this.who(c)}
|
|
171
177
|
<span class="jmono jdim ct-path" title=${o.path}>${shortPath(o.path)}</span>
|
|
@@ -19,7 +19,7 @@ import { emit } from '../../core/events.js';
|
|
|
19
19
|
* @csspart label - a tile's label
|
|
20
20
|
* @csspart value - a tile's value
|
|
21
21
|
* @csspart sub - each line under the value
|
|
22
|
-
* @csspart action - a tile's link
|
|
22
|
+
* @csspart action - a tile's link, over the whole tile
|
|
23
23
|
*/
|
|
24
24
|
@customElement('ca-kpis')
|
|
25
25
|
@localized()
|
|
@@ -31,6 +31,8 @@ export class CaKpis extends LitElement {
|
|
|
31
31
|
@property({ attribute: false }) journal: JournalRow[] = [];
|
|
32
32
|
/** The scheduled tasks; without it, tasks are counted from the journal. */
|
|
33
33
|
@property({ attribute: false }) plan: Plan = { nodes: [], groups: [] };
|
|
34
|
+
/** Leaves the kind with the highest cost out of the cost tile, for a page that shows the cost by kind beside it. */
|
|
35
|
+
@property({ type: Boolean, attribute: 'no-highest-cost' }) noHighestCost = false;
|
|
34
36
|
|
|
35
37
|
private readonly onAction = (e: CustomEvent<{ action: string }>): void => {
|
|
36
38
|
e.stopPropagation();
|
|
@@ -40,7 +42,7 @@ export class CaKpis extends LitElement {
|
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
override render() {
|
|
43
|
-
return html`<cae-stat-tiles exportparts="tiles, tile, label, value, sub, action" .tiles=${kpiTiles(this.journal, deriveRun(this.journal, this.plan))} @cae-action=${this.onAction}></cae-stat-tiles>`;
|
|
45
|
+
return html`<cae-stat-tiles exportparts="tiles, tile, label, value, sub, action" .tiles=${kpiTiles(this.journal, deriveRun(this.journal, this.plan), { highestCost: !this.noHighestCost })} @cae-action=${this.onAction}></cae-stat-tiles>`;
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -16,7 +16,8 @@ const COLUMNS: readonly ModelMeasure[] = ['cost', 'tokens', 'time', 'sessions'];
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* What each model was used for in a run (`Plan.models`): the share of each model in
|
|
19
|
-
* the chosen measure (cost, tokens, time or agent sessions) as one bar
|
|
19
|
+
* the chosen measure (cost, tokens, time or agent sessions) as one bar (the table
|
|
20
|
+
* names each segment, so the bar has no names under it), and a table
|
|
20
21
|
* of every model with its share and every measure, and its peak context when
|
|
21
22
|
* a session on it reported one. A model keeps its colour, and
|
|
22
23
|
* its place, in the order it was first used, whatever the measure; past six models
|
|
@@ -74,7 +75,7 @@ export class CaModelUsage extends LitElement {
|
|
|
74
75
|
@cae-change=${(e: CustomEvent<{ value: string }>) => (this.measure = e.detail.value as ModelMeasure)}></cae-segmented>
|
|
75
76
|
</div>`
|
|
76
77
|
: nothing}
|
|
77
|
-
${shareBar(shareParts(this.models, measure), { label: measureLabel(measure) })}
|
|
78
|
+
${shareBar(shareParts(this.models, measure), { label: measureLabel(measure), names: false })}
|
|
78
79
|
<div class="wrap" data-scroll="x" tabindex="0" role="region" aria-label=${msg('Models')}>
|
|
79
80
|
<table>
|
|
80
81
|
<thead>
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
type TypedPanelBody,
|
|
8
8
|
liveActivities,
|
|
9
9
|
progress,
|
|
10
|
-
result,
|
|
11
10
|
runAccounts,
|
|
12
11
|
runRelative,
|
|
13
12
|
spendOf,
|
|
@@ -29,9 +28,9 @@ import { facts, kindUnit, localTime, sentence } from '../../words/format.js';
|
|
|
29
28
|
import { notStartedCause, outcomeLabel, stateCount, stopLabel } from '../../words/index.js';
|
|
30
29
|
import { base } from '@contraagent/elements/styles';
|
|
31
30
|
import { pill, statusWord } from '@contraagent/elements/primitives';
|
|
32
|
-
import {
|
|
31
|
+
import { outcomeTone } from '../../lib/tone.js';
|
|
33
32
|
import { contractChip, formatOf } from '../shared/contract-parts.js';
|
|
34
|
-
import { contractStatusLabel
|
|
33
|
+
import { contractStatusLabel } from '../../words/contract.js';
|
|
35
34
|
import { taskChip } from '../shared/task-chip.js';
|
|
36
35
|
import '../lists/ca-proc-tree.js';
|
|
37
36
|
import './ca-kpis.js';
|
|
@@ -132,11 +131,15 @@ export class CaOverview extends LitElement {
|
|
|
132
131
|
}
|
|
133
132
|
.ok { display: flex; align-items: baseline; gap: 6px; color: var(--text-2); }
|
|
134
133
|
.ok span[aria-hidden] { color: var(--status-done); }
|
|
135
|
-
/* a failed output
|
|
136
|
-
.ofl { grid-template-columns:
|
|
137
|
-
.ofl > .jstop { white-space: nowrap; }
|
|
134
|
+
/* a failed output, under the chip of its status: its task and what it is on one line, where under them */
|
|
135
|
+
.ofl { grid-template-columns: minmax(0, 1fr) auto; }
|
|
138
136
|
.ofl > b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
139
|
-
.ofl > .meta {
|
|
137
|
+
.ofl > .meta { overflow: hidden; text-overflow: ellipsis; }
|
|
138
|
+
.ofl > .meta:last-child { grid-column: 1 / -1; }
|
|
139
|
+
.ogroup + .ogroup, .ogroup + .ct-row { margin-top: 10px; }
|
|
140
|
+
/* a heading that is a link to its tab */
|
|
141
|
+
h3 a.hl { color: inherit; text-decoration: none; }
|
|
142
|
+
h3 a.hl:hover { color: var(--accent); text-decoration: underline; }
|
|
140
143
|
.ct-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
|
|
141
144
|
.pcards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); gap: 12px; }
|
|
142
145
|
.pcard { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 12px; align-items: start; padding: 12px 14px; border-radius: 10px;
|
|
@@ -158,10 +161,8 @@ export class CaOverview extends LitElement {
|
|
|
158
161
|
.ofl > .meta { white-space: nowrap; }
|
|
159
162
|
.stopped { grid-column: 1 / 4; color: var(--text-muted); font-size: 11px; display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
|
|
160
163
|
.fl.muted { opacity: 0.7; }
|
|
161
|
-
|
|
162
|
-
.
|
|
163
|
-
.next { margin: 10px 2px 2px; font-size: 12px; color: var(--text-2); }
|
|
164
|
-
.next code { font-family: var(--mono); }
|
|
164
|
+
/* the tasks done after retrying: a group of their own under a rule, after the problems */
|
|
165
|
+
.recovered { margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--line); }
|
|
165
166
|
.more { font-size: 12px; margin: 6px 10px 0; }
|
|
166
167
|
`,
|
|
167
168
|
];
|
|
@@ -211,29 +212,27 @@ export class CaOverview extends LitElement {
|
|
|
211
212
|
<section class="panel enter problems">
|
|
212
213
|
<h3>${msg('Problems')}
|
|
213
214
|
<span class="r"><a class="lnk" href="#" @click=${(e: Event) => this.nav(e, () => emit(this, 'ca-navigate', { tab: 'journal', stop: 'bad' }))}>${msg('Failed attempts in history')}</a></span></h3>
|
|
214
|
-
${this.problems(
|
|
215
|
+
${this.problems()}
|
|
215
216
|
</section>
|
|
216
217
|
<section class="panel enter outputs">
|
|
217
|
-
<h3 title=${msg('Checked files (OutputContract, InputContract)')}>${msg('Outputs')}
|
|
218
|
-
<span class="r"><a class="lnk" href="#" @click=${(e: Event) => this.nav(e, () => this.go('outputs'))}>${msg('All outputs')}</a></span></h3>
|
|
218
|
+
<h3 title=${msg('Checked files (OutputContract, InputContract)')}><a class="hl" href="#" @click=${(e: Event) => this.nav(e, () => this.go('outputs'))}>${msg('Outputs')}</a></h3>
|
|
219
219
|
${this.outputs()}
|
|
220
220
|
</section>
|
|
221
221
|
${running ? html`<div class="row">${this.runningCard(live, busy, max)}</div>` : nothing}
|
|
222
222
|
</div>
|
|
223
223
|
<section class="panel enter">
|
|
224
224
|
<h3 title=${msg('Every task the run scheduled, by kind (plan.jsonl)')}>${msg('Pipeline')}
|
|
225
|
-
|
|
225
|
+
${live ? html`<span class="r">${this.progressLine()}</span>` : nothing}</h3>
|
|
226
226
|
<ca-pipeline-graph .plan=${s.plan} .forecast=${s.forecast} .journal=${s.journal} .run=${this.run} .layer=${this.layer} .unit=${this.unit} .sel=${this.sel} ?live=${live}></ca-pipeline-graph>
|
|
227
227
|
</section>
|
|
228
228
|
<section class="panel enter">
|
|
229
229
|
<h3 title=${msg('Counted from every finished attempt (journal.jsonl)')}>${msg('Totals')}</h3>
|
|
230
|
-
<ca-kpis .journal=${s.journal} .plan=${s.plan}></ca-kpis>
|
|
230
|
+
<ca-kpis .journal=${s.journal} .plan=${s.plan} no-highest-cost></ca-kpis>
|
|
231
231
|
</section>
|
|
232
232
|
<div class="band parts">
|
|
233
233
|
${live
|
|
234
234
|
? html`<section class="panel enter">
|
|
235
|
-
<h3>${msg('Process tree')}
|
|
236
|
-
<span class="r"><a class="lnk" href="#" @click=${(e: Event) => this.nav(e, () => this.go('procs'))}>${msg('All processes')}</a></span></h3>
|
|
235
|
+
<h3><a class="hl" href="#" @click=${(e: Event) => this.nav(e, () => this.go('procs'))}>${msg('Process tree')}</a></h3>
|
|
237
236
|
<ca-proc-tree .procs=${s.procs}></ca-proc-tree>
|
|
238
237
|
</section>`
|
|
239
238
|
: nothing}
|
|
@@ -245,15 +244,13 @@ export class CaOverview extends LitElement {
|
|
|
245
244
|
: nothing}
|
|
246
245
|
${s.plan.models?.length
|
|
247
246
|
? html`<section class="panel enter">
|
|
248
|
-
<h3 title=${msg('What the agent sessions spent on each model, each model of a model switch counted separately (Plan.models)')}>${msg('Models')}
|
|
249
|
-
<span class="r">${s.plan.models.length === 1 ? msg('1 model') : msg(str`${s.plan.models.length} models`)}</span></h3>
|
|
247
|
+
<h3 title=${msg('What the agent sessions spent on each model, each model of a model switch counted separately (Plan.models)')}>${msg('Models')}</h3>
|
|
250
248
|
<ca-model-usage .models=${s.plan.models}></ca-model-usage>
|
|
251
249
|
</section>`
|
|
252
250
|
: nothing}
|
|
253
251
|
${runAccounts(s.journal).length || s.plan.rests?.length
|
|
254
252
|
? html`<section class="panel enter">
|
|
255
|
-
<h3 title=${msg('Account-wide | read at each agent session start')}>
|
|
256
|
-
${msg('Subscription usage')}<span class="r">${msg('Per account, first → last reading')}</span></h3>
|
|
253
|
+
<h3 title=${msg('Account-wide | first and last reading per account | read at each agent session start')}>${msg('Subscription usage')}</h3>
|
|
257
254
|
<ca-quota .journal=${s.journal} .caps=${s.plan.quota_caps ?? []} .rests=${s.plan.rests ?? []} .rule=${s.plan.quota_rule ?? ''} ?live=${live}></ca-quota>
|
|
258
255
|
</section>`
|
|
259
256
|
: nothing}
|
|
@@ -277,8 +274,7 @@ export class CaOverview extends LitElement {
|
|
|
277
274
|
</section>`
|
|
278
275
|
: nothing}
|
|
279
276
|
<section class="panel enter">
|
|
280
|
-
<h3>${msg('Timeline')}
|
|
281
|
-
<span class="r">${this.triesLine()}</span></h3>
|
|
277
|
+
<h3>${msg('Timeline')}</h3>
|
|
282
278
|
<ca-timeline .journal=${s.journal} .plan=${s.plan} .procs=${s.procs} .live=${live} .run=${this.run}></ca-timeline>
|
|
283
279
|
</section>
|
|
284
280
|
</div>`;
|
|
@@ -298,60 +294,44 @@ export class CaOverview extends LitElement {
|
|
|
298
294
|
if (!contracts.length) {
|
|
299
295
|
return html`<div class="meta">${msg('No output files')}</div>`;
|
|
300
296
|
}
|
|
301
|
-
const chips = CT_ORDER.filter((k) => tally[k]);
|
|
302
297
|
// Only what went wrong with a file; tasks that never started are the problems section's.
|
|
303
298
|
const bad = contracts
|
|
304
299
|
.flatMap((c) => (c.outputs ?? []).map((o): [ContractRecord, ContractSpec] => [c, o]))
|
|
305
300
|
.filter(([, o]) => o.status === 'invalid' || o.status === 'failed');
|
|
306
301
|
const shown = bad.slice(0, OUTPUTS_SHOWN);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
302
|
+
// The rows shown sit under the chip of their status; the other statuses are a row of chips after them.
|
|
303
|
+
const groups = CT_ORDER.filter((k) => tally[k] && shown.some(([, o]) => o.status === k));
|
|
304
|
+
const chips = CT_ORDER.filter((k) => tally[k] && !groups.includes(k));
|
|
305
|
+
return html`${groups.map(
|
|
306
|
+
(k) => html`<div class="ogroup" role="group" aria-label=${contractStatusLabel(k)}>
|
|
307
|
+
<div class="ct-row">${contractChip(k, tally[k])}</div>
|
|
308
|
+
${shown
|
|
309
|
+
.filter(([, o]) => o.status === k)
|
|
310
|
+
.map(
|
|
311
|
+
([c, o]) => html`<button type="button" class="fl ofl" @click=${() => this.go('outputs')}>
|
|
312
|
+
<b class="jmono" title=${c.key}>${kindUnit({ kind: c.kind, unit: c.unit === c.key ? '' : c.unit })}</b>
|
|
313
|
+
<span class="meta">${formatOf(o)}</span>
|
|
314
|
+
<span class="meta" title=${runRelative(o.errors?.[0] || o.path, this.run)}>${runRelative(o.errors?.[0] || o.path, this.run)}</span>
|
|
315
|
+
</button>`,
|
|
316
|
+
)}
|
|
317
|
+
</div>`,
|
|
320
318
|
)}
|
|
321
319
|
${bad.length > shown.length
|
|
322
320
|
? html`<div class="more"><a class="lnk" href="#" @click=${(e: Event) => this.nav(e, () => this.go('outputs'))}>${bad.length - shown.length === 1
|
|
323
321
|
? msg('1 more output')
|
|
324
322
|
: msg(str`${bad.length - shown.length} more outputs`)}</a></div>`
|
|
325
323
|
: nothing}
|
|
326
|
-
${
|
|
327
|
-
? html`<div class="
|
|
328
|
-
?
|
|
329
|
-
|
|
324
|
+
${chips.length || !groups.length
|
|
325
|
+
? html`<div class="ct-row">
|
|
326
|
+
${chips.length ? chips.map((k) => contractChip(k, tally[k])) : html`<span class="meta">${msg('No checked files')}</span>`}
|
|
327
|
+
</div>`
|
|
330
328
|
: nothing}`;
|
|
331
329
|
}
|
|
332
330
|
|
|
333
|
-
/** While running, how far the run is; once over, how its tasks ended. */
|
|
334
|
-
private progressLine(
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
if (live || r.left) {
|
|
338
|
-
const p = progress(plan);
|
|
339
|
-
return facts(msg(str`${p.pct}%`), msg(str`${p.done} of ${p.total} tasks done`), p.partial ? stateCount('partial', p.partial) : '', p.skipped ? stateCount('skipped', p.skipped) : '', p.cancelled ? stateCount('cancelled', p.cancelled) : '');
|
|
340
|
-
}
|
|
341
|
-
return facts(
|
|
342
|
-
stateCount('done', r.done),
|
|
343
|
-
r.skipped ? stateCount('skipped', r.skipped) : '',
|
|
344
|
-
r.failed ? stateCount('failed', r.failed) : '',
|
|
345
|
-
r.notStarted ? stateCount('not_started', r.notStarted) : '',
|
|
346
|
-
r.partial ? stateCount('partial', r.partial) : '',
|
|
347
|
-
r.cancelled ? stateCount('cancelled', r.cancelled) : '',
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/** "23 attempts | 4 not started": a task that never started has no attempt. */
|
|
352
|
-
private triesLine(): string {
|
|
353
|
-
const { tries, notStarted } = deriveRun(this.state.journal, this.state.plan).tries;
|
|
354
|
-
return facts(tries === 1 ? msg('1 attempt') : msg(str`${tries} attempts`), notStarted ? stateCount('not_started', notStarted) : '');
|
|
331
|
+
/** While running, how far the run is; once over, the run's state and the tiles tell how its tasks ended. */
|
|
332
|
+
private progressLine(): string {
|
|
333
|
+
const p = progress(this.state.plan);
|
|
334
|
+
return facts(msg(str`${p.pct}%`), msg(str`${p.done} of ${p.total} tasks done`), p.partial ? stateCount('partial', p.partial) : '', p.skipped ? stateCount('skipped', p.skipped) : '', p.cancelled ? stateCount('cancelled', p.cancelled) : '');
|
|
355
335
|
}
|
|
356
336
|
|
|
357
337
|
/** A link's click: no navigation of the page's own, then `go`. */
|
|
@@ -425,7 +405,7 @@ export class CaOverview extends LitElement {
|
|
|
425
405
|
return kindUnit(t);
|
|
426
406
|
}
|
|
427
407
|
|
|
428
|
-
private problems(
|
|
408
|
+
private problems() {
|
|
429
409
|
const { outcomes, problems } = deriveRun(this.state.journal, this.state.plan);
|
|
430
410
|
const { failed, notStarted, partial, recovered } = problems;
|
|
431
411
|
const issues = failed.length + notStarted.length + partial.length;
|
|
@@ -482,8 +462,8 @@ export class CaOverview extends LitElement {
|
|
|
482
462
|
</div>`,
|
|
483
463
|
)}
|
|
484
464
|
${rest(partial.length, PROBLEMS_SHOWN, 'partial')}
|
|
485
|
-
${recovered.length
|
|
486
|
-
|
|
465
|
+
${recovered.length
|
|
466
|
+
? html`<div class="recovered" role="group" aria-label=${sentence(outcomeLabel('recovered'))}>${repeat(
|
|
487
467
|
recovered.slice(0, RECOVERED_SHOWN),
|
|
488
468
|
(t) => t.key,
|
|
489
469
|
(t) => {
|
|
@@ -498,24 +478,14 @@ export class CaOverview extends LitElement {
|
|
|
498
478
|
</div>`;
|
|
499
479
|
},
|
|
500
480
|
)}
|
|
501
|
-
${rest(recovered.length, RECOVERED_SHOWN, 'retried')}
|
|
502
|
-
|
|
481
|
+
${rest(recovered.length, RECOVERED_SHOWN, 'retried')}</div>`
|
|
482
|
+
: nothing}`;
|
|
503
483
|
}
|
|
504
484
|
|
|
505
485
|
/** A row's second line: one line, the whole of it in its tooltip. */
|
|
506
486
|
private metaLine(text: string) {
|
|
507
487
|
return html`<div class="meta" title=${text}>${text}</div>`;
|
|
508
488
|
}
|
|
509
|
-
|
|
510
|
-
/** For a finished run with problems: how many tasks are not done (the tasks resume=True runs again). */
|
|
511
|
-
private nextStep(live: boolean, problemCount: number) {
|
|
512
|
-
if (live || !problemCount) return nothing;
|
|
513
|
-
const c = deriveRun(this.state.journal, this.state.plan).counts;
|
|
514
|
-
// A partly done task is finished in this run, and not done: a resume runs it again.
|
|
515
|
-
const again = c.total - c.finished + c.partial;
|
|
516
|
-
if (!again) return nothing;
|
|
517
|
-
return html`<div class="next" title="resume=True">${msg(str`${again} of ${c.total} tasks not done`)}</div>`;
|
|
518
|
-
}
|
|
519
489
|
}
|
|
520
490
|
|
|
521
491
|
declare global {
|