@hanzo/build 0.2.79 → 0.2.81

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 (167) hide show
  1. package/README.md +22 -4
  2. package/lib/activity.d.ts +3 -7
  3. package/lib/activity.js +18 -5
  4. package/lib/api/agents.d.ts +36 -35
  5. package/lib/api/agents.js +66 -29
  6. package/lib/api/auto.d.ts +3 -23
  7. package/lib/api/auto.js +39 -22
  8. package/lib/api/billing.d.ts +0 -35
  9. package/lib/api/billing.js +6 -5
  10. package/lib/api/call.d.ts +16 -8
  11. package/lib/api/call.js +32 -1
  12. package/lib/api/capabilities.d.ts +0 -11
  13. package/lib/api/capabilities.js +2 -1
  14. package/lib/api/codebases.d.ts +0 -10
  15. package/lib/api/codebases.js +3 -2
  16. package/lib/api/coding.d.ts +3 -32
  17. package/lib/api/coding.js +13 -7
  18. package/lib/api/connectors.d.ts +1 -17
  19. package/lib/api/connectors.js +5 -4
  20. package/lib/api/environment.d.ts +6 -20
  21. package/lib/api/environment.js +22 -19
  22. package/lib/api/github.d.ts +0 -22
  23. package/lib/api/github.js +3 -2
  24. package/lib/api/keys.d.ts +0 -14
  25. package/lib/api/keys.js +4 -3
  26. package/lib/api/library.d.ts +3 -17
  27. package/lib/api/library.js +23 -22
  28. package/lib/api/machines.d.ts +0 -15
  29. package/lib/api/machines.js +6 -5
  30. package/lib/api/mcp.d.ts +0 -9
  31. package/lib/api/mcp.js +3 -2
  32. package/lib/api/members.d.ts +0 -15
  33. package/lib/api/members.js +4 -3
  34. package/lib/api/memory.d.ts +0 -11
  35. package/lib/api/memory.js +2 -1
  36. package/lib/api/places.d.ts +1 -9
  37. package/lib/api/places.js +12 -1
  38. package/lib/api/plugins.d.ts +1 -16
  39. package/lib/api/plugins.js +4 -3
  40. package/lib/api/profile.d.ts +0 -10
  41. package/lib/api/profile.js +5 -4
  42. package/lib/api/projects.d.ts +0 -14
  43. package/lib/api/projects.js +2 -1
  44. package/lib/api/provider.d.ts +2 -14
  45. package/lib/api/provider.js +12 -10
  46. package/lib/api/sandbox.d.ts +0 -17
  47. package/lib/api/sandbox.js +5 -4
  48. package/lib/api/sessions.d.ts +0 -17
  49. package/lib/api/sessions.js +5 -4
  50. package/lib/api/skills.d.ts +1 -15
  51. package/lib/api/skills.js +5 -4
  52. package/lib/api/studio.d.ts +0 -14
  53. package/lib/api/studio.js +6 -5
  54. package/lib/api/turn.d.ts +6 -28
  55. package/lib/api/turn.js +105 -49
  56. package/lib/api/verdict.d.ts +0 -9
  57. package/lib/api/verdict.js +2 -1
  58. package/lib/api/webhooks.d.ts +1 -14
  59. package/lib/api/webhooks.js +5 -4
  60. package/lib/ask.js +7 -4
  61. package/lib/automations.d.ts +5 -4
  62. package/lib/automations.js +95 -74
  63. package/lib/back.js +10 -4
  64. package/lib/builder.js +7 -5
  65. package/lib/carry.js +20 -16
  66. package/lib/customize/agents.d.ts +3 -0
  67. package/lib/customize/agents.js +152 -55
  68. package/lib/customize/connectors.js +62 -48
  69. package/lib/customize/index.js +45 -14
  70. package/lib/customize/plugins.js +34 -22
  71. package/lib/customize/say.d.ts +13 -1
  72. package/lib/customize/say.js +15 -21
  73. package/lib/customize/skills.js +31 -22
  74. package/lib/customize/ui.d.ts +5 -9
  75. package/lib/customize/ui.js +26 -16
  76. package/lib/data.d.ts +1 -1
  77. package/lib/data.js +3 -2
  78. package/lib/desk.js +45 -33
  79. package/lib/door.js +35 -10
  80. package/lib/environment.js +25 -19
  81. package/lib/files.js +43 -34
  82. package/lib/find.js +12 -9
  83. package/lib/foot.js +7 -4
  84. package/lib/forge.js +61 -40
  85. package/lib/gear.d.ts +2 -1
  86. package/lib/gear.js +6 -4
  87. package/lib/git.js +29 -19
  88. package/lib/host.d.ts +3 -1
  89. package/lib/landing.d.ts +5 -5
  90. package/lib/landing.js +52 -44
  91. package/lib/library.js +73 -51
  92. package/lib/meter.d.ts +1 -1
  93. package/lib/meter.js +14 -5
  94. package/lib/mic.js +12 -5
  95. package/lib/mind.d.ts +1 -0
  96. package/lib/mind.js +5 -3
  97. package/lib/nav.d.ts +6 -5
  98. package/lib/nav.js +37 -30
  99. package/lib/panel.d.ts +2 -1
  100. package/lib/panel.js +52 -36
  101. package/lib/plan.d.ts +4 -20
  102. package/lib/plan.js +26 -10
  103. package/lib/plans.js +49 -30
  104. package/lib/plugins/design.js +3 -2
  105. package/lib/plugins/index.d.ts +23 -4
  106. package/lib/plugins/index.js +11 -26
  107. package/lib/plugins/pdf.js +2 -1
  108. package/lib/plugins/project.js +6 -3
  109. package/lib/plugins/research.js +3 -2
  110. package/lib/plugins/sketch.js +6 -3
  111. package/lib/plugins/slides.js +3 -2
  112. package/lib/plugins/template.js +6 -3
  113. package/lib/plus.js +25 -13
  114. package/lib/prefs.d.ts +0 -14
  115. package/lib/prefs.js +2 -1
  116. package/lib/project.js +69 -62
  117. package/lib/prompt.d.ts +12 -11
  118. package/lib/prompt.js +38 -20
  119. package/lib/publish.js +17 -12
  120. package/lib/reply.js +6 -3
  121. package/lib/run.js +72 -54
  122. package/lib/say.d.ts +2 -0
  123. package/lib/say.js +7 -0
  124. package/lib/scheduled.js +54 -43
  125. package/lib/section.d.ts +1 -0
  126. package/lib/section.js +8 -2
  127. package/lib/settings/account.d.ts +1 -0
  128. package/lib/settings/account.js +31 -21
  129. package/lib/settings/billing.js +55 -28
  130. package/lib/settings/capabilities.js +26 -15
  131. package/lib/settings/card.js +19 -12
  132. package/lib/settings/code.js +22 -11
  133. package/lib/settings/credits.d.ts +4 -4
  134. package/lib/settings/credits.js +34 -21
  135. package/lib/settings/environments.js +21 -14
  136. package/lib/settings/general.js +26 -14
  137. package/lib/settings/index.js +6 -3
  138. package/lib/settings/integrations.js +42 -28
  139. package/lib/settings/keys.js +37 -19
  140. package/lib/settings/list.d.ts +0 -5
  141. package/lib/settings/list.js +24 -15
  142. package/lib/settings/machines.d.ts +3 -0
  143. package/lib/settings/machines.js +63 -38
  144. package/lib/settings/members.js +23 -14
  145. package/lib/settings/memory.js +12 -6
  146. package/lib/settings/notifications.js +32 -19
  147. package/lib/settings/plan.d.ts +1 -1
  148. package/lib/settings/plan.js +40 -17
  149. package/lib/settings/privacy.js +26 -13
  150. package/lib/settings/ui.d.ts +3 -2
  151. package/lib/settings/ui.js +9 -3
  152. package/lib/settings/usage.js +7 -3
  153. package/lib/shelf.js +18 -15
  154. package/lib/side.d.ts +1 -26
  155. package/lib/side.js +11 -5
  156. package/lib/standing.d.ts +2 -10
  157. package/lib/standing.js +5 -3
  158. package/lib/suggest.d.ts +8 -28
  159. package/lib/suggest.js +39 -4
  160. package/lib/suggestions.js +14 -5
  161. package/lib/summary.d.ts +1 -1
  162. package/lib/summary.js +4 -1
  163. package/lib/sync.js +22 -12
  164. package/lib/tools.d.ts +4 -3
  165. package/lib/tools.js +10 -7
  166. package/lib/transcript.js +10 -8
  167. package/package.json +4 -2
package/lib/carry.js CHANGED
@@ -17,6 +17,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
17
17
  * (Chat its agents, Dev its devices) beside these.
18
18
  */
19
19
  import { SizableText, XStack } from '@hanzo/gui';
20
+ import { useI18n } from '@hanzo/i18n/react';
20
21
  import { BookOpen, ListChecks, Palette, ScrollText } from '@hanzogui/lucide-icons-2';
21
22
  import { Button, Dialog, DialogContent, DialogDescription, DialogTitle } from '@hanzo/ui';
22
23
  import { useEffect, useMemo, useRef, useState } from 'react';
@@ -65,7 +66,8 @@ async function said(t, skills, memory, mode, design) {
65
66
  }
66
67
  /** Manual's pause: what is about to run, what it does, and the three answers. Closing it is a no. */
67
68
  function Allow({ what, onAnswer }) {
68
- return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && onAnswer('deny'), children: _jsxs(DialogContent, { maxW: 440, width: "100%", "data-slot": "allow", children: [_jsx(DialogTitle, { children: `Allow ${what.name}?` }), _jsx(DialogDescription, { children: what.note ? `${what.note}. Manual asks before each one runs.` : 'Manual asks before each one runs.' }), _jsxs(XStack, { justify: "flex-end", gap: "$2", pt: "$3", flexWrap: "wrap", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => onAnswer('deny'), children: "Deny" }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => onAnswer('always'), children: _jsx(SizableText, { fontSize: 14, children: "Always in this chat" }) }), _jsx(Button, { size: "sm", onPress: () => onAnswer('once'), children: "Allow once" })] })] }) }));
69
+ const { t } = useI18n();
70
+ return (_jsx(Dialog, { open: true, onOpenChange: (o) => !o && onAnswer('deny'), children: _jsxs(DialogContent, { maxW: 440, width: "100%", "data-slot": "allow", children: [_jsx(DialogTitle, { children: t('Allow {name}?', { name: t(what.name) }) }), _jsx(DialogDescription, { children: what.note ? `${t(what.note)}. ${t('Manual asks before each one runs.')}` : t('Manual asks before each one runs.') }), _jsxs(XStack, { justify: "flex-end", gap: "$2", pt: "$3", flexWrap: "wrap", children: [_jsx(Button, { size: "sm", variant: "ghost", onPress: () => onAnswer('deny'), children: t('Deny') }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => onAnswer('always'), children: _jsx(SizableText, { fontSize: 14, children: t('Always in this chat') }) }), _jsx(Button, { size: "sm", onPress: () => onAnswer('once'), children: t('Allow once') })] })] }) }));
69
71
  }
70
72
  /**
71
73
  * The conversation `key` names (`''` for one not begun), for target `t`.
@@ -73,6 +75,7 @@ function Allow({ what, onAnswer }) {
73
75
  * surface offers (a choice it does not offer reads as Auto).
74
76
  */
75
77
  export function useCarry(t, key, customize, lent, modes = DEV_MODES) {
78
+ const { t: say } = useI18n();
76
79
  const gear = useGear(t, key);
77
80
  const [dialog, show] = useState(null);
78
81
  // The composer a plugin acts on, read when it acts: the draft moves under it.
@@ -98,13 +101,13 @@ export function useCarry(t, key, customize, lent, modes = DEV_MODES) {
98
101
  rows: names.map((n) => ({ id: n, label: n, icon: _jsx(ScrollText, { size: 16 }), on: skills.includes(n) })),
99
102
  pick: (id) => gear.toggle('skills', id),
100
103
  loading: list.loading,
101
- error: list.error ? 'The skills could not be read.' : null,
102
- search: names.length > 8 ? 'Search skills' : undefined,
103
- empty: 'No skills are on for this organization',
104
- manage: { label: 'Manage skills', onPress: () => customize('skills') },
105
- browse: { label: 'Browse skills', onPress: () => customize('skills') },
104
+ error: list.error ? say('The skills could not be read.') : null,
105
+ search: names.length > 8 ? say('Search skills') : undefined,
106
+ empty: say('No skills are on for this organization'),
107
+ manage: { label: say('Manage skills'), onPress: () => customize('skills') },
108
+ browse: { label: say('Browse skills'), onPress: () => customize('skills') },
106
109
  };
107
- const plugins = pluginShelf(found, gear, composer);
110
+ const plugins = pluginShelf(found, gear, composer, say);
108
111
  // The design systems: Studio's brands, the one store for them.
109
112
  const kits = useRead(signed ? () => brands(t) : null, [], [t, signed, design]);
110
113
  const named = kits.value.find((b) => b.id === design);
@@ -114,26 +117,27 @@ export function useCarry(t, key, customize, lent, modes = DEV_MODES) {
114
117
  }),
115
118
  pick: (id) => gear.set({ design: design === id ? '' : id }),
116
119
  loading: kits.loading,
117
- error: kits.error ? 'The design systems could not be read.' : null,
118
- empty: 'No design systems yet',
120
+ error: kits.error ? say('The design systems could not be read.') : null,
121
+ empty: say('No design systems yet'),
119
122
  create: {
120
- label: 'Create design system',
123
+ label: say('Create design system'),
121
124
  onPress: () => {
122
125
  if (!gear.gear.plugins.includes('design'))
123
126
  gear.toggle('plugins', 'design');
124
- lentNow.current.setDraft('Make me a design system for ');
127
+ // The person's own words to finish, so in their language.
128
+ lentNow.current.setDraft(say('Make me a design system for '));
125
129
  },
126
130
  },
127
131
  };
128
132
  const lit = lighted(found.plugins, gear.gear);
129
133
  const on = [
130
134
  ...skills.map((n) => ({ id: `skill:${n}`, label: n, icon: _jsx(ScrollText, { size: 14 }), onRemove: () => gear.toggle('skills', n) })),
131
- ...(research ? [{ id: 'research', label: 'Research', icon: _jsx(BookOpen, { size: 14 }), onRemove: () => gear.set({ research: false }) }] : []),
132
- ...(mode === 'plan' ? [{ id: 'plan', label: 'Plan', icon: _jsx(ListChecks, { size: 14 }), onRemove: () => gear.set({ mode: 'auto' }) }] : []),
133
- ...(design ? [{ id: 'design', label: named?.name ?? 'Design system', icon: _jsx(Palette, { size: 14 }), onRemove: () => gear.set({ design: '' }) }] : []),
135
+ ...(research ? [{ id: 'research', label: say('Research'), icon: _jsx(BookOpen, { size: 14 }), onRemove: () => gear.set({ research: false }) }] : []),
136
+ ...(mode === 'plan' ? [{ id: 'plan', label: say('Plan'), icon: _jsx(ListChecks, { size: 14 }), onRemove: () => gear.set({ mode: 'auto' }) }] : []),
137
+ ...(design ? [{ id: 'design', label: named?.name ?? say('Design system'), icon: _jsx(Palette, { size: 14 }), onRemove: () => gear.set({ design: '' }) }] : []),
134
138
  ...lit
135
139
  .filter((p) => p.id !== 'research')
136
- .map((p) => ({ id: `plugin:${p.id}`, label: p.name, icon: p.icon, onRemove: () => gear.toggle('plugins', p.id) })),
140
+ .map((p) => ({ id: `plugin:${p.id}`, label: say(p.name), icon: p.icon, onRemove: () => gear.toggle('plugins', p.id) })),
137
141
  ];
138
142
  const signedIn = Boolean(t.token());
139
143
  const [text, setText] = useState('');
@@ -183,7 +187,7 @@ export function useCarry(t, key, customize, lent, modes = DEV_MODES) {
183
187
  for (const x of lit)
184
188
  if (!signal.aborted && !(await allow({ id: x.id, name: x.name, note: x.note })))
185
189
  denied.push(x.id);
186
- const p = await prep(found.plugins, gear.gear, { ...turn, design }, composer, signal, denied);
190
+ const p = await prep(found.plugins, gear.gear, { ...turn, design }, composer, signal, denied, say);
187
191
  if (p.design)
188
192
  gear.set({ design: p.design, plugins: gear.gear.plugins.filter((x) => x !== 'design') });
189
193
  return p;
@@ -1,4 +1,7 @@
1
+ import { type Cast } from '../api/agents.ts';
1
2
  import { type Pane } from './ui.tsx';
3
+ /** Categories as Browse offers them: the default agents' and presets' first, as listed, then the rest by how many each holds. */
4
+ export declare function kinds(first: readonly Cast[], rest: readonly Cast[]): string[];
2
5
  export declare function Agents({ view, q, adding, onAdding, onView, onCount }: Pane): import("react").JSX.Element;
3
6
  /** One agent's page: who it is, what it runs on and reads, what it may call and spend, and what it has done. */
4
7
  export declare function Profile({ name }: {
@@ -3,23 +3,31 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
3
3
  * Agents: the org's own — a model, instructions, the tools it may call, and
4
4
  * what it may spend.
5
5
  *
6
- * Browse is the platform's presets whose tool calls run on the platform;
7
- * starting from one opens a new agent written from it. Yours lists the org's
8
- * agents, and Add default agents makes every member of the host's cast the org
9
- * has none for (an org starts with none). Opening one is its profile page
6
+ * Browse is every agent there is to start from: the host's default agents
7
+ * (`cast`) and personas (`personas`), and the platform's presets whose tool
8
+ * calls run on the platform, by name and category. Use makes the org's own
9
+ * agent from one and opens it; nothing is added any other way, so an org starts
10
+ * with none. An agent is also made from scratch, from the person signed in
11
+ * (Clone yourself) or from anyone named. Yours lists the org's agents. Opening one is its profile page
10
12
  * (`-/customize/agents/<name>`): its face, part, model, instructions, tools,
11
13
  * budget and runs, edited in place, and only what changed is sent. An
12
14
  * agent calls only the tools it names, and only those that are on for the org,
13
15
  * so the choices are the tools that are on; "every tool" is whatever the
14
16
  * fleet's MCP server serves when it runs. A budget is required: what it may
15
17
  * spend each period, and in one run.
18
+ *
19
+ * Its words are the reader's language (@hanzo/i18n `t`); an agent's name,
20
+ * description, instructions, model and runs, and the categories the host and
21
+ * the platform give their templates, are drawn as given.
16
22
  */
17
23
  import { SizableText, XStack, YStack } from '@hanzo/gui';
18
- import { ArrowLeft, Bot, Users } from '@hanzogui/lucide-icons-2';
24
+ import { phrase } from '@hanzo/i18n';
25
+ import { useI18n } from '@hanzo/i18n/react';
26
+ import { ArrowLeft, Upload } from '@hanzogui/lucide-icons-2';
19
27
  import { Button, Input, Switch, Textarea } from '@hanzo/ui';
20
28
  import { ModelPicker } from '@hanzo/ui/models';
21
- import { useEffect, useMemo, useState } from 'react';
22
- import { agents, ALL, create, draft, EMPTY, fromPreset, hire, missing, one, PERIODS, presets, profile, remove, update } from '../api/agents.js';
29
+ import { useEffect, useMemo, useRef, useState } from 'react';
30
+ import { agents, ALL, create, draft, EMPTY, fromPreset, one, PERIODS, person, presets, profile, remove, update, use } from '../api/agents.js';
23
31
  import { models } from '../api/models.js';
24
32
  import { tools } from '../api/tools.js';
25
33
  import { useHost, useTarget } from '../host.js';
@@ -27,80 +35,165 @@ import { path } from '../route.js';
27
35
  import { useLoad } from './load.js';
28
36
  import { say } from './say.js';
29
37
  import { Add, Choice, Confirm, Empty, Failed, Field, Grid, Line, Mark, matches, mono, Part, Sheet, Soft, Tile, useCount, Visitor } from './ui.js';
38
+ /** How many cards Browse draws at a time. */
39
+ const PAGE = 60;
40
+ const ANY = phrase('All');
41
+ /** Categories as Browse offers them: the default agents' and presets' first, as listed, then the rest by how many each holds. */
42
+ export function kinds(first, rest) {
43
+ const lead = [...new Set(first.map((c) => c.category))];
44
+ const count = new Map();
45
+ for (const c of rest)
46
+ if (!lead.includes(c.category))
47
+ count.set(c.category, (count.get(c.category) ?? 0) + 1);
48
+ return [ANY, ...lead, ...[...count.keys()].sort((a, b) => count.get(b) - count.get(a) || a.localeCompare(b))];
49
+ }
30
50
  const money = (m) => `$${(m / 1_000_000).toFixed(m % 10_000 ? 4 : 2).replace(/\.?0+$/, '')}`;
51
+ /** A category as Browse names it: All, and the platform's presets and people, in the reader's words; the rest are the host's, as given. */
52
+ const category = (c, t) => (c === ANY || c === 'Preset' || c === 'Person' ? t(c) : c);
53
+ /**
54
+ * A budget's sentences by its period ('' where the agent names none): whole
55
+ * sentences, so a language says the period its own way.
56
+ */
57
+ const SPENT = {
58
+ day: phrase('{spent} of {cap} this day'),
59
+ week: phrase('{spent} of {cap} this week'),
60
+ month: phrase('{spent} of {cap} this month'),
61
+ '': phrase('{spent} of {cap} this period'),
62
+ };
63
+ const BUDGET = {
64
+ day: phrase('{spent} of {cap} spent this day; at most {task} a run.'),
65
+ week: phrase('{spent} of {cap} spent this week; at most {task} a run.'),
66
+ month: phrase('{spent} of {cap} spent this month; at most {task} a run.'),
67
+ '': phrase('{spent} of {cap} spent this period; at most {task} a run.'),
68
+ };
69
+ const SO_FAR = {
70
+ day: phrase('Spent {spent} this day.'),
71
+ week: phrase('Spent {spent} this week.'),
72
+ month: phrase('Spent {spent} this month.'),
73
+ '': phrase('Spent {spent} this period.'),
74
+ };
75
+ const EACH = { day: phrase('Each day, USD'), week: phrase('Each week, USD'), month: phrase('Each month, USD') };
76
+ const PERIOD = { day: phrase('Day'), week: phrase('Week'), month: phrase('Month') };
31
77
  export function Agents({ view, q, adding, onAdding, onView, onCount }) {
78
+ const { t } = useI18n();
32
79
  const host = useHost();
33
- const t = useTarget();
80
+ const target = useTarget();
34
81
  const signed = Boolean(host.person);
35
- const mine = useLoad(signed ? () => agents(t) : null, [], [t, signed]);
36
- const offered = useLoad(view === 'discover' ? () => presets(t) : null, [], [t, view]);
82
+ const browsing = view === 'discover';
83
+ const mine = useLoad(signed ? () => agents(target) : null, [], [target, signed]);
84
+ const offered = useLoad(browsing ? () => presets(target) : null, [], [target, view]);
85
+ const figures = useLoad(browsing && host.personas ? host.personas : null, [], [view]);
37
86
  useCount(mine.value.length, onCount);
38
- const [start, setStart] = useState(null);
39
87
  const [note, setNote] = useState('');
40
- const [hiring, setHiring] = useState(false);
41
- const cast = host.cast ?? [];
42
- const lacking = missing(cast, mine.value);
43
- // The host's cast, made in one press: each member the org has no agent for.
44
- const addCast = async () => {
45
- setHiring(true);
88
+ const [kind, setKind] = useState(ANY);
89
+ const [more, setMore] = useState(PAGE);
90
+ const [using, setUsing] = useState(null);
91
+ const [making, setMaking] = useState(null);
92
+ useEffect(() => setMore(PAGE), [q, kind]);
93
+ const opened = (a) => host.go(path({ kind: 'agent', name: a.name }));
94
+ const take = async (c) => {
95
+ setUsing(c);
46
96
  setNote('');
47
- const { made, error } = await hire(t, cast, mine.value);
48
- setHiring(false);
49
- mine.reload();
50
- setNote(error ? `${made.length ? `Added ${made.length}. ` : ''}${say(error)}` : `Added ${made.length} default ${made.length === 1 ? 'agent' : 'agents'}`);
51
- };
52
- const castButton = signed && lacking.length ? (_jsxs(Button, { size: "sm", variant: "outline", disabled: hiring, onPress: () => void addCast(), children: [_jsx(Users, { size: 14 }), " ", hiring ? 'Adding…' : lacking.length === cast.length ? 'Add default agents' : `Add ${lacking.length} default ${lacking.length === 1 ? 'agent' : 'agents'}`] })) : null;
53
- const closeNew = () => {
54
- setStart(null);
55
- onAdding(false);
97
+ try {
98
+ opened(await use(target, c, window.location.href));
99
+ }
100
+ catch (e) {
101
+ setNote(say(e, t));
102
+ }
103
+ finally {
104
+ setUsing(null);
105
+ }
56
106
  };
57
- const editor = adding || start ? (_jsx(Editor, { start: start ?? EMPTY, onClose: closeNew, onSaved: (a) => {
58
- closeNew();
107
+ const editor = adding ? (_jsx(Editor, { onClose: () => onAdding(false), onSaved: (a) => {
108
+ onAdding(false);
59
109
  onView('yours');
60
110
  mine.reload();
61
- setNote(`${a.name} is saved`);
111
+ setNote(t('{name} is saved', { name: a.name }));
62
112
  } })) : null;
63
- if (view === 'discover') {
64
- const names = new Set(mine.value.map((a) => a.name));
65
- const shown = offered.value.filter((p) => matches(q, p.id, p.title, p.prompt));
66
- return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), _jsx(Part, { title: "Start from a preset", detail: "A ready agent from Hanzo. You can change it before you save it.", children: offered.error && !offered.value.length ? (_jsx(Failed, { error: offered.error, onRetry: offered.reload })) : offered.loading && !offered.value.length ? (_jsx(Soft, { children: "Loading presets\u2026" })) : !shown.length ? (_jsx(Soft, { children: offered.value.length ? `No preset matches “${q.trim()}”.` : 'There are no presets yet. Start from a blank agent with New agent.' })) : (_jsx(Grid, { label: "Presets", children: shown.map((p) => (_jsx(Tile, { title: p.title || p.id, detail: p.prompt, meta: `Preset ${p.id}`, mark: _jsx(Mark, { name: p.title || p.id, icon: _jsx(Bot, { size: 15 }) }), action: signed ? _jsx(Add, { name: p.title || p.id, label: "Use", added: names.has(p.id), onPress: () => setStart(fromPreset(p)) }) : undefined }, p.id))) })) }), editor] }));
113
+ if (browsing) {
114
+ const first = [...(host.cast ?? []), ...offered.value.map(fromPreset)];
115
+ const all = [...first, ...figures.value];
116
+ const shown = all.filter((c) => (kind === ANY || c.category === kind) && matches(q, c.title, c.name, c.description, c.category));
117
+ const failed = figures.error ?? offered.error;
118
+ return (_jsxs(YStack, { gap: "$3", children: [signed ? (_jsxs(XStack, { gap: "$2", flexWrap: "wrap", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onAdding(true), children: t('Start from scratch') }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setMaking('self'), children: t('Clone yourself') }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setMaking('person'), children: t('Create from a person') })] })) : null, _jsx(XStack, { overflowX: "auto", overflowY: "hidden", pb: "$1", children: _jsx(Choice, { label: t('Category'), value: kind, options: kinds(first, figures.value).map((id) => ({ id, label: category(id, t) })), onChange: setKind }) }), _jsx(Line, { children: note }), shown.length ? (_jsx(Grid, { label: t('Agents to start from'), children: shown.slice(0, more).map((c) => (_jsx(Tile, { title: c.title, detail: c.description === c.title || c.description === c.category ? undefined : c.description, meta: category(c.category, t), mark: _jsx(Mark, { name: c.title, size: 40, logo: c.avatar }), action: signed ? _jsx(Add, { name: c.title, use: true, added: false, busy: using !== null, onPress: () => void take(c) }) : undefined }, `${c.category}/${c.name}/${c.title}`))) })) : figures.loading || offered.loading ? null : (_jsx(Soft, { children: t('No agent matches “{q}”.', { q: q.trim() }) })), shown.length > more ? (_jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setMore(more + PAGE), children: t('More') }) })) : null, failed ? _jsx(Failed, { error: failed, onRetry: figures.error ? figures.reload : offered.reload }) : figures.loading || offered.loading ? _jsx(Soft, { children: t('Loading…') }) : null, editor, making ? _jsx(Person, { self: making === 'self', onClose: () => setMaking(null), onMade: opened }) : null] }));
67
119
  }
68
120
  if (!signed) {
69
- return _jsx(Visitor, { children: "Sign in to see your agents." });
121
+ return _jsx(Visitor, { children: t('Sign in to see your agents.') });
70
122
  }
71
123
  const shown = mine.value.filter((a) => matches(q, a.name, a.description, a.model));
72
- return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Failed, { error: mine.error, onRetry: mine.reload })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: "Loading your agents\u2026" })) : !mine.value.length ? (_jsxs(Empty, { title: "No agents yet", detail: cast.length ? 'Add the default team, start from a preset, or give a new one a model, instructions and tools.' : 'Start from a preset, or give a new one a model, instructions and tools.', children: [castButton, _jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: "Browse presets" }), _jsx(Button, { size: "sm", onPress: () => onAdding(true), children: "New agent" })] })) : !shown.length ? (_jsx(Soft, { children: `No agent of yours matches “${q.trim()}”.` })) : (_jsxs(_Fragment, { children: [castButton ? _jsx(XStack, { children: castButton }) : null, _jsx(Grid, { label: "Your agents", children: shown.map((a) => (_jsx(Tile, { title: a.name, detail: a.description || 'No description.', meta: [a.model, `${a.runs} ${a.runs === 1 ? 'run' : 'runs'}`, reach(a), a.cap ? `${money(a.spent)} of ${money(a.cap)} this ${a.period || 'period'}` : ''].filter(Boolean).join(' · '), mark: _jsx(Face, { agent: a, cast: cast }), onOpen: () => host.go(path({ kind: 'agent', name: a.name })) }, a.id || a.name))) })] })), editor] }));
124
+ return (_jsxs(YStack, { gap: "$3", children: [_jsx(Line, { children: note }), mine.error && !mine.value.length ? (_jsx(Failed, { error: mine.error, onRetry: mine.reload })) : mine.loading && !mine.value.length ? (_jsx(Soft, { children: t('Loading your agents…') })) : !mine.value.length ? (_jsxs(Empty, { title: t('No agents yet'), detail: "", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => onView('discover'), children: t('Browse agents') }), _jsx(Button, { size: "sm", onPress: () => onAdding(true), children: t('New agent') })] })) : !shown.length ? (_jsx(Soft, { children: t('No agent of yours matches “{q}”.', { q: q.trim() }) })) : (_jsx(Grid, { label: t('Your agents'), children: shown.map((a) => (_jsx(Tile, { title: a.name, detail: a.description || t('No description.'), meta: [
125
+ a.model,
126
+ a.runs === 1 ? t('1 run') : t('{n} runs', { n: a.runs }),
127
+ reach(a, t),
128
+ a.cap ? t(SPENT[a.period], { spent: money(a.spent), cap: money(a.cap) }) : '',
129
+ ]
130
+ .filter(Boolean)
131
+ .join(' · '), mark: _jsx(Face, { agent: a, cast: host.cast ?? [] }), onOpen: () => opened(a) }, a.id || a.name))) })), editor] }));
132
+ }
133
+ /** Clone yourself, or anyone named: a name, who they are, and writing of theirs, pasted or uploaded. */
134
+ function Person({ self, onClose, onMade }) {
135
+ const { t } = useI18n();
136
+ const host = useHost();
137
+ const target = useTarget();
138
+ const me = self ? host.person : null;
139
+ const [name, setName] = useState(me?.name ?? '');
140
+ const [about, setAbout] = useState('');
141
+ const [writing, setWriting] = useState('');
142
+ const [working, setWorking] = useState(false);
143
+ const [note, setNote] = useState('');
144
+ const file = useRef(null);
145
+ const read = async (files) => {
146
+ const texts = await Promise.all([...(files ?? [])].map((f) => f.text()));
147
+ setWriting((w) => [w.trim(), ...texts.map((x) => x.trim())].filter(Boolean).join('\n\n'));
148
+ };
149
+ const save = async () => {
150
+ setWorking(true);
151
+ setNote('');
152
+ try {
153
+ onMade(await use(target, person(name, about, writing, me?.avatar), window.location.href));
154
+ }
155
+ catch (e) {
156
+ setNote(say(e, t));
157
+ setWorking(false);
158
+ }
159
+ };
160
+ return (_jsxs(Sheet, { title: self ? t('Clone yourself') : t('Create from a person'), open: true, onOpenChange: (o) => !o && onClose(), children: [_jsx(Field, { label: t('Name'), children: _jsx(Input, { value: name, onChangeText: setName, "aria-label": t('Name') }) }), _jsx(Field, { label: t('Description'), children: _jsx(Textarea, { value: about, onChangeText: setAbout, "aria-label": t('Description'), rows: 3 }) }), _jsx(Field, { label: t('Writing'), children: _jsx(Textarea, { value: writing, onChangeText: setWriting, "aria-label": t('Writing'), rows: 6 }) }), _jsxs(XStack, { children: [_jsxs(Button, { size: "sm", variant: "ghost", onPress: () => file.current?.click(), children: [_jsx(Upload, { size: 14 }), " ", t('Upload')] }), _jsx("input", { ref: file, type: "file", multiple: true, accept: ".txt,.md,.markdown,.csv,.json,.html,text/*", hidden: true, "aria-label": t('Upload writing'), onChange: (e) => {
161
+ void read(e.currentTarget.files);
162
+ e.currentTarget.value = '';
163
+ } })] }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", items: "center", children: [_jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: working || !name.trim(), onPress: () => void save(), children: t('Create') })] })] }));
73
164
  }
74
- function reach(a) {
165
+ function reach(a, t) {
75
166
  if (a.tools.includes(ALL))
76
- return 'every tool';
77
- return a.tools.length === 1 ? '1 tool' : `${a.tools.length} tools`;
167
+ return t('every tool');
168
+ return a.tools.length === 1 ? t('1 tool') : t('{n} tools', { n: a.tools.length });
78
169
  }
79
- /** An agent's face: the cast member's picture, its emoji, or its initial. */
170
+ /** An agent's face: its picture, the default agent's of its name, its emoji, or its initial. */
80
171
  function Face({ agent: a, cast, size = 32 }) {
81
- const avatar = cast.find((c) => c.name.toLowerCase() === a.name.toLowerCase())?.avatar;
172
+ const avatar = a.avatar || cast.find((c) => c.name.toLowerCase() === a.name.toLowerCase())?.avatar;
82
173
  return _jsx(Mark, { name: a.name, size: size, logo: avatar, icon: a.emoji ? _jsx(SizableText, { size: size > 40 ? '$8' : '$4', children: a.emoji }) : undefined });
83
174
  }
84
175
  /** One agent's page: who it is, what it runs on and reads, what it may call and spend, and what it has done. */
85
176
  export function Profile({ name }) {
177
+ const { t } = useI18n();
86
178
  const host = useHost();
87
- const t = useTarget();
88
- const got = useLoad(host.person ? () => profile(t, name) : null, null, [t, name]);
179
+ const target = useTarget();
180
+ const got = useLoad(host.person ? () => profile(target, name) : null, null, [target, name]);
89
181
  const [editing, setEditing] = useState(false);
90
182
  const back = () => host.go(path({ kind: 'customize', tab: 'agents' }));
91
183
  const a = got.value?.agent;
92
- return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: [_jsxs(YStack, { width: "100%", maxW: 880, mx: "auto", gap: "$5", children: [_jsxs(XStack, { render: "button", onPress: back, items: "center", gap: "$1.5", self: "flex-start", cursor: "pointer", "aria-label": "Back to agents", children: [_jsx(ArrowLeft, { size: 14, color: "$soft" }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Agents" })] }), !host.person ? (_jsx(Visitor, { children: "Sign in to see this agent." })) : got.error && !a ? (_jsx(Failed, { error: got.error, onRetry: got.reload })) : !a ? (_jsx(Soft, { children: "Loading the agent\u2026" })) : (_jsxs(_Fragment, { children: [_jsxs(XStack, { items: "center", gap: "$4", children: [_jsx(Face, { agent: a, cast: host.cast ?? [], size: 64 }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsx(SizableText, { render: "h1", size: "$7", color: "$ink", numberOfLines: 1, children: a.name }), _jsx(SizableText, { size: "$3", color: "$soft", children: a.description || 'No role written yet.' })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setEditing(true), children: "Edit" })] }), _jsx(Part, { title: "Model", detail: a.model || 'The deployment’s default' }), _jsx(Part, { title: "Instructions", detail: a.instructions ? '' : 'None written: it runs on its model alone.', children: a.instructions ? (_jsx(YStack, { p: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", maxH: 320, overflow: "scroll", children: _jsx(SizableText, { size: "$2", color: "$ink", style: { whiteSpace: 'pre-wrap' }, children: a.instructions }) })) : null }), _jsx(Part, { title: "Tools", detail: a.tools.includes(ALL) ? 'Every tool the fleet’s MCP server serves.' : a.tools.length ? a.tools.join(', ') : 'None: it calls no tool.' }), _jsx(Part, { title: "Budget", detail: a.cap ? `${money(a.spent)} of ${money(a.cap)} spent this ${a.period || 'period'}; at most ${money(a.task)} a run.` : 'No budget set.' }), _jsx(Part, { title: "Runs", detail: a.runs ? `${a.runs} in all; the latest ${Math.min(a.runs, got.value.runs.length)} below.` : 'It has not run yet.', children: got.value.runs.map((r) => (_jsxs(XStack, { gap: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", items: "flex-start", children: [_jsx(SizableText, { size: "$1", color: r.status === 'ok' ? '$soft' : '$bad', width: 44, shrink: 0, children: r.status === 'ok' ? 'ok' : 'failed' }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: r.input || '(no input)' }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 2, children: r.status === 'ok' ? r.output : r.error })] }), _jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: [r.model, r.at ? new Date(r.at).toLocaleString() : ''].filter(Boolean).join(' · ') })] }, r.id))) })] }))] }), editing && a ? (_jsx(Editor, { agent: a, onClose: () => setEditing(false), onSaved: () => {
184
+ return (_jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$6", py: "$6", "$max-md": { px: '$4', py: '$4' }, children: [_jsxs(YStack, { width: "100%", maxW: 880, mx: "auto", gap: "$5", children: [_jsxs(XStack, { render: "button", onPress: back, items: "center", gap: "$1.5", self: "flex-start", cursor: "pointer", "aria-label": t('Back to agents'), children: [_jsx(ArrowLeft, { size: 14, color: "$soft", 'data-i18n-mirror': '' }), _jsx(SizableText, { size: "$2", color: "$soft", children: t('Agents') })] }), !host.person ? (_jsx(Visitor, { children: t('Sign in to see this agent.') })) : got.error && !a ? (_jsx(Failed, { error: got.error, onRetry: got.reload })) : !a ? (_jsx(Soft, { children: t('Loading the agent…') })) : (_jsxs(_Fragment, { children: [_jsxs(XStack, { items: "center", gap: "$4", children: [_jsx(Face, { agent: a, cast: host.cast ?? [], size: 64 }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsx(SizableText, { render: "h1", size: "$7", color: "$ink", numberOfLines: 1, children: a.name }), _jsx(SizableText, { size: "$3", color: "$soft", children: a.description || t('No role written yet.') })] }), _jsx(Button, { size: "sm", variant: "outline", onPress: () => setEditing(true), children: t('Edit') })] }), _jsx(Part, { title: t('Model'), detail: a.model || t('The deployment’s default') }), _jsx(Part, { title: t('Instructions'), detail: a.instructions ? '' : t('None written: it runs on its model alone.'), children: a.instructions ? (_jsx(YStack, { p: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", maxH: 320, overflow: "scroll", children: _jsx(SizableText, { size: "$2", color: "$ink", style: { whiteSpace: 'pre-wrap' }, children: a.instructions }) })) : null }), _jsx(Part, { title: t('Tools'), detail: a.tools.includes(ALL) ? t('Every tool the fleet’s MCP server serves.') : a.tools.length ? a.tools.join(', ') : t('None: it calls no tool.') }), _jsx(Part, { title: t('Budget'), detail: a.cap ? t(BUDGET[a.period], { spent: money(a.spent), cap: money(a.cap), task: money(a.task) }) : t('No budget set.') }), _jsx(Part, { title: t('Runs'), detail: a.runs ? t('{runs} in all; the latest {n} below.', { runs: a.runs, n: Math.min(a.runs, got.value.runs.length) }) : t('It has not run yet.'), children: got.value.runs.map((r) => (_jsxs(XStack, { gap: "$3", py: "$2", borderTopWidth: 1, borderColor: "$borderColor", items: "flex-start", children: [_jsx(SizableText, { size: "$1", color: r.status === 'ok' ? '$soft' : '$bad', width: 44, shrink: 0, children: r.status === 'ok' ? t('ok') : t('failed') }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: r.input || t('(no input)') }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 2, children: r.status === 'ok' ? r.output : r.error })] }), _jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: [r.model, r.at ? new Date(r.at).toLocaleString() : ''].filter(Boolean).join(' · ') })] }, r.id))) })] }))] }), editing && a ? (_jsx(Editor, { agent: a, onClose: () => setEditing(false), onSaved: () => {
93
185
  setEditing(false);
94
186
  got.reload();
95
187
  }, onDeleted: back })) : null] }));
96
188
  }
97
189
  /** Write a new agent, or change one; an existing agent is read whole first, for its instructions. */
98
- function Editor({ agent, start, onClose, onSaved, onDeleted, }) {
99
- const t = useTarget();
100
- const full = useLoad(agent ? () => one(t, agent.id || agent.name) : null, null, [t, agent?.id]);
101
- const catalog = useLoad(() => models(t), [], [t]);
102
- const on = useLoad(() => tools(t, { activated: true }), [], [t]);
103
- const [d, setD] = useState(start ?? EMPTY);
190
+ function Editor({ agent, onClose, onSaved, onDeleted, }) {
191
+ const { t } = useI18n();
192
+ const target = useTarget();
193
+ const full = useLoad(agent ? () => one(target, agent.id || agent.name) : null, null, [target, agent?.id]);
194
+ const catalog = useLoad(() => models(target), [], [target]);
195
+ const on = useLoad(() => tools(target, { activated: true }), [], [target]);
196
+ const [d, setD] = useState(EMPTY);
104
197
  const [ready, setReady] = useState(!agent);
105
198
  const [working, setWorking] = useState(false);
106
199
  const [note, setNote] = useState('');
@@ -118,28 +211,32 @@ function Editor({ agent, start, onClose, onSaved, onDeleted, }) {
118
211
  const choices = useMemo(() => {
119
212
  const listed = on.value.map((x) => ({ name: x.name, source: x.source, description: x.description }));
120
213
  const seen = new Set(listed.map((x) => x.name));
121
- const kept = d.tools.filter((n) => n !== ALL && !seen.has(n)).map((name) => ({ name, source: 'not on', description: '' }));
214
+ const kept = d.tools.filter((n) => n !== ALL && !seen.has(n)).map((name) => ({ name, source: t('not on'), description: '' }));
122
215
  return [...kept, ...listed];
123
- }, [on.value, d.tools]);
216
+ }, [on.value, d.tools, t]);
124
217
  const shownTools = choices.filter((x) => matches(find, x.name, x.source, x.description));
125
218
  const save = async () => {
126
219
  setWorking(true);
127
220
  setNote('');
128
221
  try {
129
222
  const was = full.value;
130
- const saved = agent && was ? await update(t, was, d) : await create(t, d);
223
+ const saved = agent && was ? await update(target, was, d) : await create(target, d);
131
224
  onSaved(saved);
132
225
  }
133
226
  catch (e) {
134
- setNote(say(e));
227
+ setNote(say(e, t));
135
228
  }
136
229
  finally {
137
230
  setWorking(false);
138
231
  }
139
232
  };
140
233
  const pick = (name, yes) => set({ tools: yes ? [...d.tools, name] : d.tools.filter((n) => n !== name) });
141
- return (_jsx(Sheet, { title: agent ? agent.name : 'New agent', open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: agent && full.error && !full.value ? (_jsx(Failed, { error: full.error, onRetry: full.reload })) : !ready ? (_jsx(Soft, { children: "Loading the agent\u2026" })) : (_jsxs(_Fragment, { children: [_jsx(Field, { label: "Name", hint: agent ? 'An agent keeps its name.' : 'Letters, digits, . _ or -. Other agents call it as agent_<name>.', children: _jsx(Input, { value: d.name, onChangeText: (v) => set({ name: v }), "aria-label": "Name", disabled: Boolean(agent), autoCapitalize: "none" }) }), _jsx(Field, { label: "Description", hint: "The line another agent reads to decide whether to call this one.", children: _jsx(Input, { value: d.description, onChangeText: (v) => set({ description: v }), placeholder: "Answers questions about our codebase", "aria-label": "Description" }) }), _jsx(Field, { label: "Model", children: _jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(ModelPicker, { size: "sm", name: "Model", placeholder: "The deployment\u2019s default", models: catalog.value, scope: "chat", value: d.model || undefined, onChange: (id) => set({ model: id }), loading: catalog.loading, error: catalog.error ? say(catalog.error) : null }), d.model && !agent ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => set({ model: '' }), children: "Use the default" })) : null] }) }), _jsx(Field, { label: "Instructions", hint: "The system prompt: what the model reads before every run.", children: _jsx(Textarea, { value: d.instructions, onChangeText: (v) => set({ instructions: v }), placeholder: "Be terse, and cite the file you read.", "aria-label": "Instructions", rows: 8 }) }), _jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", gap: "$3", children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", children: "Tools" }), _jsx(SizableText, { size: "$1", color: "$soft", children: every ? 'Every tool the fleet’s MCP server serves when it runs.' : d.tools.length ? `${d.tools.length} chosen. It can call only these.` : 'None chosen: it can call no tool.' })] }), _jsx(SizableText, { size: "$1", color: "$soft", children: "Every tool" }), _jsx(Switch, { checked: every, onCheckedChange: (v) => set({ tools: v ? [ALL] : [] }), "aria-label": "Every tool" })] }), every ? null : (_jsxs(_Fragment, { children: [_jsx(Input, { value: find, onChangeText: setFind, placeholder: "Find a tool\u2026", "aria-label": "Find a tool" }), _jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", maxH: 240, overflow: "scroll", children: on.loading && !on.value.length ? (_jsx(Soft, { children: "Loading the tools that are on\u2026" })) : on.error && !on.value.length ? (_jsx(Failed, { error: on.error, onRetry: on.reload })) : !shownTools.length ? (_jsx(Soft, { children: choices.length ? 'No tool matches.' : 'No tool is on for your organization yet. Add skills or connectors first.' })) : (shownTools.map((x, i) => (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", style: mono, numberOfLines: 1, children: x.name }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [x.source, x.description].filter(Boolean).join(' · ') })] }), _jsx(Switch, { checked: d.tools.includes(x.name), onCheckedChange: (v) => pick(x.name, v), "aria-label": `Use ${x.name}` })] }, x.name)))) })] }))] }), _jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: "Budget" }), _jsxs(XStack, { gap: "$3", flexWrap: "wrap", rowGap: "$2", items: "flex-end", children: [_jsxs(YStack, { gap: "$1", width: 140, children: [_jsxs(SizableText, { size: "$1", color: "$soft", children: ["Each ", d.period, ", USD"] }), _jsx(Input, { value: d.cap, onChangeText: (v) => set({ cap: v }), placeholder: "10", "aria-label": "Budget each period", inputMode: "decimal" })] }), _jsxs(YStack, { gap: "$1", width: 140, children: [_jsx(SizableText, { size: "$1", color: "$soft", children: "One run, USD" }), _jsx(Input, { value: d.task, onChangeText: (v) => set({ task: v }), placeholder: "1", "aria-label": "Budget for one run", inputMode: "decimal" })] }), _jsx(Choice, { label: "Period", value: d.period, options: PERIODS.map((p) => ({ id: p, label: p[0].toUpperCase() + p.slice(1) })), onChange: (p) => set({ period: p }) })] }), _jsxs(SizableText, { size: "$1", color: "$soft", children: [full.value && full.value.cap ? `Spent ${money(full.value.spent)} this ${full.value.period}. ` : '', "A run stops at either limit."] })] }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", items: "center", children: [agent && onDeleted ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: "Delete" })) : null, _jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working, onPress: () => void save(), children: agent ? 'Save' : 'Create' })] }), agent && onDeleted ? (_jsx(Confirm, { what: agent.name, says: "The agent and every run recorded against it are removed.", open: asking, onOpenChange: setAsking, onYes: async () => {
142
- await remove(t, agent.id || agent.name);
234
+ return (_jsx(Sheet, { title: agent ? agent.name : t('New agent'), open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: agent && full.error && !full.value ? (_jsx(Failed, { error: full.error, onRetry: full.reload })) : !ready ? (_jsx(Soft, { children: t('Loading the agent…') })) : (_jsxs(_Fragment, { children: [_jsx(Field, { label: t('Name'), hint: agent ? t('An agent keeps its name.') : t('Letters, digits, . _ or -. Other agents call it as agent_<name>.'), children: _jsx(Input, { value: d.name, onChangeText: (v) => set({ name: v }), "aria-label": t('Name'), disabled: Boolean(agent), autoCapitalize: "none" }) }), _jsx(Field, { label: t('Description'), hint: t('The line another agent reads to decide whether to call this one.'), children: _jsx(Input, { value: d.description, onChangeText: (v) => set({ description: v }), placeholder: t('Answers questions about our codebase'), "aria-label": t('Description') }) }), _jsx(Field, { label: t('Model'), children: _jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsx(ModelPicker, { size: "sm", name: t('Model'), placeholder: t('The deployment’s default'), models: catalog.value, scope: "chat", value: d.model || undefined, onChange: (id) => set({ model: id }), loading: catalog.loading, error: catalog.error ? say(catalog.error, t) : null }), d.model && !agent ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => set({ model: '' }), children: t('Use the default') })) : null] }) }), _jsx(Field, { label: t('Instructions'), hint: t('The system prompt: what the model reads before every run.'), children: _jsx(Textarea, { value: d.instructions, onChangeText: (v) => set({ instructions: v }), placeholder: t('Be terse, and cite the file you read.'), "aria-label": t('Instructions'), rows: 8 }) }), _jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", gap: "$3", children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", children: t('Tools') }), _jsx(SizableText, { size: "$1", color: "$soft", children: every
235
+ ? t('Every tool the fleet’s MCP server serves when it runs.')
236
+ : d.tools.length
237
+ ? t('{n} chosen. It can call only these.', { n: d.tools.length })
238
+ : t('None chosen: it can call no tool.') })] }), _jsx(SizableText, { size: "$1", color: "$soft", children: t('Every tool') }), _jsx(Switch, { checked: every, onCheckedChange: (v) => set({ tools: v ? [ALL] : [] }), "aria-label": t('Every tool') })] }), every ? null : (_jsxs(_Fragment, { children: [_jsx(Input, { value: find, onChangeText: setFind, placeholder: t('Find a tool…'), "aria-label": t('Find a tool') }), _jsx(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", maxH: 240, overflow: "scroll", children: on.loading && !on.value.length ? (_jsx(Soft, { children: t('Loading the tools that are on…') })) : on.error && !on.value.length ? (_jsx(Failed, { error: on.error, onRetry: on.reload })) : !shownTools.length ? (_jsx(Soft, { children: choices.length ? t('No tool matches.') : t('No tool is on for your organization yet. Add skills or connectors first.') })) : (shownTools.map((x, i) => (_jsxs(XStack, { items: "center", gap: "$3", px: "$3", py: "$2", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", style: mono, numberOfLines: 1, children: x.name }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [x.source, x.description].filter(Boolean).join(' · ') })] }), _jsx(Switch, { checked: d.tools.includes(x.name), onCheckedChange: (v) => pick(x.name, v), "aria-label": t('Use {name}', { name: x.name }) })] }, x.name)))) })] }))] }), _jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: t('Budget') }), _jsxs(XStack, { gap: "$3", flexWrap: "wrap", rowGap: "$2", items: "flex-end", children: [_jsxs(YStack, { gap: "$1", width: 140, children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t(EACH[d.period]) }), _jsx(Input, { value: d.cap, onChangeText: (v) => set({ cap: v }), placeholder: "10", "aria-label": t('Budget each period'), inputMode: "decimal" })] }), _jsxs(YStack, { gap: "$1", width: 140, children: [_jsx(SizableText, { size: "$1", color: "$soft", children: t('One run, USD') }), _jsx(Input, { value: d.task, onChangeText: (v) => set({ task: v }), placeholder: "1", "aria-label": t('Budget for one run'), inputMode: "decimal" })] }), _jsx(Choice, { label: t('Period'), value: d.period, options: PERIODS.map((p) => ({ id: p, label: t(PERIOD[p]) })), onChange: (p) => set({ period: p }) })] }), _jsx(SizableText, { size: "$1", color: "$soft", children: [full.value && full.value.cap ? t(SO_FAR[full.value.period], { spent: money(full.value.spent) }) : '', t('A run stops at either limit.')].filter(Boolean).join(' ') })] }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", items: "center", children: [agent && onDeleted ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => setAsking(true), children: t('Delete') })) : null, _jsx(XStack, { flex: 1 }), _jsx(Button, { size: "sm", variant: "ghost", onPress: onClose, children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: working, onPress: () => void save(), children: agent ? t('Save') : t('Create') })] }), agent && onDeleted ? (_jsx(Confirm, { what: agent.name, says: t('The agent and every run recorded against it are removed.'), open: asking, onOpenChange: setAsking, onYes: async () => {
239
+ await remove(target, agent.id || agent.name);
143
240
  onDeleted();
144
241
  } })) : null] })) }));
145
242
  }