@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.
- package/README.md +22 -4
- package/lib/activity.d.ts +3 -7
- package/lib/activity.js +18 -5
- package/lib/api/agents.d.ts +36 -35
- package/lib/api/agents.js +66 -29
- package/lib/api/auto.d.ts +3 -23
- package/lib/api/auto.js +39 -22
- package/lib/api/billing.d.ts +0 -35
- package/lib/api/billing.js +6 -5
- package/lib/api/call.d.ts +16 -8
- package/lib/api/call.js +32 -1
- package/lib/api/capabilities.d.ts +0 -11
- package/lib/api/capabilities.js +2 -1
- package/lib/api/codebases.d.ts +0 -10
- package/lib/api/codebases.js +3 -2
- package/lib/api/coding.d.ts +3 -32
- package/lib/api/coding.js +13 -7
- package/lib/api/connectors.d.ts +1 -17
- package/lib/api/connectors.js +5 -4
- package/lib/api/environment.d.ts +6 -20
- package/lib/api/environment.js +22 -19
- package/lib/api/github.d.ts +0 -22
- package/lib/api/github.js +3 -2
- package/lib/api/keys.d.ts +0 -14
- package/lib/api/keys.js +4 -3
- package/lib/api/library.d.ts +3 -17
- package/lib/api/library.js +23 -22
- package/lib/api/machines.d.ts +0 -15
- package/lib/api/machines.js +6 -5
- package/lib/api/mcp.d.ts +0 -9
- package/lib/api/mcp.js +3 -2
- package/lib/api/members.d.ts +0 -15
- package/lib/api/members.js +4 -3
- package/lib/api/memory.d.ts +0 -11
- package/lib/api/memory.js +2 -1
- package/lib/api/places.d.ts +1 -9
- package/lib/api/places.js +12 -1
- package/lib/api/plugins.d.ts +1 -16
- package/lib/api/plugins.js +4 -3
- package/lib/api/profile.d.ts +0 -10
- package/lib/api/profile.js +5 -4
- package/lib/api/projects.d.ts +0 -14
- package/lib/api/projects.js +2 -1
- package/lib/api/provider.d.ts +2 -14
- package/lib/api/provider.js +12 -10
- package/lib/api/sandbox.d.ts +0 -17
- package/lib/api/sandbox.js +5 -4
- package/lib/api/sessions.d.ts +0 -17
- package/lib/api/sessions.js +5 -4
- package/lib/api/skills.d.ts +1 -15
- package/lib/api/skills.js +5 -4
- package/lib/api/studio.d.ts +0 -14
- package/lib/api/studio.js +6 -5
- package/lib/api/turn.d.ts +6 -28
- package/lib/api/turn.js +105 -49
- package/lib/api/verdict.d.ts +0 -9
- package/lib/api/verdict.js +2 -1
- package/lib/api/webhooks.d.ts +1 -14
- package/lib/api/webhooks.js +5 -4
- package/lib/ask.js +7 -4
- package/lib/automations.d.ts +5 -4
- package/lib/automations.js +95 -74
- package/lib/back.js +10 -4
- package/lib/builder.js +7 -5
- package/lib/carry.js +20 -16
- package/lib/customize/agents.d.ts +3 -0
- package/lib/customize/agents.js +152 -55
- package/lib/customize/connectors.js +62 -48
- package/lib/customize/index.js +45 -14
- package/lib/customize/plugins.js +34 -22
- package/lib/customize/say.d.ts +13 -1
- package/lib/customize/say.js +15 -21
- package/lib/customize/skills.js +31 -22
- package/lib/customize/ui.d.ts +5 -9
- package/lib/customize/ui.js +26 -16
- package/lib/data.d.ts +1 -1
- package/lib/data.js +3 -2
- package/lib/desk.js +45 -33
- package/lib/door.js +35 -10
- package/lib/environment.js +25 -19
- package/lib/files.js +43 -34
- package/lib/find.js +12 -9
- package/lib/foot.js +7 -4
- package/lib/forge.js +61 -40
- package/lib/gear.d.ts +2 -1
- package/lib/gear.js +6 -4
- package/lib/git.js +29 -19
- package/lib/host.d.ts +3 -1
- package/lib/landing.d.ts +5 -5
- package/lib/landing.js +52 -44
- package/lib/library.js +73 -51
- package/lib/meter.d.ts +1 -1
- package/lib/meter.js +14 -5
- package/lib/mic.js +12 -5
- package/lib/mind.d.ts +1 -0
- package/lib/mind.js +5 -3
- package/lib/nav.d.ts +6 -5
- package/lib/nav.js +37 -30
- package/lib/panel.d.ts +2 -1
- package/lib/panel.js +52 -36
- package/lib/plan.d.ts +4 -20
- package/lib/plan.js +26 -10
- package/lib/plans.js +49 -30
- package/lib/plugins/design.js +3 -2
- package/lib/plugins/index.d.ts +23 -4
- package/lib/plugins/index.js +11 -26
- package/lib/plugins/pdf.js +2 -1
- package/lib/plugins/project.js +6 -3
- package/lib/plugins/research.js +3 -2
- package/lib/plugins/sketch.js +6 -3
- package/lib/plugins/slides.js +3 -2
- package/lib/plugins/template.js +6 -3
- package/lib/plus.js +25 -13
- package/lib/prefs.d.ts +0 -14
- package/lib/prefs.js +2 -1
- package/lib/project.js +69 -62
- package/lib/prompt.d.ts +12 -11
- package/lib/prompt.js +38 -20
- package/lib/publish.js +17 -12
- package/lib/reply.js +6 -3
- package/lib/run.js +72 -54
- package/lib/say.d.ts +2 -0
- package/lib/say.js +7 -0
- package/lib/scheduled.js +54 -43
- package/lib/section.d.ts +1 -0
- package/lib/section.js +8 -2
- package/lib/settings/account.d.ts +1 -0
- package/lib/settings/account.js +31 -21
- package/lib/settings/billing.js +55 -28
- package/lib/settings/capabilities.js +26 -15
- package/lib/settings/card.js +19 -12
- package/lib/settings/code.js +22 -11
- package/lib/settings/credits.d.ts +4 -4
- package/lib/settings/credits.js +34 -21
- package/lib/settings/environments.js +21 -14
- package/lib/settings/general.js +26 -14
- package/lib/settings/index.js +6 -3
- package/lib/settings/integrations.js +42 -28
- package/lib/settings/keys.js +37 -19
- package/lib/settings/list.d.ts +0 -5
- package/lib/settings/list.js +24 -15
- package/lib/settings/machines.d.ts +3 -0
- package/lib/settings/machines.js +63 -38
- package/lib/settings/members.js +23 -14
- package/lib/settings/memory.js +12 -6
- package/lib/settings/notifications.js +32 -19
- package/lib/settings/plan.d.ts +1 -1
- package/lib/settings/plan.js +40 -17
- package/lib/settings/privacy.js +26 -13
- package/lib/settings/ui.d.ts +3 -2
- package/lib/settings/ui.js +9 -3
- package/lib/settings/usage.js +7 -3
- package/lib/shelf.js +18 -15
- package/lib/side.d.ts +1 -26
- package/lib/side.js +11 -5
- package/lib/standing.d.ts +2 -10
- package/lib/standing.js +5 -3
- package/lib/suggest.d.ts +8 -28
- package/lib/suggest.js +39 -4
- package/lib/suggestions.js +14 -5
- package/lib/summary.d.ts +1 -1
- package/lib/summary.js +4 -1
- package/lib/sync.js +22 -12
- package/lib/tools.d.ts +4 -3
- package/lib/tools.js +10 -7
- package/lib/transcript.js +10 -8
- 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
|
-
|
|
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
|
-
|
|
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 }: {
|
package/lib/customize/agents.js
CHANGED
|
@@ -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
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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 {
|
|
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,
|
|
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
|
|
80
|
+
const target = useTarget();
|
|
34
81
|
const signed = Boolean(host.person);
|
|
35
|
-
const
|
|
36
|
-
const
|
|
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 [
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
58
|
-
|
|
107
|
+
const editor = adding ? (_jsx(Editor, { onClose: () => onAdding(false), onSaved: (a) => {
|
|
108
|
+
onAdding(false);
|
|
59
109
|
onView('yours');
|
|
60
110
|
mine.reload();
|
|
61
|
-
setNote(
|
|
111
|
+
setNote(t('{name} is saved', { name: a.name }));
|
|
62
112
|
} })) : null;
|
|
63
|
-
if (
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
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:
|
|
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:
|
|
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' :
|
|
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
|
|
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
|
|
88
|
-
const got = useLoad(host.person ? () => profile(
|
|
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":
|
|
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,
|
|
99
|
-
const t =
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
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(
|
|
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:
|
|
142
|
-
|
|
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
|
}
|