@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.
Files changed (46) hide show
  1. package/README.md +21 -0
  2. package/dist/index.d.ts +21 -0
  3. package/dist/index.js +93 -0
  4. package/dist/lib/format.d.ts +3 -0
  5. package/dist/lib/format.js +9 -0
  6. package/dist/orchestrator-document.d.ts +37 -0
  7. package/dist/orchestrator-document.js +122 -0
  8. package/dist/plan-detail.d.ts +102 -0
  9. package/dist/plan-detail.js +385 -0
  10. package/dist/plan-graph.d.ts +39 -0
  11. package/dist/plan-graph.js +597 -0
  12. package/dist/plan-node-detail.d.ts +29 -0
  13. package/dist/plan-node-detail.js +346 -0
  14. package/dist/plan-task-detail.d.ts +76 -0
  15. package/dist/plan-task-detail.js +450 -0
  16. package/dist/plan-types.d.ts +85 -0
  17. package/dist/plan-types.js +51 -0
  18. package/dist/run-kanban-filter-menu.d.ts +24 -0
  19. package/dist/run-kanban-filter-menu.js +152 -0
  20. package/dist/run-kanban.d.ts +61 -0
  21. package/dist/run-kanban.js +234 -0
  22. package/dist/swarm-agent-badge.d.ts +15 -0
  23. package/dist/swarm-agent-badge.js +39 -0
  24. package/dist/swarm-run-activity.d.ts +39 -0
  25. package/dist/swarm-run-activity.js +289 -0
  26. package/dist/swarm-run-card.d.ts +22 -0
  27. package/dist/swarm-run-card.js +91 -0
  28. package/dist/swarm-run-detail.d.ts +45 -0
  29. package/dist/swarm-run-detail.js +559 -0
  30. package/dist/swarm-run-list.d.ts +22 -0
  31. package/dist/swarm-run-list.js +75 -0
  32. package/dist/swarm-run-row.d.ts +22 -0
  33. package/dist/swarm-run-row.js +125 -0
  34. package/dist/swarm-skeletons.d.ts +28 -0
  35. package/dist/swarm-skeletons.js +78 -0
  36. package/dist/swarm-status-bar.d.ts +15 -0
  37. package/dist/swarm-status-bar.js +79 -0
  38. package/dist/swarm-status-pill.d.ts +12 -0
  39. package/dist/swarm-status-pill.js +86 -0
  40. package/dist/swarm-timeline.d.ts +21 -0
  41. package/dist/swarm-timeline.js +414 -0
  42. package/dist/task-workspace-sidebar.d.ts +71 -0
  43. package/dist/task-workspace-sidebar.js +352 -0
  44. package/dist/types.d.ts +285 -0
  45. package/dist/types.js +44 -0
  46. 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 };