@hanzo/ui 8.13.3 → 8.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/agents/Board.cjs +119 -0
  2. package/dist/agents/Board.d.ts +98 -0
  3. package/dist/agents/Board.d.ts.map +1 -0
  4. package/dist/agents/Board.js +115 -0
  5. package/dist/agents/Board.js.map +1 -0
  6. package/dist/agents/Screen.cjs +91 -0
  7. package/dist/agents/Screen.d.ts +53 -0
  8. package/dist/agents/Screen.d.ts.map +1 -0
  9. package/dist/agents/Screen.js +89 -0
  10. package/dist/agents/Screen.js.map +1 -0
  11. package/dist/agents/Steer.cjs +42 -0
  12. package/dist/agents/Steer.d.ts +54 -0
  13. package/dist/agents/Steer.d.ts.map +1 -0
  14. package/dist/agents/Steer.js +39 -0
  15. package/dist/agents/Steer.js.map +1 -0
  16. package/dist/agents/Transcript.cjs +59 -0
  17. package/dist/agents/Transcript.d.ts +50 -0
  18. package/dist/agents/Transcript.d.ts.map +1 -0
  19. package/dist/agents/Transcript.js +56 -0
  20. package/dist/agents/Transcript.js.map +1 -0
  21. package/dist/agents/index.cjs +36 -0
  22. package/dist/agents/index.d.ts +26 -0
  23. package/dist/agents/index.d.ts.map +1 -0
  24. package/dist/agents/index.js +26 -0
  25. package/dist/agents/index.js.map +1 -0
  26. package/dist/backends/gui/drawer.cjs +20 -1
  27. package/dist/backends/gui/drawer.d.ts +11 -1
  28. package/dist/backends/gui/drawer.d.ts.map +1 -1
  29. package/dist/backends/gui/drawer.js +18 -0
  30. package/dist/backends/gui/drawer.js.map +1 -1
  31. package/dist/backends/gui/index.cjs +2 -1
  32. package/dist/backends/gui/index.d.ts +1 -1
  33. package/dist/backends/gui/index.d.ts.map +1 -1
  34. package/dist/backends/gui/index.js +1 -1
  35. package/dist/backends/gui/index.js.map +1 -1
  36. package/dist/box.cjs +19 -0
  37. package/dist/box.d.ts.map +1 -1
  38. package/dist/box.js +19 -0
  39. package/dist/box.js.map +1 -1
  40. package/dist/chat/Sidebar.cjs +26 -19
  41. package/dist/chat/Sidebar.d.ts +46 -19
  42. package/dist/chat/Sidebar.d.ts.map +1 -1
  43. package/dist/chat/Sidebar.js +26 -19
  44. package/dist/chat/Sidebar.js.map +1 -1
  45. package/dist/gallery.html +1 -1
  46. package/dist/index.cjs +3 -2
  47. package/dist/index.d.ts +1 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/index.node.js +1 -1
  52. package/dist/primitives/DrawerTrigger.cjs +8 -0
  53. package/dist/primitives/DrawerTrigger.d.ts +2 -0
  54. package/dist/primitives/DrawerTrigger.d.ts.map +1 -0
  55. package/dist/primitives/DrawerTrigger.js +5 -0
  56. package/dist/primitives/DrawerTrigger.js.map +1 -0
  57. package/package.json +13 -7
@@ -0,0 +1,119 @@
1
+ 'use client';"use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.RunRow = RunRow;
5
+ exports.AgentBoard = AgentBoard;
6
+ exports.nest = nest;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ /**
9
+ * AgentBoard — the agents working view: who is running, on what, and where.
10
+ *
11
+ * A run is a SESSION, and a session that spawned others is the root of a tree.
12
+ * A flat list draws a fan-out as N unrelated rows and loses the one fact worth
13
+ * seeing — that they are one piece of work — so the board nests children under
14
+ * their root and a collapsed root says how many it is hiding.
15
+ *
16
+ * Grouped by PROJECT, because that is the question a person opens this with:
17
+ * not "what is running" but "what is running on the thing I am working on".
18
+ * A session with no project groups under `ungrouped`, named rather than hidden.
19
+ *
20
+ * Presentational. It takes rows and raises `onOpen`; it does no fetching, no
21
+ * grouping policy beyond the field it is given, and no status derivation. The
22
+ * surface holds the data — `@hanzo/ai`'s `sessions.list` / `sessions.tree` and
23
+ * the stream — so the same board serves hanzo.ai, a channel workspace, or
24
+ * anything else that can answer with rows.
25
+ */
26
+ const gui_1 = require("@hanzo/gui");
27
+ const lucide_icons_2_1 = require("@hanzogui/lucide-icons-2");
28
+ const react_1 = require("react");
29
+ const slot_1 = require("../backends/gui/slot.cjs");
30
+ const TONE = {
31
+ running: '$green10',
32
+ paused: '$yellow10',
33
+ done: '$color10',
34
+ error: '$red10',
35
+ unknown: '$color8',
36
+ };
37
+ /** The label a status wears. Separate from the colour so neither is guessed. */
38
+ const SAYS = {
39
+ running: 'running',
40
+ paused: 'paused',
41
+ done: 'done',
42
+ error: 'error',
43
+ unknown: '—',
44
+ };
45
+ function Dot({ status }) {
46
+ return ((0, jsx_runtime_1.jsx)(gui_1.YStack, { width: 7, height: 7, rounded: 9999, bg: TONE[status], shrink: 0, "aria-label": SAYS[status] }));
47
+ }
48
+ /**
49
+ * One row, and its children under it.
50
+ *
51
+ * Expansion is per-row local state: a board is read top-down and a caller that
52
+ * wanted to persist which roots are open would be holding UI state for a list
53
+ * it does not own. A root with children opens by default, because the fan-out
54
+ * is the thing worth seeing.
55
+ */
56
+ function RunRow({ run, depth = 0, active = false, onOpen, trailing, ...rest }) {
57
+ const kids = run.children ?? [];
58
+ const [open, setOpen] = (0, react_1.useState)(true);
59
+ return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('run-row'), ...rest, children: [(0, jsx_runtime_1.jsxs)(gui_1.XStack, { items: "center", gap: "$2", px: "$2", py: "$1.5", rounded: "$3", pl: 8 + depth * 16, cursor: "pointer", role: "button", tabIndex: 0, "aria-current": active ? 'true' : undefined, bg: active ? '$edge' : undefined, hoverStyle: { bg: active ? '$edge' : '$hover' }, pressStyle: { bg: '$raised' }, onPress: () => onOpen?.(run.id), children: [kids.length ? ((0, jsx_runtime_1.jsx)(gui_1.XStack, { width: 16, height: 16, items: "center", justify: "center", shrink: 0, role: "button", tabIndex: 0, "aria-expanded": open, "aria-label": open ? 'Hide sub-agents' : 'Show sub-agents', onPress: (e) => {
60
+ e.stopPropagation?.();
61
+ setOpen((v) => !v);
62
+ }, children: open ? (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ChevronDown, { size: 13, opacity: 0.6 }) : (0, jsx_runtime_1.jsx)(lucide_icons_2_1.ChevronRight, { size: 13, opacity: 0.6 }) })) : ((0, jsx_runtime_1.jsx)(gui_1.YStack, { width: 16, shrink: 0 })), (0, jsx_runtime_1.jsx)(Dot, { status: run.status }), (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", numberOfLines: 1, shrink: 1, color: active ? '$color' : '$quiet', children: run.agent }), run.doing ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", numberOfLines: 1, flex: 1, color: "$soft", children: run.doing })) : ((0, jsx_runtime_1.jsx)(gui_1.XStack, { flex: 1 })), kids.length && !open ? ((0, jsx_runtime_1.jsxs)(gui_1.SizableText, { size: "$1", color: "$soft", children: [kids.length, " sub-agent", kids.length === 1 ? '' : 's'] })) : null, run.target ? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: run.target })) : null, trailing] }), open && kids.length
63
+ ? kids.map((kid) => ((0, jsx_runtime_1.jsx)(RunRow, { run: kid, depth: depth + 1, onOpen: onOpen }, kid.id)))
64
+ : null] }));
65
+ }
66
+ /**
67
+ * The board.
68
+ *
69
+ * Groups are ordered by name with the un-projected group LAST, so the answer to
70
+ * "what is running on my project" is never below a pile of stray runs.
71
+ */
72
+ function AgentBoard({ runs, active, onOpen, empty, otherLabel = 'Other work', ...rest }) {
73
+ const groups = (0, react_1.useMemo)(() => group(runs, otherLabel), [runs, otherLabel]);
74
+ if (!runs.length) {
75
+ return ((0, jsx_runtime_1.jsx)(gui_1.YStack, { ...(0, slot_1.slot)('agent-board'), p: "$4", ...rest, children: empty ?? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", color: "$soft", children: "Nothing running." })) }));
76
+ }
77
+ return ((0, jsx_runtime_1.jsx)(gui_1.ScrollView, { ...(0, slot_1.slot)('agent-board'), flex: 1, showsVerticalScrollIndicator: false, ...rest, children: (0, jsx_runtime_1.jsx)(gui_1.YStack, { gap: "$1", p: "$2", children: groups.map(([project, rows]) => ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { mt: "$2", gap: "$0.5", children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", px: "$2", py: "$1", color: "$soft", fontWeight: "500", children: project }), rows.map((run) => ((0, jsx_runtime_1.jsx)(RunRow, { run: run, active: run.id === active, onOpen: onOpen }, run.id)))] }, project))) }) }));
78
+ }
79
+ /** Group roots by project, un-projected last. */
80
+ function group(runs, otherLabel) {
81
+ const by = new Map();
82
+ for (const run of runs) {
83
+ const key = run.project?.trim() || otherLabel;
84
+ const rows = by.get(key);
85
+ if (rows)
86
+ rows.push(run);
87
+ else
88
+ by.set(key, [run]);
89
+ }
90
+ return [...by.entries()].sort(([a], [b]) => a === otherLabel ? 1 : b === otherLabel ? -1 : a.localeCompare(b));
91
+ }
92
+ /**
93
+ * Build the tree a board draws from the flat list an API answers with.
94
+ *
95
+ * A session names its root rather than its parent, so a fan-out is TWO levels
96
+ * and not N: every non-root hangs off its root. A row whose root is not in the
97
+ * list is treated as a root itself — otherwise a filtered page silently drops
98
+ * work, which is the one thing a board must not do.
99
+ */
100
+ function nest(rows) {
101
+ const roots = new Map();
102
+ const orphans = [];
103
+ for (const row of rows) {
104
+ if (!row.rootId || row.rootId === row.id)
105
+ roots.set(row.id, { ...row, children: [] });
106
+ }
107
+ for (const row of rows) {
108
+ if (!row.rootId || row.rootId === row.id)
109
+ continue;
110
+ const root = roots.get(row.rootId);
111
+ if (root)
112
+ root.children.push({ ...row, children: [] });
113
+ else
114
+ orphans.push(row);
115
+ }
116
+ for (const row of orphans)
117
+ roots.set(row.id, { ...row, children: [] });
118
+ return [...roots.values()];
119
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * AgentBoard — the agents working view: who is running, on what, and where.
3
+ *
4
+ * A run is a SESSION, and a session that spawned others is the root of a tree.
5
+ * A flat list draws a fan-out as N unrelated rows and loses the one fact worth
6
+ * seeing — that they are one piece of work — so the board nests children under
7
+ * their root and a collapsed root says how many it is hiding.
8
+ *
9
+ * Grouped by PROJECT, because that is the question a person opens this with:
10
+ * not "what is running" but "what is running on the thing I am working on".
11
+ * A session with no project groups under `ungrouped`, named rather than hidden.
12
+ *
13
+ * Presentational. It takes rows and raises `onOpen`; it does no fetching, no
14
+ * grouping policy beyond the field it is given, and no status derivation. The
15
+ * surface holds the data — `@hanzo/ai`'s `sessions.list` / `sessions.tree` and
16
+ * the stream — so the same board serves hanzo.ai, a channel workspace, or
17
+ * anything else that can answer with rows.
18
+ */
19
+ import { ScrollView, YStack } from '@hanzo/gui';
20
+ import { type ComponentProps, type ReactNode } from 'react';
21
+ type Col = Omit<ComponentProps<typeof YStack>, 'children'>;
22
+ type Scroll = Omit<ComponentProps<typeof ScrollView>, 'children'>;
23
+ /** The states a run can be in. `unknown` is honest about a value we do not model. */
24
+ export type RunStatus = 'running' | 'paused' | 'done' | 'error' | 'unknown';
25
+ /**
26
+ * One run.
27
+ *
28
+ * `children` is the fan-out. It is the SHAPE the board draws, so a caller with
29
+ * a flat list plus parent ids builds the tree before handing it over — `nest()`
30
+ * below does exactly that and is exported for the purpose.
31
+ */
32
+ export interface Run {
33
+ id: string;
34
+ /** The agent doing the work. */
35
+ agent: string;
36
+ status: RunStatus;
37
+ /** What it is doing right now — one line, already trimmed by the caller. */
38
+ doing?: string;
39
+ /** Where it is running: a machine, a sandbox, a registered target. */
40
+ target?: string;
41
+ /** The project this run belongs to. Absent groups under "Other work". */
42
+ project?: string;
43
+ /** Sub-agents this run spawned. */
44
+ children?: Run[];
45
+ }
46
+ export interface RunRowProps extends Col {
47
+ run: Run;
48
+ depth?: number;
49
+ active?: boolean;
50
+ onOpen?: (id: string) => void;
51
+ /** Rendered at the end of the row — a watch affordance, a menu, a count. */
52
+ trailing?: ReactNode;
53
+ }
54
+ /**
55
+ * One row, and its children under it.
56
+ *
57
+ * Expansion is per-row local state: a board is read top-down and a caller that
58
+ * wanted to persist which roots are open would be holding UI state for a list
59
+ * it does not own. A root with children opens by default, because the fan-out
60
+ * is the thing worth seeing.
61
+ */
62
+ export declare function RunRow({ run, depth, active, onOpen, trailing, ...rest }: RunRowProps): import("react/jsx-runtime").JSX.Element;
63
+ export interface AgentBoardProps extends Scroll {
64
+ /** Roots. Children ride on each root's `children`. */
65
+ runs: Run[];
66
+ /** The run currently open, so the board can mark it. */
67
+ active?: string;
68
+ onOpen?: (id: string) => void;
69
+ /** What to draw when there is nothing running. */
70
+ empty?: ReactNode;
71
+ /** Heading for runs carrying no project. */
72
+ otherLabel?: string;
73
+ }
74
+ /**
75
+ * The board.
76
+ *
77
+ * Groups are ordered by name with the un-projected group LAST, so the answer to
78
+ * "what is running on my project" is never below a pile of stray runs.
79
+ */
80
+ export declare function AgentBoard({ runs, active, onOpen, empty, otherLabel, ...rest }: AgentBoardProps): import("react/jsx-runtime").JSX.Element;
81
+ /**
82
+ * Build the tree a board draws from the flat list an API answers with.
83
+ *
84
+ * A session names its root rather than its parent, so a fan-out is TWO levels
85
+ * and not N: every non-root hangs off its root. A row whose root is not in the
86
+ * list is treated as a root itself — otherwise a filtered page silently drops
87
+ * work, which is the one thing a board must not do.
88
+ */
89
+ export declare function nest<T extends {
90
+ id: string;
91
+ rootId?: string;
92
+ }>(rows: T[]): (T & {
93
+ children: (T & {
94
+ children: never[];
95
+ })[];
96
+ })[];
97
+ export {};
98
+ //# sourceMappingURL=Board.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Board.d.ts","sourceRoot":"","sources":["../../src/agents/Board.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,UAAU,EAAuB,MAAM,EAAE,MAAM,YAAY,CAAA;AAEpE,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAI9E,KAAK,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,UAAU,CAAC,CAAA;AAC1D,KAAK,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,UAAU,CAAC,EAAE,UAAU,CAAC,CAAA;AAEjE,qFAAqF;AACrF,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;AAE3E;;;;;;GAMG;AACH,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,SAAS,CAAA;IACjB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAA;CACjB;AAkCD,MAAM,WAAW,WAAY,SAAQ,GAAG;IACtC,GAAG,EAAE,GAAG,CAAA;IACR,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,EAAE,GAAG,EAAE,KAAS,EAAE,MAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,WAAW,2CAiFhG;AAED,MAAM,WAAW,eAAgB,SAAQ,MAAM;IAC7C,sDAAsD;IACtD,IAAI,EAAE,GAAG,EAAE,CAAA;IACX,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,kDAAkD;IAClD,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,MAAM,EACN,MAAM,EACN,KAAK,EACL,UAAyB,EACzB,GAAG,IAAI,EACR,EAAE,eAAe,2CA+BjB;AAgBD;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5D,IAAI,EAAE,CAAC,EAAE,GACR,CAAC,CAAC,GAAG;IAAE,QAAQ,EAAE,CAAC,CAAC,GAAG;QAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;KAAE,CAAC,EAAE,CAAA;CAAE,CAAC,EAAE,CAgBrD"}
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * AgentBoard — the agents working view: who is running, on what, and where.
5
+ *
6
+ * A run is a SESSION, and a session that spawned others is the root of a tree.
7
+ * A flat list draws a fan-out as N unrelated rows and loses the one fact worth
8
+ * seeing — that they are one piece of work — so the board nests children under
9
+ * their root and a collapsed root says how many it is hiding.
10
+ *
11
+ * Grouped by PROJECT, because that is the question a person opens this with:
12
+ * not "what is running" but "what is running on the thing I am working on".
13
+ * A session with no project groups under `ungrouped`, named rather than hidden.
14
+ *
15
+ * Presentational. It takes rows and raises `onOpen`; it does no fetching, no
16
+ * grouping policy beyond the field it is given, and no status derivation. The
17
+ * surface holds the data — `@hanzo/ai`'s `sessions.list` / `sessions.tree` and
18
+ * the stream — so the same board serves hanzo.ai, a channel workspace, or
19
+ * anything else that can answer with rows.
20
+ */
21
+ import { ScrollView, SizableText, XStack, YStack } from '@hanzo/gui';
22
+ import { ChevronDown, ChevronRight } from '@hanzogui/lucide-icons-2';
23
+ import { useMemo, useState } from 'react';
24
+ import { slot } from '../backends/gui/slot.js';
25
+ const TONE = {
26
+ running: '$green10',
27
+ paused: '$yellow10',
28
+ done: '$color10',
29
+ error: '$red10',
30
+ unknown: '$color8',
31
+ };
32
+ /** The label a status wears. Separate from the colour so neither is guessed. */
33
+ const SAYS = {
34
+ running: 'running',
35
+ paused: 'paused',
36
+ done: 'done',
37
+ error: 'error',
38
+ unknown: '—',
39
+ };
40
+ function Dot({ status }) {
41
+ return (_jsx(YStack, { width: 7, height: 7, rounded: 9999, bg: TONE[status], shrink: 0, "aria-label": SAYS[status] }));
42
+ }
43
+ /**
44
+ * One row, and its children under it.
45
+ *
46
+ * Expansion is per-row local state: a board is read top-down and a caller that
47
+ * wanted to persist which roots are open would be holding UI state for a list
48
+ * it does not own. A root with children opens by default, because the fan-out
49
+ * is the thing worth seeing.
50
+ */
51
+ export function RunRow({ run, depth = 0, active = false, onOpen, trailing, ...rest }) {
52
+ const kids = run.children ?? [];
53
+ const [open, setOpen] = useState(true);
54
+ return (_jsxs(YStack, { ...slot('run-row'), ...rest, children: [_jsxs(XStack, { items: "center", gap: "$2", px: "$2", py: "$1.5", rounded: "$3", pl: 8 + depth * 16, cursor: "pointer", role: "button", tabIndex: 0, "aria-current": active ? 'true' : undefined, bg: active ? '$edge' : undefined, hoverStyle: { bg: active ? '$edge' : '$hover' }, pressStyle: { bg: '$raised' }, onPress: () => onOpen?.(run.id), children: [kids.length ? (_jsx(XStack, { width: 16, height: 16, items: "center", justify: "center", shrink: 0, role: "button", tabIndex: 0, "aria-expanded": open, "aria-label": open ? 'Hide sub-agents' : 'Show sub-agents', onPress: (e) => {
55
+ e.stopPropagation?.();
56
+ setOpen((v) => !v);
57
+ }, children: open ? _jsx(ChevronDown, { size: 13, opacity: 0.6 }) : _jsx(ChevronRight, { size: 13, opacity: 0.6 }) })) : (_jsx(YStack, { width: 16, shrink: 0 })), _jsx(Dot, { status: run.status }), _jsx(SizableText, { size: "$2", numberOfLines: 1, shrink: 1, color: active ? '$color' : '$quiet', children: run.agent }), run.doing ? (_jsx(SizableText, { size: "$1", numberOfLines: 1, flex: 1, color: "$soft", children: run.doing })) : (_jsx(XStack, { flex: 1 })), kids.length && !open ? (_jsxs(SizableText, { size: "$1", color: "$soft", children: [kids.length, " sub-agent", kids.length === 1 ? '' : 's'] })) : null, run.target ? (_jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: run.target })) : null, trailing] }), open && kids.length
58
+ ? kids.map((kid) => (_jsx(RunRow, { run: kid, depth: depth + 1, onOpen: onOpen }, kid.id)))
59
+ : null] }));
60
+ }
61
+ /**
62
+ * The board.
63
+ *
64
+ * Groups are ordered by name with the un-projected group LAST, so the answer to
65
+ * "what is running on my project" is never below a pile of stray runs.
66
+ */
67
+ export function AgentBoard({ runs, active, onOpen, empty, otherLabel = 'Other work', ...rest }) {
68
+ const groups = useMemo(() => group(runs, otherLabel), [runs, otherLabel]);
69
+ if (!runs.length) {
70
+ return (_jsx(YStack, { ...slot('agent-board'), p: "$4", ...rest, children: empty ?? (_jsx(SizableText, { size: "$2", color: "$soft", children: "Nothing running." })) }));
71
+ }
72
+ return (_jsx(ScrollView, { ...slot('agent-board'), flex: 1, showsVerticalScrollIndicator: false, ...rest, children: _jsx(YStack, { gap: "$1", p: "$2", children: groups.map(([project, rows]) => (_jsxs(YStack, { mt: "$2", gap: "$0.5", children: [_jsx(SizableText, { size: "$1", px: "$2", py: "$1", color: "$soft", fontWeight: "500", children: project }), rows.map((run) => (_jsx(RunRow, { run: run, active: run.id === active, onOpen: onOpen }, run.id)))] }, project))) }) }));
73
+ }
74
+ /** Group roots by project, un-projected last. */
75
+ function group(runs, otherLabel) {
76
+ const by = new Map();
77
+ for (const run of runs) {
78
+ const key = run.project?.trim() || otherLabel;
79
+ const rows = by.get(key);
80
+ if (rows)
81
+ rows.push(run);
82
+ else
83
+ by.set(key, [run]);
84
+ }
85
+ return [...by.entries()].sort(([a], [b]) => a === otherLabel ? 1 : b === otherLabel ? -1 : a.localeCompare(b));
86
+ }
87
+ /**
88
+ * Build the tree a board draws from the flat list an API answers with.
89
+ *
90
+ * A session names its root rather than its parent, so a fan-out is TWO levels
91
+ * and not N: every non-root hangs off its root. A row whose root is not in the
92
+ * list is treated as a root itself — otherwise a filtered page silently drops
93
+ * work, which is the one thing a board must not do.
94
+ */
95
+ export function nest(rows) {
96
+ const roots = new Map();
97
+ const orphans = [];
98
+ for (const row of rows) {
99
+ if (!row.rootId || row.rootId === row.id)
100
+ roots.set(row.id, { ...row, children: [] });
101
+ }
102
+ for (const row of rows) {
103
+ if (!row.rootId || row.rootId === row.id)
104
+ continue;
105
+ const root = roots.get(row.rootId);
106
+ if (root)
107
+ root.children.push({ ...row, children: [] });
108
+ else
109
+ orphans.push(row);
110
+ }
111
+ for (const row of orphans)
112
+ roots.set(row.id, { ...row, children: [] });
113
+ return [...roots.values()];
114
+ }
115
+ //# sourceMappingURL=Board.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Board.js","sourceRoot":"","sources":["../../src/agents/Board.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ 'use client';"use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Screen = Screen;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ /**
7
+ * Screen — watching a run while it works.
8
+ *
9
+ * Some runs have something to look at: a sandbox serving an interactive page, a
10
+ * headful browser a person can watch over VNC. Most have nothing, and this
11
+ * component's first job is to say which it is, honestly — a viewer that draws a
12
+ * black rectangle for a run with no screen has told the reader the run is
13
+ * broken.
14
+ *
15
+ * THE CREDENTIAL NEVER OUTLIVES THE VIEW. A watchable surface is authorized by
16
+ * a short-lived TICKET the caller mints per view; this component asks for one
17
+ * when it mounts, embeds the page the ticket names, and asks for another if the
18
+ * viewer stays past its life. It does NOT take a URL with a long-lived token in
19
+ * it, and there is no prop to pass one — an embed URL ends up in browser
20
+ * history, in a screenshot, and in whatever the parent frame can read.
21
+ *
22
+ * The mint is a PROP, not an import, because who may watch is the surface's
23
+ * question and the answer lives in `@hanzo/ai` on one deployment and behind a
24
+ * BFF on another. This file owns the frame, the states and the refresh.
25
+ */
26
+ const gui_1 = require("@hanzo/gui");
27
+ const react_1 = require("react");
28
+ const slot_1 = require("../backends/gui/slot.cjs");
29
+ const NAMES = {
30
+ sandbox: 'sandbox',
31
+ browser: 'browser',
32
+ };
33
+ /** Re-mint at 80% of a ticket's life — early enough that the frame never blinks. */
34
+ const EARLY = 0.8;
35
+ function Screen({ sessionId, kind, mint, empty, title, ...rest }) {
36
+ const [state, setState] = (0, react_1.useState)({ phase: 'idle' });
37
+ const timer = (0, react_1.useRef)(null);
38
+ const live = (0, react_1.useRef)(true);
39
+ const open = (0, react_1.useCallback)(async () => {
40
+ if (!mint || !kind)
41
+ return;
42
+ setState({ phase: 'minting' });
43
+ try {
44
+ const ticket = await mint(sessionId);
45
+ if (!live.current)
46
+ return;
47
+ setState({ phase: 'watching', url: ticket.url });
48
+ if (ticket.expiresIn && ticket.expiresIn > 0) {
49
+ if (timer.current)
50
+ clearTimeout(timer.current);
51
+ timer.current = setTimeout(() => void open(), ticket.expiresIn * 1000 * EARLY);
52
+ }
53
+ }
54
+ catch (err) {
55
+ if (!live.current)
56
+ return;
57
+ setState({
58
+ phase: 'failed',
59
+ message: err instanceof Error ? err.message : 'Could not open this screen.',
60
+ });
61
+ }
62
+ }, [mint, kind, sessionId]);
63
+ (0, react_1.useEffect)(() => {
64
+ live.current = true;
65
+ void open();
66
+ return () => {
67
+ live.current = false;
68
+ if (timer.current)
69
+ clearTimeout(timer.current);
70
+ };
71
+ }, [open]);
72
+ if (!kind) {
73
+ return ((0, jsx_runtime_1.jsx)(Frame, { title: title, ...rest, children: empty ?? ((0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", color: "$soft", children: "This run has no screen." })) }));
74
+ }
75
+ if (!mint) {
76
+ return ((0, jsx_runtime_1.jsx)(Frame, { title: title, ...rest, children: (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", color: "$soft", children: "Watching is not wired up on this surface." }) }));
77
+ }
78
+ if (state.phase === 'failed') {
79
+ return ((0, jsx_runtime_1.jsx)(Frame, { title: title, ...rest, children: (0, jsx_runtime_1.jsxs)(gui_1.YStack, { gap: "$2", items: "center", children: [(0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", color: "$soft", children: state.message }), (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$2", color: "$color", cursor: "pointer", role: "button", tabIndex: 0, onPress: () => void open(), children: "Try again" })] }) }));
80
+ }
81
+ if (state.phase !== 'watching') {
82
+ return ((0, jsx_runtime_1.jsx)(Frame, { title: title, ...rest, children: (0, jsx_runtime_1.jsxs)(gui_1.SizableText, { size: "$2", color: "$soft", children: ["Opening the ", NAMES[kind], "\u2026"] }) }));
83
+ }
84
+ return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('screen'), flex: 1, minH: 0, bg: "$panel", rounded: "$4", overflow: "hidden", ...rest, children: [title ? (0, jsx_runtime_1.jsx)(Caption, { children: title }) : null, (0, jsx_runtime_1.jsx)("iframe", { src: state.url, title: title ?? `Live ${NAMES[kind]}`, sandbox: "allow-scripts allow-same-origin allow-forms", referrerPolicy: "no-referrer", allow: "clipboard-read; clipboard-write", style: { border: 0, width: '100%', height: '100%', flex: 1, display: 'block' } })] }));
85
+ }
86
+ function Caption({ children }) {
87
+ return ((0, jsx_runtime_1.jsx)(gui_1.XStack, { px: "$3", py: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: children }) }));
88
+ }
89
+ function Frame({ title, children, ...rest }) {
90
+ return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('screen'), flex: 1, minH: 0, bg: "$panel", rounded: "$4", overflow: "hidden", ...rest, children: [title ? (0, jsx_runtime_1.jsx)(Caption, { children: title }) : null, (0, jsx_runtime_1.jsx)(gui_1.YStack, { flex: 1, items: "center", justify: "center", p: "$4", children: children })] }));
91
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Screen — watching a run while it works.
3
+ *
4
+ * Some runs have something to look at: a sandbox serving an interactive page, a
5
+ * headful browser a person can watch over VNC. Most have nothing, and this
6
+ * component's first job is to say which it is, honestly — a viewer that draws a
7
+ * black rectangle for a run with no screen has told the reader the run is
8
+ * broken.
9
+ *
10
+ * THE CREDENTIAL NEVER OUTLIVES THE VIEW. A watchable surface is authorized by
11
+ * a short-lived TICKET the caller mints per view; this component asks for one
12
+ * when it mounts, embeds the page the ticket names, and asks for another if the
13
+ * viewer stays past its life. It does NOT take a URL with a long-lived token in
14
+ * it, and there is no prop to pass one — an embed URL ends up in browser
15
+ * history, in a screenshot, and in whatever the parent frame can read.
16
+ *
17
+ * The mint is a PROP, not an import, because who may watch is the surface's
18
+ * question and the answer lives in `@hanzo/ai` on one deployment and behind a
19
+ * BFF on another. This file owns the frame, the states and the refresh.
20
+ */
21
+ import { YStack } from '@hanzo/gui';
22
+ import { type ComponentProps, type ReactNode } from 'react';
23
+ type Col = Omit<ComponentProps<typeof YStack>, 'children'>;
24
+ /** What a mint answers with: where to look, and for how long. */
25
+ export interface Ticket {
26
+ /** The page to embed. Already carries whatever the grant put in it. */
27
+ url: string;
28
+ /** Seconds until it stops working. Absent, the view is not refreshed. */
29
+ expiresIn?: number;
30
+ }
31
+ /** How a run's screen is served, for the label a viewer reads. */
32
+ export type ScreenKind = 'sandbox' | 'browser';
33
+ export interface ScreenProps extends Col {
34
+ /** The run being watched. Changing it re-mints. */
35
+ sessionId: string;
36
+ /**
37
+ * What this run has to look at. `null` means nothing does — the honest empty
38
+ * state, and the common case. The caller decides; this component never guesses
39
+ * from a session's shape.
40
+ */
41
+ kind?: ScreenKind | null;
42
+ /**
43
+ * Mint a view. Called on mount, on `sessionId` change, and shortly before the
44
+ * previous ticket expires. Throwing renders the failure with a retry.
45
+ */
46
+ mint?: (sessionId: string) => Promise<Ticket>;
47
+ /** Shown when `kind` is null. */
48
+ empty?: ReactNode;
49
+ title?: string;
50
+ }
51
+ export declare function Screen({ sessionId, kind, mint, empty, title, ...rest }: ScreenProps): import("react/jsx-runtime").JSX.Element;
52
+ export {};
53
+ //# sourceMappingURL=Screen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Screen.d.ts","sourceRoot":"","sources":["../../src/agents/Screen.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAuB,MAAM,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,EAA4C,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAIrG,KAAK,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,UAAU,CAAC,CAAA;AAE1D,iEAAiE;AACjE,MAAM,WAAW,MAAM;IACrB,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAA;IACX,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,kEAAkE;AAClE,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,CAAA;AAO9C,MAAM,WAAW,WAAY,SAAQ,GAAG;IACtC,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC7C,iCAAiC;IACjC,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAWD,wBAAgB,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE,WAAW,2CA2GnF"}
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Screen — watching a run while it works.
5
+ *
6
+ * Some runs have something to look at: a sandbox serving an interactive page, a
7
+ * headful browser a person can watch over VNC. Most have nothing, and this
8
+ * component's first job is to say which it is, honestly — a viewer that draws a
9
+ * black rectangle for a run with no screen has told the reader the run is
10
+ * broken.
11
+ *
12
+ * THE CREDENTIAL NEVER OUTLIVES THE VIEW. A watchable surface is authorized by
13
+ * a short-lived TICKET the caller mints per view; this component asks for one
14
+ * when it mounts, embeds the page the ticket names, and asks for another if the
15
+ * viewer stays past its life. It does NOT take a URL with a long-lived token in
16
+ * it, and there is no prop to pass one — an embed URL ends up in browser
17
+ * history, in a screenshot, and in whatever the parent frame can read.
18
+ *
19
+ * The mint is a PROP, not an import, because who may watch is the surface's
20
+ * question and the answer lives in `@hanzo/ai` on one deployment and behind a
21
+ * BFF on another. This file owns the frame, the states and the refresh.
22
+ */
23
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
24
+ import { useCallback, useEffect, useRef, useState } from 'react';
25
+ import { slot } from '../backends/gui/slot.js';
26
+ const NAMES = {
27
+ sandbox: 'sandbox',
28
+ browser: 'browser',
29
+ };
30
+ /** Re-mint at 80% of a ticket's life — early enough that the frame never blinks. */
31
+ const EARLY = 0.8;
32
+ export function Screen({ sessionId, kind, mint, empty, title, ...rest }) {
33
+ const [state, setState] = useState({ phase: 'idle' });
34
+ const timer = useRef(null);
35
+ const live = useRef(true);
36
+ const open = useCallback(async () => {
37
+ if (!mint || !kind)
38
+ return;
39
+ setState({ phase: 'minting' });
40
+ try {
41
+ const ticket = await mint(sessionId);
42
+ if (!live.current)
43
+ return;
44
+ setState({ phase: 'watching', url: ticket.url });
45
+ if (ticket.expiresIn && ticket.expiresIn > 0) {
46
+ if (timer.current)
47
+ clearTimeout(timer.current);
48
+ timer.current = setTimeout(() => void open(), ticket.expiresIn * 1000 * EARLY);
49
+ }
50
+ }
51
+ catch (err) {
52
+ if (!live.current)
53
+ return;
54
+ setState({
55
+ phase: 'failed',
56
+ message: err instanceof Error ? err.message : 'Could not open this screen.',
57
+ });
58
+ }
59
+ }, [mint, kind, sessionId]);
60
+ useEffect(() => {
61
+ live.current = true;
62
+ void open();
63
+ return () => {
64
+ live.current = false;
65
+ if (timer.current)
66
+ clearTimeout(timer.current);
67
+ };
68
+ }, [open]);
69
+ if (!kind) {
70
+ return (_jsx(Frame, { title: title, ...rest, children: empty ?? (_jsx(SizableText, { size: "$2", color: "$soft", children: "This run has no screen." })) }));
71
+ }
72
+ if (!mint) {
73
+ return (_jsx(Frame, { title: title, ...rest, children: _jsx(SizableText, { size: "$2", color: "$soft", children: "Watching is not wired up on this surface." }) }));
74
+ }
75
+ if (state.phase === 'failed') {
76
+ return (_jsx(Frame, { title: title, ...rest, children: _jsxs(YStack, { gap: "$2", items: "center", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: state.message }), _jsx(SizableText, { size: "$2", color: "$color", cursor: "pointer", role: "button", tabIndex: 0, onPress: () => void open(), children: "Try again" })] }) }));
77
+ }
78
+ if (state.phase !== 'watching') {
79
+ return (_jsx(Frame, { title: title, ...rest, children: _jsxs(SizableText, { size: "$2", color: "$soft", children: ["Opening the ", NAMES[kind], "\u2026"] }) }));
80
+ }
81
+ return (_jsxs(YStack, { ...slot('screen'), flex: 1, minH: 0, bg: "$panel", rounded: "$4", overflow: "hidden", ...rest, children: [title ? _jsx(Caption, { children: title }) : null, _jsx("iframe", { src: state.url, title: title ?? `Live ${NAMES[kind]}`, sandbox: "allow-scripts allow-same-origin allow-forms", referrerPolicy: "no-referrer", allow: "clipboard-read; clipboard-write", style: { border: 0, width: '100%', height: '100%', flex: 1, display: 'block' } })] }));
82
+ }
83
+ function Caption({ children }) {
84
+ return (_jsx(XStack, { px: "$3", py: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: children }) }));
85
+ }
86
+ function Frame({ title, children, ...rest }) {
87
+ return (_jsxs(YStack, { ...slot('screen'), flex: 1, minH: 0, bg: "$panel", rounded: "$4", overflow: "hidden", ...rest, children: [title ? _jsx(Caption, { children: title }) : null, _jsx(YStack, { flex: 1, items: "center", justify: "center", p: "$4", children: children })] }));
88
+ }
89
+ //# sourceMappingURL=Screen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Screen.js","sourceRoot":"","sources":["../../src/agents/Screen.tsx"],"names":[],"mappings":"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"}