@lumea-labs/orchestrator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +93 -0
- package/dist/lib/format.d.ts +3 -0
- package/dist/lib/format.js +9 -0
- package/dist/orchestrator-document.d.ts +37 -0
- package/dist/orchestrator-document.js +122 -0
- package/dist/plan-detail.d.ts +102 -0
- package/dist/plan-detail.js +385 -0
- package/dist/plan-graph.d.ts +39 -0
- package/dist/plan-graph.js +597 -0
- package/dist/plan-node-detail.d.ts +29 -0
- package/dist/plan-node-detail.js +346 -0
- package/dist/plan-task-detail.d.ts +76 -0
- package/dist/plan-task-detail.js +450 -0
- package/dist/plan-types.d.ts +85 -0
- package/dist/plan-types.js +51 -0
- package/dist/run-kanban-filter-menu.d.ts +24 -0
- package/dist/run-kanban-filter-menu.js +152 -0
- package/dist/run-kanban.d.ts +61 -0
- package/dist/run-kanban.js +234 -0
- package/dist/swarm-agent-badge.d.ts +15 -0
- package/dist/swarm-agent-badge.js +39 -0
- package/dist/swarm-run-activity.d.ts +39 -0
- package/dist/swarm-run-activity.js +289 -0
- package/dist/swarm-run-card.d.ts +22 -0
- package/dist/swarm-run-card.js +91 -0
- package/dist/swarm-run-detail.d.ts +45 -0
- package/dist/swarm-run-detail.js +559 -0
- package/dist/swarm-run-list.d.ts +22 -0
- package/dist/swarm-run-list.js +75 -0
- package/dist/swarm-run-row.d.ts +22 -0
- package/dist/swarm-run-row.js +125 -0
- package/dist/swarm-skeletons.d.ts +28 -0
- package/dist/swarm-skeletons.js +78 -0
- package/dist/swarm-status-bar.d.ts +15 -0
- package/dist/swarm-status-bar.js +79 -0
- package/dist/swarm-status-pill.d.ts +12 -0
- package/dist/swarm-status-pill.js +86 -0
- package/dist/swarm-timeline.d.ts +21 -0
- package/dist/swarm-timeline.js +414 -0
- package/dist/task-workspace-sidebar.d.ts +71 -0
- package/dist/task-workspace-sidebar.js +352 -0
- package/dist/types.d.ts +285 -0
- package/dist/types.js +44 -0
- package/package.json +41 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import { Check, ChevronDown, Filter } from "lucide-react";
|
|
5
|
+
function RunKanbanFilterMenu({
|
|
6
|
+
lanes,
|
|
7
|
+
value,
|
|
8
|
+
onChange,
|
|
9
|
+
hideEmpty = false,
|
|
10
|
+
triggerLabel = "Filter",
|
|
11
|
+
className
|
|
12
|
+
}) {
|
|
13
|
+
const [open, setOpen] = useState(false);
|
|
14
|
+
const triggerRef = useRef(null);
|
|
15
|
+
const [pos, setPos] = useState(null);
|
|
16
|
+
const visibleLanes = hideEmpty ? lanes.filter((l) => l.count > 0) : lanes;
|
|
17
|
+
const allKeys = visibleLanes.map((l) => l.key);
|
|
18
|
+
const visibleCount = allKeys.filter((k) => value.has(k)).length;
|
|
19
|
+
const allOn = visibleCount === allKeys.length && allKeys.length > 0;
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!open) return;
|
|
22
|
+
const place = () => {
|
|
23
|
+
const r = triggerRef.current?.getBoundingClientRect();
|
|
24
|
+
if (!r) return;
|
|
25
|
+
const W = 220;
|
|
26
|
+
const left = Math.min(r.left, window.innerWidth - W - 8);
|
|
27
|
+
const top = r.bottom + 6;
|
|
28
|
+
setPos({ top, left });
|
|
29
|
+
};
|
|
30
|
+
place();
|
|
31
|
+
window.addEventListener("resize", place);
|
|
32
|
+
window.addEventListener("scroll", place, true);
|
|
33
|
+
return () => {
|
|
34
|
+
window.removeEventListener("resize", place);
|
|
35
|
+
window.removeEventListener("scroll", place, true);
|
|
36
|
+
};
|
|
37
|
+
}, [open]);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!open) return;
|
|
40
|
+
const onPointer = (e) => {
|
|
41
|
+
const t = e.target;
|
|
42
|
+
if (triggerRef.current?.contains(t)) return;
|
|
43
|
+
const menu = document.getElementById("run-kanban-filter-menu");
|
|
44
|
+
if (menu?.contains(t)) return;
|
|
45
|
+
setOpen(false);
|
|
46
|
+
};
|
|
47
|
+
const onKey = (e) => {
|
|
48
|
+
if (e.key === "Escape") setOpen(false);
|
|
49
|
+
};
|
|
50
|
+
window.addEventListener("pointerdown", onPointer, true);
|
|
51
|
+
window.addEventListener("keydown", onKey);
|
|
52
|
+
return () => {
|
|
53
|
+
window.removeEventListener("pointerdown", onPointer, true);
|
|
54
|
+
window.removeEventListener("keydown", onKey);
|
|
55
|
+
};
|
|
56
|
+
}, [open]);
|
|
57
|
+
const toggle = (key) => {
|
|
58
|
+
const next = new Set(value);
|
|
59
|
+
if (next.has(key)) next.delete(key);
|
|
60
|
+
else next.add(key);
|
|
61
|
+
onChange(next);
|
|
62
|
+
};
|
|
63
|
+
const setAll = () => onChange(new Set(allKeys));
|
|
64
|
+
const clearAll = () => onChange(/* @__PURE__ */ new Set());
|
|
65
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
66
|
+
"button",
|
|
67
|
+
{
|
|
68
|
+
ref: triggerRef,
|
|
69
|
+
type: "button",
|
|
70
|
+
onClick: () => setOpen((v) => !v),
|
|
71
|
+
"aria-haspopup": "menu",
|
|
72
|
+
"aria-expanded": open,
|
|
73
|
+
className: [
|
|
74
|
+
"inline-flex items-center gap-1.5 rounded-md border border-p-line bg-p-surface px-2.5 py-1 font-mono text-[10.5px] font-bold uppercase tracking-[0.16em] text-p-ink-2 transition-colors cursor-pointer hover:border-p-ink-3 hover:text-p-ink",
|
|
75
|
+
open ? "border-p-ink-3 text-p-ink" : "",
|
|
76
|
+
className || ""
|
|
77
|
+
].join(" ")
|
|
78
|
+
},
|
|
79
|
+
/* @__PURE__ */ React.createElement(Filter, { className: "size-3" }),
|
|
80
|
+
/* @__PURE__ */ React.createElement("span", null, triggerLabel),
|
|
81
|
+
/* @__PURE__ */ React.createElement("span", { className: "rounded-full bg-p-warm px-1.5 py-px font-mono text-[9.5px] tabular-nums text-p-ink" }, visibleCount, /* @__PURE__ */ React.createElement("span", { className: "text-p-ink-3" }, "/", allKeys.length)),
|
|
82
|
+
/* @__PURE__ */ React.createElement(
|
|
83
|
+
ChevronDown,
|
|
84
|
+
{
|
|
85
|
+
className: [
|
|
86
|
+
"size-3 transition-transform",
|
|
87
|
+
open ? "rotate-180" : ""
|
|
88
|
+
].join(" ")
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
), open && pos && typeof document !== "undefined" ? createPortal(
|
|
92
|
+
/* @__PURE__ */ React.createElement(
|
|
93
|
+
"div",
|
|
94
|
+
{
|
|
95
|
+
id: "run-kanban-filter-menu",
|
|
96
|
+
role: "menu",
|
|
97
|
+
style: { left: pos.left, top: pos.top, width: 220 },
|
|
98
|
+
className: "fixed z-[120] overflow-hidden rounded-md border border-p-line bg-p-surface shadow-[0_12px_40px_-16px_rgba(0,0,0,0.25)]"
|
|
99
|
+
},
|
|
100
|
+
/* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between border-b border-p-line px-2.5 py-1.5" }, /* @__PURE__ */ React.createElement("span", { className: "font-mono text-[9.5px] font-bold uppercase tracking-[0.18em] text-p-ink-3" }, "show lanes"), /* @__PURE__ */ React.createElement(
|
|
101
|
+
"button",
|
|
102
|
+
{
|
|
103
|
+
type: "button",
|
|
104
|
+
onClick: allOn ? clearAll : setAll,
|
|
105
|
+
className: "font-mono text-[9.5px] font-bold uppercase tracking-[0.16em] text-p-ink-3 transition-colors cursor-pointer hover:text-p-ink"
|
|
106
|
+
},
|
|
107
|
+
allOn ? "clear" : "all"
|
|
108
|
+
)),
|
|
109
|
+
/* @__PURE__ */ React.createElement("ul", { className: "max-h-[280px] overflow-y-auto py-1" }, visibleLanes.map((lane) => {
|
|
110
|
+
const active = value.has(lane.key);
|
|
111
|
+
return /* @__PURE__ */ React.createElement("li", { key: lane.key }, /* @__PURE__ */ React.createElement(
|
|
112
|
+
"button",
|
|
113
|
+
{
|
|
114
|
+
type: "button",
|
|
115
|
+
role: "menuitemcheckbox",
|
|
116
|
+
"aria-checked": active,
|
|
117
|
+
onClick: () => toggle(lane.key),
|
|
118
|
+
className: "flex w-full items-center gap-2 border-0 bg-transparent px-2.5 py-1.5 text-left transition-colors cursor-pointer hover:bg-p-warm"
|
|
119
|
+
},
|
|
120
|
+
/* @__PURE__ */ React.createElement(
|
|
121
|
+
"span",
|
|
122
|
+
{
|
|
123
|
+
"aria-hidden": true,
|
|
124
|
+
className: [
|
|
125
|
+
"grid size-4 shrink-0 place-items-center rounded border",
|
|
126
|
+
active ? "border-p-ink bg-p-ink text-p-bg" : "border-p-line bg-p-surface text-transparent"
|
|
127
|
+
].join(" ")
|
|
128
|
+
},
|
|
129
|
+
/* @__PURE__ */ React.createElement(Check, { className: "size-3" })
|
|
130
|
+
),
|
|
131
|
+
lane.dotClassName ? /* @__PURE__ */ React.createElement(
|
|
132
|
+
"span",
|
|
133
|
+
{
|
|
134
|
+
"aria-hidden": true,
|
|
135
|
+
className: [
|
|
136
|
+
"size-1.5 shrink-0 rounded-full",
|
|
137
|
+
lane.dotClassName,
|
|
138
|
+
active ? "" : "opacity-50"
|
|
139
|
+
].join(" ")
|
|
140
|
+
}
|
|
141
|
+
) : null,
|
|
142
|
+
/* @__PURE__ */ React.createElement("span", { className: "min-w-0 flex-1 truncate font-body text-[12.5px] text-p-ink" }, lane.label),
|
|
143
|
+
/* @__PURE__ */ React.createElement("span", { className: "font-mono text-[10px] tabular-nums text-p-ink-3" }, lane.count)
|
|
144
|
+
));
|
|
145
|
+
}))
|
|
146
|
+
),
|
|
147
|
+
document.body
|
|
148
|
+
) : null);
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
RunKanbanFilterMenu
|
|
152
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { RunKanbanLabels } from './plan-types.js';
|
|
4
|
+
import { SwarmRun, SwarmRunStatus, SwarmLabels } from './types.js';
|
|
5
|
+
|
|
6
|
+
type RunKanbanGroupBy = "status" | "agent";
|
|
7
|
+
interface RunKanbanProps {
|
|
8
|
+
runs: SwarmRun[];
|
|
9
|
+
/** Default `"status"` — lanes are pending / running / done / failed.
|
|
10
|
+
* `"agent"` swaps to one lane per agent. */
|
|
11
|
+
groupBy?: RunKanbanGroupBy;
|
|
12
|
+
now?: number;
|
|
13
|
+
renderAvatar?: (agent: SwarmRun["agent"]) => ReactNode;
|
|
14
|
+
onCancel?: (id: string) => void;
|
|
15
|
+
onRetry?: (id: string) => void;
|
|
16
|
+
onOpen?: (id: string) => void;
|
|
17
|
+
/** Override the default lane order (only relevant for `groupBy="status"`). */
|
|
18
|
+
statusOrder?: SwarmRunStatus[];
|
|
19
|
+
/** Lane keys to render. When provided, lanes whose key isn't in
|
|
20
|
+
* the set are hidden. Use together with `<RunKanbanFilters>` for a
|
|
21
|
+
* Linear-style toggleable filter strip. Omit for "show all". */
|
|
22
|
+
visibleKeys?: ReadonlySet<string>;
|
|
23
|
+
labels?: Partial<RunKanbanLabels>;
|
|
24
|
+
swarmLabels?: Partial<SwarmLabels>;
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
interface RunKanbanLaneSummary {
|
|
28
|
+
key: string;
|
|
29
|
+
label: string;
|
|
30
|
+
count: number;
|
|
31
|
+
/** Tailwind class for the dot tone — only set in `groupBy="status"` mode. */
|
|
32
|
+
dotClassName?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Project the same lanes the kanban would render — useful to feed
|
|
36
|
+
* `<RunKanbanFilters>` without recomputing the grouping in the
|
|
37
|
+
* consumer.
|
|
38
|
+
*/
|
|
39
|
+
declare function summariseRunKanbanLanes(runs: SwarmRun[], opts?: {
|
|
40
|
+
groupBy?: RunKanbanGroupBy;
|
|
41
|
+
statusOrder?: SwarmRunStatus[];
|
|
42
|
+
labels?: Partial<RunKanbanLabels>;
|
|
43
|
+
}): RunKanbanLaneSummary[];
|
|
44
|
+
interface RunKanbanFiltersProps {
|
|
45
|
+
lanes: RunKanbanLaneSummary[];
|
|
46
|
+
/** Currently-visible lane keys. */
|
|
47
|
+
value: ReadonlySet<string>;
|
|
48
|
+
onChange: (next: Set<string>) => void;
|
|
49
|
+
/** Show only lanes that have at least one item. Default `false`. */
|
|
50
|
+
hideEmpty?: boolean;
|
|
51
|
+
className?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Linear-style filter strip — one chip per lane with the count and a
|
|
55
|
+
* checkmark dot. Click toggles the lane's visibility in the kanban.
|
|
56
|
+
* Pure UI; the consumer holds the visibility set.
|
|
57
|
+
*/
|
|
58
|
+
declare function RunKanbanFilters({ lanes, value, onChange, hideEmpty, className, }: RunKanbanFiltersProps): react.JSX.Element;
|
|
59
|
+
declare function RunKanban({ runs, groupBy, now, renderAvatar, onCancel, onRetry, onOpen, statusOrder, visibleKeys, labels, swarmLabels, className, }: RunKanbanProps): react.JSX.Element;
|
|
60
|
+
|
|
61
|
+
export { RunKanban, RunKanbanFilters, type RunKanbanFiltersProps, type RunKanbanGroupBy, type RunKanbanLaneSummary, type RunKanbanProps, summariseRunKanbanLanes };
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import {
|
|
4
|
+
defaultRunKanbanLabels
|
|
5
|
+
} from "./plan-types";
|
|
6
|
+
import {
|
|
7
|
+
defaultSwarmLabels
|
|
8
|
+
} from "./types";
|
|
9
|
+
import { SwarmRunCard } from "./swarm-run-card";
|
|
10
|
+
function summariseRunKanbanLanes(runs, opts) {
|
|
11
|
+
const groupBy = opts?.groupBy ?? "status";
|
|
12
|
+
const statusOrder = opts?.statusOrder ?? DEFAULT_STATUS_ORDER;
|
|
13
|
+
const L = { ...defaultRunKanbanLabels, ...opts?.labels };
|
|
14
|
+
if (groupBy === "agent") {
|
|
15
|
+
const byAgent = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const r of runs) {
|
|
17
|
+
const e = byAgent.get(r.agent.name);
|
|
18
|
+
if (e) e.count += 1;
|
|
19
|
+
else
|
|
20
|
+
byAgent.set(r.agent.name, {
|
|
21
|
+
label: r.agent.displayName ?? r.agent.name,
|
|
22
|
+
count: 1
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return Array.from(byAgent, ([key, v]) => ({
|
|
26
|
+
key,
|
|
27
|
+
label: v.label,
|
|
28
|
+
count: v.count
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
const counts = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const s of statusOrder) counts.set(s, 0);
|
|
33
|
+
for (const r of runs) {
|
|
34
|
+
counts.set(r.status, (counts.get(r.status) ?? 0) + 1);
|
|
35
|
+
}
|
|
36
|
+
return statusOrder.map((s) => ({
|
|
37
|
+
key: s,
|
|
38
|
+
label: s === "scheduled" ? L.scheduled : s === "pending" ? L.pending : s === "running" ? L.running : s === "done" ? L.done : s === "failed" ? L.failed : L.cancelled,
|
|
39
|
+
count: counts.get(s) ?? 0,
|
|
40
|
+
dotClassName: STATUS_DOT[s]
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
function RunKanbanFilters({
|
|
44
|
+
lanes,
|
|
45
|
+
value,
|
|
46
|
+
onChange,
|
|
47
|
+
hideEmpty = false,
|
|
48
|
+
className
|
|
49
|
+
}) {
|
|
50
|
+
const visibleLanes = hideEmpty ? lanes.filter((l) => l.count > 0) : lanes;
|
|
51
|
+
const toggle = (key) => {
|
|
52
|
+
const next = new Set(value);
|
|
53
|
+
if (next.has(key)) next.delete(key);
|
|
54
|
+
else next.add(key);
|
|
55
|
+
onChange(next);
|
|
56
|
+
};
|
|
57
|
+
const allVisible = visibleLanes.every((l) => value.has(l.key));
|
|
58
|
+
return /* @__PURE__ */ React.createElement(
|
|
59
|
+
"div",
|
|
60
|
+
{
|
|
61
|
+
className: [
|
|
62
|
+
"flex flex-wrap items-center gap-1.5",
|
|
63
|
+
className || ""
|
|
64
|
+
].join(" ")
|
|
65
|
+
},
|
|
66
|
+
/* @__PURE__ */ React.createElement(
|
|
67
|
+
"button",
|
|
68
|
+
{
|
|
69
|
+
type: "button",
|
|
70
|
+
onClick: () => onChange(allVisible ? /* @__PURE__ */ new Set() : new Set(visibleLanes.map((l) => l.key))),
|
|
71
|
+
className: "inline-flex items-center gap-1 rounded-full border border-p-line bg-p-surface px-2 py-1 font-mono text-[9.5px] font-bold uppercase tracking-[0.16em] text-p-ink-3 transition-colors cursor-pointer hover:border-p-ink-3 hover:text-p-ink"
|
|
72
|
+
},
|
|
73
|
+
allVisible ? "clear" : "all"
|
|
74
|
+
),
|
|
75
|
+
visibleLanes.map((lane) => {
|
|
76
|
+
const active = value.has(lane.key);
|
|
77
|
+
return /* @__PURE__ */ React.createElement(
|
|
78
|
+
"button",
|
|
79
|
+
{
|
|
80
|
+
key: lane.key,
|
|
81
|
+
type: "button",
|
|
82
|
+
onClick: () => toggle(lane.key),
|
|
83
|
+
"aria-pressed": active,
|
|
84
|
+
className: [
|
|
85
|
+
"inline-flex items-center gap-1.5 rounded-full border px-2 py-1 font-mono text-[10px] font-bold uppercase tracking-[0.14em] transition-colors cursor-pointer",
|
|
86
|
+
active ? "border-p-ink/20 bg-p-warm text-p-ink" : "border-p-line bg-p-surface text-p-ink-3 hover:border-p-ink-3 hover:text-p-ink"
|
|
87
|
+
].join(" ")
|
|
88
|
+
},
|
|
89
|
+
lane.dotClassName ? /* @__PURE__ */ React.createElement(
|
|
90
|
+
"span",
|
|
91
|
+
{
|
|
92
|
+
"aria-hidden": true,
|
|
93
|
+
className: [
|
|
94
|
+
"size-1.5 rounded-full",
|
|
95
|
+
lane.dotClassName,
|
|
96
|
+
active ? "" : "opacity-60"
|
|
97
|
+
].join(" ")
|
|
98
|
+
}
|
|
99
|
+
) : null,
|
|
100
|
+
/* @__PURE__ */ React.createElement("span", null, lane.label),
|
|
101
|
+
/* @__PURE__ */ React.createElement("span", { className: "tabular-nums opacity-70" }, lane.count)
|
|
102
|
+
);
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const DEFAULT_STATUS_ORDER = [
|
|
107
|
+
"scheduled",
|
|
108
|
+
"pending",
|
|
109
|
+
"running",
|
|
110
|
+
"done",
|
|
111
|
+
"failed",
|
|
112
|
+
"cancelled"
|
|
113
|
+
];
|
|
114
|
+
const STATUS_DOT = {
|
|
115
|
+
scheduled: "bg-[#7B3FE4]",
|
|
116
|
+
pending: "bg-p-ink-3",
|
|
117
|
+
running: "bg-p-accent",
|
|
118
|
+
review: "bg-[#2B44FF]",
|
|
119
|
+
done: "bg-p-green",
|
|
120
|
+
failed: "bg-[#E63946]",
|
|
121
|
+
cancelled: "bg-p-ink-3/50"
|
|
122
|
+
};
|
|
123
|
+
function RunKanban({
|
|
124
|
+
runs,
|
|
125
|
+
groupBy = "status",
|
|
126
|
+
now,
|
|
127
|
+
renderAvatar,
|
|
128
|
+
onCancel,
|
|
129
|
+
onRetry,
|
|
130
|
+
onOpen,
|
|
131
|
+
statusOrder = DEFAULT_STATUS_ORDER,
|
|
132
|
+
visibleKeys,
|
|
133
|
+
labels,
|
|
134
|
+
swarmLabels,
|
|
135
|
+
className
|
|
136
|
+
}) {
|
|
137
|
+
const L = { ...defaultRunKanbanLabels, ...labels };
|
|
138
|
+
const lanes = useMemo(() => {
|
|
139
|
+
if (groupBy === "agent") {
|
|
140
|
+
const byAgent = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const r of runs) {
|
|
142
|
+
const key = r.agent.name;
|
|
143
|
+
if (!byAgent.has(key)) byAgent.set(key, { ref: r.agent, runs: [] });
|
|
144
|
+
byAgent.get(key).runs.push(r);
|
|
145
|
+
}
|
|
146
|
+
return Array.from(byAgent.values()).map((a) => ({
|
|
147
|
+
key: a.ref.name,
|
|
148
|
+
label: a.ref.displayName ?? a.ref.name,
|
|
149
|
+
runs: a.runs
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
const byStatus = /* @__PURE__ */ new Map();
|
|
153
|
+
for (const s of statusOrder) byStatus.set(s, []);
|
|
154
|
+
for (const r of runs) {
|
|
155
|
+
if (!byStatus.has(r.status)) byStatus.set(r.status, []);
|
|
156
|
+
byStatus.get(r.status).push(r);
|
|
157
|
+
}
|
|
158
|
+
return statusOrder.map((s) => ({
|
|
159
|
+
key: s,
|
|
160
|
+
label: s === "scheduled" ? L.scheduled : s === "pending" ? L.pending : s === "running" ? L.running : s === "done" ? L.done : s === "failed" ? L.failed : L.cancelled,
|
|
161
|
+
dotClassName: STATUS_DOT[s],
|
|
162
|
+
status: s,
|
|
163
|
+
runs: byStatus.get(s) ?? []
|
|
164
|
+
}));
|
|
165
|
+
}, [runs, groupBy, statusOrder, L.scheduled, L.pending, L.running, L.done, L.failed, L.cancelled]);
|
|
166
|
+
const renderedLanes = visibleKeys ? lanes.filter((l) => visibleKeys.has(l.key)) : lanes;
|
|
167
|
+
return /* @__PURE__ */ React.createElement(
|
|
168
|
+
"div",
|
|
169
|
+
{
|
|
170
|
+
className: [
|
|
171
|
+
// `h-full` so the kanban fills its container vertically — works
|
|
172
|
+
// both inside a fixed-height shell (task workspace, mission
|
|
173
|
+
// control) and in normal flow (the parent caps the height).
|
|
174
|
+
"flex h-full min-h-0 gap-3 overflow-x-auto pb-2",
|
|
175
|
+
className || ""
|
|
176
|
+
].join(" ")
|
|
177
|
+
},
|
|
178
|
+
renderedLanes.map((lane) => /* @__PURE__ */ React.createElement(
|
|
179
|
+
KanbanLane,
|
|
180
|
+
{
|
|
181
|
+
key: lane.key,
|
|
182
|
+
lane,
|
|
183
|
+
now,
|
|
184
|
+
renderAvatar,
|
|
185
|
+
onCancel,
|
|
186
|
+
onRetry,
|
|
187
|
+
onOpen,
|
|
188
|
+
emptyLabel: L.emptyLane,
|
|
189
|
+
swarmLabels
|
|
190
|
+
}
|
|
191
|
+
))
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
function KanbanLane({
|
|
195
|
+
lane,
|
|
196
|
+
now,
|
|
197
|
+
renderAvatar,
|
|
198
|
+
onCancel,
|
|
199
|
+
onRetry,
|
|
200
|
+
onOpen,
|
|
201
|
+
emptyLabel,
|
|
202
|
+
swarmLabels
|
|
203
|
+
}) {
|
|
204
|
+
const merged = { ...defaultSwarmLabels, ...swarmLabels };
|
|
205
|
+
const dot = lane.dotClassName ?? (lane.runs[0]?.agent.color ? "" : "bg-p-ink-3");
|
|
206
|
+
return /* @__PURE__ */ React.createElement("section", { className: "flex h-full min-h-0 w-[280px] shrink-0 flex-col rounded-2xl border border-p-line bg-p-bg/60" }, /* @__PURE__ */ React.createElement("header", { className: "flex shrink-0 items-center justify-between gap-2 px-3 py-2" }, /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-2" }, lane.dotClassName ? /* @__PURE__ */ React.createElement(
|
|
207
|
+
"span",
|
|
208
|
+
{
|
|
209
|
+
"aria-hidden": true,
|
|
210
|
+
className: [
|
|
211
|
+
"size-1.5 rounded-full",
|
|
212
|
+
dot,
|
|
213
|
+
lane.status === "running" ? "animate-pulse" : ""
|
|
214
|
+
].join(" ")
|
|
215
|
+
}
|
|
216
|
+
) : null, /* @__PURE__ */ React.createElement("span", { className: "font-mono text-[10px] font-bold uppercase tracking-[0.22em] text-p-ink-2" }, lane.label)), /* @__PURE__ */ React.createElement("span", { className: "font-mono text-[10px] tabular-nums text-p-ink-3" }, String(lane.runs.length).padStart(2, "0"))), /* @__PURE__ */ React.createElement("div", { className: "flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto px-2 pb-2" }, lane.runs.length === 0 ? /* @__PURE__ */ React.createElement("span", { className: "px-2 py-6 text-center font-mono text-[10.5px] uppercase tracking-[0.16em] text-p-ink-3" }, emptyLabel) : lane.runs.map((r) => /* @__PURE__ */ React.createElement(
|
|
217
|
+
SwarmRunCard,
|
|
218
|
+
{
|
|
219
|
+
key: r.id,
|
|
220
|
+
run: r,
|
|
221
|
+
now,
|
|
222
|
+
renderAvatar,
|
|
223
|
+
onCancel,
|
|
224
|
+
onRetry,
|
|
225
|
+
onOpen,
|
|
226
|
+
labels: merged
|
|
227
|
+
}
|
|
228
|
+
))));
|
|
229
|
+
}
|
|
230
|
+
export {
|
|
231
|
+
RunKanban,
|
|
232
|
+
RunKanbanFilters,
|
|
233
|
+
summariseRunKanbanLanes
|
|
234
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { SwarmAgentRef } from './types.js';
|
|
4
|
+
|
|
5
|
+
interface SwarmAgentBadgeProps {
|
|
6
|
+
agent: SwarmAgentRef;
|
|
7
|
+
/** Optional consumer-rendered avatar — overrides the glyph chip. */
|
|
8
|
+
avatar?: ReactNode;
|
|
9
|
+
size?: "sm" | "md";
|
|
10
|
+
showName?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
declare function SwarmAgentBadge({ agent, avatar, size, showName, className, }: SwarmAgentBadgeProps): react.JSX.Element;
|
|
14
|
+
|
|
15
|
+
export { SwarmAgentBadge, type SwarmAgentBadgeProps };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
function SwarmAgentBadge({
|
|
3
|
+
agent,
|
|
4
|
+
avatar,
|
|
5
|
+
size = "md",
|
|
6
|
+
showName = true,
|
|
7
|
+
className
|
|
8
|
+
}) {
|
|
9
|
+
const px = size === "sm" ? 18 : 22;
|
|
10
|
+
const txt = size === "sm" ? "text-[11px]" : "text-[12px]";
|
|
11
|
+
const glyph = agent.glyph || (agent.displayName || agent.name).charAt(0).toUpperCase();
|
|
12
|
+
return /* @__PURE__ */ React.createElement(
|
|
13
|
+
"span",
|
|
14
|
+
{
|
|
15
|
+
className: [
|
|
16
|
+
"inline-flex items-center gap-2",
|
|
17
|
+
className || ""
|
|
18
|
+
].join(" ")
|
|
19
|
+
},
|
|
20
|
+
avatar ?? /* @__PURE__ */ React.createElement(
|
|
21
|
+
"span",
|
|
22
|
+
{
|
|
23
|
+
"aria-hidden": true,
|
|
24
|
+
className: "grid shrink-0 place-items-center rounded font-display font-bold text-white",
|
|
25
|
+
style: {
|
|
26
|
+
width: px,
|
|
27
|
+
height: px,
|
|
28
|
+
background: agent.color || "#999",
|
|
29
|
+
fontSize: px <= 18 ? 10 : 11
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
glyph
|
|
33
|
+
),
|
|
34
|
+
showName ? /* @__PURE__ */ React.createElement("span", { className: [txt, "font-semibold text-p-ink truncate"].join(" ") }, agent.displayName || agent.name) : null
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
SwarmAgentBadge
|
|
39
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { SwarmRunEvent, SwarmRunActivity, SwarmAgentRef, SwarmRun } from './types.js';
|
|
4
|
+
|
|
5
|
+
interface BuildSwarmRunActivityOptions {
|
|
6
|
+
/** Extra events (e.g. mapped from log sessions) merged with the run's
|
|
7
|
+
* own fields. Sorted with everything else by `ts`. */
|
|
8
|
+
extras?: SwarmRunEvent[];
|
|
9
|
+
}
|
|
10
|
+
declare function buildSwarmRunActivity(run: SwarmRun, options?: BuildSwarmRunActivityOptions): SwarmRunActivity;
|
|
11
|
+
interface SwarmRunActivityStreamProps {
|
|
12
|
+
events: SwarmRunActivity;
|
|
13
|
+
/** Used to label `agentName` references (avatar / displayName lookup). */
|
|
14
|
+
agents?: SwarmAgentRef[];
|
|
15
|
+
/** Slot for tool-call rendering — wire `<ToolCallChip variant="task"/>`
|
|
16
|
+
* from `@lumea-labs/tool-calls` for the rich card view. When omitted,
|
|
17
|
+
* a minimal text row is rendered. */
|
|
18
|
+
renderToolCall?: (tool: Extract<SwarmRunEvent, {
|
|
19
|
+
kind: "tool_call";
|
|
20
|
+
}>["tool"]) => ReactNode;
|
|
21
|
+
/** Slot for markdown rendering — wire `<MarkdownViewer/>` from
|
|
22
|
+
* `@lumea-labs/markdown` for output / thought / user_input bodies.
|
|
23
|
+
* Default: pre-formatted plain text. */
|
|
24
|
+
renderMarkdown?: (text: string) => ReactNode;
|
|
25
|
+
/** Override the entire rendering for an event. Return `undefined` to
|
|
26
|
+
* fall back to the default. Use this for chat-style rendering. */
|
|
27
|
+
renderEvent?: (event: SwarmRunEvent) => ReactNode | undefined;
|
|
28
|
+
/** Number of events to show before the "Show N more" footer button.
|
|
29
|
+
* Default `6`. Set to `Infinity` to disable the peek behaviour and
|
|
30
|
+
* always show every event. The peek shows the LATEST events (the
|
|
31
|
+
* tail of the timeline) since that's "what's happening now". */
|
|
32
|
+
peek?: number;
|
|
33
|
+
/** Empty-state copy when `events` is empty. */
|
|
34
|
+
emptyLabel?: string;
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
declare function SwarmRunActivityStream({ events, agents, renderToolCall, renderMarkdown, renderEvent, peek, emptyLabel, className, }: SwarmRunActivityStreamProps): react.JSX.Element;
|
|
38
|
+
|
|
39
|
+
export { type BuildSwarmRunActivityOptions, SwarmRunActivityStream, type SwarmRunActivityStreamProps, buildSwarmRunActivity };
|