@hanzo/rooms 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 (235) hide show
  1. package/LICENSE-APACHE +205 -0
  2. package/LICENSE-MIT +21 -0
  3. package/README.md +23 -0
  4. package/dist/Account.d.ts +8 -0
  5. package/dist/Account.js +58 -0
  6. package/dist/Beluga.d.ts +12 -0
  7. package/dist/Beluga.js +152 -0
  8. package/dist/Billing.d.ts +1 -0
  9. package/dist/Billing.js +31 -0
  10. package/dist/Board.d.ts +9 -0
  11. package/dist/Board.js +447 -0
  12. package/dist/Boxes.d.ts +1 -0
  13. package/dist/Boxes.js +173 -0
  14. package/dist/CalScheduler.d.ts +15 -0
  15. package/dist/CalScheduler.js +196 -0
  16. package/dist/Chat.d.ts +35 -0
  17. package/dist/Chat.js +1049 -0
  18. package/dist/Column.d.ts +15 -0
  19. package/dist/Column.js +88 -0
  20. package/dist/Contacts.d.ts +1 -0
  21. package/dist/Contacts.js +301 -0
  22. package/dist/Directory.d.ts +24 -0
  23. package/dist/Directory.js +356 -0
  24. package/dist/Drive.d.ts +2 -0
  25. package/dist/Drive.js +466 -0
  26. package/dist/Find.d.ts +7 -0
  27. package/dist/Find.js +82 -0
  28. package/dist/Guide.d.ts +6 -0
  29. package/dist/Guide.js +272 -0
  30. package/dist/Home.d.ts +39 -0
  31. package/dist/Home.js +306 -0
  32. package/dist/Inbox.d.ts +9 -0
  33. package/dist/Inbox.js +193 -0
  34. package/dist/Market.d.ts +3 -0
  35. package/dist/Market.js +149 -0
  36. package/dist/Me.d.ts +27 -0
  37. package/dist/Me.js +251 -0
  38. package/dist/Meet.d.ts +9 -0
  39. package/dist/Meet.js +370 -0
  40. package/dist/ModeMark.d.ts +8 -0
  41. package/dist/ModeMark.js +18 -0
  42. package/dist/OrgSwitcher.d.ts +31 -0
  43. package/dist/OrgSwitcher.js +120 -0
  44. package/dist/Orgs.d.ts +10 -0
  45. package/dist/Orgs.js +582 -0
  46. package/dist/Profile.d.ts +4 -0
  47. package/dist/Profile.js +67 -0
  48. package/dist/Prose.d.ts +5 -0
  49. package/dist/Prose.js +111 -0
  50. package/dist/ProviderMark.d.ts +17 -0
  51. package/dist/ProviderMark.js +277 -0
  52. package/dist/Recents.d.ts +10 -0
  53. package/dist/Recents.js +91 -0
  54. package/dist/RightPane.d.ts +13 -0
  55. package/dist/RightPane.js +426 -0
  56. package/dist/Room.d.ts +8 -0
  57. package/dist/Room.js +50 -0
  58. package/dist/Scene.d.ts +12 -0
  59. package/dist/Scene.js +105 -0
  60. package/dist/Settings.d.ts +46 -0
  61. package/dist/Settings.js +321 -0
  62. package/dist/Setup.d.ts +1 -0
  63. package/dist/Setup.js +36 -0
  64. package/dist/Share.d.ts +3 -0
  65. package/dist/Share.js +130 -0
  66. package/dist/Shared.d.ts +1 -0
  67. package/dist/Shared.js +113 -0
  68. package/dist/Shell.d.ts +63 -0
  69. package/dist/Shell.js +1225 -0
  70. package/dist/Start.d.ts +1 -0
  71. package/dist/Start.js +96 -0
  72. package/dist/Support.d.ts +14 -0
  73. package/dist/Support.js +122 -0
  74. package/dist/Talk.d.ts +4 -0
  75. package/dist/Talk.js +107 -0
  76. package/dist/Upgrade.d.ts +11 -0
  77. package/dist/Upgrade.js +75 -0
  78. package/dist/Usage.d.ts +1 -0
  79. package/dist/Usage.js +21 -0
  80. package/dist/Vibe.d.ts +2 -0
  81. package/dist/Vibe.js +188 -0
  82. package/dist/Work.d.ts +12 -0
  83. package/dist/Work.js +256 -0
  84. package/dist/bots/Bots.d.ts +2 -0
  85. package/dist/bots/Bots.js +456 -0
  86. package/dist/bots/Lights.d.ts +8 -0
  87. package/dist/bots/Lights.js +13 -0
  88. package/dist/bots/api.d.ts +120 -0
  89. package/dist/bots/api.js +75 -0
  90. package/dist/bots/face.d.ts +5 -0
  91. package/dist/bots/face.js +13 -0
  92. package/dist/bots/ink.d.ts +7 -0
  93. package/dist/bots/ink.js +29 -0
  94. package/dist/bots/kit.d.ts +113 -0
  95. package/dist/bots/kit.js +43 -0
  96. package/dist/bots/runtime.d.ts +4 -0
  97. package/dist/bots/runtime.js +326 -0
  98. package/dist/card.d.ts +52 -0
  99. package/dist/card.js +52 -0
  100. package/dist/cast.d.ts +143 -0
  101. package/dist/cast.js +251 -0
  102. package/dist/connect.d.ts +29 -0
  103. package/dist/connect.js +64 -0
  104. package/dist/contacts-store.d.ts +111 -0
  105. package/dist/contacts-store.js +276 -0
  106. package/dist/conversations.d.ts +54 -0
  107. package/dist/conversations.js +98 -0
  108. package/dist/copy.d.ts +23 -0
  109. package/dist/copy.js +39 -0
  110. package/dist/crew.d.ts +36 -0
  111. package/dist/crew.js +304 -0
  112. package/dist/dev.d.ts +3 -0
  113. package/dist/dev.js +102 -0
  114. package/dist/enso.d.ts +70 -0
  115. package/dist/enso.js +112 -0
  116. package/dist/failure.d.ts +57 -0
  117. package/dist/failure.js +143 -0
  118. package/dist/ground.d.ts +32 -0
  119. package/dist/ground.js +32 -0
  120. package/dist/host.d.ts +76 -0
  121. package/dist/host.js +76 -0
  122. package/dist/index.d.ts +14 -0
  123. package/dist/index.js +12 -0
  124. package/dist/kept.d.ts +5 -0
  125. package/dist/kept.js +27 -0
  126. package/dist/lib/account.d.ts +49 -0
  127. package/dist/lib/account.js +229 -0
  128. package/dist/lib/ai.d.ts +46 -0
  129. package/dist/lib/ai.js +140 -0
  130. package/dist/lib/api.d.ts +43 -0
  131. package/dist/lib/api.js +62 -0
  132. package/dist/lib/bare.d.ts +9 -0
  133. package/dist/lib/bare.js +7 -0
  134. package/dist/lib/coding.d.ts +105 -0
  135. package/dist/lib/coding.js +87 -0
  136. package/dist/lib/destination.d.ts +102 -0
  137. package/dist/lib/destination.js +260 -0
  138. package/dist/lib/durable.d.ts +66 -0
  139. package/dist/lib/durable.js +107 -0
  140. package/dist/lib/first.d.ts +11 -0
  141. package/dist/lib/first.js +23 -0
  142. package/dist/lib/home.d.ts +61 -0
  143. package/dist/lib/home.js +54 -0
  144. package/dist/lib/host.d.ts +104 -0
  145. package/dist/lib/host.js +132 -0
  146. package/dist/lib/hostname.d.ts +1 -0
  147. package/dist/lib/hostname.js +14 -0
  148. package/dist/lib/hydrated.d.ts +1 -0
  149. package/dist/lib/hydrated.js +20 -0
  150. package/dist/lib/integrations.d.ts +21 -0
  151. package/dist/lib/integrations.js +1 -0
  152. package/dist/lib/intent.d.ts +6 -0
  153. package/dist/lib/intent.js +71 -0
  154. package/dist/lib/invite.d.ts +155 -0
  155. package/dist/lib/invite.js +245 -0
  156. package/dist/lib/limits.d.ts +55 -0
  157. package/dist/lib/limits.js +119 -0
  158. package/dist/lib/list.d.ts +9 -0
  159. package/dist/lib/list.js +11 -0
  160. package/dist/lib/mark.d.ts +13 -0
  161. package/dist/lib/mark.js +85 -0
  162. package/dist/lib/mix.d.ts +22 -0
  163. package/dist/lib/mix.js +22 -0
  164. package/dist/lib/models.d.ts +27 -0
  165. package/dist/lib/models.js +106 -0
  166. package/dist/lib/org.d.ts +109 -0
  167. package/dist/lib/org.js +140 -0
  168. package/dist/lib/path.d.ts +9 -0
  169. package/dist/lib/path.js +29 -0
  170. package/dist/lib/plans.d.ts +193 -0
  171. package/dist/lib/plans.js +310 -0
  172. package/dist/lib/prose.d.ts +13 -0
  173. package/dist/lib/prose.js +62 -0
  174. package/dist/lib/reach.d.ts +17 -0
  175. package/dist/lib/reach.js +70 -0
  176. package/dist/lib/rooms.json +23 -0
  177. package/dist/lib/served.d.ts +20 -0
  178. package/dist/lib/served.js +92 -0
  179. package/dist/lib/session.d.ts +211 -0
  180. package/dist/lib/session.js +493 -0
  181. package/dist/lib/share.d.ts +131 -0
  182. package/dist/lib/share.js +216 -0
  183. package/dist/lib/signout.d.ts +14 -0
  184. package/dist/lib/signout.js +40 -0
  185. package/dist/lib/tags.d.ts +27 -0
  186. package/dist/lib/tags.js +46 -0
  187. package/dist/lib/tap.d.ts +17 -0
  188. package/dist/lib/tap.js +9 -0
  189. package/dist/lib/tier.d.ts +115 -0
  190. package/dist/lib/tier.js +207 -0
  191. package/dist/lib/tiers.d.ts +9 -0
  192. package/dist/lib/tiers.js +141 -0
  193. package/dist/lib/todo.d.ts +125 -0
  194. package/dist/lib/todo.js +379 -0
  195. package/dist/look.d.ts +1 -0
  196. package/dist/look.js +21 -0
  197. package/dist/meters.d.ts +8 -0
  198. package/dist/meters.js +31 -0
  199. package/dist/model.d.ts +4 -0
  200. package/dist/model.js +44 -0
  201. package/dist/network.d.ts +64 -0
  202. package/dist/network.js +216 -0
  203. package/dist/open.d.ts +81 -0
  204. package/dist/open.js +149 -0
  205. package/dist/pane.d.ts +92 -0
  206. package/dist/pane.js +231 -0
  207. package/dist/progress.d.ts +46 -0
  208. package/dist/progress.js +51 -0
  209. package/dist/recent.d.ts +67 -0
  210. package/dist/recent.js +161 -0
  211. package/dist/rooms.d.ts +131 -0
  212. package/dist/rooms.js +195 -0
  213. package/dist/roster.d.ts +28 -0
  214. package/dist/roster.js +70 -0
  215. package/dist/span.d.ts +14 -0
  216. package/dist/span.js +50 -0
  217. package/dist/speech.d.ts +44 -0
  218. package/dist/speech.js +229 -0
  219. package/dist/stars.d.ts +16 -0
  220. package/dist/stars.js +75 -0
  221. package/dist/tabs.d.ts +26 -0
  222. package/dist/tabs.js +25 -0
  223. package/dist/team.d.ts +11 -0
  224. package/dist/team.gen.d.ts +10 -0
  225. package/dist/team.gen.js +11 -0
  226. package/dist/team.js +30 -0
  227. package/dist/tools.d.ts +35 -0
  228. package/dist/tools.js +143 -0
  229. package/dist/tours.d.ts +24 -0
  230. package/dist/tours.js +311 -0
  231. package/dist/waiting.d.ts +15 -0
  232. package/dist/waiting.js +17 -0
  233. package/dist/where.d.ts +54 -0
  234. package/dist/where.js +14 -0
  235. package/package.json +108 -0
@@ -0,0 +1,75 @@
1
+ 'use client';
2
+ const SESSION = '/v1/agents/sessions';
3
+ // ── What a Bot owns ─────────────────────────────────────────────────────────
4
+ //
5
+ // A Bot is not a row in a list of prompts; it is a worker with things of its
6
+ // own. Most are fields on the record — the tools it may call, the machine it
7
+ // runs on, the clock it wakes to, the identity it acts as — and two are named
8
+ // spaces derived from its name:
9
+ //
10
+ // its DISK, because a sandbox volume is named per (org, project) and the name
11
+ // is deterministic, so leasing `bot-<name>` twice finds the same disk and the
12
+ // Bot's runtime state survives a lease that ended;
13
+ //
14
+ // its SECRETS, because `/v1/kms/secrets` filters by `path`, so a Bot's
15
+ // credentials sit under a prefix nothing else reads.
16
+ /** The sandbox project that is this Bot's own disk. */
17
+ export const disk = (name) => `bot-${name}`;
18
+ /** The KMS path prefix that is this Bot's own secrets. */
19
+ export const vault = (name) => `bots/${name}`;
20
+ /** Long-running is the platform's word for a Bot that stays up. The set is
21
+ * closed, and long-running is REFUSED without a schedule — measured, 400
22
+ * "a long-running agent requires a 'schedule' (5-field cron)". */
23
+ export const ALWAYS = 'long-running';
24
+ export const ONCE = 'one-shot';
25
+ // ── Its work ────────────────────────────────────────────────────────────────
26
+ /**
27
+ * The sessions opened under this Bot's name.
28
+ *
29
+ * NARROWED HERE, BECAUSE THE ROUTE WILL NOT. Its query is root, parent,
30
+ * status, project, room and limit — there is no per-agent filter — and an
31
+ * `agent=` it does not know is SILENTLY IGNORED rather than refused. Measured:
32
+ * `?agent=nope` and no query at all answer the same five rows. A caller that
33
+ * trusts the parameter draws the whole org's work as one bot's, which is how
34
+ * this was wrong once already.
35
+ */
36
+ export const sessions = (c, agent, limit = 200, signal) => c.http
37
+ .collection('sessions', { path: SESSION, query: { limit }, signal })
38
+ .then((rows) => rows.filter((s) => s.agent === agent));
39
+ /** Opens a session under this Bot's name and answers the row. */
40
+ export const launch = (c, agent, title) => c.http.json({ method: 'POST', path: SESSION, body: { agent, title } });
41
+ /** One session with its children and its most recent turns. */
42
+ export const transcript = (c, id, signal) => c.http.json({ path: `${SESSION}/${encodeURIComponent(id)}`, signal });
43
+ /** Records one turn in a session's log and answers it. */
44
+ export const record = (c, id, kind, text) => c.http.json({ method: 'POST', path: `${SESSION}/${encodeURIComponent(id)}/events`, body: { kind, text } });
45
+ /** The four things you can say to a running session. Each one is recorded as a
46
+ * control turn whether or not a runner is attached to hear it — the reply
47
+ * carries `forwarded`, which says which of the two happened. */
48
+ export const steer = (c, id, command, message) => c.http.json({
49
+ method: 'POST',
50
+ path: `${SESSION}/${encodeURIComponent(id)}/${command}`,
51
+ body: message === undefined ? {} : { message },
52
+ });
53
+ export const answering = (c, channel, signal) => c.http.json({ path: '/v1/channels/agent', query: { channel }, signal });
54
+ /** Makes this Bot the one that answers a channel. */
55
+ export const answer = (c, channel, agent) => c.http.json({ method: 'PUT', path: '/v1/channels/agent', body: { channel, default: agent } });
56
+ const rpc = (c, method, params) => c.http
57
+ .json({
58
+ method: 'POST',
59
+ path: '/v1/mcp',
60
+ body: { jsonrpc: '2.0', id: 1, method, params },
61
+ })
62
+ .then((r) => {
63
+ if (r.error)
64
+ throw new Error(r.error.message || 'the tool refused');
65
+ return r.result;
66
+ });
67
+ export const tools = (c) => rpc(c, 'tools/list').then((r) => r.tools ?? []);
68
+ /** Runs one operation of one tool and answers the text it produced. */
69
+ export const call = (c, name, op, input = {}) => rpc(c, 'tools/call', { name, arguments: { op, input } }).then((r) => (r.content ?? []).map((p) => p.text ?? '').join('\n'));
70
+ /** Starts one autonomous coding run against a repo in the caller's org. */
71
+ export const workOn = (c, params) => c.http.json({ method: 'POST', path: '/v1/agents/coding', body: params });
72
+ export const projects = (c, signal) => c.http.json({ path: '/v1/projects', signal });
73
+ export const secrets = (c, path, signal) => c.http.collection('secrets', { path: '/v1/kms/secrets', query: { path }, signal });
74
+ export const connectors = (c, signal) => c.http.collection('connectors', { path: '/v1/integrations/connectors', signal });
75
+ export const grant = (c, id, kind) => c.http.json({ method: 'POST', path: `/v1/sandbox/${encodeURIComponent(id)}/${kind}/ticket` });
@@ -0,0 +1,5 @@
1
+ import type { Agent } from '@hanzo/ai';
2
+ export declare function Face({ bot, size }: {
3
+ bot: Pick<Agent, 'name' | 'avatar' | 'emoji'>;
4
+ size?: number;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Text, YStack } from '@hanzo/gui';
4
+ import { INK, LINE, SURFACE } from './ink.js';
5
+ export function Face({ bot, size = 36 }) {
6
+ const round = { width: size, height: size, shrink: 0, rounded: 9999, borderWidth: 1, borderColor: LINE };
7
+ if (bot.avatar)
8
+ return _jsx(YStack, { render: "img", src: bot.avatar, alt: '', "aria-hidden": true, ...round, objectFit: "cover" });
9
+ // The glyph scales with the disc, so its size is a number of pixels rather
10
+ // than a rung; the leading is the size itself, which centres it.
11
+ const glyph = Math.round(size * (bot.emoji ? 0.5 : 0.4));
12
+ return (_jsx(YStack, { "aria-hidden": true, ...round, items: "center", justify: "center", bg: SURFACE, userSelect: 'none', children: _jsx(Text, { color: INK, fontSize: glyph, lineHeight: glyph, fontWeight: "600", children: bot.emoji || (bot.name || '?').trim().charAt(0).toUpperCase() }) }));
13
+ }
@@ -0,0 +1,7 @@
1
+ export declare const GROUND = "var(--background)";
2
+ export declare const RAISED = "var(--surface-1)";
3
+ export declare const INK = "var(--foreground)";
4
+ export declare const DIM = "var(--muted-foreground)";
5
+ export declare const FAINT = "$faint";
6
+ export declare const LINE = "var(--border)";
7
+ export declare const SURFACE = "var(--surface-card-quiet)";
@@ -0,0 +1,29 @@
1
+ // The ramp this room is drawn in.
2
+ //
3
+ // Greyscale, because nothing on this screen is a category: a Bot is not red and
4
+ // a session is not green. What used to stand here was seven personas each given
5
+ // a colour from a table in the source, which told a reader which of seven
6
+ // fictional workers they were looking at and could not survive the eighth.
7
+ //
8
+ // Status is said in words. A colour that means "done" has to be learned; the
9
+ // word does not, and it survives a reader who cannot see it.
10
+ //
11
+ // ROLES, NOT VALUES. These were seven literals — black ground, white ink, white
12
+ // at four opacities — which reads right in dark by coincidence and cannot
13
+ // retune. On the light theme the room came out an island: the rail and the
14
+ // column beside it followed the page while everything these painted stayed
15
+ // night, which is also what a screenshot of the room then carried onto the
16
+ // landing. Naming the role hands the decision to @hanzo/design, which tunes
17
+ // both grounds already.
18
+ //
19
+ // The loud controls are @hanzo/ui's primary Button, so their fill is design's
20
+ // accent and follows whatever @hanzo/appearance sets; this ramp holds no pair.
21
+ export const GROUND = 'var(--background)';
22
+ export const RAISED = 'var(--surface-1)';
23
+ export const INK = 'var(--foreground)';
24
+ export const DIM = 'var(--muted-foreground)';
25
+ // The dimmest ink is gui's `$faint` rung, a theme token: design's
26
+ // --text-tertiary is 4.32:1 on the light ground, under text's 4.5.
27
+ export const FAINT = '$faint';
28
+ export const LINE = 'var(--border)';
29
+ export const SURFACE = 'var(--surface-card-quiet)';
@@ -0,0 +1,113 @@
1
+ /** A bare icon control: no ground, a 6px pad, the glyph centred. The glyph
2
+ * takes its ink itself — a frame carries no text colour to hand down. */
3
+ export declare const GHOST: {
4
+ readonly render: 'button';
5
+ readonly type: 'button';
6
+ readonly p: 6;
7
+ readonly rounded: 6;
8
+ readonly bg: 'transparent';
9
+ readonly borderWidth: 0;
10
+ readonly items: 'center';
11
+ readonly justify: 'center';
12
+ readonly cursor: 'pointer';
13
+ };
14
+ /** The small outlined control every action here is. A `Text`, so the label's
15
+ * size and ink are the button's and an icon beside it paints in `currentColor`. */
16
+ export declare const TAB: {
17
+ readonly render: 'button';
18
+ readonly type: 'button';
19
+ readonly display: 'inline-flex';
20
+ readonly items: 'center';
21
+ readonly gap: 5;
22
+ readonly px: 10;
23
+ readonly py: 5;
24
+ readonly rounded: 7;
25
+ readonly fontSize: '$1';
26
+ readonly fontWeight: '600';
27
+ readonly borderWidth: 1;
28
+ readonly borderColor: "var(--border)";
29
+ readonly bg: 'transparent';
30
+ readonly color: "var(--muted-foreground)";
31
+ readonly cursor: 'pointer';
32
+ };
33
+ /** A field with its chrome off, inside a row that draws the edge. Its padding
34
+ * is still @hanzo/design's base field gutter. */
35
+ export declare const BARE: {
36
+ readonly unstyled: true;
37
+ readonly color: "var(--foreground)";
38
+ readonly placeholderTextColor: '$placeholderColor';
39
+ };
40
+ /** A field that draws its own edge. */
41
+ export declare const BOXED: {
42
+ readonly unstyled: true;
43
+ readonly width: '100%';
44
+ readonly px: 10;
45
+ readonly py: 7;
46
+ readonly rounded: 8;
47
+ readonly bg: "var(--surface-card-quiet)";
48
+ readonly borderWidth: 1;
49
+ readonly borderColor: "var(--border)";
50
+ readonly color: "var(--foreground)";
51
+ readonly fontSize: '$2';
52
+ readonly placeholderTextColor: '$placeholderColor';
53
+ };
54
+ /** The platform's select, wearing the same edge as `BOXED`. */
55
+ export declare const PICK: {
56
+ readonly width: '100%';
57
+ readonly rounded: 8;
58
+ readonly bg: "var(--surface-card-quiet)";
59
+ readonly borderColor: "var(--border)";
60
+ readonly color: "var(--foreground)";
61
+ readonly fontSize: '$2';
62
+ };
63
+ /** A raised card in the stream. */
64
+ export declare const CARD: {
65
+ readonly bg: "var(--surface-1)";
66
+ readonly borderWidth: 1;
67
+ readonly borderColor: "var(--border)";
68
+ readonly rounded: 14;
69
+ readonly p: 16;
70
+ readonly gap: 8;
71
+ readonly maxW: 620;
72
+ };
73
+ /** A section's small-caps heading. */
74
+ export declare const LABEL: {
75
+ readonly render: 'h3';
76
+ readonly fontSize: '$1';
77
+ readonly fontWeight: '600';
78
+ readonly letterSpacing: 0.6;
79
+ readonly color: "$faint";
80
+ readonly textTransform: 'uppercase';
81
+ };
82
+ /** A sentence the room says when it has nothing else to draw. */
83
+ export declare const NOTE: {
84
+ readonly render: 'p';
85
+ readonly px: 12;
86
+ readonly py: 10;
87
+ readonly fontSize: '$2';
88
+ readonly lineHeight: '1.3rem';
89
+ readonly color: "var(--muted-foreground)";
90
+ };
91
+ /** One line, cut with an ellipsis where it runs out of room. */
92
+ export declare const CLIP: {
93
+ readonly whiteSpace: 'nowrap';
94
+ readonly overflow: 'hidden';
95
+ readonly textOverflow: 'ellipsis';
96
+ };
97
+ /** What a run printed: a bounded block of mono that scrolls. */
98
+ export declare const OUT: {
99
+ readonly render: 'pre';
100
+ readonly display: 'block';
101
+ readonly maxH: 180;
102
+ readonly overflowY: 'auto';
103
+ readonly p: 10;
104
+ readonly rounded: 8;
105
+ readonly bg: "var(--surface-card-quiet)";
106
+ readonly borderWidth: 1;
107
+ readonly borderColor: "var(--border)";
108
+ readonly fontFamily: '$mono';
109
+ readonly fontSize: '$1';
110
+ readonly lineHeight: '1.03125rem';
111
+ readonly whiteSpace: 'pre-wrap';
112
+ readonly color: "var(--foreground)";
113
+ };
@@ -0,0 +1,43 @@
1
+ // The controls and surfaces the Bots room is drawn from, as gui props.
2
+ //
3
+ // The room and its runtime pane spelled the same tab, field and label twice
4
+ // each; they are stated once here and spread where they are drawn. Each is a
5
+ // plain prop object, so a call site adds to one and never needs a style.
6
+ import { DIM, FAINT, INK, LINE, RAISED, SURFACE } from './ink.js';
7
+ /** A bare icon control: no ground, a 6px pad, the glyph centred. The glyph
8
+ * takes its ink itself — a frame carries no text colour to hand down. */
9
+ export const GHOST = {
10
+ render: 'button', type: 'button', p: 6, rounded: 6, bg: 'transparent', borderWidth: 0,
11
+ items: 'center', justify: 'center', cursor: 'pointer',
12
+ };
13
+ /** The small outlined control every action here is. A `Text`, so the label's
14
+ * size and ink are the button's and an icon beside it paints in `currentColor`. */
15
+ export const TAB = {
16
+ render: 'button', type: 'button', display: 'inline-flex', items: 'center', gap: 5, px: 10, py: 5, rounded: 7,
17
+ fontSize: '$1', fontWeight: '600', borderWidth: 1, borderColor: LINE, bg: 'transparent', color: DIM, cursor: 'pointer',
18
+ };
19
+ /** A field with its chrome off, inside a row that draws the edge. Its padding
20
+ * is still @hanzo/design's base field gutter. */
21
+ export const BARE = { unstyled: true, color: INK, placeholderTextColor: '$placeholderColor' };
22
+ /** A field that draws its own edge. */
23
+ export const BOXED = {
24
+ unstyled: true, width: '100%', px: 10, py: 7, rounded: 8, bg: SURFACE, borderWidth: 1, borderColor: LINE,
25
+ color: INK, fontSize: '$2', placeholderTextColor: '$placeholderColor',
26
+ };
27
+ /** The platform's select, wearing the same edge as `BOXED`. */
28
+ export const PICK = { width: '100%', rounded: 8, bg: SURFACE, borderColor: LINE, color: INK, fontSize: '$2' };
29
+ /** A raised card in the stream. */
30
+ export const CARD = { bg: RAISED, borderWidth: 1, borderColor: LINE, rounded: 14, p: 16, gap: 8, maxW: 620 };
31
+ /** A section's small-caps heading. */
32
+ export const LABEL = {
33
+ render: 'h3', fontSize: '$1', fontWeight: '600', letterSpacing: 0.6, color: FAINT, textTransform: 'uppercase',
34
+ };
35
+ /** A sentence the room says when it has nothing else to draw. */
36
+ export const NOTE = { render: 'p', px: 12, py: 10, fontSize: '$2', lineHeight: '1.3rem', color: DIM };
37
+ /** One line, cut with an ellipsis where it runs out of room. */
38
+ export const CLIP = { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' };
39
+ /** What a run printed: a bounded block of mono that scrolls. */
40
+ export const OUT = {
41
+ render: 'pre', display: 'block', maxH: 180, overflowY: 'auto', p: 10, rounded: 8, bg: SURFACE, borderWidth: 1,
42
+ borderColor: LINE, fontFamily: '$mono', fontSize: '$1', lineHeight: '1.03125rem', whiteSpace: 'pre-wrap', color: INK,
43
+ };
@@ -0,0 +1,4 @@
1
+ import type { Agent } from '@hanzo/ai';
2
+ export declare function Runtime({ bot }: {
3
+ bot: Agent;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,326 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // WHAT ONE BOT HAS.
4
+ //
5
+ // A Bot is a worker, and a worker owns things. The platform holds each one
6
+ // somewhere, and this pane is one section per thing:
7
+ //
8
+ // its MACHINE `computeRef`, an id from /v1/agents/targets
9
+ // its COMPUTER a sandbox leased on the project `bot-<name>`, which is this
10
+ // Bot's disk: a volume is named per (org, project) and the name
11
+ // is deterministic, so the same disk comes back after a lease ends
12
+ // its TOOLS `tools[]` — the names this Bot may call, out of the platform's
13
+ // own MCP surface plus whatever the org has registered
14
+ // its CHANNELS the rooms it answers, from /v1/channels/agent
15
+ // its CODE an autonomous run against one of the org's repos
16
+ // its SECRETS the KMS names under `bots/<name>`
17
+ //
18
+ // ONE THING A READER WILL LOOK FOR IS NOT THE BOT'S: a connector is held by the
19
+ // PERSON who signed in to it, and the contract publishes no route binding one to
20
+ // an agent. It is listed as what it is, with no switch to press.
21
+ //
22
+ // The screen and the terminal are real or they are absent. Each is a URL the
23
+ // platform mints against a sandbox that exists; where there is no sandbox there
24
+ // is no window, and where the mint is refused the refusal is what is drawn.
25
+ import { useCallback, useEffect, useState } from 'react';
26
+ import { Play, Square, X } from 'lucide-react';
27
+ import { useOrganizations } from '@hanzo/iam/react';
28
+ import { useAi } from '../lib/ai.js';
29
+ import { say } from '../failure.js';
30
+ import { Input, Text, XStack, YStack } from '@hanzo/gui';
31
+ import { Picker } from '@hanzo/ui';
32
+ import { DIM, FAINT, GROUND, INK, LINE } from './ink.js';
33
+ import { BOXED, LABEL, OUT, PICK, TAB } from './kit.js';
34
+ import { answer, answering, call, connectors as readConnectors, disk, grant, projects as readProjects, secrets as readSecrets, tools as readTools, vault, workOn, } from './api.js';
35
+ export function Runtime({ bot }) {
36
+ const { client } = useAi();
37
+ const { currentOrg } = useOrganizations();
38
+ const org = currentOrg?.displayName || currentOrg?.name || '';
39
+ const project = disk(bot.name);
40
+ const [machines, setMachines] = useState([]);
41
+ const [boxes, setBoxes] = useState([]);
42
+ const [servers, setServers] = useState([]);
43
+ const [skills, setSkills] = useState([]);
44
+ const [activated, setActivated] = useState([]);
45
+ const [published, setPublished] = useState([]);
46
+ const [links, setLinks] = useState([]);
47
+ const [rooms, setRooms] = useState([]);
48
+ const [keys, setKeys] = useState([]);
49
+ const [repos, setRepos] = useState([]);
50
+ const [why, setWhy] = useState({});
51
+ const blame = useCallback((k, e, subject) => {
52
+ setWhy((w) => ({ ...w, [k]: say(e, subject) }));
53
+ }, []);
54
+ const survey = useCallback(async () => {
55
+ if (!client)
56
+ return;
57
+ await Promise.all([
58
+ client.machines.list().then(setMachines).catch((e) => blame('machine', e, 'this org’s machines')),
59
+ client.sandboxes.list({ project }).then(setBoxes).catch((e) => blame('box', e, 'this bot’s computer')),
60
+ client.tools.mcp().then(setServers).catch((e) => blame('mcp', e, 'this org’s MCP servers')),
61
+ client.tools.skills().then((s) => setSkills(s.tools ?? [])).catch((e) => blame('skill', e, 'this org’s skills')),
62
+ client.tools.activation().then((a) => setActivated(a.enabled ?? [])).catch(() => setActivated([])),
63
+ readTools(client).then(setPublished).catch((e) => blame('served', e, 'the platform’s tools')),
64
+ readConnectors(client).then(setLinks).catch((e) => blame('link', e, 'your connectors')),
65
+ client.channels.list().then(setRooms).catch((e) => blame('room', e, 'this org’s channels')),
66
+ readSecrets(client, vault(bot.name)).then(setKeys).catch((e) => blame('key', e, 'this bot’s secrets')),
67
+ readProjects(client).then(setRepos).catch((e) => blame('repo', e, 'this org’s repos')),
68
+ ]);
69
+ }, [client, project, bot.name, blame]);
70
+ useEffect(() => {
71
+ void survey();
72
+ }, [survey]);
73
+ return (_jsxs(YStack, { render: "aside", "aria-label": `${bot.name} runtime`, "data-hz": "runtime-pane", width: 360, shrink: 0, overflowY: "auto", borderLeftWidth: 1, borderColor: LINE, bg: GROUND, children: [_jsxs(Part, { title: "Runtime", children: [_jsxs(Line, { children: [bot.name, " runs for ", _jsx(Text, { color: INK, children: org || 'your org' }), bot.serviceAccountId ? ` as ${bot.serviceAccountId}` : '', ". Nothing on this panel crosses into another org."] }), bot.schedule ? (_jsxs(Line, { children: ["Wakes on ", _jsx(Text, { color: INK, children: bot.schedule }), bot.executionMode ? ` (${bot.executionMode})` : '', "."] })) : null] }), _jsx(Machine, { bot: bot, machines: machines, why: why.machine }), _jsx(Computer, { bot: bot, box: boxes[0] ?? null, project: project, why: why.box, onChange: survey }), _jsx(Tools, { bot: bot, servers: servers, skills: skills, published: published, activated: activated, why: why.served ?? why.mcp ?? why.skill }), _jsx(Channels, { bot: bot, rooms: rooms, why: why.room }), _jsx(Code, { bot: bot, repos: repos, why: why.repo }), _jsx(Secrets, { bot: bot, keys: keys, why: why.key }), _jsx(Held, { links: links, why: why.link })] }));
74
+ }
75
+ /** The machine this Bot runs on. The list is the org's; the binding is the Bot's. */
76
+ function Machine({ bot, machines, why }) {
77
+ const { client } = useAi();
78
+ const [bound, setBound] = useState(bot.computeRef ?? '');
79
+ const [failed, setFailed] = useState(null);
80
+ useEffect(() => setBound(bot.computeRef ?? ''), [bot.computeRef]);
81
+ return (_jsxs(Part, { title: "Machine", children: [why ? _jsx(Line, { children: why }) : null, !why && machines.length === 0 ? (_jsx(Line, { children: "No machine is registered to this org, so this bot runs wherever the platform places it." })) : null, machines.length > 0 ? (_jsxs(Picker, { ...PICK, value: bound, "aria-label": "Machine", onChange: async (e) => {
82
+ const id = e.target.value;
83
+ setBound(id);
84
+ if (!client)
85
+ return;
86
+ try {
87
+ await client.agents.update(bot.name, { computeRef: id });
88
+ setFailed(null);
89
+ }
90
+ catch (err) {
91
+ setFailed(say(err, 'this bot', 'save'));
92
+ }
93
+ }, children: [_jsx("option", { value: "", children: "Unbound" }), machines.map((m) => (_jsxs("option", { value: m.id, children: [m.label || m.host || m.id, m.status ? ` — ${m.status}` : ''] }, m.id)))] })) : null, failed ? _jsx(Line, { children: failed }) : null] }));
94
+ }
95
+ /**
96
+ * This Bot's computer.
97
+ *
98
+ * A `desktop` lease has a screen and every lease has a terminal; both are opened
99
+ * through a ticket the platform mints against THIS sandbox, and the iframe points
100
+ * at the URL that came back. There is no other source for either window.
101
+ *
102
+ * MEASURED 2026-09-07: every class of lease is refused by the fleet —
103
+ * `exec` and `desktop` with "pod … not running after 2m0s", `dev` with
104
+ * "admin credentials: DO_API_TOKEN not configured" — and `/v1/exec`, the other
105
+ * door onto the same pool, answers the first of those. So the terminal, the
106
+ * screen and running code are all one outage, and the refusal is the whole of
107
+ * what this section can honestly show until the pool comes back.
108
+ */
109
+ function Computer({ bot, box, project, why, onChange, }) {
110
+ const { client } = useAi();
111
+ const [busy, setBusy] = useState(false);
112
+ const [failed, setFailed] = useState(null);
113
+ const [command, setCommand] = useState('');
114
+ const [ran, setRan] = useState(null);
115
+ const [window, setWindow] = useState(null);
116
+ const attempt = useCallback(async (what, act) => {
117
+ if (!client || busy)
118
+ return;
119
+ setBusy(true);
120
+ setFailed(null);
121
+ try {
122
+ await act();
123
+ }
124
+ catch (e) {
125
+ // The fleet's own words. A sandbox that will not start is the reader's
126
+ // situation and only the fleet knows why, so it is not translated away.
127
+ setFailed(`${what}: ${e instanceof Error ? e.message : String(e)}`);
128
+ }
129
+ finally {
130
+ setBusy(false);
131
+ }
132
+ }, [client, busy]);
133
+ const open = (kind) => attempt(`Could not open the ${kind}`, async () => {
134
+ if (!client || !box)
135
+ return;
136
+ const g = await grant(client, box.id, kind);
137
+ if (!g.url)
138
+ throw new Error('the platform minted no address');
139
+ setWindow({ kind, url: g.url });
140
+ });
141
+ return (_jsxs(Part, { title: "Computer", children: [_jsxs(Line, { children: ["Its own disk, ", _jsx(Text, { render: "code", fontSize: "$1", color: INK, children: project }), ". A lease on this project finds the same disk every time, so what this bot writes outlives the lease."] }), why ? _jsx(Line, { children: why }) : null, box ? (_jsxs(_Fragment, { children: [_jsxs(Row, { children: [_jsxs(Text, { fontSize: "$2", color: INK, "data-hz": "box", children: [box.class ?? 'sandbox', " \u00B7 ", box.status ?? 'unknown'] }), _jsx(Text, { fontSize: "$1", color: FAINT, children: box.id.slice(0, 14) })] }), box.status === 'pending' ? (_jsx(Line, { children: "Waiting for the fleet to place it. Nothing can be opened on it until it is running." })) : null, box.status === 'error' ? _jsx(Line, { children: box.error || 'The fleet could not start it.' }) : null, _jsxs(XStack, { gap: 6, flexWrap: "wrap", children: [box.status === 'running' ? (_jsx(Text, { ...TAB, onClick: () => open('terminal'), disabled: busy, children: "Terminal" })) : null, box.status === 'running' && box.class === 'desktop' ? (_jsx(Text, { ...TAB, onClick: () => open('screen'), disabled: busy, children: "Screen" })) : null, _jsxs(Text, { ...TAB, disabled: busy, onClick: () => attempt('Could not stop it', async () => {
142
+ await client.sandboxes.stop({ id: box.id });
143
+ }), children: [_jsx(Square, { size: 11, "aria-hidden": true }), "Stop"] }), _jsxs(Text, { ...TAB, disabled: busy, onClick: () => attempt('Could not end the lease', async () => {
144
+ await client.sandboxes.end({ id: box.id });
145
+ setWindow(null);
146
+ await onChange();
147
+ }), children: [_jsx(X, { size: 11, "aria-hidden": true }), "End"] })] }), window ? (_jsx(YStack, { render: "iframe", src: window.url, title: `${bot.name} ${window.kind}`, width: "100%", height: 240, borderWidth: 1, borderColor: LINE, rounded: 8, bg: GROUND })) : null, _jsxs(XStack, { render: "form", onSubmit: (e) => {
148
+ e.preventDefault();
149
+ void attempt('Could not run it', async () => {
150
+ setRan(await client.sandboxes.run({ id: box.id, command }));
151
+ });
152
+ }, display: box.status === 'running' ? 'flex' : 'none', gap: 6, children: [_jsx(Input, { ...BOXED, flex: 1, minW: 0, value: command, onChange: (e) => setCommand(e.target.value), placeholder: "Command", "aria-label": "Command to run in this bot's computer" }), _jsxs(Text, { ...TAB, type: 'submit', disabled: busy || !command.trim(), children: [_jsx(Play, { size: 11, "aria-hidden": true }), "Run"] })] }), ran ? (_jsxs(Text, { ...OUT, children: [(ran.stdout ?? '') + (ran.stderr ?? ''), `\nexit ${ran.exitCode ?? 0}`] })) : null] })) : (_jsxs(_Fragment, { children: [_jsx(Line, { children: "No computer is leased. A lease is what a terminal, a screen and a command all hang off, so until one starts there is nothing here to open." }), _jsxs(XStack, { gap: 6, flexWrap: "wrap", children: [_jsx(Text, { ...TAB, "data-hz": "lease", disabled: busy, onClick: () => attempt('Could not lease a computer', async () => {
153
+ await client.sandboxes.lease({ class: 'dev', project });
154
+ await onChange();
155
+ }), children: busy ? 'Leasing…' : 'Lease a computer' }), _jsx(Text, { ...TAB, "data-hz": "lease-desktop", disabled: busy, onClick: () => attempt('Could not lease a screen', async () => {
156
+ await client.sandboxes.lease({ class: 'desktop', project });
157
+ await onChange();
158
+ }), children: busy ? 'Leasing…' : 'Lease a screen' })] })] })), failed ? _jsx(Line, { "data-hz": "computer-failed", children: failed }) : null] }));
159
+ }
160
+ /**
161
+ * What this Bot may call, and calling one.
162
+ *
163
+ * `tools[]` on the agent record is the Bot's OWN permission set, and it is a
164
+ * PATCH to this Bot and nothing else — another Bot in the same org keeps its
165
+ * own list. The names come from three places, and they are three because the
166
+ * platform keeps them in three:
167
+ *
168
+ * PLATFORM `/v1/mcp` tools/list — every subsystem's operations, projected.
169
+ * 116 for a signed-in caller, with 127 more refused by a stated
170
+ * rule the endpoint publishes. This is the set that can be CALLED.
171
+ * MCP the org's own registered servers, `/v1/tools/mcp/servers`.
172
+ * SKILL the org's authored skills, `/v1/tools/skills`.
173
+ *
174
+ * A name from the last two is dispatchable only once the org ACTIVATES it —
175
+ * `/v1/tools/call` answers 404 "unknown tool" for everything outside the
176
+ * activated set — so an inactive one is labelled rather than drawn as ready.
177
+ *
178
+ * The call below runs against this Bot's granted set and nothing wider, which
179
+ * is what makes the grant mean something on this screen rather than only in
180
+ * the record.
181
+ */
182
+ function Tools({ bot, servers, skills, published, activated, why, }) {
183
+ const { client } = useAi();
184
+ const [held, setHeld] = useState(bot.tools ?? []);
185
+ const [failed, setFailed] = useState(null);
186
+ const [pick, setPick] = useState('');
187
+ const [op, setOp] = useState('');
188
+ const [ran, setRan] = useState(null);
189
+ const [busy, setBusy] = useState(false);
190
+ useEffect(() => setHeld(bot.tools ?? []), [bot.tools, bot.name]);
191
+ useEffect(() => setPick((p) => (held.includes(p) ? p : (held[0] ?? ''))), [held]);
192
+ const offered = [
193
+ ...published.map((t) => ({ name: t.name, from: 'platform', ready: true })),
194
+ ...servers.map((s) => ({ name: s.name || s.id, from: 'MCP', ready: activated.includes(s.name || s.id) })),
195
+ ...skills.map((s) => ({ name: s.name, from: 'skill', ready: activated.includes(s.name) })),
196
+ ];
197
+ const flip = async (name) => {
198
+ const next = held.includes(name) ? held.filter((t) => t !== name) : [...held, name];
199
+ setHeld(next);
200
+ if (!client)
201
+ return;
202
+ try {
203
+ await client.agents.update(bot.name, { tools: next });
204
+ setFailed(null);
205
+ }
206
+ catch (e) {
207
+ setFailed(say(e, 'this bot', 'save'));
208
+ }
209
+ };
210
+ return (_jsxs(Part, { title: "Tools", children: [why ? _jsx(Line, { children: why }) : null, offered.length === 0 && !why ? (_jsx(Line, { children: "Nothing publishes a tool for this org yet, so there is nothing to grant." })) : null, _jsx(YStack, { "data-hz": "tools", maxH: 200, overflowY: "auto", gap: 4, children: offered.map((t) => (_jsxs(XStack, { render: "label", items: "center", justify: "space-between", gap: 8, cursor: "pointer", children: [_jsx("input", { type: "checkbox", checked: held.includes(t.name), onChange: () => flip(t.name), "aria-label": t.name }), _jsx(Text, { flex: 1, fontSize: "$2", color: INK, children: t.name }), _jsx(Text, { fontSize: "$1", color: FAINT, children: t.ready ? t.from : `${t.from} · not activated` })] }, `${t.from}-${t.name}`))) }), held.length > 0 ? (_jsxs(YStack, { render: "form", onSubmit: async (e) => {
211
+ e.preventDefault();
212
+ if (!client || busy || !pick || !op.trim())
213
+ return;
214
+ setBusy(true);
215
+ try {
216
+ setRan(await call(client, pick, op.trim()));
217
+ }
218
+ catch (err) {
219
+ setRan(err instanceof Error ? err.message : String(err));
220
+ }
221
+ finally {
222
+ setBusy(false);
223
+ }
224
+ }, gap: 6, children: [_jsx(Picker, { ...PICK, value: pick, onChange: (e) => setPick(e.target.value), "aria-label": "Tool to call", children: held.map((h) => (_jsx("option", { value: h, children: h }, h))) }), _jsxs(XStack, { gap: 6, children: [_jsx(Input, { ...BOXED, flex: 1, minW: 0, "data-hz": "op", value: op, onChange: (e) => setOp(e.target.value), placeholder: "Operation", "aria-label": "Operation to run" }), _jsx(Text, { ...TAB, type: 'submit', "data-hz": "call", disabled: busy || !op.trim(), children: busy ? 'Calling…' : 'Call' })] })] })) : null, ran ? _jsx(Text, { ...OUT, "data-hz": "called", children: ran }) : null, failed ? _jsx(Line, { children: failed }) : null] }));
225
+ }
226
+ /**
227
+ * The rooms this Bot answers.
228
+ *
229
+ * `/v1/channels/agent` binds ONE agent per channel as the org's default
230
+ * answerer, so this is a per-Bot binding and not an account-wide list: pressing
231
+ * Answer here moves that channel from whichever bot holds it to this one.
232
+ */
233
+ function Channels({ bot, rooms, why }) {
234
+ const { client } = useAi();
235
+ const [bound, setBound] = useState({});
236
+ const [failed, setFailed] = useState(null);
237
+ const read = useCallback(async () => {
238
+ if (!client)
239
+ return;
240
+ const pairs = await Promise.all(rooms.map(async (r) => {
241
+ try {
242
+ return [r.id, (await answering(client, r.id)).default ?? ''];
243
+ }
244
+ catch {
245
+ return [r.id, ''];
246
+ }
247
+ }));
248
+ setBound(Object.fromEntries(pairs));
249
+ }, [client, rooms]);
250
+ useEffect(() => {
251
+ void read();
252
+ }, [read]);
253
+ return (_jsxs(Part, { title: "Channels", children: [why ? _jsx(Line, { children: why }) : null, rooms.length === 0 && !why ? _jsx(Line, { children: "This org has no channels." }) : null, rooms.map((r) => {
254
+ const mine = bound[r.id] === bot.name;
255
+ return (_jsxs(Row, { children: [_jsx(Text, { fontSize: "$2", color: INK, children: r.id }), _jsxs(XStack, { items: "center", gap: 8, children: [_jsx(Text, { fontSize: "$1", color: FAINT, children: r.connected ? bound[r.id] || 'unbound' : 'not connected' }), _jsx(Text, { ...TAB, opacity: mine ? 0.5 : 1, disabled: mine, onClick: async () => {
256
+ if (!client)
257
+ return;
258
+ try {
259
+ await answer(client, r.id, bot.name);
260
+ setFailed(null);
261
+ await read();
262
+ }
263
+ catch (e) {
264
+ setFailed(say(e, 'this channel', 'save'));
265
+ }
266
+ }, children: mine ? 'Answers' : 'Answer' })] })] }, r.id));
267
+ }), failed ? _jsx(Line, { children: failed }) : null] }));
268
+ }
269
+ /**
270
+ * Work on a repo.
271
+ *
272
+ * `/v1/agents/coding` starts ONE autonomous run against a repo the org owns and
273
+ * hangs a session off it, so this is the door between a Bot and a codebase. The
274
+ * repos offered are the org's own projects that carry one; a name outside that
275
+ * set is refused by the platform naming it, and the refusal is what is shown.
276
+ */
277
+ function Code({ bot, repos, why }) {
278
+ const { client } = useAi();
279
+ const withRepo = repos.filter((p) => p.repo?.url);
280
+ const [repo, setRepo] = useState('');
281
+ const [task, setTask] = useState('');
282
+ const [busy, setBusy] = useState(false);
283
+ const [said, setSaid] = useState(null);
284
+ useEffect(() => {
285
+ if (!repo && withRepo[0])
286
+ setRepo(withRepo[0].slug);
287
+ }, [repo, withRepo]);
288
+ return (_jsxs(Part, { title: "Code", children: [why ? _jsx(Line, { children: why }) : null, withRepo.length === 0 && !why ? (_jsx(Line, { children: "No project in this org names a repo, so there is nothing for this bot to work on." })) : (_jsxs(YStack, { render: "form", onSubmit: async (e) => {
289
+ e.preventDefault();
290
+ if (!client || busy || !repo || !task.trim())
291
+ return;
292
+ setBusy(true);
293
+ setSaid(null);
294
+ try {
295
+ const started = await workOn(client, { repo, prompt: task.trim(), agentRef: bot.name, project: repo });
296
+ setSaid(`Started ${started.sessionId || started.id || 'a run'} on ${repo}.`);
297
+ setTask('');
298
+ }
299
+ catch (err) {
300
+ setSaid(err instanceof Error ? err.message : String(err));
301
+ }
302
+ finally {
303
+ setBusy(false);
304
+ }
305
+ }, gap: 6, children: [_jsx(Picker, { ...PICK, value: repo, onChange: (e) => setRepo(e.target.value), "aria-label": "Repo", children: withRepo.map((p) => (_jsx("option", { value: p.slug, children: p.slug }, p.id))) }), _jsx(Input, { ...BOXED, "data-hz": "task", value: task, onChange: (e) => setTask(e.target.value), placeholder: "What to change", "aria-label": "What to change" }), _jsx(Text, { ...TAB, type: 'submit', "data-hz": "code", self: "flex-start", disabled: busy || !task.trim(), children: busy ? 'Starting…' : 'Work on it' })] })), said ? _jsx(Line, { children: said }) : null] }));
306
+ }
307
+ /** The names under this Bot's KMS prefix. The values stay in KMS. */
308
+ function Secrets({ bot, keys, why }) {
309
+ return (_jsxs(Part, { title: "Secrets", children: [_jsxs(Line, { children: ["Held under ", _jsx(Text, { render: "code", fontSize: "$1", color: INK, children: vault(bot.name) }), " in KMS. Names are readable here; values are not, by KMS and not by this screen."] }), why ? _jsx(Line, { children: why }) : null, keys.length === 0 && !why ? _jsx(Line, { children: "None yet." }) : null, keys.map((k) => (_jsxs(Row, { children: [_jsx(Text, { fontSize: "$2", color: INK, children: k.name }), _jsx(Text, { fontSize: "$1", color: FAINT, children: k.env })] }, `${k.path}/${k.name}`)))] }));
310
+ }
311
+ /**
312
+ * The one thing that is NOT this Bot's.
313
+ *
314
+ * A connector is held by the person who signed in to it.
315
+ * `/v1/integrations/connectors` answers one set for the caller and no route in
316
+ * the contract narrows it to one agent — so it is listed as what it is, and
317
+ * there is no switch to press.
318
+ */
319
+ function Held({ links, why }) {
320
+ return (_jsxs(Part, { title: "Your connectors", children: [_jsx(Line, { children: "Signed in by you, reachable by every bot in this org. The platform publishes no per-bot binding for a connector, so nothing here is scoped down to this one." }), why ? _jsx(Line, { children: why }) : null, links.length === 0 && !why ? _jsx(Line, { children: "None connected." }) : null, links.map((l) => (_jsxs(Row, { children: [_jsx(Text, { fontSize: "$1", color: DIM, children: l.label || l.provider }), _jsx(Text, { fontSize: "$1", color: FAINT, children: l.account })] }, l.id)))] }));
321
+ }
322
+ function Part({ title, children }) {
323
+ return (_jsxs(YStack, { render: "section", px: 16, py: 14, borderBottomWidth: 1, borderColor: LINE, gap: 8, children: [_jsx(Text, { ...LABEL, children: title }), children] }));
324
+ }
325
+ const Line = ({ children, ...rest }) => (_jsx(Text, { render: "p", fontSize: "$1", lineHeight: "1.1rem", color: DIM, ...rest, children: children }));
326
+ const Row = ({ children }) => (_jsx(XStack, { items: "center", justify: "space-between", gap: 8, children: children }));