pi-agent-fleet 0.7.0 → 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/package.json +1 -1
- package/src/canvas-client.tsx +20 -9
- package/src/canvas-layout.ts +73 -10
- package/src/canvas.ts +52 -25
package/package.json
CHANGED
package/src/canvas-client.tsx
CHANGED
|
@@ -90,6 +90,9 @@ function minimapColor(status: string): string {
|
|
|
90
90
|
if (status === "killed" || status === "blocked") return cssVar("--wire");
|
|
91
91
|
return cssVar("--line");
|
|
92
92
|
}
|
|
93
|
+
function shortModel(model: string): string {
|
|
94
|
+
return model.split("/").pop()?.replace(/^deepseek-/, "") || model;
|
|
95
|
+
}
|
|
93
96
|
function j<T>(u: string): Promise<T> {
|
|
94
97
|
return fetch(u).then((r) => {
|
|
95
98
|
if (!r.ok) throw new Error(String(r.status));
|
|
@@ -103,6 +106,7 @@ type FleetNodeData = {
|
|
|
103
106
|
selected: boolean;
|
|
104
107
|
demo: boolean;
|
|
105
108
|
gate: boolean;
|
|
109
|
+
nodeRole: "start" | "end" | "gate" | "normal";
|
|
106
110
|
fleet: string | null;
|
|
107
111
|
onOpen: (id: string) => void;
|
|
108
112
|
};
|
|
@@ -110,6 +114,7 @@ type FleetNodeData = {
|
|
|
110
114
|
function FleetNode({ data }: NodeProps<Node<FleetNodeData>>) {
|
|
111
115
|
const { view: n, selected } = data;
|
|
112
116
|
const running = n.status === "running";
|
|
117
|
+
const roleLabel = data.nodeRole === "start" ? "START" : data.nodeRole === "end" ? "END" : data.nodeRole === "gate" ? "GATE" : null;
|
|
113
118
|
|
|
114
119
|
const flags: Array<{ label: string; title: string }> = [];
|
|
115
120
|
if (data.gate) flags.push({ label: "⟳ loop gate", title: "Reviewer gate: its verdict decides whether the fleet iterates again" });
|
|
@@ -129,7 +134,7 @@ function FleetNode({ data }: NodeProps<Node<FleetNodeData>>) {
|
|
|
129
134
|
|
|
130
135
|
return (
|
|
131
136
|
<div
|
|
132
|
-
className={"node " + statusClass(n.status) + (selected ? " sel" : "")}
|
|
137
|
+
className={"node " + statusClass(n.status) + " role-" + data.nodeRole + (selected ? " sel" : "")}
|
|
133
138
|
data-node-id={n.id}
|
|
134
139
|
role="button"
|
|
135
140
|
tabIndex={0}
|
|
@@ -138,28 +143,28 @@ function FleetNode({ data }: NodeProps<Node<FleetNodeData>>) {
|
|
|
138
143
|
onClick={activate}
|
|
139
144
|
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); activate(); } }}
|
|
140
145
|
>
|
|
141
|
-
<Handle type="target" position={Position.
|
|
142
|
-
<Handle type="target" position={Position.
|
|
146
|
+
<Handle type="target" position={Position.Left} />
|
|
147
|
+
<Handle type="target" position={Position.Bottom} id="loopIn" />
|
|
143
148
|
<div className="card-body">
|
|
144
149
|
<div className="node-header">
|
|
145
150
|
<span className={"node-dot" + (running ? " pulse" : "")} style={{ background: minimapColor(n.status) }} aria-hidden="true" />
|
|
146
151
|
<span className="id" title={n.id}>{n.id}</span>
|
|
152
|
+
{roleLabel && <span className="role-badge">{roleLabel}</span>}
|
|
147
153
|
<span className="badge">{n.type}</span>
|
|
148
154
|
</div>
|
|
149
155
|
<div className="status-row">
|
|
150
156
|
{running && <span className="spinner" />}
|
|
151
157
|
<span className="st-word">{n.status}</span>
|
|
152
158
|
{n.effort && <><span>·</span><span>{n.effort}</span></>}
|
|
153
|
-
<span>·</span><span>{n.model}</span>
|
|
159
|
+
<span>·</span><span title={n.model}>{shortModel(n.model)}</span>
|
|
154
160
|
</div>
|
|
155
161
|
<div className="stats">
|
|
156
162
|
{(n.turns | 0)} turns · {(Number(n.tokens || 0) / 1000).toFixed(1)}k tok · ${Number(n.cost_usd_estimate || 0).toFixed(2)}
|
|
157
163
|
</div>
|
|
158
164
|
{n.outputs?.length > 0 && (
|
|
159
165
|
<div className="outputs">
|
|
160
|
-
{n.outputs.map((o
|
|
161
|
-
|
|
162
|
-
))}
|
|
166
|
+
<span className="out-count" title={n.outputs.map((o) => `${o.path} · ${o.kind}`).join("\n")}>{n.outputs.length} output{n.outputs.length === 1 ? "" : "s"}</span>
|
|
167
|
+
<span className="out-chip">{n.outputs[0].kind}</span>
|
|
163
168
|
</div>
|
|
164
169
|
)}
|
|
165
170
|
{flags.length > 0 && (
|
|
@@ -172,7 +177,7 @@ function FleetNode({ data }: NodeProps<Node<FleetNodeData>>) {
|
|
|
172
177
|
{n.status_note && <div className="note nodrag">{n.status_note}</div>}
|
|
173
178
|
{failReason && <div className="fail-reason nodrag">{failReason}</div>}
|
|
174
179
|
</div>
|
|
175
|
-
<Handle type="source" position={Position.
|
|
180
|
+
<Handle type="source" position={Position.Right} />
|
|
176
181
|
<Handle type="source" position={Position.Bottom} id="loop" />
|
|
177
182
|
</div>
|
|
178
183
|
);
|
|
@@ -573,12 +578,14 @@ function Flow() {
|
|
|
573
578
|
const gateId = payload.loop
|
|
574
579
|
? (payload.nodes.find((n) => n.id === payload.loop!.gate) ?? payload.nodes.find((n) => n.type === payload.loop!.gate))?.id
|
|
575
580
|
: undefined;
|
|
581
|
+
const hasOutgoing = new Set(payload.edges.map((e) => e.from));
|
|
576
582
|
setNodes((prev) => {
|
|
577
583
|
const byId: Record<string, Node<FleetNodeData>> = {};
|
|
578
584
|
prev.forEach((n) => { byId[n.id] = n; });
|
|
579
585
|
return payload.nodes.map((v) => {
|
|
580
586
|
const existing = byId[v.id];
|
|
581
|
-
const
|
|
587
|
+
const nodeRole: FleetNodeData["nodeRole"] = v.id === gateId ? "gate" : v.depends_on.length === 0 ? "start" : !hasOutgoing.has(v.id) ? "end" : "normal";
|
|
588
|
+
const data: FleetNodeData = { view: v, selected: selected === v.id, demo, gate: v.id === gateId, nodeRole, fleet, onOpen };
|
|
582
589
|
return existing
|
|
583
590
|
? { ...existing, data }
|
|
584
591
|
: { id: v.id, type: "fleet", position: pos[v.id] ?? { x: 0, y: 0 }, data, width: NODE_W };
|
|
@@ -593,7 +600,11 @@ function Flow() {
|
|
|
593
600
|
id: "e" + i,
|
|
594
601
|
source: e.from,
|
|
595
602
|
target: e.to,
|
|
603
|
+
type: "smoothstep",
|
|
604
|
+
pathOptions: { borderRadius: 18 },
|
|
596
605
|
animated: payload.nodes.find((n) => n.id === e.to)?.status === "running",
|
|
606
|
+
style: { strokeWidth: 1.6 },
|
|
607
|
+
markerEnd: { type: MarkerType.ArrowClosed, color: "var(--edge)" } as Edge["markerEnd"],
|
|
597
608
|
}));
|
|
598
609
|
|
|
599
610
|
// feedback loop: the gate node re-triggers the iterate roots each iteration
|
package/src/canvas-layout.ts
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
export const NODE_W =
|
|
2
|
-
export const NODE_H_GAP =
|
|
3
|
-
export const NODE_W_GAP =
|
|
1
|
+
export const NODE_W = 304;
|
|
2
|
+
export const NODE_H_GAP = 44;
|
|
3
|
+
export const NODE_W_GAP = 156;
|
|
4
|
+
|
|
5
|
+
export interface LayoutNode {
|
|
6
|
+
id: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
outputs?: Array<unknown>;
|
|
9
|
+
depends_on?: string[];
|
|
10
|
+
}
|
|
4
11
|
|
|
5
12
|
export function excerptText(text: string, max: number): { excerpt: string; truncated: boolean } {
|
|
6
13
|
const truncated = text.length > max;
|
|
7
14
|
return { excerpt: truncated ? text.slice(0, max) + "…" : text, truncated };
|
|
8
15
|
}
|
|
9
16
|
|
|
17
|
+
export function estimateNodeHeight(node: LayoutNode): number {
|
|
18
|
+
const outputs = node.outputs?.length ?? 0;
|
|
19
|
+
const outputRows = outputs > 0 ? 1 : 0;
|
|
20
|
+
const gateRow = node.type === "reviewer" ? 18 : 0;
|
|
21
|
+
return 126 + outputRows * 26 + gateRow;
|
|
22
|
+
}
|
|
23
|
+
|
|
10
24
|
export function topoLayers(ids: string[], edges: Array<{ from: string; to: string }>): string[][] {
|
|
11
25
|
const indeg: Record<string, number> = {};
|
|
12
26
|
const nextById: Record<string, string[]> = {};
|
|
@@ -64,20 +78,69 @@ export function reduceCrossings(layers: string[][], edges: Array<{ from: string;
|
|
|
64
78
|
return ordered;
|
|
65
79
|
}
|
|
66
80
|
|
|
81
|
+
function longestPathStages(ids: string[], edges: Array<{ from: string; to: string }>): string[][] {
|
|
82
|
+
const indeg: Record<string, number> = {};
|
|
83
|
+
const nextById: Record<string, string[]> = {};
|
|
84
|
+
const rank: Record<string, number> = {};
|
|
85
|
+
for (const id of ids) {
|
|
86
|
+
indeg[id] = 0;
|
|
87
|
+
nextById[id] = [];
|
|
88
|
+
rank[id] = 0;
|
|
89
|
+
}
|
|
90
|
+
for (const edge of edges) {
|
|
91
|
+
if (!(edge.from in indeg) || !(edge.to in indeg)) continue;
|
|
92
|
+
indeg[edge.to]++;
|
|
93
|
+
nextById[edge.from].push(edge.to);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const queue = ids.filter((id) => indeg[id] === 0);
|
|
97
|
+
const seen = new Set<string>();
|
|
98
|
+
for (let qi = 0; qi < queue.length; qi++) {
|
|
99
|
+
const id = queue[qi];
|
|
100
|
+
seen.add(id);
|
|
101
|
+
for (const to of nextById[id]) {
|
|
102
|
+
rank[to] = Math.max(rank[to], rank[id] + 1);
|
|
103
|
+
indeg[to]--;
|
|
104
|
+
if (indeg[to] === 0) queue.push(to);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const id of ids) {
|
|
108
|
+
if (!seen.has(id)) rank[id] = Math.max(...Object.values(rank), 0) + 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const rightmost = Math.max(...Object.values(rank), 0);
|
|
112
|
+
const hasOutgoing = new Set(edges.filter((edge) => edge.from in rank && edge.to in rank).map((edge) => edge.from));
|
|
113
|
+
for (const id of ids) {
|
|
114
|
+
if (!hasOutgoing.has(id)) rank[id] = rightmost;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const stages: string[][] = [];
|
|
118
|
+
for (const id of ids) (stages[rank[id]] ??= []).push(id);
|
|
119
|
+
return stages.filter(Boolean);
|
|
120
|
+
}
|
|
121
|
+
|
|
67
122
|
export function computePositions(
|
|
68
|
-
nodes:
|
|
123
|
+
nodes: LayoutNode[],
|
|
69
124
|
edges: Array<{ from: string; to: string }>,
|
|
70
125
|
): Record<string, { x: number; y: number }> {
|
|
71
|
-
const
|
|
72
|
-
const
|
|
126
|
+
const byId = Object.fromEntries(nodes.map((node) => [node.id, node]));
|
|
127
|
+
const stages = reduceCrossings(longestPathStages(nodes.map((node) => node.id), edges), edges);
|
|
73
128
|
const stepX = NODE_W + NODE_W_GAP;
|
|
129
|
+
const heights = Object.fromEntries(nodes.map((node) => [node.id, estimateNodeHeight(node)]));
|
|
130
|
+
const stageHeights = stages.map((stage) => stage.reduce((sum, id, i) => sum + heights[id] + (i ? NODE_H_GAP : 0), 0));
|
|
131
|
+
const maxStageHeight = Math.max(...stageHeights, 0);
|
|
74
132
|
const pos: Record<string, { x: number; y: number }> = {};
|
|
75
133
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
134
|
+
stages.forEach((stage, si) => {
|
|
135
|
+
let y = Math.max(0, (maxStageHeight - stageHeights[si]) / 2);
|
|
136
|
+
stage.forEach((id) => {
|
|
137
|
+
const node = byId[id];
|
|
138
|
+
const fan = edges.filter((edge) => edge.from === id || edge.to === id).length;
|
|
139
|
+
const repel = Math.min(fan * 3, 24);
|
|
140
|
+
pos[id] = { x: si * stepX, y };
|
|
141
|
+
y += estimateNodeHeight(node) + NODE_H_GAP + repel;
|
|
80
142
|
});
|
|
81
143
|
});
|
|
144
|
+
|
|
82
145
|
return pos;
|
|
83
146
|
}
|
package/src/canvas.ts
CHANGED
|
@@ -508,6 +508,15 @@ export interface SessionTailView {
|
|
|
508
508
|
events: TimelineEvent[];
|
|
509
509
|
}
|
|
510
510
|
|
|
511
|
+
function compactToolArguments(value: unknown): unknown {
|
|
512
|
+
if (typeof value === "string") return value.length > 1000 ? `[omitted ${value.length} chars]` : value;
|
|
513
|
+
if (Array.isArray(value)) return value.map(compactToolArguments);
|
|
514
|
+
if (value && typeof value === "object") {
|
|
515
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, compactToolArguments(v)]));
|
|
516
|
+
}
|
|
517
|
+
return value;
|
|
518
|
+
}
|
|
519
|
+
|
|
511
520
|
export function parseSessionTail(jsonl: string, maxEntries: number): SessionTailView {
|
|
512
521
|
const events: TimelineEvent[] = [];
|
|
513
522
|
const entries: SessionEntryView[] = [];
|
|
@@ -545,9 +554,10 @@ export function parseSessionTail(jsonl: string, maxEntries: number): SessionTail
|
|
|
545
554
|
if (p.type === "text" && typeof p.text === "string") parts.push(p.text);
|
|
546
555
|
else if ((p.type === "toolCall" || p.type === "tool_call") && typeof p.name === "string") {
|
|
547
556
|
parts.push(`[tool: ${p.name}]`);
|
|
548
|
-
const
|
|
557
|
+
const args = compactToolArguments(p.arguments) as Record<string, unknown> | undefined;
|
|
558
|
+
const a: ActionView = { type: "tool_call", name: p.name, arguments: args, timestamp: ts };
|
|
549
559
|
actions.push(a);
|
|
550
|
-
events.push({ type: "tool_call", name: p.name, arguments:
|
|
560
|
+
events.push({ type: "tool_call", name: p.name, arguments: args, timestamp: ts });
|
|
551
561
|
} else if (p.type === "toolResult" || p.type === "tool_result") {
|
|
552
562
|
parts.push("[tool result]");
|
|
553
563
|
const a: ActionView = { type: "tool_result", toolName: typeof p.toolName === "string" ? p.toolName : p.name, isError: p.isError, timestamp: ts };
|
|
@@ -649,8 +659,8 @@ function readFileSyncSafe(p: string): string {
|
|
|
649
659
|
}
|
|
650
660
|
|
|
651
661
|
const PAGE_CSS = `
|
|
652
|
-
:root { color-scheme: dark; --bg:#0d1117; --fg:#c9d1d9; --muted:#8b949e; --line:#30363d; --panel:#
|
|
653
|
-
[data-theme="light"] { color-scheme: light; --bg:#f6f8fa; --fg:#1f2328; --muted:#57606a; --line:#
|
|
662
|
+
:root { color-scheme: dark; --bg:#0d1117; --fg:#c9d1d9; --muted:#8b949e; --line:#30363d; --panel:#171d26; --panel-2:#212936; --stage-bg:#0a0c10; --accent:#58a6ff; --ok:#3fb950; --bad:#f85149; --warn:#d29922; --wire:#6e7681; --hdr:#f0f6fc; --card-shadow:0 1px 2px rgba(0,0,0,0.42), 0 12px 28px -12px rgba(0,0,0,0.72); --card-shadow-lg:0 2px 4px rgba(0,0,0,0.5), 0 18px 42px -14px rgba(0,0,0,0.78); --edge:#4a5361; --edge-soft:#363d49; --mm-mask:rgba(8,10,14,0.55); --mm-frame:rgba(255,255,255,0.14); }
|
|
663
|
+
[data-theme="light"] { color-scheme: light; --bg:#f6f8fa; --fg:#1f2328; --muted:#57606a; --line:#d8dee7; --panel:#ffffff; --panel-2:#f6f8fa; --stage-bg:#f2f5f9; --accent:#0969da; --ok:#1a7f37; --bad:#cf222e; --warn:#9a6700; --wire:#8c959f; --hdr:#1f2328; --card-shadow:0 1px 2px rgba(15,23,42,0.08), 0 14px 30px -16px rgba(15,23,42,0.26); --card-shadow-lg:0 2px 4px rgba(15,23,42,0.10), 0 18px 44px -16px rgba(15,23,42,0.30); --edge:#aeb8c6; --edge-soft:#d0d7de; --mm-mask:rgba(15,23,42,0.10); --mm-frame:rgba(9,105,218,0.35); }
|
|
654
664
|
:root { --ui:system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; --mono:"SF Mono", "JetBrains Mono", Menlo, Consolas, monospace; }
|
|
655
665
|
* { box-sizing:border-box; }
|
|
656
666
|
html,body,#root { margin:0; height:100%; }
|
|
@@ -687,43 +697,60 @@ button:hover, select:hover { border-color:var(--accent); }
|
|
|
687
697
|
.taskbox-side-label { font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:0.04em; margin-bottom:4px; }
|
|
688
698
|
main { display:flex; flex:1 1 auto; min-height:0; }
|
|
689
699
|
#stage { flex:1; position:relative; min-width:0; background:var(--stage-bg); }
|
|
690
|
-
.react-flow { background:var(--stage-bg); }
|
|
691
|
-
.react-flow__node { width:
|
|
700
|
+
.react-flow { background:radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--muted) 18%, transparent) 1px, transparent 0) 0 0/24px 24px, var(--stage-bg); }
|
|
701
|
+
.react-flow__node { width:304px; }
|
|
692
702
|
.react-flow__handle { width:6px; height:6px; background:var(--wire); border:none; opacity:0; }
|
|
693
|
-
.react-flow__edge-path { stroke:var(--edge); stroke-width:1.
|
|
694
|
-
.react-flow__edge:hover .react-flow__edge-path { stroke:var(--accent); stroke-width:2; }
|
|
695
|
-
.react-flow__edge.animated .react-flow__edge-path { stroke:var(--accent); stroke-width:2; stroke-dasharray:1
|
|
696
|
-
@keyframes dashflow { to { stroke-dashoffset:-
|
|
703
|
+
.react-flow__edge-path { stroke:var(--edge); stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round; opacity:0.56; }
|
|
704
|
+
.react-flow__edge:hover .react-flow__edge-path { stroke:var(--accent); stroke-width:2.2; opacity:0.95; }
|
|
705
|
+
.react-flow__edge.animated .react-flow__edge-path { stroke:var(--accent); stroke-width:2.2; stroke-dasharray:1 8; opacity:0.95; animation:dashflow 0.7s linear infinite; }
|
|
706
|
+
@keyframes dashflow { to { stroke-dashoffset:-18; } }
|
|
697
707
|
.react-flow__controls button { background:var(--panel); color:var(--fg); border-bottom:1px solid var(--line); fill:var(--fg); }
|
|
698
708
|
.react-flow__minimap { width:172px; height:112px; background:var(--panel); border-radius:12px; box-shadow:var(--card-shadow); overflow:hidden; }
|
|
699
709
|
.react-flow__minimap svg { border-radius:10px; }
|
|
700
710
|
.react-flow__minimap-mask { fill:var(--mm-mask) !important; stroke:none; }
|
|
701
711
|
.react-flow__minimap-node { stroke:var(--panel) !important; stroke-width:4px !important; rx:3; ry:3; }
|
|
702
712
|
.react-flow__attribution { display:none; }
|
|
703
|
-
.node { position:relative; width:
|
|
713
|
+
.node { position:relative; width:304px; border-radius:14px; background:var(--panel); cursor:pointer; box-shadow:var(--card-shadow); overflow:hidden; transition:box-shadow 0.18s ease, transform 0.18s ease, background 0.18s ease; }
|
|
714
|
+
.node::before { content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--wire); opacity:0.7; }
|
|
704
715
|
.node:hover { box-shadow:var(--card-shadow-lg); transform:translateY(-2px); }
|
|
705
|
-
.node.sel { background:color-mix(in srgb, var(--accent)
|
|
706
|
-
.node.sel.st-failed, .node.sel.st-contract_failed { background:color-mix(in srgb, var(--bad)
|
|
716
|
+
.node.sel { background:color-mix(in srgb, var(--accent) 12%, var(--panel)); box-shadow:var(--card-shadow-lg); transform:translateY(-2px); }
|
|
717
|
+
.node.sel.st-failed, .node.sel.st-contract_failed { background:color-mix(in srgb, var(--bad) 12%, var(--panel)); }
|
|
707
718
|
/* keyboard focus ring only — never on mouse click */
|
|
708
719
|
.node:focus:not(:focus-visible) { outline:none; }
|
|
709
|
-
.node.st-running { background:color-mix(in srgb, var(--accent)
|
|
710
|
-
.node.st-
|
|
711
|
-
.
|
|
720
|
+
.node.st-running { background:color-mix(in srgb, var(--accent) 8%, var(--panel)); }
|
|
721
|
+
.node.st-completed::before { background:var(--ok); }
|
|
722
|
+
.node.st-running::before { background:var(--accent); }
|
|
723
|
+
.node.st-failed::before, .node.st-contract_failed::before { background:var(--bad); }
|
|
724
|
+
.node.st-pending::before, .node.st-ready::before { background:var(--line); }
|
|
725
|
+
.node.role-start { box-shadow:var(--card-shadow), inset 0 0 0 1px color-mix(in srgb, var(--ok) 24%, transparent); }
|
|
726
|
+
.node.role-end { box-shadow:var(--card-shadow), inset 0 0 0 1px color-mix(in srgb, var(--accent) 24%, transparent); }
|
|
727
|
+
.node.role-gate { box-shadow:var(--card-shadow), inset 0 0 0 1px color-mix(in srgb, var(--warn) 38%, transparent); }
|
|
728
|
+
.node.role-start::after, .node.role-end::after, .node.role-gate::after { content:""; position:absolute; inset:auto 12px 0 12px; height:1px; background:linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 45%, transparent), transparent); opacity:0.75; }
|
|
729
|
+
.node.role-start::after { background:linear-gradient(90deg, transparent, color-mix(in srgb, var(--ok) 45%, transparent), transparent); }
|
|
730
|
+
.node.role-gate::after { background:linear-gradient(90deg, transparent, color-mix(in srgb, var(--warn) 55%, transparent), transparent); }
|
|
731
|
+
.node.st-failed, .node.st-contract_failed { background:color-mix(in srgb, var(--bad) 9%, var(--panel)); }
|
|
732
|
+
.card-body { padding:15px 16px 14px 17px; }
|
|
712
733
|
.node-header { display:flex; align-items:center; gap:7px; }
|
|
713
734
|
.node-dot { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
|
|
714
735
|
.node-dot.pulse { animation:pulse 1.6s ease-in-out infinite; }
|
|
715
736
|
.node-header .id { flex:1; }
|
|
716
737
|
.node-header .badge { margin-left:auto; }
|
|
717
|
-
.id { font-weight:
|
|
718
|
-
.badge { font-size:11px; padding:1px 7px; border:1px solid var(--line); border-radius:10px; color:var(--muted); white-space:nowrap; }
|
|
719
|
-
.
|
|
720
|
-
.
|
|
738
|
+
.id { font-weight:760; font-size:16px; letter-spacing:-0.02em; color:var(--hdr); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
739
|
+
.badge, .role-badge { font-size:11px; padding:1px 7px; border:1px solid var(--line); border-radius:10px; color:var(--muted); white-space:nowrap; background:color-mix(in srgb, var(--panel-2) 70%, transparent); }
|
|
740
|
+
.role-badge { font-weight:700; letter-spacing:0.04em; color:var(--fg); }
|
|
741
|
+
.role-start .role-badge { border-color:color-mix(in srgb, var(--ok) 55%, var(--line)); color:var(--ok); }
|
|
742
|
+
.role-end .role-badge { border-color:color-mix(in srgb, var(--accent) 55%, var(--line)); color:var(--accent); }
|
|
743
|
+
.role-gate .role-badge { border-color:color-mix(in srgb, var(--warn) 60%, var(--line)); color:var(--warn); }
|
|
744
|
+
.status-row { margin-top:9px; display:flex; align-items:center; gap:6px; font-size:13px; color:var(--muted); min-width:0; }
|
|
745
|
+
.status-row span:last-child { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
746
|
+
.status-row .st-word { color:var(--fg); font-weight:650; }
|
|
721
747
|
.spinner { width:10px; height:10px; border:2px solid var(--accent); border-top-color:transparent; border-radius:50%; animation:spin 1s linear infinite; display:inline-block; flex-shrink:0; }
|
|
722
748
|
@keyframes spin { to { transform:rotate(360deg); } }
|
|
723
|
-
.stats { margin-top:
|
|
724
|
-
.outputs { margin-top:6px; }
|
|
725
|
-
.out-chip { display:inline-block; font-size:11px; line-height:1.
|
|
726
|
-
.
|
|
749
|
+
.stats { margin-top:6px; font-size:13px; color:var(--muted); }
|
|
750
|
+
.outputs { margin-top:10px; display:flex; gap:6px; align-items:center; }
|
|
751
|
+
.out-count, .out-chip { display:inline-block; font-size:11px; line-height:1.45; border:1px solid var(--line); border-radius:8px; padding:4px 8px; color:var(--muted); background:var(--bg); }
|
|
752
|
+
.out-count { color:var(--fg); background:color-mix(in srgb, var(--accent) 8%, var(--bg)); }
|
|
753
|
+
.flags { margin-top:8px; font-size:11px; color:var(--warn); }
|
|
727
754
|
.flags span { cursor:help; }
|
|
728
755
|
.note { margin-top:6px; font-size:13px; color:var(--warn); }
|
|
729
756
|
.note, .fail-reason { user-select:text; cursor:text; }
|
|
@@ -750,7 +777,7 @@ main { display:flex; flex:1 1 auto; min-height:0; }
|
|
|
750
777
|
.timeline { margin-bottom:12px; }
|
|
751
778
|
.timeline-action { background:var(--panel); border-radius:5px; margin-bottom:5px; }
|
|
752
779
|
.timeline-action.open { background:var(--panel-2); }
|
|
753
|
-
.timeline-action.action-error {
|
|
780
|
+
.timeline-action.action-error { box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--bad) 40%, transparent); background:color-mix(in srgb, var(--bad) 9%, var(--panel)); }
|
|
754
781
|
.timeline-row { display:flex; align-items:center; gap:8px; padding:5px 8px; font-size:12px; }
|
|
755
782
|
.timeline-row .action-icon { flex-shrink:0; width:34px; text-align:center; color:var(--accent); font-family:var(--mono); font-size:11px; text-transform:uppercase; letter-spacing:0.02em; }
|
|
756
783
|
.timeline-row .action-name { flex-shrink:0; font-weight:600; min-width:48px; font-size:12px; white-space:nowrap; }
|