@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,352 @@
1
+ "use client";
2
+ import { useMemo, useState } from "react";
3
+ import {
4
+ ArrowUpRight,
5
+ CheckCircle2,
6
+ ChevronRight,
7
+ ChevronsLeft,
8
+ Circle,
9
+ Clock,
10
+ LayoutGrid,
11
+ RotateCcw,
12
+ Scale,
13
+ Search,
14
+ User,
15
+ XCircle
16
+ } from "lucide-react";
17
+ const defaultTaskWorkspaceSidebarLabels = {
18
+ title: "Tasks",
19
+ searchPlaceholder: "Search tasks\u2026",
20
+ collapseSidebar: "Collapse sidebar",
21
+ openKanban: "Open kanban",
22
+ closeKanban: "Close kanban",
23
+ loading: "Loading\u2026",
24
+ noTasks: "No tasks yet.",
25
+ orphanGroup: "Other",
26
+ draftBadge: "draft",
27
+ expand: "Expand",
28
+ collapse: "Collapse"
29
+ };
30
+ function TaskWorkspaceSidebar({
31
+ tasks,
32
+ missions,
33
+ agents,
34
+ isLoading = false,
35
+ activeRuntimeId,
36
+ activePlanRef,
37
+ activeMissionId,
38
+ closed,
39
+ onCollapse,
40
+ kanbanView,
41
+ onToggleKanban,
42
+ runtimeTaskHref,
43
+ planTaskHref,
44
+ missionHref,
45
+ renderLink,
46
+ parsePlan = defaultParsePlan,
47
+ labels,
48
+ className
49
+ }) {
50
+ const L = useMemo(
51
+ () => ({ ...defaultTaskWorkspaceSidebarLabels, ...labels }),
52
+ [labels]
53
+ );
54
+ const [query, setQuery] = useState("");
55
+ const [groupCollapsed, setGroupCollapsed] = useState(
56
+ {}
57
+ );
58
+ const missionByKey = useMemo(() => {
59
+ const m = /* @__PURE__ */ new Map();
60
+ for (const mi of missions) {
61
+ m.set(mi.id, mi);
62
+ m.set(mi.name, mi);
63
+ }
64
+ return m;
65
+ }, [missions]);
66
+ const groups = useMemo(() => {
67
+ const q = query.trim().toLowerCase();
68
+ const matches = (text) => !q || text.toLowerCase().includes(q);
69
+ const runtimeTitlesByMission = /* @__PURE__ */ new Map();
70
+ for (const tk of tasks) {
71
+ if (!tk.group) continue;
72
+ if (!runtimeTitlesByMission.has(tk.group))
73
+ runtimeTitlesByMission.set(tk.group, /* @__PURE__ */ new Set());
74
+ runtimeTitlesByMission.get(tk.group).add(tk.title);
75
+ }
76
+ const map = /* @__PURE__ */ new Map();
77
+ for (const tk of tasks) {
78
+ if (!matches(tk.title + " " + (tk.description || ""))) continue;
79
+ const key = tk.group || "__orphan__";
80
+ if (!map.has(key)) map.set(key, []);
81
+ map.get(key).push({ kind: "runtime", task: tk });
82
+ }
83
+ for (const m of missions) {
84
+ const planTasks = parsePlan(m.data);
85
+ if (planTasks.length === 0) continue;
86
+ const instantiated = runtimeTitlesByMission.get(m.name) ?? runtimeTitlesByMission.get(m.id) ?? /* @__PURE__ */ new Set();
87
+ const key = m.id;
88
+ for (const pt of planTasks) {
89
+ if (instantiated.has(pt.title)) continue;
90
+ if (!matches(pt.title + " " + (pt.description || ""))) continue;
91
+ if (!map.has(key)) map.set(key, []);
92
+ map.get(key).push({
93
+ kind: "plan",
94
+ title: pt.title,
95
+ description: pt.description,
96
+ assignTo: pt.assignTo,
97
+ missionId: m.id
98
+ });
99
+ }
100
+ }
101
+ for (const list of map.values()) {
102
+ list.sort((a, b) => {
103
+ if (a.kind !== b.kind) return a.kind === "runtime" ? -1 : 1;
104
+ if (a.kind === "runtime" && b.kind === "runtime") {
105
+ return (b.task.updatedAt || "").localeCompare(a.task.updatedAt || "");
106
+ }
107
+ if (a.kind === "plan" && b.kind === "plan") {
108
+ return a.title.localeCompare(b.title);
109
+ }
110
+ return 0;
111
+ });
112
+ }
113
+ return Array.from(map.entries()).sort((a, b) => {
114
+ const recent = (list) => {
115
+ const r = list.find((x) => x.kind === "runtime");
116
+ return r && r.kind === "runtime" ? r.task.updatedAt || "" : "";
117
+ };
118
+ return recent(b[1]).localeCompare(recent(a[1]));
119
+ });
120
+ }, [tasks, missions, query, parsePlan]);
121
+ return /* @__PURE__ */ React.createElement(
122
+ "aside",
123
+ {
124
+ "aria-hidden": closed,
125
+ ...{ inert: closed },
126
+ className: [
127
+ "flex h-full min-h-0 min-w-0 flex-col overflow-hidden bg-p-surface",
128
+ closed ? "border-r-0 pointer-events-none" : "border-r border-p-line",
129
+ className || ""
130
+ ].join(" ")
131
+ },
132
+ /* @__PURE__ */ React.createElement("div", { className: "flex h-11 shrink-0 items-center justify-between gap-2 px-2.5" }, /* @__PURE__ */ React.createElement("h3", { className: "min-w-0 truncate font-display text-[13px] font-semibold uppercase tracking-[0.08em] text-p-ink-2" }, L.title), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-0.5" }, /* @__PURE__ */ React.createElement(
133
+ "button",
134
+ {
135
+ type: "button",
136
+ onClick: onToggleKanban,
137
+ "aria-pressed": kanbanView,
138
+ "aria-label": kanbanView ? L.closeKanban : L.openKanban,
139
+ title: kanbanView ? L.closeKanban : L.openKanban,
140
+ className: [
141
+ "grid size-7 place-items-center rounded-md transition-colors cursor-pointer",
142
+ kanbanView ? "bg-p-ink text-p-bg hover:bg-p-ink-2" : "bg-transparent text-p-ink-3 hover:bg-p-warm hover:text-p-ink"
143
+ ].join(" ")
144
+ },
145
+ /* @__PURE__ */ React.createElement(LayoutGrid, { className: "size-4" })
146
+ ), /* @__PURE__ */ React.createElement(
147
+ "button",
148
+ {
149
+ type: "button",
150
+ onClick: onCollapse,
151
+ "aria-label": L.collapseSidebar,
152
+ className: "grid size-7 place-items-center rounded-md bg-transparent text-p-ink-3 transition-colors cursor-pointer hover:bg-p-warm hover:text-p-ink"
153
+ },
154
+ /* @__PURE__ */ React.createElement(ChevronsLeft, { className: "size-4" })
155
+ ))),
156
+ /* @__PURE__ */ React.createElement("div", { className: "shrink-0 px-2.5 pt-2 pb-2" }, /* @__PURE__ */ React.createElement("div", { className: "relative" }, /* @__PURE__ */ React.createElement(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-p-ink-3" }), /* @__PURE__ */ React.createElement(
157
+ "input",
158
+ {
159
+ value: query,
160
+ onChange: (e) => setQuery(e.target.value),
161
+ placeholder: L.searchPlaceholder,
162
+ className: "h-8 w-full rounded-md border border-p-line bg-p-bg pl-8 pr-2.5 font-body text-[12px] text-p-ink outline-none transition-colors duration-150 placeholder:text-p-ink-3 focus:border-p-ink-3 focus:bg-p-surface"
163
+ }
164
+ ))),
165
+ /* @__PURE__ */ React.createElement("div", { className: "min-h-0 flex-1 overflow-y-auto px-1.5 pt-1 pb-2" }, isLoading ? /* @__PURE__ */ React.createElement("div", { className: "px-3 py-6 text-center text-[12px] text-p-ink-3" }, L.loading) : groups.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "px-3 py-6 text-center text-[12px] text-p-ink-3" }, L.noTasks) : /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-1" }, groups.map(([key, list]) => {
166
+ const mission = key === "__orphan__" ? null : missionByKey.get(key);
167
+ const title = mission?.name ?? (key === "__orphan__" ? L.orphanGroup : key);
168
+ const isCollapsed = groupCollapsed[key] ?? false;
169
+ return /* @__PURE__ */ React.createElement("div", { key, className: "flex flex-col" }, /* @__PURE__ */ React.createElement(
170
+ MissionGroupHeader,
171
+ {
172
+ title,
173
+ missionLink: mission ? missionHref(mission.id) : null,
174
+ active: !!mission && activeMissionId === mission.id,
175
+ count: list.length,
176
+ collapsed: isCollapsed,
177
+ onToggle: () => setGroupCollapsed((c) => ({ ...c, [key]: !isCollapsed })),
178
+ renderLink,
179
+ L
180
+ }
181
+ ), !isCollapsed && /* @__PURE__ */ React.createElement("div", { className: "mt-1 mb-1.5 ml-3 flex flex-col gap-0.5 border-l border-p-line pl-1.5" }, list.map(
182
+ (row, i) => row.kind === "runtime" ? /* @__PURE__ */ React.createElement(
183
+ TaskRow,
184
+ {
185
+ key: row.task.id,
186
+ task: row.task,
187
+ agents,
188
+ active: row.task.id === activeRuntimeId,
189
+ href: runtimeTaskHref(row.task.id),
190
+ renderLink
191
+ }
192
+ ) : /* @__PURE__ */ React.createElement(
193
+ DraftTaskRow,
194
+ {
195
+ key: `plan-${row.missionId}-${row.title}-${i}`,
196
+ title: row.title,
197
+ assignTo: row.assignTo,
198
+ agents,
199
+ active: activePlanRef?.missionId === row.missionId && activePlanRef.title === row.title,
200
+ href: planTaskHref(row.missionId, row.title),
201
+ renderLink,
202
+ L
203
+ }
204
+ )
205
+ )));
206
+ })))
207
+ );
208
+ }
209
+ function MissionGroupHeader({
210
+ title,
211
+ missionLink,
212
+ active,
213
+ count,
214
+ collapsed,
215
+ onToggle,
216
+ renderLink,
217
+ L
218
+ }) {
219
+ const titleContent = /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { className: "min-w-0 flex-1 truncate text-[12.5px] font-semibold leading-tight" }, title), /* @__PURE__ */ React.createElement("span", { className: "shrink-0 rounded-full bg-p-warm px-2 py-0.5 text-[10px] font-semibold tabular-nums text-p-ink-2" }, count));
220
+ return /* @__PURE__ */ React.createElement(
221
+ "div",
222
+ {
223
+ className: [
224
+ "group flex w-full items-center gap-1 rounded-md",
225
+ active ? "bg-p-warm" : ""
226
+ ].join(" ")
227
+ },
228
+ /* @__PURE__ */ React.createElement(
229
+ "button",
230
+ {
231
+ type: "button",
232
+ onClick: onToggle,
233
+ "aria-label": collapsed ? L.expand : L.collapse,
234
+ className: "grid size-6 shrink-0 place-items-center rounded-md text-p-ink-3 transition-colors cursor-pointer hover:bg-p-warm hover:text-p-ink"
235
+ },
236
+ /* @__PURE__ */ React.createElement(
237
+ ChevronRight,
238
+ {
239
+ className: [
240
+ "size-3.5 transition-transform duration-150",
241
+ !collapsed ? "rotate-90" : ""
242
+ ].join(" ")
243
+ }
244
+ )
245
+ ),
246
+ missionLink ? renderLink({
247
+ href: missionLink,
248
+ className: [
249
+ "group/row flex min-w-0 flex-1 items-center gap-2 rounded-md px-1 py-1 no-underline transition-colors duration-100 cursor-pointer hover:bg-p-warm/60",
250
+ active ? "text-p-ink font-semibold" : "text-p-ink"
251
+ ].join(" "),
252
+ children: /* @__PURE__ */ React.createElement(React.Fragment, null, titleContent, /* @__PURE__ */ React.createElement(ArrowUpRight, { className: "size-3 shrink-0 text-p-ink-3 opacity-0 transition-opacity duration-150 group-hover/row:opacity-100" }))
253
+ }) : /* @__PURE__ */ React.createElement("div", { className: "flex min-w-0 flex-1 items-center gap-2 rounded-md px-1 py-1 text-p-ink-2" }, titleContent)
254
+ );
255
+ }
256
+ const TASK_STATUS_COLOR = {
257
+ draft: "var(--ink-3)",
258
+ pending: "var(--ink-3)",
259
+ awaiting_approval: "#D4A017",
260
+ assigned: "var(--p-accent)",
261
+ in_progress: "var(--p-accent)",
262
+ review: "#2B44FF",
263
+ done: "var(--green)",
264
+ failed: "#E63946"
265
+ };
266
+ const TASK_STATUS_ICON = {
267
+ draft: Circle,
268
+ pending: Clock,
269
+ awaiting_approval: Clock,
270
+ assigned: User,
271
+ in_progress: RotateCcw,
272
+ review: Scale,
273
+ done: CheckCircle2,
274
+ failed: XCircle
275
+ };
276
+ function TaskRow({
277
+ task,
278
+ agents,
279
+ active,
280
+ href,
281
+ renderLink
282
+ }) {
283
+ const agent = agents?.find((a) => a.name === task.assignTo);
284
+ const color = TASK_STATUS_COLOR[task.status];
285
+ const Icon = TASK_STATUS_ICON[task.status];
286
+ return renderLink({
287
+ href,
288
+ className: [
289
+ "group relative flex w-full items-center gap-2 rounded-md px-2.5 py-2 no-underline transition-colors duration-100 cursor-pointer",
290
+ active ? "bg-p-warm text-p-ink font-semibold" : "text-p-ink-2 hover:bg-p-warm hover:text-p-ink"
291
+ ].join(" "),
292
+ children: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Icon, { className: "size-3 shrink-0", style: { color } }), /* @__PURE__ */ React.createElement("span", { className: "min-w-0 flex-1 truncate text-[13px] leading-tight" }, task.title), /* @__PURE__ */ React.createElement(MiniAgentAvatar, { agent, fallback: task.assignTo }))
293
+ });
294
+ }
295
+ function DraftTaskRow({
296
+ title,
297
+ assignTo,
298
+ agents,
299
+ active,
300
+ href,
301
+ renderLink,
302
+ L
303
+ }) {
304
+ const agent = assignTo ? agents?.find((a) => a.name === assignTo) : void 0;
305
+ return renderLink({
306
+ href,
307
+ className: [
308
+ "group relative flex w-full items-center gap-2 rounded-md px-2.5 py-2 no-underline transition-colors duration-100 cursor-pointer",
309
+ active ? "bg-p-warm text-p-ink font-semibold" : "text-p-ink-3 hover:bg-p-warm hover:text-p-ink-2"
310
+ ].join(" "),
311
+ children: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Circle, { className: "size-3 shrink-0 text-p-ink-3/60" }), /* @__PURE__ */ React.createElement("span", { className: "min-w-0 flex-1 truncate text-[13px] italic leading-tight" }, title), /* @__PURE__ */ React.createElement("span", { className: "font-mono text-[9px] font-bold uppercase tracking-[0.16em] text-p-ink-3" }, L.draftBadge), assignTo ? /* @__PURE__ */ React.createElement(MiniAgentAvatar, { agent, fallback: assignTo }) : null)
312
+ });
313
+ }
314
+ function MiniAgentAvatar({
315
+ agent,
316
+ fallback
317
+ }) {
318
+ const ref = agent ?? (fallback ? { name: fallback } : void 0);
319
+ if (!ref) return null;
320
+ const display = (ref.displayName ?? ref.name).charAt(0).toUpperCase();
321
+ return /* @__PURE__ */ React.createElement(
322
+ "span",
323
+ {
324
+ "aria-hidden": true,
325
+ className: "grid size-4 shrink-0 place-items-center rounded-[3px] font-display text-[9px] font-bold text-white",
326
+ style: { background: ref.color || "#999" }
327
+ },
328
+ ref.glyph || display
329
+ );
330
+ }
331
+ function defaultParsePlan(raw) {
332
+ if (!raw || typeof raw !== "string") return [];
333
+ try {
334
+ const parsed = JSON.parse(raw);
335
+ if (!parsed || typeof parsed !== "object") return [];
336
+ const maybe = parsed.tasks;
337
+ if (!Array.isArray(maybe)) return [];
338
+ return maybe.filter(
339
+ (t) => !!t && typeof t === "object" && typeof t.title === "string"
340
+ ).map((t) => ({
341
+ title: t.title,
342
+ description: t.description,
343
+ assignTo: t.assignTo
344
+ }));
345
+ } catch {
346
+ return [];
347
+ }
348
+ }
349
+ export {
350
+ TaskWorkspaceSidebar,
351
+ defaultTaskWorkspaceSidebarLabels
352
+ };
@@ -0,0 +1,285 @@
1
+ type SwarmRunStatus = "scheduled" | "pending" | "running" | "review" | "done" | "failed" | "cancelled";
2
+ interface SwarmAgentRef {
3
+ /** Stable agent name / id. */
4
+ name: string;
5
+ displayName?: string;
6
+ /** Avatar gradient hint. */
7
+ color?: string;
8
+ /** Single-character monogram. */
9
+ glyph?: string;
10
+ /** One-line role / pitch. */
11
+ role?: string;
12
+ }
13
+ type SwarmRunEventKind = "phase" | "thought" | "tool_call" | "ask_user" | "user_input" | "output" | "artifact" | "error" | "retry" | "checkpoint" | "score";
14
+ interface BaseEvent {
15
+ /** Stable id within the run. */
16
+ id: string;
17
+ /** ISO 8601 timestamp — the timeline's discriminator. */
18
+ ts: string;
19
+ /** Optional: the agent that emitted the event. */
20
+ agentName?: string;
21
+ }
22
+ type SwarmRunEvent = (BaseEvent & {
23
+ kind: "phase";
24
+ phase: string;
25
+ }) | (BaseEvent & {
26
+ kind: "thought";
27
+ text: string;
28
+ }) | (BaseEvent & {
29
+ kind: "tool_call";
30
+ /** A `ToolCallEvent`-shaped object. Kept opaque (`Record<string, unknown>`-like)
31
+ * here so orchestrator doesn't depend on `@lumea-labs/tool-calls` for types;
32
+ * the consumer (`<SwarmRunActivityStream/>`'s renderer) can pass it
33
+ * through to `<ToolCallChip/>` without conversion. */
34
+ tool: {
35
+ id: string;
36
+ name: string;
37
+ state: "calling" | "completed" | "error";
38
+ arguments?: Record<string, unknown>;
39
+ result?: string;
40
+ summary?: string;
41
+ };
42
+ }) | (BaseEvent & {
43
+ kind: "ask_user";
44
+ question: string;
45
+ }) | (BaseEvent & {
46
+ kind: "user_input";
47
+ text: string;
48
+ }) | (BaseEvent & {
49
+ kind: "output";
50
+ text: string;
51
+ partial?: boolean;
52
+ }) | (BaseEvent & {
53
+ kind: "artifact";
54
+ artifact: SwarmArtifact;
55
+ }) | (BaseEvent & {
56
+ kind: "error";
57
+ message: string;
58
+ }) | (BaseEvent & {
59
+ kind: "retry";
60
+ attempt: number;
61
+ reason?: string;
62
+ }) | (BaseEvent & {
63
+ kind: "checkpoint";
64
+ name: string;
65
+ verdict: "passed" | "rejected" | "pending";
66
+ message?: string;
67
+ }) | (BaseEvent & {
68
+ kind: "score";
69
+ criterion: string;
70
+ value: number;
71
+ scale?: {
72
+ min: number;
73
+ max: number;
74
+ };
75
+ reasoning?: string;
76
+ });
77
+ /**
78
+ * Ordered chronological timeline of events for a single Swarm run.
79
+ * Just an alias for ergonomics — `buildSwarmRunActivity(...)` produces
80
+ * one of these and `<SwarmRunActivityStream events={...}/>` consumes it.
81
+ */
82
+ type SwarmRunActivity = SwarmRunEvent[];
83
+ /**
84
+ * Workspace task — opaque, matches the Polpo SDK `Task` shape but
85
+ * narrowed to the fields the workspace sidebar needs. The package
86
+ * doesn't take a hard dep on `@polpo-ai/sdk`; consumer maps as needed.
87
+ */
88
+ type WorkspaceTaskStatus = "draft" | "pending" | "awaiting_approval" | "assigned" | "in_progress" | "review" | "done" | "failed";
89
+ interface WorkspaceTask {
90
+ id: string;
91
+ title: string;
92
+ description?: string;
93
+ status: WorkspaceTaskStatus;
94
+ /** Mission group key — usually mission name or id. */
95
+ group?: string;
96
+ assignTo?: string;
97
+ /** ISO 8601. */
98
+ updatedAt?: string;
99
+ }
100
+ /**
101
+ * Workspace mission — opaque, the sidebar uses `id`, `name`, and the
102
+ * raw `data` blob (JSON-encoded plan) when expanding plan-only tasks.
103
+ */
104
+ interface WorkspaceMission {
105
+ id: string;
106
+ name: string;
107
+ /** JSON-encoded plan. The sidebar parses it on the fly to surface
108
+ * not-yet-instantiated plan tasks alongside runtime tasks. */
109
+ data?: string;
110
+ updatedAt?: string;
111
+ }
112
+ /**
113
+ * Opaque evaluation overlay — structurally compatible with
114
+ * `JudgeReport` from `@lumea-labs/judge` so consumers can pass the
115
+ * judge package's own data straight through. Kept as a local type
116
+ * here so orchestrator doesn't take a hard dep on judge.
117
+ */
118
+ interface SwarmEvaluation {
119
+ id: string;
120
+ evaluatedAt: string;
121
+ scores: Array<{
122
+ criterion: string;
123
+ value: number;
124
+ scale?: {
125
+ min: number;
126
+ max: number;
127
+ };
128
+ reasoning?: string;
129
+ evidence?: string;
130
+ }>;
131
+ verdict: {
132
+ overall: number;
133
+ scale?: {
134
+ min: number;
135
+ max: number;
136
+ };
137
+ passed?: boolean;
138
+ summary?: string;
139
+ };
140
+ }
141
+ type SwarmArtifactKind = "file" | "url" | "image" | "data" | "note";
142
+ /**
143
+ * A produced output of a run that's worth surfacing on its own — distinct
144
+ * from the textual `output` (which is the assistant's reply). Files
145
+ * created/edited, URLs reached, structured data dropped — these belong
146
+ * here so consumers can show a file ledger / link list / preview grid.
147
+ */
148
+ interface SwarmArtifact {
149
+ id: string;
150
+ kind: SwarmArtifactKind;
151
+ /** Short human label (filename, page title, dataset name). */
152
+ label: string;
153
+ /** Local-ish path (when `kind === "file"`). */
154
+ path?: string;
155
+ /** Absolute URL (when `kind === "url"` / `"image"`). */
156
+ url?: string;
157
+ /** One-line description / hover preview. */
158
+ summary?: string;
159
+ /** Free-form tags rendered as small chips. */
160
+ tags?: string[];
161
+ }
162
+ interface SwarmToolCall {
163
+ id: string;
164
+ name: string;
165
+ state: "calling" | "completed" | "error";
166
+ /** Short human summary — e.g. file path, query, command. */
167
+ summary?: string;
168
+ /** Tool input arguments. Optional, but when present this shape is
169
+ * compatible with `@lumea-labs/chat`'s `ToolCallEvent`, so the same
170
+ * object can be handed to `<ToolCallChip>` for rich rendering
171
+ * without conversion. */
172
+ arguments?: Record<string, unknown>;
173
+ /** Tool execution result on completion. Same compatibility note as
174
+ * `arguments`. */
175
+ result?: string;
176
+ }
177
+ interface SwarmRun {
178
+ id: string;
179
+ agent: SwarmAgentRef;
180
+ /** Short, scannable label — displayed in lists, cards, kanban tiles,
181
+ * timeline rows. Maps to `Task.title` from Polpo. */
182
+ title: string;
183
+ /** Full instruction / prompt given to the agent — shown in the run
184
+ * detail's goal block. Maps to `Task.description` from Polpo (or
185
+ * the `goal` argument passed to `SwarmStartInput.start()`). */
186
+ goal: string;
187
+ status: SwarmRunStatus;
188
+ /** Future ISO timestamp when the run is scheduled to fire. Set on
189
+ * runs with status `"scheduled"` (and optionally `"pending"`). */
190
+ scheduledFor?: string;
191
+ /** ISO timestamps. */
192
+ startedAt?: string;
193
+ finishedAt?: string;
194
+ /** 0..1 when known, otherwise undefined → indeterminate. */
195
+ progress?: number;
196
+ /** Latest log line for the running list view. */
197
+ lastLog?: string;
198
+ /** Currently-active or recently-completed tool calls. */
199
+ toolCalls?: SwarmToolCall[];
200
+ /** Produced artifacts — files, URLs, datasets. Distinct from `output`,
201
+ * which is the textual reply. */
202
+ artifacts?: SwarmArtifact[];
203
+ /** Output text on completion. May be Markdown. */
204
+ output?: string;
205
+ /** Error message on failure. */
206
+ error?: string;
207
+ /** How many retries this run has gone through. */
208
+ retries?: number;
209
+ /** Max retries allowed before the run is marked failed. */
210
+ maxRetries?: number;
211
+ /** Optional LLM-as-a-judge / G-Eval result for this run. Surface via
212
+ * `<SwarmRunDetail renderEvaluation/>` to plug in the consumer's
213
+ * preferred view (radar, score grid, full report). */
214
+ evaluation?: SwarmEvaluation;
215
+ /** Background runs do not block the user — surfaced in a dedicated
216
+ * group. Foreground runs are the ones the user is actively watching. */
217
+ background?: boolean;
218
+ /** Parent run when this was spawned by another agent. */
219
+ parentRunId?: string;
220
+ }
221
+ interface SwarmStartInput {
222
+ agent: string;
223
+ goal: string;
224
+ background?: boolean;
225
+ /** Future ISO timestamp — when set the adapter creates the run in
226
+ * `"scheduled"` state and only flips it to `"running"` when the
227
+ * fire-time is reached. */
228
+ startAt?: string;
229
+ }
230
+ interface SwarmAdapter {
231
+ list?: () => Promise<SwarmRun[]>;
232
+ /** Subscribe to live updates of the whole swarm. Return unsubscribe. */
233
+ subscribe?: (onUpdate: (runs: SwarmRun[]) => void) => () => void;
234
+ start?: (input: SwarmStartInput) => Promise<SwarmRun>;
235
+ cancel?: (id: string) => Promise<void>;
236
+ retry?: (id: string) => Promise<SwarmRun>;
237
+ }
238
+ interface SwarmCapabilities {
239
+ canList: boolean;
240
+ canSubscribe: boolean;
241
+ canStart: boolean;
242
+ canCancel: boolean;
243
+ canRetry: boolean;
244
+ }
245
+ declare function deriveSwarmCapabilities(a: SwarmAdapter): SwarmCapabilities;
246
+ interface SwarmCounts {
247
+ scheduled: number;
248
+ pending: number;
249
+ running: number;
250
+ review: number;
251
+ done: number;
252
+ failed: number;
253
+ cancelled: number;
254
+ total: number;
255
+ }
256
+ interface SwarmLabels {
257
+ scheduled: string;
258
+ pending: string;
259
+ running: string;
260
+ review: string;
261
+ done: string;
262
+ failed: string;
263
+ cancelled: string;
264
+ cancel: string;
265
+ retry: string;
266
+ cancelAll: string;
267
+ retryFailed: string;
268
+ noRuns: string;
269
+ /** "Background" group header shown in lists/grids. */
270
+ backgroundGroup: string;
271
+ foregroundGroup: string;
272
+ goal: string;
273
+ output: string;
274
+ error: string;
275
+ log: string;
276
+ tools: string;
277
+ /** "X running · Y done · Z failed" row in the status bar. */
278
+ countsLine: (c: SwarmCounts) => string;
279
+ /** "Started Xs ago" / "Took Ys" — short relative durations. */
280
+ startedAgo: (seconds: number) => string;
281
+ duration: (seconds: number) => string;
282
+ }
283
+ declare const defaultSwarmLabels: SwarmLabels;
284
+
285
+ export { type SwarmAdapter, type SwarmAgentRef, type SwarmArtifact, type SwarmArtifactKind, type SwarmCapabilities, type SwarmCounts, type SwarmEvaluation, type SwarmLabels, type SwarmRun, type SwarmRunActivity, type SwarmRunEvent, type SwarmRunEventKind, type SwarmRunStatus, type SwarmStartInput, type SwarmToolCall, type WorkspaceMission, type WorkspaceTask, type WorkspaceTaskStatus, defaultSwarmLabels, deriveSwarmCapabilities };
package/dist/types.js ADDED
@@ -0,0 +1,44 @@
1
+ function deriveSwarmCapabilities(a) {
2
+ return {
3
+ canList: typeof a.list === "function",
4
+ canSubscribe: typeof a.subscribe === "function",
5
+ canStart: typeof a.start === "function",
6
+ canCancel: typeof a.cancel === "function",
7
+ canRetry: typeof a.retry === "function"
8
+ };
9
+ }
10
+ const defaultSwarmLabels = {
11
+ scheduled: "Scheduled",
12
+ pending: "Pending",
13
+ running: "Running",
14
+ review: "In review",
15
+ done: "Done",
16
+ failed: "Failed",
17
+ cancelled: "Cancelled",
18
+ cancel: "Cancel",
19
+ retry: "Retry",
20
+ cancelAll: "Cancel all",
21
+ retryFailed: "Retry failed",
22
+ noRuns: "No agents are running. Launch one to get started.",
23
+ backgroundGroup: "Background",
24
+ foregroundGroup: "Foreground",
25
+ goal: "Goal",
26
+ output: "Output",
27
+ error: "Error",
28
+ log: "Log",
29
+ tools: "Tools",
30
+ countsLine: (c) => `${c.running} running \xB7 ${c.done} done \xB7 ${c.failed} failed`,
31
+ startedAgo: (s) => `${formatShort(s)} ago`,
32
+ duration: (s) => formatShort(s)
33
+ };
34
+ function formatShort(seconds) {
35
+ if (!Number.isFinite(seconds) || seconds < 0) return "\u2014";
36
+ if (seconds < 1) return `${Math.round(seconds * 1e3)}ms`;
37
+ if (seconds < 60) return `${Math.round(seconds)}s`;
38
+ if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
39
+ return `${(seconds / 3600).toFixed(1)}h`;
40
+ }
41
+ export {
42
+ defaultSwarmLabels,
43
+ deriveSwarmCapabilities
44
+ };