@contraagent/ui 0.6.2 → 0.7.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/custom-elements.json +24 -3
- package/dist/adapters/forecast.d.ts +20 -0
- package/dist/adapters/forecast.d.ts.map +1 -0
- package/dist/adapters/forecast.js +35 -0
- package/dist/adapters/forecast.js.map +1 -0
- package/dist/adapters/graph/compound.d.ts.map +1 -1
- package/dist/adapters/graph/compound.js +7 -5
- package/dist/adapters/graph/compound.js.map +1 -1
- package/dist/adapters/graph/highlight.d.ts +21 -5
- package/dist/adapters/graph/highlight.d.ts.map +1 -1
- package/dist/adapters/graph/highlight.js +49 -21
- package/dist/adapters/graph/highlight.js.map +1 -1
- package/dist/adapters/graph/input.d.ts +3 -1
- package/dist/adapters/graph/input.d.ts.map +1 -1
- package/dist/adapters/graph/input.js +7 -1
- package/dist/adapters/graph/input.js.map +1 -1
- package/dist/adapters/graph/project.d.ts +5 -3
- package/dist/adapters/graph/project.d.ts.map +1 -1
- package/dist/adapters/graph/project.js +13 -9
- package/dist/adapters/graph/project.js.map +1 -1
- package/dist/adapters/graph/selection.js +1 -1
- package/dist/adapters/graph/selection.js.map +1 -1
- package/dist/adapters/graph/states.d.ts.map +1 -1
- package/dist/adapters/graph/states.js +1 -1
- package/dist/adapters/graph/states.js.map +1 -1
- package/dist/adapters/graph/types.d.ts +12 -4
- package/dist/adapters/graph/types.d.ts.map +1 -1
- package/dist/adapters/graph/types.js +2 -0
- package/dist/adapters/graph/types.js.map +1 -1
- package/dist/adapters/graph/words.d.ts +5 -4
- package/dist/adapters/graph/words.d.ts.map +1 -1
- package/dist/adapters/graph/words.js +11 -9
- package/dist/adapters/graph/words.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/journal-columns.d.ts.map +1 -1
- package/dist/adapters/journal-columns.js +4 -1
- package/dist/adapters/journal-columns.js.map +1 -1
- package/dist/adapters/kpi-tiles.d.ts.map +1 -1
- package/dist/adapters/kpi-tiles.js +1 -0
- package/dist/adapters/kpi-tiles.js.map +1 -1
- package/dist/contraagent-ui.js +674 -657
- package/dist/core/scroll.d.ts +13 -1
- package/dist/core/scroll.d.ts.map +1 -1
- package/dist/core/scroll.js +46 -12
- package/dist/core/scroll.js.map +1 -1
- package/dist/elements/overview/ca-overview.d.ts.map +1 -1
- package/dist/elements/overview/ca-overview.js +3 -3
- package/dist/elements/overview/ca-overview.js.map +1 -1
- package/dist/elements/overview/ca-pipeline-graph.d.ts +6 -1
- package/dist/elements/overview/ca-pipeline-graph.d.ts.map +1 -1
- package/dist/elements/overview/ca-pipeline-graph.js +30 -13
- package/dist/elements/overview/ca-pipeline-graph.js.map +1 -1
- package/dist/elements/overview/pipeline-graph/crumbs.d.ts +5 -0
- package/dist/elements/overview/pipeline-graph/crumbs.d.ts.map +1 -1
- package/dist/elements/overview/pipeline-graph/crumbs.js +27 -1
- package/dist/elements/overview/pipeline-graph/crumbs.js.map +1 -1
- package/dist/elements/overview/pipeline-graph/inspector.d.ts.map +1 -1
- package/dist/elements/overview/pipeline-graph/inspector.js +39 -5
- package/dist/elements/overview/pipeline-graph/inspector.js.map +1 -1
- package/dist/elements/overview/pipeline-graph/outline.js +1 -1
- package/dist/elements/overview/pipeline-graph/outline.js.map +1 -1
- package/dist/elements/overview/pipeline-graph/strings.d.ts.map +1 -1
- package/dist/elements/overview/pipeline-graph/strings.js +6 -3
- package/dist/elements/overview/pipeline-graph/strings.js.map +1 -1
- package/dist/elements/overview/pipeline-graph/styles.d.ts.map +1 -1
- package/dist/elements/overview/pipeline-graph/styles.js +6 -2
- package/dist/elements/overview/pipeline-graph/styles.js.map +1 -1
- package/dist/elements/shell/ca-monitor.d.ts +2 -0
- package/dist/elements/shell/ca-monitor.d.ts.map +1 -1
- package/dist/elements/shell/ca-monitor.js +11 -0
- package/dist/elements/shell/ca-monitor.js.map +1 -1
- package/dist/elements/shell/ca-run-picker.d.ts.map +1 -1
- package/dist/elements/shell/ca-run-picker.js +2 -0
- package/dist/elements/shell/ca-run-picker.js.map +1 -1
- package/dist/elements/task/ca-task-view.d.ts.map +1 -1
- package/dist/elements/task/ca-task-view.js +4 -1
- package/dist/elements/task/ca-task-view.js.map +1 -1
- package/dist/elements/task/header.d.ts.map +1 -1
- package/dist/elements/task/header.js +34 -3
- package/dist/elements/task/header.js.map +1 -1
- package/dist/elements/task/panes/io.d.ts +5 -1
- package/dist/elements/task/panes/io.d.ts.map +1 -1
- package/dist/elements/task/panes/io.js +56 -8
- package/dist/elements/task/panes/io.js.map +1 -1
- package/dist/elements/task/styles.d.ts.map +1 -1
- package/dist/elements/task/styles.js +6 -2
- package/dist/elements/task/styles.js.map +1 -1
- package/dist/lib/tone.d.ts.map +1 -1
- package/dist/lib/tone.js +2 -0
- package/dist/lib/tone.js.map +1 -1
- package/dist/locales/ja.d.ts +47 -5
- package/dist/locales/ja.d.ts.map +1 -1
- package/dist/locales/ja.js +47 -5
- package/dist/locales/ja.js.map +1 -1
- package/dist/words/flow-calls.d.ts +12 -0
- package/dist/words/flow-calls.d.ts.map +1 -0
- package/dist/words/flow-calls.js +31 -0
- package/dist/words/flow-calls.js.map +1 -0
- package/dist/words/forecast.d.ts +16 -0
- package/dist/words/forecast.d.ts.map +1 -0
- package/dist/words/forecast.js +17 -0
- package/dist/words/forecast.js.map +1 -0
- package/dist/words/format.d.ts +10 -0
- package/dist/words/format.d.ts.map +1 -1
- package/dist/words/format.js +17 -0
- package/dist/words/format.js.map +1 -1
- package/dist/words/index.d.ts +4 -0
- package/dist/words/index.d.ts.map +1 -1
- package/dist/words/index.js +4 -0
- package/dist/words/index.js.map +1 -1
- package/dist/words/lineage.d.ts +8 -0
- package/dist/words/lineage.d.ts.map +1 -0
- package/dist/words/lineage.js +35 -0
- package/dist/words/lineage.js.map +1 -0
- package/dist/words/outcome.d.ts.map +1 -1
- package/dist/words/outcome.js +4 -0
- package/dist/words/outcome.js.map +1 -1
- package/dist/words/rerun.d.ts +24 -0
- package/dist/words/rerun.d.ts.map +1 -0
- package/dist/words/rerun.js +41 -0
- package/dist/words/rerun.js.map +1 -0
- package/dist/words/stop.js +2 -0
- package/dist/words/stop.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/forecast.ts +43 -0
- package/src/adapters/graph/compound.ts +10 -9
- package/src/adapters/graph/highlight.ts +53 -22
- package/src/adapters/graph/input.ts +10 -2
- package/src/adapters/graph/plans.fixture.ts +2 -2
- package/src/adapters/graph/project.ts +16 -12
- package/src/adapters/graph/selection.ts +1 -1
- package/src/adapters/graph/states.ts +1 -1
- package/src/adapters/graph/types.ts +14 -4
- package/src/adapters/graph/words.ts +17 -12
- package/src/adapters/index.ts +1 -0
- package/src/adapters/journal-columns.ts +4 -1
- package/src/adapters/kpi-tiles.ts +1 -0
- package/src/core/scroll.ts +43 -11
- package/src/elements/overview/ca-overview.ts +3 -2
- package/src/elements/overview/ca-pipeline-graph.ts +34 -18
- package/src/elements/overview/pipeline-graph/crumbs.ts +28 -0
- package/src/elements/overview/pipeline-graph/inspector.ts +45 -5
- package/src/elements/overview/pipeline-graph/outline.ts +1 -1
- package/src/elements/overview/pipeline-graph/strings.ts +6 -3
- package/src/elements/overview/pipeline-graph/styles.ts +6 -2
- package/src/elements/shell/ca-monitor.ts +9 -0
- package/src/elements/shell/ca-run-picker.ts +1 -0
- package/src/elements/task/ca-task-view.ts +7 -2
- package/src/elements/task/header.ts +34 -3
- package/src/elements/task/panes/io.ts +72 -8
- package/src/elements/task/styles.ts +6 -2
- package/src/lib/tone.ts +2 -0
- package/src/locales/ja.ts +47 -5
- package/src/words/flow-calls.ts +36 -0
- package/src/words/forecast.ts +28 -0
- package/src/words/format.ts +19 -0
- package/src/words/index.ts +4 -0
- package/src/words/lineage.ts +41 -0
- package/src/words/outcome.ts +4 -0
- package/src/words/rerun.ts +61 -0
- package/src/words/stop.ts +2 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { JournalRow, Plan } from '@contraagent/api';
|
|
1
|
+
import type { Forecast, JournalRow, Plan } from '@contraagent/api';
|
|
2
2
|
import '@contraagent/elements/graph/define';
|
|
3
|
-
import { NARROW, arrange, extentOf, type CaeLayeredGraph, type GraphInput, type GraphOrder } from '@contraagent/elements/graph';
|
|
3
|
+
import { NARROW, arrange, extentOf, type CaeLayeredGraph, type EdgeStyle, type GraphInput, type GraphOrder, type LitMarks } from '@contraagent/elements/graph';
|
|
4
4
|
import type { MarkMatrixInput } from '@contraagent/elements/matrix';
|
|
5
5
|
import type { StateStyles } from '@contraagent/elements/primitives';
|
|
6
6
|
import { localized, msg } from '@lit/localize';
|
|
@@ -12,16 +12,16 @@ import { related, taskLinks, type TaskLinks } from '../../adapters/graph/highlig
|
|
|
12
12
|
import { graphInput, matrixFromPlan } from '../../adapters/graph/input.js';
|
|
13
13
|
import { autoLayer, parentLayer, resolveLayer, splitLayer } from '../../adapters/graph/layer.js';
|
|
14
14
|
import type { MatrixSort, PassMatrix } from '../../adapters/graph/matrix.js';
|
|
15
|
-
import { arranged, project, type Projection } from '../../adapters/graph/project.js';
|
|
15
|
+
import { arranged, edgeStyle, project, type Projection } from '../../adapters/graph/project.js';
|
|
16
16
|
import { markSel, parseSelection } from '../../adapters/graph/selection.js';
|
|
17
|
-
import type
|
|
17
|
+
import { EDGE_TYPES, type LayerGraph, type LayerMark } from '../../adapters/graph/types.js';
|
|
18
18
|
import { taskStateStyles } from '../../adapters/graph/words.js';
|
|
19
19
|
import { emit } from '../../core/events.js';
|
|
20
20
|
import { getLocale } from '../../core/i18n.js';
|
|
21
21
|
import { flushMemory, RememberedSet } from '../../core/memory.js';
|
|
22
22
|
import { executorIcon } from '../../words/executor.js';
|
|
23
23
|
import { go, leavePasses, open, primary, select, tasksOf } from './pipeline-graph/actions.js';
|
|
24
|
-
import { crumbs, missingLine, openControls, type OpenMode } from './pipeline-graph/crumbs.js';
|
|
24
|
+
import { crumbs, forecastNote, missingLine, openControls, unawaitedLine, type OpenMode } from './pipeline-graph/crumbs.js';
|
|
25
25
|
import type { PipelineHost } from './pipeline-graph/host.js';
|
|
26
26
|
import { inspector } from './pipeline-graph/inspector.js';
|
|
27
27
|
import { outline, outlineTree, type OutlineHost, type OutlineItem } from './pipeline-graph/outline.js';
|
|
@@ -115,6 +115,7 @@ export class CaPipelineGraph extends LitElement {
|
|
|
115
115
|
static override styles = graphStyles;
|
|
116
116
|
static override properties = {
|
|
117
117
|
plan: { attribute: false },
|
|
118
|
+
forecast: { attribute: false },
|
|
118
119
|
journal: { attribute: false },
|
|
119
120
|
run: {},
|
|
120
121
|
live: { type: Boolean },
|
|
@@ -130,6 +131,8 @@ export class CaPipelineGraph extends LitElement {
|
|
|
130
131
|
};
|
|
131
132
|
|
|
132
133
|
plan: Plan = { nodes: [], groups: [] };
|
|
134
|
+
/** The run's forecast (`Forecast`): a kind node shows the tasks it is expected to have beyond its own; null for none. */
|
|
135
|
+
forecast: Forecast | null = null;
|
|
133
136
|
/** The finished attempts: they tell why a task did not start. */
|
|
134
137
|
journal: JournalRow[] = [];
|
|
135
138
|
/** The run id: paths in a cause are shown relative to it. */
|
|
@@ -195,7 +198,7 @@ export class CaPipelineGraph extends LitElement {
|
|
|
195
198
|
private wide = true;
|
|
196
199
|
private readonly oh: OutlineHost = this.outlineHost();
|
|
197
200
|
/** What a hovered or selected mark lights, for this layer. */
|
|
198
|
-
private lights: (key: string) =>
|
|
201
|
+
private lights: (key: string) => LitMarks = () => new Set();
|
|
199
202
|
/** After this update, the focus goes into the new layer: on the item it came from (`cameFrom`), else the first. */
|
|
200
203
|
private refocus = false;
|
|
201
204
|
private cameFrom = '';
|
|
@@ -265,7 +268,7 @@ export class CaPipelineGraph extends LitElement {
|
|
|
265
268
|
this.prevOrder = this.orders.get(path.join('/'));
|
|
266
269
|
}
|
|
267
270
|
const locale = getLocale();
|
|
268
|
-
const words = structure || changed.has('journal') || changed.has('live') || locale !== this.locale;
|
|
271
|
+
const words = structure || changed.has('journal') || changed.has('live') || changed.has('forecast') || locale !== this.locale;
|
|
269
272
|
if (words || changed.has('sort')) this.build(words, locale);
|
|
270
273
|
}
|
|
271
274
|
|
|
@@ -275,7 +278,7 @@ export class CaPipelineGraph extends LitElement {
|
|
|
275
278
|
if (!model) return;
|
|
276
279
|
const proj = this.proj;
|
|
277
280
|
const { path, loop } = splitLayer(this.segments);
|
|
278
|
-
const x = { plan: this.plan, journal: this.journal, run: this.run, live: this.live, links: this.links };
|
|
281
|
+
const x = { plan: this.plan, journal: this.journal, run: this.run, live: this.live, links: this.links, forecast: this.forecast };
|
|
279
282
|
if (words) {
|
|
280
283
|
this.locale = locale;
|
|
281
284
|
this.states = taskStateStyles(this.live);
|
|
@@ -429,12 +432,17 @@ export class CaPipelineGraph extends LitElement {
|
|
|
429
432
|
if (!this.plan.nodes.length) {
|
|
430
433
|
return html`<div class="blank" style="padding:24px">${this.live ? msg('No tasks scheduled yet') : msg('No tasks scheduled')}</div>`;
|
|
431
434
|
}
|
|
432
|
-
const graph = this.renderGraph();
|
|
433
|
-
if (!this.tree.length) return graph;
|
|
434
435
|
// The outline: a column beside the graph, or, on a narrow frame, a part above it that opens.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
436
|
+
// The graph keeps its place in one template whichever it is, and whether there is an
|
|
437
|
+
// outline: a change between them (the first size report, a resize, a plan that gains a
|
|
438
|
+
// function) keeps the graph's element, and with it the focus, the zoom and the pan.
|
|
439
|
+
const side = this.wide && this.tree.length > 0;
|
|
440
|
+
const part = !this.tree.length
|
|
441
|
+
? nothing
|
|
442
|
+
: side
|
|
443
|
+
? outline(this.oh, this.tree)
|
|
444
|
+
: html`<details class="odet"><summary>${msg('Outline of the flow')}</summary>${outline(this.oh, this.tree)}</details>`;
|
|
445
|
+
return html`<div class="whole ${side ? 'side' : ''}">${part}<div class="g">${this.renderGraph()}</div></div>`;
|
|
438
446
|
}
|
|
439
447
|
|
|
440
448
|
private renderGraph() {
|
|
@@ -446,7 +454,7 @@ export class CaPipelineGraph extends LitElement {
|
|
|
446
454
|
.renderIcon=${renderIcon} .iconStyles=${iconStyles}
|
|
447
455
|
@cae-select=${this.onSelect} @cae-activate=${this.onActivate} @cae-drill=${this.onDrill} @cae-view-change=${this.onView} @cae-toggle=${this.onToggle}
|
|
448
456
|
@cae-sort=${(e: CustomEvent<{ id: string }>) => (this.sort = e.detail.id as MatrixSort)}>
|
|
449
|
-
<div slot="crumbs">${crumbs(this.h, splitLayer(this.segments).loop ? null : openControls(this.openMode(), (m) => this.setMode(m)))}${missingLine(this.missing)}</div>
|
|
457
|
+
<div slot="crumbs">${crumbs(this.h, splitLayer(this.segments).loop ? null : openControls(this.openMode(), (m) => this.setMode(m)))}${missingLine(this.missing)}${this.segments.join('/') === this.auto.join('/') ? html`${forecastNote(this.plan, this.forecast)}${unawaitedLine(this.h)}` : nothing}</div>
|
|
450
458
|
${s ? inspector(this.h, s) : nothing}
|
|
451
459
|
</cae-layered-graph>`;
|
|
452
460
|
}
|
|
@@ -582,19 +590,27 @@ const renderIcon = (name: string) => executorIcon(name === 'python');
|
|
|
582
590
|
|
|
583
591
|
/**
|
|
584
592
|
* What a hovered or selected mark lights in a layer: for a unit (or the key of a mark
|
|
585
|
-
* without one), the marks of the tasks joined to its tasks upstream or downstream
|
|
593
|
+
* without one), the marks of the tasks joined to its tasks upstream or downstream, each
|
|
594
|
+
* with the style of the edge of its link (a mark of several tasks, the strongest), none
|
|
595
|
+
* for a mark of the unit's own tasks.
|
|
586
596
|
*/
|
|
587
|
-
function lightsOf(plan: Plan, links: TaskLinks, g: LayerGraph): (key: string) =>
|
|
597
|
+
function lightsOf(plan: Plan, links: TaskLinks, g: LayerGraph): (key: string) => LitMarks {
|
|
588
598
|
const marks: [string, LayerMark][] = g.nodes.flatMap((n) =>
|
|
589
599
|
[...n.units, ...n.single].filter((m): m is LayerMark => m !== null).map((m): [string, LayerMark] => [markSel(n.id, m), m]),
|
|
590
600
|
);
|
|
591
601
|
const keyOf = new Map(marks.map(([id, m]) => [id, m.key]));
|
|
592
|
-
const memo = new Map<string,
|
|
602
|
+
const memo = new Map<string, LitMarks>();
|
|
593
603
|
return (key) => {
|
|
594
604
|
const had = memo.get(key);
|
|
595
605
|
if (had) return had;
|
|
596
606
|
const on = related(plan.nodes, links, keyOf.get(key) ?? key);
|
|
597
|
-
const lit = new
|
|
607
|
+
const lit = new Map<string, EdgeStyle | null>();
|
|
608
|
+
for (const [id, m] of marks) {
|
|
609
|
+
const types = m.keys.filter((k) => on.has(k)).map((k) => on.get(k) ?? null);
|
|
610
|
+
if (!types.length) continue;
|
|
611
|
+
const strongest = EDGE_TYPES.find((t) => types.includes(t));
|
|
612
|
+
lit.set(id, types.includes(null) || !strongest ? null : edgeStyle(strongest));
|
|
613
|
+
}
|
|
598
614
|
memo.set(key, lit);
|
|
599
615
|
return lit;
|
|
600
616
|
};
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
// The line above the frame: where the layer is, the pinned unit, and a layer the run does not have.
|
|
2
|
+
import type { Forecast, Plan } from '@contraagent/api';
|
|
2
3
|
import { msg, str } from '@lit/localize';
|
|
4
|
+
import { forecastShown, notInPlan } from '../../../adapters/forecast.js';
|
|
5
|
+
import { forecastLine, forecastTip, notInPlanLine } from '../../../words/forecast.js';
|
|
3
6
|
import { html, nothing, type TemplateResult } from 'lit';
|
|
4
7
|
import { shortName } from '../../../adapters/graph/flow-index.js';
|
|
5
8
|
import { splitLayer } from '../../../adapters/graph/layer.js';
|
|
6
9
|
import { LOOP_SEGMENT } from '../../../adapters/graph/types.js';
|
|
7
10
|
import { where } from '../../../adapters/graph/words.js';
|
|
8
11
|
import { emit } from '../../../core/events.js';
|
|
12
|
+
import { kindUnit } from '../../../words/format.js';
|
|
13
|
+
import { unawaitedCount, unawaitedTip } from '../../../words/flow-calls.js';
|
|
14
|
+
import { taskChip } from '../../shared/task-chip.js';
|
|
9
15
|
import { go } from './actions.js';
|
|
10
16
|
import type { PipelineHost } from './host.js';
|
|
11
17
|
|
|
@@ -73,3 +79,25 @@ export function missingLine(missing: string) {
|
|
|
73
79
|
const name = segs[segs.length - 1] || missing;
|
|
74
80
|
return html`<div class="missing" role="status" title=${missing}>${msg(html`No layer <code>${name}</code> in this run`)}</div>`;
|
|
75
81
|
}
|
|
82
|
+
|
|
83
|
+
/** The tasks a top flow call created that no flow awaited (`FlowCallNode.unawaited`): a count and their names, each a chip that opens it. */
|
|
84
|
+
export function unawaitedLine(h: PipelineHost) {
|
|
85
|
+
const keys = [...new Set((h.plan.flow_calls ?? []).filter((f) => f.parent === null).flatMap((f) => f.unawaited ?? []))];
|
|
86
|
+
if (!keys.length) return nothing;
|
|
87
|
+
return html`<div class="unawaited" title=${unawaitedTip()}>
|
|
88
|
+
<span class="meta">${unawaitedCount(keys.length)}</span>
|
|
89
|
+
${keys.map((k) => {
|
|
90
|
+
const n = h.links.byKey.get(k);
|
|
91
|
+
return taskChip(h.el, n ? kindUnit(n) : k, n ?? { key: k });
|
|
92
|
+
})}
|
|
93
|
+
</div>`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The forecast, when it expects tasks this run has not created: their count, time and cost, and the kinds not in the plan yet. */
|
|
97
|
+
export function forecastNote(plan: Plan, forecast: Forecast | null) {
|
|
98
|
+
if (!forecastShown(forecast)) return nothing;
|
|
99
|
+
const missing = notInPlanLine(notInPlan(forecast, plan));
|
|
100
|
+
return html`<div class="forecast" title=${forecastTip(forecast)}><span class="expected-mark" aria-hidden="true"></span>${forecastLine(forecast)}${missing
|
|
101
|
+
? html`<span class="sep" aria-hidden="true"> | </span>${missing}`
|
|
102
|
+
: nothing}</div>`;
|
|
103
|
+
}
|
|
@@ -12,6 +12,8 @@ import { doneOf, exceptions, executorWord, neighbours, notDone, stateWord, taskS
|
|
|
12
12
|
import { executorIcon } from '../../../words/executor.js';
|
|
13
13
|
import { fmtDur, kindUnit, listText, usd } from '../../../words/format.js';
|
|
14
14
|
import { deadlineAtText, passCount, passLabel, passSpread, passesRan, timeBoxFacts, timeBoxTip } from '../../../words/index.js';
|
|
15
|
+
import { stoppedAt, stoppedHere } from '../../../words/flow-calls.js';
|
|
16
|
+
import { taskChip } from '../../shared/task-chip.js';
|
|
15
17
|
import { primary, select, tasksOf } from './actions.js';
|
|
16
18
|
import type { PipelineHost } from './host.js';
|
|
17
19
|
|
|
@@ -39,7 +41,8 @@ function inspect(h: PipelineHost, s: Selected) {
|
|
|
39
41
|
if (s.type === 'node') {
|
|
40
42
|
const n = s.node;
|
|
41
43
|
const c = n.counts;
|
|
42
|
-
const {
|
|
44
|
+
const { given, order, created, feeds } = neighbours(n, h.graph);
|
|
45
|
+
const earlier = edgeRows(row, list, given, order, created);
|
|
43
46
|
const states = exceptions(n, h.live).map(([cls, t]) => html`<div class=${cls}>${t}</div>`);
|
|
44
47
|
if (n.type === 'flow') {
|
|
45
48
|
return html`<h4 title=${n.name}>${innerIcon()}${n.label}</h4>
|
|
@@ -48,8 +51,7 @@ function inspect(h: PipelineHost, s: Selected) {
|
|
|
48
51
|
${row(msg('Flow calls'), n.calls)}
|
|
49
52
|
${n.timeBox ? row(msg('Deadline'), html`<span title=${timeBoxTip()}>${timeBoxFacts(n.timeBox)}</span>`) : nothing}
|
|
50
53
|
${row(msg('Tasks', { id: 'tasks', desc: 'A count of tasks' }), html`${doneOf(c)}${states}`)}
|
|
51
|
-
${
|
|
52
|
-
${after.length ? row(msg('Created after'), list(after)) : nothing}
|
|
54
|
+
${earlier}
|
|
53
55
|
${row(msg('Later tasks'), list(feeds))}
|
|
54
56
|
</dl>
|
|
55
57
|
<div class="acts">${link(msg(str`Inside ${n.label}`), () => primary(h, s), true)}${clear}</div>`;
|
|
@@ -59,8 +61,7 @@ function inspect(h: PipelineHost, s: Selected) {
|
|
|
59
61
|
<dl>
|
|
60
62
|
${n.kinds.length > 1 ? row(msg('Kinds'), listText(n.kinds)) : nothing}
|
|
61
63
|
${row(msg('Tasks', { id: 'tasks', desc: 'A count of tasks' }), html`${doneOf(c)}${states}`)}
|
|
62
|
-
${
|
|
63
|
-
${after.length ? row(msg('Created after'), list(after)) : nothing}
|
|
64
|
+
${earlier}
|
|
64
65
|
${row(msg('Later tasks'), list(feeds))}
|
|
65
66
|
</dl>
|
|
66
67
|
<div class="acts">${link(msg(str`Tasks of ${n.label}`), () => primary(h, s), true)}${clear}</div>`;
|
|
@@ -82,6 +83,7 @@ function inspect(h: PipelineHost, s: Selected) {
|
|
|
82
83
|
${m.deadlineAt ? row(msg('Deadline'), html`<span title=${timeBoxTip()}>${deadlineAtText(m.deadlineAt)}</span>`) : nothing}
|
|
83
84
|
${c ? row(msg('Tasks', { id: 'tasks', desc: 'A count of tasks' }), html`${doneOf(c)}${notDone(c, h.live).map((t) => html`<div>${t}</div>`)}`) : nothing}
|
|
84
85
|
${m.error !== undefined ? row(msg('Error'), html`<span class="err">${m.error || msg('Flow call stopped with an error')}</span>`) : nothing}
|
|
86
|
+
${stopRow(h, row, m.key)}
|
|
85
87
|
</dl>
|
|
86
88
|
<div class="acts">${link(msg(str`Inside ${shortName(m.flow)}`), () => primary(h, s), true)}${clear}</div>`;
|
|
87
89
|
}
|
|
@@ -97,8 +99,46 @@ function inspect(h: PipelineHost, s: Selected) {
|
|
|
97
99
|
${row(msg('Attempts'), ran.length || msg('none', { id: 'none', desc: 'An empty value' }))}
|
|
98
100
|
${row(msg('Time'), ran.length ? fmtDur(wall) : msg('none', { id: 'none', desc: 'An empty value' }))}
|
|
99
101
|
${row(msg('Cost'), costs.length ? usd(costs.reduce((a, x) => a + x, 0)) : '—')}
|
|
102
|
+
${task ? stoppedRow(h, row, task.key) : nothing}
|
|
100
103
|
</dl>
|
|
101
104
|
<div class="acts">${link(msg('Task detail'), () => primary(h, s), true)}${node.kinds.length === 1
|
|
102
105
|
? link(msg(str`Tasks of ${node.label}`), () => tasksOf(h, node))
|
|
103
106
|
: nothing}${clear}</div>`;
|
|
104
107
|
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The nodes a node comes after, one row per lineage it has: given their output, run after
|
|
111
|
+
* them (order only), created after their results; "Given output of: none" when it has none.
|
|
112
|
+
*/
|
|
113
|
+
function edgeRows(
|
|
114
|
+
row: (dt: string, dd: unknown) => unknown,
|
|
115
|
+
list: (xs: string[]) => string,
|
|
116
|
+
given: string[],
|
|
117
|
+
order: string[],
|
|
118
|
+
created: string[],
|
|
119
|
+
) {
|
|
120
|
+
const none = !given.length && !order.length && !created.length;
|
|
121
|
+
return html`${given.length || none ? row(msg('Given output of'), list(given)) : nothing}
|
|
122
|
+
${order.length ? row(msg('Runs after'), list(order)) : nothing}
|
|
123
|
+
${created.length ? row(msg('Created after'), list(created)) : nothing}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** A flow call's mark that raised: where it stopped and the failed task it stopped for, which opens. */
|
|
127
|
+
function stopRow(h: PipelineHost, row: (dt: string, dd: unknown) => unknown, id: string) {
|
|
128
|
+
const call = h.plan.flow_calls?.find((f) => f.id === id);
|
|
129
|
+
if (!call || call.state !== 'raised') return nothing;
|
|
130
|
+
const by = call.raised_by ? h.links.byKey.get(call.raised_by) : undefined;
|
|
131
|
+
const name = by ? kindUnit(by) : (call.raised_by ?? '');
|
|
132
|
+
const text = stoppedHere(call, name, h.run);
|
|
133
|
+
if (!text) return nothing;
|
|
134
|
+
return row(msg('Stopped', { id: 'flow-call-stopped', desc: 'Where a flow call stopped, and the failure that stopped it' }), html`<div>${text}</div>${call.raised_by
|
|
135
|
+
? html`<div class="down">${taskChip(h.el, name, by ?? { key: call.raised_by })}</div>`
|
|
136
|
+
: nothing}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** A task's mark whose failure stopped flow calls (`raised_by`): each, with where it stopped. */
|
|
140
|
+
function stoppedRow(h: PipelineHost, row: (dt: string, dd: unknown) => unknown, key: string) {
|
|
141
|
+
const calls = (h.plan.flow_calls ?? []).filter((f) => f.raised_by === key);
|
|
142
|
+
if (!calls.length) return nothing;
|
|
143
|
+
return row(msg('Stopped', { id: 'flow-call-stopped', desc: 'Where a flow call stopped, and the failure that stopped it' }), calls.map((f) => html`<div>${stoppedAt(f, h.run)}</div>`));
|
|
144
|
+
}
|
|
@@ -32,7 +32,7 @@ export interface OutlineItem {
|
|
|
32
32
|
parts: { id: string; layer: string[]; name: string }[];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const ZERO = (): NodeCounts => ({ total: 0, done: 0, partial: 0, failed: 0, running: 0, pending: 0, skipped: 0, blocked: 0 });
|
|
35
|
+
const ZERO = (): NodeCounts => ({ total: 0, done: 0, partial: 0, failed: 0, running: 0, pending: 0, skipped: 0, blocked: 0, cancelled: 0 });
|
|
36
36
|
|
|
37
37
|
/** The outline of the flow: the places of `stats` (from the top of the flow) as a tree, a loop an item around the places in it. */
|
|
38
38
|
export function outlineTree(stats: ReadonlyMap<string, PlaceStats>): OutlineItem[] {
|
|
@@ -3,6 +3,7 @@ import type { LayeredGraphStrings } from '@contraagent/elements/graph';
|
|
|
3
3
|
import type { MarkMatrixStrings } from '@contraagent/elements/matrix';
|
|
4
4
|
import { msg, str } from '@lit/localize';
|
|
5
5
|
import { listText } from '../../../words/format.js';
|
|
6
|
+
import { lineageWord } from '../../../words/lineage.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Every word of the graph (`up`: Escape's hint without a selection, '' at the top;
|
|
@@ -29,8 +30,9 @@ export function graphStrings(up: boolean, functions: boolean): Omit<LayeredGraph
|
|
|
29
30
|
stubTip: msg('Outside this layer'),
|
|
30
31
|
more: (n) => msg(str`+${n}`),
|
|
31
32
|
badge: msg('number of passes'),
|
|
32
|
-
solidEdge:
|
|
33
|
-
|
|
33
|
+
solidEdge: lineageWord('given_from'),
|
|
34
|
+
dottedEdge: lineageWord('after'),
|
|
35
|
+
dashedEdge: lineageWord('created_from'),
|
|
34
36
|
cycle: msg('next pass'),
|
|
35
37
|
drill: msg('has a level below'),
|
|
36
38
|
open: msg('Show inside'),
|
|
@@ -38,7 +40,8 @@ export function graphStrings(up: boolean, functions: boolean): Omit<LayeredGraph
|
|
|
38
40
|
iconNames: { agent: msg('agent task'), python: msg('Python task') },
|
|
39
41
|
nameHeader: functions ? msg('Kind or function') : msg('Kind'),
|
|
40
42
|
statusHeader: msg('Status', { id: 'status', desc: 'The status of a task: done, failed, not started' }),
|
|
41
|
-
solidHeader: msg('
|
|
43
|
+
solidHeader: msg('Given output of'),
|
|
44
|
+
dottedHeader: msg('Runs after'),
|
|
42
45
|
dashedHeader: msg('Created after'),
|
|
43
46
|
laterHeader: msg('Later tasks'),
|
|
44
47
|
none: msg('none', { id: 'none', desc: 'An empty value' }),
|
|
@@ -54,10 +54,14 @@ export const graphStyles = [
|
|
|
54
54
|
.switch:focus-visible { outline: var(--focus-width) solid var(--focus); outline-offset: 1px; }
|
|
55
55
|
.switch[aria-pressed='true'] { color: var(--text); background: var(--accent-bg); border-color: var(--accent); }
|
|
56
56
|
.opens { display: inline-flex; gap: 6px; margin-left: auto; }
|
|
57
|
+
.forecast { font-size: 12px; color: var(--text-muted); margin: 0 0 8px; display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
|
|
58
|
+
.forecast .expected-mark { display: inline-block; width: 18px; height: 10px; border: 1.5px dotted var(--line2); border-radius: 999px; margin-right: 4px; }
|
|
59
|
+
.unawaited { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0 0 8px; }
|
|
60
|
+
.card .down { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
|
|
57
61
|
|
|
58
62
|
/* The outline: a column beside the graph, or a part above it on a narrow frame. */
|
|
59
|
-
.whole { display:
|
|
60
|
-
.whole
|
|
63
|
+
.whole, .whole > .g { display: flex; flex-direction: column; flex: auto; min-height: 0; min-width: 0; }
|
|
64
|
+
.whole.side { display: grid; grid-template-columns: minmax(200px, 282px) minmax(0, 1fr); gap: 14px; align-items: start; }
|
|
61
65
|
.outline { font-size: 12px; max-height: 70vh; overflow: auto; padding: 2px 6px 2px 0; border-right: 1px solid var(--line); }
|
|
62
66
|
.otree, .otree ul { list-style: none; margin: 0; padding: 0; }
|
|
63
67
|
.otree ul { padding-left: 10px; }
|
|
@@ -374,6 +374,15 @@ export class CaMonitor extends LitElement {
|
|
|
374
374
|
else this.mainScroll.restore();
|
|
375
375
|
this.pendingScroll = null;
|
|
376
376
|
}
|
|
377
|
+
if (this.snap && this.viewRoute) this.mainScroll.update(this.complete());
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Whether all of what the place shows has arrived: the run's replay, the trends asked for, the hosts. */
|
|
381
|
+
private complete(): boolean {
|
|
382
|
+
const { snap, viewRoute } = this;
|
|
383
|
+
if (viewRoute.page === 'hosts') return snap.hosts !== null;
|
|
384
|
+
if (viewRoute.page === 'trends') return !!snap.trends?.data && !snap.trends.loading;
|
|
385
|
+
return snap.run === viewRoute.run && snap.state.ready;
|
|
377
386
|
}
|
|
378
387
|
|
|
379
388
|
/** Move the line under the selected tab when the tab bar changes size. */
|
|
@@ -231,6 +231,7 @@ export class CaRunPicker extends LitElement {
|
|
|
231
231
|
if (t.not_started) parts.push(stateCount('not_started', t.not_started));
|
|
232
232
|
if (t.partial) parts.push(stateCount('partial', t.partial));
|
|
233
233
|
if (t.skipped) parts.push(stateCount('skipped', t.skipped));
|
|
234
|
+
if (t.cancelled) parts.push(stateCount('cancelled', t.cancelled));
|
|
234
235
|
if (t.running) parts.push(stateCount('running', t.running));
|
|
235
236
|
if (t.waiting) parts.push(stateCount('waiting', t.waiting));
|
|
236
237
|
if (t.not_run) parts.push(stateCount('not_run', t.not_run));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { deriveRun, type JournalRow, type LaunchEvent, type MonitorClient, type Plan, type TaskDetail, type TaskOutcome } from '@contraagent/api';
|
|
1
|
+
import { deriveRun, type JournalRow, type LaunchEvent, type MonitorClient, type Plan, type TaskDetail, type TaskOutcome, type UpstreamTask } from '@contraagent/api';
|
|
2
2
|
import { localized, msg } from '@lit/localize';
|
|
3
3
|
import { LitElement, html, nothing, type PropertyValues } from 'lit';
|
|
4
4
|
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
@@ -129,7 +129,9 @@ export class CaTaskView extends LitElement {
|
|
|
129
129
|
const io = d.io;
|
|
130
130
|
const py = isPy(d) && !d.stream;
|
|
131
131
|
const n: Partial<Record<TaskView, number>> = {
|
|
132
|
-
input:
|
|
132
|
+
input:
|
|
133
|
+
new Set([...io.inputs.map((o) => o.path), ...(io.input_files ?? []).map((f) => f.path), ...io.upstream.filter(readsFrom).flatMap((u) => u.outputs.map((o) => o.path))]).size +
|
|
134
|
+
Object.keys(io.params).length,
|
|
133
135
|
output: io.outputs.length,
|
|
134
136
|
};
|
|
135
137
|
return html`<div class="tviews" role="tablist">
|
|
@@ -155,3 +157,6 @@ declare global {
|
|
|
155
157
|
'ca-task-view': CaTaskView;
|
|
156
158
|
}
|
|
157
159
|
}
|
|
160
|
+
|
|
161
|
+
/** An earlier task this one reads from or waits for (not one it was only created after); every one of an export written before lineage. */
|
|
162
|
+
const readsFrom = (u: UpstreamTask): boolean => !u.lineage?.length || u.lineage.some((l) => l !== 'created_from');
|
|
@@ -5,12 +5,14 @@ import { msg, str } from '@lit/localize';
|
|
|
5
5
|
import { html, nothing, type TemplateResult } from 'lit';
|
|
6
6
|
import { toneAttr } from '@contraagent/elements';
|
|
7
7
|
import { pill } from '@contraagent/elements/primitives';
|
|
8
|
-
import { facts, fmtDur, kindUnit, listText, localTime, tokenCount, unreadEvents, usd } from '../../words/format.js';
|
|
8
|
+
import { facts, fmtDur, kindUnit, listText, localTime, tokenCount, unreadEvents, usageUnknown, usageUnknownWords, usd } from '../../words/format.js';
|
|
9
9
|
import { attemptNames, deferredText, changedFiles, changedFilesTip, changeLabel, credentialLabel, credentialTip, locksLabel, locksTip, modelSwitch, outcomeLabel, priorityText, priorityTip, replayedLabel, replayedTip, stopLabel, type ChangeKind } from '../../words/index.js';
|
|
10
10
|
import { contextWarnedText, contextWarnedTip, peakContextLine, peakContextTip, peakTry } from '../../words/context.js';
|
|
11
11
|
import { readingShort, readingTip } from '../../words/quota.js';
|
|
12
12
|
import { safetyStopCount, safetyStopLine, safetyStopTip } from '../../words/safety.js';
|
|
13
13
|
import { customColumnTip, customValues } from '../../words/row-extras.js';
|
|
14
|
+
import { divergenceTip, divergenceWord, rerunLine, rerunOf } from '../../words/rerun.js';
|
|
15
|
+
import { stoppedAt } from '../../words/flow-calls.js';
|
|
14
16
|
import { isStop, type Tone } from '../../lib/tone.js';
|
|
15
17
|
import { executorIcon } from '../../words/executor.js';
|
|
16
18
|
import '@contraagent/elements/json-tree/define';
|
|
@@ -120,6 +122,7 @@ export function taskHeader(c: TaskContext, key: string, onSearch: (query: string
|
|
|
120
122
|
const otherModels = session ? [...new Set(byTs.map((r) => r.model).filter((x): x is string => !!x && x !== who.model))] : [];
|
|
121
123
|
const first = rows[0];
|
|
122
124
|
const tries = rows.filter(isLaunched);
|
|
125
|
+
const unknownUsage = usageUnknownWords(usageUnknown(tries));
|
|
123
126
|
const extras = Object.entries(d).filter(([k]) => !SHOWN_KEYS.has(k));
|
|
124
127
|
const [verdictText, tone] = verdict(c);
|
|
125
128
|
const kindName = py ? msg('Python task') : msg('Agent task');
|
|
@@ -136,7 +139,8 @@ export function taskHeader(c: TaskContext, key: string, onSearch: (query: string
|
|
|
136
139
|
</div>`
|
|
137
140
|
: nothing}
|
|
138
141
|
${node?.priority ? html`<div class="meta" title=${priorityTip()}>${priorityText(node.priority)}</div>` : nothing}
|
|
139
|
-
${
|
|
142
|
+
${rerunMeta(rows, node?.rerun)}
|
|
143
|
+
${stoppedFlowCalls(c)}
|
|
140
144
|
${session
|
|
141
145
|
? html`<div class="whos">
|
|
142
146
|
${tagChips([...whoTags(who), ...identityTags(tries)])}
|
|
@@ -166,6 +170,9 @@ export function taskHeader(c: TaskContext, key: string, onSearch: (query: string
|
|
|
166
170
|
<span aria-hidden="true">⚠</span> ${msg(str`Unrecognized events: ${unreadEvents(tries)}`)}
|
|
167
171
|
</div>`
|
|
168
172
|
: nothing}
|
|
173
|
+
${unknownUsage
|
|
174
|
+
? html`<div class="meta unread" title=${unknownUsage.tip}><span aria-hidden="true">⚠</span> ${unknownUsage.label}</div>`
|
|
175
|
+
: nothing}
|
|
169
176
|
${d.task_detail_error ? html`<div class="meta v-bad">${msg(str`The task_detail callback failed: ${d.task_detail_error ?? ''}`)}</div>` : nothing}
|
|
170
177
|
${launches.map(
|
|
171
178
|
(lc) => html`<details class="launch">
|
|
@@ -286,6 +293,8 @@ function quotaLine(c: TaskContext, tries: JournalRow[]) {
|
|
|
286
293
|
/** One finished attempt: how it ended, how long, its cost, its safety stops; the rest (when, corrections, model, model switch) in its tooltip. */
|
|
287
294
|
function runChip(c: TaskContext, r: JournalRow, name: string, session: boolean, extras: readonly string[]) {
|
|
288
295
|
const look = r.stop === 'partial' ? 'part' : r.ok ? 'ok' : 'bad';
|
|
296
|
+
const rows = c.detail.rows;
|
|
297
|
+
const rerun = rerunOf(rows, rows.indexOf(r));
|
|
289
298
|
const played = !!c.replayMarks && !!r.replayed;
|
|
290
299
|
const reason = runRelative(r.reason, c.run);
|
|
291
300
|
const sw = modelSwitch(r.hops, null, r.skipped_hops);
|
|
@@ -300,12 +309,34 @@ function runChip(c: TaskContext, r: JournalRow, name: string, session: boolean,
|
|
|
300
309
|
reason,
|
|
301
310
|
r.route || '',
|
|
302
311
|
played ? replayedTip() : '',
|
|
312
|
+
rerun ? rerunLine(rerun) : '',
|
|
303
313
|
...customValues(r, extras).map(([k, v]) => `${k}: ${v}`),
|
|
304
314
|
]
|
|
305
315
|
.filter(Boolean)
|
|
306
316
|
.join('\n');
|
|
307
317
|
const cost = r.cost_usd !== null && r.cost_usd !== undefined ? usd(r.cost_usd) : '';
|
|
308
318
|
return html`<span class="runchip ${look}" title=${tip}>
|
|
309
|
-
${mark(r)} ${facts(name, stopLabel(r.stop, r.reason), fmtDur(r.wall_s), cost, safety, played ? replayedLabel() : '')}
|
|
319
|
+
${mark(r)} ${facts(name, stopLabel(r.stop, r.reason), fmtDur(r.wall_s), cost, safety, played ? replayedLabel() : '', rerun?.divergence ? divergenceWord() : '')}
|
|
310
320
|
</span>`;
|
|
311
321
|
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Why a resume ran the task again, from its latest journal row with a `rerun`: the parts
|
|
325
|
+
* of its fingerprint that changed, and a divergence; the plan's `rerun` before its row
|
|
326
|
+
* arrives. The recorded reason is the tooltip.
|
|
327
|
+
*/
|
|
328
|
+
function rerunMeta(rows: readonly JournalRow[], planned: string | undefined) {
|
|
329
|
+
const i = rows.map((r) => !!r.rerun).lastIndexOf(true);
|
|
330
|
+
const r = i >= 0 ? rerunOf(rows, i) : planned ? { text: planned, changed: [], divergence: planned.startsWith('divergence') } : null;
|
|
331
|
+
if (!r) return nothing;
|
|
332
|
+
return html`<div class="meta rerun" title=${r.text}>${rerunLine(r)}${r.divergence
|
|
333
|
+
? html` <span class="divergence" title=${divergenceTip()}>${pill({ text: divergenceWord(), tone: 'attention' })}</span>`
|
|
334
|
+
: nothing}</div>`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** The flow calls this task's failure stopped (`FlowCallNode.raised_by`), each with where: "This failure stopped review_loop at flow.py:42". */
|
|
338
|
+
function stoppedFlowCalls(c: TaskContext) {
|
|
339
|
+
const calls = (c.plan?.flow_calls ?? []).filter((f) => f.raised_by === c.detail.key);
|
|
340
|
+
if (!calls.length) return nothing;
|
|
341
|
+
return html`${calls.map((f) => html`<div class="meta stopped" title=${f.error ?? ''}>${stoppedAt(f, c.run)}</div>`)}`;
|
|
342
|
+
}
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
// The input and output panes of `ca-task-view`: what the task read (parameters,
|
|
2
2
|
// files, earlier tasks, the prompt) and what it wrote (files against their
|
|
3
3
|
// contracts, the outcome, tokens and cost, later tasks).
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
isLaunched,
|
|
6
|
+
isPython,
|
|
7
|
+
runReadings,
|
|
8
|
+
runRelative,
|
|
9
|
+
runStarts,
|
|
10
|
+
tryQuota,
|
|
11
|
+
type DownstreamTask,
|
|
12
|
+
type JournalRow,
|
|
13
|
+
type Lineage,
|
|
14
|
+
type Outcome,
|
|
15
|
+
type SourcedReading,
|
|
16
|
+
type TaskInputFile,
|
|
17
|
+
} from '@contraagent/api';
|
|
5
18
|
import { msg, str } from '@lit/localize';
|
|
6
19
|
import { html, nothing, type TemplateResult } from 'lit';
|
|
7
20
|
import { pill } from '@contraagent/elements/primitives';
|
|
@@ -17,6 +30,7 @@ import { ioFileCard } from '../../shared/file-card.js';
|
|
|
17
30
|
import { taskChip } from '../../shared/task-chip.js';
|
|
18
31
|
import { inputView, notPassed, type EarlierTask, type InputFile } from '../inputs.js';
|
|
19
32
|
import { roundLabel, roundWhy } from '../../../words/rounds.js';
|
|
33
|
+
import { inputFileCheckWord, lineageTip, lineageWord } from '../../../words/lineage.js';
|
|
20
34
|
import { correctionsOf, hadSession, isPy, producerOf, stateOf, taskLink, type TaskContext } from '../context.js';
|
|
21
35
|
|
|
22
36
|
const valueText = (v: unknown): string => (typeof v === 'string' ? v : JSON.stringify(v) ?? String(v));
|
|
@@ -31,7 +45,11 @@ const sameJson = (a: unknown, b: unknown): boolean => {
|
|
|
31
45
|
/** A task's outcome as a pill, in its words. */
|
|
32
46
|
const outcomeChip = (o: Outcome) => pill({ text: outcomeLabel(o), tone: outcomeTone(o), mark: outcomeMark(o) });
|
|
33
47
|
|
|
34
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* What the task read: its parameters, its input files (`InputFile` params), the files
|
|
50
|
+
* an input contract checks before it starts, the earlier tasks, and (for an agent task)
|
|
51
|
+
* its prompt.
|
|
52
|
+
*/
|
|
35
53
|
export function inputPane(c: TaskContext): TemplateResult {
|
|
36
54
|
const d = c.detail;
|
|
37
55
|
const io = d.io;
|
|
@@ -52,13 +70,37 @@ export function inputPane(c: TaskContext): TemplateResult {
|
|
|
52
70
|
</table>`
|
|
53
71
|
: html`<div class="meta">${msg('No parameters')}</div>`}
|
|
54
72
|
<h4 class="sect-h">${msg('Input files')}</h4>
|
|
55
|
-
${
|
|
73
|
+
${io.input_files?.length ? io.input_files.map((f) => inputFileCard(c, f)) : html`<div class="meta">${msg('No input files')}</div>`}
|
|
74
|
+
${files.length
|
|
75
|
+
? html`<h4 class="sect-h">${msg('Checked files it reads')}</h4>
|
|
76
|
+
${files.map((f) => inputCard(c, f))}`
|
|
77
|
+
: nothing}
|
|
56
78
|
<h4 class="sect-h">${msg('Earlier tasks')}</h4>
|
|
57
79
|
${earlier.length ? earlier.map((e) => earlierTask(c, e)) : html`<div class="meta">${msg('No earlier tasks')}</div>`}
|
|
58
80
|
${isPy(d) ? nothing : promptSection(c)}`;
|
|
59
81
|
}
|
|
60
82
|
|
|
61
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* One input file of the task (an `InputFile` param): its check at the start, its path,
|
|
85
|
+
* the param that holds it, its hash, and the earlier task whose output file it is.
|
|
86
|
+
*/
|
|
87
|
+
function inputFileCard(c: TaskContext, f: TaskInputFile) {
|
|
88
|
+
const tone = f.check === 'passed' ? 'done' : f.check === 'refused' ? 'failed' : 'neutral';
|
|
89
|
+
const mark = f.check === 'passed' ? 'done' : f.check === 'refused' ? 'failed' : 'pending';
|
|
90
|
+
const writer = f.writer ? (c.detail.io.upstream.find((u) => u.key === f.writer) ?? c.outcomes.get(f.writer)) : undefined;
|
|
91
|
+
const origin = f.writer
|
|
92
|
+
? html`<span>${msg(html`Output of ${taskLink(c, writer ?? { key: f.writer, kind: f.writer, unit: '' })}`)}</span>`
|
|
93
|
+
: nothing;
|
|
94
|
+
return html`<div class="io-card one infile">
|
|
95
|
+
<div class="io-hd">${pill({ text: inputFileCheckWord(f.check), tone, mark })}<span class="jmono ct-path" title=${f.path}>${runRelative(f.path, c.run)}</span></div>
|
|
96
|
+
<div class="meta io-from"><span title=${f.sha256}>${facts(msg(str`Param ${f.param}`), `sha256 ${f.sha256.slice(0, 12)}`)}</span>${f.writer
|
|
97
|
+
? html` | ${origin}`
|
|
98
|
+
: nothing}</div>
|
|
99
|
+
${f.reason ? html`<div class="meta err">${f.reason}</div>` : nothing}
|
|
100
|
+
</div>`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** One file an input contract checks before the task starts: its check, and the earlier task that writes it or that it is from before the run. */
|
|
62
104
|
function inputCard(c: TaskContext, f: InputFile) {
|
|
63
105
|
const u = f.from;
|
|
64
106
|
const state = u ? c.outcomes.get(u.key)?.outcome : undefined;
|
|
@@ -72,7 +114,11 @@ function inputCard(c: TaskContext, f: InputFile) {
|
|
|
72
114
|
return ioFileCard({ ...f.spec, status: st }, 'input', c.run, { origin, chip });
|
|
73
115
|
}
|
|
74
116
|
|
|
75
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* One earlier task: its status, how this task is joined to it (its lineage, in the words of
|
|
119
|
+
* the graph's edges), how many of its output files this task reads, and the others folded
|
|
120
|
+
* under it (open when one did not pass).
|
|
121
|
+
*/
|
|
76
122
|
function earlierTask(c: TaskContext, e: EarlierTask) {
|
|
77
123
|
const u = e.task;
|
|
78
124
|
const kindName = isPython(u) ? msg('Python task') : msg('Agent task');
|
|
@@ -85,6 +131,7 @@ function earlierTask(c: TaskContext, e: EarlierTask) {
|
|
|
85
131
|
${executorIcon(isPython(u), kindName)}
|
|
86
132
|
${taskLink(c, u)}
|
|
87
133
|
${state ? outcomeChip(state) : nothing}
|
|
134
|
+
${lineageTags(u.lineage ?? [])}
|
|
88
135
|
<span class="meta">${summary}</span>
|
|
89
136
|
</div>`;
|
|
90
137
|
if (!e.rest.length) return html`<div class="upstream">${head}</div>`;
|
|
@@ -184,7 +231,24 @@ export function outputPane(c: TaskContext): TemplateResult {
|
|
|
184
231
|
: nothing}
|
|
185
232
|
${usageTable(c, d.rows)}
|
|
186
233
|
<h4 class="sect-h">${msg('Later tasks')}</h4>
|
|
187
|
-
${io.downstream.length
|
|
188
|
-
|
|
189
|
-
|
|
234
|
+
${io.downstream.length ? laterTasks(c, io.downstream) : html`<div class="meta">${msg('No later tasks')}</div>`}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** A task's lineage as tags, in the words of the graph's edges, each with the field it comes from as its tooltip. */
|
|
238
|
+
function lineageTags(lineage: readonly Lineage[]) {
|
|
239
|
+
return lineage.map((l) => html`<span class="lineage" data-lineage=${l} title=${lineageTip(l)}>${lineageWord(l)}</span>`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The later tasks, a row of chips per lineage (by the strongest each has): given output,
|
|
244
|
+
* runs after, created after result; one row without a tag for those an export written
|
|
245
|
+
* before lineage holds.
|
|
246
|
+
*/
|
|
247
|
+
function laterTasks(c: TaskContext, down: readonly DownstreamTask[]) {
|
|
248
|
+
const order: Lineage[] = ['given_from', 'after', 'created_from'];
|
|
249
|
+
const first = (x: DownstreamTask): Lineage | null => order.find((l) => (x.lineage ?? []).includes(l)) ?? null;
|
|
250
|
+
return [...order, null]
|
|
251
|
+
.map((l) => [l, down.filter((x) => first(x) === l)] as const)
|
|
252
|
+
.filter(([, xs]) => xs.length)
|
|
253
|
+
.map(([l, xs]) => html`<div class="down">${l ? lineageTags([l]) : nothing}${xs.map((x) => taskChip(c.host, kindUnit(x), x))}</div>`);
|
|
190
254
|
}
|
|
@@ -56,8 +56,12 @@ export const taskViewStyles = css`
|
|
|
56
56
|
/* A positioned box: what is placed in it (a screen reader's text) scrolls with it, not past the view's end. */
|
|
57
57
|
.pane { flex: 1 1 auto; min-height: 0; overflow: auto; position: relative; }
|
|
58
58
|
.upstream { margin-bottom: 12px; }
|
|
59
|
-
.up-hd { display: flex; align-items: center; gap: 10px; margin: 2px 0 6px; }
|
|
60
|
-
.down { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
59
|
+
.up-hd { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; margin: 2px 0 6px; }
|
|
60
|
+
.down { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 6px; }
|
|
61
|
+
/* How a task is joined to an earlier one, drawn as the graph draws its edge: given output solid, runs after dotted, created after result dashed. */
|
|
62
|
+
.lineage { font-size: 11px; color: var(--text-2); border: 1.5px solid var(--line2); border-radius: 999px; padding: 0 7px; white-space: nowrap; }
|
|
63
|
+
.lineage[data-lineage='after'] { border-style: dotted; }
|
|
64
|
+
.lineage[data-lineage='created_from'] { border-style: dashed; }
|
|
61
65
|
.notrans { flex: 1 1 auto; border: 1px solid var(--line); border-radius: 14px; }
|
|
62
66
|
details.upstream > summary { cursor: pointer; list-style: none; }
|
|
63
67
|
details.upstream > summary::-webkit-details-marker { display: none; }
|
package/src/lib/tone.ts
CHANGED
|
@@ -37,6 +37,7 @@ const STOP_TONE: Readonly<Record<Stop, Tone>> = {
|
|
|
37
37
|
partial: 'attention',
|
|
38
38
|
invalid_input: 'attention',
|
|
39
39
|
skipped: 'muted',
|
|
40
|
+
cancelled: 'muted',
|
|
40
41
|
timeout: 'failed',
|
|
41
42
|
max_turns: 'failed',
|
|
42
43
|
budget: 'failed',
|
|
@@ -81,6 +82,7 @@ export function outcomeTone(o: Outcome): Tone {
|
|
|
81
82
|
return 'running';
|
|
82
83
|
case 'waiting':
|
|
83
84
|
case 'skipped':
|
|
85
|
+
case 'cancelled':
|
|
84
86
|
return 'muted';
|
|
85
87
|
}
|
|
86
88
|
}
|