@signal9/era-ui 4.15.4 → 4.16.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/dist/ai/swarm/context.d.ts +7 -1
- package/dist/ai/swarm/index.d.ts +1 -1
- package/dist/ai/swarm/index.js +1 -1
- package/dist/ai/swarm/swarm-artifact.svelte +24 -2
- package/dist/ai/swarm/swarm-node.svelte +35 -3
- package/dist/ai/swarm/swarm-root.svelte +7 -1
- package/dist/ai/swarm/swarm-root.svelte.d.ts +3 -1
- package/dist/ai/swarm/swarm-sandbox.svelte +30 -2
- package/dist/ai/swarm/swarm.d.ts +219 -5
- package/dist/ai/swarm/swarm.js +149 -0
- package/dist/apps/llm-shell/llm-shell.svelte +15 -1
- package/dist/apps/llm-shell/llm-shell.svelte.d.ts +6 -0
- package/dist/apps/llm-shell/llm-swarm.svelte +9 -2
- package/dist/apps/llm-shell/llm-swarm.svelte.d.ts +2 -0
- package/dist/apps/notes/notes.svelte +9 -4
- package/dist/era-ui.css +1 -1
- package/dist/generated-docs/llms-full.txt +33 -0
- package/dist/generated-docs/llms.txt +1 -0
- package/dist/generated-docs/manifest.json +12 -1
- package/dist/generated-docs/utilities.json +10 -0
- package/dist/generated-docs/utilities.md +32 -0
- package/dist/styles/index.css +25 -0
- package/dist/ui/combobox/combobox-input.svelte +2 -2
- package/dist/ui/input/input.svelte +6 -3
- package/dist/ui/pin-input/pin-input-cell.svelte +4 -1
- package/package.json +1 -1
package/dist/ai/swarm/swarm.js
CHANGED
|
@@ -25,6 +25,31 @@
|
|
|
25
25
|
* host that already holds swarm state in its own store calls these functions
|
|
26
26
|
* directly and never constructs one.
|
|
27
27
|
*/
|
|
28
|
+
/** Whether a sandbox id names a live fork of another VM. */
|
|
29
|
+
export function isSwarmSandboxFork(id) {
|
|
30
|
+
return /-fk-[0-9a-z]+$/i.test(id);
|
|
31
|
+
}
|
|
32
|
+
/** The parent VM a forked sandbox was cloned from, or null for a fresh VM. */
|
|
33
|
+
export function swarmSandboxForkOf(sandbox) {
|
|
34
|
+
if (sandbox.forkOf)
|
|
35
|
+
return sandbox.forkOf;
|
|
36
|
+
if (sandbox.id && isSwarmSandboxFork(sandbox.id))
|
|
37
|
+
return sandbox.id.slice(0, sandbox.id.lastIndexOf('-fk-'));
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/** Narrow an agent's progress to the retrying arm (gates the countdown chip). */
|
|
41
|
+
export function isSwarmAgentRetrying(progress) {
|
|
42
|
+
return progress?.phase === 'retrying';
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The countdown a retrying agent renders: `attempt 2 — resuming in 8s`.
|
|
46
|
+
* Same clock rule as everything here — `now` is an argument.
|
|
47
|
+
*/
|
|
48
|
+
export function formatSwarmRetry(progress, now) {
|
|
49
|
+
const seconds = Math.ceil((progress.resumeAt - now) / 1000);
|
|
50
|
+
const when = seconds > 0 ? `resuming in ${seconds}s` : 'resuming now';
|
|
51
|
+
return `attempt ${progress.attempt} — ${when}`;
|
|
52
|
+
}
|
|
28
53
|
/**
|
|
29
54
|
* Build the delegation forest from a flat agent list.
|
|
30
55
|
*
|
|
@@ -96,12 +121,15 @@ export function summarizeSwarm(agents) {
|
|
|
96
121
|
failed: 0,
|
|
97
122
|
cancelled: 0,
|
|
98
123
|
active: 0,
|
|
124
|
+
settled: 0,
|
|
99
125
|
sandboxes: 0
|
|
100
126
|
};
|
|
101
127
|
for (const agent of agents) {
|
|
102
128
|
counts[agent.status]++;
|
|
103
129
|
if (isSwarmAgentActive(agent))
|
|
104
130
|
counts.active++;
|
|
131
|
+
else
|
|
132
|
+
counts.settled++;
|
|
105
133
|
if (agent.sandbox && agent.sandbox.status !== 'terminated')
|
|
106
134
|
counts.sandboxes++;
|
|
107
135
|
}
|
|
@@ -194,3 +222,124 @@ export function formatSwarmDuration(ms) {
|
|
|
194
222
|
return `${minutes}m ${String(Math.floor(seconds % 60)).padStart(2, '0')}s`;
|
|
195
223
|
return `${Math.floor(minutes / 60)}h ${String(minutes % 60).padStart(2, '0')}m`;
|
|
196
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Classify a payload STRUCTURALLY — by the fields it carries, never by a tool
|
|
227
|
+
* name or a declared type tag (producers rename tools; shapes are the
|
|
228
|
+
* contract). Detection order mirrors the reference implementation: exec
|
|
229
|
+
* (exitCode + stdout) → chart (viz.type + data.columns[]) → component
|
|
230
|
+
* (component string) → markdown (a lone text/markdown field) → json.
|
|
231
|
+
* Returns null only for empty input, so a renderer switch is total.
|
|
232
|
+
*/
|
|
233
|
+
export function parseSwarmContent(payload) {
|
|
234
|
+
if (payload == null)
|
|
235
|
+
return null;
|
|
236
|
+
if (typeof payload !== 'object')
|
|
237
|
+
return { kind: 'json', data: payload };
|
|
238
|
+
const p = payload;
|
|
239
|
+
if (typeof p.exitCode === 'number' && typeof p.stdout === 'string') {
|
|
240
|
+
return {
|
|
241
|
+
kind: 'exec',
|
|
242
|
+
exitCode: p.exitCode,
|
|
243
|
+
stdout: p.stdout,
|
|
244
|
+
stderr: typeof p.stderr === 'string' ? p.stderr : '',
|
|
245
|
+
hint: typeof p.hint === 'string' ? p.hint : undefined,
|
|
246
|
+
command: typeof p.command === 'string' ? p.command : undefined
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const viz = p.viz;
|
|
250
|
+
const data = p.data;
|
|
251
|
+
if (viz &&
|
|
252
|
+
typeof viz.type === 'string' &&
|
|
253
|
+
data &&
|
|
254
|
+
Array.isArray(data.columns) &&
|
|
255
|
+
Array.isArray(data.rows)) {
|
|
256
|
+
return {
|
|
257
|
+
kind: 'chart',
|
|
258
|
+
viz: viz,
|
|
259
|
+
data: data,
|
|
260
|
+
title: typeof p.title === 'string' ? p.title : undefined
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (typeof p.component === 'string') {
|
|
264
|
+
return {
|
|
265
|
+
kind: 'component',
|
|
266
|
+
component: p.component,
|
|
267
|
+
props: p.props ?? undefined,
|
|
268
|
+
inline: p.inline !== false
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const text = p.markdown ?? p.text;
|
|
272
|
+
if (typeof text === 'string' && Object.keys(p).length <= 2) {
|
|
273
|
+
return { kind: 'markdown', text };
|
|
274
|
+
}
|
|
275
|
+
return { kind: 'json', data: payload };
|
|
276
|
+
}
|
|
277
|
+
/** An artifact's rich body, when it has one — `parseSwarmContent` over `payload`. */
|
|
278
|
+
export function swarmArtifactContent(artifact) {
|
|
279
|
+
return parseSwarmContent(artifact.payload ?? null);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Summarize each delegation tree in a flat agent list to a table row.
|
|
283
|
+
* Rows come back in first-seen order; the caller sorts.
|
|
284
|
+
*/
|
|
285
|
+
export function summarizeSwarmRoots(agents) {
|
|
286
|
+
const byRoot = new Map();
|
|
287
|
+
for (const root of buildSwarmTree(agents)) {
|
|
288
|
+
byRoot.set(root.agent.id, flattenSwarmTree([root]).map((node) => node.agent));
|
|
289
|
+
}
|
|
290
|
+
const rows = [];
|
|
291
|
+
for (const [rootId, members] of byRoot) {
|
|
292
|
+
const root = members[0];
|
|
293
|
+
let updatedAt;
|
|
294
|
+
for (const agent of members) {
|
|
295
|
+
const at = agent.endedAt ?? agent.startedAt;
|
|
296
|
+
if (at != null && (updatedAt == null || at > updatedAt))
|
|
297
|
+
updatedAt = at;
|
|
298
|
+
}
|
|
299
|
+
rows.push({
|
|
300
|
+
rootId,
|
|
301
|
+
title: root.task,
|
|
302
|
+
status: root.status,
|
|
303
|
+
counts: summarizeSwarm(members),
|
|
304
|
+
updatedAt
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return rows;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* An agent and every transitive descendant, in tree order.
|
|
311
|
+
*
|
|
312
|
+
* The cascade selector: the reference runtime cancels SUBTREES (and cancels
|
|
313
|
+
* dependents when a dependency fails), so a host wiring `onCancel` calls this
|
|
314
|
+
* to know what a cancellation will take with it — and to filter it to
|
|
315
|
+
* `isSwarmAgentActive` for the confirm prompt's count.
|
|
316
|
+
*/
|
|
317
|
+
export function swarmSubtree(agents, agentId) {
|
|
318
|
+
const byParent = new Map();
|
|
319
|
+
let self;
|
|
320
|
+
for (const agent of agents) {
|
|
321
|
+
if (agent.id === agentId)
|
|
322
|
+
self = agent;
|
|
323
|
+
if (agent.parentId) {
|
|
324
|
+
const siblings = byParent.get(agent.parentId);
|
|
325
|
+
if (siblings)
|
|
326
|
+
siblings.push(agent);
|
|
327
|
+
else
|
|
328
|
+
byParent.set(agent.parentId, [agent]);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (!self)
|
|
332
|
+
return [];
|
|
333
|
+
const out = [];
|
|
334
|
+
const seen = new Set();
|
|
335
|
+
const walk = (agent) => {
|
|
336
|
+
if (seen.has(agent.id))
|
|
337
|
+
return;
|
|
338
|
+
seen.add(agent.id);
|
|
339
|
+
out.push(agent);
|
|
340
|
+
for (const child of byParent.get(agent.id) ?? [])
|
|
341
|
+
walk(child);
|
|
342
|
+
};
|
|
343
|
+
walk(self);
|
|
344
|
+
return out;
|
|
345
|
+
}
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
onStop,
|
|
49
49
|
onOpenAgent,
|
|
50
50
|
onCancelAgent,
|
|
51
|
+
swarmRenderers,
|
|
51
52
|
empty,
|
|
52
53
|
class: className
|
|
53
54
|
}: {
|
|
@@ -72,6 +73,12 @@
|
|
|
72
73
|
onOpenAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
73
74
|
/** Cancel a swarm agent. Offered only while the agent has work left to stop. */
|
|
74
75
|
onCancelAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
76
|
+
/**
|
|
77
|
+
* Inline renderers for swarm artifact payloads, one per content kind
|
|
78
|
+
* (chart, exec, component, …) — see `SwarmContentRenderers`. The host
|
|
79
|
+
* registers them once here; every swarm block resolves the same registry.
|
|
80
|
+
*/
|
|
81
|
+
swarmRenderers?: AI.Swarm.SwarmContentRenderers;
|
|
75
82
|
/** Replaces the built-in empty state for a conversation with no messages. */
|
|
76
83
|
empty?: Snippet;
|
|
77
84
|
class?: string;
|
|
@@ -278,7 +285,14 @@
|
|
|
278
285
|
</AI.Tool.Content>
|
|
279
286
|
</AI.Tool.Root>
|
|
280
287
|
{:else if block.kind === 'swarm'}
|
|
281
|
-
<LlmSwarmBlock
|
|
288
|
+
<LlmSwarmBlock
|
|
289
|
+
swarm={block.swarm}
|
|
290
|
+
view={block.view}
|
|
291
|
+
{now}
|
|
292
|
+
{onOpenAgent}
|
|
293
|
+
{onCancelAgent}
|
|
294
|
+
renderers={swarmRenderers}
|
|
295
|
+
/>
|
|
282
296
|
{:else if block.kind === 'approval'}
|
|
283
297
|
<AI.Confirmation.Root state={approvalState}>
|
|
284
298
|
Approve <span class="font-mono text-fg">{block.toolName}</span>? Review the arguments before
|
|
@@ -42,6 +42,12 @@ type $$ComponentProps = {
|
|
|
42
42
|
onOpenAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
43
43
|
/** Cancel a swarm agent. Offered only while the agent has work left to stop. */
|
|
44
44
|
onCancelAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Inline renderers for swarm artifact payloads, one per content kind
|
|
47
|
+
* (chart, exec, component, …) — see `SwarmContentRenderers`. The host
|
|
48
|
+
* registers them once here; every swarm block resolves the same registry.
|
|
49
|
+
*/
|
|
50
|
+
swarmRenderers?: AI.Swarm.SwarmContentRenderers;
|
|
45
51
|
/** Replaces the built-in empty state for a conversation with no messages. */
|
|
46
52
|
empty?: Snippet;
|
|
47
53
|
class?: string;
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
view = 'tree',
|
|
18
18
|
now = Date.now(),
|
|
19
19
|
onOpenAgent,
|
|
20
|
-
onCancelAgent
|
|
20
|
+
onCancelAgent,
|
|
21
|
+
renderers
|
|
21
22
|
}: {
|
|
22
23
|
swarm: LlmSwarm;
|
|
23
24
|
/** `tree` keeps the delegation shape; `grid` is the console layout. */
|
|
@@ -25,6 +26,8 @@
|
|
|
25
26
|
now?: number;
|
|
26
27
|
onOpenAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
27
28
|
onCancelAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
29
|
+
/** Host renderers for artifact payloads — handed to `Swarm.Root`'s registry. */
|
|
30
|
+
renderers?: AI.Swarm.SwarmContentRenderers;
|
|
28
31
|
} = $props();
|
|
29
32
|
|
|
30
33
|
// A plain record, not a Map: it is rebuilt whole by the derived, so it never
|
|
@@ -42,13 +45,17 @@
|
|
|
42
45
|
const artifactsFor = (agent: AI.Swarm.SwarmAgent) => byAgent[agent.id] ?? NONE;
|
|
43
46
|
</script>
|
|
44
47
|
|
|
45
|
-
<AI.Swarm.Root {now} onOpen={onOpenAgent} onCancel={onCancelAgent}>
|
|
48
|
+
<AI.Swarm.Root {now} onOpen={onOpenAgent} onCancel={onCancelAgent} {renderers}>
|
|
46
49
|
<AI.Swarm.Header title={swarm.plan?.title} status={swarm.plan?.status}>
|
|
47
50
|
<AI.Swarm.Counts agents={swarm.agents} />
|
|
48
51
|
</AI.Swarm.Header>
|
|
49
52
|
|
|
50
53
|
{#if view === 'grid'}
|
|
51
54
|
<AI.Swarm.Grid agents={swarm.agents} />
|
|
55
|
+
<!-- The console view was dropping artifacts on the floor — cards show the
|
|
56
|
+
agents, and nothing showed what they PRODUCED. The tree hangs them off
|
|
57
|
+
each agent's detail chip; the grid gets them as one labelled section. -->
|
|
58
|
+
<AI.Swarm.Artifacts artifacts={swarm.artifacts ?? []} />
|
|
52
59
|
{:else}
|
|
53
60
|
<!-- The chip names what it opens, so the section heading inside would say it
|
|
54
61
|
twice — the detail is just the cards. -->
|
|
@@ -17,6 +17,8 @@ type $$ComponentProps = {
|
|
|
17
17
|
now?: number;
|
|
18
18
|
onOpenAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
19
19
|
onCancelAgent?: (agent: AI.Swarm.SwarmAgent) => void;
|
|
20
|
+
/** Host renderers for artifact payloads — handed to `Swarm.Root`'s registry. */
|
|
21
|
+
renderers?: AI.Swarm.SwarmContentRenderers;
|
|
20
22
|
};
|
|
21
23
|
declare const LlmSwarm: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
22
24
|
type LlmSwarm = ReturnType<typeof LlmSwarm>;
|
|
@@ -295,7 +295,7 @@
|
|
|
295
295
|
{#if store.loading}
|
|
296
296
|
<div class="flex flex-col gap-gutter p-gutter">
|
|
297
297
|
{#each Array.from({ length: 5 }, (_, i) => i) as i (i)}
|
|
298
|
-
<Skeleton class="h-md rounded-
|
|
298
|
+
<Skeleton class="h-md rounded-md" />
|
|
299
299
|
{/each}
|
|
300
300
|
</div>
|
|
301
301
|
{:else if visible.length === 0}
|
|
@@ -321,13 +321,18 @@
|
|
|
321
321
|
the row instead of wrapping it, so the row stays a direct
|
|
322
322
|
role="option" child of the listbox. A wrapper element there
|
|
323
323
|
would put a non-option between them and break the relationship
|
|
324
|
-
screen readers rely on.
|
|
324
|
+
screen readers rely on.
|
|
325
|
+
rounded-md, not rounded-item: item-rd is the FLOATING-MENU row
|
|
326
|
+
radius, gated by --era-item-rd-scale = 0 on flat — it ignores
|
|
327
|
+
the corners axis there. A list row on a plain surface takes its
|
|
328
|
+
own tier radius (the conversations-rail pattern), which rides
|
|
329
|
+
the corners toggle. -->
|
|
325
330
|
<ContextMenu.Root>
|
|
326
331
|
<ContextMenu.Trigger>
|
|
327
332
|
{#snippet child({ props })}
|
|
328
333
|
<div
|
|
329
334
|
{...props}
|
|
330
|
-
class="group/row flex h-md cursor-pointer items-center gap-(--era-xxs-inset-md) rounded-
|
|
335
|
+
class="group/row flex h-md cursor-pointer items-center gap-(--era-xxs-inset-md) rounded-md px-(--era-xxs-inset-md) era-text-trim {current
|
|
331
336
|
? 'bg-hover text-bright shadow-(--era-shadow-pressed)'
|
|
332
337
|
: 'hover:bg-highlight hover:shadow-highlight'}"
|
|
333
338
|
role="option"
|
|
@@ -366,7 +371,7 @@
|
|
|
366
371
|
{formatAge(note.updatedAt)}
|
|
367
372
|
</span>
|
|
368
373
|
<span
|
|
369
|
-
class="hidden shrink-0 items-center gap-(--era-
|
|
374
|
+
class="hidden shrink-0 items-center gap-(--era-xxs-inset-md) group-hover/row:flex"
|
|
370
375
|
>
|
|
371
376
|
<Button
|
|
372
377
|
icon
|